blob: 43137715cbe4494f242eb736a7c680a98e5f81b1 [file] [log] [blame]
// Copyright 2021 The Tint Authors.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
#include "src/transform/localize_struct_array_assignment.h"
#include <unordered_map>
#include <utility>
#include "src/ast/assignment_statement.h"
#include "src/ast/traverse_expressions.h"
#include "src/program_builder.h"
#include "src/sem/expression.h"
#include "src/sem/member_accessor_expression.h"
#include "src/sem/reference_type.h"
#include "src/sem/statement.h"
#include "src/sem/variable.h"
#include "src/transform/simplify_pointers.h"
#include "src/utils/scoped_assignment.h"
namespace tint {
namespace transform {
/// Private implementation of LocalizeStructArrayAssignment transform
class LocalizeStructArrayAssignment::State {
CloneContext& ctx;
ProgramBuilder& b;
/// Returns true if `expr` contains an index accessor expression to a
/// structure member of array type.
bool ContainsStructArrayIndex(const ast::Expression* expr) {
bool result = false;
expr, b.Diagnostics(), [&](const ast::IndexAccessorExpression* ia) {
// Indexing using a runtime value?
auto* idx_sem = ctx.src->Sem().Get(ia->index);
if (!idx_sem->ConstantValue().IsValid()) {
// Indexing a member access expr?
if (auto* ma = ia->object->As<ast::MemberAccessorExpression>()) {
// That accesses an array?
if (ctx.src->TypeOf(ma)->UnwrapRef()->Is<sem::Array>()) {
result = true;
return ast::TraverseAction::Stop;
return ast::TraverseAction::Descend;
return result;
// Returns the type and storage class of the originating variable of the lhs
// of the assignment statement.
// See
std::pair<const sem::Type*, ast::StorageClass>
const ast::AssignmentStatement* assign_stmt) {
// Get first IdentifierExpr from lhs of assignment, which should resolve to
// the pointer or reference of the originating variable of the assignment.
// TraverseExpressions traverses left to right, and this code depends on the
// fact that for an assignment statement, the variable will be the left-most
// expression.
// TODO( do this in the Resolver, setting the
// originating variable on sem::Expression.
const ast::IdentifierExpression* ident = nullptr;
ast::TraverseExpressions(assign_stmt->lhs, b.Diagnostics(),
[&](const ast::IdentifierExpression* id) {
ident = id;
return ast::TraverseAction::Stop;
auto* sem_var_user = ctx.src->Sem().Get<sem::VariableUser>(ident);
if (!sem_var_user) {
TINT_ICE(Transform, b.Diagnostics())
<< "Expected to find variable of lhs of assignment statement";
return {};
auto* var = sem_var_user->Variable();
if (auto* ptr = var->Type()->As<sem::Pointer>()) {
return {ptr->StoreType(), ptr->StorageClass()};
auto* ref = var->Type()->As<sem::Reference>();
if (!ref) {
TINT_ICE(Transform, b.Diagnostics())
<< "Expecting to find variable of type pointer or reference on lhs "
"of assignment statement";
return {};
return {ref->StoreType(), ref->StorageClass()};
/// Constructor
/// @param ctx_in the CloneContext primed with the input program and
/// ProgramBuilder
explicit State(CloneContext& ctx_in) : ctx(ctx_in), b(*ctx_in.dst) {}
/// Runs the transform
void Run() {
struct Shared {
bool process_nested_nodes = false;
ast::StatementList insert_before_stmts;
ast::StatementList insert_after_stmts;
} s;
ctx.ReplaceAll([&](const ast::AssignmentStatement* assign_stmt)
-> const ast::Statement* {
// Process if it's an assignment statement to a dynamically indexed array
// within a struct on a function or private storage variable. This
// specific use-case is what FXC fails to compile with:
// error X3500: array reference cannot be used as an l-value; not natively
// addressable
if (!ContainsStructArrayIndex(assign_stmt->lhs)) {
return nullptr;
auto og = GetOriginatingTypeAndStorageClass(assign_stmt);
if (!(og.first->Is<sem::Struct>() &&
(og.second == ast::StorageClass::kFunction ||
og.second == ast::StorageClass::kPrivate))) {
return nullptr;
// Reset shared state for this assignment statement
s = Shared{};
const ast::Expression* new_lhs = nullptr;
TINT_SCOPED_ASSIGNMENT(s.process_nested_nodes, true);
new_lhs = ctx.Clone(assign_stmt->lhs);
auto* new_assign_stmt = b.Assign(new_lhs, ctx.Clone(assign_stmt->rhs));
// Combine insert_before_stmts + new_assign_stmt + insert_after_stmts into
// a block and return it
ast::StatementList stmts = std::move(s.insert_before_stmts);
stmts.reserve(1 + s.insert_after_stmts.size());
stmts.insert(stmts.end(), s.insert_after_stmts.begin(),
return b.Block(std::move(stmts));
ctx.ReplaceAll([&](const ast::IndexAccessorExpression* index_access)
-> const ast::Expression* {
if (!s.process_nested_nodes) {
return nullptr;
// Indexing a member access expr?
auto* mem_access =
if (!mem_access) {
return nullptr;
// Process any nested IndexAccessorExpressions
mem_access = ctx.Clone(mem_access);
// Store the address of the member access into a let as we need to read
// the value twice e.g. let tint_symbol = &(s.a1);
auto mem_access_ptr = b.Sym();
b.Decl(b.Const(mem_access_ptr, nullptr, b.AddressOf(mem_access))));
// Disable further transforms when cloning
TINT_SCOPED_ASSIGNMENT(s.process_nested_nodes, false);
// Copy entire array out of struct into local temp var
// e.g. var tint_symbol_1 = *(tint_symbol);
auto tmp_var = b.Sym();
b.Decl(b.Var(tmp_var, nullptr, b.Deref(mem_access_ptr))));
// Replace input index_access with a clone of itself, but with its
// .object replaced by the new temp var. This is returned from this
// function to modify the original assignment statement. e.g.
// tint_symbol_1[uniforms.i]
auto* new_index_access =
b.IndexAccessor(tmp_var, ctx.Clone(index_access->index));
// Assign temp var back to array
// e.g. *(tint_symbol) = tint_symbol_1;
auto* assign_rhs_to_temp = b.Assign(b.Deref(mem_access_ptr), tmp_var);
assign_rhs_to_temp); // push_front
return new_index_access;
LocalizeStructArrayAssignment::LocalizeStructArrayAssignment() = default;
LocalizeStructArrayAssignment::~LocalizeStructArrayAssignment() = default;
void LocalizeStructArrayAssignment::Run(CloneContext& ctx,
const DataMap&,
DataMap&) const {
State state(ctx);
} // namespace transform
} // namespace tint