spirv-reader: flatten output matrix, array, struct
Bug: tint:912
Change-Id: Iebbcb7ea8d870cccadad7dd1ce8aaccf8965b370
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/56301
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: James Price <jrprice@google.com>
Auto-Submit: David Neto <dneto@google.com>
diff --git a/src/reader/spirv/function.cc b/src/reader/spirv/function.cc
index 55c0a27..85ab480 100644
--- a/src/reader/spirv/function.cc
+++ b/src/reader/spirv/function.cc
@@ -935,14 +935,15 @@
return body;
}
-bool FunctionEmitter::EmitInputParameter(std::string var_name,
- const Type* var_type,
- ast::DecorationList* decos,
- std::vector<int> index_prefix,
- const Type* tip_type,
- const Type* forced_param_type,
- ast::VariableList* params,
- ast::StatementList* statements) {
+bool FunctionEmitter::EmitPipelineInput(std::string var_name,
+ const Type* var_type,
+ ast::DecorationList* decos,
+ std::vector<int> index_prefix,
+ const Type* tip_type,
+ const Type* forced_param_type,
+ ast::VariableList* params,
+ ast::StatementList* statements) {
+ // TODO(dneto): Handle structs where the locations are annotated on members.
tip_type = tip_type->UnwrapAlias();
if (auto* ref_type = tip_type->As<Reference>()) {
tip_type = ref_type->type;
@@ -954,8 +955,8 @@
const Type* vec_ty = ty_.Vector(matrix_type->type, matrix_type->rows);
for (int col = 0; col < num_columns; col++) {
index_prefix.back() = col;
- if (!EmitInputParameter(var_name, var_type, decos, index_prefix, vec_ty,
- forced_param_type, params, statements)) {
+ if (!EmitPipelineInput(var_name, var_type, decos, index_prefix, vec_ty,
+ forced_param_type, params, statements)) {
return false;
}
}
@@ -968,8 +969,8 @@
const Type* elem_ty = array_type->type;
for (int i = 0; i < static_cast<int>(array_type->size); i++) {
index_prefix.back() = i;
- if (!EmitInputParameter(var_name, var_type, decos, index_prefix, elem_ty,
- forced_param_type, params, statements)) {
+ if (!EmitPipelineInput(var_name, var_type, decos, index_prefix, elem_ty,
+ forced_param_type, params, statements)) {
return false;
}
}
@@ -979,9 +980,9 @@
index_prefix.push_back(0);
for (int i = 0; i < static_cast<int>(members.size()); ++i) {
index_prefix.back() = i;
- if (!EmitInputParameter(var_name, var_type, decos, index_prefix,
- members[i], forced_param_type, params,
- statements)) {
+ if (!EmitPipelineInput(var_name, var_type, decos, index_prefix,
+ members[i], forced_param_type, params,
+ statements)) {
return false;
}
}
@@ -1047,6 +1048,120 @@
return success();
}
+bool FunctionEmitter::EmitPipelineOutput(std::string var_name,
+ const Type* var_type,
+ ast::DecorationList* decos,
+ std::vector<int> index_prefix,
+ const Type* tip_type,
+ const Type* forced_member_type,
+ ast::StructMemberList* return_members,
+ ast::ExpressionList* return_exprs) {
+ // TODO(dneto): Handle structs where the locations are annotated on members.
+ tip_type = tip_type->UnwrapAlias();
+ if (auto* ref_type = tip_type->As<Reference>()) {
+ tip_type = ref_type->type;
+ }
+
+ if (auto* matrix_type = tip_type->As<Matrix>()) {
+ index_prefix.push_back(0);
+ const auto num_columns = static_cast<int>(matrix_type->columns);
+ const Type* vec_ty = ty_.Vector(matrix_type->type, matrix_type->rows);
+ for (int col = 0; col < num_columns; col++) {
+ index_prefix.back() = col;
+ if (!EmitPipelineOutput(var_name, var_type, decos, index_prefix, vec_ty,
+ forced_member_type, return_members,
+ return_exprs)) {
+ return false;
+ }
+ }
+ return success();
+ } else if (auto* array_type = tip_type->As<Array>()) {
+ if (array_type->size == 0) {
+ return Fail() << "runtime-size array not allowed on pipeline IO";
+ }
+ index_prefix.push_back(0);
+ const Type* elem_ty = array_type->type;
+ for (int i = 0; i < static_cast<int>(array_type->size); i++) {
+ index_prefix.back() = i;
+ if (!EmitPipelineOutput(var_name, var_type, decos, index_prefix, elem_ty,
+ forced_member_type, return_members,
+ return_exprs)) {
+ return false;
+ }
+ }
+ return success();
+ } else if (auto* struct_type = tip_type->As<Struct>()) {
+ const auto& members = struct_type->members;
+ index_prefix.push_back(0);
+ for (int i = 0; i < static_cast<int>(members.size()); ++i) {
+ index_prefix.back() = i;
+ if (!EmitPipelineOutput(var_name, var_type, decos, index_prefix,
+ members[i], forced_member_type, return_members,
+ return_exprs)) {
+ return false;
+ }
+ }
+ return success();
+ }
+
+ const bool is_builtin = ast::HasDecoration<ast::BuiltinDecoration>(*decos);
+
+ const Type* member_type = is_builtin ? forced_member_type : tip_type;
+ // Derive the member name directly from the variable name. They can't
+ // collide.
+ const auto member_name = namer_.MakeDerivedName(var_name);
+ // Create the member.
+ // TODO(dneto): Note: If the parameter has non-location decorations,
+ // then those decoration AST nodes will be reused between multiple elements
+ // of a matrix, array, or structure. Normally that's disallowed but currently
+ // the SPIR-V reader will make duplicates when the entire AST is cloned
+ // at the top level of the SPIR-V reader flow. Consider rewriting this
+ // to avoid this node-sharing.
+ return_members->push_back(
+ builder_.Member(member_name, member_type->Build(builder_), *decos));
+
+ // Create an expression to evaluate the part of the variable indexed by
+ // the index_prefix.
+ ast::Expression* load_source = builder_.Expr(var_name);
+
+ // Index into the variable as needed to pick out the flattened member.
+ auto* current_type = var_type->UnwrapAlias()->UnwrapRef()->UnwrapAlias();
+ for (auto index : index_prefix) {
+ if (auto* matrix_type = current_type->As<Matrix>()) {
+ load_source = builder_.IndexAccessor(load_source, builder_.Expr(index));
+ current_type = ty_.Vector(matrix_type->type, matrix_type->rows);
+ } else if (auto* array_type = current_type->As<Array>()) {
+ load_source = builder_.IndexAccessor(load_source, builder_.Expr(index));
+ current_type = array_type->type->UnwrapAlias();
+ } else if (auto* struct_type = current_type->As<Struct>()) {
+ load_source = builder_.MemberAccessor(
+ load_source,
+ builder_.Expr(parser_impl_.GetMemberName(*struct_type, index)));
+ current_type = struct_type->members[index];
+ }
+ }
+
+ if (is_builtin && (tip_type != forced_member_type)) {
+ // The member will have the WGSL type, but we need bitcast to
+ // the variable store type.
+ load_source = create<ast::BitcastExpression>(
+ forced_member_type->Build(builder_), load_source);
+ }
+ return_exprs->push_back(load_source);
+
+ // Increment the location attribute, in case more parameters will follow.
+ for (auto*& deco : *decos) {
+ if (auto* loc_deco = deco->As<ast::LocationDecoration>()) {
+ // Replace this location decoration with a new one with one higher index.
+ // The old one doesn't leak because it's kept in the builder's AST node
+ // list.
+ deco = builder_.Location(loc_deco->source(), loc_deco->value() + 1);
+ }
+ }
+
+ return success();
+}
+
bool FunctionEmitter::EmitEntryPointAsWrapper() {
Source source;
@@ -1082,7 +1197,6 @@
// variables must not have them.
const auto var_name = namer_.GetName(var_id);
- const auto var_sym = builder_.Symbols().Register(var_name);
bool ok = true;
if (HasBuiltinSampleMask(param_decos)) {
@@ -1091,13 +1205,12 @@
auto* sample_mask_array_type =
store_type->UnwrapRef()->UnwrapAlias()->As<Array>();
TINT_ASSERT(Reader, sample_mask_array_type);
- ok = EmitInputParameter(var_name, store_type, ¶m_decos, {0},
- sample_mask_array_type->type, forced_param_type,
- &(decl.params), &stmts);
+ ok = EmitPipelineInput(var_name, store_type, ¶m_decos, {0},
+ sample_mask_array_type->type, forced_param_type,
+ &(decl.params), &stmts);
} else {
// The normal path.
- ok =
- EmitInputParameter(var_name, store_type, ¶m_decos, {}, store_type,
+ ok = EmitPipelineInput(var_name, store_type, ¶m_decos, {}, store_type,
forced_param_type, &(decl.params), &stmts);
}
if (!ok) {
@@ -1127,30 +1240,34 @@
const auto return_struct_sym =
builder_.Symbols().Register(return_struct_name);
+ // Define the structure.
+ std::vector<ast::StructMember*> return_members;
+ ast::ExpressionList return_exprs;
+
const auto& builtin_position_info = parser_impl_.GetBuiltInPositionInfo();
- // Define the structure.
- ast::ExpressionList return_exprs;
- std::vector<ast::StructMember*> return_members;
for (uint32_t var_id : ep_info_->outputs) {
- const Type* param_type = nullptr;
- const Type* store_type = nullptr;
- ast::DecorationList out_decos;
if (var_id == builtin_position_info.per_vertex_var_id) {
// The SPIR-V gl_PerVertex variable has already been remapped to
- // a gl_Position variable.
- // Substitute the type.
- store_type = param_type = ty_.Vector(ty_.F32(), 4);
- out_decos.emplace_back(
- create<ast::BuiltinDecoration>(source, ast::Builtin::kPosition));
+ // a gl_Position variable. Substitute the type.
+ const Type* param_type = ty_.Vector(ty_.F32(), 4);
+ ast::DecorationList out_decos{
+ create<ast::BuiltinDecoration>(source, ast::Builtin::kPosition)};
+
+ const auto var_name = namer_.GetName(var_id);
+ return_members.push_back(
+ builder_.Member(var_name, param_type->Build(builder_), out_decos));
+ return_exprs.push_back(builder_.Expr(var_name));
+
} else {
const auto* var = def_use_mgr_->GetDef(var_id);
TINT_ASSERT(Reader, var != nullptr);
TINT_ASSERT(Reader, var->opcode() == SpvOpVariable);
- store_type = GetVariableStoreType(*var);
- param_type = store_type;
- if (!parser_impl_.ConvertDecorationsForVariable(var_id, ¶m_type,
- &out_decos, true)) {
+ const Type* store_type = GetVariableStoreType(*var);
+ const Type* forced_member_type = store_type;
+ ast::DecorationList out_decos;
+ if (!parser_impl_.ConvertDecorationsForVariable(
+ var_id, &forced_member_type, &out_decos, true)) {
// This occurs, and is not an error, for the PointSize builtin.
if (!success()) {
// But exit early if an error was logged.
@@ -1158,45 +1275,29 @@
}
continue;
}
- }
- // TODO(dneto): flatten structs and arrays to vectors or scalars.
- // The Per-vertex structure is already flattened.
-
- // The member name is the same as the variable name, which is already
- // unique across all module-scope declarations.
- const auto var_name = namer_.GetName(var_id);
- const auto var_sym = builder_.Symbols().Register(var_name);
-
- // Form the member type.
- // Reuse the var name for the member name. They can't clash.
- ast::StructMember* return_member = create<ast::StructMember>(
- Source{}, var_sym, param_type->Build(builder_), out_decos);
- return_members.push_back(return_member);
-
- ast::Expression* return_member_value =
- create<ast::IdentifierExpression>(source, var_sym);
- if (HasBuiltinSampleMask(out_decos)) {
- // In Vulkan SPIR-V, the sample mask is an array. In WGSL it's a scalar.
- // Get the first element only.
- return_member_value = create<ast::ArrayAccessorExpression>(
- source, return_member_value, parser_impl_.MakeNullValue(ty_.I32()));
- if (const auto* arr_ty = store_type->UnwrapAlias()->As<Array>()) {
- if (arr_ty->type->IsSignedScalarOrVector()) {
- // sample_mask is unsigned in WGSL. Bitcast it.
- return_member_value = create<ast::BitcastExpression>(
- source, param_type->Build(builder_), return_member_value);
- }
+ const auto var_name = namer_.GetName(var_id);
+ bool ok = true;
+ if (HasBuiltinSampleMask(out_decos)) {
+ // In Vulkan SPIR-V, the sample mask is an array. In WGSL it's a
+ // scalar. Use the first element only.
+ auto* sample_mask_array_type =
+ store_type->UnwrapRef()->UnwrapAlias()->As<Array>();
+ TINT_ASSERT(Reader, sample_mask_array_type);
+ ok = EmitPipelineOutput(var_name, store_type, &out_decos, {0},
+ sample_mask_array_type->type,
+ forced_member_type, &return_members,
+ &return_exprs);
} else {
- // Vulkan SPIR-V requires this. Validation should have failed already.
- return Fail()
- << "expected SampleMask to be an array of integer scalars";
+ // The normal path.
+ ok = EmitPipelineOutput(var_name, store_type, &out_decos, {},
+ store_type, forced_member_type,
+ &return_members, &return_exprs);
}
- } else {
- // No other builtin outputs need signedness conversion.
+ if (!ok) {
+ return false;
+ }
}
- // Save the expression.
- return_exprs.push_back(return_member_value);
}
if (return_members.empty()) {
diff --git a/src/reader/spirv/function.h b/src/reader/spirv/function.h
index b4319ed..3a287a4 100644
--- a/src/reader/spirv/function.h
+++ b/src/reader/spirv/function.h
@@ -428,14 +428,43 @@
/// @param params The parameter list where the new parameter is appended.
/// @param statements The statement list where the assignment is appended.
/// @returns false if emission failed
- bool EmitInputParameter(std::string var_name,
+ bool EmitPipelineInput(std::string var_name,
+ const Type* var_type,
+ ast::DecorationList* decos,
+ std::vector<int> index_prefix,
+ const Type* tip_type,
+ const Type* forced_param_type,
+ ast::VariableList* params,
+ ast::StatementList* statements);
+
+ /// Creates one or more struct members from an output variable, and the
+ /// expressions that compute the value they contribute to the entry point
+ /// return value. The part of the output variable is specfied
+ /// by the `index_prefix`, which successively indexes into the variable.
+ /// Assumes the variable has already been created in the Private storage
+ /// class.
+ /// @param var_name The name of the variable
+ /// @param var_type The store type of the variable
+ /// @param decos The variable's decorations
+ /// @param index_prefix Indices stepping into the variable, indicating
+ /// what part of the variable to populate.
+ /// @param tip_type The type of the component inside variable, after indexing
+ /// with the indices in `index_prefix`.
+ /// @param forced_member_type The type forced by WGSL, if the variable is a
+ /// builtin, otherwise the same as var_type.
+ /// @param return_members The struct member list where the new member is
+ /// added.
+ /// @param return_exprs The expression list where the return expression is
+ /// added.
+ /// @returns false if emission failed
+ bool EmitPipelineOutput(std::string var_name,
const Type* var_type,
ast::DecorationList* decos,
std::vector<int> index_prefix,
const Type* tip_type,
- const Type* forced_param_type,
- ast::VariableList* params,
- ast::StatementList* statements);
+ const Type* forced_member_type,
+ ast::StructMemberList* return_members,
+ ast::ExpressionList* return_exprs);
/// Create an ast::BlockStatement representing the body of the function.
/// This creates the statement stack, which is non-empty for the lifetime
diff --git a/src/reader/spirv/parser_impl_module_var_test.cc b/src/reader/spirv/parser_impl_module_var_test.cc
index 4243eb5..78e1ea9 100644
--- a/src/reader/spirv/parser_impl_module_var_test.cc
+++ b/src/reader/spirv/parser_impl_module_var_test.cc
@@ -3373,7 +3373,7 @@
const std::string expected = R"(Module{
Struct main_out {
StructMember{[[ BuiltinDecoration{sample_mask}
- ]] x_1: __u32}
+ ]] x_1_1: __u32}
}
Variable{
x_1
@@ -3438,7 +3438,7 @@
const std::string expected = R"(Module{
Struct main_out {
StructMember{[[ BuiltinDecoration{sample_mask}
- ]] x_1: __u32}
+ ]] x_1_1: __u32}
}
Variable{
x_1
@@ -3503,7 +3503,7 @@
const std::string expected = R"(Module{
Struct main_out {
StructMember{[[ BuiltinDecoration{sample_mask}
- ]] x_1: __u32}
+ ]] x_1_1: __u32}
}
Variable{
x_1
@@ -3567,7 +3567,7 @@
const std::string expected = R"(Module{
Struct main_out {
StructMember{[[ BuiltinDecoration{sample_mask}
- ]] x_1: __u32}
+ ]] x_1_1: __u32}
}
Variable{
x_1
@@ -3634,7 +3634,7 @@
const std::string expected = R"(Module{
Struct main_out {
StructMember{[[ BuiltinDecoration{sample_mask}
- ]] x_1: __u32}
+ ]] x_1_1: __u32}
}
Variable{
x_1
@@ -3701,7 +3701,7 @@
const std::string expected = R"(Module{
Struct main_out {
StructMember{[[ BuiltinDecoration{sample_mask}
- ]] x_1: __u32}
+ ]] x_1_1: __u32}
}
Variable{
x_1
@@ -3844,7 +3844,7 @@
Arr_3 -> __array__i32_2_stride_4
Struct main_out {
StructMember{[[ BuiltinDecoration{sample_mask}
- ]] x_1: __u32}
+ ]] x_1_1: __u32}
}
Variable{
x_1
@@ -5549,9 +5549,9 @@
R"(
Struct main_out {
StructMember{[[ LocationDecoration{0}
- ]] x_2: __u32}
+ ]] x_2_1: __u32}
StructMember{[[ LocationDecoration{40}
- ]] x_4: __u32}
+ ]] x_4_1: __u32}
}
Variable{
x_1
@@ -5953,7 +5953,7 @@
const std::string expected = R"(Module{
Struct main_out {
StructMember{[[ BuiltinDecoration{sample_mask}
- ]] x_1: __u32}
+ ]] x_1_1: __u32}
}
Variable{
x_1
@@ -6026,7 +6026,7 @@
const std::string expected = R"(Module{
Struct main_out {
StructMember{[[ BuiltinDecoration{sample_mask}
- ]] x_1: __u32}
+ ]] x_1_1: __u32}
}
Variable{
x_1
@@ -6100,7 +6100,7 @@
const std::string expected = R"(Module{
Struct main_out {
StructMember{[[ BuiltinDecoration{frag_depth}
- ]] x_1: __f32}
+ ]] x_1_1: __f32}
}
Variable{
x_1
@@ -6326,7 +6326,7 @@
const std::string expected = R"(Module{
Struct main_out {
StructMember{[[ BuiltinDecoration{position}
- ]] x_2: __vec_4__f32}
+ ]] x_2_1: __vec_4__f32}
}
Variable{
x_1
@@ -6453,7 +6453,7 @@
const std::string expected = R"(Module{
Struct main_out {
StructMember{[[ BuiltinDecoration{position}
- ]] x_2: __vec_4__f32}
+ ]] x_2_1: __vec_4__f32}
}
Variable{
x_1
@@ -6573,7 +6573,7 @@
}
Struct main_out {
StructMember{[[ BuiltinDecoration{position}
- ]] x_2: __vec_4__f32}
+ ]] x_2_1: __vec_4__f32}
}
Variable{
x_1
@@ -6686,7 +6686,7 @@
const std::string expected = R"(Module{
Struct main_out {
StructMember{[[ BuiltinDecoration{position}
- ]] x_2: __vec_4__f32}
+ ]] x_2_1: __vec_4__f32}
}
Variable{
x_1
@@ -6805,7 +6805,292 @@
EXPECT_EQ(got, expected) << got;
}
-// TODO(dneto): flatting structures
+TEST_F(SpvModuleScopeVarParserTest, Output_FlattenArray_OneLevel) {
+ const std::string assembly = R"(
+ OpCapability Shader
+ OpMemoryModel Logical Simple
+ OpEntryPoint Vertex %main "main" %1 %2
+ OpDecorate %1 Location 4
+ OpDecorate %2 BuiltIn Position
+
+ %void = OpTypeVoid
+ %voidfn = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %v4float = OpTypeVector %float 4
+ %uint = OpTypeInt 32 0
+ %uint_0 = OpConstant %uint 0
+ %uint_1 = OpConstant %uint 1
+ %uint_3 = OpConstant %uint 3
+ %arr = OpTypeArray %float %uint_3
+ %11 = OpTypePointer Output %arr
+
+ %1 = OpVariable %11 Output
+
+ %12 = OpTypePointer Output %v4float
+ %2 = OpVariable %12 Output
+
+ %main = OpFunction %void None %voidfn
+ %entry = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+ auto p = parser(test::Assemble(assembly));
+
+ ASSERT_TRUE(p->Parse()) << p->error() << assembly;
+ EXPECT_TRUE(p->error().empty());
+
+ const auto got = p->program().to_str();
+ const std::string expected = R"(Module{
+ Struct main_out {
+ StructMember{[[ LocationDecoration{4}
+ ]] x_1_1: __f32}
+ StructMember{[[ LocationDecoration{5}
+ ]] x_1_2: __f32}
+ StructMember{[[ LocationDecoration{6}
+ ]] x_1_3: __f32}
+ StructMember{[[ BuiltinDecoration{position}
+ ]] x_2_1: __vec_4__f32}
+ }
+ Variable{
+ x_1
+ private
+ undefined
+ __array__f32_3
+ }
+ Variable{
+ x_2
+ private
+ undefined
+ __vec_4__f32
+ }
+ Function main_1 -> __void
+ ()
+ {
+ Return{}
+ }
+ Function main -> __type_name_main_out
+ StageDecoration{vertex}
+ ()
+ {
+ Call[not set]{
+ Identifier[not set]{main_1}
+ (
+ )
+ }
+ Return{
+ {
+ TypeConstructor[not set]{
+ __type_name_main_out
+ ArrayAccessor[not set]{
+ Identifier[not set]{x_1}
+ ScalarConstructor[not set]{0}
+ }
+ ArrayAccessor[not set]{
+ Identifier[not set]{x_1}
+ ScalarConstructor[not set]{1}
+ }
+ ArrayAccessor[not set]{
+ Identifier[not set]{x_1}
+ ScalarConstructor[not set]{2}
+ }
+ Identifier[not set]{x_2}
+ }
+ }
+ }
+ }
+}
+)";
+ EXPECT_EQ(got, expected) << got;
+}
+
+TEST_F(SpvModuleScopeVarParserTest, Output_FlattenMatrix) {
+ const std::string assembly = R"(
+ OpCapability Shader
+ OpMemoryModel Logical Simple
+ OpEntryPoint Vertex %main "main" %1 %2
+ OpDecorate %1 Location 9
+ OpDecorate %2 BuiltIn Position
+
+ %void = OpTypeVoid
+ %voidfn = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %v4float = OpTypeVector %float 4
+ %m2v4float = OpTypeMatrix %v4float 2
+ %uint = OpTypeInt 32 0
+
+ %11 = OpTypePointer Output %m2v4float
+
+ %1 = OpVariable %11 Output
+
+ %12 = OpTypePointer Output %v4float
+ %2 = OpVariable %12 Output
+
+ %main = OpFunction %void None %voidfn
+ %entry = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+ auto p = parser(test::Assemble(assembly));
+
+ ASSERT_TRUE(p->Parse()) << p->error() << assembly;
+ EXPECT_TRUE(p->error().empty());
+
+ const auto got = p->program().to_str();
+ const std::string expected = R"(Module{
+ Struct main_out {
+ StructMember{[[ LocationDecoration{9}
+ ]] x_1_1: __vec_4__f32}
+ StructMember{[[ LocationDecoration{10}
+ ]] x_1_2: __vec_4__f32}
+ StructMember{[[ BuiltinDecoration{position}
+ ]] x_2_1: __vec_4__f32}
+ }
+ Variable{
+ x_1
+ private
+ undefined
+ __mat_4_2__f32
+ }
+ Variable{
+ x_2
+ private
+ undefined
+ __vec_4__f32
+ }
+ Function main_1 -> __void
+ ()
+ {
+ Return{}
+ }
+ Function main -> __type_name_main_out
+ StageDecoration{vertex}
+ ()
+ {
+ Call[not set]{
+ Identifier[not set]{main_1}
+ (
+ )
+ }
+ Return{
+ {
+ TypeConstructor[not set]{
+ __type_name_main_out
+ ArrayAccessor[not set]{
+ Identifier[not set]{x_1}
+ ScalarConstructor[not set]{0}
+ }
+ ArrayAccessor[not set]{
+ Identifier[not set]{x_1}
+ ScalarConstructor[not set]{1}
+ }
+ Identifier[not set]{x_2}
+ }
+ }
+ }
+ }
+}
+)";
+ EXPECT_EQ(got, expected) << got;
+}
+
+TEST_F(SpvModuleScopeVarParserTest, Output_FlattenStruct) {
+ const std::string assembly = R"(
+ OpCapability Shader
+ OpMemoryModel Logical Simple
+ OpEntryPoint Vertex %main "main" %1 %2
+
+ OpName %strct "Communicators"
+ OpMemberName %strct 0 "alice"
+ OpMemberName %strct 1 "bob"
+
+ OpDecorate %1 Location 9
+ OpDecorate %2 BuiltIn Position
+
+
+ %void = OpTypeVoid
+ %voidfn = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %v4float = OpTypeVector %float 4
+ %strct = OpTypeStruct %float %v4float
+
+ %11 = OpTypePointer Output %strct
+
+ %1 = OpVariable %11 Output
+
+ %12 = OpTypePointer Output %v4float
+ %2 = OpVariable %12 Output
+
+ %main = OpFunction %void None %voidfn
+ %entry = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+ auto p = parser(test::Assemble(assembly));
+
+ ASSERT_TRUE(p->Parse()) << p->error() << assembly;
+ EXPECT_TRUE(p->error().empty());
+
+ const auto got = p->program().to_str();
+ const std::string expected = R"(Module{
+ Struct Communicators {
+ StructMember{alice: __f32}
+ StructMember{bob: __vec_4__f32}
+ }
+ Struct main_out {
+ StructMember{[[ LocationDecoration{9}
+ ]] x_1_1: __f32}
+ StructMember{[[ LocationDecoration{10}
+ ]] x_1_2: __vec_4__f32}
+ StructMember{[[ BuiltinDecoration{position}
+ ]] x_2_1: __vec_4__f32}
+ }
+ Variable{
+ x_1
+ private
+ undefined
+ __type_name_Communicators
+ }
+ Variable{
+ x_2
+ private
+ undefined
+ __vec_4__f32
+ }
+ Function main_1 -> __void
+ ()
+ {
+ Return{}
+ }
+ Function main -> __type_name_main_out
+ StageDecoration{vertex}
+ ()
+ {
+ Call[not set]{
+ Identifier[not set]{main_1}
+ (
+ )
+ }
+ Return{
+ {
+ TypeConstructor[not set]{
+ __type_name_main_out
+ MemberAccessor[not set]{
+ Identifier[not set]{x_1}
+ Identifier[not set]{alice}
+ }
+ MemberAccessor[not set]{
+ Identifier[not set]{x_1}
+ Identifier[not set]{bob}
+ }
+ Identifier[not set]{x_2}
+ }
+ }
+ }
+ }
+}
+)";
+ EXPECT_EQ(got, expected) << got;
+}
} // namespace
} // namespace spirv
diff --git a/test/bug/tint/749.spvasm.expected.hlsl b/test/bug/tint/749.spvasm.expected.hlsl
index f7e8761..0de0f40 100644
--- a/test/bug/tint/749.spvasm.expected.hlsl
+++ b/test/bug/tint/749.spvasm.expected.hlsl
@@ -1545,13 +1545,13 @@
}
struct main_out {
- float4 x_GLF_color;
+ float4 x_GLF_color_1;
};
struct tint_symbol_1 {
float4 gl_FragCoord_param : SV_Position;
};
struct tint_symbol_2 {
- float4 x_GLF_color : SV_Target0;
+ float4 x_GLF_color_1 : SV_Target0;
};
tint_symbol_2 main(tint_symbol_1 tint_symbol) {
@@ -1559,6 +1559,6 @@
gl_FragCoord = gl_FragCoord_param;
main_1();
const main_out tint_symbol_3 = {x_GLF_color};
- const tint_symbol_2 tint_symbol_83 = {tint_symbol_3.x_GLF_color};
+ const tint_symbol_2 tint_symbol_83 = {tint_symbol_3.x_GLF_color_1};
return tint_symbol_83;
}
diff --git a/test/bug/tint/749.spvasm.expected.msl b/test/bug/tint/749.spvasm.expected.msl
index 9e417f0..b11eedc 100644
--- a/test/bug/tint/749.spvasm.expected.msl
+++ b/test/bug/tint/749.spvasm.expected.msl
@@ -11,10 +11,10 @@
/* 0x0000 */ packed_float2 resolution;
};
struct main_out {
- float4 x_GLF_color;
+ float4 x_GLF_color_1;
};
struct tint_symbol_2 {
- float4 x_GLF_color [[color(0)]];
+ float4 x_GLF_color_1 [[color(0)]];
};
void swap_i1_i1_(thread int* const i, thread int* const j, thread QuicksortObject* const tint_symbol_83) {
@@ -1553,8 +1553,8 @@
thread float4 tint_symbol_91 = 0.0f;
tint_symbol_89 = gl_FragCoord_param;
main_1(x_188, &(tint_symbol_90), &(tint_symbol_89), &(tint_symbol_91));
- main_out const tint_symbol_3 = {.x_GLF_color=tint_symbol_91};
- tint_symbol_2 const tint_symbol_82 = {.x_GLF_color=tint_symbol_3.x_GLF_color};
+ main_out const tint_symbol_3 = {.x_GLF_color_1=tint_symbol_91};
+ tint_symbol_2 const tint_symbol_82 = {.x_GLF_color_1=tint_symbol_3.x_GLF_color_1};
return tint_symbol_82;
}
diff --git a/test/bug/tint/749.spvasm.expected.spvasm b/test/bug/tint/749.spvasm.expected.spvasm
index 1ad4312..2dc95c7 100644
--- a/test/bug/tint/749.spvasm.expected.spvasm
+++ b/test/bug/tint/749.spvasm.expected.spvasm
@@ -47,7 +47,7 @@
OpName %i_2 "i_2"
OpName %uv "uv"
OpName %main_out "main_out"
- OpMemberName %main_out 0 "x_GLF_color"
+ OpMemberName %main_out 0 "x_GLF_color_1"
OpName %tint_symbol_3 "tint_symbol_3"
OpName %tint_symbol_1 "tint_symbol_1"
OpName %main "main"
diff --git a/test/bug/tint/749.spvasm.expected.wgsl b/test/bug/tint/749.spvasm.expected.wgsl
index f24a266..ef02a83 100644
--- a/test/bug/tint/749.spvasm.expected.wgsl
+++ b/test/bug/tint/749.spvasm.expected.wgsl
@@ -1494,7 +1494,7 @@
struct main_out {
[[location(0)]]
- x_GLF_color : vec4<f32>;
+ x_GLF_color_1 : vec4<f32>;
};
[[stage(fragment)]]