Austin Eng | cc2516a | 2023-10-17 20:57:54 +0000 | [diff] [blame] | 1 | // Copyright 2021 The Dawn & Tint Authors |
Ryan Harrison | dbc13af | 2022-02-21 15:19:07 +0000 | [diff] [blame] | 2 | // |
Austin Eng | cc2516a | 2023-10-17 20:57:54 +0000 | [diff] [blame] | 3 | // Redistribution and use in source and binary forms, with or without |
| 4 | // modification, are permitted provided that the following conditions are met: |
Ryan Harrison | dbc13af | 2022-02-21 15:19:07 +0000 | [diff] [blame] | 5 | // |
Austin Eng | cc2516a | 2023-10-17 20:57:54 +0000 | [diff] [blame] | 6 | // 1. Redistributions of source code must retain the above copyright notice, this |
| 7 | // list of conditions and the following disclaimer. |
Ryan Harrison | dbc13af | 2022-02-21 15:19:07 +0000 | [diff] [blame] | 8 | // |
Austin Eng | cc2516a | 2023-10-17 20:57:54 +0000 | [diff] [blame] | 9 | // 2. Redistributions in binary form must reproduce the above copyright notice, |
| 10 | // this list of conditions and the following disclaimer in the documentation |
| 11 | // and/or other materials provided with the distribution. |
| 12 | // |
| 13 | // 3. Neither the name of the copyright holder nor the names of its |
| 14 | // contributors may be used to endorse or promote products derived from |
| 15 | // this software without specific prior written permission. |
| 16 | // |
| 17 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| 18 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| 19 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| 20 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE |
| 21 | // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| 22 | // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
| 23 | // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
| 24 | // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
| 25 | // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 26 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
Ryan Harrison | dbc13af | 2022-02-21 15:19:07 +0000 | [diff] [blame] | 27 | |
dan sinclair | 99181d8 | 2023-07-20 01:14:15 +0000 | [diff] [blame] | 28 | #include "src/tint/lang/wgsl/ast/transform/simplify_pointers.h" |
Ryan Harrison | dbc13af | 2022-02-21 15:19:07 +0000 | [diff] [blame] | 29 | |
| 30 | #include <memory> |
| 31 | #include <unordered_map> |
| 32 | #include <utility> |
| 33 | #include <vector> |
| 34 | |
dan sinclair | 99181d8 | 2023-07-20 01:14:15 +0000 | [diff] [blame] | 35 | #include "src/tint/lang/wgsl/ast/transform/unshadow.h" |
Ben Clayton | ae18c41 | 2023-07-29 13:00:40 +0000 | [diff] [blame] | 36 | #include "src/tint/lang/wgsl/program/clone_context.h" |
dan sinclair | 96db554 | 2023-07-20 09:21:10 +0000 | [diff] [blame] | 37 | #include "src/tint/lang/wgsl/program/program_builder.h" |
Ben Clayton | 915ceca | 2023-07-29 13:12:58 +0000 | [diff] [blame] | 38 | #include "src/tint/lang/wgsl/resolver/resolve.h" |
dan sinclair | d3b1369 | 2023-07-20 01:14:15 +0000 | [diff] [blame] | 39 | #include "src/tint/lang/wgsl/sem/block_statement.h" |
| 40 | #include "src/tint/lang/wgsl/sem/function.h" |
| 41 | #include "src/tint/lang/wgsl/sem/statement.h" |
| 42 | #include "src/tint/lang/wgsl/sem/variable.h" |
dan sinclair | 22b4dd2 | 2023-07-21 00:40:07 +0000 | [diff] [blame] | 43 | #include "src/tint/utils/rtti/switch.h" |
Ryan Harrison | dbc13af | 2022-02-21 15:19:07 +0000 | [diff] [blame] | 44 | |
James Price | b4acbb8 | 2023-05-11 21:27:16 +0000 | [diff] [blame] | 45 | TINT_INSTANTIATE_TYPEINFO(tint::ast::transform::SimplifyPointers); |
Ryan Harrison | dbc13af | 2022-02-21 15:19:07 +0000 | [diff] [blame] | 46 | |
James Price | b4acbb8 | 2023-05-11 21:27:16 +0000 | [diff] [blame] | 47 | namespace tint::ast::transform { |
Ryan Harrison | dbc13af | 2022-02-21 15:19:07 +0000 | [diff] [blame] | 48 | |
| 49 | namespace { |
| 50 | |
| 51 | /// PointerOp describes either possible indirection or address-of action on an |
| 52 | /// expression. |
| 53 | struct PointerOp { |
dan sinclair | 41e4d9a | 2022-05-01 14:40:55 +0000 | [diff] [blame] | 54 | /// Positive: Number of times the `expr` was dereferenced (*expr) |
| 55 | /// Negative: Number of times the `expr` was 'addressed-of' (&expr) |
| 56 | /// Zero: no pointer op on `expr` |
| 57 | int indirections = 0; |
| 58 | /// The expression being operated on |
James Price | 2b7406a | 2023-05-12 01:43:50 +0000 | [diff] [blame] | 59 | const Expression* expr = nullptr; |
Ryan Harrison | dbc13af | 2022-02-21 15:19:07 +0000 | [diff] [blame] | 60 | }; |
| 61 | |
| 62 | } // namespace |
| 63 | |
Ben Clayton | c6b3814 | 2022-11-03 08:41:19 +0000 | [diff] [blame] | 64 | /// PIMPL state for the transform |
Ryan Harrison | dbc13af | 2022-02-21 15:19:07 +0000 | [diff] [blame] | 65 | struct SimplifyPointers::State { |
Ben Clayton | c6b3814 | 2022-11-03 08:41:19 +0000 | [diff] [blame] | 66 | /// The source program |
Ben Clayton | 5ed5cc4 | 2023-09-22 10:31:04 +0000 | [diff] [blame] | 67 | const Program& src; |
Ben Clayton | c6b3814 | 2022-11-03 08:41:19 +0000 | [diff] [blame] | 68 | /// The target program builder |
| 69 | ProgramBuilder b; |
dan sinclair | 41e4d9a | 2022-05-01 14:40:55 +0000 | [diff] [blame] | 70 | /// The clone context |
Ben Clayton | 5ed5cc4 | 2023-09-22 10:31:04 +0000 | [diff] [blame] | 71 | program::CloneContext ctx = {&b, &src, /* auto_clone_symbols */ true}; |
Ryan Harrison | dbc13af | 2022-02-21 15:19:07 +0000 | [diff] [blame] | 72 | |
dan sinclair | 41e4d9a | 2022-05-01 14:40:55 +0000 | [diff] [blame] | 73 | /// Constructor |
Ben Clayton | c6b3814 | 2022-11-03 08:41:19 +0000 | [diff] [blame] | 74 | /// @param program the source program |
Ben Clayton | 5ed5cc4 | 2023-09-22 10:31:04 +0000 | [diff] [blame] | 75 | explicit State(const Program& program) : src(program) {} |
Ryan Harrison | dbc13af | 2022-02-21 15:19:07 +0000 | [diff] [blame] | 76 | |
dan sinclair | 41e4d9a | 2022-05-01 14:40:55 +0000 | [diff] [blame] | 77 | /// Traverses the expression `expr` looking for non-literal array indexing |
| 78 | /// expressions that would affect the computed address of a pointer |
| 79 | /// expression. The function-like argument `cb` is called for each found. |
| 80 | /// @param expr the expression to traverse |
| 81 | /// @param cb a function-like object with the signature |
James Price | 2b7406a | 2023-05-12 01:43:50 +0000 | [diff] [blame] | 82 | /// `void(const Expression*)`, which is called for each array index |
dan sinclair | 41e4d9a | 2022-05-01 14:40:55 +0000 | [diff] [blame] | 83 | /// expression |
| 84 | template <typename F> |
James Price | 2b7406a | 2023-05-12 01:43:50 +0000 | [diff] [blame] | 85 | static void CollectSavedArrayIndices(const Expression* expr, F&& cb) { |
| 86 | if (auto* a = expr->As<IndexAccessorExpression>()) { |
dan sinclair | 41e4d9a | 2022-05-01 14:40:55 +0000 | [diff] [blame] | 87 | CollectSavedArrayIndices(a->object, cb); |
James Price | 2b7406a | 2023-05-12 01:43:50 +0000 | [diff] [blame] | 88 | if (!a->index->Is<LiteralExpression>()) { |
dan sinclair | 41e4d9a | 2022-05-01 14:40:55 +0000 | [diff] [blame] | 89 | cb(a->index); |
| 90 | } |
| 91 | return; |
Ryan Harrison | dbc13af | 2022-02-21 15:19:07 +0000 | [diff] [blame] | 92 | } |
| 93 | |
James Price | 2b7406a | 2023-05-12 01:43:50 +0000 | [diff] [blame] | 94 | if (auto* m = expr->As<MemberAccessorExpression>()) { |
Ben Clayton | ad31565 | 2023-02-05 12:36:50 +0000 | [diff] [blame] | 95 | CollectSavedArrayIndices(m->object, cb); |
dan sinclair | 41e4d9a | 2022-05-01 14:40:55 +0000 | [diff] [blame] | 96 | return; |
Ryan Harrison | dbc13af | 2022-02-21 15:19:07 +0000 | [diff] [blame] | 97 | } |
| 98 | |
James Price | 2b7406a | 2023-05-12 01:43:50 +0000 | [diff] [blame] | 99 | if (auto* u = expr->As<UnaryOpExpression>()) { |
dan sinclair | 41e4d9a | 2022-05-01 14:40:55 +0000 | [diff] [blame] | 100 | CollectSavedArrayIndices(u->expr, cb); |
| 101 | return; |
Ryan Harrison | dbc13af | 2022-02-21 15:19:07 +0000 | [diff] [blame] | 102 | } |
| 103 | |
James Price | 2b7406a | 2023-05-12 01:43:50 +0000 | [diff] [blame] | 104 | // Note: Other Expression types can be safely ignored as they cannot be |
dan sinclair | 41e4d9a | 2022-05-01 14:40:55 +0000 | [diff] [blame] | 105 | // used to generate a reference or pointer. |
| 106 | // See https://gpuweb.github.io/gpuweb/wgsl/#forming-references-and-pointers |
Ryan Harrison | dbc13af | 2022-02-21 15:19:07 +0000 | [diff] [blame] | 107 | } |
dan sinclair | 41e4d9a | 2022-05-01 14:40:55 +0000 | [diff] [blame] | 108 | |
| 109 | /// Reduce walks the expression chain, collapsing all address-of and |
| 110 | /// indirection ops into a PointerOp. |
| 111 | /// @param in the expression to walk |
| 112 | /// @returns the reduced PointerOp |
James Price | 2b7406a | 2023-05-12 01:43:50 +0000 | [diff] [blame] | 113 | PointerOp Reduce(const Expression* in) const { |
dan sinclair | 41e4d9a | 2022-05-01 14:40:55 +0000 | [diff] [blame] | 114 | PointerOp op{0, in}; |
| 115 | while (true) { |
James Price | 2b7406a | 2023-05-12 01:43:50 +0000 | [diff] [blame] | 116 | if (auto* unary = op.expr->As<UnaryOpExpression>()) { |
dan sinclair | 41e4d9a | 2022-05-01 14:40:55 +0000 | [diff] [blame] | 117 | switch (unary->op) { |
Ben Clayton | 67b461b | 2023-08-08 07:58:19 +0000 | [diff] [blame] | 118 | case core::UnaryOp::kIndirection: |
dan sinclair | 41e4d9a | 2022-05-01 14:40:55 +0000 | [diff] [blame] | 119 | op.indirections++; |
| 120 | op.expr = unary->expr; |
| 121 | continue; |
Ben Clayton | 67b461b | 2023-08-08 07:58:19 +0000 | [diff] [blame] | 122 | case core::UnaryOp::kAddressOf: |
dan sinclair | 41e4d9a | 2022-05-01 14:40:55 +0000 | [diff] [blame] | 123 | op.indirections--; |
| 124 | op.expr = unary->expr; |
| 125 | continue; |
| 126 | default: |
| 127 | break; |
| 128 | } |
| 129 | } |
| 130 | if (auto* user = ctx.src->Sem().Get<sem::VariableUser>(op.expr)) { |
| 131 | auto* var = user->Variable(); |
James Price | 2b7406a | 2023-05-12 01:43:50 +0000 | [diff] [blame] | 132 | if (var->Is<sem::LocalVariable>() && // |
| 133 | var->Declaration()->Is<Let>() && // |
dan sinclair | cedcdf3 | 2023-08-10 02:39:48 +0000 | [diff] [blame] | 134 | var->Type()->Is<core::type::Pointer>()) { |
dan sinclair | 6e77b47 | 2022-10-20 13:38:28 +0000 | [diff] [blame] | 135 | op.expr = var->Declaration()->initializer; |
dan sinclair | 41e4d9a | 2022-05-01 14:40:55 +0000 | [diff] [blame] | 136 | continue; |
| 137 | } |
| 138 | } |
| 139 | return op; |
| 140 | } |
| 141 | } |
| 142 | |
Ben Clayton | c6b3814 | 2022-11-03 08:41:19 +0000 | [diff] [blame] | 143 | /// Runs the transform |
| 144 | /// @returns the new program or SkipTransform if the transform is not required |
| 145 | ApplyResult Run() { |
dan sinclair | 41e4d9a | 2022-05-01 14:40:55 +0000 | [diff] [blame] | 146 | // A map of saved expressions to their saved variable name |
dan sinclair | bae54e7 | 2023-07-28 15:01:54 +0000 | [diff] [blame] | 147 | Hashmap<const Expression*, Symbol, 8> saved_vars; |
dan sinclair | 41e4d9a | 2022-05-01 14:40:55 +0000 | [diff] [blame] | 148 | |
Ben Clayton | 42363a5 | 2023-01-12 18:29:07 +0000 | [diff] [blame] | 149 | bool needs_transform = false; |
Ben Clayton | 559e5a2 | 2023-04-17 14:24:58 +0000 | [diff] [blame] | 150 | for (auto* ty : ctx.src->Types()) { |
dan sinclair | cedcdf3 | 2023-08-10 02:39:48 +0000 | [diff] [blame] | 151 | if (ty->Is<core::type::Pointer>()) { |
Ben Clayton | 559e5a2 | 2023-04-17 14:24:58 +0000 | [diff] [blame] | 152 | // Program contains pointers which need removing. |
| 153 | needs_transform = true; |
| 154 | break; |
| 155 | } |
| 156 | } |
Ben Clayton | 42363a5 | 2023-01-12 18:29:07 +0000 | [diff] [blame] | 157 | |
| 158 | // Find all the pointer-typed `let` declarations. |
| 159 | // Note that these must be function-scoped, as module-scoped `let`s are not |
| 160 | // permitted. |
| 161 | for (auto* node : ctx.src->ASTNodes().Objects()) { |
| 162 | Switch( |
| 163 | node, // |
James Price | 2b7406a | 2023-05-12 01:43:50 +0000 | [diff] [blame] | 164 | [&](const VariableDeclStatement* let) { |
| 165 | if (!let->variable->Is<Let>()) { |
Ben Clayton | 42363a5 | 2023-01-12 18:29:07 +0000 | [diff] [blame] | 166 | return; // Not a `let` declaration. Ignore. |
| 167 | } |
| 168 | |
| 169 | auto* var = ctx.src->Sem().Get(let->variable); |
dan sinclair | cedcdf3 | 2023-08-10 02:39:48 +0000 | [diff] [blame] | 170 | if (!var->Type()->Is<core::type::Pointer>()) { |
Ben Clayton | 42363a5 | 2023-01-12 18:29:07 +0000 | [diff] [blame] | 171 | return; // Not a pointer type. Ignore. |
| 172 | } |
| 173 | |
| 174 | // We're dealing with a pointer-typed `let` declaration. |
| 175 | |
| 176 | // Scan the initializer expression for array index expressions that need |
| 177 | // to be hoist to temporary "saved" variables. |
dan sinclair | bae54e7 | 2023-07-28 15:01:54 +0000 | [diff] [blame] | 178 | tint::Vector<const VariableDeclStatement*, 8> saved; |
Ben Clayton | 42363a5 | 2023-01-12 18:29:07 +0000 | [diff] [blame] | 179 | CollectSavedArrayIndices( |
James Price | 2b7406a | 2023-05-12 01:43:50 +0000 | [diff] [blame] | 180 | var->Declaration()->initializer, [&](const Expression* idx_expr) { |
Ben Clayton | 42363a5 | 2023-01-12 18:29:07 +0000 | [diff] [blame] | 181 | // We have a sub-expression that needs to be saved. |
| 182 | // Create a new variable |
| 183 | auto saved_name = ctx.dst->Symbols().New( |
dan sinclair | d026e13 | 2023-04-18 19:38:25 +0000 | [diff] [blame] | 184 | var->Declaration()->name->symbol.Name() + "_save"); |
Ben Clayton | 42363a5 | 2023-01-12 18:29:07 +0000 | [diff] [blame] | 185 | auto* decl = |
| 186 | ctx.dst->Decl(ctx.dst->Let(saved_name, ctx.Clone(idx_expr))); |
| 187 | saved.Push(decl); |
| 188 | // Record the substitution of `idx_expr` to the saved variable |
| 189 | // with the symbol `saved_name`. This will be used by the |
| 190 | // ReplaceAll() handler above. |
| 191 | saved_vars.Add(idx_expr, saved_name); |
| 192 | }); |
| 193 | |
| 194 | // Find the place to insert the saved declarations. |
| 195 | // Special care needs to be made for lets declared as the initializer |
| 196 | // part of for-loops. In this case the block will hold the for-loop |
| 197 | // statement, not the let. |
| 198 | if (!saved.IsEmpty()) { |
| 199 | auto* stmt = ctx.src->Sem().Get(let); |
| 200 | auto* block = stmt->Block(); |
| 201 | // Find the statement owned by the block (either the let decl or a |
| 202 | // for-loop) |
| 203 | while (block != stmt->Parent()) { |
| 204 | stmt = stmt->Parent(); |
| 205 | } |
| 206 | // Declare the stored variables just before stmt. Order here is |
| 207 | // important as order-of-operations needs to be preserved. |
| 208 | // CollectSavedArrayIndices() visits the LHS of an index accessor |
| 209 | // before the index expression. |
| 210 | for (auto* decl : saved) { |
| 211 | // Note that repeated calls to InsertBefore() with the same `before` |
| 212 | // argument will result in nodes to inserted in the order the |
| 213 | // calls are made (last call is inserted last). |
| 214 | ctx.InsertBefore(block->Declaration()->statements, stmt->Declaration(), |
| 215 | decl); |
| 216 | } |
| 217 | } |
| 218 | |
| 219 | // As the original `let` declaration will be fully inlined, there's no |
| 220 | // need for the original declaration to exist. Remove it. |
| 221 | RemoveStatement(ctx, let); |
| 222 | }, |
James Price | 2b7406a | 2023-05-12 01:43:50 +0000 | [diff] [blame] | 223 | [&](const UnaryOpExpression* op) { |
Ben Clayton | 67b461b | 2023-08-08 07:58:19 +0000 | [diff] [blame] | 224 | if (op->op == core::UnaryOp::kAddressOf) { |
Ben Clayton | 42363a5 | 2023-01-12 18:29:07 +0000 | [diff] [blame] | 225 | // Transform can be skipped if no address-of operator is used, as there |
| 226 | // will be no pointers that can be inlined. |
| 227 | needs_transform = true; |
| 228 | } |
| 229 | }); |
| 230 | } |
| 231 | |
| 232 | if (!needs_transform) { |
| 233 | return SkipTransform; |
| 234 | } |
| 235 | |
James Price | 2b7406a | 2023-05-12 01:43:50 +0000 | [diff] [blame] | 236 | // Register the Expression transform handler. |
dan sinclair | 41e4d9a | 2022-05-01 14:40:55 +0000 | [diff] [blame] | 237 | // This performs two different transformations: |
| 238 | // * Identifiers that resolve to the pointer-typed `let` declarations are |
| 239 | // replaced with the recursively inlined initializer expression for the |
| 240 | // `let` declaration. |
| 241 | // * Sub-expressions inside the pointer-typed `let` initializer expression |
| 242 | // that have been hoisted to a saved variable are replaced with the saved |
| 243 | // variable identifier. |
James Price | 2b7406a | 2023-05-12 01:43:50 +0000 | [diff] [blame] | 244 | ctx.ReplaceAll([&](const Expression* expr) -> const Expression* { |
dan sinclair | 41e4d9a | 2022-05-01 14:40:55 +0000 | [diff] [blame] | 245 | // Look to see if we need to swap this Expression with a saved variable. |
Ben Clayton | 7c6e229 | 2022-11-23 21:04:25 +0000 | [diff] [blame] | 246 | if (auto saved_var = saved_vars.Find(expr)) { |
Ben Clayton | c6b3814 | 2022-11-03 08:41:19 +0000 | [diff] [blame] | 247 | return ctx.dst->Expr(*saved_var); |
dan sinclair | 41e4d9a | 2022-05-01 14:40:55 +0000 | [diff] [blame] | 248 | } |
| 249 | |
| 250 | // Reduce the expression, folding away chains of address-of / indirections |
| 251 | auto op = Reduce(expr); |
| 252 | |
| 253 | // Clone the reduced root expression |
| 254 | expr = ctx.CloneWithoutTransform(op.expr); |
| 255 | |
| 256 | // And reapply the minimum number of address-of / indirections |
| 257 | for (int i = 0; i < op.indirections; i++) { |
| 258 | expr = ctx.dst->Deref(expr); |
| 259 | } |
| 260 | for (int i = 0; i > op.indirections; i--) { |
| 261 | expr = ctx.dst->AddressOf(expr); |
| 262 | } |
| 263 | return expr; |
| 264 | }); |
| 265 | |
dan sinclair | 41e4d9a | 2022-05-01 14:40:55 +0000 | [diff] [blame] | 266 | ctx.Clone(); |
Ben Clayton | 915ceca | 2023-07-29 13:12:58 +0000 | [diff] [blame] | 267 | return resolver::Resolve(b); |
dan sinclair | 41e4d9a | 2022-05-01 14:40:55 +0000 | [diff] [blame] | 268 | } |
Ryan Harrison | dbc13af | 2022-02-21 15:19:07 +0000 | [diff] [blame] | 269 | }; |
| 270 | |
| 271 | SimplifyPointers::SimplifyPointers() = default; |
| 272 | |
| 273 | SimplifyPointers::~SimplifyPointers() = default; |
| 274 | |
Ben Clayton | 5ed5cc4 | 2023-09-22 10:31:04 +0000 | [diff] [blame] | 275 | Transform::ApplyResult SimplifyPointers::Apply(const Program& src, const DataMap&, DataMap&) const { |
Ben Clayton | c6b3814 | 2022-11-03 08:41:19 +0000 | [diff] [blame] | 276 | return State(src).Run(); |
Ryan Harrison | dbc13af | 2022-02-21 15:19:07 +0000 | [diff] [blame] | 277 | } |
| 278 | |
James Price | b4acbb8 | 2023-05-11 21:27:16 +0000 | [diff] [blame] | 279 | } // namespace tint::ast::transform |