| // 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/tint/transform/simplify_pointers.h" | |
| #include <memory> | |
| #include <unordered_map> | |
| #include <utility> | |
| #include <vector> | |
| #include "src/tint/program_builder.h" | |
| #include "src/tint/sem/block_statement.h" | |
| #include "src/tint/sem/function.h" | |
| #include "src/tint/sem/statement.h" | |
| #include "src/tint/sem/variable.h" | |
| #include "src/tint/transform/unshadow.h" | |
| TINT_INSTANTIATE_TYPEINFO(tint::transform::SimplifyPointers); | |
| namespace tint::transform { | |
| namespace { | |
| /// PointerOp describes either possible indirection or address-of action on an | |
| /// expression. | |
| struct PointerOp { | |
| /// Positive: Number of times the `expr` was dereferenced (*expr) | |
| /// Negative: Number of times the `expr` was 'addressed-of' (&expr) | |
| /// Zero: no pointer op on `expr` | |
| int indirections = 0; | |
| /// The expression being operated on | |
| const ast::Expression* expr = nullptr; | |
| }; | |
| } // namespace | |
| /// The PIMPL state for the SimplifyPointers transform | |
| struct SimplifyPointers::State { | |
| /// The clone context | |
| CloneContext& ctx; | |
| /// Constructor | |
| /// @param context the clone context | |
| explicit State(CloneContext& context) : ctx(context) {} | |
| /// Traverses the expression `expr` looking for non-literal array indexing | |
| /// expressions that would affect the computed address of a pointer | |
| /// expression. The function-like argument `cb` is called for each found. | |
| /// @param expr the expression to traverse | |
| /// @param cb a function-like object with the signature | |
| /// `void(const ast::Expression*)`, which is called for each array index | |
| /// expression | |
| template <typename F> | |
| static void CollectSavedArrayIndices(const ast::Expression* expr, F&& cb) { | |
| if (auto* a = expr->As<ast::IndexAccessorExpression>()) { | |
| CollectSavedArrayIndices(a->object, cb); | |
| if (!a->index->Is<ast::LiteralExpression>()) { | |
| cb(a->index); | |
| } | |
| return; | |
| } | |
| if (auto* m = expr->As<ast::MemberAccessorExpression>()) { | |
| CollectSavedArrayIndices(m->structure, cb); | |
| return; | |
| } | |
| if (auto* u = expr->As<ast::UnaryOpExpression>()) { | |
| CollectSavedArrayIndices(u->expr, cb); | |
| return; | |
| } | |
| // Note: Other ast::Expression types can be safely ignored as they cannot be | |
| // used to generate a reference or pointer. | |
| // See https://gpuweb.github.io/gpuweb/wgsl/#forming-references-and-pointers | |
| } | |
| /// Reduce walks the expression chain, collapsing all address-of and | |
| /// indirection ops into a PointerOp. | |
| /// @param in the expression to walk | |
| /// @returns the reduced PointerOp | |
| PointerOp Reduce(const ast::Expression* in) const { | |
| PointerOp op{0, in}; | |
| while (true) { | |
| if (auto* unary = op.expr->As<ast::UnaryOpExpression>()) { | |
| switch (unary->op) { | |
| case ast::UnaryOp::kIndirection: | |
| op.indirections++; | |
| op.expr = unary->expr; | |
| continue; | |
| case ast::UnaryOp::kAddressOf: | |
| op.indirections--; | |
| op.expr = unary->expr; | |
| continue; | |
| default: | |
| break; | |
| } | |
| } | |
| if (auto* user = ctx.src->Sem().Get<sem::VariableUser>(op.expr)) { | |
| auto* var = user->Variable(); | |
| if (var->Is<sem::LocalVariable>() && // | |
| var->Declaration()->Is<ast::Let>() && // | |
| var->Type()->Is<sem::Pointer>()) { | |
| op.expr = var->Declaration()->constructor; | |
| continue; | |
| } | |
| } | |
| return op; | |
| } | |
| } | |
| /// Performs the transformation | |
| void Run() { | |
| // A map of saved expressions to their saved variable name | |
| std::unordered_map<const ast::Expression*, Symbol> saved_vars; | |
| // Register the ast::Expression transform handler. | |
| // This performs two different transformations: | |
| // * Identifiers that resolve to the pointer-typed `let` declarations are | |
| // replaced with the recursively inlined initializer expression for the | |
| // `let` declaration. | |
| // * Sub-expressions inside the pointer-typed `let` initializer expression | |
| // that have been hoisted to a saved variable are replaced with the saved | |
| // variable identifier. | |
| ctx.ReplaceAll([&](const ast::Expression* expr) -> const ast::Expression* { | |
| // Look to see if we need to swap this Expression with a saved variable. | |
| auto it = saved_vars.find(expr); | |
| if (it != saved_vars.end()) { | |
| return ctx.dst->Expr(it->second); | |
| } | |
| // Reduce the expression, folding away chains of address-of / indirections | |
| auto op = Reduce(expr); | |
| // Clone the reduced root expression | |
| expr = ctx.CloneWithoutTransform(op.expr); | |
| // And reapply the minimum number of address-of / indirections | |
| for (int i = 0; i < op.indirections; i++) { | |
| expr = ctx.dst->Deref(expr); | |
| } | |
| for (int i = 0; i > op.indirections; i--) { | |
| expr = ctx.dst->AddressOf(expr); | |
| } | |
| return expr; | |
| }); | |
| // Find all the pointer-typed `let` declarations. | |
| // Note that these must be function-scoped, as module-scoped `let`s are not | |
| // permitted. | |
| for (auto* node : ctx.src->ASTNodes().Objects()) { | |
| if (auto* let = node->As<ast::VariableDeclStatement>()) { | |
| if (!let->variable->Is<ast::Let>()) { | |
| continue; // Not a `let` declaration. Ignore. | |
| } | |
| auto* var = ctx.src->Sem().Get(let->variable); | |
| if (!var->Type()->Is<sem::Pointer>()) { | |
| continue; // Not a pointer type. Ignore. | |
| } | |
| // We're dealing with a pointer-typed `let` declaration. | |
| // Scan the initializer expression for array index expressions that need | |
| // to be hoist to temporary "saved" variables. | |
| std::vector<const ast::VariableDeclStatement*> saved; | |
| CollectSavedArrayIndices( | |
| var->Declaration()->constructor, [&](const ast::Expression* idx_expr) { | |
| // We have a sub-expression that needs to be saved. | |
| // Create a new variable | |
| auto saved_name = ctx.dst->Symbols().New( | |
| ctx.src->Symbols().NameFor(var->Declaration()->symbol) + "_save"); | |
| auto* decl = | |
| ctx.dst->Decl(ctx.dst->Let(saved_name, nullptr, ctx.Clone(idx_expr))); | |
| saved.emplace_back(decl); | |
| // Record the substitution of `idx_expr` to the saved variable | |
| // with the symbol `saved_name`. This will be used by the | |
| // ReplaceAll() handler above. | |
| saved_vars.emplace(idx_expr, saved_name); | |
| }); | |
| // Find the place to insert the saved declarations. | |
| // Special care needs to be made for lets declared as the initializer | |
| // part of for-loops. In this case the block will hold the for-loop | |
| // statement, not the let. | |
| if (!saved.empty()) { | |
| auto* stmt = ctx.src->Sem().Get(let); | |
| auto* block = stmt->Block(); | |
| // Find the statement owned by the block (either the let decl or a | |
| // for-loop) | |
| while (block != stmt->Parent()) { | |
| stmt = stmt->Parent(); | |
| } | |
| // Declare the stored variables just before stmt. Order here is | |
| // important as order-of-operations needs to be preserved. | |
| // CollectSavedArrayIndices() visits the LHS of an index accessor | |
| // before the index expression. | |
| for (auto* decl : saved) { | |
| // Note that repeated calls to InsertBefore() with the same `before` | |
| // argument will result in nodes to inserted in the order the | |
| // calls are made (last call is inserted last). | |
| ctx.InsertBefore(block->Declaration()->statements, stmt->Declaration(), | |
| decl); | |
| } | |
| } | |
| // As the original `let` declaration will be fully inlined, there's no | |
| // need for the original declaration to exist. Remove it. | |
| RemoveStatement(ctx, let); | |
| } | |
| } | |
| ctx.Clone(); | |
| } | |
| }; | |
| SimplifyPointers::SimplifyPointers() = default; | |
| SimplifyPointers::~SimplifyPointers() = default; | |
| void SimplifyPointers::Run(CloneContext& ctx, const DataMap&, DataMap&) const { | |
| State(ctx).Run(); | |
| } | |
| } // namespace tint::transform |