diff --git a/BUILD.gn b/BUILD.gn index 707cab3dcd..69cc327c69 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -373,6 +373,8 @@ source_set("libtint_core_src") { "src/reader/reader.h", "src/scope_stack.h", "src/source.h", + "src/transform/bound_array_accessors_transform.cc", + "src/transform/bound_array_accessors_transform.h", "src/transform/vertex_pulling_transform.cc", "src/transform/vertex_pulling_transform.h", "src/type_determiner.cc", @@ -745,6 +747,7 @@ source_set("tint_unittests_core_src") { "src/ast/variable_test.cc", "src/ast/workgroup_decoration_test.cc", "src/scope_stack_test.cc", + "src/transform/bound_array_accessors_transform_test.cc", "src/transform/vertex_pulling_transform_test.cc", "src/type_determiner_test.cc", "src/type_manager_test.cc", diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 798bc6ab36..4325e8e571 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -194,6 +194,8 @@ set(TINT_LIB_SRCS reader/reader.h scope_stack.h source.h + transform/bound_array_accessors_transform.cc + transform/bound_array_accessors_transform.h transform/vertex_pulling_transform.cc transform/vertex_pulling_transform.h type_determiner.cc @@ -355,6 +357,7 @@ set(TINT_TEST_SRCS ast/variable_test.cc ast/workgroup_decoration_test.cc scope_stack_test.cc + transform/bound_array_accessors_transform_test.cc transform/vertex_pulling_transform_test.cc type_determiner_test.cc type_manager_test.cc diff --git a/src/ast/array_accessor_expression.h b/src/ast/array_accessor_expression.h index 5f90fb6eed..0359161333 100644 --- a/src/ast/array_accessor_expression.h +++ b/src/ast/array_accessor_expression.h @@ -60,6 +60,9 @@ class ArrayAccessorExpression : public Expression { } /// @returns the index expression Expression* idx_expr() const { return idx_expr_.get(); } + /// Removes the index expression from the array accessor + /// @returns the unique pointer to the index expression + std::unique_ptr take_idx_expr() { return std::move(idx_expr_); } /// @returns true if this is an array accessor expression bool IsArrayAccessor() const override; diff --git a/src/ast/case_statement.h b/src/ast/case_statement.h index 571eeb384f..0b3cf7a6b3 100644 --- a/src/ast/case_statement.h +++ b/src/ast/case_statement.h @@ -72,6 +72,8 @@ class CaseStatement : public Statement { } /// @returns the case body const BlockStatement* body() const { return body_.get(); } + /// @returns the case body + BlockStatement* body() { return body_.get(); } /// @returns true if this is a case statement bool IsCase() const override; diff --git a/src/ast/else_statement.h b/src/ast/else_statement.h index fda156b80e..c79ffb8a2e 100644 --- a/src/ast/else_statement.h +++ b/src/ast/else_statement.h @@ -71,6 +71,8 @@ class ElseStatement : public Statement { } /// @returns the else body const BlockStatement* body() const { return body_.get(); } + /// @returns the else body + BlockStatement* body() { return body_.get(); } /// @returns true if this is a else statement bool IsElse() const override; diff --git a/src/ast/function.h b/src/ast/function.h index 0dd540b020..7e21c28263 100644 --- a/src/ast/function.h +++ b/src/ast/function.h @@ -163,7 +163,9 @@ class Function : public Node { body_ = std::move(body); } /// @returns the function body - BlockStatement* body() const { return body_.get(); } + const BlockStatement* body() const { return body_.get(); } + /// @returns the function body + BlockStatement* body() { return body_.get(); } /// @returns true if the name and type are both present bool IsValid() const override; diff --git a/src/ast/if_statement.h b/src/ast/if_statement.h index 8108ebf948..53af7b732d 100644 --- a/src/ast/if_statement.h +++ b/src/ast/if_statement.h @@ -62,6 +62,8 @@ class IfStatement : public Statement { } /// @returns the if body const BlockStatement* body() const { return body_.get(); } + /// @returns the if body + BlockStatement* body() { return body_.get(); } /// Sets the else statements /// @param else_statements the else statements to set @@ -70,6 +72,9 @@ class IfStatement : public Statement { } /// @returns the else statements const ElseStatementList& else_statements() const { return else_statements_; } + /// @returns the else statements + ElseStatementList& else_statements() { return else_statements_; } + /// @returns true if there are else statements bool has_else_statements() const { return !else_statements_.empty(); } diff --git a/src/ast/loop_statement.h b/src/ast/loop_statement.h index bf8f8a5131..edb2381187 100644 --- a/src/ast/loop_statement.h +++ b/src/ast/loop_statement.h @@ -51,6 +51,8 @@ class LoopStatement : public Statement { } /// @returns the body statements const BlockStatement* body() const { return body_.get(); } + /// @returns the body statements + BlockStatement* body() { return body_.get(); } /// Sets the continuing statements /// @param continuing the continuing statements @@ -59,6 +61,8 @@ class LoopStatement : public Statement { } /// @returns the continuing statements const BlockStatement* continuing() const { return continuing_.get(); } + /// @returns the continuing statements + BlockStatement* continuing() { return continuing_.get(); } /// @returns true if there are continuing statements in the loop bool has_continuing() const { return continuing_ != nullptr && !continuing_->empty(); diff --git a/src/ast/sint_literal.h b/src/ast/sint_literal.h index 9d23cf763b..3995148e49 100644 --- a/src/ast/sint_literal.h +++ b/src/ast/sint_literal.h @@ -34,6 +34,9 @@ class SintLiteral : public IntLiteral { /// @returns true if this is a signed int literal bool IsSint() const override; + /// Updates the literals value + /// @param val the value to set + void set_value(int32_t val) { value_ = val; } /// @returns the int literal value int32_t value() const { return value_; } diff --git a/src/ast/uint_literal.h b/src/ast/uint_literal.h index 7eb1311295..6189ee4d0c 100644 --- a/src/ast/uint_literal.h +++ b/src/ast/uint_literal.h @@ -34,6 +34,9 @@ class UintLiteral : public IntLiteral { /// @returns true if this is a uint literal bool IsUint() const override; + /// Updates the literals value + /// @param val the value to set + void set_value(uint32_t val) { value_ = val; } /// @returns the uint literal value uint32_t value() const { return value_; } diff --git a/src/transform/bound_array_accessors_transform.cc b/src/transform/bound_array_accessors_transform.cc new file mode 100644 index 0000000000..8ae5bd84ea --- /dev/null +++ b/src/transform/bound_array_accessors_transform.cc @@ -0,0 +1,258 @@ +// Copyright 2020 The Tint Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "src/transform/bound_array_accessors_transform.h" + +#include "src/ast/assignment_statement.h" +#include "src/ast/binary_expression.h" +#include "src/ast/bitcast_expression.h" +#include "src/ast/block_statement.h" +#include "src/ast/call_expression.h" +#include "src/ast/call_statement.h" +#include "src/ast/case_statement.h" +#include "src/ast/else_statement.h" +#include "src/ast/if_statement.h" +#include "src/ast/loop_statement.h" +#include "src/ast/member_accessor_expression.h" +#include "src/ast/return_statement.h" +#include "src/ast/scalar_constructor_expression.h" +#include "src/ast/sint_literal.h" +#include "src/ast/switch_statement.h" +#include "src/ast/type/array_type.h" +#include "src/ast/type/matrix_type.h" +#include "src/ast/type/u32_type.h" +#include "src/ast/type/vector_type.h" +#include "src/ast/type_constructor_expression.h" +#include "src/ast/uint_literal.h" +#include "src/ast/unary_op_expression.h" +#include "src/ast/variable.h" +#include "src/ast/variable_decl_statement.h" +#include "src/type_manager.h" + +namespace tint { +namespace transform { + +BoundArrayAccessorsTransform::BoundArrayAccessorsTransform(Context* ctx, + ast::Module* mod) + : ctx_(ctx), mod_(mod) {} + +BoundArrayAccessorsTransform::~BoundArrayAccessorsTransform() = default; + +bool BoundArrayAccessorsTransform::Run() { + // We skip over global variables as the constructor for a global must be a + // constant expression. There can't be any array accessors as per the current + // grammar. + + for (auto& func : mod_->functions()) { + scope_stack_.push_scope(); + if (!ProcessStatement(func->body())) { + return false; + } + scope_stack_.pop_scope(); + } + return true; +} + +bool BoundArrayAccessorsTransform::ProcessStatement(ast::Statement* stmt) { + if (stmt->IsAssign()) { + auto* as = stmt->AsAssign(); + return ProcessExpression(as->lhs()) && ProcessExpression(as->rhs()); + } else if (stmt->IsBlock()) { + for (auto& s : *(stmt->AsBlock())) { + if (!ProcessStatement(s.get())) { + return false; + } + } + } else if (stmt->IsBreak()) { + /* nop */ + } else if (stmt->IsCall()) { + return ProcessExpression(stmt->AsCall()->expr()); + } else if (stmt->IsCase()) { + return ProcessStatement(stmt->AsCase()->body()); + } else if (stmt->IsContinue()) { + /* nop */ + } else if (stmt->IsDiscard()) { + /* nop */ + } else if (stmt->IsElse()) { + auto* e = stmt->AsElse(); + return ProcessExpression(e->condition()) && ProcessStatement(e->body()); + } else if (stmt->IsFallthrough()) { + /* nop */ + } else if (stmt->IsIf()) { + auto* e = stmt->AsIf(); + if (!ProcessExpression(e->condition()) || !ProcessStatement(e->body())) { + return false; + } + for (auto& s : e->else_statements()) { + if (!ProcessStatement(s.get())) { + return false; + } + } + } else if (stmt->IsLoop()) { + auto* l = stmt->AsLoop(); + if (l->has_continuing() && !ProcessStatement(l->continuing())) { + return false; + } + return ProcessStatement(l->body()); + } else if (stmt->IsReturn()) { + if (stmt->AsReturn()->has_value()) { + return ProcessExpression(stmt->AsReturn()->value()); + } + } else if (stmt->IsSwitch()) { + auto* s = stmt->AsSwitch(); + if (!ProcessExpression(s->condition())) { + return false; + } + + for (auto& c : s->body()) { + if (!ProcessStatement(c.get())) { + return false; + } + } + } else if (stmt->IsVariableDecl()) { + auto* v = stmt->AsVariableDecl()->variable(); + if (v->has_constructor() && !ProcessExpression(v->constructor())) { + return false; + } + scope_stack_.set(v->name(), v); + } else { + error_ = "unknown statement in bound array accessors transform"; + return false; + } + return true; +} + +bool BoundArrayAccessorsTransform::ProcessExpression(ast::Expression* expr) { + if (expr->IsArrayAccessor()) { + return ProcessArrayAccessor(expr->AsArrayAccessor()); + } else if (expr->IsBitcast()) { + return ProcessExpression(expr->AsBitcast()->expr()); + } else if (expr->IsCall()) { + auto* c = expr->AsCall(); + if (!ProcessExpression(c->func())) { + return false; + } + for (auto& e : c->params()) { + if (!ProcessExpression(e.get())) { + return false; + } + } + } else if (expr->IsIdentifier()) { + /* nop */ + } else if (expr->IsConstructor()) { + auto* c = expr->AsConstructor(); + if (c->IsTypeConstructor()) { + for (auto& e : c->AsTypeConstructor()->values()) { + if (!ProcessExpression(e.get())) { + return false; + } + } + } + } else if (expr->IsMemberAccessor()) { + auto* m = expr->AsMemberAccessor(); + return ProcessExpression(m->structure()) && ProcessExpression(m->member()); + } else if (expr->IsBinary()) { + auto* b = expr->AsBinary(); + return ProcessExpression(b->lhs()) && ProcessExpression(b->rhs()); + } else if (expr->IsUnaryOp()) { + return ProcessExpression(expr->AsUnaryOp()->expr()); + } else { + error_ = "unknown statement in bound array accessors transform"; + return false; + } + return true; +} + +bool BoundArrayAccessorsTransform::ProcessArrayAccessor( + ast::ArrayAccessorExpression* expr) { + if (!ProcessExpression(expr->array()) || + !ProcessExpression(expr->idx_expr())) { + return false; + } + + auto* ret_type = expr->array()->result_type()->UnwrapAliasPtrAlias(); + if (!ret_type->IsArray() && !ret_type->IsMatrix() && !ret_type->IsVector()) { + return true; + } + + if (ret_type->IsVector() || ret_type->IsArray()) { + uint32_t size = ret_type->IsVector() ? ret_type->AsVector()->size() + : ret_type->AsArray()->size(); + if (size == 0) { + error_ = "invalid 0 size for array or vector"; + return false; + } + + if (!ProcessAccessExpression(expr, size)) { + return false; + } + } else { + // The row accessor would have been an embedded array accessor and already + // handled, so we just need to do columns here. + uint32_t size = ret_type->AsMatrix()->columns(); + if (!ProcessAccessExpression(expr, size)) { + return false; + } + } + return true; +} + +bool BoundArrayAccessorsTransform::ProcessAccessExpression( + ast::ArrayAccessorExpression* expr, + uint32_t size) { + // Scalar constructor we can re-write the value to be within bounds. + if (expr->idx_expr()->IsConstructor() && + expr->idx_expr()->AsConstructor()->IsScalarConstructor()) { + auto* lit = + expr->idx_expr()->AsConstructor()->AsScalarConstructor()->literal(); + if (lit->IsSint()) { + int32_t val = lit->AsSint()->value(); + if (val < 0) { + val = 0; + } else if (val >= int32_t(size)) { + val = int32_t(size) - 1; + } + lit->AsSint()->set_value(val); + } else if (lit->IsUint()) { + uint32_t val = lit->AsUint()->value(); + if (val >= size - 1) { + val = size - 1; + } + lit->AsUint()->set_value(val); + } else { + error_ = "unknown scalar constructor type for accessor"; + return false; + } + } else { + auto* u32 = ctx_->type_mgr().Get(std::make_unique()); + + ast::ExpressionList params; + params.push_back(expr->take_idx_expr()); + params.push_back(std::make_unique( + std::make_unique(u32, 0))); + params.push_back(std::make_unique( + std::make_unique(u32, size - 1))); + + auto call_expr = std::make_unique( + std::make_unique("clamp"), + std::move(params)); + call_expr->set_result_type(u32); + + expr->set_idx_expr(std::move(call_expr)); + } + return true; +} + +} // namespace transform +} // namespace tint diff --git a/src/transform/bound_array_accessors_transform.h b/src/transform/bound_array_accessors_transform.h new file mode 100644 index 0000000000..2a40225fe1 --- /dev/null +++ b/src/transform/bound_array_accessors_transform.h @@ -0,0 +1,64 @@ +// Copyright 2020 The Tint Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SRC_TRANSFORM_BOUND_ARRAY_ACCESSORS_TRANSFORM_H_ +#define SRC_TRANSFORM_BOUND_ARRAY_ACCESSORS_TRANSFORM_H_ + +#include "src/ast/array_accessor_expression.h" +#include "src/ast/expression.h" +#include "src/ast/module.h" +#include "src/ast/statement.h" +#include "src/context.h" +#include "src/scope_stack.h" + +#include + +namespace tint { +namespace transform { + +/// This transformer is responsible for clamping all array accesses to be within +/// the bounds of the array. Any access before the start of the array will clamp +/// to zero and any access past the end of the array will clamp to +/// (array length - 1). +class BoundArrayAccessorsTransform { + public: + /// Constructor + /// @param ctx the Tint context object + /// @param mod the module transform + explicit BoundArrayAccessorsTransform(Context* ctx, ast::Module* mod); + ~BoundArrayAccessorsTransform(); + + /// @returns true if the transformation was successful + bool Run(); + + /// @returns error messages + const std::string& error() { return error_; } + + private: + bool ProcessStatement(ast::Statement* stmt); + bool ProcessExpression(ast::Expression* expr); + bool ProcessArrayAccessor(ast::ArrayAccessorExpression* expr); + bool ProcessAccessExpression(ast::ArrayAccessorExpression* expr, + uint32_t size); + + Context* ctx_ = nullptr; + ast::Module* mod_ = nullptr; + std::string error_; + ScopeStack scope_stack_; +}; + +} // namespace transform +} // namespace tint + +#endif // SRC_TRANSFORM_BOUND_ARRAY_ACCESSORS_TRANSFORM_H_ diff --git a/src/transform/bound_array_accessors_transform_test.cc b/src/transform/bound_array_accessors_transform_test.cc new file mode 100644 index 0000000000..b1b774da42 --- /dev/null +++ b/src/transform/bound_array_accessors_transform_test.cc @@ -0,0 +1,1119 @@ +// Copyright 2020 The Tint Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "src/transform/bound_array_accessors_transform.h" + +#include + +#include "gtest/gtest.h" +#include "src/ast/array_accessor_expression.h" +#include "src/ast/binary_expression.h" +#include "src/ast/block_statement.h" +#include "src/ast/call_expression.h" +#include "src/ast/function.h" +#include "src/ast/identifier_expression.h" +#include "src/ast/module.h" +#include "src/ast/scalar_constructor_expression.h" +#include "src/ast/sint_literal.h" +#include "src/ast/storage_class.h" +#include "src/ast/type/array_type.h" +#include "src/ast/type/f32_type.h" +#include "src/ast/type/i32_type.h" +#include "src/ast/type/matrix_type.h" +#include "src/ast/type/pointer_type.h" +#include "src/ast/type/u32_type.h" +#include "src/ast/type/vector_type.h" +#include "src/ast/type/void_type.h" +#include "src/ast/uint_literal.h" +#include "src/ast/variable.h" +#include "src/ast/variable_decl_statement.h" +#include "src/context.h" +#include "src/type_determiner.h" + +namespace tint { +namespace transform { +namespace { + +class BoundArrayAccessorsTest : public testing::Test { + public: + BoundArrayAccessorsTest() : td_(&ctx_, &mod_), transform_(&ctx_, &mod_) {} + + ast::BlockStatement* SetupFunctionAndBody() { + auto func = std::make_unique("func", ast::VariableList{}, + &void_type_); + auto block = std::make_unique(); + body_ = block.get(); + func->set_body(std::move(block)); + mod_.AddFunction(std::move(func)); + return body_; + } + + void DeclareVariable(std::unique_ptr var) { + ASSERT_NE(body_, nullptr); + body_->append(std::make_unique(std::move(var))); + } + + TypeDeterminer* td() { return &td_; } + + BoundArrayAccessorsTransform* transform() { return &transform_; } + + private: + Context ctx_; + ast::Module mod_; + TypeDeterminer td_; + ast::type::VoidType void_type_; + BoundArrayAccessorsTransform transform_; + ast::BlockStatement* body_ = nullptr; +}; + +TEST_F(BoundArrayAccessorsTest, Ptrs_Clamp) { + // var a : array; + // const c : u32 = 1; + // const b : ptr = a[c] + // + // -> const b : ptr = a[clamp(c, 0, 2)] + + ast::type::F32Type f32; + ast::type::U32Type u32; + ast::type::ArrayType ary(&f32, 3); + ast::type::PointerType ptr_type(&f32, ast::StorageClass::kFunction); + + SetupFunctionAndBody(); + DeclareVariable( + std::make_unique("a", ast::StorageClass::kFunction, &ary)); + + auto c_var = + std::make_unique("c", ast::StorageClass::kFunction, &u32); + c_var->set_is_const(true); + DeclareVariable(std::move(c_var)); + + auto access_idx = std::make_unique("c"); + auto* access_ptr = access_idx.get(); + + auto accessor = std::make_unique( + std::make_unique("a"), std::move(access_idx)); + auto* ptr = accessor.get(); + + auto b = std::make_unique("b", ast::StorageClass::kFunction, + &ptr_type); + b->set_constructor(std::move(accessor)); + b->set_is_const(true); + DeclareVariable(std::move(b)); + + ASSERT_TRUE(td()->Determine()) << td()->error(); + + ASSERT_TRUE(transform()->Run()); + ASSERT_TRUE(ptr->IsArrayAccessor()); + ASSERT_TRUE(ptr->idx_expr()->IsCall()); + + auto* idx = ptr->idx_expr()->AsCall(); + ASSERT_TRUE(idx->func()->IsIdentifier()); + EXPECT_EQ(idx->func()->AsIdentifier()->name(), "clamp"); + + ASSERT_EQ(idx->params().size(), 3u); + ASSERT_EQ(idx->params()[0].get(), access_ptr); + + ASSERT_TRUE(idx->params()[1]->IsConstructor()); + ASSERT_TRUE(idx->params()[1]->AsConstructor()->IsScalarConstructor()); + auto* scalar = idx->params()[1]->AsConstructor()->AsScalarConstructor(); + ASSERT_TRUE(scalar->literal()->IsUint()); + EXPECT_EQ(scalar->literal()->AsUint()->value(), 0u); + + ASSERT_TRUE(idx->params()[2]->IsConstructor()); + ASSERT_TRUE(idx->params()[2]->AsConstructor()->IsScalarConstructor()); + scalar = idx->params()[2]->AsConstructor()->AsScalarConstructor(); + ASSERT_TRUE(scalar->literal()->IsUint()); + EXPECT_EQ(scalar->literal()->AsUint()->value(), 2u); + + ASSERT_NE(ptr->idx_expr()->result_type(), nullptr); + ASSERT_TRUE(ptr->idx_expr()->result_type()->IsU32()); +} + +TEST_F(BoundArrayAccessorsTest, Array_Idx_Nested_Scalar) { + // var a : array; + // var b : array; + // var i : u32; + // var c : f32 = a[b[i]]; + // + // -> var c : f32 = a[clamp(b[clamp(i, 0, 4)], 0, 2)]; + + ast::type::F32Type f32; + ast::type::U32Type u32; + ast::type::ArrayType ary3(&f32, 3); + ast::type::ArrayType ary5(&f32, 5); + + SetupFunctionAndBody(); + DeclareVariable(std::make_unique( + "a", ast::StorageClass::kFunction, &ary3)); + DeclareVariable(std::make_unique( + "b", ast::StorageClass::kFunction, &ary5)); + DeclareVariable( + std::make_unique("i", ast::StorageClass::kFunction, &u32)); + + auto b_access_idx = std::make_unique("i"); + auto* b_access_ptr = b_access_idx.get(); + + auto a_access_idx = std::make_unique( + std::make_unique("b"), + std::move(b_access_idx)); + + auto accessor = std::make_unique( + std::make_unique("a"), + std::move(a_access_idx)); + auto* ptr = accessor.get(); + + auto b = + std::make_unique("c", ast::StorageClass::kFunction, &f32); + b->set_constructor(std::move(accessor)); + DeclareVariable(std::move(b)); + + ASSERT_TRUE(td()->Determine()) << td()->error(); + + ASSERT_TRUE(transform()->Run()); + ASSERT_TRUE(ptr->IsArrayAccessor()); + ASSERT_TRUE(ptr->idx_expr()->IsCall()); + + auto* idx = ptr->idx_expr()->AsCall(); + ASSERT_TRUE(idx->func()->IsIdentifier()); + EXPECT_EQ(idx->func()->AsIdentifier()->name(), "clamp"); + + ASSERT_EQ(idx->params().size(), 3u); + + auto* sub = idx->params()[0].get(); + ASSERT_TRUE(sub->IsArrayAccessor()); + ASSERT_TRUE(sub->AsArrayAccessor()->idx_expr()->IsCall()); + + auto* sub_idx = sub->AsArrayAccessor()->idx_expr()->AsCall(); + ASSERT_TRUE(sub_idx->func()->IsIdentifier()); + EXPECT_EQ(sub_idx->func()->AsIdentifier()->name(), "clamp"); + + ASSERT_EQ(sub_idx->params()[0].get(), b_access_ptr); + + ASSERT_TRUE(sub_idx->params()[1]->IsConstructor()); + ASSERT_TRUE(sub_idx->params()[1]->AsConstructor()->IsScalarConstructor()); + auto* scalar = sub_idx->params()[1]->AsConstructor()->AsScalarConstructor(); + ASSERT_TRUE(scalar->literal()->IsUint()); + EXPECT_EQ(scalar->literal()->AsUint()->value(), 0u); + + ASSERT_TRUE(sub_idx->params()[2]->IsConstructor()); + ASSERT_TRUE(sub_idx->params()[2]->AsConstructor()->IsScalarConstructor()); + scalar = sub_idx->params()[2]->AsConstructor()->AsScalarConstructor(); + ASSERT_TRUE(scalar->literal()->IsUint()); + EXPECT_EQ(scalar->literal()->AsUint()->value(), 4u); + + ASSERT_TRUE(idx->params()[1]->IsConstructor()); + ASSERT_TRUE(idx->params()[1]->AsConstructor()->IsScalarConstructor()); + scalar = idx->params()[1]->AsConstructor()->AsScalarConstructor(); + ASSERT_TRUE(scalar->literal()->IsUint()); + EXPECT_EQ(scalar->literal()->AsUint()->value(), 0u); + + ASSERT_TRUE(idx->params()[2]->IsConstructor()); + ASSERT_TRUE(idx->params()[2]->AsConstructor()->IsScalarConstructor()); + scalar = idx->params()[2]->AsConstructor()->AsScalarConstructor(); + ASSERT_TRUE(scalar->literal()->IsUint()); + EXPECT_EQ(scalar->literal()->AsUint()->value(), 2u); + + ASSERT_NE(ptr->idx_expr()->result_type(), nullptr); + ASSERT_TRUE(ptr->idx_expr()->result_type()->IsU32()); +} + +TEST_F(BoundArrayAccessorsTest, Array_Idx_Scalar) { + // var a : array + // var b : f32 = a[1]; + // + // -> var b : f32 = a[1]; + + ast::type::F32Type f32; + ast::type::U32Type u32; + ast::type::ArrayType ary(&f32, 3); + + SetupFunctionAndBody(); + DeclareVariable( + std::make_unique("a", ast::StorageClass::kFunction, &ary)); + + auto accessor = std::make_unique( + std::make_unique("a"), + std::make_unique( + std::make_unique(&u32, 1u))); + auto* ptr = accessor.get(); + + auto b = + std::make_unique("b", ast::StorageClass::kFunction, &f32); + b->set_constructor(std::move(accessor)); + DeclareVariable(std::move(b)); + + ASSERT_TRUE(td()->Determine()) << td()->error(); + + ASSERT_TRUE(transform()->Run()); + ASSERT_TRUE(ptr->IsArrayAccessor()); + ASSERT_TRUE(ptr->idx_expr()->IsConstructor()); + ASSERT_TRUE(ptr->idx_expr()->AsConstructor()->IsScalarConstructor()); + + auto* scalar = ptr->idx_expr()->AsConstructor()->AsScalarConstructor(); + ASSERT_TRUE(scalar->literal()->IsUint()); + EXPECT_EQ(scalar->literal()->AsUint()->value(), 1u); + + ASSERT_NE(ptr->idx_expr()->result_type(), nullptr); + ASSERT_TRUE(ptr->idx_expr()->result_type()->IsU32()); +} + +TEST_F(BoundArrayAccessorsTest, Array_Idx_Expr) { + // var a : array + // var c : u32; + // var b : f32 = a[c + 2 - 3] + // + // -> var b : f32 = a[clamp((c + 2 - 3), 0, 2)] + + ast::type::F32Type f32; + ast::type::U32Type u32; + ast::type::ArrayType ary(&f32, 3); + + SetupFunctionAndBody(); + DeclareVariable( + std::make_unique("a", ast::StorageClass::kFunction, &ary)); + DeclareVariable( + std::make_unique("c", ast::StorageClass::kFunction, &u32)); + + auto access_idx = std::make_unique( + ast::BinaryOp::kAdd, std::make_unique("c"), + std::make_unique( + ast::BinaryOp::kSubtract, + std::make_unique( + std::make_unique(&u32, 2)), + std::make_unique( + std::make_unique(&u32, 3)))); + auto* access_ptr = access_idx.get(); + + auto accessor = std::make_unique( + std::make_unique("a"), std::move(access_idx)); + auto* ptr = accessor.get(); + + auto b = + std::make_unique("b", ast::StorageClass::kFunction, &f32); + b->set_constructor(std::move(accessor)); + DeclareVariable(std::move(b)); + + ASSERT_TRUE(td()->Determine()) << td()->error(); + + ASSERT_TRUE(transform()->Run()); + ASSERT_TRUE(ptr->IsArrayAccessor()); + ASSERT_TRUE(ptr->idx_expr()->IsCall()); + + auto* idx = ptr->idx_expr()->AsCall(); + ASSERT_TRUE(idx->func()->IsIdentifier()); + EXPECT_EQ(idx->func()->AsIdentifier()->name(), "clamp"); + + ASSERT_EQ(idx->params().size(), 3u); + ASSERT_EQ(idx->params()[0].get(), access_ptr); + + ASSERT_TRUE(idx->params()[1]->IsConstructor()); + ASSERT_TRUE(idx->params()[1]->AsConstructor()->IsScalarConstructor()); + auto* scalar = idx->params()[1]->AsConstructor()->AsScalarConstructor(); + ASSERT_TRUE(scalar->literal()->IsUint()); + EXPECT_EQ(scalar->literal()->AsUint()->value(), 0u); + + ASSERT_TRUE(idx->params()[2]->IsConstructor()); + ASSERT_TRUE(idx->params()[2]->AsConstructor()->IsScalarConstructor()); + scalar = idx->params()[2]->AsConstructor()->AsScalarConstructor(); + ASSERT_TRUE(scalar->literal()->IsUint()); + EXPECT_EQ(scalar->literal()->AsUint()->value(), 2u); + + ASSERT_NE(ptr->idx_expr()->result_type(), nullptr); + ASSERT_TRUE(ptr->idx_expr()->result_type()->IsU32()); +} + +TEST_F(BoundArrayAccessorsTest, Array_Idx_Negative) { + // var a : array + // var b : f32 = a[-1] + // + // -> var b : f32 = a[0] + + ast::type::F32Type f32; + ast::type::I32Type i32; + ast::type::ArrayType ary(&f32, 3); + + SetupFunctionAndBody(); + DeclareVariable( + std::make_unique("a", ast::StorageClass::kFunction, &ary)); + + auto accessor = std::make_unique( + std::make_unique("a"), + std::make_unique( + std::make_unique(&i32, -1))); + auto* ptr = accessor.get(); + + auto b = + std::make_unique("b", ast::StorageClass::kFunction, &f32); + b->set_constructor(std::move(accessor)); + DeclareVariable(std::move(b)); + + ASSERT_TRUE(td()->Determine()) << td()->error(); + + ASSERT_TRUE(transform()->Run()); + ASSERT_TRUE(ptr->IsArrayAccessor()); + ASSERT_TRUE(ptr->idx_expr()->IsConstructor()); + ASSERT_TRUE(ptr->idx_expr()->AsConstructor()->IsScalarConstructor()); + + auto* scalar = ptr->idx_expr()->AsConstructor()->AsScalarConstructor(); + ASSERT_TRUE(scalar->literal()->IsSint()); + EXPECT_EQ(scalar->literal()->AsSint()->value(), 0); + + ASSERT_NE(ptr->idx_expr()->result_type(), nullptr); + ASSERT_TRUE(ptr->idx_expr()->result_type()->IsI32()); +} + +TEST_F(BoundArrayAccessorsTest, Array_Idx_OutOfBounds) { + // var a : array + // var b : f32 = a[3] + // + // -> var b : f32 = a[2] + + ast::type::F32Type f32; + ast::type::U32Type u32; + ast::type::ArrayType ary(&f32, 3); + + SetupFunctionAndBody(); + DeclareVariable( + std::make_unique("a", ast::StorageClass::kFunction, &ary)); + + auto accessor = std::make_unique( + std::make_unique("a"), + std::make_unique( + std::make_unique(&u32, 3u))); + auto* ptr = accessor.get(); + + auto b = + std::make_unique("b", ast::StorageClass::kFunction, &f32); + b->set_constructor(std::move(accessor)); + DeclareVariable(std::move(b)); + + ASSERT_TRUE(td()->Determine()) << td()->error(); + + ASSERT_TRUE(transform()->Run()); + ASSERT_TRUE(ptr->IsArrayAccessor()); + ASSERT_TRUE(ptr->idx_expr()->IsConstructor()); + ASSERT_TRUE(ptr->idx_expr()->AsConstructor()->IsScalarConstructor()); + + auto* scalar = ptr->idx_expr()->AsConstructor()->AsScalarConstructor(); + ASSERT_TRUE(scalar->literal()->IsUint()); + EXPECT_EQ(scalar->literal()->AsUint()->value(), 2u); + + ASSERT_NE(ptr->idx_expr()->result_type(), nullptr); + ASSERT_TRUE(ptr->idx_expr()->result_type()->IsU32()); +} + +TEST_F(BoundArrayAccessorsTest, Vector_Idx_Scalar) { + // var a : vec3 + // var b : f32 = a[1]; + // + // -> var b : f32 = a[1] + + ast::type::F32Type f32; + ast::type::U32Type u32; + ast::type::VectorType vec(&f32, 3); + + SetupFunctionAndBody(); + DeclareVariable( + std::make_unique("a", ast::StorageClass::kFunction, &vec)); + + auto accessor = std::make_unique( + std::make_unique("a"), + std::make_unique( + std::make_unique(&u32, 1u))); + auto* ptr = accessor.get(); + + auto b = + std::make_unique("b", ast::StorageClass::kFunction, &f32); + b->set_constructor(std::move(accessor)); + DeclareVariable(std::move(b)); + + ASSERT_TRUE(td()->Determine()) << td()->error(); + + ASSERT_TRUE(transform()->Run()); + ASSERT_TRUE(ptr->IsArrayAccessor()); + ASSERT_TRUE(ptr->idx_expr()->IsConstructor()); + ASSERT_TRUE(ptr->idx_expr()->AsConstructor()->IsScalarConstructor()); + + auto* scalar = ptr->idx_expr()->AsConstructor()->AsScalarConstructor(); + ASSERT_TRUE(scalar->literal()->IsUint()); + EXPECT_EQ(scalar->literal()->AsUint()->value(), 1u); + + ASSERT_NE(ptr->idx_expr()->result_type(), nullptr); + ASSERT_TRUE(ptr->idx_expr()->result_type()->IsU32()); +} + +TEST_F(BoundArrayAccessorsTest, Vector_Idx_Expr) { + // var a : vec3 + // var c : u32; + // var b : f32 = a[c + 2 - 3] + // + // -> var b : f32 = a[clamp((c + 2 - 3), 0, 2)] + + ast::type::F32Type f32; + ast::type::U32Type u32; + ast::type::VectorType vec(&f32, 3); + + SetupFunctionAndBody(); + DeclareVariable( + std::make_unique("a", ast::StorageClass::kFunction, &vec)); + DeclareVariable( + std::make_unique("c", ast::StorageClass::kFunction, &u32)); + + auto access_idx = std::make_unique( + ast::BinaryOp::kAdd, std::make_unique("c"), + std::make_unique( + ast::BinaryOp::kSubtract, + std::make_unique( + std::make_unique(&u32, 2)), + std::make_unique( + std::make_unique(&u32, 3)))); + auto* access_ptr = access_idx.get(); + + auto accessor = std::make_unique( + std::make_unique("a"), std::move(access_idx)); + auto* ptr = accessor.get(); + + auto b = + std::make_unique("b", ast::StorageClass::kFunction, &f32); + b->set_constructor(std::move(accessor)); + DeclareVariable(std::move(b)); + + ASSERT_TRUE(td()->Determine()) << td()->error(); + + ASSERT_TRUE(transform()->Run()); + ASSERT_TRUE(ptr->IsArrayAccessor()); + ASSERT_TRUE(ptr->idx_expr()->IsCall()); + + auto* idx = ptr->idx_expr()->AsCall(); + ASSERT_TRUE(idx->func()->IsIdentifier()); + EXPECT_EQ(idx->func()->AsIdentifier()->name(), "clamp"); + + ASSERT_EQ(idx->params().size(), 3u); + ASSERT_EQ(idx->params()[0].get(), access_ptr); + + ASSERT_TRUE(idx->params()[1]->IsConstructor()); + ASSERT_TRUE(idx->params()[1]->AsConstructor()->IsScalarConstructor()); + auto* scalar = idx->params()[1]->AsConstructor()->AsScalarConstructor(); + ASSERT_TRUE(scalar->literal()->IsUint()); + EXPECT_EQ(scalar->literal()->AsUint()->value(), 0u); + + ASSERT_TRUE(idx->params()[2]->IsConstructor()); + ASSERT_TRUE(idx->params()[2]->AsConstructor()->IsScalarConstructor()); + scalar = idx->params()[2]->AsConstructor()->AsScalarConstructor(); + ASSERT_TRUE(scalar->literal()->IsUint()); + EXPECT_EQ(scalar->literal()->AsUint()->value(), 2u); + + ASSERT_NE(ptr->idx_expr()->result_type(), nullptr); + ASSERT_TRUE(ptr->idx_expr()->result_type()->IsU32()); +} + +TEST_F(BoundArrayAccessorsTest, Vector_Idx_Negative) { + // var a : vec3 + // var b : f32 = a[-1] + // + // -> var b : f32 = a[0] + + ast::type::F32Type f32; + ast::type::I32Type i32; + ast::type::VectorType vec(&f32, 3); + + SetupFunctionAndBody(); + DeclareVariable( + std::make_unique("a", ast::StorageClass::kFunction, &vec)); + + auto accessor = std::make_unique( + std::make_unique("a"), + std::make_unique( + std::make_unique(&i32, -1))); + auto* ptr = accessor.get(); + + auto b = + std::make_unique("b", ast::StorageClass::kFunction, &f32); + b->set_constructor(std::move(accessor)); + DeclareVariable(std::move(b)); + + ASSERT_TRUE(td()->Determine()) << td()->error(); + + ASSERT_TRUE(transform()->Run()); + ASSERT_TRUE(ptr->IsArrayAccessor()); + ASSERT_TRUE(ptr->idx_expr()->IsConstructor()); + ASSERT_TRUE(ptr->idx_expr()->AsConstructor()->IsScalarConstructor()); + + auto* scalar = ptr->idx_expr()->AsConstructor()->AsScalarConstructor(); + ASSERT_TRUE(scalar->literal()->IsSint()); + EXPECT_EQ(scalar->literal()->AsSint()->value(), 0); + + ASSERT_NE(ptr->idx_expr()->result_type(), nullptr); + ASSERT_TRUE(ptr->idx_expr()->result_type()->IsI32()); +} + +TEST_F(BoundArrayAccessorsTest, Vector_Idx_OutOfBounds) { + // var a : vec3 + // var b : f32 = a[3] + // + // -> var b : f32 = a[2] + + ast::type::F32Type f32; + ast::type::U32Type u32; + ast::type::VectorType vec(&f32, 3); + + SetupFunctionAndBody(); + DeclareVariable( + std::make_unique("a", ast::StorageClass::kFunction, &vec)); + + auto accessor = std::make_unique( + std::make_unique("a"), + std::make_unique( + std::make_unique(&u32, 3u))); + auto* ptr = accessor.get(); + + auto b = + std::make_unique("b", ast::StorageClass::kFunction, &f32); + b->set_constructor(std::move(accessor)); + DeclareVariable(std::move(b)); + + ASSERT_TRUE(td()->Determine()) << td()->error(); + + ASSERT_TRUE(transform()->Run()); + ASSERT_TRUE(ptr->IsArrayAccessor()); + ASSERT_TRUE(ptr->idx_expr()->IsConstructor()); + ASSERT_TRUE(ptr->idx_expr()->AsConstructor()->IsScalarConstructor()); + + auto* scalar = ptr->idx_expr()->AsConstructor()->AsScalarConstructor(); + ASSERT_TRUE(scalar->literal()->IsUint()); + EXPECT_EQ(scalar->literal()->AsUint()->value(), 2u); + + ASSERT_NE(ptr->idx_expr()->result_type(), nullptr); + ASSERT_TRUE(ptr->idx_expr()->result_type()->IsU32()); +} + +TEST_F(BoundArrayAccessorsTest, Matrix_Idx_Scalar) { + // var a : mat3x2 + // var b : f32 = a[2][1]; + // + // -> var b : f32 = a[2][1] + + ast::type::F32Type f32; + ast::type::U32Type u32; + ast::type::MatrixType mat(&f32, 2, 3); + + SetupFunctionAndBody(); + DeclareVariable( + std::make_unique("a", ast::StorageClass::kFunction, &mat)); + + auto accessor = std::make_unique( + std::make_unique( + std::make_unique("a"), + std::make_unique( + std::make_unique(&u32, 2u))), + std::make_unique( + std::make_unique(&u32, 1u))); + auto* ptr = accessor.get(); + + auto b = + std::make_unique("b", ast::StorageClass::kFunction, &f32); + b->set_constructor(std::move(accessor)); + DeclareVariable(std::move(b)); + + ASSERT_TRUE(td()->Determine()) << td()->error(); + + ASSERT_TRUE(transform()->Run()); + ASSERT_TRUE(ptr->IsArrayAccessor()); + + ASSERT_TRUE(ptr->array()->IsArrayAccessor()); + auto* ary = ptr->array()->AsArrayAccessor(); + ASSERT_TRUE(ary->idx_expr()->IsConstructor()); + ASSERT_TRUE(ary->idx_expr()->AsConstructor()->IsScalarConstructor()); + + auto* scalar = ary->idx_expr()->AsConstructor()->AsScalarConstructor(); + ASSERT_TRUE(scalar->literal()->IsUint()); + EXPECT_EQ(scalar->literal()->AsUint()->value(), 2u); + + ASSERT_NE(ary->idx_expr()->result_type(), nullptr); + ASSERT_TRUE(ary->idx_expr()->result_type()->IsU32()); + + ASSERT_TRUE(ptr->idx_expr()->IsConstructor()); + ASSERT_TRUE(ptr->idx_expr()->AsConstructor()->IsScalarConstructor()); + + scalar = ptr->idx_expr()->AsConstructor()->AsScalarConstructor(); + ASSERT_TRUE(scalar->literal()->IsUint()); + EXPECT_EQ(scalar->literal()->AsUint()->value(), 1u); + + ASSERT_NE(ptr->idx_expr()->result_type(), nullptr); + ASSERT_TRUE(ptr->idx_expr()->result_type()->IsU32()); +} + +TEST_F(BoundArrayAccessorsTest, Matrix_Idx_Expr_Column) { + // var a : mat3x2 + // var c : u32; + // var b : f32 = a[c + 2 - 3][1] + // + // -> var b : f32 = a[clamp((c + 2 - 3), 0, 2)][1] + + ast::type::F32Type f32; + ast::type::U32Type u32; + ast::type::MatrixType mat(&f32, 2, 3); + + SetupFunctionAndBody(); + DeclareVariable( + std::make_unique("a", ast::StorageClass::kFunction, &mat)); + DeclareVariable( + std::make_unique("c", ast::StorageClass::kFunction, &u32)); + + auto access_idx = std::make_unique( + ast::BinaryOp::kAdd, std::make_unique("c"), + std::make_unique( + ast::BinaryOp::kSubtract, + std::make_unique( + std::make_unique(&u32, 2)), + std::make_unique( + std::make_unique(&u32, 3)))); + auto* access_ptr = access_idx.get(); + + auto accessor = std::make_unique( + std::make_unique( + std::make_unique("a"), + std::move(access_idx)), + std::make_unique( + std::make_unique(&u32, 1u))); + auto* ptr = accessor.get(); + + auto b = + std::make_unique("b", ast::StorageClass::kFunction, &f32); + b->set_constructor(std::move(accessor)); + DeclareVariable(std::move(b)); + + ASSERT_TRUE(td()->Determine()) << td()->error(); + + ASSERT_TRUE(transform()->Run()); + ASSERT_TRUE(ptr->IsArrayAccessor()); + + ASSERT_TRUE(ptr->array()->IsArrayAccessor()); + auto* ary = ptr->array()->AsArrayAccessor(); + + ASSERT_TRUE(ary->idx_expr()->IsCall()); + auto* idx = ary->idx_expr()->AsCall(); + ASSERT_TRUE(idx->func()->IsIdentifier()); + EXPECT_EQ(idx->func()->AsIdentifier()->name(), "clamp"); + + ASSERT_EQ(idx->params().size(), 3u); + ASSERT_EQ(idx->params()[0].get(), access_ptr); + + ASSERT_TRUE(idx->params()[1]->IsConstructor()); + ASSERT_TRUE(idx->params()[1]->AsConstructor()->IsScalarConstructor()); + auto* scalar = idx->params()[1]->AsConstructor()->AsScalarConstructor(); + ASSERT_TRUE(scalar->literal()->IsUint()); + EXPECT_EQ(scalar->literal()->AsUint()->value(), 0u); + + ASSERT_TRUE(idx->params()[2]->IsConstructor()); + ASSERT_TRUE(idx->params()[2]->AsConstructor()->IsScalarConstructor()); + scalar = idx->params()[2]->AsConstructor()->AsScalarConstructor(); + ASSERT_TRUE(scalar->literal()->IsUint()); + EXPECT_EQ(scalar->literal()->AsUint()->value(), 2u); + + ASSERT_NE(ary->idx_expr()->result_type(), nullptr); + ASSERT_TRUE(ary->idx_expr()->result_type()->IsU32()); + + ASSERT_TRUE(ptr->idx_expr()->IsConstructor()); + ASSERT_TRUE(ptr->idx_expr()->AsConstructor()->IsScalarConstructor()); + + scalar = ptr->idx_expr()->AsConstructor()->AsScalarConstructor(); + ASSERT_TRUE(scalar->literal()->IsUint()); + EXPECT_EQ(scalar->literal()->AsUint()->value(), 1u); + + ASSERT_NE(ptr->idx_expr()->result_type(), nullptr); + ASSERT_TRUE(ptr->idx_expr()->result_type()->IsU32()); +} + +TEST_F(BoundArrayAccessorsTest, Matrix_Idx_Expr_Row) { + // var a : mat3x2 + // var c : u32; + // var b : f32 = a[1][c + 2 - 3] + // + // -> var b : f32 = a[1][clamp((c + 2 - 3), 0, 1)] + + ast::type::F32Type f32; + ast::type::U32Type u32; + ast::type::MatrixType mat(&f32, 2, 3); + + SetupFunctionAndBody(); + DeclareVariable( + std::make_unique("a", ast::StorageClass::kFunction, &mat)); + DeclareVariable( + std::make_unique("c", ast::StorageClass::kFunction, &u32)); + + auto access_idx = std::make_unique( + ast::BinaryOp::kAdd, std::make_unique("c"), + std::make_unique( + ast::BinaryOp::kSubtract, + std::make_unique( + std::make_unique(&u32, 2)), + std::make_unique( + std::make_unique(&u32, 3)))); + auto* access_ptr = access_idx.get(); + + auto accessor = std::make_unique( + std::make_unique( + std::make_unique("a"), + std::make_unique( + std::make_unique(&u32, 1u))), + std::move(access_idx)); + auto* ptr = accessor.get(); + + auto b = + std::make_unique("b", ast::StorageClass::kFunction, &f32); + b->set_constructor(std::move(accessor)); + DeclareVariable(std::move(b)); + + ASSERT_TRUE(td()->Determine()) << td()->error(); + + ASSERT_TRUE(transform()->Run()); + ASSERT_TRUE(ptr->IsArrayAccessor()); + + ASSERT_TRUE(ptr->array()->IsArrayAccessor()); + auto* ary = ptr->array()->AsArrayAccessor(); + + ASSERT_TRUE(ary->idx_expr()->IsConstructor()); + ASSERT_TRUE(ary->idx_expr()->AsConstructor()->IsScalarConstructor()); + + auto* scalar = ary->idx_expr()->AsConstructor()->AsScalarConstructor(); + ASSERT_TRUE(scalar->literal()->IsUint()); + EXPECT_EQ(scalar->literal()->AsUint()->value(), 1u); + + ASSERT_TRUE(ptr->idx_expr()->IsCall()); + auto* idx = ptr->idx_expr()->AsCall(); + ASSERT_TRUE(idx->func()->IsIdentifier()); + EXPECT_EQ(idx->func()->AsIdentifier()->name(), "clamp"); + + ASSERT_EQ(idx->params().size(), 3u); + ASSERT_EQ(idx->params()[0].get(), access_ptr); + + ASSERT_TRUE(idx->params()[1]->IsConstructor()); + ASSERT_TRUE(idx->params()[1]->AsConstructor()->IsScalarConstructor()); + scalar = idx->params()[1]->AsConstructor()->AsScalarConstructor(); + ASSERT_TRUE(scalar->literal()->IsUint()); + EXPECT_EQ(scalar->literal()->AsUint()->value(), 0u); + + ASSERT_TRUE(idx->params()[2]->IsConstructor()); + ASSERT_TRUE(idx->params()[2]->AsConstructor()->IsScalarConstructor()); + scalar = idx->params()[2]->AsConstructor()->AsScalarConstructor(); + ASSERT_TRUE(scalar->literal()->IsUint()); + EXPECT_EQ(scalar->literal()->AsUint()->value(), 1u); + + ASSERT_NE(ary->idx_expr()->result_type(), nullptr); + ASSERT_TRUE(ary->idx_expr()->result_type()->IsU32()); + + ASSERT_NE(ptr->idx_expr()->result_type(), nullptr); + ASSERT_TRUE(ptr->idx_expr()->result_type()->IsU32()); +} + +TEST_F(BoundArrayAccessorsTest, Matrix_Idx_Negative_Column) { + // var a : mat3x2 + // var b : f32 = a[-1][1] + // + // -> var b : f32 = a[0][1] + ast::type::F32Type f32; + ast::type::I32Type i32; + ast::type::MatrixType mat(&f32, 2, 3); + + SetupFunctionAndBody(); + DeclareVariable( + std::make_unique("a", ast::StorageClass::kFunction, &mat)); + + auto accessor = std::make_unique( + std::make_unique( + std::make_unique("a"), + std::make_unique( + std::make_unique(&i32, -1))), + std::make_unique( + std::make_unique(&i32, 1))); + auto* ptr = accessor.get(); + + auto b = + std::make_unique("b", ast::StorageClass::kFunction, &f32); + b->set_constructor(std::move(accessor)); + DeclareVariable(std::move(b)); + + ASSERT_TRUE(td()->Determine()) << td()->error(); + + ASSERT_TRUE(transform()->Run()); + ASSERT_TRUE(ptr->IsArrayAccessor()); + + ASSERT_TRUE(ptr->array()->IsArrayAccessor()); + auto* ary = ptr->array()->AsArrayAccessor(); + ASSERT_TRUE(ary->idx_expr()->IsConstructor()); + ASSERT_TRUE(ary->idx_expr()->AsConstructor()->IsScalarConstructor()); + + auto* scalar = ary->idx_expr()->AsConstructor()->AsScalarConstructor(); + ASSERT_TRUE(scalar->literal()->IsSint()); + EXPECT_EQ(scalar->literal()->AsSint()->value(), 0); + + ASSERT_NE(ary->idx_expr()->result_type(), nullptr); + ASSERT_TRUE(ary->idx_expr()->result_type()->IsI32()); + + ASSERT_TRUE(ptr->idx_expr()->IsConstructor()); + ASSERT_TRUE(ptr->idx_expr()->AsConstructor()->IsScalarConstructor()); + + scalar = ptr->idx_expr()->AsConstructor()->AsScalarConstructor(); + ASSERT_TRUE(scalar->literal()->IsSint()); + EXPECT_EQ(scalar->literal()->AsSint()->value(), 1); + + ASSERT_NE(ptr->idx_expr()->result_type(), nullptr); + ASSERT_TRUE(ptr->idx_expr()->result_type()->IsI32()); +} + +TEST_F(BoundArrayAccessorsTest, Matrix_Idx_Negative_Row) { + // var a : mat3x2 + // var b : f32 = a[2][-1] + // + // -> var b : f32 = a[2][0] + ast::type::F32Type f32; + ast::type::I32Type i32; + ast::type::MatrixType mat(&f32, 2, 3); + + SetupFunctionAndBody(); + DeclareVariable( + std::make_unique("a", ast::StorageClass::kFunction, &mat)); + + auto accessor = std::make_unique( + std::make_unique( + std::make_unique("a"), + std::make_unique( + std::make_unique(&i32, 2))), + std::make_unique( + std::make_unique(&i32, -1))); + auto* ptr = accessor.get(); + + auto b = + std::make_unique("b", ast::StorageClass::kFunction, &f32); + b->set_constructor(std::move(accessor)); + DeclareVariable(std::move(b)); + + ASSERT_TRUE(td()->Determine()) << td()->error(); + + ASSERT_TRUE(transform()->Run()); + ASSERT_TRUE(ptr->IsArrayAccessor()); + + ASSERT_TRUE(ptr->array()->IsArrayAccessor()); + auto* ary = ptr->array()->AsArrayAccessor(); + ASSERT_TRUE(ary->idx_expr()->IsConstructor()); + ASSERT_TRUE(ary->idx_expr()->AsConstructor()->IsScalarConstructor()); + + auto* scalar = ary->idx_expr()->AsConstructor()->AsScalarConstructor(); + ASSERT_TRUE(scalar->literal()->IsSint()); + EXPECT_EQ(scalar->literal()->AsSint()->value(), 2); + + ASSERT_NE(ary->idx_expr()->result_type(), nullptr); + ASSERT_TRUE(ary->idx_expr()->result_type()->IsI32()); + + ASSERT_TRUE(ptr->idx_expr()->IsConstructor()); + ASSERT_TRUE(ptr->idx_expr()->AsConstructor()->IsScalarConstructor()); + + scalar = ptr->idx_expr()->AsConstructor()->AsScalarConstructor(); + ASSERT_TRUE(scalar->literal()->IsSint()); + EXPECT_EQ(scalar->literal()->AsSint()->value(), 0); + + ASSERT_NE(ptr->idx_expr()->result_type(), nullptr); + ASSERT_TRUE(ptr->idx_expr()->result_type()->IsI32()); +} + +TEST_F(BoundArrayAccessorsTest, Matrix_Idx_OutOfBounds_Column) { + // var a : mat3x2 + // var b : f32 = a[5][1] + // + // -> var b : f32 = a[2][1] + + ast::type::F32Type f32; + ast::type::U32Type u32; + ast::type::MatrixType mat(&f32, 2, 3); + + SetupFunctionAndBody(); + DeclareVariable( + std::make_unique("a", ast::StorageClass::kFunction, &mat)); + + auto accessor = std::make_unique( + std::make_unique( + std::make_unique("a"), + std::make_unique( + std::make_unique(&u32, 5u))), + std::make_unique( + std::make_unique(&u32, 1u))); + auto* ptr = accessor.get(); + + auto b = + std::make_unique("b", ast::StorageClass::kFunction, &f32); + b->set_constructor(std::move(accessor)); + DeclareVariable(std::move(b)); + + ASSERT_TRUE(td()->Determine()) << td()->error(); + + ASSERT_TRUE(transform()->Run()); + ASSERT_TRUE(ptr->IsArrayAccessor()); + + ASSERT_TRUE(ptr->array()->IsArrayAccessor()); + auto* ary = ptr->array()->AsArrayAccessor(); + ASSERT_TRUE(ary->idx_expr()->IsConstructor()); + ASSERT_TRUE(ary->idx_expr()->AsConstructor()->IsScalarConstructor()); + + auto* scalar = ary->idx_expr()->AsConstructor()->AsScalarConstructor(); + ASSERT_TRUE(scalar->literal()->IsUint()); + EXPECT_EQ(scalar->literal()->AsUint()->value(), 2u); + + ASSERT_NE(ary->idx_expr()->result_type(), nullptr); + ASSERT_TRUE(ary->idx_expr()->result_type()->IsU32()); + + ASSERT_TRUE(ptr->idx_expr()->IsConstructor()); + ASSERT_TRUE(ptr->idx_expr()->AsConstructor()->IsScalarConstructor()); + + scalar = ptr->idx_expr()->AsConstructor()->AsScalarConstructor(); + ASSERT_TRUE(scalar->literal()->IsUint()); + EXPECT_EQ(scalar->literal()->AsUint()->value(), 1u); + + ASSERT_NE(ptr->idx_expr()->result_type(), nullptr); + ASSERT_TRUE(ptr->idx_expr()->result_type()->IsU32()); +} + +TEST_F(BoundArrayAccessorsTest, Matrix_Idx_OutOfBounds_Row) { + // var a : mat3x2 + // var b : f32 = a[2][5] + // + // -> var b : f32 = a[2][1] + + ast::type::F32Type f32; + ast::type::U32Type u32; + ast::type::MatrixType mat(&f32, 2, 3); + + SetupFunctionAndBody(); + DeclareVariable( + std::make_unique("a", ast::StorageClass::kFunction, &mat)); + + auto accessor = std::make_unique( + std::make_unique( + std::make_unique("a"), + std::make_unique( + std::make_unique(&u32, 2u))), + std::make_unique( + std::make_unique(&u32, 5u))); + auto* ptr = accessor.get(); + + auto b = + std::make_unique("b", ast::StorageClass::kFunction, &f32); + b->set_constructor(std::move(accessor)); + DeclareVariable(std::move(b)); + + ASSERT_TRUE(td()->Determine()) << td()->error(); + + ASSERT_TRUE(transform()->Run()); + ASSERT_TRUE(ptr->IsArrayAccessor()); + + ASSERT_TRUE(ptr->array()->IsArrayAccessor()); + auto* ary = ptr->array()->AsArrayAccessor(); + ASSERT_TRUE(ary->idx_expr()->IsConstructor()); + ASSERT_TRUE(ary->idx_expr()->AsConstructor()->IsScalarConstructor()); + + auto* scalar = ary->idx_expr()->AsConstructor()->AsScalarConstructor(); + ASSERT_TRUE(scalar->literal()->IsUint()); + EXPECT_EQ(scalar->literal()->AsUint()->value(), 2u); + + ASSERT_NE(ary->idx_expr()->result_type(), nullptr); + ASSERT_TRUE(ary->idx_expr()->result_type()->IsU32()); + + ASSERT_TRUE(ptr->idx_expr()->IsConstructor()); + ASSERT_TRUE(ptr->idx_expr()->AsConstructor()->IsScalarConstructor()); + + scalar = ptr->idx_expr()->AsConstructor()->AsScalarConstructor(); + ASSERT_TRUE(scalar->literal()->IsUint()); + EXPECT_EQ(scalar->literal()->AsUint()->value(), 1u); + + ASSERT_NE(ptr->idx_expr()->result_type(), nullptr); + ASSERT_TRUE(ptr->idx_expr()->result_type()->IsU32()); +} + +// TODO(dsinclair): Implement when constant_id exists +TEST_F(BoundArrayAccessorsTest, DISABLED_Vector_Constant_Id_Clamps) { + // [[constant_id(1300)]] const idx : i32; + // var a : vec3 + // var b : f32 = a[idx] + // + // ->var b : f32 = a[clamp(idx, 0, 2)] +} + +// TODO(dsinclair): Implement when constant_id exists +TEST_F(BoundArrayAccessorsTest, DISABLED_Array_Constant_Id_Clamps) { + // [[constant_id(1300)]] const idx : i32; + // var a : array + // var b : f32 = a[idx] + // + // -> var b : f32 = a[clamp(idx, 0, 3)] +} + +// TODO(dsinclair): Implement when constant_id exists +TEST_F(BoundArrayAccessorsTest, DISABLED_Matrix_Column_Constant_Id_Clamps) { + // [[constant_id(1300)]] const idx : i32; + // var a : mat3x2 + // var b : f32 = a[idx][1] + // + // -> var b : f32 = a[clamp(idx, 0, 2)][1] +} + +// TODO(dsinclair): Implement when constant_id exists +TEST_F(BoundArrayAccessorsTest, DISABLED_Matrix_Row_Constant_Id_Clamps) { + // [[constant_id(1300)]] const idx : i32; + // var a : mat3x2 + // var b : f32 = a[1][idx] + // + // -> var b : f32 = a[1][clamp(idx, 0, 1)] +} + +// TODO(dsinclair): Implement when we have arrayLength for Runtime Arrays +TEST_F(BoundArrayAccessorsTest, DISABLED_RuntimeArray_Clamps) { + // struct S { + // a : f32; + // b : array; + // } + // S s; + // var b : f32 = s.b[25] + // + // -> var b : f32 = s.b[clamp(25, 0, arrayLength(s.b))] +} + +// TODO(dsinclair): Clamp atomics when available. +TEST_F(BoundArrayAccessorsTest, DISABLED_Atomics_Clamp) { + FAIL(); +} + +// TODO(dsinclair): Clamp texture coord values. Depends on: +// https://github.com/gpuweb/gpuweb/issues/1107 +TEST_F(BoundArrayAccessorsTest, DISABLED_TextureCoord_Clamp) { + FAIL(); +} + +// TODO(dsinclair): Test for scoped variables when Lexical Scopes implemented +TEST_F(BoundArrayAccessorsTest, DISABLED_Scoped_Variable) { + // var a : array; + // var i : u32; + // { + // var a : array; + // var b : f32 = a[i]; + // } + // var c : f32 = a[i]; + // + // -> var b : f32 = a[clamp(i, 0, 4)]; + // var c : f32 = a[clamp(i, 0, 2)]; + FAIL(); +} + +} // namespace +} // namespace transform +} // namespace tint diff --git a/src/transform/vertex_pulling_transform.cc b/src/transform/vertex_pulling_transform.cc index 76b2522f31..ab668f99bc 100644 --- a/src/transform/vertex_pulling_transform.cc +++ b/src/transform/vertex_pulling_transform.cc @@ -37,7 +37,6 @@ namespace tint { namespace transform { - namespace { static const char kVertexBufferNamePrefix[] = "_tint_pulling_vertex_buffer_";