• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2020 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/transform/robustness.h"
16 
17 #include <algorithm>
18 #include <limits>
19 #include <utility>
20 
21 #include "src/program_builder.h"
22 #include "src/sem/block_statement.h"
23 #include "src/sem/call.h"
24 #include "src/sem/expression.h"
25 #include "src/sem/reference_type.h"
26 #include "src/sem/statement.h"
27 
28 TINT_INSTANTIATE_TYPEINFO(tint::transform::Robustness);
29 TINT_INSTANTIATE_TYPEINFO(tint::transform::Robustness::Config);
30 
31 namespace tint {
32 namespace transform {
33 
34 /// State holds the current transform state
35 struct Robustness::State {
36   /// The clone context
37   CloneContext& ctx;
38 
39   /// Set of storage classes to not apply the transform to
40   std::unordered_set<ast::StorageClass> omitted_classes;
41 
42   /// Applies the transformation state to `ctx`.
Transformtint::transform::Robustness::State43   void Transform() {
44     ctx.ReplaceAll([&](const ast::IndexAccessorExpression* expr) {
45       return Transform(expr);
46     });
47     ctx.ReplaceAll(
48         [&](const ast::CallExpression* expr) { return Transform(expr); });
49   }
50 
51   /// Apply bounds clamping to array, vector and matrix indexing
52   /// @param expr the array, vector or matrix index expression
53   /// @return the clamped replacement expression, or nullptr if `expr` should be
54   /// cloned without changes.
Transformtint::transform::Robustness::State55   const ast::IndexAccessorExpression* Transform(
56       const ast::IndexAccessorExpression* expr) {
57     auto* ret_type = ctx.src->Sem().Get(expr->object)->Type();
58 
59     auto* ref = ret_type->As<sem::Reference>();
60     if (ref && omitted_classes.count(ref->StorageClass()) != 0) {
61       return nullptr;
62     }
63 
64     auto* ret_unwrapped = ret_type->UnwrapRef();
65 
66     ProgramBuilder& b = *ctx.dst;
67     using u32 = ProgramBuilder::u32;
68 
69     struct Value {
70       const ast::Expression* expr = nullptr;  // If null, then is a constant
71       union {
72         uint32_t u32 = 0;  // use if is_signed == false
73         int32_t i32;       // use if is_signed == true
74       };
75       bool is_signed = false;
76     };
77 
78     Value size;              // size of the array, vector or matrix
79     size.is_signed = false;  // size is always unsigned
80     if (auto* vec = ret_unwrapped->As<sem::Vector>()) {
81       size.u32 = vec->Width();
82 
83     } else if (auto* arr = ret_unwrapped->As<sem::Array>()) {
84       size.u32 = arr->Count();
85     } else if (auto* mat = ret_unwrapped->As<sem::Matrix>()) {
86       // The row accessor would have been an embedded index accessor and already
87       // handled, so we just need to do columns here.
88       size.u32 = mat->columns();
89     } else {
90       return nullptr;
91     }
92 
93     if (size.u32 == 0) {
94       if (!ret_unwrapped->Is<sem::Array>()) {
95         b.Diagnostics().add_error(diag::System::Transform,
96                                   "invalid 0 sized non-array", expr->source);
97         return nullptr;
98       }
99       // Runtime sized array
100       auto* arr = ctx.Clone(expr->object);
101       size.expr = b.Call("arrayLength", b.AddressOf(arr));
102     }
103 
104     // Calculate the maximum possible index value (size-1u)
105     // Size must be positive (non-zero), so we can safely subtract 1 here
106     // without underflow.
107     Value limit;
108     limit.is_signed = false;  // Like size, limit is always unsigned.
109     if (size.expr) {
110       // Dynamic size
111       limit.expr = b.Sub(size.expr, 1u);
112     } else {
113       // Constant size
114       limit.u32 = size.u32 - 1u;
115     }
116 
117     Value idx;  // index value
118 
119     auto* idx_sem = ctx.src->Sem().Get(expr->index);
120     auto* idx_ty = idx_sem->Type()->UnwrapRef();
121     if (!idx_ty->IsAnyOf<sem::I32, sem::U32>()) {
122       TINT_ICE(Transform, b.Diagnostics())
123           << "index must be u32 or i32, got " << idx_sem->Type()->type_name();
124       return nullptr;
125     }
126 
127     if (auto idx_constant = idx_sem->ConstantValue()) {
128       // Constant value index
129       if (idx_constant.Type()->Is<sem::I32>()) {
130         idx.i32 = idx_constant.Elements()[0].i32;
131         idx.is_signed = true;
132       } else if (idx_constant.Type()->Is<sem::U32>()) {
133         idx.u32 = idx_constant.Elements()[0].u32;
134         idx.is_signed = false;
135       } else {
136         b.Diagnostics().add_error(diag::System::Transform,
137                                   "unsupported constant value for accessor: " +
138                                       idx_constant.Type()->type_name(),
139                                   expr->source);
140         return nullptr;
141       }
142     } else {
143       // Dynamic value index
144       idx.expr = ctx.Clone(expr->index);
145       idx.is_signed = idx_ty->Is<sem::I32>();
146     }
147 
148     // Clamp the index so that it cannot exceed limit.
149     if (idx.expr || limit.expr) {
150       // One of, or both of idx and limit are non-constant.
151 
152       // If the index is signed, cast it to a u32 (with clamping if constant).
153       if (idx.is_signed) {
154         if (idx.expr) {
155           // We don't use a max(idx, 0) here, as that incurs a runtime
156           // performance cost, and if the unsigned value will be clamped by
157           // limit, resulting in a value between [0..limit)
158           idx.expr = b.Construct<u32>(idx.expr);
159           idx.is_signed = false;
160         } else {
161           idx.u32 = static_cast<uint32_t>(std::max(idx.i32, 0));
162           idx.is_signed = false;
163         }
164       }
165 
166       // Convert idx and limit to expressions, so we can emit `min(idx, limit)`.
167       if (!idx.expr) {
168         idx.expr = b.Expr(idx.u32);
169       }
170       if (!limit.expr) {
171         limit.expr = b.Expr(limit.u32);
172       }
173 
174       // Perform the clamp with `min(idx, limit)`
175       idx.expr = b.Call("min", idx.expr, limit.expr);
176     } else {
177       // Both idx and max are constant.
178       if (idx.is_signed) {
179         // The index is signed. Calculate limit as signed.
180         int32_t signed_limit = static_cast<int32_t>(
181             std::min<uint32_t>(limit.u32, std::numeric_limits<int32_t>::max()));
182         idx.i32 = std::max(idx.i32, 0);
183         idx.i32 = std::min(idx.i32, signed_limit);
184       } else {
185         // The index is unsigned.
186         idx.u32 = std::min(idx.u32, limit.u32);
187       }
188     }
189 
190     // Convert idx to an expression, so we can emit the new accessor.
191     if (!idx.expr) {
192       idx.expr = idx.is_signed
193                      ? static_cast<const ast::Expression*>(b.Expr(idx.i32))
194                      : static_cast<const ast::Expression*>(b.Expr(idx.u32));
195     }
196 
197     // Clone arguments outside of create() call to have deterministic ordering
198     auto src = ctx.Clone(expr->source);
199     auto* obj = ctx.Clone(expr->object);
200     return b.IndexAccessor(src, obj, idx.expr);
201   }
202 
203   /// @param type intrinsic type
204   /// @returns true if the given intrinsic is a texture function that requires
205   /// argument clamping,
TextureIntrinsicNeedsClampingtint::transform::Robustness::State206   bool TextureIntrinsicNeedsClamping(sem::IntrinsicType type) {
207     return type == sem::IntrinsicType::kTextureLoad ||
208            type == sem::IntrinsicType::kTextureStore;
209   }
210 
211   /// Apply bounds clamping to the coordinates, array index and level arguments
212   /// of the `textureLoad()` and `textureStore()` intrinsics.
213   /// @param expr the intrinsic call expression
214   /// @return the clamped replacement call expression, or nullptr if `expr`
215   /// should be cloned without changes.
Transformtint::transform::Robustness::State216   const ast::CallExpression* Transform(const ast::CallExpression* expr) {
217     auto* call = ctx.src->Sem().Get(expr);
218     auto* call_target = call->Target();
219     auto* intrinsic = call_target->As<sem::Intrinsic>();
220     if (!intrinsic || !TextureIntrinsicNeedsClamping(intrinsic->Type())) {
221       return nullptr;  // No transform, just clone.
222     }
223 
224     ProgramBuilder& b = *ctx.dst;
225 
226     // Indices of the mandatory texture and coords parameters, and the optional
227     // array and level parameters.
228     auto& signature = intrinsic->Signature();
229     auto texture_idx = signature.IndexOf(sem::ParameterUsage::kTexture);
230     auto coords_idx = signature.IndexOf(sem::ParameterUsage::kCoords);
231     auto array_idx = signature.IndexOf(sem::ParameterUsage::kArrayIndex);
232     auto level_idx = signature.IndexOf(sem::ParameterUsage::kLevel);
233 
234     auto* texture_arg = expr->args[texture_idx];
235     auto* coords_arg = expr->args[coords_idx];
236     auto* coords_ty = intrinsic->Parameters()[coords_idx]->Type();
237 
238     // If the level is provided, then we need to clamp this. As the level is
239     // used by textureDimensions() and the texture[Load|Store]() calls, we need
240     // to clamp both usages.
241     // TODO(bclayton): We probably want to place this into a let so that the
242     // calculation can be reused. This is fiddly to get right.
243     std::function<const ast::Expression*()> level_arg;
244     if (level_idx >= 0) {
245       level_arg = [&] {
246         auto* arg = expr->args[level_idx];
247         auto* num_levels = b.Call("textureNumLevels", ctx.Clone(texture_arg));
248         auto* zero = b.Expr(0);
249         auto* max = ctx.dst->Sub(num_levels, 1);
250         auto* clamped = b.Call("clamp", ctx.Clone(arg), zero, max);
251         return clamped;
252       };
253     }
254 
255     // Clamp the coordinates argument
256     {
257       auto* texture_dims =
258           level_arg
259               ? b.Call("textureDimensions", ctx.Clone(texture_arg), level_arg())
260               : b.Call("textureDimensions", ctx.Clone(texture_arg));
261       auto* zero = b.Construct(CreateASTTypeFor(ctx, coords_ty));
262       auto* max = ctx.dst->Sub(
263           texture_dims, b.Construct(CreateASTTypeFor(ctx, coords_ty), 1));
264       auto* clamped_coords = b.Call("clamp", ctx.Clone(coords_arg), zero, max);
265       ctx.Replace(coords_arg, clamped_coords);
266     }
267 
268     // Clamp the array_index argument, if provided
269     if (array_idx >= 0) {
270       auto* arg = expr->args[array_idx];
271       auto* num_layers = b.Call("textureNumLayers", ctx.Clone(texture_arg));
272       auto* zero = b.Expr(0);
273       auto* max = ctx.dst->Sub(num_layers, 1);
274       auto* clamped = b.Call("clamp", ctx.Clone(arg), zero, max);
275       ctx.Replace(arg, clamped);
276     }
277 
278     // Clamp the level argument, if provided
279     if (level_idx >= 0) {
280       auto* arg = expr->args[level_idx];
281       ctx.Replace(arg, level_arg ? level_arg() : ctx.dst->Expr(0));
282     }
283 
284     return nullptr;  // Clone, which will use the argument replacements above.
285   }
286 };
287 
288 Robustness::Config::Config() = default;
289 Robustness::Config::Config(const Config&) = default;
290 Robustness::Config::~Config() = default;
291 Robustness::Config& Robustness::Config::operator=(const Config&) = default;
292 
293 Robustness::Robustness() = default;
294 Robustness::~Robustness() = default;
295 
Run(CloneContext & ctx,const DataMap & inputs,DataMap &)296 void Robustness::Run(CloneContext& ctx, const DataMap& inputs, DataMap&) {
297   Config cfg;
298   if (auto* cfg_data = inputs.Get<Config>()) {
299     cfg = *cfg_data;
300   }
301 
302   std::unordered_set<ast::StorageClass> omitted_classes;
303   for (auto sc : cfg.omitted_classes) {
304     switch (sc) {
305       case StorageClass::kUniform:
306         omitted_classes.insert(ast::StorageClass::kUniform);
307         break;
308       case StorageClass::kStorage:
309         omitted_classes.insert(ast::StorageClass::kStorage);
310         break;
311     }
312   }
313 
314   State state{ctx, std::move(omitted_classes)};
315 
316   state.Transform();
317   ctx.Clone();
318 }
319 
320 }  // namespace transform
321 }  // namespace tint
322