Block sorting was assuming reducible CFG. Meaning we always had a best node to continue with. Irreducible CFG makes breaks this assumption, so the algorithm looped indefinitely because no node was a valid candidate. Fixes #116692 --------- Signed-off-by: Nathan Gauër <brioche@google.com>
390 lines
8.8 KiB
C++
390 lines
8.8 KiB
C++
//===- SPIRVSortBlocksTests.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 "SPIRVUtils.h"
|
|
#include "llvm/Analysis/DominanceFrontier.h"
|
|
#include "llvm/Analysis/PostDominators.h"
|
|
#include "llvm/AsmParser/Parser.h"
|
|
#include "llvm/IR/Instructions.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;
|
|
|
|
class SPIRVSortBlocksTest : public testing::Test {
|
|
protected:
|
|
void TearDown() override { M.reset(); }
|
|
|
|
bool run(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?");
|
|
llvm::Function *F = M->getFunction("main");
|
|
return sortBlocks(*F);
|
|
}
|
|
|
|
void checkBasicBlockOrder(std::vector<const char *> &&Expected) {
|
|
llvm::Function *F = M->getFunction("main");
|
|
auto It = F->begin();
|
|
for (const char *Name : Expected) {
|
|
ASSERT_TRUE(It != F->end())
|
|
<< "Expected block \"" << Name
|
|
<< "\" but reached the end of the function instead.";
|
|
ASSERT_TRUE(It->getName() == Name)
|
|
<< "Error: expected block \"" << Name << "\" got \"" << It->getName()
|
|
<< "\"";
|
|
It++;
|
|
}
|
|
ASSERT_TRUE(It == F->end())
|
|
<< "No more blocks were expected, but function has more.";
|
|
}
|
|
|
|
protected:
|
|
LLVMContext Context;
|
|
std::unique_ptr<Module> M;
|
|
};
|
|
|
|
TEST_F(SPIRVSortBlocksTest, DefaultRegion) {
|
|
StringRef Assembly = R"(
|
|
define void @main() convergent "hlsl.numthreads"="4,8,16" "hlsl.shader"="compute" {
|
|
ret void
|
|
}
|
|
)";
|
|
|
|
// No sorting is required.
|
|
EXPECT_FALSE(run(Assembly));
|
|
}
|
|
|
|
TEST_F(SPIRVSortBlocksTest, BasicBlockSwap) {
|
|
StringRef Assembly = R"(
|
|
define void @main() convergent "hlsl.numthreads"="4,8,16" "hlsl.shader"="compute" {
|
|
entry:
|
|
br label %middle
|
|
exit:
|
|
ret void
|
|
middle:
|
|
br label %exit
|
|
}
|
|
)";
|
|
|
|
EXPECT_TRUE(run(Assembly));
|
|
checkBasicBlockOrder({"entry", "middle", "exit"});
|
|
}
|
|
|
|
// Skip condition:
|
|
// +-> A -+
|
|
// entry -+ +-> C
|
|
// +------+
|
|
TEST_F(SPIRVSortBlocksTest, SkipCondition) {
|
|
StringRef Assembly = R"(
|
|
define void @main() convergent "hlsl.numthreads"="4,8,16" "hlsl.shader"="compute" {
|
|
entry:
|
|
%1 = icmp ne i32 0, 0
|
|
br i1 %1, label %c, label %a
|
|
c:
|
|
ret void
|
|
a:
|
|
br label %c
|
|
}
|
|
)";
|
|
|
|
EXPECT_TRUE(run(Assembly));
|
|
checkBasicBlockOrder({"entry", "a", "c"});
|
|
}
|
|
|
|
// Simple loop:
|
|
// entry -> header <-----------------+
|
|
// | `-> body -> continue -+
|
|
// `-> end
|
|
TEST_F(SPIRVSortBlocksTest, LoopOrdering) {
|
|
StringRef Assembly = R"(
|
|
define void @main() convergent "hlsl.numthreads"="4,8,16" "hlsl.shader"="compute" {
|
|
entry:
|
|
%1 = icmp ne i32 0, 0
|
|
br label %header
|
|
end:
|
|
ret void
|
|
body:
|
|
br label %continue
|
|
continue:
|
|
br label %header
|
|
header:
|
|
br i1 %1, label %body, label %end
|
|
}
|
|
)";
|
|
|
|
EXPECT_TRUE(run(Assembly));
|
|
checkBasicBlockOrder({"entry", "header", "end", "body", "continue"});
|
|
}
|
|
|
|
// Diamond condition:
|
|
// +-> A -+
|
|
// entry -+ +-> C
|
|
// +-> B -+
|
|
//
|
|
// A and B order can be flipped with no effect, but it must be remain
|
|
// deterministic/stable.
|
|
TEST_F(SPIRVSortBlocksTest, DiamondCondition) {
|
|
StringRef Assembly = R"(
|
|
define void @main() convergent "hlsl.numthreads"="4,8,16" "hlsl.shader"="compute" {
|
|
entry:
|
|
%1 = icmp ne i32 0, 0
|
|
br i1 %1, label %b, label %a
|
|
c:
|
|
ret void
|
|
b:
|
|
br label %c
|
|
a:
|
|
br label %c
|
|
}
|
|
)";
|
|
|
|
EXPECT_TRUE(run(Assembly));
|
|
checkBasicBlockOrder({"entry", "a", "b", "c"});
|
|
}
|
|
|
|
// Crossing conditions:
|
|
// +------+ +-> C -+
|
|
// +-> A -+ | | |
|
|
// entry -+ +--_|_-+ +-> E
|
|
// +-> B -+ | |
|
|
// +------+----> D -+
|
|
//
|
|
// A & B have the same rank.
|
|
// C & D have the same rank, but are after A & B.
|
|
// E if the last block.
|
|
TEST_F(SPIRVSortBlocksTest, CrossingCondition) {
|
|
StringRef Assembly = R"(
|
|
define void @main() convergent "hlsl.numthreads"="4,8,16" "hlsl.shader"="compute" {
|
|
entry:
|
|
%1 = icmp ne i32 0, 0
|
|
br i1 %1, label %b, label %a
|
|
e:
|
|
ret void
|
|
c:
|
|
br label %e
|
|
b:
|
|
br i1 %1, label %d, label %c
|
|
d:
|
|
br label %e
|
|
a:
|
|
br i1 %1, label %d, label %c
|
|
}
|
|
)";
|
|
|
|
EXPECT_TRUE(run(Assembly));
|
|
checkBasicBlockOrder({"entry", "a", "b", "c", "d", "e"});
|
|
}
|
|
|
|
// Irreducible CFG
|
|
// digraph {
|
|
// entry -> A;
|
|
//
|
|
// A -> B;
|
|
// A -> C;
|
|
//
|
|
// B -> A;
|
|
// B -> C;
|
|
//
|
|
// C -> B;
|
|
// }
|
|
//
|
|
// Order starts with Entry and A. Order of B and C can change, but must remain
|
|
// stable.
|
|
// In such case, rank will be defined by the arbitrary traversal order. What's
|
|
// important is to have a stable value.
|
|
TEST_F(SPIRVSortBlocksTest, IrreducibleOrdering) {
|
|
StringRef Assembly = R"(
|
|
define void @main() convergent "hlsl.numthreads"="4,8,16" "hlsl.shader"="compute" {
|
|
entry:
|
|
%1 = icmp ne i32 0, 0
|
|
br label %a
|
|
|
|
b:
|
|
br i1 %1, label %a, label %c
|
|
|
|
c:
|
|
br label %b
|
|
|
|
a:
|
|
br i1 %1, label %b, label %c
|
|
|
|
}
|
|
)";
|
|
|
|
EXPECT_TRUE(run(Assembly));
|
|
checkBasicBlockOrder({"entry", "a", "b", "c"});
|
|
}
|
|
|
|
TEST_F(SPIRVSortBlocksTest, IrreducibleOrderingBeforeReduction) {
|
|
StringRef Assembly = R"(
|
|
define void @main() convergent "hlsl.numthreads"="4,8,16" "hlsl.shader"="compute" {
|
|
entry:
|
|
%1 = icmp ne i32 0, 0
|
|
br label %a
|
|
|
|
c:
|
|
br i1 %1, label %e, label %d
|
|
|
|
e:
|
|
ret void
|
|
|
|
b:
|
|
br i1 %1, label %c, label %d
|
|
|
|
a:
|
|
br label %b
|
|
|
|
d:
|
|
br i1 %1, label %b, label %c
|
|
|
|
}
|
|
)";
|
|
|
|
EXPECT_TRUE(run(Assembly));
|
|
checkBasicBlockOrder({"entry", "a", "b", "c", "d", "e"});
|
|
}
|
|
|
|
TEST_F(SPIRVSortBlocksTest, LoopDiamond) {
|
|
StringRef Assembly = R"(
|
|
define void @main() convergent "hlsl.numthreads"="4,8,16" "hlsl.shader"="compute" {
|
|
entry:
|
|
%1 = icmp ne i32 0, 0
|
|
br label %header
|
|
header:
|
|
br i1 %1, label %body, label %end
|
|
body:
|
|
br i1 %1, label %break, label %inside_a
|
|
inside_a:
|
|
br label %inside_b
|
|
inside_b:
|
|
br i1 %1, label %inside_d, label %inside_c
|
|
inside_c:
|
|
br label %continue
|
|
inside_d:
|
|
br label %continue
|
|
break:
|
|
br label %end
|
|
continue:
|
|
br label %header
|
|
end:
|
|
ret void
|
|
}
|
|
)";
|
|
|
|
EXPECT_TRUE(run(Assembly));
|
|
checkBasicBlockOrder({"entry", "header", "body", "inside_a", "inside_b",
|
|
"inside_c", "inside_d", "continue", "break", "end"});
|
|
}
|
|
|
|
TEST_F(SPIRVSortBlocksTest, LoopNested) {
|
|
StringRef Assembly = R"(
|
|
define void @main() convergent "hlsl.numthreads"="4,8,16" "hlsl.shader"="compute" {
|
|
entry:
|
|
%1 = icmp ne i32 0, 0
|
|
br label %a
|
|
a:
|
|
br i1 %1, label %h, label %b
|
|
b:
|
|
br label %c
|
|
c:
|
|
br i1 %1, label %d, label %e
|
|
d:
|
|
br label %g
|
|
e:
|
|
br label %f
|
|
f:
|
|
br label %c
|
|
g:
|
|
br label %a
|
|
h:
|
|
ret void
|
|
}
|
|
)";
|
|
|
|
EXPECT_TRUE(run(Assembly));
|
|
checkBasicBlockOrder({"entry", "a", "b", "c", "e", "f", "d", "g", "h"});
|
|
}
|
|
|
|
TEST_F(SPIRVSortBlocksTest, IfNested) {
|
|
StringRef Assembly = R"(
|
|
define void @main() convergent "hlsl.numthreads"="4,8,16" "hlsl.shader"="compute" {
|
|
entry:
|
|
br i1 true, label %d, label %a
|
|
i:
|
|
br label %j
|
|
j:
|
|
ret void
|
|
a:
|
|
br i1 true, label %b, label %c
|
|
d:
|
|
br i1 true, label %f, label %e
|
|
e:
|
|
br label %i
|
|
b:
|
|
br label %c
|
|
f:
|
|
br i1 true, label %h, label %g
|
|
g:
|
|
br label %h
|
|
c:
|
|
br label %j
|
|
h:
|
|
br label %i
|
|
}
|
|
)";
|
|
EXPECT_TRUE(run(Assembly));
|
|
checkBasicBlockOrder(
|
|
{"entry", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j"});
|
|
}
|
|
|
|
// Same as above, but this time blocks are already sorted, so no need to reorder
|
|
// them.
|
|
TEST_F(SPIRVSortBlocksTest, IfNestedSorted) {
|
|
StringRef Assembly = R"(
|
|
define void @main() convergent "hlsl.numthreads"="4,8,16" "hlsl.shader"="compute" {
|
|
entry:
|
|
br i1 true, label %d, label %z
|
|
z:
|
|
br i1 true, label %b, label %c
|
|
b:
|
|
br label %c
|
|
c:
|
|
br label %j
|
|
d:
|
|
br i1 true, label %f, label %e
|
|
e:
|
|
br label %i
|
|
f:
|
|
br i1 true, label %h, label %g
|
|
g:
|
|
br label %h
|
|
h:
|
|
br label %i
|
|
i:
|
|
br label %j
|
|
j:
|
|
ret void
|
|
}
|
|
)";
|
|
EXPECT_FALSE(run(Assembly));
|
|
}
|