Ryan Harrison | dbc13af | 2022-02-21 15:19:07 +0000 | [diff] [blame] | 1 | // Copyright 2021 The Tint Authors. |
| 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | // you may not use this file except in compliance with the License. |
| 5 | // You may obtain a copy of the License at |
| 6 | // |
| 7 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | // |
| 9 | // Unless required by applicable law or agreed to in writing, software |
| 10 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | // See the License for the specific language governing permissions and |
| 13 | // limitations under the License. |
| 14 | |
| 15 | #include "src/tint/transform/calculate_array_length.h" |
| 16 | |
| 17 | #include <unordered_map> |
| 18 | #include <utility> |
| 19 | |
| 20 | #include "src/tint/ast/call_statement.h" |
| 21 | #include "src/tint/ast/disable_validation_attribute.h" |
| 22 | #include "src/tint/program_builder.h" |
| 23 | #include "src/tint/sem/block_statement.h" |
| 24 | #include "src/tint/sem/call.h" |
| 25 | #include "src/tint/sem/function.h" |
| 26 | #include "src/tint/sem/statement.h" |
| 27 | #include "src/tint/sem/struct.h" |
| 28 | #include "src/tint/sem/variable.h" |
Ben Clayton | 23946b3 | 2023-03-09 16:50:19 +0000 | [diff] [blame] | 29 | #include "src/tint/switch.h" |
Ryan Harrison | dbc13af | 2022-02-21 15:19:07 +0000 | [diff] [blame] | 30 | #include "src/tint/transform/simplify_pointers.h" |
dan sinclair | 4d56b48 | 2022-12-08 17:50:50 +0000 | [diff] [blame] | 31 | #include "src/tint/type/reference.h" |
Ryan Harrison | dbc13af | 2022-02-21 15:19:07 +0000 | [diff] [blame] | 32 | #include "src/tint/utils/hash.h" |
| 33 | #include "src/tint/utils/map.h" |
| 34 | |
| 35 | TINT_INSTANTIATE_TYPEINFO(tint::transform::CalculateArrayLength); |
dan sinclair | 41e4d9a | 2022-05-01 14:40:55 +0000 | [diff] [blame] | 36 | TINT_INSTANTIATE_TYPEINFO(tint::transform::CalculateArrayLength::BufferSizeIntrinsic); |
Ryan Harrison | dbc13af | 2022-02-21 15:19:07 +0000 | [diff] [blame] | 37 | |
Ben Clayton | 0ce9ab0 | 2022-05-05 20:23:40 +0000 | [diff] [blame] | 38 | using namespace tint::number_suffixes; // NOLINT |
| 39 | |
dan sinclair | b5599d3 | 2022-04-07 16:55:14 +0000 | [diff] [blame] | 40 | namespace tint::transform { |
Ryan Harrison | dbc13af | 2022-02-21 15:19:07 +0000 | [diff] [blame] | 41 | |
| 42 | namespace { |
| 43 | |
Ben Clayton | c6b3814 | 2022-11-03 08:41:19 +0000 | [diff] [blame] | 44 | bool ShouldRun(const Program* program) { |
| 45 | for (auto* fn : program->AST().Functions()) { |
| 46 | if (auto* sem_fn = program->Sem().Get(fn)) { |
| 47 | for (auto* builtin : sem_fn->DirectlyCalledBuiltins()) { |
dan sinclair | 9543f74 | 2023-03-09 01:20:16 +0000 | [diff] [blame] | 48 | if (builtin->Type() == builtin::Function::kArrayLength) { |
Ben Clayton | c6b3814 | 2022-11-03 08:41:19 +0000 | [diff] [blame] | 49 | return true; |
| 50 | } |
| 51 | } |
| 52 | } |
| 53 | } |
| 54 | return false; |
| 55 | } |
| 56 | |
Ryan Harrison | dbc13af | 2022-02-21 15:19:07 +0000 | [diff] [blame] | 57 | /// ArrayUsage describes a runtime array usage. |
| 58 | /// It is used as a key by the array_length_by_usage map. |
| 59 | struct ArrayUsage { |
dan sinclair | 41e4d9a | 2022-05-01 14:40:55 +0000 | [diff] [blame] | 60 | ast::BlockStatement const* const block; |
| 61 | sem::Variable const* const buffer; |
| 62 | bool operator==(const ArrayUsage& rhs) const { |
| 63 | return block == rhs.block && buffer == rhs.buffer; |
Ryan Harrison | dbc13af | 2022-02-21 15:19:07 +0000 | [diff] [blame] | 64 | } |
dan sinclair | 41e4d9a | 2022-05-01 14:40:55 +0000 | [diff] [blame] | 65 | struct Hasher { |
| 66 | inline std::size_t operator()(const ArrayUsage& u) const { |
| 67 | return utils::Hash(u.block, u.buffer); |
| 68 | } |
| 69 | }; |
Ryan Harrison | dbc13af | 2022-02-21 15:19:07 +0000 | [diff] [blame] | 70 | }; |
| 71 | |
| 72 | } // namespace |
| 73 | |
Ben Clayton | 4a92a3c | 2022-07-18 20:50:02 +0000 | [diff] [blame] | 74 | CalculateArrayLength::BufferSizeIntrinsic::BufferSizeIntrinsic(ProgramID pid, ast::NodeID nid) |
Ben Clayton | 63d0fab | 2023-03-06 15:43:16 +0000 | [diff] [blame] | 75 | : Base(pid, nid, utils::Empty) {} |
Ryan Harrison | dbc13af | 2022-02-21 15:19:07 +0000 | [diff] [blame] | 76 | CalculateArrayLength::BufferSizeIntrinsic::~BufferSizeIntrinsic() = default; |
| 77 | std::string CalculateArrayLength::BufferSizeIntrinsic::InternalName() const { |
dan sinclair | 41e4d9a | 2022-05-01 14:40:55 +0000 | [diff] [blame] | 78 | return "intrinsic_buffer_size"; |
Ryan Harrison | dbc13af | 2022-02-21 15:19:07 +0000 | [diff] [blame] | 79 | } |
| 80 | |
dan sinclair | 41e4d9a | 2022-05-01 14:40:55 +0000 | [diff] [blame] | 81 | const CalculateArrayLength::BufferSizeIntrinsic* CalculateArrayLength::BufferSizeIntrinsic::Clone( |
| 82 | CloneContext* ctx) const { |
Ben Clayton | 4a92a3c | 2022-07-18 20:50:02 +0000 | [diff] [blame] | 83 | return ctx->dst->ASTNodes().Create<CalculateArrayLength::BufferSizeIntrinsic>( |
| 84 | ctx->dst->ID(), ctx->dst->AllocateNodeID()); |
Ryan Harrison | dbc13af | 2022-02-21 15:19:07 +0000 | [diff] [blame] | 85 | } |
| 86 | |
| 87 | CalculateArrayLength::CalculateArrayLength() = default; |
| 88 | CalculateArrayLength::~CalculateArrayLength() = default; |
| 89 | |
Ben Clayton | c6b3814 | 2022-11-03 08:41:19 +0000 | [diff] [blame] | 90 | Transform::ApplyResult CalculateArrayLength::Apply(const Program* src, |
| 91 | const DataMap&, |
| 92 | DataMap&) const { |
| 93 | if (!ShouldRun(src)) { |
| 94 | return SkipTransform; |
Ryan Harrison | dbc13af | 2022-02-21 15:19:07 +0000 | [diff] [blame] | 95 | } |
Ryan Harrison | dbc13af | 2022-02-21 15:19:07 +0000 | [diff] [blame] | 96 | |
Ben Clayton | c6b3814 | 2022-11-03 08:41:19 +0000 | [diff] [blame] | 97 | ProgramBuilder b; |
| 98 | CloneContext ctx{&b, src, /* auto_clone_symbols */ true}; |
| 99 | auto& sem = src->Sem(); |
Ryan Harrison | dbc13af | 2022-02-21 15:19:07 +0000 | [diff] [blame] | 100 | |
dan sinclair | 41e4d9a | 2022-05-01 14:40:55 +0000 | [diff] [blame] | 101 | // get_buffer_size_intrinsic() emits the function decorated with |
| 102 | // BufferSizeIntrinsic that is transformed by the HLSL writer into a call to |
| 103 | // [RW]ByteAddressBuffer.GetDimensions(). |
dan sinclair | 4d56b48 | 2022-12-08 17:50:50 +0000 | [diff] [blame] | 104 | std::unordered_map<const type::Reference*, Symbol> buffer_size_intrinsics; |
| 105 | auto get_buffer_size_intrinsic = [&](const type::Reference* buffer_type) { |
dan sinclair | 41e4d9a | 2022-05-01 14:40:55 +0000 | [diff] [blame] | 106 | return utils::GetOrCreate(buffer_size_intrinsics, buffer_type, [&] { |
Ben Clayton | c6b3814 | 2022-11-03 08:41:19 +0000 | [diff] [blame] | 107 | auto name = b.Sym(); |
Ben Clayton | 971318f | 2023-02-14 13:52:43 +0000 | [diff] [blame] | 108 | auto type = CreateASTTypeFor(ctx, buffer_type); |
Ben Clayton | c6b3814 | 2022-11-03 08:41:19 +0000 | [diff] [blame] | 109 | auto* disable_validation = b.Disable(ast::DisabledValidation::kFunctionParameter); |
Ben Clayton | 971318f | 2023-02-14 13:52:43 +0000 | [diff] [blame] | 110 | b.Func( |
| 111 | name, |
Ben Clayton | 783b169 | 2022-08-02 17:03:35 +0000 | [diff] [blame] | 112 | utils::Vector{ |
Ben Clayton | c6b3814 | 2022-11-03 08:41:19 +0000 | [diff] [blame] | 113 | b.Param("buffer", |
| 114 | b.ty.pointer(type, buffer_type->AddressSpace(), buffer_type->Access()), |
| 115 | utils::Vector{disable_validation}), |
dan sinclair | 2a65163 | 2023-02-19 04:03:55 +0000 | [diff] [blame] | 116 | b.Param("result", b.ty.pointer(b.ty.u32(), builtin::AddressSpace::kFunction)), |
dan sinclair | 41e4d9a | 2022-05-01 14:40:55 +0000 | [diff] [blame] | 117 | }, |
Ben Clayton | c6b3814 | 2022-11-03 08:41:19 +0000 | [diff] [blame] | 118 | b.ty.void_(), nullptr, |
Ben Clayton | 783b169 | 2022-08-02 17:03:35 +0000 | [diff] [blame] | 119 | utils::Vector{ |
Ben Clayton | c6b3814 | 2022-11-03 08:41:19 +0000 | [diff] [blame] | 120 | b.ASTNodes().Create<BufferSizeIntrinsic>(b.ID(), b.AllocateNodeID()), |
Ben Clayton | 971318f | 2023-02-14 13:52:43 +0000 | [diff] [blame] | 121 | }); |
Ryan Harrison | dbc13af | 2022-02-21 15:19:07 +0000 | [diff] [blame] | 122 | |
dan sinclair | 41e4d9a | 2022-05-01 14:40:55 +0000 | [diff] [blame] | 123 | return name; |
| 124 | }); |
| 125 | }; |
Ryan Harrison | dbc13af | 2022-02-21 15:19:07 +0000 | [diff] [blame] | 126 | |
dan sinclair | 41e4d9a | 2022-05-01 14:40:55 +0000 | [diff] [blame] | 127 | std::unordered_map<ArrayUsage, Symbol, ArrayUsage::Hasher> array_length_by_usage; |
Ryan Harrison | dbc13af | 2022-02-21 15:19:07 +0000 | [diff] [blame] | 128 | |
dan sinclair | 41e4d9a | 2022-05-01 14:40:55 +0000 | [diff] [blame] | 129 | // Find all the arrayLength() calls... |
Ben Clayton | c6b3814 | 2022-11-03 08:41:19 +0000 | [diff] [blame] | 130 | for (auto* node : src->ASTNodes().Objects()) { |
dan sinclair | 41e4d9a | 2022-05-01 14:40:55 +0000 | [diff] [blame] | 131 | if (auto* call_expr = node->As<ast::CallExpression>()) { |
Ben Clayton | e5a67ac | 2022-05-19 21:50:59 +0000 | [diff] [blame] | 132 | auto* call = sem.Get(call_expr)->UnwrapMaterialize()->As<sem::Call>(); |
dan sinclair | 41e4d9a | 2022-05-01 14:40:55 +0000 | [diff] [blame] | 133 | if (auto* builtin = call->Target()->As<sem::Builtin>()) { |
dan sinclair | 9543f74 | 2023-03-09 01:20:16 +0000 | [diff] [blame] | 134 | if (builtin->Type() == builtin::Function::kArrayLength) { |
dan sinclair | 41e4d9a | 2022-05-01 14:40:55 +0000 | [diff] [blame] | 135 | // We're dealing with an arrayLength() call |
Ryan Harrison | dbc13af | 2022-02-21 15:19:07 +0000 | [diff] [blame] | 136 | |
Ben Clayton | 4b70776 | 2022-09-09 20:42:29 +0000 | [diff] [blame] | 137 | if (auto* call_stmt = call->Stmt()->Declaration()->As<ast::CallStatement>()) { |
| 138 | if (call_stmt->expr == call_expr) { |
| 139 | // arrayLength() is used as a statement. |
| 140 | // The argument expression must be side-effect free, so just drop the |
| 141 | // statement. |
| 142 | RemoveStatement(ctx, call_stmt); |
| 143 | continue; |
| 144 | } |
| 145 | } |
| 146 | |
Ben Clayton | 2032d03 | 2022-06-15 19:32:37 +0000 | [diff] [blame] | 147 | // A runtime-sized array can only appear as the store type of a variable, or the |
| 148 | // last element of a structure (which cannot itself be nested). Given that we |
| 149 | // require SimplifyPointers, we can assume that the arrayLength() call has one |
| 150 | // of two forms: |
dan sinclair | 41e4d9a | 2022-05-01 14:40:55 +0000 | [diff] [blame] | 151 | // arrayLength(&struct_var.array_member) |
| 152 | // arrayLength(&array_var) |
| 153 | auto* arg = call_expr->args[0]; |
| 154 | auto* address_of = arg->As<ast::UnaryOpExpression>(); |
Ben Clayton | 884f952 | 2023-01-12 22:52:57 +0000 | [diff] [blame] | 155 | if (TINT_UNLIKELY(!address_of || address_of->op != ast::UnaryOp::kAddressOf)) { |
Ben Clayton | c6b3814 | 2022-11-03 08:41:19 +0000 | [diff] [blame] | 156 | TINT_ICE(Transform, b.Diagnostics()) |
dan sinclair | 41e4d9a | 2022-05-01 14:40:55 +0000 | [diff] [blame] | 157 | << "arrayLength() expected address-of, got " << arg->TypeInfo().name; |
| 158 | } |
| 159 | auto* storage_buffer_expr = address_of->expr; |
| 160 | if (auto* accessor = storage_buffer_expr->As<ast::MemberAccessorExpression>()) { |
Ben Clayton | ad31565 | 2023-02-05 12:36:50 +0000 | [diff] [blame] | 161 | storage_buffer_expr = accessor->object; |
dan sinclair | 41e4d9a | 2022-05-01 14:40:55 +0000 | [diff] [blame] | 162 | } |
| 163 | auto* storage_buffer_sem = sem.Get<sem::VariableUser>(storage_buffer_expr); |
Ben Clayton | 884f952 | 2023-01-12 22:52:57 +0000 | [diff] [blame] | 164 | if (TINT_UNLIKELY(!storage_buffer_sem)) { |
Ben Clayton | c6b3814 | 2022-11-03 08:41:19 +0000 | [diff] [blame] | 165 | TINT_ICE(Transform, b.Diagnostics()) |
dan sinclair | 41e4d9a | 2022-05-01 14:40:55 +0000 | [diff] [blame] | 166 | << "expected form of arrayLength argument to be &array_var or " |
| 167 | "&struct_var.array_member"; |
| 168 | break; |
| 169 | } |
| 170 | auto* storage_buffer_var = storage_buffer_sem->Variable(); |
dan sinclair | 4d56b48 | 2022-12-08 17:50:50 +0000 | [diff] [blame] | 171 | auto* storage_buffer_type = storage_buffer_sem->Type()->As<type::Reference>(); |
Ryan Harrison | dbc13af | 2022-02-21 15:19:07 +0000 | [diff] [blame] | 172 | |
Ben Clayton | 2032d03 | 2022-06-15 19:32:37 +0000 | [diff] [blame] | 173 | // Generate BufferSizeIntrinsic for this storage type if we haven't already |
dan sinclair | 41e4d9a | 2022-05-01 14:40:55 +0000 | [diff] [blame] | 174 | auto buffer_size = get_buffer_size_intrinsic(storage_buffer_type); |
Ryan Harrison | dbc13af | 2022-02-21 15:19:07 +0000 | [diff] [blame] | 175 | |
dan sinclair | 41e4d9a | 2022-05-01 14:40:55 +0000 | [diff] [blame] | 176 | // Find the current statement block |
| 177 | auto* block = call->Stmt()->Block()->Declaration(); |
Ryan Harrison | dbc13af | 2022-02-21 15:19:07 +0000 | [diff] [blame] | 178 | |
dan sinclair | 41e4d9a | 2022-05-01 14:40:55 +0000 | [diff] [blame] | 179 | auto array_length = |
| 180 | utils::GetOrCreate(array_length_by_usage, {block, storage_buffer_var}, [&] { |
| 181 | // First time this array length is used for this block. |
| 182 | // Let's calculate it. |
Ryan Harrison | dbc13af | 2022-02-21 15:19:07 +0000 | [diff] [blame] | 183 | |
dan sinclair | 41e4d9a | 2022-05-01 14:40:55 +0000 | [diff] [blame] | 184 | // Construct the variable that'll hold the result of |
| 185 | // RWByteAddressBuffer.GetDimensions() |
Ben Clayton | c6b3814 | 2022-11-03 08:41:19 +0000 | [diff] [blame] | 186 | auto* buffer_size_result = |
| 187 | b.Decl(b.Var(b.Sym(), b.ty.u32(), b.Expr(0_u))); |
Ryan Harrison | dbc13af | 2022-02-21 15:19:07 +0000 | [diff] [blame] | 188 | |
dan sinclair | 41e4d9a | 2022-05-01 14:40:55 +0000 | [diff] [blame] | 189 | // Call storage_buffer.GetDimensions(&buffer_size_result) |
Ben Clayton | c6b3814 | 2022-11-03 08:41:19 +0000 | [diff] [blame] | 190 | auto* call_get_dims = b.CallStmt(b.Call( |
dan sinclair | 41e4d9a | 2022-05-01 14:40:55 +0000 | [diff] [blame] | 191 | // BufferSizeIntrinsic(X, ARGS...) is |
| 192 | // translated to: |
| 193 | // X.GetDimensions(ARGS..) by the writer |
Ben Clayton | c6b3814 | 2022-11-03 08:41:19 +0000 | [diff] [blame] | 194 | buffer_size, b.AddressOf(ctx.Clone(storage_buffer_expr)), |
Ben Clayton | 651d9e2 | 2023-02-09 10:34:14 +0000 | [diff] [blame] | 195 | b.AddressOf(b.Expr(buffer_size_result->variable->name->symbol)))); |
Ryan Harrison | dbc13af | 2022-02-21 15:19:07 +0000 | [diff] [blame] | 196 | |
dan sinclair | 41e4d9a | 2022-05-01 14:40:55 +0000 | [diff] [blame] | 197 | // Calculate actual array length |
| 198 | // total_storage_buffer_size - array_offset |
| 199 | // array_length = ---------------------------------------- |
| 200 | // array_stride |
Ben Clayton | c6b3814 | 2022-11-03 08:41:19 +0000 | [diff] [blame] | 201 | auto name = b.Sym(); |
dan sinclair | 41e4d9a | 2022-05-01 14:40:55 +0000 | [diff] [blame] | 202 | const ast::Expression* total_size = |
Ben Clayton | c6b3814 | 2022-11-03 08:41:19 +0000 | [diff] [blame] | 203 | b.Expr(buffer_size_result->variable); |
Ben Clayton | 2032d03 | 2022-06-15 19:32:37 +0000 | [diff] [blame] | 204 | |
dan sinclair | 946858a | 2022-12-08 22:21:24 +0000 | [diff] [blame] | 205 | const type::Array* array_type = Switch( |
Ben Clayton | 2032d03 | 2022-06-15 19:32:37 +0000 | [diff] [blame] | 206 | storage_buffer_type->StoreType(), |
| 207 | [&](const sem::Struct* str) { |
| 208 | // The variable is a struct, so subtract the byte offset of |
| 209 | // the array member. |
dan sinclair | ad9cd0a | 2022-12-06 20:01:54 +0000 | [diff] [blame] | 210 | auto* array_member_sem = str->Members().Back(); |
Ben Clayton | c6b3814 | 2022-11-03 08:41:19 +0000 | [diff] [blame] | 211 | total_size = b.Sub(total_size, u32(array_member_sem->Offset())); |
dan sinclair | 946858a | 2022-12-08 22:21:24 +0000 | [diff] [blame] | 212 | return array_member_sem->Type()->As<type::Array>(); |
Ben Clayton | 2032d03 | 2022-06-15 19:32:37 +0000 | [diff] [blame] | 213 | }, |
dan sinclair | 946858a | 2022-12-08 22:21:24 +0000 | [diff] [blame] | 214 | [&](const type::Array* arr) { return arr; }); |
Ben Clayton | 2032d03 | 2022-06-15 19:32:37 +0000 | [diff] [blame] | 215 | |
Ben Clayton | 884f952 | 2023-01-12 22:52:57 +0000 | [diff] [blame] | 216 | if (TINT_UNLIKELY(!array_type)) { |
Ben Clayton | c6b3814 | 2022-11-03 08:41:19 +0000 | [diff] [blame] | 217 | TINT_ICE(Transform, b.Diagnostics()) |
dan sinclair | 41e4d9a | 2022-05-01 14:40:55 +0000 | [diff] [blame] | 218 | << "expected form of arrayLength argument to be " |
| 219 | "&array_var or &struct_var.array_member"; |
| 220 | return name; |
| 221 | } |
Ben Clayton | 2032d03 | 2022-06-15 19:32:37 +0000 | [diff] [blame] | 222 | |
dan sinclair | 41e4d9a | 2022-05-01 14:40:55 +0000 | [diff] [blame] | 223 | uint32_t array_stride = array_type->Size(); |
Ben Clayton | c6b3814 | 2022-11-03 08:41:19 +0000 | [diff] [blame] | 224 | auto* array_length_var = b.Decl( |
| 225 | b.Let(name, b.ty.u32(), b.Div(total_size, u32(array_stride)))); |
dan sinclair | 41e4d9a | 2022-05-01 14:40:55 +0000 | [diff] [blame] | 226 | |
| 227 | // Insert the array length calculations at the top of the block |
| 228 | ctx.InsertBefore(block->statements, block->statements[0], |
| 229 | buffer_size_result); |
| 230 | ctx.InsertBefore(block->statements, block->statements[0], |
| 231 | call_get_dims); |
| 232 | ctx.InsertBefore(block->statements, block->statements[0], |
| 233 | array_length_var); |
| 234 | return name; |
| 235 | }); |
| 236 | |
| 237 | // Replace the call to arrayLength() with the array length variable |
Ben Clayton | c6b3814 | 2022-11-03 08:41:19 +0000 | [diff] [blame] | 238 | ctx.Replace(call_expr, b.Expr(array_length)); |
Ryan Harrison | dbc13af | 2022-02-21 15:19:07 +0000 | [diff] [blame] | 239 | } |
dan sinclair | 41e4d9a | 2022-05-01 14:40:55 +0000 | [diff] [blame] | 240 | } |
Ryan Harrison | dbc13af | 2022-02-21 15:19:07 +0000 | [diff] [blame] | 241 | } |
Ryan Harrison | dbc13af | 2022-02-21 15:19:07 +0000 | [diff] [blame] | 242 | } |
Ryan Harrison | dbc13af | 2022-02-21 15:19:07 +0000 | [diff] [blame] | 243 | |
dan sinclair | 41e4d9a | 2022-05-01 14:40:55 +0000 | [diff] [blame] | 244 | ctx.Clone(); |
Ben Clayton | c6b3814 | 2022-11-03 08:41:19 +0000 | [diff] [blame] | 245 | return Program(std::move(b)); |
Ryan Harrison | dbc13af | 2022-02-21 15:19:07 +0000 | [diff] [blame] | 246 | } |
| 247 | |
dan sinclair | b5599d3 | 2022-04-07 16:55:14 +0000 | [diff] [blame] | 248 | } // namespace tint::transform |