[transform] Add BufferArrayAccessors transform
This CL adds a buffer array accessor clamping transform to
the available transforms in Tint.
Bug: tint:101
Change-Id: If9d5b0fb2c3adba723ce2185870b0e10981103a6
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/28980
Commit-Queue: Ryan Harrison <rharrison@chromium.org>
Reviewed-by: Ryan Harrison <rharrison@chromium.org>
diff --git a/BUILD.gn b/BUILD.gn
index 707cab3..69cc327 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -373,6 +373,8 @@
"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 @@
"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 798bc6a..4325e8e 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -194,6 +194,8 @@
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 @@
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 5f90fb6..0359161 100644
--- a/src/ast/array_accessor_expression.h
+++ b/src/ast/array_accessor_expression.h
@@ -60,6 +60,9 @@
}
/// @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<Expression> 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 571eeb3..0b3cf7a 100644
--- a/src/ast/case_statement.h
+++ b/src/ast/case_statement.h
@@ -72,6 +72,8 @@
}
/// @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 fda156b..c79ffb8 100644
--- a/src/ast/else_statement.h
+++ b/src/ast/else_statement.h
@@ -71,6 +71,8 @@
}
/// @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 0dd540b..7e21c28 100644
--- a/src/ast/function.h
+++ b/src/ast/function.h
@@ -163,7 +163,9 @@
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 8108ebf..53af7b7 100644
--- a/src/ast/if_statement.h
+++ b/src/ast/if_statement.h
@@ -62,6 +62,8 @@
}
/// @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 @@
}
/// @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 bf8f8a5..edb2381 100644
--- a/src/ast/loop_statement.h
+++ b/src/ast/loop_statement.h
@@ -51,6 +51,8 @@
}
/// @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 @@
}
/// @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 9d23cf7..3995148 100644
--- a/src/ast/sint_literal.h
+++ b/src/ast/sint_literal.h
@@ -34,6 +34,9 @@
/// @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 7eb1311..6189ee4 100644
--- a/src/ast/uint_literal.h
+++ b/src/ast/uint_literal.h
@@ -34,6 +34,9 @@
/// @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 0000000..8ae5bd8
--- /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::type::U32Type>());
+
+ ast::ExpressionList params;
+ params.push_back(expr->take_idx_expr());
+ params.push_back(std::make_unique<ast::ScalarConstructorExpression>(
+ std::make_unique<ast::UintLiteral>(u32, 0)));
+ params.push_back(std::make_unique<ast::ScalarConstructorExpression>(
+ std::make_unique<ast::UintLiteral>(u32, size - 1)));
+
+ auto call_expr = std::make_unique<ast::CallExpression>(
+ std::make_unique<ast::IdentifierExpression>("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 0000000..2a40225
--- /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 <string>
+
+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<ast::Variable*> 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 0000000..b1b774d
--- /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 <memory>
+
+#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<ast::Function>("func", ast::VariableList{},
+ &void_type_);
+ auto block = std::make_unique<ast::BlockStatement>();
+ body_ = block.get();
+ func->set_body(std::move(block));
+ mod_.AddFunction(std::move(func));
+ return body_;
+ }
+
+ void DeclareVariable(std::unique_ptr<ast::Variable> var) {
+ ASSERT_NE(body_, nullptr);
+ body_->append(std::make_unique<ast::VariableDeclStatement>(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<f32, 3>;
+ // const c : u32 = 1;
+ // const b : ptr<function, f32> = a[c]
+ //
+ // -> const b : ptr<function, i32> = 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<ast::Variable>("a", ast::StorageClass::kFunction, &ary));
+
+ auto c_var =
+ std::make_unique<ast::Variable>("c", ast::StorageClass::kFunction, &u32);
+ c_var->set_is_const(true);
+ DeclareVariable(std::move(c_var));
+
+ auto access_idx = std::make_unique<ast::IdentifierExpression>("c");
+ auto* access_ptr = access_idx.get();
+
+ auto accessor = std::make_unique<ast::ArrayAccessorExpression>(
+ std::make_unique<ast::IdentifierExpression>("a"), std::move(access_idx));
+ auto* ptr = accessor.get();
+
+ auto b = std::make_unique<ast::Variable>("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<f32, 3>;
+ // var b : array<f32, 5>;
+ // 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<ast::Variable>(
+ "a", ast::StorageClass::kFunction, &ary3));
+ DeclareVariable(std::make_unique<ast::Variable>(
+ "b", ast::StorageClass::kFunction, &ary5));
+ DeclareVariable(
+ std::make_unique<ast::Variable>("i", ast::StorageClass::kFunction, &u32));
+
+ auto b_access_idx = std::make_unique<ast::IdentifierExpression>("i");
+ auto* b_access_ptr = b_access_idx.get();
+
+ auto a_access_idx = std::make_unique<ast::ArrayAccessorExpression>(
+ std::make_unique<ast::IdentifierExpression>("b"),
+ std::move(b_access_idx));
+
+ auto accessor = std::make_unique<ast::ArrayAccessorExpression>(
+ std::make_unique<ast::IdentifierExpression>("a"),
+ std::move(a_access_idx));
+ auto* ptr = accessor.get();
+
+ auto b =
+ std::make_unique<ast::Variable>("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<f32, 3>
+ // 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<ast::Variable>("a", ast::StorageClass::kFunction, &ary));
+
+ auto accessor = std::make_unique<ast::ArrayAccessorExpression>(
+ std::make_unique<ast::IdentifierExpression>("a"),
+ std::make_unique<ast::ScalarConstructorExpression>(
+ std::make_unique<ast::UintLiteral>(&u32, 1u)));
+ auto* ptr = accessor.get();
+
+ auto b =
+ std::make_unique<ast::Variable>("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<f32, 3>
+ // 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<ast::Variable>("a", ast::StorageClass::kFunction, &ary));
+ DeclareVariable(
+ std::make_unique<ast::Variable>("c", ast::StorageClass::kFunction, &u32));
+
+ auto access_idx = std::make_unique<ast::BinaryExpression>(
+ ast::BinaryOp::kAdd, std::make_unique<ast::IdentifierExpression>("c"),
+ std::make_unique<ast::BinaryExpression>(
+ ast::BinaryOp::kSubtract,
+ std::make_unique<ast::ScalarConstructorExpression>(
+ std::make_unique<ast::UintLiteral>(&u32, 2)),
+ std::make_unique<ast::ScalarConstructorExpression>(
+ std::make_unique<ast::UintLiteral>(&u32, 3))));
+ auto* access_ptr = access_idx.get();
+
+ auto accessor = std::make_unique<ast::ArrayAccessorExpression>(
+ std::make_unique<ast::IdentifierExpression>("a"), std::move(access_idx));
+ auto* ptr = accessor.get();
+
+ auto b =
+ std::make_unique<ast::Variable>("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<f32, 3>
+ // 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<ast::Variable>("a", ast::StorageClass::kFunction, &ary));
+
+ auto accessor = std::make_unique<ast::ArrayAccessorExpression>(
+ std::make_unique<ast::IdentifierExpression>("a"),
+ std::make_unique<ast::ScalarConstructorExpression>(
+ std::make_unique<ast::SintLiteral>(&i32, -1)));
+ auto* ptr = accessor.get();
+
+ auto b =
+ std::make_unique<ast::Variable>("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<f32, 3>
+ // 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<ast::Variable>("a", ast::StorageClass::kFunction, &ary));
+
+ auto accessor = std::make_unique<ast::ArrayAccessorExpression>(
+ std::make_unique<ast::IdentifierExpression>("a"),
+ std::make_unique<ast::ScalarConstructorExpression>(
+ std::make_unique<ast::UintLiteral>(&u32, 3u)));
+ auto* ptr = accessor.get();
+
+ auto b =
+ std::make_unique<ast::Variable>("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<f32>
+ // 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<ast::Variable>("a", ast::StorageClass::kFunction, &vec));
+
+ auto accessor = std::make_unique<ast::ArrayAccessorExpression>(
+ std::make_unique<ast::IdentifierExpression>("a"),
+ std::make_unique<ast::ScalarConstructorExpression>(
+ std::make_unique<ast::UintLiteral>(&u32, 1u)));
+ auto* ptr = accessor.get();
+
+ auto b =
+ std::make_unique<ast::Variable>("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<f32>
+ // 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<ast::Variable>("a", ast::StorageClass::kFunction, &vec));
+ DeclareVariable(
+ std::make_unique<ast::Variable>("c", ast::StorageClass::kFunction, &u32));
+
+ auto access_idx = std::make_unique<ast::BinaryExpression>(
+ ast::BinaryOp::kAdd, std::make_unique<ast::IdentifierExpression>("c"),
+ std::make_unique<ast::BinaryExpression>(
+ ast::BinaryOp::kSubtract,
+ std::make_unique<ast::ScalarConstructorExpression>(
+ std::make_unique<ast::UintLiteral>(&u32, 2)),
+ std::make_unique<ast::ScalarConstructorExpression>(
+ std::make_unique<ast::UintLiteral>(&u32, 3))));
+ auto* access_ptr = access_idx.get();
+
+ auto accessor = std::make_unique<ast::ArrayAccessorExpression>(
+ std::make_unique<ast::IdentifierExpression>("a"), std::move(access_idx));
+ auto* ptr = accessor.get();
+
+ auto b =
+ std::make_unique<ast::Variable>("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<f32>
+ // 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<ast::Variable>("a", ast::StorageClass::kFunction, &vec));
+
+ auto accessor = std::make_unique<ast::ArrayAccessorExpression>(
+ std::make_unique<ast::IdentifierExpression>("a"),
+ std::make_unique<ast::ScalarConstructorExpression>(
+ std::make_unique<ast::SintLiteral>(&i32, -1)));
+ auto* ptr = accessor.get();
+
+ auto b =
+ std::make_unique<ast::Variable>("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<f32>
+ // 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<ast::Variable>("a", ast::StorageClass::kFunction, &vec));
+
+ auto accessor = std::make_unique<ast::ArrayAccessorExpression>(
+ std::make_unique<ast::IdentifierExpression>("a"),
+ std::make_unique<ast::ScalarConstructorExpression>(
+ std::make_unique<ast::UintLiteral>(&u32, 3u)));
+ auto* ptr = accessor.get();
+
+ auto b =
+ std::make_unique<ast::Variable>("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<f32>
+ // 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<ast::Variable>("a", ast::StorageClass::kFunction, &mat));
+
+ auto accessor = std::make_unique<ast::ArrayAccessorExpression>(
+ std::make_unique<ast::ArrayAccessorExpression>(
+ std::make_unique<ast::IdentifierExpression>("a"),
+ std::make_unique<ast::ScalarConstructorExpression>(
+ std::make_unique<ast::UintLiteral>(&u32, 2u))),
+ std::make_unique<ast::ScalarConstructorExpression>(
+ std::make_unique<ast::UintLiteral>(&u32, 1u)));
+ auto* ptr = accessor.get();
+
+ auto b =
+ std::make_unique<ast::Variable>("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<f32>
+ // 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<ast::Variable>("a", ast::StorageClass::kFunction, &mat));
+ DeclareVariable(
+ std::make_unique<ast::Variable>("c", ast::StorageClass::kFunction, &u32));
+
+ auto access_idx = std::make_unique<ast::BinaryExpression>(
+ ast::BinaryOp::kAdd, std::make_unique<ast::IdentifierExpression>("c"),
+ std::make_unique<ast::BinaryExpression>(
+ ast::BinaryOp::kSubtract,
+ std::make_unique<ast::ScalarConstructorExpression>(
+ std::make_unique<ast::UintLiteral>(&u32, 2)),
+ std::make_unique<ast::ScalarConstructorExpression>(
+ std::make_unique<ast::UintLiteral>(&u32, 3))));
+ auto* access_ptr = access_idx.get();
+
+ auto accessor = std::make_unique<ast::ArrayAccessorExpression>(
+ std::make_unique<ast::ArrayAccessorExpression>(
+ std::make_unique<ast::IdentifierExpression>("a"),
+ std::move(access_idx)),
+ std::make_unique<ast::ScalarConstructorExpression>(
+ std::make_unique<ast::UintLiteral>(&u32, 1u)));
+ auto* ptr = accessor.get();
+
+ auto b =
+ std::make_unique<ast::Variable>("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<f32>
+ // 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<ast::Variable>("a", ast::StorageClass::kFunction, &mat));
+ DeclareVariable(
+ std::make_unique<ast::Variable>("c", ast::StorageClass::kFunction, &u32));
+
+ auto access_idx = std::make_unique<ast::BinaryExpression>(
+ ast::BinaryOp::kAdd, std::make_unique<ast::IdentifierExpression>("c"),
+ std::make_unique<ast::BinaryExpression>(
+ ast::BinaryOp::kSubtract,
+ std::make_unique<ast::ScalarConstructorExpression>(
+ std::make_unique<ast::UintLiteral>(&u32, 2)),
+ std::make_unique<ast::ScalarConstructorExpression>(
+ std::make_unique<ast::UintLiteral>(&u32, 3))));
+ auto* access_ptr = access_idx.get();
+
+ auto accessor = std::make_unique<ast::ArrayAccessorExpression>(
+ std::make_unique<ast::ArrayAccessorExpression>(
+ std::make_unique<ast::IdentifierExpression>("a"),
+ std::make_unique<ast::ScalarConstructorExpression>(
+ std::make_unique<ast::UintLiteral>(&u32, 1u))),
+ std::move(access_idx));
+ auto* ptr = accessor.get();
+
+ auto b =
+ std::make_unique<ast::Variable>("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<f32>
+ // 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<ast::Variable>("a", ast::StorageClass::kFunction, &mat));
+
+ auto accessor = std::make_unique<ast::ArrayAccessorExpression>(
+ std::make_unique<ast::ArrayAccessorExpression>(
+ std::make_unique<ast::IdentifierExpression>("a"),
+ std::make_unique<ast::ScalarConstructorExpression>(
+ std::make_unique<ast::SintLiteral>(&i32, -1))),
+ std::make_unique<ast::ScalarConstructorExpression>(
+ std::make_unique<ast::SintLiteral>(&i32, 1)));
+ auto* ptr = accessor.get();
+
+ auto b =
+ std::make_unique<ast::Variable>("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<f32>
+ // 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<ast::Variable>("a", ast::StorageClass::kFunction, &mat));
+
+ auto accessor = std::make_unique<ast::ArrayAccessorExpression>(
+ std::make_unique<ast::ArrayAccessorExpression>(
+ std::make_unique<ast::IdentifierExpression>("a"),
+ std::make_unique<ast::ScalarConstructorExpression>(
+ std::make_unique<ast::SintLiteral>(&i32, 2))),
+ std::make_unique<ast::ScalarConstructorExpression>(
+ std::make_unique<ast::SintLiteral>(&i32, -1)));
+ auto* ptr = accessor.get();
+
+ auto b =
+ std::make_unique<ast::Variable>("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<f32>
+ // 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<ast::Variable>("a", ast::StorageClass::kFunction, &mat));
+
+ auto accessor = std::make_unique<ast::ArrayAccessorExpression>(
+ std::make_unique<ast::ArrayAccessorExpression>(
+ std::make_unique<ast::IdentifierExpression>("a"),
+ std::make_unique<ast::ScalarConstructorExpression>(
+ std::make_unique<ast::UintLiteral>(&u32, 5u))),
+ std::make_unique<ast::ScalarConstructorExpression>(
+ std::make_unique<ast::UintLiteral>(&u32, 1u)));
+ auto* ptr = accessor.get();
+
+ auto b =
+ std::make_unique<ast::Variable>("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<f32>
+ // 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<ast::Variable>("a", ast::StorageClass::kFunction, &mat));
+
+ auto accessor = std::make_unique<ast::ArrayAccessorExpression>(
+ std::make_unique<ast::ArrayAccessorExpression>(
+ std::make_unique<ast::IdentifierExpression>("a"),
+ std::make_unique<ast::ScalarConstructorExpression>(
+ std::make_unique<ast::UintLiteral>(&u32, 2u))),
+ std::make_unique<ast::ScalarConstructorExpression>(
+ std::make_unique<ast::UintLiteral>(&u32, 5u)));
+ auto* ptr = accessor.get();
+
+ auto b =
+ std::make_unique<ast::Variable>("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<f32>
+ // 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<f32, 4>
+ // 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<f32>
+ // 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<f32>
+ // 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<f32>;
+ // }
+ // 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<f32, 3>;
+ // var i : u32;
+ // {
+ // var a : array<f32, 5>;
+ // 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 76b2522..ab668f9 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_";