[tint][ir][val] Reject discards outside of functions
While working on root causing the issue that led to this change I
found some other related issues that should be addressed. They have
been bundled up into this change:
- Run() is modified so that later validation passes don't run if
CheckStructureSoundness fails, since they assume that it passed.
- ContainingFunction returns nullptr if the input is in the root
block, which helps prevent ContainingEntryPoint from crashing in a
very confusing way.
Fixes: 382298307
Fixes: 382291447
Fixes: 382294238
Change-Id: Ibce8c5c52d6df960a54b0438cff7a07fe740c961
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/218174
Commit-Queue: dan sinclair <dsinclair@chromium.org>
Auto-Submit: Ryan Harrison <rharrison@chromium.org>
Reviewed-by: dan sinclair <dsinclair@chromium.org>
diff --git a/src/tint/lang/core/ir/validator.cc b/src/tint/lang/core/ir/validator.cc
index c96bda2..7b9e25a 100644
--- a/src/tint/lang/core/ir/validator.cc
+++ b/src/tint/lang/core/ir/validator.cc
@@ -1250,6 +1250,10 @@
/// @param inst the instruction
/// @returns the function
const ir::Function* ContainingFunction(const ir::Instruction* inst) {
+ if (inst->Block() == mod_.root_block) {
+ return nullptr;
+ }
+
return block_to_function_.GetOrAdd(inst->Block(), [&] { //
return ContainingFunction(inst->Block()->Parent());
});
@@ -1259,6 +1263,10 @@
/// @param f the function
/// @returns all end points that call the function
Hashset<const ir::Function*, 4> ContainingEndPoints(const ir::Function* f) {
+ if (!f) {
+ return {};
+ }
+
Hashset<const ir::Function*, 4> result{};
Hashset<const ir::Function*, 4> visited{f};
@@ -1266,6 +1274,10 @@
while (!call_sites.IsEmpty()) {
auto call_site = call_sites.Pop();
auto calling_function = ContainingFunction(call_site);
+ if (!calling_function) {
+ continue;
+ }
+
if (visited.Contains(calling_function)) {
continue;
}
@@ -1336,8 +1348,13 @@
Result<SuccessType> Validator::Run() {
RunStructuralSoundnessChecks();
- CheckForOrphanedInstructions();
- CheckForNonFragmentDiscards();
+ if (!diagnostics_.ContainsErrors()) {
+ CheckForOrphanedInstructions();
+ }
+
+ if (!diagnostics_.ContainsErrors()) {
+ CheckForNonFragmentDiscards();
+ }
if (diagnostics_.ContainsErrors()) {
diagnostics_.AddNote(Source{}) << "# Disassembly\n" << Disassemble().Text();
@@ -1861,6 +1878,9 @@
AddError(inst) << "root block: invalid instruction: " << inst->TypeInfo().name;
}
},
+ [&](const core::ir::Discard* d) {
+ AddError(d) << "root block: invalid instruction: " << d->TypeInfo().name;
+ },
[&](Default) {
// Note, this validation is looser then it could be. There are only certain
// expressions and builtins which can be used in an override. We can tighten this up
diff --git a/src/tint/lang/core/ir/validator_test.cc b/src/tint/lang/core/ir/validator_test.cc
index 5e4dc7b..1381505 100644
--- a/src/tint/lang/core/ir/validator_test.cc
+++ b/src/tint/lang/core/ir/validator_test.cc
@@ -3849,6 +3849,29 @@
)");
}
+TEST_F(IR_ValidatorTest, Discard_OutsideFunction) {
+ auto* d = b.Discard();
+ mod.root_block->Append(d);
+
+ auto res = ir::Validate(mod);
+ ASSERT_NE(res, Success);
+ EXPECT_EQ(res.Failure().reason.Str(),
+ R"(:2:3 error: discard: root block: invalid instruction: tint::core::ir::Discard
+ discard
+ ^^^^^^^
+
+:1:1 note: in block
+$B1: { # root
+^^^
+
+note: # Disassembly
+$B1: { # root
+ discard
+}
+
+)");
+}
+
TEST_F(IR_ValidatorTest, Terminator_HasResult) {
auto* ret_func = b.Function("ret_func", ty.void_());
b.Append(ret_func->Block(), [&] {