validation: textureSample*() offset argument must be literal or const_expr

Bug: tint:939
Change-Id: I4ba44902948e38aa9ce3e9ecae9b3b4b593bd93d
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/57660
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
Commit-Queue: Sarah Mashayekhi <sarahmashay@google.com>
This commit is contained in:
Sarah
2021-07-16 13:38:05 +00:00
committed by Tint LUCI CQ
parent 979a0b4446
commit 71198438ac
7 changed files with 275 additions and 46 deletions

View File

@@ -12,40 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#include "src/resolver/resolver.h"
#include "gmock/gmock.h"
#include "src/ast/assignment_statement.h"
#include "src/ast/bitcast_expression.h"
#include "src/ast/break_statement.h"
#include "src/ast/call_statement.h"
#include "src/ast/continue_statement.h"
#include "src/ast/if_statement.h"
#include "src/ast/intrinsic_texture_helper_test.h"
#include "src/ast/loop_statement.h"
#include "src/ast/return_statement.h"
#include "src/ast/stage_decoration.h"
#include "src/ast/struct_block_decoration.h"
#include "src/ast/switch_statement.h"
#include "src/ast/unary_op_expression.h"
#include "src/ast/variable_decl_statement.h"
#include "src/resolver/resolver_test_helper.h"
#include "src/sem/call.h"
#include "src/sem/function.h"
#include "src/sem/member_accessor_expression.h"
#include "src/sem/sampled_texture_type.h"
#include "src/sem/statement.h"
#include "src/sem/variable.h"
using ::testing::ElementsAre;
using ::testing::HasSubstr;
namespace tint {
namespace resolver {
namespace {
using IntrinsicType = sem::IntrinsicType;
using ResolverIntrinsicValidationTest = ResolverTest;
TEST_F(ResolverIntrinsicValidationTest, InvalidPipelineStageDirect) {
@@ -90,6 +63,207 @@ TEST_F(ResolverIntrinsicValidationTest, InvalidPipelineStageIndirect) {
7:8 note: called by entry point 'main')");
}
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_(), {Ignore(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_(), {Ignore(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_(), {Ignore(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_(), {Ignore(call)},
{create<ast::StageDecoration>(ast::PipelineStage::kFragment)});
EXPECT_FALSE(r()->Resolve());
std::stringstream err;
err << "12:34 error: '" << param.function
<< "' offset parameter must be provided as"
<< " a literal or const_expr 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), Ignore(call)},
{create<ast::StageDecoration>(ast::PipelineStage::kFragment)});
EXPECT_FALSE(r()->Resolve());
std::stringstream err;
err << "12:34 error: '" << param.function
<< "' offset parameter must be provided as"
<< " a literal or const_expr 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

View File

@@ -2431,13 +2431,67 @@ bool Resolver::IntrinsicCall(ast::CallExpression* call,
AddWarning("use of deprecated intrinsic", call->source());
}
builder_->Sem().Add(
call, builder_->create<sem::Call>(call, result, current_statement_));
auto* out = builder_->create<sem::Call>(call, result, current_statement_);
builder_->Sem().Add(call, out);
SetExprInfo(call, result->ReturnType());
current_function_->intrinsic_calls.emplace_back(
IntrinsicCallInfo{call, result});
if (IsTextureIntrinsic(intrinsic_type) &&
!ValidateTextureIntrinsicFunction(call, out)) {
return false;
}
return true;
}
bool Resolver::ValidateTextureIntrinsicFunction(
const ast::CallExpression* ast_call,
const sem::Call* sem_call) {
auto* intrinsic = sem_call->Target()->As<sem::Intrinsic>();
if (!intrinsic) {
return false;
}
std::string func_name = intrinsic->str();
auto index =
sem::IndexOf(intrinsic->Parameters(), sem::ParameterUsage::kOffset);
if (index > -1) {
auto* param = ast_call->params()[index];
if (param->Is<ast::TypeConstructorExpression>()) {
auto values = ConstantValueOf(param);
if (!values.IsValid()) {
AddError("'" + func_name +
"' offset parameter must be provided as a literal or "
"const_expr expression",
param->source());
return false;
}
if (!values.Type()->Is<sem::Vector>() ||
!values.ElementType()->Is<sem::I32>()) {
TINT_ICE(Resolver, diagnostics_)
<< "failed to resolve '" + func_name + "' offset parameter type";
return false;
}
for (auto offset : values.Elements()) {
auto offset_value = offset.i32;
if (offset_value < -8 || offset_value > 7) {
AddError("each offset component of '" + func_name +
"' must be at least -8 and at most 7. "
"found: '" +
std::to_string(offset_value) + "'",
param->source());
return false;
}
}
} else {
AddError("'" + func_name +
"' offset parameter must be provided as a literal or "
"const_expr expression",
param->source());
return false;
}
}
return true;
}

View File

@@ -323,6 +323,8 @@ class Resolver {
bool ValidateArrayConstructor(const ast::TypeConstructorExpression* ctor,
const sem::Array* arr_type);
bool ValidateTypeDecl(const ast::TypeDecl* named_type) const;
bool ValidateTextureIntrinsicFunction(const ast::CallExpression* ast_call,
const sem::Call* sem_call);
bool ValidateNoDuplicateDecorations(const ast::DecorationList& decorations);
// sem::Struct is assumed to have at least one member
bool ValidateStorageClassLayout(const sem::Struct* type,