| // Copyright 2021 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 "src/ast/intrinsic_texture_helper_test.h" |
| #include "src/resolver/resolver_test_helper.h" |
| |
| namespace tint { |
| namespace resolver { |
| namespace { |
| |
| using ResolverIntrinsicValidationTest = ResolverTest; |
| |
| TEST_F(ResolverIntrinsicValidationTest, |
| FunctionTypeMustMatchReturnStatementType_void_fail) { |
| // fn func { return workgroupBarrier(); } |
| Func("func", {}, ty.void_(), |
| { |
| Return(Call(Source{Source::Location{12, 34}}, "workgroupBarrier")), |
| }); |
| |
| EXPECT_FALSE(r()->Resolve()); |
| EXPECT_EQ( |
| r()->error(), |
| "12:34 error: intrinsic 'workgroupBarrier' does not return a value"); |
| } |
| |
| TEST_F(ResolverIntrinsicValidationTest, InvalidPipelineStageDirect) { |
| // [[stage(compute), workgroup_size(1)]] fn func { return dpdx(1.0); } |
| |
| auto* dpdx = create<ast::CallExpression>(Source{{3, 4}}, Expr("dpdx"), |
| ast::ExpressionList{Expr(1.0f)}); |
| Func(Source{{1, 2}}, "func", ast::VariableList{}, ty.void_(), |
| {CallStmt(dpdx)}, |
| {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)}); |
| |
| EXPECT_FALSE(r()->Resolve()); |
| EXPECT_EQ(r()->error(), |
| "3:4 error: built-in cannot be used by compute pipeline stage"); |
| } |
| |
| TEST_F(ResolverIntrinsicValidationTest, InvalidPipelineStageIndirect) { |
| // fn f0 { return dpdx(1.0); } |
| // fn f1 { f0(); } |
| // fn f2 { f1(); } |
| // [[stage(compute), workgroup_size(1)]] fn main { return f2(); } |
| |
| auto* dpdx = create<ast::CallExpression>(Source{{3, 4}}, Expr("dpdx"), |
| ast::ExpressionList{Expr(1.0f)}); |
| Func(Source{{1, 2}}, "f0", {}, ty.void_(), {CallStmt(dpdx)}); |
| |
| Func(Source{{3, 4}}, "f1", {}, ty.void_(), {CallStmt(Call("f0"))}); |
| |
| Func(Source{{5, 6}}, "f2", {}, ty.void_(), {CallStmt(Call("f1"))}); |
| |
| Func(Source{{7, 8}}, "main", {}, ty.void_(), {CallStmt(Call("f2"))}, |
| {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)}); |
| |
| EXPECT_FALSE(r()->Resolve()); |
| EXPECT_EQ(r()->error(), |
| R"(3:4 error: built-in cannot be used by compute pipeline stage |
| 1:2 note: called by function 'f0' |
| 3:4 note: called by function 'f1' |
| 5:6 note: called by function 'f2' |
| 7:8 note: called by entry point 'main')"); |
| } |
| |
| TEST_F(ResolverIntrinsicValidationTest, IntrinsicRedeclaredAsFunction) { |
| Func(Source{{12, 34}}, "mix", {}, ty.i32(), {}); |
| |
| EXPECT_FALSE(r()->Resolve()); |
| EXPECT_EQ( |
| r()->error(), |
| R"(12:34 error: 'mix' is a builtin and cannot be redeclared as a function)"); |
| } |
| |
| TEST_F(ResolverIntrinsicValidationTest, IntrinsicRedeclaredAsGlobalLet) { |
| GlobalConst(Source{{12, 34}}, "mix", ty.i32(), Expr(1)); |
| |
| EXPECT_FALSE(r()->Resolve()); |
| EXPECT_EQ( |
| r()->error(), |
| R"(12:34 error: 'mix' is a builtin and cannot be redeclared as a module-scope let)"); |
| } |
| |
| TEST_F(ResolverIntrinsicValidationTest, IntrinsicRedeclaredAsGlobalVar) { |
| Global(Source{{12, 34}}, "mix", ty.i32(), Expr(1), |
| ast::StorageClass::kPrivate); |
| |
| EXPECT_FALSE(r()->Resolve()); |
| EXPECT_EQ( |
| r()->error(), |
| R"(12:34 error: 'mix' is a builtin and cannot be redeclared as a module-scope var)"); |
| } |
| |
| TEST_F(ResolverIntrinsicValidationTest, IntrinsicRedeclaredAsAlias) { |
| Alias(Source{{12, 34}}, "mix", ty.i32()); |
| |
| EXPECT_FALSE(r()->Resolve()); |
| EXPECT_EQ( |
| r()->error(), |
| R"(12:34 error: 'mix' is a builtin and cannot be redeclared as an alias)"); |
| } |
| |
| TEST_F(ResolverIntrinsicValidationTest, IntrinsicRedeclaredAsStruct) { |
| Structure(Source{{12, 34}}, "mix", {Member("m", ty.i32())}); |
| |
| EXPECT_FALSE(r()->Resolve()); |
| EXPECT_EQ( |
| r()->error(), |
| R"(12:34 error: 'mix' is a builtin and cannot be redeclared as a struct)"); |
| } |
| |
| namespace TextureSamplerOffset { |
| |
| using TextureOverloadCase = ast::intrinsic::test::TextureOverloadCase; |
| using ValidTextureOverload = ast::intrinsic::test::ValidTextureOverload; |
| using TextureKind = ast::intrinsic::test::TextureKind; |
| using TextureDataType = ast::intrinsic::test::TextureDataType; |
| using u32 = ProgramBuilder::u32; |
| using i32 = ProgramBuilder::i32; |
| using f32 = ProgramBuilder::f32; |
| |
| static std::vector<TextureOverloadCase> ValidCases() { |
| std::vector<TextureOverloadCase> cases; |
| for (auto c : TextureOverloadCase::ValidCases()) { |
| if (std::string(c.function).find("textureSample") == 0) { |
| if (std::string(c.description).find("offset ") != std::string::npos) { |
| cases.push_back(c); |
| } |
| } |
| } |
| return cases; |
| } |
| |
| struct OffsetCase { |
| bool is_valid; |
| int32_t x; |
| int32_t y; |
| int32_t z; |
| int32_t illegal_value = 0; |
| }; |
| |
| static std::vector<OffsetCase> OffsetCases() { |
| return { |
| {true, 0, 1, 2}, // |
| {true, 7, -8, 7}, // |
| {false, 10, 10, 20, 10}, // |
| {false, -9, 0, 0, -9}, // |
| {false, 0, 8, 0, 8}, // |
| }; |
| } |
| |
| using IntrinsicTextureSamplerValidationTest = |
| ResolverTestWithParam<std::tuple<TextureOverloadCase, // texture info |
| OffsetCase // offset info |
| >>; |
| TEST_P(IntrinsicTextureSamplerValidationTest, ConstExpr) { |
| auto& p = GetParam(); |
| auto param = std::get<0>(p); |
| auto offset = std::get<1>(p); |
| param.BuildTextureVariable(this); |
| param.BuildSamplerVariable(this); |
| |
| auto args = param.args(this); |
| // Make Resolver visit the Node about to be removed |
| WrapInFunction(args.back()); |
| args.pop_back(); |
| if (NumCoordinateAxes(param.texture_dimension) == 2) { |
| args.push_back( |
| Construct(Source{{12, 34}}, ty.vec2<i32>(), offset.x, offset.y)); |
| } else if (NumCoordinateAxes(param.texture_dimension) == 3) { |
| args.push_back(Construct(Source{{12, 34}}, ty.vec3<i32>(), offset.x, |
| offset.y, offset.z)); |
| } |
| |
| auto* call = Call(param.function, args); |
| Func("func", {}, ty.void_(), {CallStmt(call)}, |
| {create<ast::StageDecoration>(ast::PipelineStage::kFragment)}); |
| |
| if (offset.is_valid) { |
| EXPECT_TRUE(r()->Resolve()) << r()->error(); |
| } else { |
| EXPECT_FALSE(r()->Resolve()); |
| std::stringstream err; |
| err << "12:34 error: each offset component of '" << param.function |
| << "' must be at least -8 and at most 7. found: '" |
| << std::to_string(offset.illegal_value) << "'"; |
| EXPECT_EQ(r()->error(), err.str()); |
| } |
| } |
| |
| TEST_P(IntrinsicTextureSamplerValidationTest, ConstExprOfConstExpr) { |
| auto& p = GetParam(); |
| auto param = std::get<0>(p); |
| auto offset = std::get<1>(p); |
| param.BuildTextureVariable(this); |
| param.BuildSamplerVariable(this); |
| |
| auto args = param.args(this); |
| // Make Resolver visit the Node about to be removed |
| WrapInFunction(args.back()); |
| args.pop_back(); |
| if (NumCoordinateAxes(param.texture_dimension) == 2) { |
| args.push_back(Construct(Source{{12, 34}}, ty.vec2<i32>(), |
| Construct(ty.i32(), offset.x), offset.y)); |
| } else if (NumCoordinateAxes(param.texture_dimension) == 3) { |
| args.push_back(Construct(Source{{12, 34}}, ty.vec3<i32>(), offset.x, |
| Construct(ty.vec2<i32>(), offset.y, offset.z))); |
| } |
| auto* call = Call(param.function, args); |
| Func("func", {}, ty.void_(), {CallStmt(call)}, |
| {create<ast::StageDecoration>(ast::PipelineStage::kFragment)}); |
| if (offset.is_valid) { |
| EXPECT_TRUE(r()->Resolve()) << r()->error(); |
| } else { |
| EXPECT_FALSE(r()->Resolve()); |
| std::stringstream err; |
| err << "12:34 error: each offset component of '" << param.function |
| << "' must be at least -8 and at most 7. found: '" |
| << std::to_string(offset.illegal_value) << "'"; |
| EXPECT_EQ(r()->error(), err.str()); |
| } |
| } |
| |
| TEST_P(IntrinsicTextureSamplerValidationTest, EmptyVectorConstructor) { |
| auto& p = GetParam(); |
| auto param = std::get<0>(p); |
| param.BuildTextureVariable(this); |
| param.BuildSamplerVariable(this); |
| |
| auto args = param.args(this); |
| // Make Resolver visit the Node about to be removed |
| WrapInFunction(args.back()); |
| args.pop_back(); |
| if (NumCoordinateAxes(param.texture_dimension) == 2) { |
| args.push_back(Construct(Source{{12, 34}}, ty.vec2<i32>())); |
| } else if (NumCoordinateAxes(param.texture_dimension) == 3) { |
| args.push_back(Construct(Source{{12, 34}}, ty.vec3<i32>())); |
| } |
| |
| auto* call = Call(param.function, args); |
| Func("func", {}, ty.void_(), {CallStmt(call)}, |
| {create<ast::StageDecoration>(ast::PipelineStage::kFragment)}); |
| EXPECT_TRUE(r()->Resolve()) << r()->error(); |
| } |
| |
| TEST_P(IntrinsicTextureSamplerValidationTest, GlobalConst) { |
| auto& p = GetParam(); |
| auto param = std::get<0>(p); |
| auto offset = std::get<1>(p); |
| param.BuildTextureVariable(this); |
| param.BuildSamplerVariable(this); |
| |
| auto args = param.args(this); |
| // Make Resolver visit the Node about to be removed |
| WrapInFunction(args.back()); |
| args.pop_back(); |
| GlobalConst("offset_2d", ty.vec2<i32>(), vec2<i32>(offset.x, offset.y)); |
| GlobalConst("offset_3d", ty.vec3<i32>(), |
| vec3<i32>(offset.x, offset.y, offset.z)); |
| if (NumCoordinateAxes(param.texture_dimension) == 2) { |
| args.push_back(Expr(Source{{12, 34}}, "offset_2d")); |
| } else if (NumCoordinateAxes(param.texture_dimension) == 3) { |
| args.push_back(Expr(Source{{12, 34}}, "offset_3d")); |
| } |
| |
| auto* call = Call(param.function, args); |
| Func("func", {}, ty.void_(), {CallStmt(call)}, |
| {create<ast::StageDecoration>(ast::PipelineStage::kFragment)}); |
| EXPECT_FALSE(r()->Resolve()); |
| std::stringstream err; |
| err << "12:34 error: '" << param.function |
| << "' offset parameter must be a const_expression"; |
| EXPECT_EQ(r()->error(), err.str()); |
| } |
| |
| TEST_P(IntrinsicTextureSamplerValidationTest, ScalarConst) { |
| auto& p = GetParam(); |
| auto param = std::get<0>(p); |
| auto offset = std::get<1>(p); |
| param.BuildTextureVariable(this); |
| param.BuildSamplerVariable(this); |
| auto* x = Const("x", ty.i32(), Construct(ty.i32(), offset.x)); |
| |
| auto args = param.args(this); |
| // Make Resolver visit the Node about to be removed |
| WrapInFunction(args.back()); |
| args.pop_back(); |
| if (NumCoordinateAxes(param.texture_dimension) == 2) { |
| args.push_back(Construct(Source{{12, 34}}, ty.vec2<i32>(), x, offset.y)); |
| } else if (NumCoordinateAxes(param.texture_dimension) == 3) { |
| args.push_back( |
| Construct(Source{{12, 34}}, ty.vec3<i32>(), x, offset.y, offset.z)); |
| } |
| |
| auto* call = Call(param.function, args); |
| Func("func", {}, ty.void_(), {Decl(x), CallStmt(call)}, |
| {create<ast::StageDecoration>(ast::PipelineStage::kFragment)}); |
| EXPECT_FALSE(r()->Resolve()); |
| std::stringstream err; |
| err << "12:34 error: '" << param.function |
| << "' offset parameter must be a const_expression"; |
| EXPECT_EQ(r()->error(), err.str()); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(IntrinsicTextureSamplerValidationTest, |
| IntrinsicTextureSamplerValidationTest, |
| testing::Combine(testing::ValuesIn(ValidCases()), |
| testing::ValuesIn(OffsetCases()))); |
| } // namespace TextureSamplerOffset |
| |
| } // namespace |
| } // namespace resolver |
| } // namespace tint |