dawn-cmake/src/transform/localize_struct_array_assignment.cc
Ben Clayton 800b8e3175 optimizations: Implement transform::ShouldRun()
This change adds an override for Transform::ShouldRun() for many of the transforms that can trivially detect whether running would be a no-op or not. Most programs do not require all the transforms to be run, and by skipping those that are not needed, significant performance wins can be had.

This change also removes Transform::Requires() and Program::HasTransformApplied(). This makes little sense now that transforms can be skipped, and the usefulness of this information has been severely reduced since the introduction of transforms that need to be run more than once.
Instread, just document on the transform class what the expectations are.

Issue: tint:1383
Change-Id: I1a6f27cc4ba61ca1475a4ba912c465db619f76c7
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/77121
Reviewed-by: Antonio Maiorano <amaiorano@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
2022-01-25 21:36:04 +00:00

225 lines
8.1 KiB
C++

// Copyright 2021 The Tint Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "src/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"
TINT_INSTANTIATE_TYPEINFO(tint::transform::LocalizeStructArrayAssignment);
namespace tint {
namespace transform {
/// Private implementation of LocalizeStructArrayAssignment transform
class LocalizeStructArrayAssignment::State {
private:
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;
ast::TraverseExpressions(
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 https://www.w3.org/TR/WGSL/#originating-variable-section
std::pair<const sem::Type*, ast::StorageClass>
GetOriginatingTypeAndStorageClass(
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(crbug.com/tint/1341): 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()};
}
public:
/// 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.emplace_back(new_assign_stmt);
stmts.insert(stmts.end(), s.insert_after_stmts.begin(),
s.insert_after_stmts.end());
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 =
index_access->object->As<ast::MemberAccessorExpression>();
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();
s.insert_before_stmts.push_back(
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();
s.insert_before_stmts.push_back(
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);
s.insert_after_stmts.insert(s.insert_after_stmts.begin(),
assign_rhs_to_temp); // push_front
return new_index_access;
});
ctx.Clone();
}
};
LocalizeStructArrayAssignment::LocalizeStructArrayAssignment() = default;
LocalizeStructArrayAssignment::~LocalizeStructArrayAssignment() = default;
void LocalizeStructArrayAssignment::Run(CloneContext& ctx,
const DataMap&,
DataMap&) const {
State state(ctx);
state.Run();
}
} // namespace transform
} // namespace tint