blob: a82437d72f10839c769b23ad05c92e51ab2fa615 [file] [log] [blame]
// Copyright 2021 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "src/tint/lang/core/builtin_value.h"
#include "src/tint/lang/core/type/texture_dimension.h"
#include "src/tint/lang/wgsl/ast/disable_validation_attribute.h"
#include "src/tint/lang/wgsl/ast/transform/add_block_attribute.h"
#include "src/tint/lang/wgsl/resolver/resolver.h"
#include "src/tint/lang/wgsl/resolver/resolver_helper_test.h"
#include "src/tint/utils/containers/transform.h"
#include "src/tint/utils/macros/compiler.h"
#include "src/tint/utils/text/string_stream.h"
#include "gmock/gmock.h"
namespace tint::resolver {
using namespace tint::core::fluent_types; // NOLINT
using namespace tint::core::number_suffixes; // NOLINT
// Helpers and typedefs
template <typename T>
using DataType = builder::DataType<T>;
template <typename T, int ID = 0>
using alias = builder::alias<T, ID>;
template <typename T>
using alias1 = builder::alias1<T>;
template <typename T>
using alias2 = builder::alias2<T>;
template <typename T>
using alias3 = builder::alias3<T>;
namespace AttributeTests {
namespace {
enum class AttributeKind {
kAlign,
kBinding,
kBuiltinPosition,
kDiagnostic,
kGroup,
kId,
kIndex,
kInterpolate,
kInvariant,
kLocation,
kMustUse,
kOffset,
kSize,
kStageCompute,
kStride,
kWorkgroupSize,
};
static std::ostream& operator<<(std::ostream& o, AttributeKind k) {
switch (k) {
case AttributeKind::kAlign:
return o << "@align";
case AttributeKind::kBinding:
return o << "@binding";
case AttributeKind::kBuiltinPosition:
return o << "@builtin(position)";
case AttributeKind::kDiagnostic:
return o << "@diagnostic";
case AttributeKind::kGroup:
return o << "@group";
case AttributeKind::kId:
return o << "@id";
case AttributeKind::kIndex:
return o << "@index";
case AttributeKind::kInterpolate:
return o << "@interpolate";
case AttributeKind::kInvariant:
return o << "@invariant";
case AttributeKind::kLocation:
return o << "@location";
case AttributeKind::kOffset:
return o << "@offset";
case AttributeKind::kMustUse:
return o << "@must_use";
case AttributeKind::kSize:
return o << "@size";
case AttributeKind::kStageCompute:
return o << "@stage(compute)";
case AttributeKind::kStride:
return o << "@stride";
case AttributeKind::kWorkgroupSize:
return o << "@workgroup_size";
}
TINT_UNREACHABLE();
return o << "<unknown>";
}
static bool IsBindingAttribute(AttributeKind kind) {
switch (kind) {
case AttributeKind::kBinding:
case AttributeKind::kGroup:
return true;
default:
return false;
}
}
struct TestParams {
Vector<AttributeKind, 2> attributes;
std::string error; // empty string (Pass) is an expected pass
};
static constexpr const char* Pass = "";
static std::vector<TestParams> OnlyDiagnosticValidFor(std::string thing) {
return {TestParams{
{AttributeKind::kAlign},
"1:2 error: @align is not valid for " + thing,
},
TestParams{
{AttributeKind::kBinding},
"1:2 error: @binding is not valid for " + thing,
},
TestParams{
{AttributeKind::kBuiltinPosition},
"1:2 error: @builtin is not valid for " + thing,
},
TestParams{
{AttributeKind::kDiagnostic},
Pass,
},
TestParams{
{AttributeKind::kGroup},
"1:2 error: @group is not valid for " + thing,
},
TestParams{
{AttributeKind::kId},
"1:2 error: @id is not valid for " + thing,
},
TestParams{
{AttributeKind::kIndex},
"1:2 error: @index is not valid for " + thing,
},
TestParams{
{AttributeKind::kInterpolate},
"1:2 error: @interpolate is not valid for " + thing,
},
TestParams{
{AttributeKind::kInvariant},
"1:2 error: @invariant is not valid for " + thing,
},
TestParams{
{AttributeKind::kLocation},
"1:2 error: @location is not valid for " + thing,
},
TestParams{
{AttributeKind::kMustUse},
"1:2 error: @must_use is not valid for " + thing,
},
TestParams{
{AttributeKind::kOffset},
"1:2 error: @offset is not valid for " + thing,
},
TestParams{
{AttributeKind::kSize},
"1:2 error: @size is not valid for " + thing,
},
TestParams{
{AttributeKind::kStageCompute},
"1:2 error: @stage is not valid for " + thing,
},
TestParams{
{AttributeKind::kStride},
"1:2 error: @stride is not valid for " + thing,
},
TestParams{
{AttributeKind::kWorkgroupSize},
"1:2 error: @workgroup_size is not valid for " + thing,
},
TestParams{
{AttributeKind::kBinding, AttributeKind::kGroup},
"1:2 error: @binding is not valid for " + thing,
}};
}
static std::ostream& operator<<(std::ostream& o, const TestParams& c) {
return o << "attributes: " << c.attributes << ", expect pass: " << c.error.empty();
}
const ast::Attribute* CreateAttribute(const Source& source,
ProgramBuilder& builder,
AttributeKind kind) {
switch (kind) {
case AttributeKind::kAlign:
return builder.MemberAlign(source, 4_i);
case AttributeKind::kBinding:
return builder.Binding(source, 1_a);
case AttributeKind::kBuiltinPosition:
return builder.Builtin(source, core::BuiltinValue::kPosition);
case AttributeKind::kDiagnostic:
return builder.DiagnosticAttribute(source, wgsl::DiagnosticSeverity::kInfo, "chromium",
"unreachable_code");
case AttributeKind::kGroup:
return builder.Group(source, 1_a);
case AttributeKind::kId:
return builder.Id(source, 0_a);
case AttributeKind::kIndex:
return builder.Index(source, 0_a);
case AttributeKind::kInterpolate:
return builder.Interpolate(source, core::InterpolationType::kLinear,
core::InterpolationSampling::kCenter);
case AttributeKind::kInvariant:
return builder.Invariant(source);
case AttributeKind::kLocation:
return builder.Location(source, 0_a);
case AttributeKind::kOffset:
return builder.MemberOffset(source, 4_a);
case AttributeKind::kMustUse:
return builder.MustUse(source);
case AttributeKind::kSize:
return builder.MemberSize(source, 16_a);
case AttributeKind::kStageCompute:
return builder.Stage(source, ast::PipelineStage::kCompute);
case AttributeKind::kStride:
return builder.create<ast::StrideAttribute>(source, 4u);
case AttributeKind::kWorkgroupSize:
return builder.create<ast::WorkgroupAttribute>(source, builder.Expr(1_i));
}
TINT_UNREACHABLE() << kind;
return nullptr;
}
struct TestWithParams : ResolverTestWithParam<TestParams> {
void EnableExtensionIfNecessary(AttributeKind attribute) {
if (attribute == AttributeKind::kIndex) {
Enable(wgsl::Extension::kChromiumInternalDualSourceBlending);
}
}
void EnableRequiredExtensions() {
for (auto attribute : GetParam().attributes) {
EnableExtensionIfNecessary(attribute);
}
}
Vector<const ast::Attribute*, 2> CreateAttributes(ProgramBuilder& builder,
VectorRef<AttributeKind> kinds) {
return Transform<2>(kinds, [&](AttributeKind kind, size_t index) {
return CreateAttribute(Source{{static_cast<uint32_t>(index) * 2 + 1,
static_cast<uint32_t>(index) * 2 + 2}},
builder, kind);
});
}
Vector<const ast::Attribute*, 2> CreateAttributes() {
return CreateAttributes(*this, GetParam().attributes);
}
};
#define CHECK() \
if (GetParam().error.empty()) { \
EXPECT_TRUE(r()->Resolve()) << r()->error(); \
} else { \
EXPECT_FALSE(r()->Resolve()); \
EXPECT_EQ(GetParam().error, r()->error()); \
} \
TINT_REQUIRE_SEMICOLON
namespace FunctionTests {
using VoidFunctionAttributeTest = TestWithParams;
TEST_P(VoidFunctionAttributeTest, IsValid) {
EnableRequiredExtensions();
Func(Source{{9, 9}}, "main", Empty, ty.void_(), Empty, CreateAttributes());
CHECK();
}
INSTANTIATE_TEST_SUITE_P(
ResolverAttributeValidationTest,
VoidFunctionAttributeTest,
testing::Values(
TestParams{
{AttributeKind::kAlign},
R"(1:2 error: @align is not valid for functions)",
},
TestParams{
{AttributeKind::kBinding},
R"(1:2 error: @binding is not valid for functions)",
},
TestParams{
{AttributeKind::kBuiltinPosition},
R"(1:2 error: @builtin is not valid for functions)",
},
TestParams{
{AttributeKind::kDiagnostic},
Pass,
},
TestParams{
{AttributeKind::kGroup},
R"(1:2 error: @group is not valid for functions)",
},
TestParams{
{AttributeKind::kId},
R"(1:2 error: @id is not valid for functions)",
},
TestParams{
{AttributeKind::kIndex},
R"(1:2 error: @index is not valid for functions)",
},
TestParams{
{AttributeKind::kInterpolate},
R"(1:2 error: @interpolate is not valid for functions)",
},
TestParams{
{AttributeKind::kInvariant},
R"(1:2 error: @invariant is not valid for functions)",
},
TestParams{
{AttributeKind::kLocation},
R"(1:2 error: @location is not valid for functions)",
},
TestParams{
{AttributeKind::kMustUse},
R"(1:2 error: @must_use can only be applied to functions that return a value)",
},
TestParams{
{AttributeKind::kOffset},
R"(1:2 error: @offset is not valid for functions)",
},
TestParams{
{AttributeKind::kSize},
R"(1:2 error: @size is not valid for functions)",
},
TestParams{
{AttributeKind::kStageCompute},
R"(9:9 error: a compute shader must include 'workgroup_size' in its attributes)",
},
TestParams{
{AttributeKind::kStageCompute, AttributeKind::kWorkgroupSize},
Pass,
},
TestParams{
{AttributeKind::kStride},
R"(1:2 error: @stride is not valid for functions)",
},
TestParams{
{AttributeKind::kWorkgroupSize},
R"(1:2 error: @workgroup_size is only valid for compute stages)",
}));
using NonVoidFunctionAttributeTest = TestWithParams;
TEST_P(NonVoidFunctionAttributeTest, IsValid) {
EnableRequiredExtensions();
Func(Source{{9, 9}}, "main", Empty, ty.i32(), Vector{Return(1_i)}, CreateAttributes());
CHECK();
}
INSTANTIATE_TEST_SUITE_P(ResolverAttributeValidationTest,
NonVoidFunctionAttributeTest,
testing::Values(
TestParams{
{AttributeKind::kAlign},
R"(1:2 error: @align is not valid for functions)",
},
TestParams{
{AttributeKind::kBinding},
R"(1:2 error: @binding is not valid for functions)",
},
TestParams{
{AttributeKind::kBuiltinPosition},
R"(1:2 error: @builtin is not valid for functions)",
},
TestParams{
{AttributeKind::kDiagnostic},
Pass,
},
TestParams{
{AttributeKind::kGroup},
R"(1:2 error: @group is not valid for functions)",
},
TestParams{
{AttributeKind::kId},
R"(1:2 error: @id is not valid for functions)",
},
TestParams{
{AttributeKind::kIndex},
R"(1:2 error: @index is not valid for functions)",
},
TestParams{
{AttributeKind::kInterpolate},
R"(1:2 error: @interpolate is not valid for functions)",
},
TestParams{
{AttributeKind::kInvariant},
R"(1:2 error: @invariant is not valid for functions)",
},
TestParams{
{AttributeKind::kLocation},
R"(1:2 error: @location is not valid for functions)",
},
TestParams{
{AttributeKind::kMustUse},
Pass,
},
TestParams{
{AttributeKind::kOffset},
R"(1:2 error: @offset is not valid for functions)",
},
TestParams{
{AttributeKind::kSize},
R"(1:2 error: @size is not valid for functions)",
},
TestParams{
{AttributeKind::kStageCompute},
R"(9:9 error: missing entry point IO attribute on return type)",
},
TestParams{
{AttributeKind::kStageCompute, AttributeKind::kWorkgroupSize},
R"(9:9 error: missing entry point IO attribute on return type)",
},
TestParams{
{AttributeKind::kStride},
R"(1:2 error: @stride is not valid for functions)",
},
TestParams{
{AttributeKind::kWorkgroupSize},
R"(1:2 error: @workgroup_size is only valid for compute stages)",
}));
} // namespace FunctionTests
namespace FunctionInputAndOutputTests {
using FunctionParameterAttributeTest = TestWithParams;
TEST_P(FunctionParameterAttributeTest, IsValid) {
EnableRequiredExtensions();
Func("main",
Vector{
Param("a", ty.vec4<f32>(), CreateAttributes()),
},
ty.void_(), tint::Empty);
CHECK();
}
INSTANTIATE_TEST_SUITE_P(
ResolverAttributeValidationTest,
FunctionParameterAttributeTest,
testing::Values(
TestParams{
{AttributeKind::kAlign},
R"(1:2 error: @align is not valid for function parameters)",
},
TestParams{
{AttributeKind::kBinding},
R"(1:2 error: @binding is not valid for function parameters)",
},
TestParams{
{AttributeKind::kBuiltinPosition},
R"(1:2 error: @builtin is not valid for non-entry point function parameters)",
},
TestParams{
{AttributeKind::kDiagnostic},
R"(1:2 error: @diagnostic is not valid for function parameters)",
},
TestParams{
{AttributeKind::kGroup},
R"(1:2 error: @group is not valid for function parameters)",
},
TestParams{
{AttributeKind::kId},
R"(1:2 error: @id is not valid for function parameters)",
},
TestParams{
{AttributeKind::kIndex},
R"(1:2 error: @index is not valid for function parameters)",
},
TestParams{
{AttributeKind::kInterpolate},
R"(1:2 error: @interpolate is not valid for non-entry point function parameters)",
},
TestParams{
{AttributeKind::kInvariant},
R"(1:2 error: @invariant is not valid for non-entry point function parameters)",
},
TestParams{
{AttributeKind::kLocation},
R"(1:2 error: @location is not valid for non-entry point function parameters)",
},
TestParams{
{AttributeKind::kMustUse},
R"(1:2 error: @must_use is not valid for function parameters)",
},
TestParams{
{AttributeKind::kOffset},
R"(1:2 error: @offset is not valid for function parameters)",
},
TestParams{
{AttributeKind::kSize},
R"(1:2 error: @size is not valid for function parameters)",
},
TestParams{
{AttributeKind::kStageCompute},
R"(1:2 error: @stage is not valid for function parameters)",
},
TestParams{
{AttributeKind::kStride},
R"(1:2 error: @stride is not valid for function parameters)",
},
TestParams{
{AttributeKind::kWorkgroupSize},
R"(1:2 error: @workgroup_size is not valid for function parameters)",
}));
using FunctionReturnTypeAttributeTest = TestWithParams;
TEST_P(FunctionReturnTypeAttributeTest, IsValid) {
EnableRequiredExtensions();
Func("main", tint::Empty, ty.f32(),
Vector{
Return(1_f),
},
tint::Empty, CreateAttributes());
CHECK();
}
INSTANTIATE_TEST_SUITE_P(
ResolverAttributeValidationTest,
FunctionReturnTypeAttributeTest,
testing::Values(
TestParams{
{AttributeKind::kAlign},
R"(1:2 error: @align is not valid for non-entry point function return types)",
},
TestParams{
{AttributeKind::kBinding},
R"(1:2 error: @binding is not valid for non-entry point function return types)",
},
TestParams{
{AttributeKind::kBuiltinPosition},
R"(1:2 error: @builtin is not valid for non-entry point function return types)",
},
TestParams{
{AttributeKind::kDiagnostic},
R"(1:2 error: @diagnostic is not valid for non-entry point function return types)",
},
TestParams{
{AttributeKind::kGroup},
R"(1:2 error: @group is not valid for non-entry point function return types)",
},
TestParams{
{AttributeKind::kId},
R"(1:2 error: @id is not valid for non-entry point function return types)",
},
TestParams{
{AttributeKind::kIndex},
R"(1:2 error: @index is not valid for non-entry point function return types)",
},
TestParams{
{AttributeKind::kInterpolate},
R"(1:2 error: @interpolate is not valid for non-entry point function return types)",
},
TestParams{
{AttributeKind::kInvariant},
R"(1:2 error: @invariant is not valid for non-entry point function return types)",
},
TestParams{
{AttributeKind::kLocation},
R"(1:2 error: @location is not valid for non-entry point function return types)",
},
TestParams{
{AttributeKind::kMustUse},
R"(1:2 error: @must_use is not valid for non-entry point function return types)",
},
TestParams{
{AttributeKind::kOffset},
R"(1:2 error: @offset is not valid for non-entry point function return types)",
},
TestParams{
{AttributeKind::kSize},
R"(1:2 error: @size is not valid for non-entry point function return types)",
},
TestParams{
{AttributeKind::kStageCompute},
R"(1:2 error: @stage is not valid for non-entry point function return types)",
},
TestParams{
{AttributeKind::kStride},
R"(1:2 error: @stride is not valid for non-entry point function return types)",
},
TestParams{
{AttributeKind::kWorkgroupSize},
R"(1:2 error: @workgroup_size is not valid for non-entry point function return types)",
}));
} // namespace FunctionInputAndOutputTests
namespace EntryPointInputAndOutputTests {
using ComputeShaderParameterAttributeTest = TestWithParams;
TEST_P(ComputeShaderParameterAttributeTest, IsValid) {
EnableRequiredExtensions();
Func("main",
Vector{
Param("a", ty.vec4<f32>(), CreateAttributes()),
},
ty.void_(), tint::Empty,
Vector{
Stage(ast::PipelineStage::kCompute),
WorkgroupSize(1_i),
});
CHECK();
}
INSTANTIATE_TEST_SUITE_P(
ResolverAttributeValidationTest,
ComputeShaderParameterAttributeTest,
testing::Values(
TestParams{
{AttributeKind::kAlign},
R"(1:2 error: @align is not valid for function parameters)",
},
TestParams{
{AttributeKind::kBinding},
R"(1:2 error: @binding is not valid for function parameters)",
},
TestParams{
{AttributeKind::kBuiltinPosition},
R"(1:2 error: @builtin(position) cannot be used in input of compute pipeline stage)",
},
TestParams{
{AttributeKind::kDiagnostic},
R"(1:2 error: @diagnostic is not valid for function parameters)",
},
TestParams{
{AttributeKind::kGroup},
R"(1:2 error: @group is not valid for function parameters)",
},
TestParams{
{AttributeKind::kId},
R"(1:2 error: @id is not valid for function parameters)",
},
TestParams{
{AttributeKind::kIndex},
R"(1:2 error: @index is not valid for function parameters)",
},
TestParams{
{AttributeKind::kInterpolate},
R"(1:2 error: @interpolate is not valid for compute shader inputs)",
},
TestParams{
{AttributeKind::kInvariant},
R"(1:2 error: @invariant is not valid for compute shader inputs)",
},
TestParams{
{AttributeKind::kLocation},
R"(1:2 error: @location is not valid for compute shader inputs)",
},
TestParams{
{AttributeKind::kMustUse},
R"(1:2 error: @must_use is not valid for function parameters)",
},
TestParams{
{AttributeKind::kOffset},
R"(1:2 error: @offset is not valid for function parameters)",
},
TestParams{
{AttributeKind::kSize},
R"(1:2 error: @size is not valid for function parameters)",
},
TestParams{
{AttributeKind::kStageCompute},
R"(1:2 error: @stage is not valid for function parameters)",
},
TestParams{
{AttributeKind::kStride},
R"(1:2 error: @stride is not valid for function parameters)",
},
TestParams{
{AttributeKind::kWorkgroupSize},
R"(1:2 error: @workgroup_size is not valid for function parameters)",
}));
using FragmentShaderParameterAttributeTest = TestWithParams;
TEST_P(FragmentShaderParameterAttributeTest, IsValid) {
EnableRequiredExtensions();
auto* p = Param(Source{{9, 9}}, "a", ty.vec4<f32>(), CreateAttributes());
Func("frag_main", Vector{p}, ty.void_(), tint::Empty,
Vector{
Stage(ast::PipelineStage::kFragment),
});
CHECK();
}
INSTANTIATE_TEST_SUITE_P(
ResolverAttributeValidationTest,
FragmentShaderParameterAttributeTest,
testing::Values(
TestParams{
{AttributeKind::kAlign},
R"(1:2 error: @align is not valid for function parameters)",
},
TestParams{
{AttributeKind::kBinding},
R"(1:2 error: @binding is not valid for function parameters)",
},
TestParams{
{AttributeKind::kBuiltinPosition},
Pass,
},
TestParams{
{AttributeKind::kDiagnostic},
R"(1:2 error: @diagnostic is not valid for function parameters)",
},
TestParams{
{AttributeKind::kGroup},
R"(1:2 error: @group is not valid for function parameters)",
},
TestParams{
{AttributeKind::kId},
R"(1:2 error: @id is not valid for function parameters)",
},
TestParams{
{AttributeKind::kIndex},
R"(1:2 error: @index is not valid for function parameters)",
},
TestParams{
{AttributeKind::kInterpolate},
R"(9:9 error: missing entry point IO attribute on parameter)",
},
TestParams{
{AttributeKind::kInterpolate, AttributeKind::kBuiltinPosition},
R"(1:2 error: @interpolate can only be used with @location)",
},
TestParams{
{AttributeKind::kInterpolate, AttributeKind::kLocation},
Pass,
},
TestParams{
{AttributeKind::kInvariant},
R"(9:9 error: missing entry point IO attribute on parameter)",
},
TestParams{
{AttributeKind::kInvariant, AttributeKind::kBuiltinPosition},
Pass,
},
TestParams{
{AttributeKind::kLocation},
Pass,
},
TestParams{
{AttributeKind::kMustUse},
R"(1:2 error: @must_use is not valid for function parameters)",
},
TestParams{
{AttributeKind::kOffset},
R"(1:2 error: @offset is not valid for function parameters)",
},
TestParams{
{AttributeKind::kSize},
R"(1:2 error: @size is not valid for function parameters)",
},
TestParams{
{AttributeKind::kStageCompute},
R"(1:2 error: @stage is not valid for function parameters)",
},
TestParams{
{AttributeKind::kStride},
R"(1:2 error: @stride is not valid for function parameters)",
},
TestParams{
{AttributeKind::kWorkgroupSize},
R"(1:2 error: @workgroup_size is not valid for function parameters)",
}));
using VertexShaderParameterAttributeTest = TestWithParams;
TEST_P(VertexShaderParameterAttributeTest, IsValid) {
EnableRequiredExtensions();
auto* p = Param(Source{{9, 9}}, "a", ty.vec4<f32>(), CreateAttributes());
Func("vertex_main", Vector{p}, ty.vec4<f32>(),
Vector{
Return(Call<vec4<f32>>()),
},
Vector{
Stage(ast::PipelineStage::kVertex),
},
Vector{
Builtin(core::BuiltinValue::kPosition),
});
CHECK();
}
INSTANTIATE_TEST_SUITE_P(
ResolverAttributeValidationTest,
VertexShaderParameterAttributeTest,
testing::Values(
TestParams{
{AttributeKind::kAlign},
R"(1:2 error: @align is not valid for function parameters)",
},
TestParams{
{AttributeKind::kBinding},
R"(1:2 error: @binding is not valid for function parameters)",
},
TestParams{
{AttributeKind::kBuiltinPosition},
R"(1:2 error: @builtin(position) cannot be used in input of vertex pipeline stage)",
},
TestParams{
{AttributeKind::kDiagnostic},
R"(1:2 error: @diagnostic is not valid for function parameters)",
},
TestParams{
{AttributeKind::kGroup},
R"(1:2 error: @group is not valid for function parameters)",
},
TestParams{
{AttributeKind::kId},
R"(1:2 error: @id is not valid for function parameters)",
},
TestParams{
{AttributeKind::kIndex},
R"(1:2 error: @index is not valid for function parameters)",
},
TestParams{
{AttributeKind::kInterpolate},
R"(9:9 error: missing entry point IO attribute on parameter)",
},
TestParams{
{AttributeKind::kInterpolate, AttributeKind::kLocation},
Pass,
},
TestParams{
{AttributeKind::kInterpolate, AttributeKind::kBuiltinPosition},
R"(3:4 error: @builtin(position) cannot be used in input of vertex pipeline stage)",
},
TestParams{
{AttributeKind::kInvariant},
R"(9:9 error: missing entry point IO attribute on parameter)",
},
TestParams{
{AttributeKind::kInvariant, AttributeKind::kLocation},
R"(1:2 error: @invariant must be applied to a position builtin)",
},
TestParams{
{AttributeKind::kInvariant, AttributeKind::kBuiltinPosition},
R"(3:4 error: @builtin(position) cannot be used in input of vertex pipeline stage)",
},
TestParams{
{AttributeKind::kLocation},
Pass,
},
TestParams{
{AttributeKind::kMustUse},
R"(1:2 error: @must_use is not valid for function parameters)",
},
TestParams{
{AttributeKind::kOffset},
R"(1:2 error: @offset is not valid for function parameters)",
},
TestParams{
{AttributeKind::kSize},
R"(1:2 error: @size is not valid for function parameters)",
},
TestParams{
{AttributeKind::kStageCompute},
R"(1:2 error: @stage is not valid for function parameters)",
},
TestParams{
{AttributeKind::kStride},
R"(1:2 error: @stride is not valid for function parameters)",
},
TestParams{
{AttributeKind::kWorkgroupSize},
R"(1:2 error: @workgroup_size is not valid for function parameters)",
}));
using ComputeShaderReturnTypeAttributeTest = TestWithParams;
TEST_P(ComputeShaderReturnTypeAttributeTest, IsValid) {
EnableRequiredExtensions();
Func("main", tint::Empty, ty.vec4<f32>(),
Vector{
Return(Call<vec4<f32>>(1_f)),
},
Vector{
Stage(ast::PipelineStage::kCompute),
WorkgroupSize(1_i),
},
CreateAttributes());
CHECK();
}
INSTANTIATE_TEST_SUITE_P(
ResolverAttributeValidationTest,
ComputeShaderReturnTypeAttributeTest,
testing::Values(
TestParams{
{AttributeKind::kAlign},
R"(1:2 error: @align is not valid for entry point return types)",
},
TestParams{
{AttributeKind::kBinding},
R"(1:2 error: @binding is not valid for entry point return types)",
},
TestParams{
{AttributeKind::kBuiltinPosition},
R"(1:2 error: @builtin(position) cannot be used in output of compute pipeline stage)",
},
TestParams{
{AttributeKind::kDiagnostic},
R"(1:2 error: @diagnostic is not valid for entry point return types)",
},
TestParams{
{AttributeKind::kGroup},
R"(1:2 error: @group is not valid for entry point return types)",
},
TestParams{
{AttributeKind::kId},
R"(1:2 error: @id is not valid for entry point return types)",
},
TestParams{
{AttributeKind::kIndex},
R"(1:2 error: @index is not valid for compute shader output)",
},
TestParams{
{AttributeKind::kInterpolate},
R"(1:2 error: @interpolate is not valid for compute shader output)",
},
TestParams{
{AttributeKind::kInvariant},
R"(1:2 error: @invariant is not valid for compute shader output)",
},
TestParams{
{AttributeKind::kLocation},
R"(1:2 error: @location is not valid for compute shader output)",
},
TestParams{
{AttributeKind::kMustUse},
R"(1:2 error: @must_use is not valid for entry point return types)",
},
TestParams{
{AttributeKind::kOffset},
R"(1:2 error: @offset is not valid for entry point return types)",
},
TestParams{
{AttributeKind::kSize},
R"(1:2 error: @size is not valid for entry point return types)",
},
TestParams{
{AttributeKind::kStageCompute},
R"(1:2 error: @stage is not valid for entry point return types)",
},
TestParams{
{AttributeKind::kStride},
R"(1:2 error: @stride is not valid for entry point return types)",
},
TestParams{
{AttributeKind::kWorkgroupSize},
R"(1:2 error: @workgroup_size is not valid for entry point return types)",
}));
using FragmentShaderReturnTypeAttributeTest = TestWithParams;
TEST_P(FragmentShaderReturnTypeAttributeTest, IsValid) {
EnableRequiredExtensions();
Func(Source{{9, 9}}, "frag_main", tint::Empty, ty.vec4<f32>(),
Vector{Return(Call<vec4<f32>>())},
Vector{
Stage(ast::PipelineStage::kFragment),
},
CreateAttributes());
CHECK();
}
INSTANTIATE_TEST_SUITE_P(
ResolverAttributeValidationTest,
FragmentShaderReturnTypeAttributeTest,
testing::Values(
TestParams{
{AttributeKind::kAlign},
R"(1:2 error: @align is not valid for entry point return types)",
},
TestParams{
{AttributeKind::kBinding},
R"(1:2 error: @binding is not valid for entry point return types)",
},
TestParams{
{AttributeKind::kBuiltinPosition},
R"(1:2 error: @builtin(position) cannot be used in output of fragment pipeline stage)",
},
TestParams{
{AttributeKind::kDiagnostic},
R"(1:2 error: @diagnostic is not valid for entry point return types)",
},
TestParams{
{AttributeKind::kGroup},
R"(1:2 error: @group is not valid for entry point return types)",
},
TestParams{
{AttributeKind::kId},
R"(1:2 error: @id is not valid for entry point return types)",
},
TestParams{
{AttributeKind::kIndex},
Pass,
},
TestParams{
{AttributeKind::kIndex, AttributeKind::kLocation},
Pass,
},
TestParams{
{AttributeKind::kInterpolate},
R"(9:9 error: missing entry point IO attribute on return type)",
},
TestParams{
{AttributeKind::kInterpolate, AttributeKind::kLocation},
Pass,
},
TestParams{
{AttributeKind::kInvariant},
R"(9:9 error: missing entry point IO attribute on return type)",
},
TestParams{
{AttributeKind::kInvariant, AttributeKind::kLocation},
R"(1:2 error: @invariant must be applied to a position builtin)",
},
TestParams{
{AttributeKind::kLocation},
Pass,
},
TestParams{
{AttributeKind::kMustUse},
R"(1:2 error: @must_use is not valid for entry point return types)",
},
TestParams{
{AttributeKind::kOffset},
R"(1:2 error: @offset is not valid for entry point return types)",
},
TestParams{
{AttributeKind::kSize},
R"(1:2 error: @size is not valid for entry point return types)",
},
TestParams{
{AttributeKind::kStageCompute},
R"(1:2 error: @stage is not valid for entry point return types)",
},
TestParams{
{AttributeKind::kStride},
R"(1:2 error: @stride is not valid for entry point return types)",
},
TestParams{
{AttributeKind::kWorkgroupSize},
R"(1:2 error: @workgroup_size is not valid for entry point return types)",
},
TestParams{
{AttributeKind::kBinding, AttributeKind::kGroup},
R"(1:2 error: @binding is not valid for entry point return types)",
},
TestParams{
{AttributeKind::kIndex, AttributeKind::kLocation},
Pass,
}));
using VertexShaderReturnTypeAttributeTest = TestWithParams;
TEST_P(VertexShaderReturnTypeAttributeTest, IsValid) {
EnableRequiredExtensions();
auto attrs = CreateAttributes();
// a vertex shader must include the 'position' builtin in its return type
if (!GetParam().attributes.Any([](auto b) { return b == AttributeKind::kBuiltinPosition; })) {
attrs.Push(Builtin(Source{{9, 9}}, core::BuiltinValue::kPosition));
}
Func("vertex_main", tint::Empty, ty.vec4<f32>(),
Vector{
Return(Call<vec4<f32>>()),
},
Vector{
Stage(ast::PipelineStage::kVertex),
},
std::move(attrs));
CHECK();
}
INSTANTIATE_TEST_SUITE_P(
ResolverAttributeValidationTest,
VertexShaderReturnTypeAttributeTest,
testing::Values(
TestParams{
{AttributeKind::kAlign},
R"(1:2 error: @align is not valid for entry point return types)",
},
TestParams{
{AttributeKind::kBinding},
R"(1:2 error: @binding is not valid for entry point return types)",
},
TestParams{
{AttributeKind::kBuiltinPosition},
Pass,
},
TestParams{
{AttributeKind::kDiagnostic},
R"(1:2 error: @diagnostic is not valid for entry point return types)",
},
TestParams{
{AttributeKind::kGroup},
R"(1:2 error: @group is not valid for entry point return types)",
},
TestParams{
{AttributeKind::kId},
R"(1:2 error: @id is not valid for entry point return types)",
},
TestParams{
{AttributeKind::kIndex},
R"(1:2 error: @index is not valid for vertex shader output)",
},
TestParams{
{AttributeKind::kInterpolate},
R"(1:2 error: @interpolate can only be used with @location)",
},
TestParams{
{AttributeKind::kInvariant},
Pass,
},
TestParams{
{AttributeKind::kLocation},
R"(9:9 error: multiple entry point IO attributes
1:2 note: previously consumed @location)",
},
TestParams{
{AttributeKind::kMustUse},
R"(1:2 error: @must_use is not valid for entry point return types)",
},
TestParams{
{AttributeKind::kOffset},
R"(1:2 error: @offset is not valid for entry point return types)",
},
TestParams{
{AttributeKind::kSize},
R"(1:2 error: @size is not valid for entry point return types)",
},
TestParams{
{AttributeKind::kStageCompute},
R"(1:2 error: @stage is not valid for entry point return types)",
},
TestParams{
{AttributeKind::kStride},
R"(1:2 error: @stride is not valid for entry point return types)",
},
TestParams{
{AttributeKind::kWorkgroupSize},
R"(1:2 error: @workgroup_size is not valid for entry point return types)",
},
TestParams{
{AttributeKind::kBinding, AttributeKind::kGroup},
R"(1:2 error: @binding is not valid for entry point return types)",
},
TestParams{
{AttributeKind::kLocation, AttributeKind::kLocation},
R"(3:4 error: duplicate location attribute
1:2 note: first attribute declared here)",
}));
using EntryPointParameterAttributeTest = TestWithParams;
TEST_F(EntryPointParameterAttributeTest, DuplicateInternalAttribute) {
auto* s = Param("s", ty.sampler(core::type::SamplerKind::kSampler),
Vector{
Binding(0_a),
Group(0_a),
Disable(ast::DisabledValidation::kBindingPointCollision),
Disable(ast::DisabledValidation::kEntryPointParameter),
});
Func("f", Vector{s}, ty.void_(), tint::Empty,
Vector{
Stage(ast::PipelineStage::kFragment),
});
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
using EntryPointReturnTypeAttributeTest = ResolverTest;
TEST_F(EntryPointReturnTypeAttributeTest, DuplicateInternalAttribute) {
Func("f", tint::Empty, ty.i32(), Vector{Return(1_i)},
Vector{
Stage(ast::PipelineStage::kFragment),
},
Vector{
Disable(ast::DisabledValidation::kBindingPointCollision),
Disable(ast::DisabledValidation::kEntryPointParameter),
});
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
} // namespace EntryPointInputAndOutputTests
namespace StructAndStructMemberTests {
using StructAttributeTest = TestWithParams;
using SpirvBlockAttribute = ast::transform::AddBlockAttribute::BlockAttribute;
TEST_P(StructAttributeTest, IsValid) {
EnableRequiredExtensions();
Structure("S", Vector{Member("a", ty.f32())}, CreateAttributes());
CHECK();
}
INSTANTIATE_TEST_SUITE_P(
ResolverAttributeValidationTest,
StructAttributeTest,
testing::Values(
TestParams{
{AttributeKind::kAlign},
R"(1:2 error: @align is not valid for struct declarations)",
},
TestParams{
{AttributeKind::kBinding},
R"(1:2 error: @binding is not valid for struct declarations)",
},
TestParams{
{AttributeKind::kBuiltinPosition},
R"(1:2 error: @builtin is not valid for struct declarations)",
},
TestParams{
{AttributeKind::kDiagnostic},
R"(1:2 error: @diagnostic is not valid for struct declarations)",
},
TestParams{
{AttributeKind::kGroup},
R"(1:2 error: @group is not valid for struct declarations)",
},
TestParams{
{AttributeKind::kId},
R"(1:2 error: @id is not valid for struct declarations)",
},
TestParams{
{AttributeKind::kIndex},
R"(1:2 error: @index is not valid for struct declarations)",
},
TestParams{
{AttributeKind::kInterpolate},
R"(1:2 error: @interpolate is not valid for struct declarations)",
},
TestParams{
{AttributeKind::kInvariant},
R"(1:2 error: @invariant is not valid for struct declarations)",
},
TestParams{
{AttributeKind::kLocation},
R"(1:2 error: @location is not valid for struct declarations)",
},
TestParams{
{AttributeKind::kMustUse},
R"(1:2 error: @must_use is not valid for struct declarations)",
},
TestParams{
{AttributeKind::kOffset},
R"(1:2 error: @offset is not valid for struct declarations)",
},
TestParams{
{AttributeKind::kSize},
R"(1:2 error: @size is not valid for struct declarations)",
},
TestParams{
{AttributeKind::kStageCompute},
R"(1:2 error: @stage is not valid for struct declarations)",
},
TestParams{
{AttributeKind::kStride},
R"(1:2 error: @stride is not valid for struct declarations)",
},
TestParams{
{AttributeKind::kWorkgroupSize},
R"(1:2 error: @workgroup_size is not valid for struct declarations)",
},
TestParams{
{AttributeKind::kBinding, AttributeKind::kGroup},
R"(1:2 error: @binding is not valid for struct declarations)",
}));
using StructMemberAttributeTest = TestWithParams;
TEST_P(StructMemberAttributeTest, IsValid) {
EnableRequiredExtensions();
Structure("S", Vector{Member("a", ty.vec4<f32>(), CreateAttributes())});
CHECK();
}
INSTANTIATE_TEST_SUITE_P(ResolverAttributeValidationTest,
StructMemberAttributeTest,
testing::Values(
TestParams{
{AttributeKind::kAlign},
Pass,
},
TestParams{
{AttributeKind::kBinding},
R"(1:2 error: @binding is not valid for struct members)",
},
TestParams{
{AttributeKind::kBuiltinPosition},
Pass,
},
TestParams{
{AttributeKind::kDiagnostic},
R"(1:2 error: @diagnostic is not valid for struct members)",
},
TestParams{
{AttributeKind::kGroup},
R"(1:2 error: @group is not valid for struct members)",
},
TestParams{
{AttributeKind::kId},
R"(1:2 error: @id is not valid for struct members)",
},
TestParams{
{AttributeKind::kIndex},
R"(1:2 error: @index can only be used with @location)",
},
TestParams{
{AttributeKind::kInterpolate},
R"(1:2 error: @interpolate can only be used with @location)",
},
TestParams{
{AttributeKind::kInterpolate, AttributeKind::kLocation},
Pass,
},
TestParams{
{AttributeKind::kInvariant},
R"(1:2 error: @invariant must be applied to a position builtin)",
},
TestParams{
{AttributeKind::kInvariant, AttributeKind::kBuiltinPosition},
Pass,
},
TestParams{
{AttributeKind::kLocation},
Pass,
},
TestParams{
{AttributeKind::kMustUse},
R"(1:2 error: @must_use is not valid for struct members)",
},
TestParams{
{AttributeKind::kOffset},
Pass,
},
TestParams{
{AttributeKind::kSize},
Pass,
},
TestParams{
{AttributeKind::kStageCompute},
R"(1:2 error: @stage is not valid for struct members)",
},
TestParams{
{AttributeKind::kStride},
R"(1:2 error: @stride is not valid for struct members)",
},
TestParams{
{AttributeKind::kWorkgroupSize},
R"(1:2 error: @workgroup_size is not valid for struct members)",
},
TestParams{
{AttributeKind::kBinding, AttributeKind::kGroup},
R"(1:2 error: @binding is not valid for struct members)",
},
TestParams{
{AttributeKind::kAlign, AttributeKind::kAlign},
R"(3:4 error: duplicate align attribute
1:2 note: first attribute declared here)",
}));
TEST_F(StructMemberAttributeTest, Align_Attribute_Const) {
GlobalConst("val", ty.i32(), Expr(1_i));
Structure("mystruct", Vector{Member("a", ty.f32(), Vector{MemberAlign("val")})});
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(StructMemberAttributeTest, Align_Attribute_ConstNegative) {
GlobalConst("val", ty.i32(), Expr(-2_i));
Structure("mystruct",
Vector{Member("a", ty.f32(), Vector{MemberAlign(Source{{12, 34}}, "val")})});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
R"(12:34 error: @align value must be a positive, power-of-two integer)");
}
TEST_F(StructMemberAttributeTest, Align_Attribute_ConstPowerOfTwo) {
GlobalConst("val", ty.i32(), Expr(3_i));
Structure("mystruct",
Vector{Member("a", ty.f32(), Vector{MemberAlign(Source{{12, 34}}, "val")})});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
R"(12:34 error: @align value must be a positive, power-of-two integer)");
}
TEST_F(StructMemberAttributeTest, Align_Attribute_ConstF32) {
GlobalConst("val", ty.f32(), Expr(1.23_f));
Structure("mystruct",
Vector{Member("a", ty.f32(), Vector{MemberAlign(Source{{12, 34}}, "val")})});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), R"(12:34 error: @align must be an i32 or u32 value)");
}
TEST_F(StructMemberAttributeTest, Align_Attribute_ConstU32) {
GlobalConst("val", ty.u32(), Expr(2_u));
Structure("mystruct",
Vector{Member("a", ty.f32(), Vector{MemberAlign(Source{{12, 34}}, "val")})});
EXPECT_TRUE(r()->Resolve());
}
TEST_F(StructMemberAttributeTest, Align_Attribute_ConstAInt) {
GlobalConst("val", Expr(2_a));
Structure("mystruct",
Vector{Member("a", ty.f32(), Vector{MemberAlign(Source{{12, 34}}, "val")})});
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(StructMemberAttributeTest, Align_Attribute_ConstAFloat) {
GlobalConst("val", Expr(2.0_a));
Structure("mystruct",
Vector{Member("a", ty.f32(), Vector{MemberAlign(Source{{12, 34}}, "val")})});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), R"(12:34 error: @align must be an i32 or u32 value)");
}
TEST_F(StructMemberAttributeTest, Align_Attribute_Var) {
GlobalVar(Source{{1, 2}}, "val", ty.f32(), core::AddressSpace::kPrivate,
core::Access::kUndefined, Expr(1.23_f));
Structure(Source{{6, 4}}, "mystruct",
Vector{Member(Source{{12, 5}}, "a", ty.f32(),
Vector{MemberAlign(Expr(Source{{12, 35}}, "val"))})});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), R"(12:35 error: var 'val' cannot be referenced at module-scope
1:2 note: var 'val' declared here)");
}
TEST_F(StructMemberAttributeTest, Align_Attribute_Override) {
Override("val", ty.f32(), Expr(1.23_f));
Structure("mystruct",
Vector{Member("a", ty.f32(), Vector{MemberAlign(Expr(Source{{12, 34}}, "val"))})});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
R"(12:34 error: @align requires a const-expression, but expression is an override-expression)");
}
TEST_F(StructMemberAttributeTest, Size_Attribute_Const) {
GlobalConst("val", ty.i32(), Expr(4_i));
Structure("mystruct", Vector{Member("a", ty.f32(), Vector{MemberSize("val")})});
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(StructMemberAttributeTest, Size_Attribute_ConstNegative) {
GlobalConst("val", ty.i32(), Expr(-2_i));
Structure("mystruct",
Vector{Member("a", ty.f32(), Vector{MemberSize(Source{{12, 34}}, "val")})});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), R"(12:34 error: @size must be a positive integer)");
}
TEST_F(StructMemberAttributeTest, Size_Attribute_ConstF32) {
GlobalConst("val", ty.f32(), Expr(1.23_f));
Structure("mystruct",
Vector{Member("a", ty.f32(), Vector{MemberSize(Source{{12, 34}}, "val")})});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), R"(12:34 error: @size must be an i32 or u32 value)");
}
TEST_F(StructMemberAttributeTest, Size_Attribute_ConstU32) {
GlobalConst("val", ty.u32(), Expr(4_u));
Structure("mystruct",
Vector{Member("a", ty.f32(), Vector{MemberSize(Source{{12, 34}}, "val")})});
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(StructMemberAttributeTest, Size_Attribute_ConstAInt) {
GlobalConst("val", Expr(4_a));
Structure("mystruct",
Vector{Member("a", ty.f32(), Vector{MemberSize(Source{{12, 34}}, "val")})});
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(StructMemberAttributeTest, Size_Attribute_ConstAFloat) {
GlobalConst("val", Expr(2.0_a));
Structure("mystruct",
Vector{Member("a", ty.f32(), Vector{MemberSize(Source{{12, 34}}, "val")})});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), R"(12:34 error: @size must be an i32 or u32 value)");
}
TEST_F(StructMemberAttributeTest, Size_Attribute_Var) {
GlobalVar(Source{{1, 2}}, "val", ty.f32(), core::AddressSpace::kPrivate,
core::Access::kUndefined, Expr(1.23_f));
Structure(Source{{6, 4}}, "mystruct",
Vector{Member(Source{{12, 5}}, "a", ty.f32(),
Vector{MemberSize(Expr(Source{{12, 35}}, "val"))})});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), R"(12:35 error: var 'val' cannot be referenced at module-scope
1:2 note: var 'val' declared here)");
}
TEST_F(StructMemberAttributeTest, Size_Attribute_Override) {
Override("val", ty.f32(), Expr(1.23_f));
Structure("mystruct",
Vector{
Member("a", ty.f32(), Vector{MemberSize(Expr(Source{{12, 34}}, "val"))}),
});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
R"(12:34 error: @size requires a const-expression, but expression is an override-expression)");
}
TEST_F(StructMemberAttributeTest, Size_On_RuntimeSizedArray) {
Structure("mystruct",
Vector{
Member("a", ty.array<i32>(), Vector{MemberSize(Source{{12, 34}}, 8_a)}),
});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
R"(12:34 error: @size can only be applied to members where the member's type size can be fully determined at shader creation time)");
}
} // namespace StructAndStructMemberTests
using ArrayAttributeTest = TestWithParams;
TEST_P(ArrayAttributeTest, IsValid) {
EnableRequiredExtensions();
auto arr = ty.array(ty.f32(), CreateAttributes());
Structure("S", Vector{
Member("a", arr),
});
CHECK();
}
INSTANTIATE_TEST_SUITE_P(ResolverAttributeValidationTest,
ArrayAttributeTest,
testing::Values(
TestParams{
{AttributeKind::kAlign},
R"(1:2 error: @align is not valid for array types)",
},
TestParams{
{AttributeKind::kBinding},
R"(1:2 error: @binding is not valid for array types)",
},
TestParams{
{AttributeKind::kBuiltinPosition},
R"(1:2 error: @builtin is not valid for array types)",
},
TestParams{
{AttributeKind::kDiagnostic},
R"(1:2 error: @diagnostic is not valid for array types)",
},
TestParams{
{AttributeKind::kGroup},
R"(1:2 error: @group is not valid for array types)",
},
TestParams{
{AttributeKind::kId},
R"(1:2 error: @id is not valid for array types)",
},
TestParams{
{AttributeKind::kIndex},
R"(1:2 error: @index is not valid for array types)",
},
TestParams{
{AttributeKind::kInterpolate},
R"(1:2 error: @interpolate is not valid for array types)",
},
TestParams{
{AttributeKind::kInvariant},
R"(1:2 error: @invariant is not valid for array types)",
},
TestParams{
{AttributeKind::kLocation},
R"(1:2 error: @location is not valid for array types)",
},
TestParams{
{AttributeKind::kMustUse},
R"(1:2 error: @must_use is not valid for array types)",
},
TestParams{
{AttributeKind::kOffset},
R"(1:2 error: @offset is not valid for array types)",
},
TestParams{
{AttributeKind::kSize},
R"(1:2 error: @size is not valid for array types)",
},
TestParams{
{AttributeKind::kStageCompute},
R"(1:2 error: @stage is not valid for array types)",
},
TestParams{
{AttributeKind::kStride},
Pass,
},
TestParams{
{AttributeKind::kWorkgroupSize},
R"(1:2 error: @workgroup_size is not valid for array types)",
},
TestParams{
{AttributeKind::kBinding, AttributeKind::kGroup},
R"(1:2 error: @binding is not valid for array types)",
},
TestParams{
{AttributeKind::kStride, AttributeKind::kStride},
R"(3:4 error: duplicate stride attribute
1:2 note: first attribute declared here)",
}));
using VariableAttributeTest = TestWithParams;
TEST_P(VariableAttributeTest, IsValid) {
EnableRequiredExtensions();
if (GetParam().attributes.Any(IsBindingAttribute)) {
GlobalVar(Source{{9, 9}}, "a", ty.sampler(core::type::SamplerKind::kSampler),
CreateAttributes());
} else {
GlobalVar(Source{{9, 9}}, "a", ty.f32(), core::AddressSpace::kPrivate, CreateAttributes());
}
CHECK();
}
INSTANTIATE_TEST_SUITE_P(
ResolverAttributeValidationTest,
VariableAttributeTest,
testing::Values(
TestParams{
{AttributeKind::kAlign},
R"(1:2 error: @align is not valid for module-scope 'var')",
},
TestParams{
{AttributeKind::kBinding},
R"(9:9 error: resource variables require @group and @binding attributes)",
},
TestParams{
{AttributeKind::kBuiltinPosition},
R"(1:2 error: @builtin is not valid for module-scope 'var')",
},
TestParams{
{AttributeKind::kDiagnostic},
R"(1:2 error: @diagnostic is not valid for module-scope 'var')",
},
TestParams{
{AttributeKind::kGroup},
R"(9:9 error: resource variables require @group and @binding attributes)",
},
TestParams{
{AttributeKind::kId},
R"(1:2 error: @id is not valid for module-scope 'var')",
},
TestParams{
{AttributeKind::kIndex},
R"(1:2 error: @index is not valid for module-scope 'var')",
},
TestParams{
{AttributeKind::kInterpolate},
R"(1:2 error: @interpolate is not valid for module-scope 'var')",
},
TestParams{
{AttributeKind::kInvariant},
R"(1:2 error: @invariant is not valid for module-scope 'var')",
},
TestParams{
{AttributeKind::kLocation},
R"(1:2 error: @location is not valid for module-scope 'var')",
},
TestParams{
{AttributeKind::kMustUse},
R"(1:2 error: @must_use is not valid for module-scope 'var')",
},
TestParams{
{AttributeKind::kOffset},
R"(1:2 error: @offset is not valid for module-scope 'var')",
},
TestParams{
{AttributeKind::kSize},
R"(1:2 error: @size is not valid for module-scope 'var')",
},
TestParams{
{AttributeKind::kStageCompute},
R"(1:2 error: @stage is not valid for module-scope 'var')",
},
TestParams{
{AttributeKind::kStride},
R"(1:2 error: @stride is not valid for module-scope 'var')",
},
TestParams{
{AttributeKind::kWorkgroupSize},
R"(1:2 error: @workgroup_size is not valid for module-scope 'var')",
},
TestParams{
{AttributeKind::kBinding, AttributeKind::kGroup},
Pass,
},
TestParams{
{AttributeKind::kBinding, AttributeKind::kGroup, AttributeKind::kBinding},
R"(5:6 error: duplicate binding attribute
1:2 note: first attribute declared here)",
}));
TEST_F(VariableAttributeTest, LocalVar) {
auto* v = Var("a", ty.f32(), Vector{Binding(Source{{12, 34}}, 2_a)});
WrapInFunction(v);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), "12:34 error: @binding is not valid for function-scope 'var'");
}
TEST_F(VariableAttributeTest, LocalLet) {
auto* v = Let("a", Vector{Binding(Source{{12, 34}}, 2_a)}, Expr(1_a));
WrapInFunction(v);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), "12:34 error: @binding is not valid for 'let' declaration");
}
using ConstantAttributeTest = TestWithParams;
TEST_P(ConstantAttributeTest, IsValid) {
EnableRequiredExtensions();
GlobalConst("a", ty.f32(), Expr(1.23_f), CreateAttributes());
CHECK();
}
INSTANTIATE_TEST_SUITE_P(
ResolverAttributeValidationTest,
ConstantAttributeTest,
testing::Values(
TestParams{
{AttributeKind::kAlign},
R"(1:2 error: @align is not valid for 'const' declaration)",
},
TestParams{
{AttributeKind::kBinding},
R"(1:2 error: @binding is not valid for 'const' declaration)",
},
TestParams{
{AttributeKind::kBuiltinPosition},
R"(1:2 error: @builtin is not valid for 'const' declaration)",
},
TestParams{
{AttributeKind::kDiagnostic},
R"(1:2 error: @diagnostic is not valid for 'const' declaration)",
},
TestParams{
{AttributeKind::kGroup},
R"(1:2 error: @group is not valid for 'const' declaration)",
},
TestParams{
{AttributeKind::kId},
R"(1:2 error: @id is not valid for 'const' declaration)",
},
TestParams{
{AttributeKind::kIndex},
R"(1:2 error: @index is not valid for 'const' declaration)",
},
TestParams{
{AttributeKind::kInterpolate},
R"(1:2 error: @interpolate is not valid for 'const' declaration)",
},
TestParams{
{AttributeKind::kInvariant},
R"(1:2 error: @invariant is not valid for 'const' declaration)",
},
TestParams{
{AttributeKind::kLocation},
R"(1:2 error: @location is not valid for 'const' declaration)",
},
TestParams{
{AttributeKind::kMustUse},
R"(1:2 error: @must_use is not valid for 'const' declaration)",
},
TestParams{
{AttributeKind::kOffset},
R"(1:2 error: @offset is not valid for 'const' declaration)",
},
TestParams{
{AttributeKind::kSize},
R"(1:2 error: @size is not valid for 'const' declaration)",
},
TestParams{
{AttributeKind::kStageCompute},
R"(1:2 error: @stage is not valid for 'const' declaration)",
},
TestParams{
{AttributeKind::kStride},
R"(1:2 error: @stride is not valid for 'const' declaration)",
},
TestParams{
{AttributeKind::kWorkgroupSize},
R"(1:2 error: @workgroup_size is not valid for 'const' declaration)",
},
TestParams{
{AttributeKind::kBinding, AttributeKind::kGroup},
R"(1:2 error: @binding is not valid for 'const' declaration)",
}));
using OverrideAttributeTest = TestWithParams;
TEST_P(OverrideAttributeTest, IsValid) {
EnableRequiredExtensions();
Override("a", ty.f32(), Expr(1.23_f), CreateAttributes());
CHECK();
}
INSTANTIATE_TEST_SUITE_P(
ResolverAttributeValidationTest,
OverrideAttributeTest,
testing::Values(
TestParams{
{AttributeKind::kAlign},
R"(1:2 error: @align is not valid for 'override' declaration)",
},
TestParams{
{AttributeKind::kBinding},
R"(1:2 error: @binding is not valid for 'override' declaration)",
},
TestParams{
{AttributeKind::kBuiltinPosition},
R"(1:2 error: @builtin is not valid for 'override' declaration)",
},
TestParams{
{AttributeKind::kDiagnostic},
R"(1:2 error: @diagnostic is not valid for 'override' declaration)",
},
TestParams{
{AttributeKind::kGroup},
R"(1:2 error: @group is not valid for 'override' declaration)",
},
TestParams{
{AttributeKind::kIndex},
R"(1:2 error: @index is not valid for 'override' declaration)",
},
TestParams{
{AttributeKind::kId},
Pass,
},
TestParams{
{AttributeKind::kInterpolate},
R"(1:2 error: @interpolate is not valid for 'override' declaration)",
},
TestParams{
{AttributeKind::kInvariant},
R"(1:2 error: @invariant is not valid for 'override' declaration)",
},
TestParams{
{AttributeKind::kLocation},
R"(1:2 error: @location is not valid for 'override' declaration)",
},
TestParams{
{AttributeKind::kMustUse},
R"(1:2 error: @must_use is not valid for 'override' declaration)",
},
TestParams{
{AttributeKind::kOffset},
R"(1:2 error: @offset is not valid for 'override' declaration)",
},
TestParams{
{AttributeKind::kSize},
R"(1:2 error: @size is not valid for 'override' declaration)",
},
TestParams{
{AttributeKind::kStageCompute},
R"(1:2 error: @stage is not valid for 'override' declaration)",
},
TestParams{
{AttributeKind::kStride},
R"(1:2 error: @stride is not valid for 'override' declaration)",
},
TestParams{
{AttributeKind::kWorkgroupSize},
R"(1:2 error: @workgroup_size is not valid for 'override' declaration)",
},
TestParams{
{AttributeKind::kBinding, AttributeKind::kGroup},
R"(1:2 error: @binding is not valid for 'override' declaration)",
},
TestParams{
{AttributeKind::kId, AttributeKind::kId},
R"(3:4 error: duplicate id attribute
1:2 note: first attribute declared here)",
}));
using SwitchStatementAttributeTest = TestWithParams;
TEST_P(SwitchStatementAttributeTest, IsValid) {
EnableRequiredExtensions();
WrapInFunction(Switch(Expr(0_a), Vector{DefaultCase()}, CreateAttributes()));
CHECK();
}
INSTANTIATE_TEST_SUITE_P(ResolverAttributeValidationTest,
SwitchStatementAttributeTest,
testing::ValuesIn(OnlyDiagnosticValidFor("switch statements")));
using SwitchBodyAttributeTest = TestWithParams;
TEST_P(SwitchBodyAttributeTest, IsValid) {
EnableRequiredExtensions();
WrapInFunction(Switch(Expr(0_a), Vector{DefaultCase()}, tint::Empty, CreateAttributes()));
CHECK();
}
INSTANTIATE_TEST_SUITE_P(ResolverAttributeValidationTest,
SwitchBodyAttributeTest,
testing::ValuesIn(OnlyDiagnosticValidFor("switch body")));
using IfStatementAttributeTest = TestWithParams;
TEST_P(IfStatementAttributeTest, IsValid) {
EnableRequiredExtensions();
WrapInFunction(If(Expr(true), Block(), ElseStmt(), CreateAttributes()));
CHECK();
}
INSTANTIATE_TEST_SUITE_P(ResolverAttributeValidationTest,
IfStatementAttributeTest,
testing::ValuesIn(OnlyDiagnosticValidFor("if statements")));
using ForStatementAttributeTest = TestWithParams;
TEST_P(ForStatementAttributeTest, IsValid) {
EnableRequiredExtensions();
WrapInFunction(For(nullptr, Expr(false), nullptr, Block(), CreateAttributes()));
CHECK();
}
INSTANTIATE_TEST_SUITE_P(ResolverAttributeValidationTest,
ForStatementAttributeTest,
testing::ValuesIn(OnlyDiagnosticValidFor("for statements")));
using LoopStatementAttributeTest = TestWithParams;
TEST_P(LoopStatementAttributeTest, IsValid) {
EnableRequiredExtensions();
WrapInFunction(Loop(Block(Return()), Block(), CreateAttributes()));
CHECK();
}
INSTANTIATE_TEST_SUITE_P(ResolverAttributeValidationTest,
LoopStatementAttributeTest,
testing::ValuesIn(OnlyDiagnosticValidFor("loop statements")));
using WhileStatementAttributeTest = TestWithParams;
TEST_P(WhileStatementAttributeTest, IsValid) {
EnableRequiredExtensions();
WrapInFunction(While(Expr(false), Block(), CreateAttributes()));
CHECK();
}
INSTANTIATE_TEST_SUITE_P(ResolverAttributeValidationTest,
WhileStatementAttributeTest,
testing::ValuesIn(OnlyDiagnosticValidFor("while statements")));
using BlockStatementTest = TestWithParams;
TEST_P(BlockStatementTest, CompoundStatement) {
Func("foo", tint::Empty, ty.void_(),
Vector{
Block(Vector{Return()}, CreateAttributes()),
});
CHECK();
}
TEST_P(BlockStatementTest, FunctionBody) {
Func("foo", tint::Empty, ty.void_(), Block(Vector{Return()}, CreateAttributes()));
CHECK();
}
TEST_P(BlockStatementTest, IfStatementBody) {
Func("foo", tint::Empty, ty.void_(),
Vector{
If(Expr(true), Block(Vector{Return()}, CreateAttributes())),
});
CHECK();
}
TEST_P(BlockStatementTest, ElseStatementBody) {
Func("foo", tint::Empty, ty.void_(),
Vector{
If(Expr(true), Block(Vector{Return()}),
Else(Block(Vector{Return()}, CreateAttributes()))),
});
CHECK();
}
TEST_P(BlockStatementTest, ForStatementBody) {
Func("foo", tint::Empty, ty.void_(),
Vector{
For(nullptr, Expr(true), nullptr, Block(Vector{Break()}, CreateAttributes())),
});
CHECK();
}
TEST_P(BlockStatementTest, LoopStatementBody) {
Func("foo", tint::Empty, ty.void_(),
Vector{
Loop(Block(Vector{Break()}, CreateAttributes())),
});
CHECK();
}
TEST_P(BlockStatementTest, WhileStatementBody) {
Func("foo", tint::Empty, ty.void_(),
Vector{
While(Expr(true), Block(Vector{Break()}, CreateAttributes())),
});
CHECK();
}
TEST_P(BlockStatementTest, CaseStatementBody) {
Func("foo", tint::Empty, ty.void_(),
Vector{
Switch(1_a, Case(CaseSelector(1_a), Block(Vector{Break()}, CreateAttributes())),
DefaultCase(Block({}))),
});
CHECK();
}
TEST_P(BlockStatementTest, DefaultStatementBody) {
Func("foo", tint::Empty, ty.void_(),
Vector{
Switch(1_a, Case(CaseSelector(1_a), Block()),
DefaultCase(Block(Vector{Break()}, CreateAttributes()))),
});
CHECK();
}
INSTANTIATE_TEST_SUITE_P(ResolverAttributeValidationTest,
BlockStatementTest,
testing::ValuesIn(OnlyDiagnosticValidFor("block statements")));
} // namespace
} // namespace AttributeTests
namespace ArrayStrideTests {
namespace {
struct Params {
builder::ast_type_func_ptr create_el_type;
uint32_t stride;
bool should_pass;
};
template <typename T>
constexpr Params ParamsFor(uint32_t stride, bool should_pass) {
return Params{DataType<T>::AST, stride, should_pass};
}
struct TestWithParams : ResolverTestWithParam<Params> {};
using ArrayStrideTest = TestWithParams;
TEST_P(ArrayStrideTest, All) {
auto& params = GetParam();
ast::Type el_ty = params.create_el_type(*this);
StringStream ss;
ss << "el_ty: " << FriendlyName(el_ty) << ", stride: " << params.stride
<< ", should_pass: " << params.should_pass;
SCOPED_TRACE(ss.str());
auto arr = ty.array(el_ty, 4_u,
Vector{
create<ast::StrideAttribute>(Source{{12, 34}}, params.stride),
});
GlobalVar("myarray", arr, core::AddressSpace::kPrivate);
if (params.should_pass) {
EXPECT_TRUE(r()->Resolve()) << r()->error();
} else {
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
"12:34 error: arrays decorated with the stride attribute must have a stride that "
"is at least the size of the element type, and be a multiple of the element "
"type's alignment value");
}
}
struct SizeAndAlignment {
uint32_t size;
uint32_t align;
};
constexpr SizeAndAlignment default_u32 = {4, 4};
constexpr SizeAndAlignment default_i32 = {4, 4};
constexpr SizeAndAlignment default_f32 = {4, 4};
constexpr SizeAndAlignment default_vec2 = {8, 8};
constexpr SizeAndAlignment default_vec3 = {12, 16};
constexpr SizeAndAlignment default_vec4 = {16, 16};
constexpr SizeAndAlignment default_mat2x2 = {16, 8};
constexpr SizeAndAlignment default_mat3x3 = {48, 16};
constexpr SizeAndAlignment default_mat4x4 = {64, 16};
INSTANTIATE_TEST_SUITE_P(ResolverAttributeValidationTest,
ArrayStrideTest,
testing::Values(
// Succeed because stride >= element size (while being multiple of
// element alignment)
ParamsFor<u32>(default_u32.size, true),
ParamsFor<i32>(default_i32.size, true),
ParamsFor<f32>(default_f32.size, true),
ParamsFor<vec2<f32>>(default_vec2.size, true),
// vec3's default size is not a multiple of its alignment
// ParamsFor<vec3<f32>, default_vec3.size, true},
ParamsFor<vec4<f32>>(default_vec4.size, true),
ParamsFor<mat2x2<f32>>(default_mat2x2.size, true),
ParamsFor<mat3x3<f32>>(default_mat3x3.size, true),
ParamsFor<mat4x4<f32>>(default_mat4x4.size, true),
// Fail because stride is < element size
ParamsFor<u32>(default_u32.size - 1, false),
ParamsFor<i32>(default_i32.size - 1, false),
ParamsFor<f32>(default_f32.size - 1, false),
ParamsFor<vec2<f32>>(default_vec2.size - 1, false),
ParamsFor<vec3<f32>>(default_vec3.size - 1, false),
ParamsFor<vec4<f32>>(default_vec4.size - 1, false),
ParamsFor<mat2x2<f32>>(default_mat2x2.size - 1, false),
ParamsFor<mat3x3<f32>>(default_mat3x3.size - 1, false),
ParamsFor<mat4x4<f32>>(default_mat4x4.size - 1, false),
// Succeed because stride equals multiple of element alignment
ParamsFor<u32>(default_u32.align * 7, true),
ParamsFor<i32>(default_i32.align * 7, true),
ParamsFor<f32>(default_f32.align * 7, true),
ParamsFor<vec2<f32>>(default_vec2.align * 7, true),
ParamsFor<vec3<f32>>(default_vec3.align * 7, true),
ParamsFor<vec4<f32>>(default_vec4.align * 7, true),
ParamsFor<mat2x2<f32>>(default_mat2x2.align * 7, true),
ParamsFor<mat3x3<f32>>(default_mat3x3.align * 7, true),
ParamsFor<mat4x4<f32>>(default_mat4x4.align * 7, true),
// Fail because stride is not multiple of element alignment
ParamsFor<u32>((default_u32.align - 1) * 7, false),
ParamsFor<i32>((default_i32.align - 1) * 7, false),
ParamsFor<f32>((default_f32.align - 1) * 7, false),
ParamsFor<vec2<f32>>((default_vec2.align - 1) * 7, false),
ParamsFor<vec3<f32>>((default_vec3.align - 1) * 7, false),
ParamsFor<vec4<f32>>((default_vec4.align - 1) * 7, false),
ParamsFor<mat2x2<f32>>((default_mat2x2.align - 1) * 7, false),
ParamsFor<mat3x3<f32>>((default_mat3x3.align - 1) * 7, false),
ParamsFor<mat4x4<f32>>((default_mat4x4.align - 1) * 7, false)));
TEST_F(ArrayStrideTest, DuplicateAttribute) {
auto arr = ty.array(Source{{12, 34}}, ty.i32(), 4_u,
Vector{
create<ast::StrideAttribute>(Source{{12, 34}}, 4u),
create<ast::StrideAttribute>(Source{{56, 78}}, 4u),
});
GlobalVar("myarray", arr, core::AddressSpace::kPrivate);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
R"(56:78 error: duplicate stride attribute
12:34 note: first attribute declared here)");
}
} // namespace
} // namespace ArrayStrideTests
namespace ResourceTests {
namespace {
using ResourceAttributeTest = ResolverTest;
TEST_F(ResourceAttributeTest, UniformBufferMissingBinding) {
auto* s = Structure("S", Vector{
Member("x", ty.i32()),
});
GlobalVar(Source{{12, 34}}, "G", ty.Of(s), core::AddressSpace::kUniform);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
R"(12:34 error: resource variables require @group and @binding attributes)");
}
TEST_F(ResourceAttributeTest, StorageBufferMissingBinding) {
auto* s = Structure("S", Vector{
Member("x", ty.i32()),
});
GlobalVar(Source{{12, 34}}, "G", ty.Of(s), core::AddressSpace::kStorage, core::Access::kRead);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
R"(12:34 error: resource variables require @group and @binding attributes)");
}
TEST_F(ResourceAttributeTest, TextureMissingBinding) {
GlobalVar(Source{{12, 34}}, "G", ty.depth_texture(core::type::TextureDimension::k2d));
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
R"(12:34 error: resource variables require @group and @binding attributes)");
}
TEST_F(ResourceAttributeTest, SamplerMissingBinding) {
GlobalVar(Source{{12, 34}}, "G", ty.sampler(core::type::SamplerKind::kSampler));
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
R"(12:34 error: resource variables require @group and @binding attributes)");
}
TEST_F(ResourceAttributeTest, BindingPairMissingBinding) {
GlobalVar(Source{{12, 34}}, "G", ty.sampler(core::type::SamplerKind::kSampler), Group(1_a));
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
R"(12:34 error: resource variables require @group and @binding attributes)");
}
TEST_F(ResourceAttributeTest, BindingPairMissingGroup) {
GlobalVar(Source{{12, 34}}, "G", ty.sampler(core::type::SamplerKind::kSampler), Binding(1_a));
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
R"(12:34 error: resource variables require @group and @binding attributes)");
}
TEST_F(ResourceAttributeTest, BindingPointUsedTwiceByEntryPoint) {
GlobalVar(Source{{12, 34}}, "A",
ty.sampled_texture(core::type::TextureDimension::k2d, ty.f32()), Binding(1_a),
Group(2_a));
GlobalVar(Source{{56, 78}}, "B",
ty.sampled_texture(core::type::TextureDimension::k2d, ty.f32()), Binding(1_a),
Group(2_a));
Func("F", tint::Empty, ty.void_(),
Vector{
Decl(Var("a", ty.vec4<f32>(),
Call("textureLoad", "A", Call<vec2<i32>>(1_i, 2_i), 0_i))),
Decl(Var("b", ty.vec4<f32>(),
Call("textureLoad", "B", Call<vec2<i32>>(1_i, 2_i), 0_i))),
},
Vector{
Stage(ast::PipelineStage::kFragment),
});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
R"(56:78 error: entry point 'F' references multiple variables that use the same resource binding @group(2), @binding(1)
12:34 note: first resource binding usage declared here)");
}
TEST_F(ResourceAttributeTest, BindingPointUsedTwiceByDifferentEntryPoints) {
GlobalVar(Source{{12, 34}}, "A",
ty.sampled_texture(core::type::TextureDimension::k2d, ty.f32()), Binding(1_a),
Group(2_a));
GlobalVar(Source{{56, 78}}, "B",
ty.sampled_texture(core::type::TextureDimension::k2d, ty.f32()), Binding(1_a),
Group(2_a));
Func("F_A", tint::Empty, ty.void_(),
Vector{
Decl(Var("a", ty.vec4<f32>(),
Call("textureLoad", "A", Call<vec2<i32>>(1_i, 2_i), 0_i))),
},
Vector{
Stage(ast::PipelineStage::kFragment),
});
Func("F_B", tint::Empty, ty.void_(),
Vector{
Decl(Var("b", ty.vec4<f32>(),
Call("textureLoad", "B", Call<vec2<i32>>(1_i, 2_i), 0_i))),
},
Vector{
Stage(ast::PipelineStage::kFragment),
});
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(ResourceAttributeTest, BindingPointOnNonResource) {
GlobalVar(Source{{12, 34}}, "G", ty.f32(), core::AddressSpace::kPrivate, Binding(1_a),
Group(2_a));
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
R"(12:34 error: non-resource variables must not have @group or @binding attributes)");
}
} // namespace
} // namespace ResourceTests
namespace WorkgroupAttributeTests {
namespace {
using WorkgroupAttribute = ResolverTest;
TEST_F(WorkgroupAttribute, NotAnEntryPoint) {
Func("main", tint::Empty, ty.void_(), tint::Empty,
Vector{
create<ast::WorkgroupAttribute>(Source{{12, 34}}, Expr(1_i)),
});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), R"(12:34 error: @workgroup_size is only valid for compute stages)");
}
TEST_F(WorkgroupAttribute, NotAComputeShader) {
Func("main", tint::Empty, ty.void_(), tint::Empty,
Vector{
Stage(ast::PipelineStage::kFragment),
create<ast::WorkgroupAttribute>(Source{{12, 34}}, Expr(1_i)),
});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), R"(12:34 error: @workgroup_size is only valid for compute stages)");
}
TEST_F(WorkgroupAttribute, DuplicateAttribute) {
Func(Source{{12, 34}}, "main", tint::Empty, ty.void_(), tint::Empty,
Vector{
Stage(ast::PipelineStage::kCompute),
WorkgroupSize(Source{{12, 34}}, 1_i, nullptr, nullptr),
WorkgroupSize(Source{{56, 78}}, 2_i, nullptr, nullptr),
});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
R"(56:78 error: duplicate workgroup_size attribute
12:34 note: first attribute declared here)");
}
} // namespace
} // namespace WorkgroupAttributeTests
namespace InterpolateTests {
namespace {
using InterpolateTest = ResolverTest;
struct Params {
core::InterpolationType type;
core::InterpolationSampling sampling;
bool should_pass;
};
struct TestWithParams : ResolverTestWithParam<Params> {};
using InterpolateParameterTest = TestWithParams;
TEST_P(InterpolateParameterTest, All) {
auto& params = GetParam();
Func("main",
Vector{
Param("a", ty.f32(),
Vector{
Location(0_a),
Interpolate(Source{{12, 34}}, params.type, params.sampling),
}),
},
ty.void_(), tint::Empty,
Vector{
Stage(ast::PipelineStage::kFragment),
});
if (params.should_pass) {
EXPECT_TRUE(r()->Resolve()) << r()->error();
} else {
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
R"(12:34 error: flat interpolation attribute must not have a sampling parameter)");
}
}
TEST_P(InterpolateParameterTest, IntegerScalar) {
auto& params = GetParam();
Func("main",
Vector{
Param("a", ty.i32(),
Vector{
Location(0_a),
Interpolate(Source{{12, 34}}, params.type, params.sampling),
}),
},
ty.void_(), tint::Empty,
Vector{
Stage(ast::PipelineStage::kFragment),
});
if (params.type != core::InterpolationType::kFlat) {
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
R"(12:34 error: interpolation type must be 'flat' for integral user-defined IO types)");
} else if (params.should_pass) {
EXPECT_TRUE(r()->Resolve()) << r()->error();
} else {
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
R"(12:34 error: flat interpolation attribute must not have a sampling parameter)");
}
}
TEST_P(InterpolateParameterTest, IntegerVector) {
auto& params = GetParam();
Func("main",
Vector{
Param("a", ty.vec4<u32>(),
Vector{
Location(0_a),
Interpolate(Source{{12, 34}}, params.type, params.sampling),
}),
},
ty.void_(), tint::Empty,
Vector{
Stage(ast::PipelineStage::kFragment),
});
if (params.type != core::InterpolationType::kFlat) {
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
R"(12:34 error: interpolation type must be 'flat' for integral user-defined IO types)");
} else if (params.should_pass) {
EXPECT_TRUE(r()->Resolve()) << r()->error();
} else {
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
R"(12:34 error: flat interpolation attribute must not have a sampling parameter)");
}
}
INSTANTIATE_TEST_SUITE_P(
ResolverAttributeValidationTest,
InterpolateParameterTest,
testing::Values(
Params{core::InterpolationType::kPerspective, core::InterpolationSampling::kUndefined,
true},
Params{core::InterpolationType::kPerspective, core::InterpolationSampling::kCenter, true},
Params{core::InterpolationType::kPerspective, core::InterpolationSampling::kCentroid, true},
Params{core::InterpolationType::kPerspective, core::InterpolationSampling::kSample, true},
Params{core::InterpolationType::kLinear, core::InterpolationSampling::kUndefined, true},
Params{core::InterpolationType::kLinear, core::InterpolationSampling::kCenter, true},
Params{core::InterpolationType::kLinear, core::InterpolationSampling::kCentroid, true},
Params{core::InterpolationType::kLinear, core::InterpolationSampling::kSample, true},
// flat interpolation must not have a sampling type
Params{core::InterpolationType::kFlat, core::InterpolationSampling::kUndefined, true},
Params{core::InterpolationType::kFlat, core::InterpolationSampling::kCenter, false},
Params{core::InterpolationType::kFlat, core::InterpolationSampling::kCentroid, false},
Params{core::InterpolationType::kFlat, core::InterpolationSampling::kSample, false}));
TEST_F(InterpolateTest, FragmentInput_Integer_MissingFlatInterpolation) {
Func("main", Vector{Param(Source{{12, 34}}, "a", ty.i32(), Vector{Location(0_a)})}, ty.void_(),
tint::Empty,
Vector{
Stage(ast::PipelineStage::kFragment),
});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
R"(12:34 error: integral user-defined fragment inputs must have a flat interpolation attribute)");
}
TEST_F(InterpolateTest, VertexOutput_Integer_MissingFlatInterpolation) {
auto* s = Structure(
"S", Vector{
Member("pos", ty.vec4<f32>(), Vector{Builtin(core::BuiltinValue::kPosition)}),
Member(Source{{12, 34}}, "u", ty.u32(), Vector{Location(0_a)}),
});
Func("main", tint::Empty, ty.Of(s),
Vector{
Return(Call(ty.Of(s))),
},
Vector{
Stage(ast::PipelineStage::kVertex),
});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
R"(12:34 error: integral user-defined vertex outputs must have a flat interpolation attribute
note: while analyzing entry point 'main')");
}
using GroupAndBindingTest = ResolverTest;
TEST_F(GroupAndBindingTest, Const_I32) {
GlobalConst("b", Expr(4_i));
GlobalConst("g", Expr(2_i));
GlobalVar("val", ty.sampled_texture(core::type::TextureDimension::k2d, ty.f32()), Binding("b"),
Group("g"));
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(GroupAndBindingTest, Const_U32) {
GlobalConst("b", Expr(4_u));
GlobalConst("g", Expr(2_u));
GlobalVar("val", ty.sampled_texture(core::type::TextureDimension::k2d, ty.f32()), Binding("b"),
Group("g"));
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(GroupAndBindingTest, Const_AInt) {
GlobalConst("b", Expr(4_a));
GlobalConst("g", Expr(2_a));
GlobalVar("val", ty.sampled_texture(core::type::TextureDimension::k2d, ty.f32()), Binding("b"),
Group("g"));
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(GroupAndBindingTest, Binding_NonConstant) {
GlobalVar("val", ty.sampled_texture(core::type::TextureDimension::k2d, ty.f32()),
Binding(Call<u32>(Call(Source{{12, 34}}, "dpdx", 1_a))), Group(1_i));
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
R"(12:34 error: @binding requires a const-expression, but expression is a runtime-expression)");
}
TEST_F(GroupAndBindingTest, Binding_Negative) {
GlobalVar("val", ty.sampled_texture(core::type::TextureDimension::k2d, ty.f32()),
Binding(Source{{12, 34}}, -2_i), Group(1_i));
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), R"(12:34 error: @binding value must be non-negative)");
}
TEST_F(GroupAndBindingTest, Binding_F32) {
GlobalVar("val", ty.sampled_texture(core::type::TextureDimension::k2d, ty.f32()),
Binding(Source{{12, 34}}, 2.0_f), Group(1_u));
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), R"(12:34 error: @binding must be an i32 or u32 value)");
}
TEST_F(GroupAndBindingTest, Binding_AFloat) {
GlobalVar("val", ty.sampled_texture(core::type::TextureDimension::k2d, ty.f32()),
Binding(Source{{12, 34}}, 2.0_a), Group(1_u));
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), R"(12:34 error: @binding must be an i32 or u32 value)");
}
TEST_F(GroupAndBindingTest, Group_NonConstant) {
GlobalVar("val", ty.sampled_texture(core::type::TextureDimension::k2d, ty.f32()), Binding(2_u),
Group(Call<u32>(Call(Source{{12, 34}}, "dpdx", 1_a))));
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
R"(12:34 error: @group requires a const-expression, but expression is a runtime-expression)");
}
TEST_F(GroupAndBindingTest, Group_Negative) {
GlobalVar("val", ty.sampled_texture(core::type::TextureDimension::k2d, ty.f32()), Binding(2_u),
Group(Source{{12, 34}}, -1_i));
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), R"(12:34 error: @group value must be non-negative)");
}
TEST_F(GroupAndBindingTest, Group_F32) {
GlobalVar("val", ty.sampled_texture(core::type::TextureDimension::k2d, ty.f32()), Binding(2_u),
Group(Source{{12, 34}}, 1.0_f));
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), R"(12:34 error: @group must be an i32 or u32 value)");
}
TEST_F(GroupAndBindingTest, Group_AFloat) {
GlobalVar("val", ty.sampled_texture(core::type::TextureDimension::k2d, ty.f32()), Binding(2_u),
Group(Source{{12, 34}}, 1.0_a));
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), R"(12:34 error: @group must be an i32 or u32 value)");
}
using IdTest = ResolverTest;
TEST_F(IdTest, Const_I32) {
Override("val", ty.f32(), Vector{Id(1_i)});
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(IdTest, Const_U32) {
Override("val", ty.f32(), Vector{Id(1_u)});
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(IdTest, Const_AInt) {
Override("val", ty.f32(), Vector{Id(1_a)});
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(IdTest, NonConstant) {
Override("val", ty.f32(), Vector{Id(Call<u32>(Call(Source{{12, 34}}, "dpdx", 1_a)))});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
R"(12:34 error: @id requires a const-expression, but expression is a runtime-expression)");
}
TEST_F(IdTest, Negative) {
Override("val", ty.f32(), Vector{Id(Source{{12, 34}}, -1_i)});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), R"(12:34 error: @id value must be non-negative)");
}
TEST_F(IdTest, F32) {
Override("val", ty.f32(), Vector{Id(Source{{12, 34}}, 1_f)});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), R"(12:34 error: @id must be an i32 or u32 value)");
}
TEST_F(IdTest, AFloat) {
Override("val", ty.f32(), Vector{Id(Source{{12, 34}}, 1.0_a)});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), R"(12:34 error: @id must be an i32 or u32 value)");
}
enum class LocationAttributeType {
kEntryPointParameter,
kEntryPointReturnType,
kStructureMember,
};
struct LocationTest : ResolverTestWithParam<LocationAttributeType> {
void Build(const ast::Expression* location_value) {
switch (GetParam()) {
case LocationAttributeType::kEntryPointParameter:
Func("main",
Vector{Param(Source{{12, 34}}, "a", ty.i32(),
Vector{
Location(Source{{12, 34}}, location_value),
Flat(),
})},
ty.void_(), tint::Empty,
Vector{
Stage(ast::PipelineStage::kFragment),
});
return;
case LocationAttributeType::kEntryPointReturnType:
Func("main", tint::Empty, ty.f32(),
Vector{
Return(1_a),
},
Vector{
Stage(ast::PipelineStage::kFragment),
},
Vector{
Location(Source{{12, 34}}, location_value),
});
return;
case LocationAttributeType::kStructureMember:
Structure("S", Vector{
Member("m", ty.f32(),
Vector{
Location(Source{{12, 34}}, location_value),
}),
});
return;
}
}
};
TEST_P(LocationTest, Const_I32) {
Build(Expr(0_i));
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_P(LocationTest, Const_U32) {
Build(Expr(0_u));
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_P(LocationTest, Const_AInt) {
Build(Expr(0_a));
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_P(LocationTest, NonConstant) {
Build(Call<u32>(Call(Source{{12, 34}}, "dpdx", 1_a)));
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
R"(12:34 error: @location value requires a const-expression, but expression is a runtime-expression)");
}
TEST_P(LocationTest, Negative) {
Build(Expr(-1_a));
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), R"(12:34 error: @location value must be non-negative)");
}
TEST_P(LocationTest, F32) {
Build(Expr(1_f));
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), R"(12:34 error: @location must be an i32 or u32 value)");
}
TEST_P(LocationTest, AFloat) {
Build(Expr(1.0_a));
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), R"(12:34 error: @location must be an i32 or u32 value)");
}
INSTANTIATE_TEST_SUITE_P(LocationTest,
LocationTest,
testing::Values(LocationAttributeType::kEntryPointParameter,
LocationAttributeType::kEntryPointReturnType,
LocationAttributeType::kStructureMember));
} // namespace
} // namespace InterpolateTests
namespace InternalAttributeDeps {
namespace {
class TestAttribute : public Castable<TestAttribute, ast::InternalAttribute> {
public:
TestAttribute(GenerationID pid, ast::NodeID nid, const ast::IdentifierExpression* dep)
: Base(pid, nid, Vector{dep}) {}
std::string InternalName() const override { return "test_attribute"; }
const Node* Clone(ast::CloneContext&) const override { return nullptr; }
};
using InternalAttributeDepsTest = ResolverTest;
TEST_F(InternalAttributeDepsTest, Dependency) {
auto* ident = Expr("v");
auto* attr = ASTNodes().Create<TestAttribute>(ID(), AllocateNodeID(), ident);
auto* f = Func("f", tint::Empty, ty.void_(), tint::Empty, Vector{attr});
auto* v = GlobalVar("v", ty.i32(), core::AddressSpace::kPrivate);
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* user = As<sem::VariableUser>(Sem().Get(ident));
ASSERT_NE(user, nullptr);
auto* var = Sem().Get(v);
EXPECT_EQ(user->Variable(), var);
auto* fn = Sem().Get(f);
EXPECT_THAT(fn->DirectlyReferencedGlobals(), testing::ElementsAre(var));
EXPECT_THAT(fn->TransitivelyReferencedGlobals(), testing::ElementsAre(var));
}
} // namespace
} // namespace InternalAttributeDeps
} // namespace tint::resolver
TINT_INSTANTIATE_TYPEINFO(tint::resolver::InternalAttributeDeps::TestAttribute);