[ir][validation] Validate `var` Add validation for the `var` instruction. This CL checks that the `result` for the `var` is not `nullptr`. Bug: 1952 Change-Id: Ib68694c83f53c0315cb4b5861b759748df1aa500 Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/138221 Reviewed-by: Ben Clayton <bclayton@google.com> Commit-Queue: Dan Sinclair <dsinclair@chromium.org> Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/src/tint/ir/builder.h b/src/tint/ir/builder.h index 8db4098..8028d02 100644 --- a/src/tint/ir/builder.h +++ b/src/tint/ir/builder.h
@@ -76,14 +76,6 @@ using DisableIfVectorLike = utils::traits::EnableIf< !utils::IsVectorLike<utils::traits::Decay<utils::traits::NthTypeOf<0, TYPES..., void>>>>; - template <typename T> - T* Append(T* val) { - if (current_block_) { - current_block_->Append(val); - } - return val; - } - /// If set, any created instruction will be auto-appended to the block. ir::Block* current_block_ = nullptr; @@ -103,6 +95,18 @@ /// @returns the builder Builder With(Block* b) { return Builder(ir, b); } + /// Appends and returns the instruction @p val to the current block. If there is no current + /// block bound, then @p val is just returned. + /// @param val the instruction to append + /// @returns the instruction + template <typename T> + T* Append(T* val) { + if (current_block_) { + current_block_->Append(val); + } + return val; + } + /// @returns a new block ir::Block* Block();
diff --git a/src/tint/ir/disassembler.cc b/src/tint/ir/disassembler.cc index 7ff5cf5..e23f88e 100644 --- a/src/tint/ir/disassembler.cc +++ b/src/tint/ir/disassembler.cc
@@ -295,7 +295,11 @@ } void Disassembler::EmitValueWithType(Instruction* val) { - EmitValueWithType(val->Result()); + if (val->Result()) { + EmitValueWithType(val->Result()); + } else { + out_ << "undef"; + } } void Disassembler::EmitValueWithType(Value* val) {
diff --git a/src/tint/ir/validate.cc b/src/tint/ir/validate.cc index 34436e3..3f82487 100644 --- a/src/tint/ir/validate.cc +++ b/src/tint/ir/validate.cc
@@ -158,6 +158,7 @@ std::string("root block: invalid instruction: ") + inst->TypeInfo().name); continue; } + CheckVar(var); } } @@ -181,6 +182,25 @@ } void CheckInstruction(Instruction* inst) { + if (!inst->Alive()) { + AddError(inst, "destroyed instruction found in instruction list"); + } + if (inst->Result()) { + if (inst->Result()->Source() == nullptr) { + AddError(inst, "instruction result source is undefined"); + } else if (inst->Result()->Source() != inst) { + AddError(inst, "instruction result source has wrong instruction"); + } + } + + for (auto* op : inst->Operands()) { + // Note, a `nullptr` is a valid operand in some cases, like `var` so we can't just check + // for `nullptr` here. + if (op && !op->Alive()) { + AddError(inst, "instruction has undefined operand"); + } + } + tint::Switch( inst, // [&](Access* a) { CheckAccess(a); }, // @@ -194,7 +214,7 @@ [&](Swizzle*) {}, // [&](Terminator* b) { CheckTerminator(b); }, // [&](Unary*) {}, // - [&](Var*) {}, // + [&](Var* var) { CheckVar(var); }, // [&](Default) { AddError(std::string("missing validation of: ") + inst->TypeInfo().name); }); @@ -310,6 +330,18 @@ AddError(if_, If::kConditionOperandOffset, "if: condition must be a `bool` type"); } } + + void CheckVar(Var* var) { + if (var->Result() == nullptr) { + AddError(var, "var: result is a nullptr"); + } + + if (var->Result() && var->Initializer()) { + if (var->Initializer()->Type() != var->Result()->Type()->UnwrapPtr()) { + AddError(var, "var initializer has incorrect type"); + } + } + } }; // namespace } // namespace
diff --git a/src/tint/ir/validate_test.cc b/src/tint/ir/validate_test.cc index d49f553..2569415 100644 --- a/src/tint/ir/validate_test.cc +++ b/src/tint/ir/validate_test.cc
@@ -528,5 +528,189 @@ )"); } +TEST_F(IR_ValidateTest, Var_RootBlock_NullResult) { + auto* v = mod.instructions.Create<ir::Var>(nullptr); + b.RootBlock()->Append(v); + + auto res = ir::Validate(mod); + ASSERT_FALSE(res); + EXPECT_EQ(res.Failure().str(), R"(:2:11 error: var: result is a nullptr + undef = var + ^^^ + +:1:1 note: In block +%b1 = block { # root +^^^^^^^^^^^ + +note: # Disassembly +%b1 = block { # root + undef = var +} + +)"); +} + +TEST_F(IR_ValidateTest, Var_Function_NullResult) { + auto* v = mod.instructions.Create<ir::Var>(nullptr); + + auto* f = b.Function("my_func", ty.void_()); + mod.functions.Push(f); + + auto sb = b.With(f->StartTarget()); + sb.Append(v); + sb.Return(f); + + auto res = ir::Validate(mod); + ASSERT_FALSE(res); + EXPECT_EQ(res.Failure().str(), R"(:3:13 error: var: result is a nullptr + undef = var + ^^^ + +:2:3 note: In block + %b1 = block { + ^^^^^^^^^^^ + +note: # Disassembly +%my_func = func():void -> %b1 { + %b1 = block { + undef = var + ret + } +} +)"); +} + +TEST_F(IR_ValidateTest, Var_Init_WrongType) { + auto* f = b.Function("my_func", ty.void_()); + mod.functions.Push(f); + + auto sb = b.With(f->StartTarget()); + auto* v = sb.Var(ty.ptr<function, f32>()); + sb.Return(f); + + auto* result = sb.InstructionResult(ty.i32()); + v->SetInitializer(result); + + auto res = ir::Validate(mod); + ASSERT_FALSE(res); + EXPECT_EQ(res.Failure().str(), R"(:3:41 error: var initializer has incorrect type + %2:ptr<function, f32, read_write> = var, %3 + ^^^ + +:2:3 note: In block + %b1 = block { + ^^^^^^^^^^^ + +note: # Disassembly +%my_func = func():void -> %b1 { + %b1 = block { + %2:ptr<function, f32, read_write> = var, %3 + ret + } +} +)"); +} + +TEST_F(IR_ValidateTest, Instruction_AppendedDead) { + auto* f = b.Function("my_func", ty.void_()); + mod.functions.Push(f); + + auto sb = b.With(f->StartTarget()); + auto* v = sb.Var(ty.ptr<function, f32>()); + auto* ret = sb.Return(f); + + v->Destroy(); + v->InsertBefore(ret); + + auto res = ir::Validate(mod); + ASSERT_FALSE(res); + EXPECT_EQ(res.Failure().str(), R"(:3:41 error: destroyed instruction found in instruction list + %2:ptr<function, f32, read_write> = var + ^^^ + +:2:3 note: In block + %b1 = block { + ^^^^^^^^^^^ + +:3:41 error: instruction result source is undefined + %2:ptr<function, f32, read_write> = var + ^^^ + +:2:3 note: In block + %b1 = block { + ^^^^^^^^^^^ + +note: # Disassembly +%my_func = func():void -> %b1 { + %b1 = block { + %2:ptr<function, f32, read_write> = var + ret + } +} +)"); +} + +TEST_F(IR_ValidateTest, Instruction_NullSource) { + auto* f = b.Function("my_func", ty.void_()); + mod.functions.Push(f); + + auto sb = b.With(f->StartTarget()); + auto* v = sb.Var(ty.ptr<function, f32>()); + sb.Return(f); + + v->Result()->SetSource(nullptr); + + auto res = ir::Validate(mod); + ASSERT_FALSE(res); + EXPECT_EQ(res.Failure().str(), R"(:3:41 error: instruction result source is undefined + %2:ptr<function, f32, read_write> = var + ^^^ + +:2:3 note: In block + %b1 = block { + ^^^^^^^^^^^ + +note: # Disassembly +%my_func = func():void -> %b1 { + %b1 = block { + %2:ptr<function, f32, read_write> = var + ret + } +} +)"); +} + +TEST_F(IR_ValidateTest, Instruction_DeadOperand) { + auto* f = b.Function("my_func", ty.void_()); + mod.functions.Push(f); + + auto sb = b.With(f->StartTarget()); + auto* v = sb.Var(ty.ptr<function, f32>()); + sb.Return(f); + + auto* result = sb.InstructionResult(ty.f32()); + result->Destroy(); + v->SetInitializer(result); + + auto res = ir::Validate(mod); + ASSERT_FALSE(res); + EXPECT_EQ(res.Failure().str(), R"(:3:41 error: instruction has undefined operand + %2:ptr<function, f32, read_write> = var, %3 + ^^^ + +:2:3 note: In block + %b1 = block { + ^^^^^^^^^^^ + +note: # Disassembly +%my_func = func():void -> %b1 { + %b1 = block { + %2:ptr<function, f32, read_write> = var, %3 + ret + } +} +)"); +} + } // namespace } // namespace tint::ir