blob: e8af52be4d90723dec05ade8df978aa81ad561c5 [file] [log] [blame]
// 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 "gmock/gmock.h"
#include "src/reader/spirv/function.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;
using SpvParserMemoryTest = SpvParserTest;
std::string Preamble() {
return R"(
OpCapability Shader
OpMemoryModel Logical Simple
OpEntryPoint Fragment %100 "main"
OpExecutionMode %100 OriginUpperLeft
)";
}
TEST_F(SpvParserMemoryTest, EmitStatement_StoreBoolConst) {
auto p = parser(test::Assemble(Preamble() + 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();
auto fe = p->function_emitter(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(SpvParserMemoryTest, EmitStatement_StoreUintConst) {
auto p = parser(test::Assemble(Preamble() + 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
OpFunctionEnd
)"));
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
auto fe = p->function_emitter(100);
EXPECT_TRUE(fe.EmitBody());
EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(Assignment{
Identifier[not set]{x_1}
ScalarConstructor[not set]{42u}
}
Assignment{
Identifier[not set]{x_1}
ScalarConstructor[not set]{0u}
})"));
}
TEST_F(SpvParserMemoryTest, EmitStatement_StoreIntConst) {
auto p = parser(test::Assemble(Preamble() + 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
OpFunctionEnd
)"));
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
auto fe = p->function_emitter(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(SpvParserMemoryTest, EmitStatement_StoreFloatConst) {
auto p = parser(test::Assemble(Preamble() + 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
OpFunctionEnd
)"));
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
auto fe = p->function_emitter(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(SpvParserMemoryTest, EmitStatement_LoadBool) {
auto p = parser(test::Assemble(Preamble() + 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();
auto fe = p->function_emitter(100);
EXPECT_TRUE(fe.EmitBody()) << p->error();
EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(
VariableConst{
x_2
none
undefined
__bool
{
Identifier[not set]{x_1}
}
})"));
}
TEST_F(SpvParserMemoryTest, EmitStatement_LoadScalar) {
auto p = parser(test::Assemble(Preamble() + 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();
auto fe = p->function_emitter(100);
EXPECT_TRUE(fe.EmitBody()) << p->error();
EXPECT_THAT(ToString(p->builder(), fe.ast_body()),
HasSubstr(R"(VariableDeclStatement{
VariableConst{
x_2
none
undefined
__u32
{
Identifier[not set]{x_1}
}
}
}
VariableDeclStatement{
VariableConst{
x_3
none
undefined
__u32
{
Identifier[not set]{x_1}
}
}
})"));
}
TEST_F(SpvParserMemoryTest, EmitStatement_UseLoadedScalarTwice) {
auto p = parser(test::Assemble(Preamble() + 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();
auto fe = p->function_emitter(100);
EXPECT_TRUE(fe.EmitBody()) << p->error();
EXPECT_THAT(ToString(p->builder(), fe.ast_body()),
HasSubstr(R"(VariableDeclStatement{
VariableConst{
x_2
none
undefined
__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(SpvParserMemoryTest, EmitStatement_StoreToModuleScopeVar) {
auto p = parser(test::Assemble(Preamble() + R"(
%void = OpTypeVoid
%voidfn = OpTypeFunction %void
%ty = OpTypeInt 32 0
%val = OpConstant %ty 42
%ptr_ty = OpTypePointer Private %ty
%1 = OpVariable %ptr_ty Private
%100 = OpFunction %void None %voidfn
%entry = OpLabel
OpStore %1 %val
OpReturn
OpFunctionEnd
)"));
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
auto fe = p->function_emitter(100);
EXPECT_TRUE(fe.EmitBody());
EXPECT_THAT(ToString(p->builder(), fe.ast_body()), HasSubstr(R"(Assignment{
Identifier[not set]{x_1}
ScalarConstructor[not set]{42u}
})"));
}
TEST_F(SpvParserMemoryTest,
EmitStatement_CopyMemory_Scalar_Function_To_Private) {
auto p = parser(test::Assemble(Preamble() + R"(
%void = OpTypeVoid
%voidfn = OpTypeFunction %void
%ty = OpTypeInt 32 0
%val = OpConstant %ty 42
%ptr_fn_ty = OpTypePointer Function %ty
%ptr_priv_ty = OpTypePointer Private %ty
%2 = OpVariable %ptr_priv_ty Private
%100 = OpFunction %void None %voidfn
%entry = OpLabel
%1 = OpVariable %ptr_fn_ty Function
OpCopyMemory %2 %1
OpReturn
OpFunctionEnd
)"));
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
auto fe = p->function_emitter(100);
EXPECT_TRUE(fe.EmitBody());
const auto got = ToString(p->builder(), fe.ast_body());
const auto* expected = R"(Assignment{
Identifier[not set]{x_2}
Identifier[not set]{x_1}
})";
EXPECT_THAT(got, HasSubstr(expected));
}
TEST_F(SpvParserMemoryTest, EmitStatement_AccessChain_NoOperands) {
auto err = test::AssembleFailure(Preamble() + R"(
%void = OpTypeVoid
%voidfn = OpTypeFunction %void
%ty = OpTypeInt 32 0
%val = OpConstant %ty 42
%ptr_ty = OpTypePointer Private %ty
%1 = OpVariable %ptr_ty Private
%100 = OpFunction %void None %voidfn
%entry = OpLabel
%2 = OpAccessChain %ptr_ty ; Needs a base operand
OpStore %1 %val
OpReturn
)");
EXPECT_THAT(err,
Eq("16:5: Expected operand, found next instruction instead."));
}
TEST_F(SpvParserMemoryTest, EmitStatement_AccessChain_BaseIsNotPointer) {
auto p = parser(test::Assemble(Preamble() + R"(
%void = OpTypeVoid
%voidfn = OpTypeFunction %void
%10 = OpTypeInt 32 0
%val = OpConstant %10 42
%ptr_ty = OpTypePointer Private %10
%20 = OpVariable %10 Private ; 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(SpvParserMemoryTest, EmitStatement_AccessChain_VectorSwizzle) {
const std::string assembly = Preamble() + 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 Private %uint
%var_ty = OpTypePointer Private %store_ty
%1 = OpVariable %var_ty Private
%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();
auto fe = p->function_emitter(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]{42u}
})"));
}
TEST_F(SpvParserMemoryTest, EmitStatement_AccessChain_VectorConstOutOfBounds) {
const std::string assembly = Preamble() + 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 Private %uint
%var_ty = OpTypePointer Private %store_ty
%1 = OpVariable %var_ty Private
%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();
auto fe = p->function_emitter(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(SpvParserMemoryTest, EmitStatement_AccessChain_VectorNonConstIndex) {
const std::string assembly = Preamble() + R"(
OpName %1 "myvar"
OpName %13 "a_dynamic_index"
%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 Private %uint
%var_ty = OpTypePointer Private %store_ty
%1 = OpVariable %var_ty Private
%10 = OpVariable %var_ty Private
%100 = OpFunction %void None %voidfn
%entry = OpLabel
%11 = OpLoad %store_ty %10
%12 = OpCompositeExtract %uint %11 2
%13 = OpCopyObject %uint %12
%2 = OpAccessChain %elem_ty %1 %13
OpStore %2 %uint_42
OpReturn
OpFunctionEnd
)";
auto p = parser(test::Assemble(assembly));
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
<< assembly << p->error();
auto fe = p->function_emitter(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]{a_dynamic_index}
}
ScalarConstructor[not set]{42u}
})"));
}
TEST_F(SpvParserMemoryTest, EmitStatement_AccessChain_Matrix) {
const std::string assembly = Preamble() + R"(
OpName %1 "myvar"
%void = OpTypeVoid
%voidfn = OpTypeFunction %void
%float = OpTypeFloat 32
%v4float = OpTypeVector %float 4
%m3v4float = OpTypeMatrix %v4float 3
%elem_ty = OpTypePointer Private %v4float
%var_ty = OpTypePointer Private %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 Private
%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();
auto fe = p->function_emitter(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]{2u}
}
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(SpvParserMemoryTest, EmitStatement_AccessChain_Array) {
const std::string assembly = Preamble() + R"(
OpName %1 "myvar"
%void = OpTypeVoid
%voidfn = OpTypeFunction %void
%float = OpTypeFloat 32
%v4float = OpTypeVector %float 4
%m3v4float = OpTypeMatrix %v4float 3
%elem_ty = OpTypePointer Private %v4float
%var_ty = OpTypePointer Private %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 Private
%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();
auto fe = p->function_emitter(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]{2u}
}
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(SpvParserMemoryTest, EmitStatement_AccessChain_Struct) {
const std::string assembly = Preamble() + 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 Private %float
%var_ty = OpTypePointer Private %strct
%uint = OpTypeInt 32 0
%uint_1 = OpConstant %uint 1
%1 = OpVariable %var_ty Private
%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();
auto fe = p->function_emitter(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(SpvParserMemoryTest,
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 = Preamble() + 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 Private %float
%var_ty = OpTypePointer Private %strct
%var2_ty = OpTypePointer Private %strct2
%uint = OpTypeInt 32 0
%uint_1 = OpConstant %uint 1
%1 = OpVariable %var_ty Private
%10 = OpVariable %var2_ty Private
%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();
auto fe = p->function_emitter(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}
})"));
}
TEST_F(SpvParserMemoryTest, EmitStatement_AccessChain_StructNonConstIndex) {
const std::string assembly = Preamble() + 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 Private %float
%var_ty = OpTypePointer Private %55
%uint = OpTypeInt 32 0
%uint_1 = OpConstant %uint 1
%uint_ptr = OpTypePointer Private %uint
%uintvar = OpVariable %uint_ptr Private
%1 = OpVariable %var_ty Private
%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();
auto fe = p->function_emitter(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(SpvParserMemoryTest, EmitStatement_AccessChain_StructConstOutOfBounds) {
const std::string assembly = Preamble() + 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 Private %float
%var_ty = OpTypePointer Private %55
%uint = OpTypeInt 32 0
%uint_99 = OpConstant %uint 99
%1 = OpVariable %var_ty Private
%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();
auto fe = p->function_emitter(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(SpvParserMemoryTest, EmitStatement_AccessChain_Struct_RuntimeArray) {
const std::string assembly = Preamble() + R"(
OpName %1 "myvar"
OpMemberName %strct 1 "age"
OpDecorate %1 DescriptorSet 0
OpDecorate %1 Binding 0
OpDecorate %strct BufferBlock
OpMemberDecorate %strct 0 Offset 0
OpMemberDecorate %strct 1 Offset 4
OpDecorate %rtarr ArrayStride 4
%void = OpTypeVoid
%voidfn = OpTypeFunction %void
%float = OpTypeFloat 32
%float_42 = OpConstant %float 42
%rtarr = OpTypeRuntimeArray %float
%strct = OpTypeStruct %float %rtarr
%elem_ty = OpTypePointer Uniform %float
%var_ty = OpTypePointer Uniform %strct
%uint = OpTypeInt 32 0
%uint_1 = OpConstant %uint 1
%uint_2 = OpConstant %uint 2
%1 = OpVariable %var_ty Uniform
%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();
auto fe = p->function_emitter(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]{2u}
}
ScalarConstructor[not set]{42.000000}
})"));
}
TEST_F(SpvParserMemoryTest, EmitStatement_AccessChain_Compound_Matrix_Vector) {
const std::string assembly = Preamble() + R"(
OpName %1 "myvar"
%void = OpTypeVoid
%voidfn = OpTypeFunction %void
%float = OpTypeFloat 32
%v4float = OpTypeVector %float 4
%m3v4float = OpTypeMatrix %v4float 3
%elem_ty = OpTypePointer Private %float
%var_ty = OpTypePointer Private %m3v4float
%uint = OpTypeInt 32 0
%uint_2 = OpConstant %uint 2
%uint_3 = OpConstant %uint 3
%float_42 = OpConstant %float 42
%1 = OpVariable %var_ty Private
%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();
auto fe = p->function_emitter(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]{2u}
}
Identifier[not set]{w}
}
ScalarConstructor[not set]{42.000000}
})"));
}
TEST_F(SpvParserMemoryTest, EmitStatement_AccessChain_InvalidPointeeType) {
const std::string assembly = Preamble() + R"(
OpName %1 "myvar"
%55 = OpTypeVoid
%voidfn = OpTypeFunction %55
%float = OpTypeFloat 32
%60 = OpTypePointer Private %55
%var_ty = OpTypePointer Private %60
%uint = OpTypeInt 32 0
%uint_2 = OpConstant %uint 2
%1 = OpVariable %var_ty Private
%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();
auto fe = p->function_emitter(100);
EXPECT_FALSE(fe.EmitBody());
EXPECT_THAT(p->error(),
HasSubstr("Access chain with unknown or invalid pointee type "
"%60: %60 = OpTypePointer Private %55"));
}
TEST_F(SpvParserMemoryTest, EmitStatement_AccessChain_DereferenceBase) {
// The base operand to OpAccessChain may have to be dereferenced first.
// crbug.com/tint/737
const std::string assembly = Preamble() + R"(
%void = OpTypeVoid
%voidfn = OpTypeFunction %void
%uint = OpTypeInt 32 0
%v2uint = OpTypeVector %uint 2
%elem_ty = OpTypePointer Private %uint
%vec_ty = OpTypePointer Private %v2uint
%ptrfn = OpTypeFunction %void %vec_ty
%uint_0 = OpConstant %uint 0
; The shortest way to make a pointer example is as a function parameter.
%200 = OpFunction %void None %ptrfn
%1 = OpFunctionParameter %vec_ty
%entry = OpLabel
%2 = OpAccessChain %elem_ty %1 %uint_0
%3 = OpLoad %uint %2
OpReturn
OpFunctionEnd
%100 = OpFunction %void None %voidfn
%main_entry = OpLabel
OpReturn
OpFunctionEnd
)";
auto p = parser(test::Assemble(assembly));
ASSERT_TRUE(p->BuildAndParseInternalModule());
const auto got = p->program().to_str();
const std::string expected = R"(Module{
Function x_200 -> __void
(
VariableConst{
x_1
none
undefined
__ptr_private__vec_2__u32
}
)
{
VariableDeclStatement{
VariableConst{
x_3
none
undefined
__u32
{
MemberAccessor[not set]{
UnaryOp[not set]{
indirection
Identifier[not set]{x_1}
}
Identifier[not set]{x}
}
}
}
}
Return{}
}
Function main_1 -> __void
()
{
Return{}
}
Function main -> __void
StageDecoration{fragment}
()
{
Call[not set]{
Identifier[not set]{main_1}
(
)
}
}
}
)";
EXPECT_EQ(got, expected) << got;
}
TEST_F(SpvParserMemoryTest,
EmitStatement_AccessChain_InferFunctionStorageClass) {
// An access chain can have no indices. When the base is a Function variable,
// the reference type has no explicit storage class in the AST representation.
// But the pointer type for the let declaration must have an explicit
// 'function' storage class. From crbug.com/tint/807
const std::string assembly = R"(
OpCapability Shader
OpMemoryModel Logical Simple
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
%uint = OpTypeInt 32 0
%ptr_ty = OpTypePointer Function %uint
%void = OpTypeVoid
%voidfn = OpTypeFunction %void
%main = OpFunction %void None %voidfn
%entry = OpLabel
%1 = OpVariable %ptr_ty Function
%2 = OpAccessChain %ptr_ty %1
OpReturn
OpFunctionEnd
)";
auto p = parser(test::Assemble(assembly));
ASSERT_TRUE(p->BuildAndParseInternalModule()) << assembly;
const auto got = p->program().to_str();
const std::string expected = R"(Module{
Function main_1 -> __void
()
{
VariableDeclStatement{
Variable{
x_1
none
undefined
__u32
}
}
VariableDeclStatement{
VariableConst{
x_2
none
undefined
__ptr_function__u32
{
UnaryOp[not set]{
address-of
Identifier[not set]{x_1}
}
}
}
}
Return{}
}
Function main -> __void
StageDecoration{fragment}
()
{
Call[not set]{
Identifier[not set]{main_1}
(
)
}
}
}
)";
EXPECT_EQ(got, expected) << got;
}
std::string OldStorageBufferPreamble() {
return Preamble() + R"(
OpName %myvar "myvar"
OpDecorate %myvar DescriptorSet 0
OpDecorate %myvar Binding 0
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(SpvParserMemoryTest, 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() + R"(
; The preamble declared %100 to be an entry point, so supply it.
%100 = OpFunction %void None %voidfn
%entry = OpLabel
OpReturn
OpFunctionEnd
)";
auto p = parser(test::Assemble(assembly));
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions())
<< assembly << p->error();
const auto module_str = p->program().to_str();
EXPECT_THAT(module_str, HasSubstr(R"(
RTArr -> __array__u32_stride_4
Struct S {
[[block]]
StructMember{[[ offset 0 ]] field0: __u32}
StructMember{[[ offset 4 ]] field1: __type_name_RTArr}
}
Variable{
Decorations{
GroupDecoration{0}
BindingDecoration{0}
}
myvar
storage
read_write
__type_name_S
})"));
}
TEST_F(SpvParserMemoryTest, 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();
auto fe = p->function_emitter(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]{0u}
}
Assignment{
ArrayAccessor[not set]{
MemberAccessor[not set]{
Identifier[not set]{myvar}
Identifier[not set]{field1}
}
ScalarConstructor[not set]{1u}
}
ScalarConstructor[not set]{0u}
})"));
}
TEST_F(SpvParserMemoryTest,
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();
auto fe = p->function_emitter(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]{0u}
}
Assignment{
ArrayAccessor[not set]{
MemberAccessor[not set]{
Identifier[not set]{myvar}
Identifier[not set]{field1}
}
ScalarConstructor[not set]{1u}
}
ScalarConstructor[not set]{0u}
})")) << got
<< p->error();
}
TEST_F(SpvParserMemoryTest, 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();
auto fe = p->function_emitter(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]{1u}
}
ScalarConstructor[not set]{0u}
})")) << p->error();
}
TEST_F(SpvParserMemoryTest,
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();
auto fe = p->function_emitter(100);
EXPECT_TRUE(fe.EmitBody()) << p->error();
EXPECT_THAT(ToString(p->builder(), fe.ast_body()),
HasSubstr(R"(VariableDeclStatement{
VariableConst{
x_2
none
undefined
__ptr_storage__u32
{
UnaryOp[not set]{
address-of
ArrayAccessor[not set]{
MemberAccessor[not set]{
Identifier[not set]{myvar}
Identifier[not set]{field1}
}
ScalarConstructor[not set]{1u}
}
}
}
}
}
Assignment{
UnaryOp[not set]{
indirection
Identifier[not set]{x_2}
}
ScalarConstructor[not set]{0u}
})")) << p->error();
p->SkipDumpingPending(
"crbug.com/tint/1041 track access mode in spirv-reader parser type");
}
TEST_F(SpvParserMemoryTest, RemapStorageBuffer_ThroughCopyObject_WithHoisting) {
// TODO(dneto): Hoisting non-storable values (pointers) is not yet supported.
// It's debatable whether this test should run at all.
// crbug.com/tint/98
// 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();
auto fe = p->function_emitter(100);
EXPECT_TRUE(fe.EmitBody()) << p->error();
EXPECT_EQ(ToString(p->builder(), fe.ast_body()),
R"(VariableDeclStatement{
Variable{
x_2
none
undefined
__ptr_storage__u32
}
}
If{
(
ScalarConstructor[not set]{true}
)
{
Assignment{
Identifier[not set]{x_2}
UnaryOp[not set]{
address-of
ArrayAccessor[not set]{
MemberAccessor[not set]{
Identifier[not set]{myvar}
Identifier[not set]{field1}
}
ScalarConstructor[not set]{1u}
}
}
}
}
}
Else{
{
Return{}
}
}
Assignment{
Identifier[not set]{x_2}
ScalarConstructor[not set]{0u}
}
Return{}
)") << p->error();
p->SkipDumpingPending("crbug.com/tint/98");
}
TEST_F(SpvParserMemoryTest, DISABLED_RemapStorageBuffer_ThroughFunctionCall) {
// WGSL does not support pointer-to-storage-buffer as function parameter
}
TEST_F(SpvParserMemoryTest,
DISABLED_RemapStorageBuffer_ThroughFunctionParameter) {
// WGSL does not support pointer-to-storage-buffer as function parameter
}
std::string RuntimeArrayPreamble() {
return R"(
OpCapability Shader
OpMemoryModel Logical Simple
OpEntryPoint Fragment %100 "main"
OpExecutionMode %100 OriginUpperLeft
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
OpDecorate %myvar DescriptorSet 0
OpDecorate %myvar Binding 0
%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(SpvParserMemoryTest, ArrayLength_FromVar) {
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();
auto fe = p->function_emitter(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
undefined
__u32
{
Call[not set]{
Identifier[not set]{arrayLength}
(
UnaryOp[not set]{
address-of
MemberAccessor[not set]{
Identifier[not set]{myvar}
Identifier[not set]{rtarr}
}
}
)
}
}
}
}
)")) << body_str;
}
TEST_F(SpvParserMemoryTest, ArrayLength_FromCopyObject) {
const auto assembly = RuntimeArrayPreamble() + R"(
%100 = OpFunction %void None %voidfn
%entry = OpLabel
%2 = OpCopyObject %ptr_struct %myvar
%1 = OpArrayLength %uint %2 1
OpReturn
OpFunctionEnd
)";
auto p = parser(test::Assemble(assembly));
ASSERT_TRUE(p->BuildAndParseInternalModule()) << assembly << p->error();
auto fe = p->function_emitter(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_2
none
undefined
__ptr_storage__type_name_S
{
UnaryOp[not set]{
address-of
Identifier[not set]{myvar}
}
}
}
}
VariableDeclStatement{
VariableConst{
x_1
none
undefined
__u32
{
Call[not set]{
Identifier[not set]{arrayLength}
(
UnaryOp[not set]{
address-of
MemberAccessor[not set]{
UnaryOp[not set]{
indirection
Identifier[not set]{x_2}
}
Identifier[not set]{rtarr}
}
}
)
}
}
}
}
)")) << body_str;
p->SkipDumpingPending(
"crbug.com/tint/1041 track access mode in spirv-reader parser type");
}
TEST_F(SpvParserMemoryTest, ArrayLength_FromAccessChain) {
const auto assembly = RuntimeArrayPreamble() + R"(
%100 = OpFunction %void None %voidfn
%entry = OpLabel
%2 = OpAccessChain %ptr_struct %myvar ; no indices
%1 = OpArrayLength %uint %2 1
OpReturn
OpFunctionEnd
)";
auto p = parser(test::Assemble(assembly));
ASSERT_TRUE(p->BuildAndParseInternalModule()) << assembly << p->error();
auto fe = p->function_emitter(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
undefined
__u32
{
Call[not set]{
Identifier[not set]{arrayLength}
(
UnaryOp[not set]{
address-of
MemberAccessor[not set]{
Identifier[not set]{myvar}
Identifier[not set]{rtarr}
}
}
)
}
}
}
}
)")) << body_str;
}
std::string InvalidPointerPreamble() {
return R"(
OpCapability Shader
OpMemoryModel Logical Simple
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
%uint = OpTypeInt 32 0
%ptr_ty = OpTypePointer Function %uint
%void = OpTypeVoid
%voidfn = OpTypeFunction %void
)";
}
TEST_F(SpvParserMemoryTest, InvalidPointer_Undef_ModuleScope_IsError) {
const std::string assembly = InvalidPointerPreamble() + R"(
%ptr = OpUndef %ptr_ty
%main = OpFunction %void None %voidfn
%entry = OpLabel
%1 = OpCopyObject %ptr_ty %ptr
%2 = OpAccessChain %ptr_ty %ptr
%3 = OpInBoundsAccessChain %ptr_ty %ptr
; now show the invalid pointer propagates
%10 = OpCopyObject %ptr_ty %1
%20 = OpAccessChain %ptr_ty %2
%30 = OpInBoundsAccessChain %ptr_ty %3
OpReturn
OpFunctionEnd
)";
auto p = parser(test::Assemble(assembly));
EXPECT_FALSE(p->BuildAndParseInternalModule()) << assembly;
EXPECT_EQ(p->error(), "undef pointer is not valid: %9 = OpUndef %6");
}
TEST_F(SpvParserMemoryTest, InvalidPointer_Undef_FunctionScope_IsError) {
const std::string assembly = InvalidPointerPreamble() + R"(
%main = OpFunction %void None %voidfn
%entry = OpLabel
%ptr = OpUndef %ptr_ty
OpReturn
OpFunctionEnd
)";
auto p = parser(test::Assemble(assembly));
EXPECT_FALSE(p->BuildAndParseInternalModule()) << assembly;
EXPECT_EQ(p->error(), "undef pointer is not valid: %7 = OpUndef %3");
}
TEST_F(SpvParserMemoryTest, InvalidPointer_ConstantNull_IsError) {
// OpConstantNull on logical pointer requires variable-pointers, which
// is not (yet) supported by WGSL features.
const std::string assembly = InvalidPointerPreamble() + R"(
%ptr = OpConstantNull %ptr_ty
%main = OpFunction %void None %voidfn
%entry = OpLabel
%1 = OpCopyObject %ptr_ty %ptr
%2 = OpAccessChain %ptr_ty %ptr
%3 = OpInBoundsAccessChain %ptr_ty %ptr
; now show the invalid pointer propagates
%10 = OpCopyObject %ptr_ty %1
%20 = OpAccessChain %ptr_ty %2
%30 = OpInBoundsAccessChain %ptr_ty %3
OpReturn
OpFunctionEnd
)";
auto p = parser(test::Assemble(assembly));
EXPECT_FALSE(p->BuildAndParseInternalModule());
EXPECT_EQ(p->error(), "null pointer is not valid: %9 = OpConstantNull %6");
}
} // namespace
} // namespace spirv
} // namespace reader
} // namespace tint