Skip to content

Commit 85f222e

Browse files
authored
spirv-opt: Fix IRContext analysis handling (KhronosGroup#6765)
1. Add more cases where invalidating on analysis imply invalidating others. 2. Remove early return limiting the consistenty checks. Tests are added to catch issues like this again. 3. Extend IRContext::IsConsistent() to verify kAnalysisDominatorAnalysis and kAnalysisStructuredCFG analyses. Assisted by Antigravity
1 parent 48097f6 commit 85f222e

9 files changed

Lines changed: 299 additions & 1 deletion

source/opt/dominator_analysis.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,11 @@ class DominatorAnalysisBase {
116116
// If there is no such basic block, nullptr is returned.
117117
BasicBlock* CommonDominator(BasicBlock* b1, BasicBlock* b2) const;
118118

119+
// Returns true if this analysis has the same tree as |other|.
120+
inline bool operator==(const DominatorAnalysisBase& other) const {
121+
return tree_ == other.tree_;
122+
}
123+
119124
protected:
120125
DominatorTree tree_;
121126
};

source/opt/dominator_tree.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,5 +381,24 @@ void DominatorTree::DumpTreeAsDot(std::ostream& out_stream) const {
381381
out_stream << "}\n";
382382
}
383383

384+
bool DominatorTree::operator==(const DominatorTree& other) const {
385+
if (postdominator_ != other.postdominator_) return false;
386+
if (nodes_.size() != other.nodes_.size()) return false;
387+
388+
for (const auto& pair : nodes_) {
389+
uint32_t id = pair.first;
390+
const DominatorTreeNode& node = pair.second;
391+
const DominatorTreeNode* other_node = other.GetTreeNode(id);
392+
if (!other_node) return false;
393+
394+
if ((node.parent_ == nullptr) != (other_node->parent_ == nullptr))
395+
return false;
396+
if (node.parent_ != nullptr) {
397+
if (node.parent_->id() != other_node->parent_->id()) return false;
398+
}
399+
}
400+
return true;
401+
}
402+
384403
} // namespace opt
385404
} // namespace spvtools

source/opt/dominator_tree.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,9 @@ class DominatorTree {
202202
// Returns true if this tree is a post dominator tree.
203203
bool IsPostDominator() const { return postdominator_; }
204204

205+
// Returns true if this tree has the same nodes and structure as |other|.
206+
bool operator==(const DominatorTree& other) const;
207+
205208
// Clean up the tree.
206209
void ClearTree() {
207210
nodes_.clear();

source/opt/ir_context.cpp

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,12 @@ void IRContext::InvalidateAnalyses(IRContext::Analysis analyses_to_invalidate) {
118118
// dominator analysis should be invalidated as well.
119119
if (analyses_to_invalidate & kAnalysisCFG) {
120120
analyses_to_invalidate |= kAnalysisDominatorAnalysis;
121+
analyses_to_invalidate |= kAnalysisStructuredCFG;
122+
}
123+
124+
if (analyses_to_invalidate & kAnalysisLoopAnalysis) {
125+
analyses_to_invalidate |= kAnalysisScalarEvolution;
126+
analyses_to_invalidate |= kAnalysisLiveness;
121127
}
122128

123129
if (analyses_to_invalidate & kAnalysisDefUse) {
@@ -142,6 +148,9 @@ void IRContext::InvalidateAnalyses(IRContext::Analysis analyses_to_invalidate) {
142148
dominator_trees_.clear();
143149
post_dominator_trees_.clear();
144150
}
151+
if (analyses_to_invalidate & kAnalysisLoopAnalysis) {
152+
loop_descriptors_.clear();
153+
}
145154
if (analyses_to_invalidate & kAnalysisNameMap) {
146155
id_to_name_.reset(nullptr);
147156
}
@@ -160,6 +169,12 @@ void IRContext::InvalidateAnalyses(IRContext::Analysis analyses_to_invalidate) {
160169
if (analyses_to_invalidate & kAnalysisLiveness) {
161170
liveness_mgr_.reset(nullptr);
162171
}
172+
if (analyses_to_invalidate & kAnalysisScalarEvolution) {
173+
scalar_evolution_analysis_.reset(nullptr);
174+
}
175+
if (analyses_to_invalidate & kAnalysisRegisterPressure) {
176+
reg_pressure_.reset(nullptr);
177+
}
163178
if (analyses_to_invalidate & kAnalysisTypes) {
164179
type_mgr_.reset(nullptr);
165180
}
@@ -392,7 +407,6 @@ bool IRContext::IsConsistent() {
392407
}
393408
}
394409

395-
return true;
396410
if (AreAnalysesValid(kAnalysisIdToFuncMapping)) {
397411
for (auto& fn : *module_) {
398412
if (id_to_func_[fn.result_id()] != &fn) {
@@ -437,6 +451,37 @@ bool IRContext::IsConsistent() {
437451
}
438452
}
439453

454+
if (AreAnalysesValid(kAnalysisDominatorAnalysis)) {
455+
for (const auto& it : dominator_trees_) {
456+
const Function* f = it.first;
457+
const DominatorAnalysis& cached_dom = it.second;
458+
DominatorAnalysis new_dom;
459+
new_dom.InitializeTree(*cfg(), f);
460+
461+
if (!(cached_dom == new_dom)) {
462+
return false;
463+
}
464+
}
465+
for (const auto& it : post_dominator_trees_) {
466+
const Function* f = it.first;
467+
const PostDominatorAnalysis& cached_post_dom = it.second;
468+
PostDominatorAnalysis new_post_dom;
469+
new_post_dom.InitializeTree(*cfg(), f);
470+
471+
if (!(cached_post_dom == new_post_dom)) {
472+
return false;
473+
}
474+
}
475+
}
476+
477+
if (AreAnalysesValid(kAnalysisStructuredCFG)) {
478+
StructuredCFGAnalysis new_struct_cfg(this);
479+
StructuredCFGAnalysis* cached_struct_cfg = struct_cfg_analysis_.get();
480+
if (!(*cached_struct_cfg == new_struct_cfg)) {
481+
return false;
482+
}
483+
}
484+
440485
if (feature_mgr_ != nullptr) {
441486
FeatureManager current(grammar_);
442487
current.Analyze(module());

source/opt/struct_cfg_analysis.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,5 +245,18 @@ StructuredCFGAnalysis::FindFuncsCalledFromContinue() {
245245
return called_from_continue;
246246
}
247247

248+
bool StructuredCFGAnalysis::operator==(
249+
const StructuredCFGAnalysis& other) const {
250+
if (bb_to_construct_.size() != other.bb_to_construct_.size()) return false;
251+
for (const auto& pair : bb_to_construct_) {
252+
uint32_t bb_id = pair.first;
253+
const ConstructInfo& info = pair.second;
254+
auto it = other.bb_to_construct_.find(bb_id);
255+
if (it == other.bb_to_construct_.end()) return false;
256+
if (!(info == it->second)) return false;
257+
}
258+
return merge_blocks_ == other.merge_blocks_;
259+
}
260+
248261
} // namespace opt
249262
} // namespace spvtools

source/opt/struct_cfg_analysis.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,10 @@ class StructuredCFGAnalysis {
115115
// Return true if |bb_id| is the merge block for a construct.
116116
bool IsMergeBlock(uint32_t bb_id);
117117

118+
// Returns true if this analysis has the same constructs and merge blocks as
119+
// |other|.
120+
bool operator==(const StructuredCFGAnalysis& other) const;
121+
118122
// Returns the set of function ids that are called directly or indirectly from
119123
// a continue construct.
120124
std::unordered_set<uint32_t> FindFuncsCalledFromContinue();
@@ -141,6 +145,13 @@ class StructuredCFGAnalysis {
141145
uint32_t containing_loop;
142146
uint32_t containing_switch;
143147
bool in_continue;
148+
149+
bool operator==(const ConstructInfo& other) const {
150+
return containing_construct == other.containing_construct &&
151+
containing_loop == other.containing_loop &&
152+
containing_switch == other.containing_switch &&
153+
in_continue == other.in_continue;
154+
}
144155
};
145156

146157
// Populates |bb_to_construct_| with the innermost containing merge and loop

source/util/bit_vector.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,5 +78,15 @@ std::ostream& operator<<(std::ostream& out, const BitVector& bv) {
7878
return out;
7979
}
8080

81+
bool BitVector::operator==(const BitVector& other) const {
82+
size_t max_size = std::max(bits_.size(), other.bits_.size());
83+
for (size_t i = 0; i < max_size; ++i) {
84+
BitContainer b1 = (i < bits_.size()) ? bits_[i] : 0;
85+
BitContainer b2 = (i < other.bits_.size()) ? other.bits_[i] : 0;
86+
if (b1 != b2) return false;
87+
}
88+
return true;
89+
}
90+
8191
} // namespace utils
8292
} // namespace spvtools

source/util/bit_vector.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,10 @@ class BitVector {
109109
// |this|. Return true if |this| changed.
110110
bool Or(const BitVector& that);
111111

112+
// Returns true if |this| and |that| have the same bits set.
113+
bool operator==(const BitVector& that) const;
114+
bool operator!=(const BitVector& that) const { return !(*this == that); }
115+
112116
private:
113117
std::vector<BitContainer> bits_;
114118
};

test/opt/ir_context_test.cpp

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -830,6 +830,194 @@ OpFunctionEnd)";
830830
EXPECT_FALSE(ctx->AreAnalysesValid(IRContext::kAnalysisDominatorAnalysis));
831831
}
832832

833+
TEST_F(IRContextTest, InvalidateCFGInvalidatesAll) {
834+
const std::string text = R"(
835+
OpCapability Shader
836+
OpCapability Linkage
837+
OpMemoryModel Logical GLSL450
838+
%1 = OpTypeVoid
839+
%2 = OpTypeFunction %1
840+
%3 = OpFunction %1 None %2
841+
%4 = OpLabel
842+
OpReturn
843+
OpFunctionEnd)";
844+
845+
std::unique_ptr<IRContext> ctx =
846+
BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
847+
SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
848+
849+
ctx->BuildInvalidAnalyses(
850+
IRContext::kAnalysisCFG | IRContext::kAnalysisDominatorAnalysis |
851+
IRContext::kAnalysisStructuredCFG | IRContext::kAnalysisLoopAnalysis |
852+
IRContext::kAnalysisScalarEvolution | IRContext::kAnalysisLiveness);
853+
854+
ctx->InvalidateAnalyses(IRContext::kAnalysisCFG);
855+
EXPECT_FALSE(ctx->AreAnalysesValid(IRContext::kAnalysisCFG));
856+
EXPECT_FALSE(ctx->AreAnalysesValid(IRContext::kAnalysisDominatorAnalysis));
857+
EXPECT_FALSE(ctx->AreAnalysesValid(IRContext::kAnalysisStructuredCFG));
858+
}
859+
860+
TEST_F(IRContextTest, InvalidateDominatorInvalidatesDownstreamOnly) {
861+
const std::string text = R"(
862+
OpCapability Shader
863+
OpCapability Linkage
864+
OpMemoryModel Logical GLSL450
865+
%1 = OpTypeVoid
866+
%2 = OpTypeFunction %1
867+
%3 = OpFunction %1 None %2
868+
%4 = OpLabel
869+
OpReturn
870+
OpFunctionEnd)";
871+
872+
std::unique_ptr<IRContext> ctx =
873+
BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
874+
SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
875+
876+
ctx->BuildInvalidAnalyses(
877+
IRContext::kAnalysisCFG | IRContext::kAnalysisDominatorAnalysis |
878+
IRContext::kAnalysisStructuredCFG | IRContext::kAnalysisLoopAnalysis |
879+
IRContext::kAnalysisScalarEvolution | IRContext::kAnalysisLiveness);
880+
881+
ctx->InvalidateAnalyses(IRContext::kAnalysisDominatorAnalysis);
882+
EXPECT_TRUE(ctx->AreAnalysesValid(IRContext::kAnalysisCFG));
883+
EXPECT_TRUE(ctx->AreAnalysesValid(IRContext::kAnalysisStructuredCFG));
884+
EXPECT_FALSE(ctx->AreAnalysesValid(IRContext::kAnalysisDominatorAnalysis));
885+
}
886+
887+
TEST_F(IRContextTest, InvalidateLoopInvalidatesDownstreamOnly) {
888+
const std::string text = R"(
889+
OpCapability Shader
890+
OpCapability Linkage
891+
OpMemoryModel Logical GLSL450
892+
%1 = OpTypeVoid
893+
%2 = OpTypeFunction %1
894+
%3 = OpFunction %1 None %2
895+
%4 = OpLabel
896+
OpReturn
897+
OpFunctionEnd)";
898+
899+
std::unique_ptr<IRContext> ctx =
900+
BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
901+
SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
902+
903+
ctx->BuildInvalidAnalyses(
904+
IRContext::kAnalysisCFG | IRContext::kAnalysisDominatorAnalysis |
905+
IRContext::kAnalysisStructuredCFG | IRContext::kAnalysisLoopAnalysis |
906+
IRContext::kAnalysisScalarEvolution | IRContext::kAnalysisLiveness);
907+
908+
ctx->InvalidateAnalyses(IRContext::kAnalysisLoopAnalysis);
909+
EXPECT_TRUE(ctx->AreAnalysesValid(IRContext::kAnalysisCFG));
910+
EXPECT_TRUE(ctx->AreAnalysesValid(IRContext::kAnalysisDominatorAnalysis));
911+
EXPECT_TRUE(ctx->AreAnalysesValid(IRContext::kAnalysisStructuredCFG));
912+
EXPECT_FALSE(ctx->AreAnalysesValid(IRContext::kAnalysisLoopAnalysis));
913+
EXPECT_FALSE(ctx->AreAnalysesValid(IRContext::kAnalysisScalarEvolution));
914+
EXPECT_FALSE(ctx->AreAnalysesValid(IRContext::kAnalysisLiveness));
915+
}
916+
917+
#ifdef SPIRV_CHECK_CONTEXT
918+
TEST_F(IRContextTest, IsConsistentCatchesInconsistentDominator) {
919+
const std::string text = R"(
920+
OpCapability Shader
921+
OpCapability Linkage
922+
OpMemoryModel Logical GLSL450
923+
%1 = OpTypeVoid
924+
%2 = OpTypeFunction %1
925+
%3 = OpFunction %1 None %2
926+
%4 = OpLabel
927+
OpReturn
928+
OpFunctionEnd)";
929+
930+
std::unique_ptr<IRContext> ctx =
931+
BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
932+
SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
933+
934+
ASSERT_TRUE(ctx->module()->begin() != ctx->module()->end());
935+
Function* func = &*ctx->module()->begin();
936+
937+
// Computes and caches dominator analysis
938+
auto* dom = ctx->GetDominatorAnalysis(func);
939+
EXPECT_TRUE(ctx->IsConsistent());
940+
941+
// Mess up the cached tree
942+
dom->GetDomTree().ClearTree();
943+
944+
// It should catch the inconsistency
945+
EXPECT_FALSE(ctx->IsConsistent());
946+
}
947+
948+
TEST_F(IRContextTest, IsConsistentCatchesInconsistentStructuredCFG) {
949+
const std::string text = R"(
950+
OpCapability Shader
951+
%1 = OpExtInstImport "GLSL.std.450"
952+
OpMemoryModel Logical GLSL450
953+
OpEntryPoint Fragment %4 "main"
954+
OpExecutionMode %4 OriginUpperLeft
955+
%2 = OpTypeVoid
956+
%3 = OpTypeFunction %2
957+
%11 = OpTypeBool
958+
%true = OpConstantTrue %11
959+
%4 = OpFunction %2 None %3
960+
%5 = OpLabel
961+
OpSelectionMerge %10 None
962+
OpBranchConditional %true %8 %9
963+
%8 = OpLabel
964+
OpBranch %10
965+
%9 = OpLabel
966+
OpBranch %10
967+
%10 = OpLabel
968+
OpReturn
969+
OpFunctionEnd
970+
)";
971+
972+
std::unique_ptr<IRContext> ctx =
973+
BuildModule(SPV_ENV_UNIVERSAL_1_3, nullptr, text,
974+
SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
975+
976+
// Build and cache structured CFG analysis
977+
ctx->BuildInvalidAnalyses(IRContext::kAnalysisStructuredCFG);
978+
EXPECT_TRUE(ctx->IsConsistent());
979+
980+
// Modify the merge block operand of the selection merge instruction without
981+
// invalidating the analysis
982+
Instruction* merge_inst = ctx->cfg()->block(5)->GetMergeInst();
983+
ASSERT_NE(merge_inst, nullptr);
984+
// Change merge block target from %10 to %8 (operand 0 is %10)
985+
merge_inst->SetInOperand(0, {8});
986+
987+
// It should catch the inconsistency
988+
EXPECT_FALSE(ctx->IsConsistent());
989+
}
990+
#else
991+
TEST_F(IRContextTest, IsConsistentAlwaysTrueWithoutMacro) {
992+
const std::string text = R"(
993+
OpCapability Shader
994+
OpCapability Linkage
995+
OpMemoryModel Logical GLSL450
996+
%1 = OpTypeVoid
997+
%2 = OpTypeFunction %1
998+
%3 = OpFunction %1 None %2
999+
%4 = OpLabel
1000+
OpReturn
1001+
OpFunctionEnd)";
1002+
1003+
std::unique_ptr<IRContext> ctx =
1004+
BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
1005+
SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
1006+
1007+
ASSERT_TRUE(ctx->module()->begin() != ctx->module()->end());
1008+
Function* func = &*ctx->module()->begin();
1009+
1010+
auto* dom = ctx->GetDominatorAnalysis(func);
1011+
EXPECT_TRUE(ctx->IsConsistent());
1012+
1013+
// Mess up the cached tree
1014+
dom->GetDomTree().ClearTree();
1015+
1016+
// Without SPIRV_CHECK_CONTEXT it should still return true
1017+
EXPECT_TRUE(ctx->IsConsistent());
1018+
}
1019+
#endif
1020+
8331021
TEST_F(IRContextTest, AsanErrorTest) {
8341022
std::string shader = R"(
8351023
OpCapability Shader

0 commit comments

Comments
 (0)