[ir] Validate construct for structure types
Check the number and type of arguments matches the structure members.
Change-Id: I61d4e16676a44edba01e9b31f3b55b64f8b3a465
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/191661
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 512ccc6..d7f00f1 100644
--- a/src/tint/lang/core/ir/validator.cc
+++ b/src/tint/lang/core/ir/validator.cc
@@ -338,6 +338,10 @@
/// @param call the call to validate
void CheckBuiltinCall(const BuiltinCall* call);
+ /// Validates the given construct
+ /// @param construct the construct to validate
+ void CheckConstruct(const Construct* construct);
+
/// Validates the given user call
/// @param call the call to validate
void CheckUserCall(const UserCall* call);
@@ -971,7 +975,7 @@
call, //
[&](const Bitcast*) {}, //
[&](const BuiltinCall* c) { CheckBuiltinCall(c); }, //
- [&](const Construct*) {}, //
+ [&](const Construct* c) { CheckConstruct(c); }, //
[&](const Convert*) {}, //
[&](const Discard*) {}, //
[&](const UserCall* c) { CheckUserCall(c); }, //
@@ -1000,6 +1004,32 @@
}
}
+void Validator::CheckConstruct(const Construct* construct) {
+ auto args = construct->Args();
+ if (args.IsEmpty()) {
+ // Zero-value constructors are valid for all constructible types.
+ return;
+ }
+
+ if (auto* str = construct->Result(0)->Type()->As<type::Struct>()) {
+ auto members = str->Members();
+ if (args.Length() != str->Members().Length()) {
+ AddError(construct) << "structure has " << members.Length()
+ << " members, but construct provides " << args.Length()
+ << " arguments";
+ return;
+ }
+ for (size_t i = 0; i < args.Length(); i++) {
+ if (args[i] && args[i]->Type() != members[i]->Type()) {
+ AddError(construct, Construct::kArgsOperandOffset + i)
+ << "sructure member " << i << " is of type "
+ << style::Type(members[i]->Type()->FriendlyName())
+ << ", but argument is of type " << style::Type(args[i]->Type()->FriendlyName());
+ }
+ }
+ }
+}
+
void Validator::CheckUserCall(const UserCall* call) {
if (call->Target()->Stage() != Function::PipelineStage::kUndefined) {
AddError(call, UserCall::kFunctionOperandOffset)
diff --git a/src/tint/lang/core/ir/validator_test.cc b/src/tint/lang/core/ir/validator_test.cc
index da74d0c..aaba721 100644
--- a/src/tint/lang/core/ir/validator_test.cc
+++ b/src/tint/lang/core/ir/validator_test.cc
@@ -30,13 +30,14 @@
#include <utility>
#include "gmock/gmock.h"
+#include "gtest/gtest.h"
#include "src/tint/lang/core/address_space.h"
#include "src/tint/lang/core/ir/builder.h"
#include "src/tint/lang/core/ir/function_param.h"
#include "src/tint/lang/core/ir/ir_helper_test.h"
#include "src/tint/lang/core/ir/validator.h"
-#include "src/tint/lang/core/type/array.h"
+#include "src/tint/lang/core/number.h"
#include "src/tint/lang/core/type/manager.h"
#include "src/tint/lang/core/type/matrix.h"
#include "src/tint/lang/core/type/memory_view.h"
@@ -462,6 +463,153 @@
)");
}
+TEST_F(IR_ValidatorTest, Construct_Struct_ZeroValue) {
+ auto* str_ty = ty.Struct(mod.symbols.New("MyStruct"), {
+ {mod.symbols.New("a"), ty.i32()},
+ {mod.symbols.New("b"), ty.u32()},
+ });
+
+ auto* f = b.Function("f", ty.void_());
+ b.Append(f->Block(), [&] {
+ b.Construct(str_ty);
+ b.Return(f);
+ });
+
+ auto res = ir::Validate(mod);
+ ASSERT_EQ(res, Success) << res.Failure();
+}
+
+TEST_F(IR_ValidatorTest, Construct_Struct_ValidArgs) {
+ auto* str_ty = ty.Struct(mod.symbols.New("MyStruct"), {
+ {mod.symbols.New("a"), ty.i32()},
+ {mod.symbols.New("b"), ty.u32()},
+ });
+
+ auto* f = b.Function("f", ty.void_());
+ b.Append(f->Block(), [&] {
+ b.Construct(str_ty, 1_i, 2_u);
+ b.Return(f);
+ });
+
+ auto res = ir::Validate(mod);
+ ASSERT_EQ(res, Success) << res.Failure();
+}
+
+TEST_F(IR_ValidatorTest, Construct_Struct_NotEnoughArgs) {
+ auto* str_ty = ty.Struct(mod.symbols.New("MyStruct"), {
+ {mod.symbols.New("a"), ty.i32()},
+ {mod.symbols.New("b"), ty.u32()},
+ });
+
+ auto* f = b.Function("f", ty.void_());
+ b.Append(f->Block(), [&] {
+ b.Construct(str_ty, 1_i);
+ b.Return(f);
+ });
+
+ auto res = ir::Validate(mod);
+ ASSERT_NE(res, Success);
+ EXPECT_EQ(res.Failure().reason.Str(),
+ R"(:8:19 error: construct: structure has 2 members, but construct provides 1 arguments
+ %2:MyStruct = construct 1i
+ ^^^^^^^^^
+
+:7:3 note: in block
+ $B1: {
+ ^^^
+
+note: # Disassembly
+MyStruct = struct @align(4) {
+ a:i32 @offset(0)
+ b:u32 @offset(4)
+}
+
+%f = func():void {
+ $B1: {
+ %2:MyStruct = construct 1i
+ ret
+ }
+}
+)");
+}
+
+TEST_F(IR_ValidatorTest, Construct_Struct_TooManyArgs) {
+ auto* str_ty = ty.Struct(mod.symbols.New("MyStruct"), {
+ {mod.symbols.New("a"), ty.i32()},
+ {mod.symbols.New("b"), ty.u32()},
+ });
+
+ auto* f = b.Function("f", ty.void_());
+ b.Append(f->Block(), [&] {
+ b.Construct(str_ty, 1_i, 2_u, 3_i);
+ b.Return(f);
+ });
+
+ auto res = ir::Validate(mod);
+ ASSERT_NE(res, Success);
+ EXPECT_EQ(res.Failure().reason.Str(),
+ R"(:8:19 error: construct: structure has 2 members, but construct provides 3 arguments
+ %2:MyStruct = construct 1i, 2u, 3i
+ ^^^^^^^^^
+
+:7:3 note: in block
+ $B1: {
+ ^^^
+
+note: # Disassembly
+MyStruct = struct @align(4) {
+ a:i32 @offset(0)
+ b:u32 @offset(4)
+}
+
+%f = func():void {
+ $B1: {
+ %2:MyStruct = construct 1i, 2u, 3i
+ ret
+ }
+}
+)");
+}
+
+TEST_F(IR_ValidatorTest, Construct_Struct_WrongArgType) {
+ auto* str_ty = ty.Struct(mod.symbols.New("MyStruct"), {
+ {mod.symbols.New("a"), ty.i32()},
+ {mod.symbols.New("b"), ty.u32()},
+ });
+
+ auto* f = b.Function("f", ty.void_());
+ b.Append(f->Block(), [&] {
+ b.Construct(str_ty, 1_i, 2_i);
+ b.Return(f);
+ });
+
+ auto res = ir::Validate(mod);
+ ASSERT_NE(res, Success);
+ EXPECT_EQ(
+ res.Failure().reason.Str(),
+ R"(:8:33 error: construct: sructure member 1 is of type 'u32', but argument is of type 'i32'
+ %2:MyStruct = construct 1i, 2i
+ ^^
+
+:7:3 note: in block
+ $B1: {
+ ^^^
+
+note: # Disassembly
+MyStruct = struct @align(4) {
+ a:i32 @offset(0)
+ b:u32 @offset(4)
+}
+
+%f = func():void {
+ $B1: {
+ %2:MyStruct = construct 1i, 2i
+ ret
+ }
+}
+)");
+}
+
TEST_F(IR_ValidatorTest, Block_NoTerminator) {
b.Function("my_func", ty.void_());