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/transform/binding_remapper.h"
16
17 #include <string>
18 #include <unordered_set>
19 #include <utility>
20
21 #include "src/ast/disable_validation_decoration.h"
22 #include "src/program_builder.h"
23 #include "src/sem/function.h"
24 #include "src/sem/variable.h"
25
26 TINT_INSTANTIATE_TYPEINFO(tint::transform::BindingRemapper);
27 TINT_INSTANTIATE_TYPEINFO(tint::transform::BindingRemapper::Remappings);
28
29 namespace tint {
30 namespace transform {
31
Remappings(BindingPoints bp,AccessControls ac,bool may_collide)32 BindingRemapper::Remappings::Remappings(BindingPoints bp,
33 AccessControls ac,
34 bool may_collide)
35 : binding_points(std::move(bp)),
36 access_controls(std::move(ac)),
37 allow_collisions(may_collide) {}
38
39 BindingRemapper::Remappings::Remappings(const Remappings&) = default;
40 BindingRemapper::Remappings::~Remappings() = default;
41
42 BindingRemapper::BindingRemapper() = default;
43 BindingRemapper::~BindingRemapper() = default;
44
Run(CloneContext & ctx,const DataMap & inputs,DataMap &)45 void BindingRemapper::Run(CloneContext& ctx, const DataMap& inputs, DataMap&) {
46 auto* remappings = inputs.Get<Remappings>();
47 if (!remappings) {
48 ctx.dst->Diagnostics().add_error(
49 diag::System::Transform,
50 "missing transform data for " + std::string(TypeInfo().name));
51 return;
52 }
53
54 // A set of post-remapped binding points that need to be decorated with a
55 // DisableValidationDecoration to disable binding-point-collision validation
56 std::unordered_set<sem::BindingPoint> add_collision_deco;
57
58 if (remappings->allow_collisions) {
59 // Scan for binding point collisions generated by this transform.
60 // Populate all collisions in the `add_collision_deco` set.
61 for (auto* func_ast : ctx.src->AST().Functions()) {
62 if (!func_ast->IsEntryPoint()) {
63 continue;
64 }
65 auto* func = ctx.src->Sem().Get(func_ast);
66 std::unordered_map<sem::BindingPoint, int> binding_point_counts;
67 for (auto* var : func->TransitivelyReferencedGlobals()) {
68 if (auto binding_point = var->Declaration()->BindingPoint()) {
69 BindingPoint from{binding_point.group->value,
70 binding_point.binding->value};
71 auto bp_it = remappings->binding_points.find(from);
72 if (bp_it != remappings->binding_points.end()) {
73 // Remapped
74 BindingPoint to = bp_it->second;
75 if (binding_point_counts[to]++) {
76 add_collision_deco.emplace(to);
77 }
78 } else {
79 // No remapping
80 if (binding_point_counts[from]++) {
81 add_collision_deco.emplace(from);
82 }
83 }
84 }
85 }
86 }
87 }
88
89 for (auto* var : ctx.src->AST().GlobalVariables()) {
90 if (auto binding_point = var->BindingPoint()) {
91 // The original binding point
92 BindingPoint from{binding_point.group->value,
93 binding_point.binding->value};
94
95 // The binding point after remapping
96 BindingPoint bp = from;
97
98 // Replace any group or binding decorations.
99 // Note: This has to be performed *before* remapping access controls, as
100 // `ctx.Clone(var->decorations)` depend on these replacements.
101 auto bp_it = remappings->binding_points.find(from);
102 if (bp_it != remappings->binding_points.end()) {
103 BindingPoint to = bp_it->second;
104 auto* new_group = ctx.dst->create<ast::GroupDecoration>(to.group);
105 auto* new_binding = ctx.dst->create<ast::BindingDecoration>(to.binding);
106
107 ctx.Replace(binding_point.group, new_group);
108 ctx.Replace(binding_point.binding, new_binding);
109 bp = to;
110 }
111
112 // Replace any access controls.
113 auto ac_it = remappings->access_controls.find(from);
114 if (ac_it != remappings->access_controls.end()) {
115 ast::Access ac = ac_it->second;
116 if (ac > ast::Access::kLastValid) {
117 ctx.dst->Diagnostics().add_error(
118 diag::System::Transform,
119 "invalid access mode (" +
120 std::to_string(static_cast<uint32_t>(ac)) + ")");
121 return;
122 }
123 auto* sem = ctx.src->Sem().Get(var);
124 if (sem->StorageClass() != ast::StorageClass::kStorage) {
125 ctx.dst->Diagnostics().add_error(
126 diag::System::Transform,
127 "cannot apply access control to variable with storage class " +
128 std::string(ast::ToString(sem->StorageClass())));
129 return;
130 }
131 auto* ty = sem->Type()->UnwrapRef();
132 const ast::Type* inner_ty = CreateASTTypeFor(ctx, ty);
133 auto* new_var = ctx.dst->create<ast::Variable>(
134 ctx.Clone(var->source), ctx.Clone(var->symbol),
135 var->declared_storage_class, ac, inner_ty, var->is_const,
136 ctx.Clone(var->constructor), ctx.Clone(var->decorations));
137 ctx.Replace(var, new_var);
138 }
139
140 // Add `DisableValidationDecoration`s if required
141 if (add_collision_deco.count(bp)) {
142 auto* decoration =
143 ctx.dst->Disable(ast::DisabledValidation::kBindingPointCollision);
144 ctx.InsertBefore(var->decorations, *var->decorations.begin(),
145 decoration);
146 }
147 }
148 }
149
150 ctx.Clone();
151 }
152
153 } // namespace transform
154 } // namespace tint
155