1100 lines
31 KiB
C++
1100 lines
31 KiB
C++
//===- 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"});
|
|
}
|