diff --git a/src/reader/spirv/function.cc b/src/reader/spirv/function.cc index 2a55512dff..41ac1a4910 100644 --- a/src/reader/spirv/function.cc +++ b/src/reader/spirv/function.cc @@ -747,6 +747,9 @@ FunctionEmitter::FunctionEmitter(ParserImpl* pi, namer_(pi->namer()), function_(function), i32_(builder_.create()), + u32_(builder_.create()), + sample_mask_in_id(0u), + sample_mask_out_id(0u), ep_info_(ep_info) { PushNewStatementBlock(nullptr, 0, nullptr); } @@ -2032,6 +2035,17 @@ TypedExpression FunctionEmitter::MakeExpression(uint32_t id) { Fail() << "unhandled use of a pointer to the SampleId builtin, with ID: " << id; return {}; + case SkipReason::kSampleMaskInBuiltinPointer: + Fail() + << "unhandled use of a pointer to the SampleMask builtin, with ID: " + << id; + return {}; + case SkipReason::kSampleMaskOutBuiltinPointer: + // The result type is always u32. + auto name = namer_.Name(sample_mask_out_id); + return TypedExpression{u32_, + create( + Source{}, builder_.Symbols().Register(name))}; } if (identifier_values_.count(id) || parser_impl_.IsScalarSpecConstant(id)) { auto name = namer_.Name(id); @@ -2968,25 +2982,41 @@ bool FunctionEmitter::EmitStatement(const spvtools::opt::Instruction& inst) { return true; case SpvOpStore: { - const auto ptr_id = inst.GetSingleWordInOperand(0); + auto ptr_id = inst.GetSingleWordInOperand(0); const auto value_id = inst.GetSingleWordInOperand(1); + auto rhs = MakeExpression(value_id); + // Handle exceptional cases - if (GetSkipReason(ptr_id) == SkipReason::kPointSizeBuiltinPointer) { - if (const auto* c = constant_mgr_->FindDeclaredConstant(value_id)) { - // If we're writing a constant 1.0, then skip the write. That's all - // that WebGPU handles. - auto* ct = c->type(); - if (ct->AsFloat() && (ct->AsFloat()->width() == 32) && - (c->GetFloat() == 1.0f)) { - // Don't store to PointSize - return true; + switch (GetSkipReason(ptr_id)) { + case SkipReason::kPointSizeBuiltinPointer: + if (const auto* c = constant_mgr_->FindDeclaredConstant(value_id)) { + // If we're writing a constant 1.0, then skip the write. That's all + // that WebGPU handles. + auto* ct = c->type(); + if (ct->AsFloat() && (ct->AsFloat()->width() == 32) && + (c->GetFloat() == 1.0f)) { + // Don't store to PointSize + return true; + } } - } - return Fail() << "cannot store a value other than constant 1.0 to " - "PointSize builtin: " - << inst.PrettyPrint(); + return Fail() << "cannot store a value other than constant 1.0 to " + "PointSize builtin: " + << inst.PrettyPrint(); + + case SkipReason::kSampleMaskOutBuiltinPointer: + ptr_id = sample_mask_out_id; + if (rhs.type != u32_) { + // WGSL requires sample_mask_out to be signed. + rhs = TypedExpression{ + u32_, create( + Source{}, u32_, ast::ExpressionList{rhs.expr})}; + } + break; + default: + break; } + const auto ptr_type_id = def_use_mgr_->GetDef(ptr_id)->type_id(); const auto& builtin_position_info = parser_impl_.GetBuiltInPositionInfo(); if (ptr_type_id == builtin_position_info.pointer_type_id) { @@ -2996,9 +3026,7 @@ bool FunctionEmitter::EmitStatement(const spvtools::opt::Instruction& inst) { } // Handle an ordinary store as an assignment. - // TODO(dneto): Order of evaluation? auto lhs = MakeExpression(ptr_id); - auto rhs = MakeExpression(value_id); AddStatement( create(Source{}, lhs.expr, rhs.expr)); return success(); @@ -3024,6 +3052,25 @@ bool FunctionEmitter::EmitStatement(const spvtools::opt::Instruction& inst) { Source{}, i32_, ast::ExpressionList{id_expr})}; return EmitConstDefinition(inst, expr); } + case SkipReason::kSampleMaskInBuiltinPointer: { + auto name = namer_.Name(sample_mask_in_id); + ast::Expression* id_expr = create( + Source{}, builder_.Symbols().Register(name)); + auto* load_result_type = parser_impl_.ConvertType(inst.type_id()); + ast::Expression* ast_expr = nullptr; + if (load_result_type == i32_) { + ast_expr = create( + Source{}, i32_, ast::ExpressionList{id_expr}); + } else if (load_result_type == u32_) { + ast_expr = id_expr; + } else { + return Fail() << "loading the whole SampleMask input array is not " + "supported: " + << inst.PrettyPrint(); + } + return EmitConstDefinition( + inst, TypedExpression{load_result_type, ast_expr}); + } default: break; } @@ -3495,9 +3542,8 @@ TypedExpression FunctionEmitter::MakeCompositeExtract( TypedExpression current_expr(MakeOperand(inst, 0)); auto make_index = [this, source](uint32_t literal) { - auto* type = create(); return create( - source, create(source, type, literal)); + source, create(source, u32_, literal)); }; const auto composite = inst.GetSingleWordInOperand(0); @@ -3655,8 +3701,8 @@ bool FunctionEmitter::RegisterSpecialBuiltInVariables() { for (auto& special_var : parser_impl_.special_builtins()) { const auto id = special_var.first; const auto builtin = special_var.second; - def_info_[id] = - std::make_unique(*(def_use_mgr_->GetDef(id)), 0, index); + const auto* var = def_use_mgr_->GetDef(id); + def_info_[id] = std::make_unique(*var, 0, index); ++index; auto& def = def_info_[id]; switch (builtin) { @@ -3666,6 +3712,19 @@ bool FunctionEmitter::RegisterSpecialBuiltInVariables() { case SpvBuiltInSampleId: def->skip = SkipReason::kSampleIdBuiltinPointer; break; + case SpvBuiltInSampleMask: { + // Distinguish between input and output variable. + const auto storage_class = + static_cast(var->GetSingleWordInOperand(0)); + if (storage_class == SpvStorageClassInput) { + sample_mask_in_id = id; + def->skip = SkipReason::kSampleMaskInBuiltinPointer; + } else { + sample_mask_out_id = id; + def->skip = SkipReason::kSampleMaskOutBuiltinPointer; + } + break; + } default: return Fail() << "unrecognized special builtin: " << int(builtin); } diff --git a/src/reader/spirv/function.h b/src/reader/spirv/function.h index 6f1ca6630d..d58c66fb70 100644 --- a/src/reader/spirv/function.h +++ b/src/reader/spirv/function.h @@ -44,6 +44,7 @@ #include "src/reader/spirv/parser_impl.h" #include "src/type/i32_type.h" #include "src/type/texture_type.h" +#include "src/type/u32_type.h" namespace tint { namespace reader { @@ -227,6 +228,14 @@ enum class SkipReason { /// `kSampleIdBuiltinPointer`: the value is a pointer to the SampleId builtin /// variable. Don't generate its address. kSampleIdBuiltinPointer, + + /// `kSampleMaskInBuiltinPointer`: the value is a pointer to the SampleMaskIn + /// builtin input variable. Don't generate its address. + kSampleMaskInBuiltinPointer, + + /// `kSampleMaskOutBuiltinPointer`: the value is a pointer to the SampleMask + /// builtin output variable. + kSampleMaskOutBuiltinPointer, }; /// Bookkeeping info for a SPIR-V ID defined in the function, or some @@ -335,6 +344,12 @@ inline std::ostream& operator<<(std::ostream& o, const DefInfo& di) { case SkipReason::kSampleIdBuiltinPointer: o << " skip:sampleid_pointer"; break; + case SkipReason::kSampleMaskInBuiltinPointer: + o << " skip:samplemaskin_pointer"; + break; + case SkipReason::kSampleMaskOutBuiltinPointer: + o << " skip:samplemaskout_pointer"; + break; } o << "}"; return o; @@ -1085,6 +1100,12 @@ class FunctionEmitter { Namer& namer_; const spvtools::opt::Function& function_; type::I32* const i32_; // The unique I32 type object. + type::U32* const u32_; // The unique U32 type object. + + // The SPIR-V ID for the SampleMask input variable. + uint32_t sample_mask_in_id; + // The SPIR-V ID for the SampleMask output variable. + uint32_t sample_mask_out_id; // A stack of statement lists. Each list is contained in a construct in // the next deeper element of stack. The 0th entry represents the statements diff --git a/src/reader/spirv/parser_impl.cc b/src/reader/spirv/parser_impl.cc index a72b47475e..8b8556f288 100644 --- a/src/reader/spirv/parser_impl.cc +++ b/src/reader/spirv/parser_impl.cc @@ -1231,6 +1231,31 @@ bool ParserImpl::EmitModuleScopeVariables() { return success_; } +// @param var_id SPIR-V id of an OpVariable, assumed to be pointer +// to an array +// @returns the IntConstant for the size of the array, or nullptr +const spvtools::opt::analysis::IntConstant* ParserImpl::GetArraySize( + uint32_t var_id) { + auto* var = def_use_mgr_->GetDef(var_id); + if (!var || var->opcode() != SpvOpVariable) { + return nullptr; + } + auto* ptr_type = def_use_mgr_->GetDef(var->type_id()); + if (!ptr_type || ptr_type->opcode() != SpvOpTypePointer) { + return nullptr; + } + auto* array_type = def_use_mgr_->GetDef(ptr_type->GetSingleWordInOperand(1)); + if (!array_type || array_type->opcode() != SpvOpTypeArray) { + return nullptr; + } + auto* size = constant_mgr_->FindDeclaredConstant( + array_type->GetSingleWordInOperand(1)); + if (!size) { + return nullptr; + } + return size->AsIntConstant(); +} + ast::Variable* ParserImpl::MakeVariable( uint32_t id, ast::StorageClass sc, @@ -1277,6 +1302,20 @@ ast::Variable* ParserImpl::MakeVariable( type = forced_type; } break; + case SpvBuiltInSampleMask: { + // In SPIR-V this is used for both input and output variable. + // The SPIR-V variable has store type of array of integer scalar, + // either signed or unsigned. + // WGSL requires the store type to be u32. + auto* size = GetArraySize(id); + if (!size || size->GetZeroExtendedValue() != 1) { + Fail() << "WGSL supports a sample mask of at most 32 bits. " + "SampleMask must be an array of 1 element."; + } + special_builtins_[id] = spv_builtin; + type = builder_.create(); + break; + } default: break; } diff --git a/src/reader/spirv/parser_impl.h b/src/reader/spirv/parser_impl.h index 3b14f7760f..bfe11de48d 100644 --- a/src/reader/spirv/parser_impl.h +++ b/src/reader/spirv/parser_impl.h @@ -287,6 +287,11 @@ class ParserImpl : Reader { /// @returns true if parser is still successful. bool EmitFunction(const spvtools::opt::Function& f); + /// Returns the integer constant for the array size of the given variable. + /// @param var_id SPIR-V ID for an array variable + /// @returns the integer constant for its array size, or nullptr. + const spvtools::opt::analysis::IntConstant* GetArraySize(uint32_t var_id); + /// Creates an AST Variable node for a SPIR-V ID, including any attached /// decorations, unless it's an ignorable builtin variable. /// @param id the SPIR-V result ID diff --git a/src/reader/spirv/parser_impl_module_var_test.cc b/src/reader/spirv/parser_impl_module_var_test.cc index 2fd4ea2fc2..21ed781d8c 100644 --- a/src/reader/spirv/parser_impl_module_var_test.cc +++ b/src/reader/spirv/parser_impl_module_var_test.cc @@ -2403,6 +2403,571 @@ TEST_F(SpvModuleScopeVarParserTest, SampleId_U32_FunctParam) { })")) << module_str; } +// Returns the start of a shader for testing SampleMask +// parameterized by store type. +std::string SampleMaskPreamble(std::string store_type) { + return R"( + OpCapability Shader + OpMemoryModel Logical Simple + OpEntryPoint Fragment %main "main" %1 + OpExecutionMode %main OriginUpperLeft + OpDecorate %1 BuiltIn SampleMask + %void = OpTypeVoid + %voidfn = OpTypeFunction %void + %float = OpTypeFloat 32 + %uint = OpTypeInt 32 0 + %int = OpTypeInt 32 1 + %int_12 = OpConstant %int 12 + %uint_0 = OpConstant %uint 0 + %uint_1 = OpConstant %uint 1 + %uint_2 = OpConstant %uint 2 + %uarr1 = OpTypeArray %uint %uint_1 + %uarr2 = OpTypeArray %uint %uint_2 + %iarr1 = OpTypeArray %int %uint_1 + %iarr2 = OpTypeArray %int %uint_2 + %iptr_in_ty = OpTypePointer Input %int + %uptr_in_ty = OpTypePointer Input %uint + %iptr_out_ty = OpTypePointer Output %int + %uptr_out_ty = OpTypePointer Output %uint + %in_ty = OpTypePointer Input )" + + store_type + R"( + %out_ty = OpTypePointer Output )" + + store_type + R"( +)"; +} + +TEST_F(SpvModuleScopeVarParserTest, SampleMask_In_ArraySize2_Error) { + const std::string assembly = SampleMaskPreamble("%uarr2") + R"( + %1 = OpVariable %in_ty Input + + %main = OpFunction %void None %voidfn + %entry = OpLabel + %2 = OpAccessChain %uptr_in_ty %1 %uint_0 + %3 = OpLoad %int %2 + OpReturn + OpFunctionEnd + )"; + auto p = parser(test::Assemble(assembly)); + ASSERT_FALSE(p->BuildAndParseInternalModule()); + EXPECT_THAT(p->error(), + HasSubstr("WGSL supports a sample mask of at most 32 bits. " + "SampleMask must be an array of 1 element")) + << p->error() << assembly; +} + +TEST_F(SpvModuleScopeVarParserTest, SampleMask_In_U32_Direct) { + const std::string assembly = SampleMaskPreamble("%uarr1") + R"( + %1 = OpVariable %in_ty Input + + %main = OpFunction %void None %voidfn + %entry = OpLabel + %2 = OpAccessChain %uptr_in_ty %1 %uint_0 + %3 = OpLoad %uint %2 + OpReturn + OpFunctionEnd + )"; + auto p = parser(test::Assemble(assembly)); + ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly; + EXPECT_TRUE(p->error().empty()); + const auto module_str = p->program().to_str(); + // Correct declaration + EXPECT_THAT(module_str, HasSubstr(R"( + Variable{ + Decorations{ + BuiltinDecoration{sample_mask_in} + } + x_1 + in + __u32 + })")); + + // Correct bodies + EXPECT_THAT(module_str, HasSubstr(R"( + VariableDeclStatement{ + VariableConst{ + x_3 + none + __u32 + { + Identifier[not set]{x_1} + } + } + })")) + << module_str; +} + +TEST_F(SpvModuleScopeVarParserTest, SampleMask_In_U32_CopyObject) { + const std::string assembly = SampleMaskPreamble("%uarr1") + R"( + %1 = OpVariable %in_ty Input + + %main = OpFunction %void None %voidfn + %entry = OpLabel + %2 = OpAccessChain %uptr_in_ty %1 %uint_0 + %3 = OpCopyObject %uptr_in_ty %2 + %4 = OpLoad %uint %3 + OpReturn + OpFunctionEnd + )"; + auto p = parser(test::Assemble(assembly)); + ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly; + EXPECT_TRUE(p->error().empty()); + const auto module_str = p->program().to_str(); + // Correct declaration + EXPECT_THAT(module_str, HasSubstr(R"( + Variable{ + Decorations{ + BuiltinDecoration{sample_mask_in} + } + x_1 + in + __u32 + })")); + + // Correct bodies + EXPECT_THAT(module_str, HasSubstr(R"( + VariableDeclStatement{ + VariableConst{ + x_4 + none + __u32 + { + Identifier[not set]{x_1} + } + } + })")) + << module_str; +} + +TEST_F(SpvModuleScopeVarParserTest, SampleMask_In_U32_AccessChain) { + const std::string assembly = SampleMaskPreamble("%uarr1") + R"( + %1 = OpVariable %in_ty Input + + %main = OpFunction %void None %voidfn + %entry = OpLabel + %2 = OpAccessChain %uptr_in_ty %1 %uint_0 + %3 = OpAccessChain %uptr_in_ty %2 + %4 = OpLoad %uint %3 + OpReturn + OpFunctionEnd + )"; + auto p = parser(test::Assemble(assembly)); + ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly; + EXPECT_TRUE(p->error().empty()); + const auto module_str = p->program().to_str(); + // Correct declaration + EXPECT_THAT(module_str, HasSubstr(R"( + Variable{ + Decorations{ + BuiltinDecoration{sample_mask_in} + } + x_1 + in + __u32 + })")); + + // Correct bodies + EXPECT_THAT(module_str, HasSubstr(R"( + VariableDeclStatement{ + VariableConst{ + x_4 + none + __u32 + { + Identifier[not set]{x_1} + } + } + })")) + << module_str; +} + +TEST_F(SpvModuleScopeVarParserTest, SampleMask_In_I32_Direct) { + const std::string assembly = SampleMaskPreamble("%iarr1") + R"( + %1 = OpVariable %in_ty Input + + %main = OpFunction %void None %voidfn + %entry = OpLabel + %2 = OpAccessChain %iptr_in_ty %1 %uint_0 + %3 = OpLoad %int %2 + OpReturn + OpFunctionEnd + )"; + auto p = parser(test::Assemble(assembly)); + ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly; + EXPECT_TRUE(p->error().empty()); + const auto module_str = p->program().to_str(); + // Correct declaration + EXPECT_THAT(module_str, HasSubstr(R"( + Variable{ + Decorations{ + BuiltinDecoration{sample_mask_in} + } + x_1 + in + __u32 + })")); + + // Correct bodies + EXPECT_THAT(module_str, HasSubstr(R"( + VariableDeclStatement{ + VariableConst{ + x_3 + none + __i32 + { + TypeConstructor[not set]{ + __i32 + Identifier[not set]{x_1} + } + } + } + })")) + << module_str; +} + +TEST_F(SpvModuleScopeVarParserTest, SampleMask_In_I32_CopyObject) { + const std::string assembly = SampleMaskPreamble("%iarr1") + R"( + %1 = OpVariable %in_ty Input + + %main = OpFunction %void None %voidfn + %entry = OpLabel + %2 = OpAccessChain %iptr_in_ty %1 %uint_0 + %3 = OpCopyObject %iptr_in_ty %2 + %4 = OpLoad %int %3 + OpReturn + OpFunctionEnd + )"; + auto p = parser(test::Assemble(assembly)); + ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly; + EXPECT_TRUE(p->error().empty()); + const auto module_str = p->program().to_str(); + // Correct declaration + EXPECT_THAT(module_str, HasSubstr(R"( + Variable{ + Decorations{ + BuiltinDecoration{sample_mask_in} + } + x_1 + in + __u32 + })")); + + // Correct bodies + EXPECT_THAT(module_str, HasSubstr(R"( + VariableDeclStatement{ + VariableConst{ + x_4 + none + __i32 + { + TypeConstructor[not set]{ + __i32 + Identifier[not set]{x_1} + } + } + } + })")) + << module_str; +} + +TEST_F(SpvModuleScopeVarParserTest, SampleMask_In_I32_AccessChain) { + const std::string assembly = SampleMaskPreamble("%iarr1") + R"( + %1 = OpVariable %in_ty Input + + %main = OpFunction %void None %voidfn + %entry = OpLabel + %2 = OpAccessChain %iptr_in_ty %1 %uint_0 + %3 = OpAccessChain %iptr_in_ty %2 + %4 = OpLoad %int %3 + OpReturn + OpFunctionEnd + )"; + auto p = parser(test::Assemble(assembly)); + ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly; + EXPECT_TRUE(p->error().empty()); + const auto module_str = p->program().to_str(); + // Correct declaration + EXPECT_THAT(module_str, HasSubstr(R"( + Variable{ + Decorations{ + BuiltinDecoration{sample_mask_in} + } + x_1 + in + __u32 + })")); + + // Correct bodies + EXPECT_THAT(module_str, HasSubstr(R"( + VariableDeclStatement{ + VariableConst{ + x_4 + none + __i32 + { + TypeConstructor[not set]{ + __i32 + Identifier[not set]{x_1} + } + } + } + })")) + << module_str; +} + +TEST_F(SpvModuleScopeVarParserTest, SampleMask_Out_ArraySize2_Error) { + const std::string assembly = SampleMaskPreamble("%uarr2") + R"( + %1 = OpVariable %out_ty Output + + %main = OpFunction %void None %voidfn + %entry = OpLabel + %2 = OpAccessChain %uptr_out_ty %1 %uint_0 + OpStore %2 %uint_0 + OpReturn + OpFunctionEnd + )"; + auto p = parser(test::Assemble(assembly)); + ASSERT_FALSE(p->BuildAndParseInternalModule()); + EXPECT_THAT(p->error(), + HasSubstr("WGSL supports a sample mask of at most 32 bits. " + "SampleMask must be an array of 1 element")) + << p->error() << assembly; +} + +TEST_F(SpvModuleScopeVarParserTest, SampleMask_Out_U32_Direct) { + const std::string assembly = SampleMaskPreamble("%uarr1") + R"( + %1 = OpVariable %out_ty Output + + %main = OpFunction %void None %voidfn + %entry = OpLabel + %2 = OpAccessChain %uptr_out_ty %1 %uint_0 + OpStore %2 %uint_0 + OpReturn + OpFunctionEnd + )"; + auto p = parser(test::Assemble(assembly)); + ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly; + EXPECT_TRUE(p->error().empty()); + const auto module_str = p->program().to_str(); + // Correct declaration + EXPECT_THAT(module_str, HasSubstr(R"( + Variable{ + Decorations{ + BuiltinDecoration{sample_mask_out} + } + x_1 + out + __u32 + })")); + + // Correct bodies + EXPECT_THAT(module_str, HasSubstr(R"( + Assignment{ + Identifier[not set]{x_1} + ScalarConstructor[not set]{0} + })")) + << module_str; +} + +TEST_F(SpvModuleScopeVarParserTest, SampleMask_Out_U32_CopyObject) { + const std::string assembly = SampleMaskPreamble("%uarr1") + R"( + %1 = OpVariable %out_ty Output + + %main = OpFunction %void None %voidfn + %entry = OpLabel + %2 = OpAccessChain %uptr_out_ty %1 %uint_0 + %3 = OpCopyObject %uptr_out_ty %2 + OpStore %2 %uint_0 + OpReturn + OpFunctionEnd + )"; + auto p = parser(test::Assemble(assembly)); + ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly; + EXPECT_TRUE(p->error().empty()); + const auto module_str = p->program().to_str(); + // Correct declaration + EXPECT_THAT(module_str, HasSubstr(R"( + Variable{ + Decorations{ + BuiltinDecoration{sample_mask_out} + } + x_1 + out + __u32 + })")); + + // Correct bodies + EXPECT_THAT(module_str, HasSubstr(R"( + Assignment{ + Identifier[not set]{x_1} + ScalarConstructor[not set]{0} + })")) + << module_str; +} + +TEST_F(SpvModuleScopeVarParserTest, SampleMask_Out_U32_AccessChain) { + const std::string assembly = SampleMaskPreamble("%uarr1") + R"( + %1 = OpVariable %out_ty Output + + %main = OpFunction %void None %voidfn + %entry = OpLabel + %2 = OpAccessChain %uptr_out_ty %1 %uint_0 + %3 = OpAccessChain %uptr_out_ty %2 + OpStore %2 %uint_0 + OpReturn + OpFunctionEnd + )"; + auto p = parser(test::Assemble(assembly)); + ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly; + EXPECT_TRUE(p->error().empty()); + const auto module_str = p->program().to_str(); + // Correct declaration + EXPECT_THAT(module_str, HasSubstr(R"( + Variable{ + Decorations{ + BuiltinDecoration{sample_mask_out} + } + x_1 + out + __u32 + })")); + + // Correct bodies + EXPECT_THAT(module_str, HasSubstr(R"( + Assignment{ + Identifier[not set]{x_1} + ScalarConstructor[not set]{0} + })")) + << module_str; +} + +TEST_F(SpvModuleScopeVarParserTest, SampleMask_Out_I32_Direct) { + const std::string assembly = SampleMaskPreamble("%iarr1") + R"( + %1 = OpVariable %out_ty Output + + %main = OpFunction %void None %voidfn + %entry = OpLabel + %2 = OpAccessChain %iptr_out_ty %1 %uint_0 + OpStore %2 %int_12 + OpReturn + OpFunctionEnd + )"; + auto p = parser(test::Assemble(assembly)); + ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly; + EXPECT_TRUE(p->error().empty()); + const auto module_str = p->program().to_str(); + // Correct declaration + EXPECT_THAT(module_str, HasSubstr(R"( + Variable{ + Decorations{ + BuiltinDecoration{sample_mask_out} + } + x_1 + out + __u32 + })")); + + // Correct bodies + EXPECT_THAT(module_str, HasSubstr(R"( + { + Assignment{ + Identifier[not set]{x_1} + TypeConstructor[not set]{ + __u32 + ScalarConstructor[not set]{12} + } + } + Return{} + })")) + << module_str; +} + +TEST_F(SpvModuleScopeVarParserTest, SampleMask_Out_I32_CopyObject) { + const std::string assembly = SampleMaskPreamble("%iarr1") + R"( + %1 = OpVariable %out_ty Output + + %main = OpFunction %void None %voidfn + %entry = OpLabel + %2 = OpAccessChain %iptr_out_ty %1 %uint_0 + %3 = OpCopyObject %iptr_out_ty %2 + OpStore %2 %int_12 + OpReturn + OpFunctionEnd + )"; + auto p = parser(test::Assemble(assembly)); + ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly; + EXPECT_TRUE(p->error().empty()); + const auto module_str = p->program().to_str(); + // Correct declaration + EXPECT_THAT(module_str, HasSubstr(R"( + Variable{ + Decorations{ + BuiltinDecoration{sample_mask_out} + } + x_1 + out + __u32 + })")); + + // Correct bodies + EXPECT_THAT(module_str, HasSubstr(R"( + { + Assignment{ + Identifier[not set]{x_1} + TypeConstructor[not set]{ + __u32 + ScalarConstructor[not set]{12} + } + } + Return{} + })")) + << module_str; +} + +TEST_F(SpvModuleScopeVarParserTest, SampleMask_Out_I32_AccessChain) { + const std::string assembly = SampleMaskPreamble("%iarr1") + R"( + %1 = OpVariable %out_ty Output + + %main = OpFunction %void None %voidfn + %entry = OpLabel + %2 = OpAccessChain %iptr_out_ty %1 %uint_0 + %3 = OpAccessChain %iptr_out_ty %2 + OpStore %2 %int_12 + OpReturn + OpFunctionEnd + )"; + auto p = parser(test::Assemble(assembly)); + ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly; + EXPECT_TRUE(p->error().empty()); + const auto module_str = p->program().to_str(); + // Correct declaration + EXPECT_THAT(module_str, HasSubstr(R"( + Variable{ + Decorations{ + BuiltinDecoration{sample_mask_out} + } + x_1 + out + __u32 + })")); + + // Correct bodies + EXPECT_THAT(module_str, HasSubstr(R"( + { + Assignment{ + Identifier[not set]{x_1} + TypeConstructor[not set]{ + __u32 + ScalarConstructor[not set]{12} + } + } + Return{} + })")) + << module_str; +} + +// TODO(dneto): Test passing pointer to SampleMask as function parameter, +// both input case and output case. + } // namespace } // namespace spirv } // namespace reader