//===- SPIRVConvergenceRegionAnalysisTests.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 "Analysis/SPIRVConvergenceRegionAnalysis.h" #include "llvm/Analysis/DominanceFrontier.h" #include "llvm/Analysis/PostDominators.h" #include "llvm/AsmParser/Parser.h" #include "llvm/IR/Instructions.h" #include "llvm/IR/IntrinsicInst.h" #include "llvm/IR/LLVMContext.h" #include "llvm/IR/LegacyPassManager.h" #include "llvm/IR/Module.h" #include "llvm/IR/PassInstrumentation.h" #include "llvm/IR/Type.h" #include "llvm/IR/TypedPointerType.h" #include "llvm/Support/SourceMgr.h" #include "gmock/gmock.h" #include "gtest/gtest.h" #include using namespace llvm; using namespace llvm::SPIRV; template struct IsA { friend bool operator==(const Value *V, const IsA &) { return isa(V); } }; class SPIRVConvergenceRegionAnalysisTest : public testing::Test { protected: void SetUp() override { // Required for tests. FAM.registerPass([&] { return PassInstrumentationAnalysis(); }); MAM.registerPass([&] { return PassInstrumentationAnalysis(); }); // Required for ConvergenceRegionAnalysis. FAM.registerPass([&] { return DominatorTreeAnalysis(); }); FAM.registerPass([&] { return LoopAnalysis(); }); FAM.registerPass([&] { return SPIRVConvergenceRegionAnalysis(); }); } void TearDown() override { M.reset(); } SPIRVConvergenceRegionAnalysis::Result &runAnalysis(StringRef Assembly) { assert(M == nullptr && "Calling runAnalysis multiple times is unsafe. See getAnalysis()."); SMDiagnostic Error; M = parseAssemblyString(Assembly, Error, Context); assert(M && "Bad assembly. Bad test?"); auto *F = getFunction(); ModulePassManager MPM; MPM.run(*M, MAM); return FAM.getResult(*F); } SPIRVConvergenceRegionAnalysis::Result &getAnalysis() { assert(M != nullptr && "Has runAnalysis been called before?"); return FAM.getResult(*getFunction()); } Function *getFunction() const { assert(M != nullptr && "Has runAnalysis been called before?"); return M->getFunction("main"); } const BasicBlock *getBlock(StringRef Name) { assert(M != nullptr && "Has runAnalysis been called before?"); auto *F = getFunction(); for (BasicBlock &BB : *F) { if (BB.getName() == Name) return &BB; } ADD_FAILURE() << "Error: Could not locate requested block. Bad test?"; return nullptr; } const ConvergenceRegion *getRegionWithEntry(StringRef Name) { assert(M != nullptr && "Has runAnalysis been called before?"); std::queue ToProcess; ToProcess.push(getAnalysis().getTopLevelRegion()); while (ToProcess.size() != 0) { auto *R = ToProcess.front(); ToProcess.pop(); for (auto *Child : R->Children) ToProcess.push(Child); if (R->Entry->getName() == Name) return R; } ADD_FAILURE() << "Error: Could not locate requested region. Bad test?"; return nullptr; } void checkRegionBlocks(const ConvergenceRegion *R, std::initializer_list InRegion, std::initializer_list NotInRegion) { for (const char *Name : InRegion) { EXPECT_TRUE(R->contains(getBlock(Name))) << "error: " << Name << " not in region " << R->Entry->getName(); } for (const char *Name : NotInRegion) { EXPECT_FALSE(R->contains(getBlock(Name))) << "error: " << Name << " in region " << R->Entry->getName(); } } protected: LLVMContext Context; FunctionAnalysisManager FAM; ModuleAnalysisManager MAM; std::unique_ptr M; }; MATCHER_P(ContainsBasicBlock, label, "") { for (const auto *bb : arg) if (bb->getName() == label) return true; return false; } TEST_F(SPIRVConvergenceRegionAnalysisTest, DefaultRegion) { StringRef Assembly = R"( define void @main() convergent "hlsl.numthreads"="4,8,16" "hlsl.shader"="compute" { ret void } )"; const auto *CR = runAnalysis(Assembly).getTopLevelRegion(); EXPECT_EQ(CR->Parent, nullptr); EXPECT_EQ(CR->ConvergenceToken, std::nullopt); EXPECT_EQ(CR->Children.size(), 0u); } TEST_F(SPIRVConvergenceRegionAnalysisTest, DefaultRegionWithToken) { StringRef Assembly = R"( define void @main() convergent "hlsl.numthreads"="4,8,16" "hlsl.shader"="compute" { %t1 = call token @llvm.experimental.convergence.entry() ret void } declare token @llvm.experimental.convergence.entry() )"; const auto *CR = runAnalysis(Assembly).getTopLevelRegion(); EXPECT_EQ(CR->Parent, nullptr); EXPECT_EQ(CR->Children.size(), 0u); EXPECT_TRUE(CR->ConvergenceToken.has_value()); EXPECT_EQ(CR->ConvergenceToken.value()->getIntrinsicID(), Intrinsic::experimental_convergence_entry); } TEST_F(SPIRVConvergenceRegionAnalysisTest, SingleLoopOneRegion) { StringRef Assembly = R"( define void @main() convergent "hlsl.numthreads"="4,8,16" "hlsl.shader"="compute" { %t1 = call token @llvm.experimental.convergence.entry() %1 = icmp ne i32 0, 0 br label %l1 l1: %tl1 = call token @llvm.experimental.convergence.loop() [ "convergencectrl"(token %t1) ] br i1 %1, label %l1_body, label %l1_end l1_body: br label %l1_continue l1_continue: br label %l1 l1_end: br label %end end: ret void } declare token @llvm.experimental.convergence.entry() declare token @llvm.experimental.convergence.control() declare token @llvm.experimental.convergence.loop() )"; const auto *CR = runAnalysis(Assembly).getTopLevelRegion(); EXPECT_EQ(CR->Parent, nullptr); EXPECT_EQ(CR->ConvergenceToken.value()->getName(), "t1"); EXPECT_TRUE(CR->ConvergenceToken.has_value()); EXPECT_EQ(CR->ConvergenceToken.value()->getIntrinsicID(), Intrinsic::experimental_convergence_entry); EXPECT_EQ(CR->Children.size(), 1u); } TEST_F(SPIRVConvergenceRegionAnalysisTest, SingleLoopLoopRegionParentsIsTopLevelRegion) { StringRef Assembly = R"( define void @main() convergent "hlsl.numthreads"="4,8,16" "hlsl.shader"="compute" { %t1 = call token @llvm.experimental.convergence.entry() %1 = icmp ne i32 0, 0 br label %l1 l1: %tl1 = call token @llvm.experimental.convergence.loop() [ "convergencectrl"(token %t1) ] br i1 %1, label %l1_body, label %l1_end l1_body: br label %l1_continue l1_continue: br label %l1 l1_end: br label %end end: ret void } declare token @llvm.experimental.convergence.entry() declare token @llvm.experimental.convergence.control() declare token @llvm.experimental.convergence.loop() )"; const auto *CR = runAnalysis(Assembly).getTopLevelRegion(); EXPECT_EQ(CR->Parent, nullptr); EXPECT_EQ(CR->ConvergenceToken.value()->getName(), "t1"); EXPECT_EQ(CR->Children[0]->Parent, CR); EXPECT_EQ(CR->Children[0]->ConvergenceToken.value()->getName(), "tl1"); } TEST_F(SPIRVConvergenceRegionAnalysisTest, SingleLoopExits) { StringRef Assembly = R"( define void @main() convergent "hlsl.numthreads"="4,8,16" "hlsl.shader"="compute" { %t1 = call token @llvm.experimental.convergence.entry() %1 = icmp ne i32 0, 0 br label %l1 l1: %tl1 = call token @llvm.experimental.convergence.loop() [ "convergencectrl"(token %t1) ] br i1 %1, label %l1_body, label %l1_end l1_body: br label %l1_continue l1_continue: br label %l1 l1_end: br label %end end: ret void } declare token @llvm.experimental.convergence.entry() declare token @llvm.experimental.convergence.control() declare token @llvm.experimental.convergence.loop() )"; const auto *CR = runAnalysis(Assembly).getTopLevelRegion(); const auto *L = CR->Children[0]; EXPECT_EQ(L->Exits.size(), 1ul); EXPECT_THAT(L->Exits, ContainsBasicBlock("l1")); } TEST_F(SPIRVConvergenceRegionAnalysisTest, SingleLoopWithBreakExits) { StringRef Assembly = R"( define void @main() convergent "hlsl.numthreads"="4,8,16" "hlsl.shader"="compute" { %t1 = call token @llvm.experimental.convergence.entry() %1 = icmp ne i32 0, 0 br label %l1_header l1_header: %tl1 = call token @llvm.experimental.convergence.loop() [ "convergencectrl"(token %t1) ] br i1 %1, label %l1_body, label %end.loopexit l1_body: %2 = icmp ne i32 0, 0 br i1 %2, label %l1_condition_true, label %l1_condition_false l1_condition_true: %call = call spir_func i32 @_Z3absi(i32 0) [ "convergencectrl"(token %tl1) ] br label %end l1_condition_false: br label %l1_continue l1_continue: br label %l1_header end.loopexit: br label %end end: ret void } declare token @llvm.experimental.convergence.entry() declare token @llvm.experimental.convergence.control() declare token @llvm.experimental.convergence.loop() ; This intrinsic is not convergent. This is only because the backend doesn't ; support convergent operations yet. declare spir_func i32 @_Z3absi(i32) convergent )"; const auto *CR = runAnalysis(Assembly).getTopLevelRegion(); const auto *L = CR->Children[0]; EXPECT_EQ(L->Exits.size(), 2ul); EXPECT_THAT(L->Exits, ContainsBasicBlock("l1_header")); EXPECT_THAT(L->Exits, ContainsBasicBlock("l1_condition_true")); EXPECT_TRUE(CR->contains(getBlock("l1_header"))); EXPECT_TRUE(CR->contains(getBlock("l1_condition_true"))); } TEST_F(SPIRVConvergenceRegionAnalysisTest, SingleLoopWithBreakRegionBlocks) { StringRef Assembly = R"( define void @main() convergent "hlsl.numthreads"="4,8,16" "hlsl.shader"="compute" { %t1 = call token @llvm.experimental.convergence.entry() %1 = icmp ne i32 0, 0 br label %l1_header l1_header: %tl1 = call token @llvm.experimental.convergence.loop() [ "convergencectrl"(token %t1) ] br i1 %1, label %l1_body, label %end.loopexit l1_body: %2 = icmp ne i32 0, 0 br i1 %2, label %l1_condition_true, label %l1_condition_false l1_condition_true: %call = call spir_func i32 @_Z3absi(i32 0) [ "convergencectrl"(token %tl1) ] br label %end l1_condition_false: br label %l1_continue l1_continue: br label %l1_header end.loopexit: br label %end end: ret void } declare token @llvm.experimental.convergence.entry() declare token @llvm.experimental.convergence.control() declare token @llvm.experimental.convergence.loop() ; This intrinsic is not convergent. This is only because the backend doesn't ; support convergent operations yet. declare spir_func i32 @_Z3absi(i32) convergent )"; const auto *CR = runAnalysis(Assembly).getTopLevelRegion(); const auto *L = CR->Children[0]; EXPECT_TRUE(CR->contains(getBlock("l1_header"))); EXPECT_TRUE(L->contains(getBlock("l1_header"))); EXPECT_TRUE(CR->contains(getBlock("l1_body"))); EXPECT_TRUE(L->contains(getBlock("l1_body"))); EXPECT_TRUE(CR->contains(getBlock("l1_condition_true"))); EXPECT_TRUE(L->contains(getBlock("l1_condition_true"))); EXPECT_TRUE(CR->contains(getBlock("l1_condition_false"))); EXPECT_TRUE(L->contains(getBlock("l1_condition_false"))); EXPECT_TRUE(CR->contains(getBlock("l1_continue"))); EXPECT_TRUE(L->contains(getBlock("l1_continue"))); EXPECT_TRUE(CR->contains(getBlock("end.loopexit"))); EXPECT_FALSE(L->contains(getBlock("end.loopexit"))); EXPECT_TRUE(CR->contains(getBlock("end"))); EXPECT_FALSE(L->contains(getBlock("end"))); } // Exact same test as before, except the 'if() break' condition in the loop is // not marked with any convergence intrinsic. In such case, it is valid to // consider it outside of the loop. TEST_F(SPIRVConvergenceRegionAnalysisTest, SingleLoopWithBreakNoConvergenceControl) { StringRef Assembly = R"( define void @main() convergent "hlsl.numthreads"="4,8,16" "hlsl.shader"="compute" { %t1 = call token @llvm.experimental.convergence.entry() %1 = icmp ne i32 0, 0 br label %l1_header l1_header: %tl1 = call token @llvm.experimental.convergence.loop() [ "convergencectrl"(token %t1) ] br i1 %1, label %l1_body, label %end.loopexit l1_body: %2 = icmp ne i32 0, 0 br i1 %2, label %l1_condition_true, label %l1_condition_false l1_condition_true: br label %end l1_condition_false: br label %l1_continue l1_continue: br label %l1_header end.loopexit: br label %end end: ret void } declare token @llvm.experimental.convergence.entry() declare token @llvm.experimental.convergence.control() declare token @llvm.experimental.convergence.loop() )"; runAnalysis(Assembly); const auto *L = getRegionWithEntry("l1_header"); EXPECT_EQ(L->Entry->getName(), "l1_header"); EXPECT_EQ(L->Exits.size(), 2ul); EXPECT_THAT(L->Exits, ContainsBasicBlock("l1_header")); EXPECT_THAT(L->Exits, ContainsBasicBlock("l1_body")); EXPECT_TRUE(L->contains(getBlock("l1_header"))); EXPECT_TRUE(L->contains(getBlock("l1_body"))); EXPECT_FALSE(L->contains(getBlock("l1_condition_true"))); EXPECT_TRUE(L->contains(getBlock("l1_condition_false"))); EXPECT_TRUE(L->contains(getBlock("l1_continue"))); EXPECT_FALSE(L->contains(getBlock("end.loopexit"))); EXPECT_FALSE(L->contains(getBlock("end"))); } TEST_F(SPIRVConvergenceRegionAnalysisTest, TwoLoopsWithControl) { StringRef Assembly = R"( define void @main() convergent "hlsl.numthreads"="4,8,16" "hlsl.shader"="compute" { %t1 = call token @llvm.experimental.convergence.entry() %1 = icmp ne i32 0, 0 br label %l1_header l1_header: %tl1 = call token @llvm.experimental.convergence.loop() [ "convergencectrl"(token %t1) ] br i1 %1, label %l1_body, label %l1_exit l1_body: br i1 %1, label %l1_condition_true, label %l1_condition_false l1_condition_true: br label %mid l1_condition_false: br label %l1_continue l1_continue: br label %l1_header l1_exit: br label %mid mid: br label %l2_header l2_header: %tl2 = call token @llvm.experimental.convergence.loop() [ "convergencectrl"(token %t1) ] br i1 %1, label %l2_body, label %l2_exit l2_body: br i1 %1, label %l2_condition_true, label %l2_condition_false l2_condition_true: br label %end l2_condition_false: br label %l2_continue l2_continue: br label %l2_header l2_exit: br label %end end: ret void } declare token @llvm.experimental.convergence.entry() declare token @llvm.experimental.convergence.control() declare token @llvm.experimental.convergence.loop() )"; runAnalysis(Assembly); { const auto *L = getRegionWithEntry("l1_header"); EXPECT_EQ(L->Entry->getName(), "l1_header"); EXPECT_EQ(L->Exits.size(), 2ul); EXPECT_THAT(L->Exits, ContainsBasicBlock("l1_header")); EXPECT_THAT(L->Exits, ContainsBasicBlock("l1_body")); checkRegionBlocks( L, {"l1_header", "l1_body", "l1_condition_false", "l1_continue"}, {"", "l2_header", "l2_body", "l2_condition_true", "l2_condition_false", "l2_continue", "l2_exit", "l1_condition_true", "l1_exit", "end"}); } { const auto *L = getRegionWithEntry("l2_header"); EXPECT_EQ(L->Entry->getName(), "l2_header"); EXPECT_EQ(L->Exits.size(), 2ul); EXPECT_THAT(L->Exits, ContainsBasicBlock("l2_header")); EXPECT_THAT(L->Exits, ContainsBasicBlock("l2_body")); checkRegionBlocks( L, {"l2_header", "l2_body", "l2_condition_false", "l2_continue"}, {"", "l1_header", "l1_body", "l1_condition_true", "l1_condition_false", "l1_continue", "l1_exit", "l2_condition_true", "l2_exit", "end"}); } } // Both branches in the loop condition break. This means the loop continue // targets are unreachable, meaning no reachable back-edge. This should // transform the loop condition into a simple condition, meaning we have a // single convergence region. TEST_F(SPIRVConvergenceRegionAnalysisTest, LoopBothBranchExits) { StringRef Assembly = R"( define void @main() convergent "hlsl.numthreads"="4,8,16" "hlsl.shader"="compute" { %t1 = call token @llvm.experimental.convergence.entry() %1 = icmp ne i32 0, 0 br label %l1_header l1_header: %tl1 = call token @llvm.experimental.convergence.loop() [ "convergencectrl"(token %t1) ] br i1 %1, label %l1_body, label %l1_exit l1_body: br i1 %1, label %l1_condition_true, label %l1_condition_false l1_condition_true: %call_true = call spir_func i32 @_Z3absi(i32 0) [ "convergencectrl"(token %tl1) ] br label %end l1_condition_false: %call_false = call spir_func i32 @_Z3absi(i32 0) [ "convergencectrl"(token %tl1) ] br label %end l1_continue: br label %l1_header l1_exit: br label %end end: ret void } declare token @llvm.experimental.convergence.entry() declare token @llvm.experimental.convergence.control() declare token @llvm.experimental.convergence.loop() ; This intrinsic is not convergent. This is only because the backend doesn't ; support convergent operations yet. declare spir_func i32 @_Z3absi(i32) convergent )"; ; const auto *R = runAnalysis(Assembly).getTopLevelRegion(); ASSERT_EQ(R->Children.size(), 0ul); EXPECT_EQ(R->Exits.size(), 1ul); EXPECT_THAT(R->Exits, ContainsBasicBlock("end")); } TEST_F(SPIRVConvergenceRegionAnalysisTest, InnerLoopBreaks) { StringRef Assembly = R"( define void @main() convergent "hlsl.numthreads"="4,8,16" "hlsl.shader"="compute" { %t1 = call token @llvm.experimental.convergence.entry() %1 = icmp ne i32 0, 0 br label %l1_header l1_header: %tl1 = call token @llvm.experimental.convergence.loop() [ "convergencectrl"(token %t1) ] br i1 %1, label %l1_body, label %l1_exit l1_body: br label %l2_header l2_header: %tl2 = call token @llvm.experimental.convergence.loop() [ "convergencectrl"(token %tl1) ] br i1 %1, label %l2_body, label %l2_exit l2_body: br i1 %1, label %l2_condition_true, label %l2_condition_false l2_condition_true: %call_true = call spir_func i32 @_Z3absi(i32 0) [ "convergencectrl"(token %tl1) ] br label %end l2_condition_false: br label %l2_continue l2_continue: br label %l2_header l2_exit: br label %l1_continue l1_continue: br label %l1_header l1_exit: br label %end end: ret void } declare token @llvm.experimental.convergence.entry() declare token @llvm.experimental.convergence.control() declare token @llvm.experimental.convergence.loop() ; This intrinsic is not convergent. This is only because the backend doesn't ; support convergent operations yet. declare spir_func i32 @_Z3absi(i32) convergent )"; const auto *R = runAnalysis(Assembly).getTopLevelRegion(); const auto *L1 = getRegionWithEntry("l1_header"); const auto *L2 = getRegionWithEntry("l2_header"); EXPECT_EQ(R->Children.size(), 1ul); EXPECT_EQ(L1->Children.size(), 1ul); EXPECT_EQ(L1->Parent, R); EXPECT_EQ(L2->Parent, L1); EXPECT_EQ(R->Entry->getName(), ""); EXPECT_EQ(R->Exits.size(), 1ul); EXPECT_THAT(R->Exits, ContainsBasicBlock("end")); EXPECT_EQ(L1->Entry->getName(), "l1_header"); EXPECT_EQ(L1->Exits.size(), 2ul); EXPECT_THAT(L1->Exits, ContainsBasicBlock("l1_header")); EXPECT_THAT(L1->Exits, ContainsBasicBlock("l2_condition_true")); checkRegionBlocks(L1, {"l1_header", "l1_body", "l2_header", "l2_body", "l2_condition_false", "l2_condition_true", "l2_continue", "l2_exit", "l1_continue"}, {"", "l1_exit", "end"}); EXPECT_EQ(L2->Entry->getName(), "l2_header"); EXPECT_EQ(L2->Exits.size(), 2ul); EXPECT_THAT(L2->Exits, ContainsBasicBlock("l2_header")); EXPECT_THAT(L2->Exits, ContainsBasicBlock("l2_body")); checkRegionBlocks( L2, {"l2_header", "l2_body", "l2_condition_false", "l2_continue"}, {"", "l1_header", "l1_body", "l2_exit", "l1_continue", "l2_condition_true", "l1_exit", "end"}); } TEST_F(SPIRVConvergenceRegionAnalysisTest, SingleLoopMultipleExits) { StringRef Assembly = R"( define void @main() convergent "hlsl.numthreads"="4,8,16" "hlsl.shader"="compute" { %t1 = call token @llvm.experimental.convergence.entry() %cond = icmp ne i32 0, 0 br label %l1 l1: %tl1 = call token @llvm.experimental.convergence.loop() [ "convergencectrl"(token %t1) ] br i1 %cond, label %l1_body, label %l1_exit l1_body: switch i32 0, label %sw.default.exit [ i32 0, label %sw.bb i32 1, label %sw.bb1 i32 2, label %sw.bb2 ] sw.default.exit: br label %sw.default sw.default: br label %l1_end sw.bb: br label %l1_end sw.bb1: br label %l1_continue sw.bb2: br label %sw.default l1_continue: br label %l1 l1_exit: br label %l1_end l1_end: br label %end end: ret void } declare token @llvm.experimental.convergence.entry() declare token @llvm.experimental.convergence.control() declare token @llvm.experimental.convergence.loop() )"; runAnalysis(Assembly).getTopLevelRegion(); const auto *L = getRegionWithEntry("l1"); ASSERT_NE(L, nullptr); EXPECT_EQ(L->Entry, getBlock("l1")); EXPECT_EQ(L->Exits.size(), 2ul); EXPECT_THAT(L->Exits, ContainsBasicBlock("l1")); EXPECT_THAT(L->Exits, ContainsBasicBlock("l1_body")); checkRegionBlocks(L, {"l1", "l1_body", "l1_continue", "sw.bb1"}, {"", "sw.default.exit", "sw.default", "l1_end", "end", "sw.bb", "sw.bb2", "l1_exit"}); } TEST_F(SPIRVConvergenceRegionAnalysisTest, SingleLoopMultipleExitsWithPartialConvergence) { StringRef Assembly = R"( define void @main() convergent "hlsl.numthreads"="4,8,16" "hlsl.shader"="compute" { %t1 = call token @llvm.experimental.convergence.entry() %cond = icmp ne i32 0, 0 br label %l1 l1: %tl1 = call token @llvm.experimental.convergence.loop() [ "convergencectrl"(token %t1) ] br i1 %cond, label %l1_body, label %l1_exit l1_body: switch i32 0, label %sw.default.exit [ i32 0, label %sw.bb i32 1, label %sw.bb1 i32 2, label %sw.bb2 ] sw.default.exit: br label %sw.default sw.default: %call = call spir_func i32 @_Z3absi(i32 0) [ "convergencectrl"(token %tl1) ] br label %l1_end sw.bb: br label %l1_end sw.bb1: br label %l1_continue sw.bb2: br label %sw.default l1_continue: br label %l1 l1_exit: br label %l1_end l1_end: br label %end end: ret void } declare token @llvm.experimental.convergence.entry() declare token @llvm.experimental.convergence.control() declare token @llvm.experimental.convergence.loop() ; This intrinsic is not convergent. This is only because the backend doesn't ; support convergent operations yet. declare spir_func i32 @_Z3absi(i32) convergent )"; runAnalysis(Assembly).getTopLevelRegion(); const auto *L = getRegionWithEntry("l1"); ASSERT_NE(L, nullptr); EXPECT_EQ(L->Entry, getBlock("l1")); EXPECT_EQ(L->Exits.size(), 3ul); EXPECT_THAT(L->Exits, ContainsBasicBlock("l1")); EXPECT_THAT(L->Exits, ContainsBasicBlock("l1_body")); EXPECT_THAT(L->Exits, ContainsBasicBlock("sw.default")); checkRegionBlocks(L, {"l1", "l1_body", "l1_continue", "sw.bb1", "sw.default.exit", "sw.bb2", "sw.default"}, {"", "l1_end", "end", "sw.bb", "l1_exit"}); } TEST_F(SPIRVConvergenceRegionAnalysisTest, SingleLoopWithDeepConvergenceBranch) { StringRef Assembly = R"( define void @main() convergent "hlsl.numthreads"="4,8,16" "hlsl.shader"="compute" { %t1 = call token @llvm.experimental.convergence.entry() %1 = icmp ne i32 0, 0 br label %l1_header l1_header: %tl1 = call token @llvm.experimental.convergence.loop() [ "convergencectrl"(token %t1) ] br i1 %1, label %l1_body, label %l1_end l1_body: %2 = icmp ne i32 0, 0 br i1 %2, label %l1_condition_true, label %l1_condition_false l1_condition_true: br label %a a: br label %b b: br label %c c: %call = call spir_func i32 @_Z3absi(i32 0) [ "convergencectrl"(token %tl1) ] br label %end l1_condition_false: br label %l1_continue l1_continue: br label %l1_header l1_end: br label %end end: ret void } declare token @llvm.experimental.convergence.entry() declare token @llvm.experimental.convergence.control() declare token @llvm.experimental.convergence.loop() ; This intrinsic is not convergent. This is only because the backend doesn't ; support convergent operations yet. declare spir_func i32 @_Z3absi(i32) convergent )"; runAnalysis(Assembly).getTopLevelRegion(); const auto *L = getRegionWithEntry("l1_header"); ASSERT_NE(L, nullptr); EXPECT_EQ(L->Entry, getBlock("l1_header")); EXPECT_EQ(L->Exits.size(), 2ul); EXPECT_THAT(L->Exits, ContainsBasicBlock("l1_header")); EXPECT_THAT(L->Exits, ContainsBasicBlock("c")); checkRegionBlocks(L, {"l1_header", "l1_body", "l1_continue", "l1_condition_false", "l1_condition_true", "a", "b", "c"}, {"", "l1_end", "end"}); } TEST_F(SPIRVConvergenceRegionAnalysisTest, SingleLoopWithDeepConvergenceLateBranch) { StringRef Assembly = R"( define void @main() convergent "hlsl.numthreads"="4,8,16" "hlsl.shader"="compute" { %t1 = call token @llvm.experimental.convergence.entry() %1 = icmp ne i32 0, 0 br label %l1_header l1_header: %tl1 = call token @llvm.experimental.convergence.loop() [ "convergencectrl"(token %t1) ] br i1 %1, label %l1_body, label %l1_end l1_body: %2 = icmp ne i32 0, 0 br i1 %2, label %l1_condition_true, label %l1_condition_false l1_condition_true: br label %a a: br label %b b: br i1 %2, label %c, label %d c: %call = call spir_func i32 @_Z3absi(i32 0) [ "convergencectrl"(token %tl1) ] br label %end d: br label %end l1_condition_false: br label %l1_continue l1_continue: br label %l1_header l1_end: br label %end end: ret void } declare token @llvm.experimental.convergence.entry() declare token @llvm.experimental.convergence.control() declare token @llvm.experimental.convergence.loop() ; This intrinsic is not convergent. This is only because the backend doesn't ; support convergent operations yet. declare spir_func i32 @_Z3absi(i32) convergent )"; runAnalysis(Assembly).getTopLevelRegion(); const auto *L = getRegionWithEntry("l1_header"); ASSERT_NE(L, nullptr); EXPECT_EQ(L->Entry, getBlock("l1_header")); EXPECT_EQ(L->Exits.size(), 3ul); EXPECT_THAT(L->Exits, ContainsBasicBlock("l1_header")); EXPECT_THAT(L->Exits, ContainsBasicBlock("b")); EXPECT_THAT(L->Exits, ContainsBasicBlock("c")); checkRegionBlocks(L, {"l1_header", "l1_body", "l1_continue", "l1_condition_false", "l1_condition_true", "a", "b", "c"}, {"", "l1_end", "end", "d"}); } TEST_F(SPIRVConvergenceRegionAnalysisTest, SingleLoopWithNoConvergenceIntrinsics) { StringRef Assembly = R"( define void @main() "hlsl.numthreads"="4,8,16" "hlsl.shader"="compute" { %1 = icmp ne i32 0, 0 br label %l1_header l1_header: br i1 %1, label %l1_body, label %l1_end l1_body: %2 = icmp ne i32 0, 0 br i1 %2, label %l1_condition_true, label %l1_condition_false l1_condition_true: br label %a a: br label %end l1_condition_false: br label %l1_continue l1_continue: br label %l1_header l1_end: br label %end end: ret void } )"; runAnalysis(Assembly).getTopLevelRegion(); const auto *L = getRegionWithEntry("l1_header"); ASSERT_NE(L, nullptr); EXPECT_EQ(L->Entry, getBlock("l1_header")); EXPECT_EQ(L->Exits.size(), 2ul); EXPECT_THAT(L->Exits, ContainsBasicBlock("l1_header")); EXPECT_THAT(L->Exits, ContainsBasicBlock("l1_body")); checkRegionBlocks( L, {"l1_header", "l1_body", "l1_continue", "l1_condition_false"}, {"", "l1_end", "end", "l1_condition_true", "a"}); } TEST_F(SPIRVConvergenceRegionAnalysisTest, SimpleFunction) { StringRef Assembly = R"( define void @main() "hlsl.numthreads"="4,8,16" "hlsl.shader"="compute" { ret void } )"; const auto *R = runAnalysis(Assembly).getTopLevelRegion(); ASSERT_NE(R, nullptr); EXPECT_EQ(R->Entry, getBlock("")); EXPECT_EQ(R->Exits.size(), 1ul); EXPECT_THAT(R->Exits, ContainsBasicBlock("")); EXPECT_TRUE(R->contains(getBlock(""))); } TEST_F(SPIRVConvergenceRegionAnalysisTest, NestedLoopInBreak) { StringRef Assembly = R"( define void @main() convergent "hlsl.numthreads"="4,8,16" "hlsl.shader"="compute" { %t1 = call token @llvm.experimental.convergence.entry() %1 = icmp ne i32 0, 0 br label %l1 l1: %tl1 = call token @llvm.experimental.convergence.loop() [ "convergencectrl"(token %t1) ] br i1 %1, label %l1_body, label %l1_to_end l1_body: br i1 %1, label %cond_inner, label %l1_continue cond_inner: br label %l2 l2: %tl2 = call token @llvm.experimental.convergence.loop() [ "convergencectrl"(token %tl1) ] br i1 %1, label %l2_body, label %l2_end l2_body: %call = call spir_func i32 @_Z3absi(i32 0) [ "convergencectrl"(token %tl2) ] br label %l2_continue l2_continue: br label %l2 l2_end: br label %l2_exit l2_exit: %call2 = call spir_func i32 @_Z3absi(i32 0) [ "convergencectrl"(token %tl1) ] br label %l1_end l1_continue: br label %l1 l1_to_end: br label %l1_end l1_end: br label %end end: ret void } declare token @llvm.experimental.convergence.entry() declare token @llvm.experimental.convergence.control() declare token @llvm.experimental.convergence.loop() declare spir_func i32 @_Z3absi(i32) convergent )"; const auto *R = runAnalysis(Assembly).getTopLevelRegion(); ASSERT_NE(R, nullptr); EXPECT_EQ(R->Children.size(), 1ul); const auto *L1 = R->Children[0]; EXPECT_EQ(L1->Children.size(), 1ul); EXPECT_EQ(L1->Entry->getName(), "l1"); EXPECT_EQ(L1->Exits.size(), 2ul); EXPECT_THAT(L1->Exits, ContainsBasicBlock("l1")); EXPECT_THAT(L1->Exits, ContainsBasicBlock("l2_exit")); checkRegionBlocks(L1, {"l1", "l1_body", "l1_continue", "cond_inner", "l2", "l2_body", "l2_end", "l2_continue", "l2_exit"}, {"", "l1_to_end", "l1_end", "end"}); const auto *L2 = L1->Children[0]; EXPECT_EQ(L2->Children.size(), 0ul); EXPECT_EQ(L2->Entry->getName(), "l2"); EXPECT_EQ(L2->Exits.size(), 1ul); EXPECT_THAT(L2->Exits, ContainsBasicBlock("l2")); checkRegionBlocks(L2, {"l2", "l2_body", "l2_continue"}, {"", "l1_to_end", "l1_end", "end", "l1", "l1_body", "l1_continue", "cond_inner", "l2_end", "l2_exit"}); }