//===- RegionTest.cpp -----------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "llvm/SandboxIR/Region.h" #include "llvm/Analysis/TargetTransformInfo.h" #include "llvm/AsmParser/Parser.h" #include "llvm/SandboxIR/Context.h" #include "llvm/SandboxIR/Function.h" #include "llvm/SandboxIR/Instruction.h" #include "llvm/Support/SourceMgr.h" #include "gmock/gmock-matchers.h" #include "gtest/gtest.h" using namespace llvm; struct RegionTest : public testing::Test { LLVMContext C; std::unique_ptr M; std::unique_ptr TTI; void parseIR(LLVMContext &C, const char *IR) { SMDiagnostic Err; M = parseAssemblyString(IR, Err, C); TTI = std::make_unique(M->getDataLayout()); if (!M) Err.print("RegionTest", errs()); } }; TEST_F(RegionTest, Basic) { parseIR(C, R"IR( define i8 @foo(i8 %v0, i8 %v1) { %t0 = add i8 %v0, 1 %t1 = add i8 %t0, %v1 ret i8 %t1 } )IR"); llvm::Function *LLVMF = &*M->getFunction("foo"); sandboxir::Context Ctx(C); auto *F = Ctx.createFunction(LLVMF); auto *BB = &*F->begin(); auto It = BB->begin(); auto *T0 = cast(&*It++); auto *T1 = cast(&*It++); auto *Ret = cast(&*It++); sandboxir::Region Rgn(Ctx, *TTI); // Check getContext. EXPECT_EQ(&Ctx, &Rgn.getContext()); // Check add / remove / empty. EXPECT_TRUE(Rgn.empty()); sandboxir::RegionInternalsAttorney::add(Rgn, T0); EXPECT_FALSE(Rgn.empty()); sandboxir::RegionInternalsAttorney::remove(Rgn, T0); EXPECT_TRUE(Rgn.empty()); // Check iteration. sandboxir::RegionInternalsAttorney::add(Rgn, T0); sandboxir::RegionInternalsAttorney::add(Rgn, T1); sandboxir::RegionInternalsAttorney::add(Rgn, Ret); // Use an ordered matcher because we're supposed to preserve the insertion // order for determinism. EXPECT_THAT(Rgn.insts(), testing::ElementsAre(T0, T1, Ret)); // Check contains EXPECT_TRUE(Rgn.contains(T0)); sandboxir::RegionInternalsAttorney::remove(Rgn, T0); EXPECT_FALSE(Rgn.contains(T0)); #ifndef NDEBUG // Check equality comparison. Insert in reverse order into `Other` to check // that comparison is order-independent. sandboxir::Region Other(Ctx, *TTI); sandboxir::RegionInternalsAttorney::add(Other, Ret); EXPECT_NE(Rgn, Other); sandboxir::RegionInternalsAttorney::add(Other, T1); EXPECT_EQ(Rgn, Other); #endif } TEST_F(RegionTest, CallbackUpdates) { parseIR(C, R"IR( define i8 @foo(i8 %v0, i8 %v1, ptr %ptr) { %t0 = add i8 %v0, 1 %t1 = add i8 %t0, %v1 ret i8 %t0 } )IR"); llvm::Function *LLVMF = &*M->getFunction("foo"); sandboxir::Context Ctx(C); auto *F = Ctx.createFunction(LLVMF); auto *Ptr = F->getArg(2); auto *BB = &*F->begin(); auto It = BB->begin(); auto *T0 = cast(&*It++); auto *T1 = cast(&*It++); auto *Ret = cast(&*It++); sandboxir::Region Rgn(Ctx, *TTI); sandboxir::RegionInternalsAttorney::add(Rgn, T0); sandboxir::RegionInternalsAttorney::add(Rgn, T1); // Test creation. auto *NewI = sandboxir::StoreInst::create(T0, Ptr, /*Align=*/std::nullopt, Ret->getIterator(), Ctx); EXPECT_THAT(Rgn.insts(), testing::ElementsAre(T0, T1, NewI)); // Test deletion. T1->eraseFromParent(); EXPECT_THAT(Rgn.insts(), testing::ElementsAre(T0, NewI)); } TEST_F(RegionTest, MetadataFromIR) { parseIR(C, R"IR( define i8 @foo(i8 %v0, i8 %v1) { %t0 = add i8 %v0, 1, !sandboxvec !0 %t1 = add i8 %t0, %v1, !sandboxvec !1 %t2 = add i8 %t1, %v1, !sandboxvec !1 ret i8 %t2 } !0 = distinct !{!"sandboxregion"} !1 = distinct !{!"sandboxregion"} )IR"); llvm::Function *LLVMF = &*M->getFunction("foo"); sandboxir::Context Ctx(C); auto *F = Ctx.createFunction(LLVMF); auto *BB = &*F->begin(); auto It = BB->begin(); auto *T0 = cast(&*It++); auto *T1 = cast(&*It++); auto *T2 = cast(&*It++); SmallVector> Regions = sandboxir::Region::createRegionsFromMD(*F, *TTI); EXPECT_THAT(Regions[0]->insts(), testing::UnorderedElementsAre(T0)); EXPECT_THAT(Regions[1]->insts(), testing::UnorderedElementsAre(T1, T2)); } TEST_F(RegionTest, NonContiguousRegion) { parseIR(C, R"IR( define i8 @foo(i8 %v0, i8 %v1) { %t0 = add i8 %v0, 1, !sandboxvec !0 %t1 = add i8 %t0, %v1 %t2 = add i8 %t1, %v1, !sandboxvec !0 ret i8 %t2 } !0 = distinct !{!"sandboxregion"} )IR"); llvm::Function *LLVMF = &*M->getFunction("foo"); sandboxir::Context Ctx(C); auto *F = Ctx.createFunction(LLVMF); auto *BB = &*F->begin(); auto It = BB->begin(); auto *T0 = cast(&*It++); [[maybe_unused]] auto *T1 = cast(&*It++); auto *T2 = cast(&*It++); SmallVector> Regions = sandboxir::Region::createRegionsFromMD(*F, *TTI); EXPECT_THAT(Regions[0]->insts(), testing::UnorderedElementsAre(T0, T2)); } TEST_F(RegionTest, DumpedMetadata) { parseIR(C, R"IR( define i8 @foo(i8 %v0, i8 %v1) { %t0 = add i8 %v0, 1 %t1 = add i8 %t0, %v1 %t2 = add i8 %t1, %v1 ret i8 %t1 } )IR"); llvm::Function *LLVMF = &*M->getFunction("foo"); sandboxir::Context Ctx(C); auto *F = Ctx.createFunction(LLVMF); auto *BB = &*F->begin(); auto It = BB->begin(); auto *T0 = cast(&*It++); [[maybe_unused]] auto *T1 = cast(&*It++); auto *T2 = cast(&*It++); [[maybe_unused]] auto *Ret = cast(&*It++); sandboxir::Region Rgn(Ctx, *TTI); sandboxir::RegionInternalsAttorney::add(Rgn, T0); sandboxir::Region Rgn2(Ctx, *TTI); sandboxir::RegionInternalsAttorney::add(Rgn2, T2); std::string output; llvm::raw_string_ostream RSO(output); M->print(RSO, nullptr, /*ShouldPreserveUseListOrder=*/true, /*IsForDebug=*/true); // TODO: Replace this with a lit test, which is more suitable for this kind // of IR comparison. std::string expected = R"(; ModuleID = '' source_filename = "" define i8 @foo(i8 %v0, i8 %v1) { %t0 = add i8 %v0, 1, !sandboxvec !0 %t1 = add i8 %t0, %v1 %t2 = add i8 %t1, %v1, !sandboxvec !1 ret i8 %t1 } !0 = distinct !{!"sandboxregion"} !1 = distinct !{!"sandboxregion"} )"; EXPECT_EQ(expected, output); } TEST_F(RegionTest, MetadataRoundTrip) { parseIR(C, R"IR( define i8 @foo(i8 %v0, i8 %v1) { %t0 = add i8 %v0, 1 %t1 = add i8 %t0, %v1 ret i8 %t1 } )IR"); llvm::Function *LLVMF = &*M->getFunction("foo"); sandboxir::Context Ctx(C); auto *F = Ctx.createFunction(LLVMF); auto *BB = &*F->begin(); auto It = BB->begin(); auto *T0 = cast(&*It++); auto *T1 = cast(&*It++); sandboxir::Region Rgn(Ctx, *TTI); sandboxir::RegionInternalsAttorney::add(Rgn, T0); sandboxir::RegionInternalsAttorney::add(Rgn, T1); SmallVector> Regions = sandboxir::Region::createRegionsFromMD(*F, *TTI); ASSERT_EQ(1U, Regions.size()); #ifndef NDEBUG EXPECT_EQ(Rgn, *Regions[0].get()); #endif } TEST_F(RegionTest, RegionCost) { parseIR(C, R"IR( define void @foo(i8 %v0, i8 %v1, i8 %v2) { %add0 = add i8 %v0, 1 %add1 = add i8 %v1, 2 %add2 = add i8 %v2, 3 ret void } )IR"); llvm::Function *LLVMF = &*M->getFunction("foo"); auto *LLVMBB = &*LLVMF->begin(); auto LLVMIt = LLVMBB->begin(); auto *LLVMAdd0 = &*LLVMIt++; auto *LLVMAdd1 = &*LLVMIt++; auto *LLVMAdd2 = &*LLVMIt++; sandboxir::Context Ctx(C); auto *F = Ctx.createFunction(LLVMF); auto *BB = &*F->begin(); auto It = BB->begin(); auto *Add0 = cast(&*It++); auto *Add1 = cast(&*It++); auto *Add2 = cast(&*It++); sandboxir::Region Rgn(Ctx, *TTI); const auto &SB = Rgn.getScoreboard(); EXPECT_EQ(SB.getAfterCost(), 0); EXPECT_EQ(SB.getBeforeCost(), 0); auto GetCost = [this](llvm::Instruction *LLVMI) { constexpr static TTI::TargetCostKind CostKind = TTI::TCK_RecipThroughput; SmallVector Operands(LLVMI->operands()); return TTI->getInstructionCost(LLVMI, Operands, CostKind); }; // Add `Add0` to the region, should be counted in "After". sandboxir::RegionInternalsAttorney::add(Rgn, Add0); EXPECT_EQ(SB.getBeforeCost(), 0); EXPECT_EQ(SB.getAfterCost(), GetCost(LLVMAdd0)); // Same for `Add1`. sandboxir::RegionInternalsAttorney::add(Rgn, Add1); EXPECT_EQ(SB.getBeforeCost(), 0); EXPECT_EQ(SB.getAfterCost(), GetCost(LLVMAdd0) + GetCost(LLVMAdd1)); // Remove `Add0`, should be subtracted from "After". sandboxir::RegionInternalsAttorney::remove(Rgn, Add0); EXPECT_EQ(SB.getBeforeCost(), 0); EXPECT_EQ(SB.getAfterCost(), GetCost(LLVMAdd1)); // Remove `Add2` which was never in the region, should counted in "Before". sandboxir::RegionInternalsAttorney::remove(Rgn, Add2); EXPECT_EQ(SB.getBeforeCost(), GetCost(LLVMAdd2)); EXPECT_EQ(SB.getAfterCost(), GetCost(LLVMAdd1)); } TEST_F(RegionTest, Aux) { parseIR(C, R"IR( define void @foo(i8 %v) { %t0 = add i8 %v, 0, !sandboxvec !0, !sandboxaux !2 %t1 = add i8 %v, 1, !sandboxvec !0, !sandboxaux !3 %t2 = add i8 %v, 2, !sandboxvec !1 %t3 = add i8 %v, 3, !sandboxvec !1, !sandboxaux !2 %t4 = add i8 %v, 4, !sandboxvec !1, !sandboxaux !4 %t5 = add i8 %v, 5, !sandboxvec !1, !sandboxaux !3 ret void } !0 = distinct !{!"sandboxregion"} !1 = distinct !{!"sandboxregion"} !2 = !{i32 0} !3 = !{i32 1} !4 = !{i32 2} )IR"); llvm::Function *LLVMF = &*M->getFunction("foo"); auto *LLVMBB = &*LLVMF->begin(); auto LLVMIt = LLVMBB->begin(); auto *LLVMI0 = &*LLVMIt++; auto *LLVMI1 = &*LLVMIt++; sandboxir::Context Ctx(C); auto *F = Ctx.createFunction(LLVMF); auto *BB = &*F->begin(); auto It = BB->begin(); auto *T0 = cast(&*It++); auto *T1 = cast(&*It++); auto *T2 = cast(&*It++); auto *T3 = cast(&*It++); auto *T4 = cast(&*It++); auto *T5 = cast(&*It++); SmallVector> Regions = sandboxir::Region::createRegionsFromMD(*F, *TTI); // Check that the regions are correct. EXPECT_THAT(Regions[0]->insts(), testing::UnorderedElementsAre(T0, T1)); EXPECT_THAT(Regions[1]->insts(), testing::UnorderedElementsAre(T2, T3, T4, T5)); // Check aux. EXPECT_THAT(Regions[0]->getAux(), testing::ElementsAre(T0, T1)); EXPECT_THAT(Regions[1]->getAux(), testing::ElementsAre(T3, T5, T4)); // Check clearAux(). EXPECT_TRUE(LLVMI0->getMetadata("sandboxaux")); EXPECT_TRUE(LLVMI1->getMetadata("sandboxaux")); Regions[0]->clearAux(); EXPECT_TRUE(Regions[0]->getAux().empty()); EXPECT_FALSE(LLVMI0->getMetadata("sandboxaux")); EXPECT_FALSE(LLVMI1->getMetadata("sandboxaux")); } // Check that Aux is well-formed. TEST_F(RegionTest, AuxVerify) { parseIR(C, R"IR( define void @foo(i8 %v) { %t0 = add i8 %v, 0, !sandboxvec !0, !sandboxaux !2 %t1 = add i8 %v, 1, !sandboxvec !0, !sandboxaux !3 ret void } !0 = distinct !{!"sandboxregion"} !2 = !{i32 0} !3 = !{i32 2} )IR"); llvm::Function *LLVMF = &*M->getFunction("foo"); sandboxir::Context Ctx(C); auto *F = Ctx.createFunction(LLVMF); EXPECT_DEBUG_DEATH(sandboxir::Region::createRegionsFromMD(*F, *TTI), ".*Gap*"); } // Check that we get an assertion failure if we try to set the same index more // than once. TEST_F(RegionTest, AuxSameIndex) { parseIR(C, R"IR( define void @foo(i8 %v) { %t0 = add i8 %v, 0, !sandboxvec !0, !sandboxaux !2 %t1 = add i8 %v, 1, !sandboxvec !0, !sandboxaux !2 ret void } !0 = distinct !{!"sandboxregion"} !2 = !{i32 0} )IR"); llvm::Function *LLVMF = &*M->getFunction("foo"); sandboxir::Context Ctx(C); auto *F = Ctx.createFunction(LLVMF); EXPECT_DEBUG_DEATH(sandboxir::Region::createRegionsFromMD(*F, *TTI), ".*already.*"); } // Check that Aux automatically drops instructions that get deleted. TEST_F(RegionTest, AuxDeleteInstr) { parseIR(C, R"IR( define void @foo(i8 %v) { %Add0 = add i8 %v, 0, !sandboxvec !0, !sandboxaux !1 %Add1 = add i8 %v, 1, !sandboxvec !0, !sandboxaux !2 %Add2 = add i8 %v, 2, !sandboxvec !0, !sandboxaux !3 %Add3 = add i8 %v, 2, !sandboxvec !0, !sandboxaux !4 ret void } !0 = distinct !{!"sandboxregion"} !1 = !{i32 0} !2 = !{i32 1} !3 = !{i32 2} !4 = !{i32 3} )IR"); llvm::Function *LLVMF = &*M->getFunction("foo"); sandboxir::Context Ctx(C); auto *F = Ctx.createFunction(LLVMF); auto *BB = &*F->begin(); auto It = BB->begin(); auto *Add0 = &*It++; auto *Add1 = &*It++; auto *Add2 = &*It++; auto *Add3 = &*It++; SmallVector> Regions = sandboxir::Region::createRegionsFromMD(*F, *TTI); auto &R = *Regions[0]; EXPECT_THAT(R.getAux(), testing::ElementsAre(Add0, Add1, Add2, Add3)); // Now delete Add1 and check that Aux contains nullptr instead of Add1. Add2->eraseFromParent(); EXPECT_THAT(R.getAux(), testing::ElementsAre(Add0, Add1, Add3)); { // Check that metadata have also been updated. // But first drop Add3 to create a legal Aux vector with no gaps. Add3->eraseFromParent(); SmallVector> Regions = sandboxir::Region::createRegionsFromMD(*F, *TTI); EXPECT_THAT(Regions[0]->getAux(), testing::ElementsAre(Add0, Add1)); } } TEST_F(RegionTest, AuxWithoutRegion) { parseIR(C, R"IR( define void @foo(i8 %v) { %Add0 = add i8 %v, 0, !sandboxaux !0 ret void } !0 = !{i32 0} )IR"); #ifndef NDEBUG llvm::Function *LLVMF = &*M->getFunction("foo"); sandboxir::Context Ctx(C); auto *F = Ctx.createFunction(LLVMF); EXPECT_DEATH(sandboxir::Region::createRegionsFromMD(*F, *TTI), "No region.*"); #endif } TEST_F(RegionTest, AuxRoundTrip) { parseIR(C, R"IR( define i8 @foo(i8 %v0, i8 %v1) { %t0 = add i8 %v0, 1 %t1 = add i8 %t0, %v1 ret i8 %t1 } )IR"); llvm::Function *LLVMF = &*M->getFunction("foo"); sandboxir::Context Ctx(C); auto *F = Ctx.createFunction(LLVMF); auto *BB = &*F->begin(); auto It = BB->begin(); auto *T0 = cast(&*It++); auto *T1 = cast(&*It++); sandboxir::Region Rgn(Ctx, *TTI); #ifndef NDEBUG EXPECT_DEATH(Rgn.setAux({T0, T0}), ".*already.*"); #endif sandboxir::RegionInternalsAttorney::add(Rgn, T0); sandboxir::RegionInternalsAttorney::add(Rgn, T1); Rgn.setAux({T1, T0}); SmallVector> Regions = sandboxir::Region::createRegionsFromMD(*F, *TTI); ASSERT_EQ(1U, Regions.size()); #ifndef NDEBUG EXPECT_EQ(Rgn, *Regions[0].get()); #endif EXPECT_THAT(Rgn.getAux(), testing::ElementsAre(T1, T0)); } // Same as before but only add instructions to aux. They should get added too // the region too automatically. TEST_F(RegionTest, AuxOnlyRoundTrip) { parseIR(C, R"IR( define void @foo(i8 %v) { %add0 = add i8 %v, 0 %add1 = add i8 %v, 1 ret void } )IR"); llvm::Function *LLVMF = &*M->getFunction("foo"); sandboxir::Context Ctx(C); auto *F = Ctx.createFunction(LLVMF); auto *BB = &*F->begin(); auto It = BB->begin(); auto *Add0 = cast(&*It++); auto *Add1 = cast(&*It++); sandboxir::Region Rgn(Ctx, *TTI); #ifndef NDEBUG EXPECT_DEATH(Rgn.setAux({Add0, Add0}), ".*already.*"); #endif Rgn.setAux({Add1, Add0}); SmallVector> Regions = sandboxir::Region::createRegionsFromMD(*F, *TTI); ASSERT_EQ(1U, Regions.size()); #ifndef NDEBUG EXPECT_EQ(Rgn, *Regions[0].get()); #endif EXPECT_THAT(Rgn.getAux(), testing::ElementsAre(Add1, Add0)); }