From 1d2f08b4ade22b03239d93ef0e33505aef2f6371 Mon Sep 17 00:00:00 2001 From: David Neto Date: Tue, 29 Jun 2021 14:38:56 +0000 Subject: [PATCH] spirv-reader: flatten input IO arrays and matrices Bug: tint:912 Change-Id: I403d504b577bda7918a81d990da5a13ca2036971 Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/56141 Auto-Submit: David Neto Kokoro: Kokoro Reviewed-by: Ben Clayton Reviewed-by: James Price --- src/reader/spirv/function.cc | 151 +++++-- src/reader/spirv/function.h | 27 ++ .../spirv/parser_impl_module_var_test.cc | 399 +++++++++++++++++- 3 files changed, 539 insertions(+), 38 deletions(-) diff --git a/src/reader/spirv/function.cc b/src/reader/spirv/function.cc index c8b4df0a5b..43466e50de 100644 --- a/src/reader/spirv/function.cc +++ b/src/reader/spirv/function.cc @@ -935,6 +935,101 @@ ast::BlockStatement* FunctionEmitter::MakeFunctionBody() { return body; } +bool FunctionEmitter::EmitInputParameter(std::string var_name, + const Type* var_type, + ast::DecorationList* decos, + std::vector index_prefix, + const Type* tip_type, + const Type* forced_param_type, + ast::VariableList* params, + ast::StatementList* statements) { + tip_type = tip_type->UnwrapAlias(); + if (auto* ref_type = tip_type->As()) { + tip_type = ref_type->type; + } + + if (auto* matrix_type = tip_type->As()) { + index_prefix.push_back(0); + const auto num_columns = static_cast(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 (!EmitInputParameter(var_name, var_type, decos, index_prefix, vec_ty, + forced_param_type, params, statements)) { + return false; + } + } + return success(); + } else if (auto* array_type = tip_type->As()) { + 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(array_type->size); i++) { + index_prefix.back() = i; + if (!EmitInputParameter(var_name, var_type, decos, index_prefix, elem_ty, + forced_param_type, params, statements)) { + return false; + } + } + return success(); + } + + const bool is_builtin = ast::HasDecoration(*decos); + + const Type* param_type = is_builtin ? forced_param_type : tip_type; + + const auto param_name = namer_.MakeDerivedName(var_name + "_param"); + // Create the parameter. + // 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. + params->push_back( + builder_.Param(param_name, param_type->Build(builder_), *decos)); + + // Add a body statement to copy the parameter to the corresponding private + // variable. + ast::Expression* param_value = builder_.Expr(param_name); + ast::Expression* store_dest = builder_.Expr(var_name); + + // Index into the LHS as needed. + auto* current_type = var_type->UnwrapAlias()->UnwrapRef()->UnwrapAlias(); + for (auto index : index_prefix) { + if (auto* matrix_type = current_type->As()) { + store_dest = builder_.IndexAccessor(store_dest, builder_.Expr(index)); + current_type = ty_.Vector(matrix_type->type, matrix_type->rows); + } else if (auto* array_type = current_type->As()) { + store_dest = builder_.IndexAccessor(store_dest, builder_.Expr(index)); + current_type = array_type->type->UnwrapAlias(); + } + } + + if (is_builtin && (tip_type != forced_param_type)) { + // The parameter will have the WGSL type, but we need bitcast to + // the variable store type. + param_value = + create(tip_type->Build(builder_), param_value); + } + + statements->push_back(builder_.Assign(store_dest, param_value)); + + // Increment the location attribute, in case more parameters will follow. + for (auto*& deco : *decos) { + if (auto* loc_deco = deco->As()) { + // 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; @@ -954,9 +1049,9 @@ bool FunctionEmitter::EmitEntryPointAsWrapper() { TINT_ASSERT(Reader, var != nullptr); TINT_ASSERT(Reader, var->opcode() == SpvOpVariable); auto* store_type = GetVariableStoreType(*var); - auto* forced_store_type = store_type; + auto* forced_param_type = store_type; ast::DecorationList param_decos; - if (!parser_impl_.ConvertDecorationsForVariable(var_id, &forced_store_type, + if (!parser_impl_.ConvertDecorationsForVariable(var_id, &forced_param_type, ¶m_decos, true)) { // This occurs, and is not an error, for the PointSize builtin. if (!success()) { @@ -966,49 +1061,31 @@ bool FunctionEmitter::EmitEntryPointAsWrapper() { continue; } - // In Vulkan SPIR-V, Input variables must not have an initializer. + // We don't have to handle initializers because in Vulkan SPIR-V, Input + // variables must not have them. const auto var_name = namer_.GetName(var_id); const auto var_sym = builder_.Symbols().Register(var_name); - const auto param_name = namer_.MakeDerivedName(var_name + "_param"); - const auto param_sym = builder_.Symbols().Register(param_name); - auto* param = create( - source, param_sym, ast::StorageClass::kNone, ast::Access::kUndefined, - forced_store_type->Build(builder_), true /* is const */, - nullptr /* no constructor */, param_decos); - decl.params.push_back(param); - // Add a body statement to copy the parameter to the corresponding private - // variable. - ast::Expression* param_value = - create(source, param_sym); - ast::Expression* store_dest = - create(source, var_sym); + bool ok = true; if (HasBuiltinSampleMask(param_decos)) { // In Vulkan SPIR-V, the sample mask is an array. In WGSL it's a scalar. // Use the first element only. - store_dest = create( - source, store_dest, parser_impl_.MakeNullValue(ty_.I32())); - if (const auto* arr_ty = store_type->UnwrapAlias()->As()) { - if (arr_ty->type->IsSignedScalarOrVector()) { - // sample_mask is unsigned in WGSL. Bitcast it. - param_value = create( - source, ty_.I32()->Build(builder_), param_value); - } - } else { - // Vulkan SPIR-V requires this. Validation should have failed already. - return Fail() - << "expected SampleMask to be an array of integer scalars"; - } - } else if (forced_store_type != store_type) { - // The parameter will have the WGSL type, but we need to add - // a bitcast to the variable store type. - param_value = create( - source, store_type->Build(builder_), param_value); + auto* sample_mask_array_type = + store_type->UnwrapRef()->UnwrapAlias()->As(); + 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); + } else { + // The normal path. + ok = + EmitInputParameter(var_name, store_type, ¶m_decos, {}, store_type, + forced_param_type, &(decl.params), &stmts); + } + if (!ok) { + return false; } - - stmts.push_back( - create(source, store_dest, param_value)); } // Call the inner function. It has no parameters. diff --git a/src/reader/spirv/function.h b/src/reader/spirv/function.h index bdbe5e282c..b4319ed13b 100644 --- a/src/reader/spirv/function.h +++ b/src/reader/spirv/function.h @@ -410,6 +410,33 @@ class FunctionEmitter { /// @returns false if emission failed. bool EmitEntryPointAsWrapper(); + /// Creates one or more entry point input parameters corresponding to a + /// part of an input variable. The part of the input variable is specfied + /// by the `index_prefix`, which successively indexes into the variable. + /// Also generates the assignment statements that copy the input parameter + /// to the corresponding part of 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_param_type The type forced by WGSL, if the variable is a + /// builtin, otherwise the same as var_type. + /// @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, + const Type* var_type, + ast::DecorationList* decos, + std::vector index_prefix, + const Type* tip_type, + const Type* forced_param_type, + ast::VariableList* params, + ast::StatementList* statements); + /// Create an ast::BlockStatement representing the body of the function. /// This creates the statement stack, which is non-empty for the lifetime /// of the function. diff --git a/src/reader/spirv/parser_impl_module_var_test.cc b/src/reader/spirv/parser_impl_module_var_test.cc index c32fb8f105..684ee82b4e 100644 --- a/src/reader/spirv/parser_impl_module_var_test.cc +++ b/src/reader/spirv/parser_impl_module_var_test.cc @@ -6288,7 +6288,404 @@ TEST_F(SpvModuleScopeVarParserTest, EXPECT_EQ(got, expected) << got; } -// TODO(dneto): pipeline IO: flatten structures, and distribute locations +TEST_F(SpvModuleScopeVarParserTest, Input_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 Input %arr + + %1 = OpVariable %11 Input + + %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{[[ BuiltinDecoration{position} + ]] x_2: __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} + ( + VariableConst{ + Decorations{ + LocationDecoration{4} + } + x_1_param + none + undefined + __f32 + } + VariableConst{ + Decorations{ + LocationDecoration{5} + } + x_1_param_1 + none + undefined + __f32 + } + VariableConst{ + Decorations{ + LocationDecoration{6} + } + x_1_param_2 + none + undefined + __f32 + } + ) + { + Assignment{ + ArrayAccessor[not set]{ + Identifier[not set]{x_1} + ScalarConstructor[not set]{0} + } + Identifier[not set]{x_1_param} + } + Assignment{ + ArrayAccessor[not set]{ + Identifier[not set]{x_1} + ScalarConstructor[not set]{1} + } + Identifier[not set]{x_1_param_1} + } + Assignment{ + ArrayAccessor[not set]{ + Identifier[not set]{x_1} + ScalarConstructor[not set]{2} + } + Identifier[not set]{x_1_param_2} + } + Call[not set]{ + Identifier[not set]{main_1} + ( + ) + } + Return{ + { + TypeConstructor[not set]{ + __type_name_main_out + Identifier[not set]{x_2} + } + } + } + } +} +)"; + EXPECT_EQ(got, expected) << got; +} + +TEST_F(SpvModuleScopeVarParserTest, Input_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 Input %m2v4float + + %1 = OpVariable %11 Input + + %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{[[ BuiltinDecoration{position} + ]] x_2: __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} + ( + VariableConst{ + Decorations{ + LocationDecoration{9} + } + x_1_param + none + undefined + __vec_4__f32 + } + VariableConst{ + Decorations{ + LocationDecoration{10} + } + x_1_param_1 + none + undefined + __vec_4__f32 + } + ) + { + Assignment{ + ArrayAccessor[not set]{ + Identifier[not set]{x_1} + ScalarConstructor[not set]{0} + } + Identifier[not set]{x_1_param} + } + Assignment{ + ArrayAccessor[not set]{ + Identifier[not set]{x_1} + ScalarConstructor[not set]{1} + } + Identifier[not set]{x_1_param_1} + } + Call[not set]{ + Identifier[not set]{main_1} + ( + ) + } + Return{ + { + TypeConstructor[not set]{ + __type_name_main_out + Identifier[not set]{x_2} + } + } + } + } +} +)"; + EXPECT_EQ(got, expected) << got; +} + +TEST_F(SpvModuleScopeVarParserTest, Input_FlattenNested) { + const std::string assembly = R"( + OpCapability Shader + OpMemoryModel Logical Simple + OpEntryPoint Vertex %main "main" %1 %2 + OpDecorate %1 Location 7 + OpDecorate %2 BuiltIn Position + + %void = OpTypeVoid + %voidfn = OpTypeFunction %void + %float = OpTypeFloat 32 + %v4float = OpTypeVector %float 4 + %m2v4float = OpTypeMatrix %v4float 2 + %uint = OpTypeInt 32 0 + %uint_2 = OpConstant %uint 2 + + %arr = OpTypeArray %m2v4float %uint_2 + + %11 = OpTypePointer Input %arr + %1 = OpVariable %11 Input + + %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{[[ BuiltinDecoration{position} + ]] x_2: __vec_4__f32} + } + Variable{ + x_1 + private + undefined + __array__mat_4_2__f32_2 + } + Variable{ + x_2 + private + undefined + __vec_4__f32 + } + Function main_1 -> __void + () + { + Return{} + } + Function main -> __type_name_main_out + StageDecoration{vertex} + ( + VariableConst{ + Decorations{ + LocationDecoration{7} + } + x_1_param + none + undefined + __vec_4__f32 + } + VariableConst{ + Decorations{ + LocationDecoration{8} + } + x_1_param_1 + none + undefined + __vec_4__f32 + } + VariableConst{ + Decorations{ + LocationDecoration{9} + } + x_1_param_2 + none + undefined + __vec_4__f32 + } + VariableConst{ + Decorations{ + LocationDecoration{10} + } + x_1_param_3 + none + undefined + __vec_4__f32 + } + ) + { + Assignment{ + ArrayAccessor[not set]{ + ArrayAccessor[not set]{ + Identifier[not set]{x_1} + ScalarConstructor[not set]{0} + } + ScalarConstructor[not set]{0} + } + Identifier[not set]{x_1_param} + } + Assignment{ + ArrayAccessor[not set]{ + ArrayAccessor[not set]{ + Identifier[not set]{x_1} + ScalarConstructor[not set]{0} + } + ScalarConstructor[not set]{1} + } + Identifier[not set]{x_1_param_1} + } + Assignment{ + ArrayAccessor[not set]{ + ArrayAccessor[not set]{ + Identifier[not set]{x_1} + ScalarConstructor[not set]{1} + } + ScalarConstructor[not set]{0} + } + Identifier[not set]{x_1_param_2} + } + Assignment{ + ArrayAccessor[not set]{ + ArrayAccessor[not set]{ + Identifier[not set]{x_1} + ScalarConstructor[not set]{1} + } + ScalarConstructor[not set]{1} + } + Identifier[not set]{x_1_param_3} + } + Call[not set]{ + Identifier[not set]{main_1} + ( + ) + } + Return{ + { + TypeConstructor[not set]{ + __type_name_main_out + Identifier[not set]{x_2} + } + } + } + } +} +)"; + EXPECT_EQ(got, expected) << got; +} + +// TODO(dneto): flatting structures } // namespace } // namespace spirv