| //===- 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 <queue> |
| |
| using namespace llvm; |
| using namespace llvm::SPIRV; |
| |
| template <typename T> struct IsA { |
| friend bool operator==(const Value *V, const IsA &) { return isa<T>(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<SPIRVConvergenceRegionAnalysis>(*F); |
| } |
| |
| SPIRVConvergenceRegionAnalysis::Result &getAnalysis() { |
| assert(M != nullptr && "Has runAnalysis been called before?"); |
| return FAM.getResult<SPIRVConvergenceRegionAnalysis>(*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<const ConvergenceRegion *> 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<const char *> InRegion, |
| std::initializer_list<const char *> 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<Module> 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"}); |
| } |