// Copyright 2020 The Tint Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #include #include "gmock/gmock.h" #include "src/demangler.h" #include "src/reader/spirv/function.h" #include "src/reader/spirv/parser_impl.h" #include "src/reader/spirv/parser_impl_test_helper.h" #include "src/reader/spirv/spirv_tools_helpers_test.h" namespace tint { namespace reader { namespace spirv { namespace { using ::testing::Eq; using ::testing::HasSubstr; TEST_F(SpvParserTest, EmitStatement_StoreBoolConst) { auto p = parser(test::Assemble(R"( %void = OpTypeVoid %voidfn = OpTypeFunction %void %ty = OpTypeBool %true = OpConstantTrue %ty %false = OpConstantFalse %ty %null = OpConstantNull %ty %ptr_ty = OpTypePointer Function %ty %100 = OpFunction %void None %voidfn %entry = OpLabel %1 = OpVariable %ptr_ty Function OpStore %1 %true OpStore %1 %false OpStore %1 %null OpReturn OpFunctionEnd )")); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100)); EXPECT_TRUE(fe.EmitBody()) << p->error(); EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(Assignment{ Identifier[not set]{x_1} ScalarConstructor[not set]{true} } Assignment{ Identifier[not set]{x_1} ScalarConstructor[not set]{false} } Assignment{ Identifier[not set]{x_1} ScalarConstructor[not set]{false} })")); } TEST_F(SpvParserTest, EmitStatement_StoreUintConst) { auto p = parser(test::Assemble(R"( %void = OpTypeVoid %voidfn = OpTypeFunction %void %ty = OpTypeInt 32 0 %val = OpConstant %ty 42 %null = OpConstantNull %ty %ptr_ty = OpTypePointer Function %ty %100 = OpFunction %void None %voidfn %entry = OpLabel %1 = OpVariable %ptr_ty Function OpStore %1 %val OpStore %1 %null OpReturn )")); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100)); EXPECT_TRUE(fe.EmitBody()); EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(Assignment{ Identifier[not set]{x_1} ScalarConstructor[not set]{42} } Assignment{ Identifier[not set]{x_1} ScalarConstructor[not set]{0} })")); } TEST_F(SpvParserTest, EmitStatement_StoreIntConst) { auto p = parser(test::Assemble(R"( %void = OpTypeVoid %voidfn = OpTypeFunction %void %ty = OpTypeInt 32 1 %val = OpConstant %ty 42 %null = OpConstantNull %ty %ptr_ty = OpTypePointer Function %ty %100 = OpFunction %void None %voidfn %entry = OpLabel %1 = OpVariable %ptr_ty Function OpStore %1 %val OpStore %1 %null OpReturn )")); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100)); EXPECT_TRUE(fe.EmitBody()); EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(Assignment{ Identifier[not set]{x_1} ScalarConstructor[not set]{42} } Assignment{ Identifier[not set]{x_1} ScalarConstructor[not set]{0} })")); } TEST_F(SpvParserTest, EmitStatement_StoreFloatConst) { auto p = parser(test::Assemble(R"( %void = OpTypeVoid %voidfn = OpTypeFunction %void %ty = OpTypeFloat 32 %val = OpConstant %ty 42 %null = OpConstantNull %ty %ptr_ty = OpTypePointer Function %ty %100 = OpFunction %void None %voidfn %entry = OpLabel %1 = OpVariable %ptr_ty Function OpStore %1 %val OpStore %1 %null OpReturn )")); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100)); EXPECT_TRUE(fe.EmitBody()); EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(Assignment{ Identifier[not set]{x_1} ScalarConstructor[not set]{42.000000} } Assignment{ Identifier[not set]{x_1} ScalarConstructor[not set]{0.000000} })")); } TEST_F(SpvParserTest, EmitStatement_LoadBool) { auto p = parser(test::Assemble(R"( %void = OpTypeVoid %voidfn = OpTypeFunction %void %ty = OpTypeBool %true = OpConstantTrue %ty %false = OpConstantFalse %ty %null = OpConstantNull %ty %ptr_ty = OpTypePointer Function %ty %100 = OpFunction %void None %voidfn %entry = OpLabel %1 = OpVariable %ptr_ty Function %true %2 = OpLoad %ty %1 OpReturn OpFunctionEnd )")); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100)); EXPECT_TRUE(fe.EmitBody()) << p->error(); EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"( VariableConst{ x_2 none __bool { Identifier[not set]{x_1} } })")); } TEST_F(SpvParserTest, EmitStatement_LoadScalar) { auto p = parser(test::Assemble(R"( %void = OpTypeVoid %voidfn = OpTypeFunction %void %ty = OpTypeInt 32 0 %ty_42 = OpConstant %ty 42 %ptr_ty = OpTypePointer Function %ty %100 = OpFunction %void None %voidfn %entry = OpLabel %1 = OpVariable %ptr_ty Function %ty_42 %2 = OpLoad %ty %1 %3 = OpLoad %ty %1 OpReturn OpFunctionEnd )")); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100)); EXPECT_TRUE(fe.EmitBody()) << p->error(); EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(VariableDeclStatement{ VariableConst{ x_2 none __u32 { Identifier[not set]{x_1} } } } VariableDeclStatement{ VariableConst{ x_3 none __u32 { Identifier[not set]{x_1} } } })")); } TEST_F(SpvParserTest, EmitStatement_UseLoadedScalarTwice) { auto p = parser(test::Assemble(R"( %void = OpTypeVoid %voidfn = OpTypeFunction %void %ty = OpTypeInt 32 0 %ty_42 = OpConstant %ty 42 %ptr_ty = OpTypePointer Function %ty %100 = OpFunction %void None %voidfn %entry = OpLabel %1 = OpVariable %ptr_ty Function %ty_42 %2 = OpLoad %ty %1 OpStore %1 %2 OpStore %1 %2 OpReturn OpFunctionEnd )")); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100)); EXPECT_TRUE(fe.EmitBody()) << p->error(); EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(VariableDeclStatement{ VariableConst{ x_2 none __u32 { Identifier[not set]{x_1} } } } Assignment{ Identifier[not set]{x_1} Identifier[not set]{x_2} } Assignment{ Identifier[not set]{x_1} Identifier[not set]{x_2} } )")); } TEST_F(SpvParserTest, EmitStatement_StoreToModuleScopeVar) { auto p = parser(test::Assemble(R"( %void = OpTypeVoid %voidfn = OpTypeFunction %void %ty = OpTypeInt 32 0 %val = OpConstant %ty 42 %ptr_ty = OpTypePointer Workgroup %ty %1 = OpVariable %ptr_ty Workgroup %100 = OpFunction %void None %voidfn %entry = OpLabel OpStore %1 %val OpReturn )")); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100)); EXPECT_TRUE(fe.EmitBody()); EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(Assignment{ Identifier[not set]{x_1} ScalarConstructor[not set]{42} })")); } TEST_F(SpvParserTest, EmitStatement_AccessChain_NoOperands) { auto err = test::AssembleFailure(R"( %void = OpTypeVoid %voidfn = OpTypeFunction %void %ty = OpTypeInt 32 0 %val = OpConstant %ty 42 %ptr_ty = OpTypePointer Workgroup %ty %1 = OpVariable %ptr_ty Workgroup %100 = OpFunction %void None %voidfn %entry = OpLabel %2 = OpAccessChain %ptr_ty ; Needs a base operand OpStore %1 %val OpReturn )"); EXPECT_THAT(err, Eq("11:5: Expected operand, found next instruction instead.")); } TEST_F(SpvParserTest, EmitStatement_AccessChain_BaseIsNotPointer) { auto p = parser(test::Assemble(R"( %void = OpTypeVoid %voidfn = OpTypeFunction %void %10 = OpTypeInt 32 0 %val = OpConstant %10 42 %ptr_ty = OpTypePointer Workgroup %10 %20 = OpVariable %10 Workgroup ; bad pointer type %100 = OpFunction %void None %voidfn %entry = OpLabel %1 = OpAccessChain %ptr_ty %20 OpStore %1 %val OpReturn )")); EXPECT_FALSE(p->BuildAndParseInternalModuleExceptFunctions()); EXPECT_THAT(p->error(), Eq("variable with ID 20 has non-pointer type 10")); } TEST_F(SpvParserTest, EmitStatement_AccessChain_VectorSwizzle) { const std::string assembly = R"( OpName %1 "myvar" %void = OpTypeVoid %voidfn = OpTypeFunction %void %uint = OpTypeInt 32 0 %store_ty = OpTypeVector %uint 4 %uint_2 = OpConstant %uint 2 %uint_42 = OpConstant %uint 42 %elem_ty = OpTypePointer Workgroup %uint %var_ty = OpTypePointer Workgroup %store_ty %1 = OpVariable %var_ty Workgroup %100 = OpFunction %void None %voidfn %entry = OpLabel %2 = OpAccessChain %elem_ty %1 %uint_2 OpStore %2 %uint_42 OpReturn OpFunctionEnd )"; auto p = parser(test::Assemble(assembly)); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly << p->error(); FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100)); EXPECT_TRUE(fe.EmitBody()); EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(Assignment{ MemberAccessor[not set]{ Identifier[not set]{myvar} Identifier[not set]{z} } ScalarConstructor[not set]{42} })")) << ToString(p->builder(), fe.ast_body()); } TEST_F(SpvParserTest, EmitStatement_AccessChain_VectorConstOutOfBounds) { const std::string assembly = R"( OpName %1 "myvar" %void = OpTypeVoid %voidfn = OpTypeFunction %void %uint = OpTypeInt 32 0 %store_ty = OpTypeVector %uint 4 %42 = OpConstant %uint 42 %uint_99 = OpConstant %uint 99 %elem_ty = OpTypePointer Workgroup %uint %var_ty = OpTypePointer Workgroup %store_ty %1 = OpVariable %var_ty Workgroup %100 = OpFunction %void None %voidfn %entry = OpLabel %2 = OpAccessChain %elem_ty %1 %42 OpStore %2 %uint_99 OpReturn OpFunctionEnd )"; auto p = parser(test::Assemble(assembly)); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly << p->error(); FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100)); EXPECT_FALSE(fe.EmitBody()); EXPECT_THAT(p->error(), Eq("Access chain %2 index %42 value 42 is out of " "bounds for vector of 4 elements")); } TEST_F(SpvParserTest, EmitStatement_AccessChain_VectorNonConstIndex) { const std::string assembly = R"( OpName %1 "myvar" %void = OpTypeVoid %voidfn = OpTypeFunction %void %uint = OpTypeInt 32 0 %store_ty = OpTypeVector %uint 4 %uint_2 = OpConstant %uint 2 %uint_42 = OpConstant %uint 42 %elem_ty = OpTypePointer Workgroup %uint %var_ty = OpTypePointer Workgroup %store_ty %1 = OpVariable %var_ty Workgroup %10 = OpVariable %var_ty Workgroup %100 = OpFunction %void None %voidfn %entry = OpLabel %11 = OpLoad %uint %10 %2 = OpAccessChain %elem_ty %1 %11 OpStore %2 %uint_42 OpReturn OpFunctionEnd )"; auto p = parser(test::Assemble(assembly)); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly << p->error(); FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100)); EXPECT_TRUE(fe.EmitBody()); EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(Assignment{ ArrayAccessor[not set]{ Identifier[not set]{myvar} Identifier[not set]{x_11} } ScalarConstructor[not set]{42} })")); } TEST_F(SpvParserTest, EmitStatement_AccessChain_Matrix) { const std::string assembly = R"( OpName %1 "myvar" %void = OpTypeVoid %voidfn = OpTypeFunction %void %float = OpTypeFloat 32 %v4float = OpTypeVector %float 4 %m3v4float = OpTypeMatrix %v4float 3 %elem_ty = OpTypePointer Workgroup %v4float %var_ty = OpTypePointer Workgroup %m3v4float %uint = OpTypeInt 32 0 %uint_2 = OpConstant %uint 2 %float_42 = OpConstant %float 42 %v4float_42 = OpConstantComposite %v4float %float_42 %float_42 %float_42 %float_42 %1 = OpVariable %var_ty Workgroup %100 = OpFunction %void None %voidfn %entry = OpLabel %2 = OpAccessChain %elem_ty %1 %uint_2 OpStore %2 %v4float_42 OpReturn OpFunctionEnd )"; auto p = parser(test::Assemble(assembly)); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly << p->error(); FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100)); EXPECT_TRUE(fe.EmitBody()); EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(Assignment{ ArrayAccessor[not set]{ Identifier[not set]{myvar} ScalarConstructor[not set]{2} } TypeConstructor[not set]{ __vec_4__f32 ScalarConstructor[not set]{42.000000} ScalarConstructor[not set]{42.000000} ScalarConstructor[not set]{42.000000} ScalarConstructor[not set]{42.000000} } })")); } TEST_F(SpvParserTest, EmitStatement_AccessChain_Array) { const std::string assembly = R"( OpName %1 "myvar" %void = OpTypeVoid %voidfn = OpTypeFunction %void %float = OpTypeFloat 32 %v4float = OpTypeVector %float 4 %m3v4float = OpTypeMatrix %v4float 3 %elem_ty = OpTypePointer Workgroup %v4float %var_ty = OpTypePointer Workgroup %m3v4float %uint = OpTypeInt 32 0 %uint_2 = OpConstant %uint 2 %float_42 = OpConstant %float 42 %v4float_42 = OpConstantComposite %v4float %float_42 %float_42 %float_42 %float_42 %1 = OpVariable %var_ty Workgroup %100 = OpFunction %void None %voidfn %entry = OpLabel %2 = OpAccessChain %elem_ty %1 %uint_2 OpStore %2 %v4float_42 OpReturn OpFunctionEnd )"; auto p = parser(test::Assemble(assembly)); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly << p->error(); FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100)); EXPECT_TRUE(fe.EmitBody()); EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(Assignment{ ArrayAccessor[not set]{ Identifier[not set]{myvar} ScalarConstructor[not set]{2} } TypeConstructor[not set]{ __vec_4__f32 ScalarConstructor[not set]{42.000000} ScalarConstructor[not set]{42.000000} ScalarConstructor[not set]{42.000000} ScalarConstructor[not set]{42.000000} } })")); } TEST_F(SpvParserTest, EmitStatement_AccessChain_Struct) { const std::string assembly = R"( OpName %1 "myvar" OpMemberName %strct 1 "age" %void = OpTypeVoid %voidfn = OpTypeFunction %void %float = OpTypeFloat 32 %float_42 = OpConstant %float 42 %strct = OpTypeStruct %float %float %elem_ty = OpTypePointer Workgroup %float %var_ty = OpTypePointer Workgroup %strct %uint = OpTypeInt 32 0 %uint_1 = OpConstant %uint 1 %1 = OpVariable %var_ty Workgroup %100 = OpFunction %void None %voidfn %entry = OpLabel %2 = OpAccessChain %elem_ty %1 %uint_1 OpStore %2 %float_42 OpReturn OpFunctionEnd )"; auto p = parser(test::Assemble(assembly)); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly << p->error(); FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100)); EXPECT_TRUE(fe.EmitBody()); EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(Assignment{ MemberAccessor[not set]{ Identifier[not set]{myvar} Identifier[not set]{age} } ScalarConstructor[not set]{42.000000} })")); } TEST_F(SpvParserTest, EmitStatement_AccessChain_Struct_DifferOnlyMemberName) { // The spirv-opt internal representation will map both structs to the // same canonicalized type, because it doesn't care about member names. // But we care about member names when producing a member-access expression. // crbug.com/tint/213 const std::string assembly = R"( OpName %1 "myvar" OpName %10 "myvar2" OpMemberName %strct 1 "age" OpMemberName %strct2 1 "ancientness" %void = OpTypeVoid %voidfn = OpTypeFunction %void %float = OpTypeFloat 32 %float_42 = OpConstant %float 42 %float_420 = OpConstant %float 420 %strct = OpTypeStruct %float %float %strct2 = OpTypeStruct %float %float %elem_ty = OpTypePointer Workgroup %float %var_ty = OpTypePointer Workgroup %strct %var2_ty = OpTypePointer Workgroup %strct2 %uint = OpTypeInt 32 0 %uint_1 = OpConstant %uint 1 %1 = OpVariable %var_ty Workgroup %10 = OpVariable %var2_ty Workgroup %100 = OpFunction %void None %voidfn %entry = OpLabel %2 = OpAccessChain %elem_ty %1 %uint_1 OpStore %2 %float_42 %20 = OpAccessChain %elem_ty %10 %uint_1 OpStore %20 %float_420 OpReturn OpFunctionEnd )"; auto p = parser(test::Assemble(assembly)); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly << p->error(); FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100)); EXPECT_TRUE(fe.EmitBody()); EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(Assignment{ MemberAccessor[not set]{ Identifier[not set]{myvar} Identifier[not set]{age} } ScalarConstructor[not set]{42.000000} } Assignment{ MemberAccessor[not set]{ Identifier[not set]{myvar2} Identifier[not set]{ancientness} } ScalarConstructor[not set]{420.000000} })")) << ToString(p->builder(), fe.ast_body()); } TEST_F(SpvParserTest, EmitStatement_AccessChain_StructNonConstIndex) { const std::string assembly = R"( OpName %1 "myvar" OpMemberName %55 1 "age" %void = OpTypeVoid %voidfn = OpTypeFunction %void %float = OpTypeFloat 32 %float_42 = OpConstant %float 42 %55 = OpTypeStruct %float %float %elem_ty = OpTypePointer Workgroup %float %var_ty = OpTypePointer Workgroup %55 %uint = OpTypeInt 32 0 %uint_1 = OpConstant %uint 1 %uint_ptr = OpTypePointer Workgroup %uint %uintvar = OpVariable %uint_ptr Workgroup %1 = OpVariable %var_ty Workgroup %100 = OpFunction %void None %voidfn %entry = OpLabel %10 = OpLoad %uint %uintvar %2 = OpAccessChain %elem_ty %1 %10 OpStore %2 %float_42 OpReturn OpFunctionEnd )"; auto p = parser(test::Assemble(assembly)); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly << p->error(); FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100)); EXPECT_FALSE(fe.EmitBody()); EXPECT_THAT(p->error(), Eq("Access chain %2 index %10 is a non-constant " "index into a structure %55")); } TEST_F(SpvParserTest, EmitStatement_AccessChain_StructConstOutOfBounds) { const std::string assembly = R"( OpName %1 "myvar" OpMemberName %55 1 "age" %void = OpTypeVoid %voidfn = OpTypeFunction %void %float = OpTypeFloat 32 %float_42 = OpConstant %float 42 %55 = OpTypeStruct %float %float %elem_ty = OpTypePointer Workgroup %float %var_ty = OpTypePointer Workgroup %55 %uint = OpTypeInt 32 0 %uint_99 = OpConstant %uint 99 %1 = OpVariable %var_ty Workgroup %100 = OpFunction %void None %voidfn %entry = OpLabel %2 = OpAccessChain %elem_ty %1 %uint_99 OpStore %2 %float_42 OpReturn OpFunctionEnd )"; auto p = parser(test::Assemble(assembly)); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly << p->error(); FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100)); EXPECT_FALSE(fe.EmitBody()); EXPECT_THAT(p->error(), Eq("Access chain %2 index value 99 is out of bounds " "for structure %55 having 2 members")); } TEST_F(SpvParserTest, EmitStatement_AccessChain_Struct_RuntimeArray) { const std::string assembly = R"( OpName %1 "myvar" OpMemberName %strct 1 "age" %void = OpTypeVoid %voidfn = OpTypeFunction %void %float = OpTypeFloat 32 %float_42 = OpConstant %float 42 %rtarr = OpTypeRuntimeArray %float %strct = OpTypeStruct %float %rtarr %elem_ty = OpTypePointer Workgroup %float %var_ty = OpTypePointer Workgroup %strct %uint = OpTypeInt 32 0 %uint_1 = OpConstant %uint 1 %uint_2 = OpConstant %uint 2 %1 = OpVariable %var_ty Workgroup %100 = OpFunction %void None %voidfn %entry = OpLabel %2 = OpAccessChain %elem_ty %1 %uint_1 %uint_2 OpStore %2 %float_42 OpReturn OpFunctionEnd )"; auto p = parser(test::Assemble(assembly)); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly << p->error(); FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100)); EXPECT_TRUE(fe.EmitBody()); EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(Assignment{ ArrayAccessor[not set]{ MemberAccessor[not set]{ Identifier[not set]{myvar} Identifier[not set]{age} } ScalarConstructor[not set]{2} } ScalarConstructor[not set]{42.000000} })")); } TEST_F(SpvParserTest, EmitStatement_AccessChain_Compound_Matrix_Vector) { const std::string assembly = R"( OpName %1 "myvar" %void = OpTypeVoid %voidfn = OpTypeFunction %void %float = OpTypeFloat 32 %v4float = OpTypeVector %float 4 %m3v4float = OpTypeMatrix %v4float 3 %elem_ty = OpTypePointer Workgroup %float %var_ty = OpTypePointer Workgroup %m3v4float %uint = OpTypeInt 32 0 %uint_2 = OpConstant %uint 2 %uint_3 = OpConstant %uint 3 %float_42 = OpConstant %float 42 %1 = OpVariable %var_ty Workgroup %100 = OpFunction %void None %voidfn %entry = OpLabel %2 = OpAccessChain %elem_ty %1 %uint_2 %uint_3 OpStore %2 %float_42 OpReturn OpFunctionEnd )"; auto p = parser(test::Assemble(assembly)); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly << p->error(); FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100)); EXPECT_TRUE(fe.EmitBody()); EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(Assignment{ MemberAccessor[not set]{ ArrayAccessor[not set]{ Identifier[not set]{myvar} ScalarConstructor[not set]{2} } Identifier[not set]{w} } ScalarConstructor[not set]{42.000000} })")); } TEST_F(SpvParserTest, EmitStatement_AccessChain_InvalidPointeeType) { const std::string assembly = R"( OpName %1 "myvar" %55 = OpTypeVoid %voidfn = OpTypeFunction %55 %float = OpTypeFloat 32 %60 = OpTypePointer Workgroup %55 %var_ty = OpTypePointer Workgroup %60 %uint = OpTypeInt 32 0 %uint_2 = OpConstant %uint 2 %1 = OpVariable %var_ty Workgroup %100 = OpFunction %55 None %voidfn %entry = OpLabel %2 = OpAccessChain %60 %1 %uint_2 OpReturn OpFunctionEnd )"; auto p = parser(test::Assemble(assembly)); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly << p->error(); FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100)); EXPECT_FALSE(fe.EmitBody()); EXPECT_THAT(p->error(), HasSubstr("Access chain with unknown or invalid pointee type " "%60: %60 = OpTypePointer Workgroup %55")); } std::string OldStorageBufferPreamble() { return R"( OpName %myvar "myvar" OpDecorate %struct BufferBlock OpMemberDecorate %struct 0 Offset 0 OpMemberDecorate %struct 1 Offset 4 OpDecorate %arr ArrayStride 4 %void = OpTypeVoid %voidfn = OpTypeFunction %void %uint = OpTypeInt 32 0 %uint_0 = OpConstant %uint 0 %uint_1 = OpConstant %uint 1 %arr = OpTypeRuntimeArray %uint %struct = OpTypeStruct %uint %arr %ptr_struct = OpTypePointer Uniform %struct %ptr_uint = OpTypePointer Uniform %uint %myvar = OpVariable %ptr_struct Uniform )"; } TEST_F(SpvParserTest, RemapStorageBuffer_TypesAndVarDeclarations) { // Enusure we get the right module-scope declaration. This tests translation // of the structure type, arrays of the structure, pointers to them, and // OpVariable of these. const auto assembly = OldStorageBufferPreamble(); auto p = parser(test::Assemble(assembly)); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly << p->error(); const auto module_str = Demangler().Demangle(p->program()); EXPECT_THAT(module_str, HasSubstr(R"( RTArr -> __array__u32_stride_4 S Struct{ [[block]] StructMember{[[ offset 0 ]] field0: __u32} StructMember{[[ offset 4 ]] field1: __alias_RTArr__array__u32_stride_4} } Variable{ myvar storage __access_control_read_write__struct_S })")); } TEST_F(SpvParserTest, RemapStorageBuffer_ThroughAccessChain_NonCascaded) { const auto assembly = OldStorageBufferPreamble() + R"( %100 = OpFunction %void None %voidfn %entry = OpLabel ; the scalar element %1 = OpAccessChain %ptr_uint %myvar %uint_0 OpStore %1 %uint_0 ; element in the runtime array %2 = OpAccessChain %ptr_uint %myvar %uint_1 %uint_1 OpStore %2 %uint_0 OpReturn OpFunctionEnd )"; auto p = parser(test::Assemble(assembly)); ASSERT_TRUE(p->BuildAndParseInternalModule()) << assembly << p->error(); FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100)); EXPECT_TRUE(fe.EmitBody()) << p->error(); const auto got = ToString(p->builder(), fe.ast_body()); EXPECT_THAT(got, HasSubstr(R"(Assignment{ MemberAccessor[not set]{ Identifier[not set]{myvar} Identifier[not set]{field0} } ScalarConstructor[not set]{0} } Assignment{ ArrayAccessor[not set]{ MemberAccessor[not set]{ Identifier[not set]{myvar} Identifier[not set]{field1} } ScalarConstructor[not set]{1} } ScalarConstructor[not set]{0} })")); } TEST_F(SpvParserTest, RemapStorageBuffer_ThroughAccessChain_NonCascaded_InBoundsAccessChain) { // Like the previous test, but using OpInBoundsAccessChain. const auto assembly = OldStorageBufferPreamble() + R"( %100 = OpFunction %void None %voidfn %entry = OpLabel ; the scalar element %1 = OpInBoundsAccessChain %ptr_uint %myvar %uint_0 OpStore %1 %uint_0 ; element in the runtime array %2 = OpInBoundsAccessChain %ptr_uint %myvar %uint_1 %uint_1 OpStore %2 %uint_0 OpReturn OpFunctionEnd )"; auto p = parser(test::Assemble(assembly)); ASSERT_TRUE(p->BuildAndParseInternalModule()) << assembly << p->error(); FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100)); EXPECT_TRUE(fe.EmitBody()) << p->error(); const auto got = ToString(p->builder(), fe.ast_body()); EXPECT_THAT(got, HasSubstr(R"(Assignment{ MemberAccessor[not set]{ Identifier[not set]{myvar} Identifier[not set]{field0} } ScalarConstructor[not set]{0} } Assignment{ ArrayAccessor[not set]{ MemberAccessor[not set]{ Identifier[not set]{myvar} Identifier[not set]{field1} } ScalarConstructor[not set]{1} } ScalarConstructor[not set]{0} })")) << got << p->error(); } TEST_F(SpvParserTest, RemapStorageBuffer_ThroughAccessChain_Cascaded) { const auto assembly = OldStorageBufferPreamble() + R"( %ptr_rtarr = OpTypePointer Uniform %arr %100 = OpFunction %void None %voidfn %entry = OpLabel ; get the runtime array %1 = OpAccessChain %ptr_rtarr %myvar %uint_1 ; now an element in it %2 = OpAccessChain %ptr_uint %1 %uint_1 OpStore %2 %uint_0 OpReturn OpFunctionEnd )"; auto p = parser(test::Assemble(assembly)); ASSERT_TRUE(p->BuildAndParseInternalModule()) << assembly << p->error(); FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100)); EXPECT_TRUE(fe.EmitBody()) << p->error(); EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(Assignment{ ArrayAccessor[not set]{ MemberAccessor[not set]{ Identifier[not set]{myvar} Identifier[not set]{field1} } ScalarConstructor[not set]{1} } ScalarConstructor[not set]{0} })")) << ToString(p->builder(), fe.ast_body()) << p->error(); } TEST_F(SpvParserTest, RemapStorageBuffer_ThroughCopyObject_WithoutHoisting) { // Generates a const declaration directly. // We have to do a bunch of storage class tracking for locally // defined values in order to get the right pointer-to-storage-buffer // value type for the const declration. const auto assembly = OldStorageBufferPreamble() + R"( %100 = OpFunction %void None %voidfn %entry = OpLabel %1 = OpAccessChain %ptr_uint %myvar %uint_1 %uint_1 %2 = OpCopyObject %ptr_uint %1 OpStore %2 %uint_0 OpReturn OpFunctionEnd )"; auto p = parser(test::Assemble(assembly)); ASSERT_TRUE(p->BuildAndParseInternalModule()) << assembly << p->error(); FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100)); EXPECT_TRUE(fe.EmitBody()) << p->error(); EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(VariableDeclStatement{ VariableConst{ x_2 none __ptr_storage__u32 { ArrayAccessor[not set]{ MemberAccessor[not set]{ Identifier[not set]{myvar} Identifier[not set]{field1} } ScalarConstructor[not set]{1} } } } } Assignment{ Identifier[not set]{x_2} ScalarConstructor[not set]{0} })")) << ToString(p->builder(), fe.ast_body()) << p->error(); } TEST_F(SpvParserTest, RemapStorageBuffer_ThroughCopyObject_WithHoisting) { // Like the previous test, but the declaration for the copy-object // has its declaration hoisted. const auto assembly = OldStorageBufferPreamble() + R"( %bool = OpTypeBool %cond = OpConstantTrue %bool %100 = OpFunction %void None %voidfn %entry = OpLabel OpSelectionMerge %99 None OpBranchConditional %cond %20 %30 %20 = OpLabel %1 = OpAccessChain %ptr_uint %myvar %uint_1 %uint_1 ; this definintion dominates the use in %99 %2 = OpCopyObject %ptr_uint %1 OpBranch %99 %30 = OpLabel OpReturn %99 = OpLabel OpStore %2 %uint_0 OpReturn OpFunctionEnd )"; auto p = parser(test::Assemble(assembly)); ASSERT_TRUE(p->BuildAndParseInternalModule()) << assembly << p->error(); FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100)); EXPECT_TRUE(fe.EmitBody()) << p->error(); EXPECT_THAT(ToString(p->builder(), fe.ast_body()), Eq(R"(VariableDeclStatement{ Variable{ x_2 function __ptr_storage__u32 } } If{ ( ScalarConstructor[not set]{true} ) { Assignment{ Identifier[not set]{x_2} ArrayAccessor[not set]{ MemberAccessor[not set]{ Identifier[not set]{myvar} Identifier[not set]{field1} } ScalarConstructor[not set]{1} } } } } Else{ { Return{} } } Assignment{ Identifier[not set]{x_2} ScalarConstructor[not set]{0} } Return{} )")) << ToString(p->builder(), fe.ast_body()) << p->error(); } TEST_F(SpvParserTest, DISABLED_RemapStorageBuffer_ThroughFunctionCall) { // TODO(dneto): Blocked on OpFunctionCall support. // We might need this for passing pointers into atomic builtins. } TEST_F(SpvParserTest, DISABLED_RemapStorageBuffer_ThroughFunctionParameter) { // TODO(dneto): Blocked on OpFunctionCall support. } std::string RuntimeArrayPreamble() { return R"( OpName %myvar "myvar" OpMemberName %struct 0 "first" OpMemberName %struct 1 "rtarr" OpDecorate %struct Block OpMemberDecorate %struct 0 Offset 0 OpMemberDecorate %struct 1 Offset 4 OpDecorate %arr ArrayStride 4 %void = OpTypeVoid %voidfn = OpTypeFunction %void %uint = OpTypeInt 32 0 %uint_0 = OpConstant %uint 0 %uint_1 = OpConstant %uint 1 %arr = OpTypeRuntimeArray %uint %struct = OpTypeStruct %uint %arr %ptr_struct = OpTypePointer StorageBuffer %struct %ptr_uint = OpTypePointer StorageBuffer %uint %myvar = OpVariable %ptr_struct StorageBuffer )"; } TEST_F(SpvParserTest, ArrayLength) { const auto assembly = RuntimeArrayPreamble() + R"( %100 = OpFunction %void None %voidfn %entry = OpLabel %1 = OpArrayLength %uint %myvar 1 OpReturn OpFunctionEnd )"; auto p = parser(test::Assemble(assembly)); ASSERT_TRUE(p->BuildAndParseInternalModule()) << assembly << p->error(); FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100)); EXPECT_TRUE(fe.EmitBody()) << p->error(); const auto body_str = ToString(p->builder(), fe.ast_body()); EXPECT_THAT(body_str, HasSubstr(R"(VariableDeclStatement{ VariableConst{ x_1 none __u32 { Call[not set]{ Identifier[not set]{arrayLength} ( MemberAccessor[not set]{ Identifier[not set]{myvar} Identifier[not set]{rtarr} } ) } } } } )")) << body_str; } } // namespace } // namespace spirv } // namespace reader } // namespace tint