• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2019 Google LLC
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 "source/opt/desc_sroa.h"
16 
17 #include "source/opt/desc_sroa_util.h"
18 #include "source/util/string_utils.h"
19 
20 namespace spvtools {
21 namespace opt {
22 namespace {
23 
IsDecorationBinding(Instruction * inst)24 bool IsDecorationBinding(Instruction* inst) {
25   if (inst->opcode() != spv::Op::OpDecorate) return false;
26   return spv::Decoration(inst->GetSingleWordInOperand(1u)) ==
27          spv::Decoration::Binding;
28 }
29 
30 }  // namespace
31 
Process()32 Pass::Status DescriptorScalarReplacement::Process() {
33   bool modified = false;
34   std::vector<Instruction*> vars_to_kill;
35 
36   for (Instruction& var : context()->types_values()) {
37     bool is_candidate =
38         flatten_arrays_ && descsroautil::IsDescriptorArray(context(), &var);
39     is_candidate |= flatten_composites_ &&
40                     descsroautil::IsDescriptorStruct(context(), &var);
41     if (is_candidate) {
42       modified = true;
43       if (!ReplaceCandidate(&var)) {
44         return Status::Failure;
45       }
46       vars_to_kill.push_back(&var);
47     }
48   }
49 
50   for (Instruction* var : vars_to_kill) {
51     context()->KillInst(var);
52   }
53 
54   return (modified ? Status::SuccessWithChange : Status::SuccessWithoutChange);
55 }
56 
ReplaceCandidate(Instruction * var)57 bool DescriptorScalarReplacement::ReplaceCandidate(Instruction* var) {
58   std::vector<Instruction*> access_chain_work_list;
59   std::vector<Instruction*> load_work_list;
60   std::vector<Instruction*> entry_point_work_list;
61   bool failed = !get_def_use_mgr()->WhileEachUser(
62       var->result_id(), [this, &access_chain_work_list, &load_work_list,
63                          &entry_point_work_list](Instruction* use) {
64         if (use->opcode() == spv::Op::OpName) {
65           return true;
66         }
67 
68         if (use->IsDecoration()) {
69           return true;
70         }
71 
72         switch (use->opcode()) {
73           case spv::Op::OpAccessChain:
74           case spv::Op::OpInBoundsAccessChain:
75             access_chain_work_list.push_back(use);
76             return true;
77           case spv::Op::OpLoad:
78             load_work_list.push_back(use);
79             return true;
80           case spv::Op::OpEntryPoint:
81             entry_point_work_list.push_back(use);
82             return true;
83           default:
84             context()->EmitErrorMessage(
85                 "Variable cannot be replaced: invalid instruction", use);
86             return false;
87         }
88         return true;
89       });
90 
91   if (failed) {
92     return false;
93   }
94 
95   for (Instruction* use : access_chain_work_list) {
96     if (!ReplaceAccessChain(var, use)) {
97       return false;
98     }
99   }
100   for (Instruction* use : load_work_list) {
101     if (!ReplaceLoadedValue(var, use)) {
102       return false;
103     }
104   }
105   for (Instruction* use : entry_point_work_list) {
106     if (!ReplaceEntryPoint(var, use)) {
107       return false;
108     }
109   }
110   return true;
111 }
112 
ReplaceAccessChain(Instruction * var,Instruction * use)113 bool DescriptorScalarReplacement::ReplaceAccessChain(Instruction* var,
114                                                      Instruction* use) {
115   if (use->NumInOperands() <= 1) {
116     context()->EmitErrorMessage(
117         "Variable cannot be replaced: invalid instruction", use);
118     return false;
119   }
120 
121   const analysis::Constant* const_index =
122       descsroautil::GetAccessChainIndexAsConst(context(), use);
123   if (const_index == nullptr) {
124     context()->EmitErrorMessage("Variable cannot be replaced: invalid index",
125                                 use);
126     return false;
127   }
128 
129   uint32_t idx = const_index->GetU32();
130   uint32_t replacement_var = GetReplacementVariable(var, idx);
131 
132   if (use->NumInOperands() == 2) {
133     // We are not indexing into the replacement variable.  We can replaces the
134     // access chain with the replacement variable itself.
135     context()->ReplaceAllUsesWith(use->result_id(), replacement_var);
136     context()->KillInst(use);
137     return true;
138   }
139 
140   // We need to build a new access chain with the replacement variable as the
141   // base address.
142   Instruction::OperandList new_operands;
143 
144   // Same result id and result type.
145   new_operands.emplace_back(use->GetOperand(0));
146   new_operands.emplace_back(use->GetOperand(1));
147 
148   // Use the replacement variable as the base address.
149   new_operands.push_back({SPV_OPERAND_TYPE_ID, {replacement_var}});
150 
151   // Drop the first index because it is consumed by the replacement, and copy
152   // the rest.
153   for (uint32_t i = 4; i < use->NumOperands(); i++) {
154     new_operands.emplace_back(use->GetOperand(i));
155   }
156 
157   use->ReplaceOperands(new_operands);
158   context()->UpdateDefUse(use);
159   return true;
160 }
161 
ReplaceEntryPoint(Instruction * var,Instruction * use)162 bool DescriptorScalarReplacement::ReplaceEntryPoint(Instruction* var,
163                                                     Instruction* use) {
164   // Build a new |OperandList| for |use| that removes |var| and adds its
165   // replacement variables.
166   Instruction::OperandList new_operands;
167 
168   // Copy all operands except |var|.
169   bool found = false;
170   for (uint32_t idx = 0; idx < use->NumOperands(); idx++) {
171     Operand& op = use->GetOperand(idx);
172     if (op.type == SPV_OPERAND_TYPE_ID && op.words[0] == var->result_id()) {
173       found = true;
174     } else {
175       new_operands.emplace_back(op);
176     }
177   }
178 
179   if (!found) {
180     context()->EmitErrorMessage(
181         "Variable cannot be replaced: invalid instruction", use);
182     return false;
183   }
184 
185   // Add all new replacement variables.
186   uint32_t num_replacement_vars =
187       descsroautil::GetNumberOfElementsForArrayOrStruct(context(), var);
188   for (uint32_t i = 0; i < num_replacement_vars; i++) {
189     new_operands.push_back(
190         {SPV_OPERAND_TYPE_ID, {GetReplacementVariable(var, i)}});
191   }
192 
193   use->ReplaceOperands(new_operands);
194   context()->UpdateDefUse(use);
195   return true;
196 }
197 
GetReplacementVariable(Instruction * var,uint32_t idx)198 uint32_t DescriptorScalarReplacement::GetReplacementVariable(Instruction* var,
199                                                              uint32_t idx) {
200   auto replacement_vars = replacement_variables_.find(var);
201   if (replacement_vars == replacement_variables_.end()) {
202     uint32_t number_of_elements =
203         descsroautil::GetNumberOfElementsForArrayOrStruct(context(), var);
204     replacement_vars =
205         replacement_variables_
206             .insert({var, std::vector<uint32_t>(number_of_elements, 0)})
207             .first;
208   }
209 
210   if (replacement_vars->second[idx] == 0) {
211     replacement_vars->second[idx] = CreateReplacementVariable(var, idx);
212   }
213 
214   return replacement_vars->second[idx];
215 }
216 
CopyDecorationsForNewVariable(Instruction * old_var,uint32_t index,uint32_t new_var_id,uint32_t new_var_ptr_type_id,const bool is_old_var_array,const bool is_old_var_struct,Instruction * old_var_type)217 void DescriptorScalarReplacement::CopyDecorationsForNewVariable(
218     Instruction* old_var, uint32_t index, uint32_t new_var_id,
219     uint32_t new_var_ptr_type_id, const bool is_old_var_array,
220     const bool is_old_var_struct, Instruction* old_var_type) {
221   // Handle OpDecorate and OpDecorateString instructions.
222   for (auto old_decoration :
223        get_decoration_mgr()->GetDecorationsFor(old_var->result_id(), true)) {
224     uint32_t new_binding = 0;
225     if (IsDecorationBinding(old_decoration)) {
226       new_binding = GetNewBindingForElement(
227           old_decoration->GetSingleWordInOperand(2), index, new_var_ptr_type_id,
228           is_old_var_array, is_old_var_struct, old_var_type);
229     }
230     CreateNewDecorationForNewVariable(old_decoration, new_var_id, new_binding);
231   }
232 
233   // Handle OpMemberDecorate instructions.
234   for (auto old_decoration : get_decoration_mgr()->GetDecorationsFor(
235            old_var_type->result_id(), true)) {
236     assert(old_decoration->opcode() == spv::Op::OpMemberDecorate);
237     if (old_decoration->GetSingleWordInOperand(1u) != index) continue;
238     CreateNewDecorationForMemberDecorate(old_decoration, new_var_id);
239   }
240 }
241 
GetNewBindingForElement(uint32_t old_binding,uint32_t index,uint32_t new_var_ptr_type_id,const bool is_old_var_array,const bool is_old_var_struct,Instruction * old_var_type)242 uint32_t DescriptorScalarReplacement::GetNewBindingForElement(
243     uint32_t old_binding, uint32_t index, uint32_t new_var_ptr_type_id,
244     const bool is_old_var_array, const bool is_old_var_struct,
245     Instruction* old_var_type) {
246   if (is_old_var_array) {
247     return old_binding + index * GetNumBindingsUsedByType(new_var_ptr_type_id);
248   }
249   if (is_old_var_struct) {
250     // The binding offset that should be added is the sum of binding
251     // numbers used by previous members of the current struct.
252     uint32_t new_binding = old_binding;
253     for (uint32_t i = 0; i < index; ++i) {
254       new_binding +=
255           GetNumBindingsUsedByType(old_var_type->GetSingleWordInOperand(i));
256     }
257     return new_binding;
258   }
259   return old_binding;
260 }
261 
CreateNewDecorationForNewVariable(Instruction * old_decoration,uint32_t new_var_id,uint32_t new_binding)262 void DescriptorScalarReplacement::CreateNewDecorationForNewVariable(
263     Instruction* old_decoration, uint32_t new_var_id, uint32_t new_binding) {
264   assert(old_decoration->opcode() == spv::Op::OpDecorate ||
265          old_decoration->opcode() == spv::Op::OpDecorateString);
266   std::unique_ptr<Instruction> new_decoration(old_decoration->Clone(context()));
267   new_decoration->SetInOperand(0, {new_var_id});
268 
269   if (IsDecorationBinding(new_decoration.get())) {
270     new_decoration->SetInOperand(2, {new_binding});
271   }
272   context()->AddAnnotationInst(std::move(new_decoration));
273 }
274 
CreateNewDecorationForMemberDecorate(Instruction * old_member_decoration,uint32_t new_var_id)275 void DescriptorScalarReplacement::CreateNewDecorationForMemberDecorate(
276     Instruction* old_member_decoration, uint32_t new_var_id) {
277   std::vector<Operand> operands(
278       {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {new_var_id}}});
279   auto new_decorate_operand_begin = old_member_decoration->begin() + 2u;
280   auto new_decorate_operand_end = old_member_decoration->end();
281   operands.insert(operands.end(), new_decorate_operand_begin,
282                   new_decorate_operand_end);
283   get_decoration_mgr()->AddDecoration(spv::Op::OpDecorate, std::move(operands));
284 }
285 
CreateReplacementVariable(Instruction * var,uint32_t idx)286 uint32_t DescriptorScalarReplacement::CreateReplacementVariable(
287     Instruction* var, uint32_t idx) {
288   // The storage class for the new variable is the same as the original.
289   spv::StorageClass storage_class =
290       static_cast<spv::StorageClass>(var->GetSingleWordInOperand(0));
291 
292   // The type for the new variable will be a pointer to type of the elements of
293   // the array.
294   uint32_t ptr_type_id = var->type_id();
295   Instruction* ptr_type_inst = get_def_use_mgr()->GetDef(ptr_type_id);
296   assert(ptr_type_inst->opcode() == spv::Op::OpTypePointer &&
297          "Variable should be a pointer to an array or structure.");
298   uint32_t pointee_type_id = ptr_type_inst->GetSingleWordInOperand(1);
299   Instruction* pointee_type_inst = get_def_use_mgr()->GetDef(pointee_type_id);
300   const bool is_array = pointee_type_inst->opcode() == spv::Op::OpTypeArray;
301   const bool is_struct = pointee_type_inst->opcode() == spv::Op::OpTypeStruct;
302   assert((is_array || is_struct) &&
303          "Variable should be a pointer to an array or structure.");
304 
305   uint32_t element_type_id =
306       is_array ? pointee_type_inst->GetSingleWordInOperand(0)
307                : pointee_type_inst->GetSingleWordInOperand(idx);
308 
309   uint32_t ptr_element_type_id = context()->get_type_mgr()->FindPointerToType(
310       element_type_id, storage_class);
311 
312   // Create the variable.
313   uint32_t id = TakeNextId();
314   std::unique_ptr<Instruction> variable(
315       new Instruction(context(), spv::Op::OpVariable, ptr_element_type_id, id,
316                       std::initializer_list<Operand>{
317                           {SPV_OPERAND_TYPE_STORAGE_CLASS,
318                            {static_cast<uint32_t>(storage_class)}}}));
319   context()->AddGlobalValue(std::move(variable));
320 
321   CopyDecorationsForNewVariable(var, idx, id, ptr_element_type_id, is_array,
322                                 is_struct, pointee_type_inst);
323 
324   // Create a new OpName for the replacement variable.
325   std::vector<std::unique_ptr<Instruction>> names_to_add;
326   for (auto p : context()->GetNames(var->result_id())) {
327     Instruction* name_inst = p.second;
328     std::string name_str = utils::MakeString(name_inst->GetOperand(1).words);
329     if (is_array) {
330       name_str += "[" + utils::ToString(idx) + "]";
331     }
332     if (is_struct) {
333       Instruction* member_name_inst =
334           context()->GetMemberName(pointee_type_inst->result_id(), idx);
335       name_str += ".";
336       if (member_name_inst)
337         name_str += utils::MakeString(member_name_inst->GetOperand(2).words);
338       else
339         // In case the member does not have a name assigned to it, use the
340         // member index.
341         name_str += utils::ToString(idx);
342     }
343 
344     std::unique_ptr<Instruction> new_name(new Instruction(
345         context(), spv::Op::OpName, 0, 0,
346         std::initializer_list<Operand>{
347             {SPV_OPERAND_TYPE_ID, {id}},
348             {SPV_OPERAND_TYPE_LITERAL_STRING, utils::MakeVector(name_str)}}));
349     Instruction* new_name_inst = new_name.get();
350     get_def_use_mgr()->AnalyzeInstDefUse(new_name_inst);
351     names_to_add.push_back(std::move(new_name));
352   }
353 
354   // We shouldn't add the new names when we are iterating over name ranges
355   // above. We can add all the new names now.
356   for (auto& new_name : names_to_add)
357     context()->AddDebug2Inst(std::move(new_name));
358 
359   return id;
360 }
361 
GetNumBindingsUsedByType(uint32_t type_id)362 uint32_t DescriptorScalarReplacement::GetNumBindingsUsedByType(
363     uint32_t type_id) {
364   Instruction* type_inst = get_def_use_mgr()->GetDef(type_id);
365 
366   // If it's a pointer, look at the underlying type.
367   if (type_inst->opcode() == spv::Op::OpTypePointer) {
368     type_id = type_inst->GetSingleWordInOperand(1);
369     type_inst = get_def_use_mgr()->GetDef(type_id);
370   }
371 
372   // Arrays consume N*M binding numbers where N is the array length, and M is
373   // the number of bindings used by each array element.
374   if (type_inst->opcode() == spv::Op::OpTypeArray) {
375     uint32_t element_type_id = type_inst->GetSingleWordInOperand(0);
376     uint32_t length_id = type_inst->GetSingleWordInOperand(1);
377     const analysis::Constant* length_const =
378         context()->get_constant_mgr()->FindDeclaredConstant(length_id);
379     // OpTypeArray's length must always be a constant
380     assert(length_const != nullptr);
381     uint32_t num_elems = length_const->GetU32();
382     return num_elems * GetNumBindingsUsedByType(element_type_id);
383   }
384 
385   // The number of bindings consumed by a structure is the sum of the bindings
386   // used by its members.
387   if (type_inst->opcode() == spv::Op::OpTypeStruct &&
388       !descsroautil::IsTypeOfStructuredBuffer(context(), type_inst)) {
389     uint32_t sum = 0;
390     for (uint32_t i = 0; i < type_inst->NumInOperands(); i++)
391       sum += GetNumBindingsUsedByType(type_inst->GetSingleWordInOperand(i));
392     return sum;
393   }
394 
395   // All other types are considered to take up 1 binding number.
396   return 1;
397 }
398 
ReplaceLoadedValue(Instruction * var,Instruction * value)399 bool DescriptorScalarReplacement::ReplaceLoadedValue(Instruction* var,
400                                                      Instruction* value) {
401   // |var| is the global variable that has to be eliminated (OpVariable).
402   // |value| is the OpLoad instruction that has loaded |var|.
403   // The function expects all users of |value| to be OpCompositeExtract
404   // instructions. Otherwise the function returns false with an error message.
405   assert(value->opcode() == spv::Op::OpLoad);
406   assert(value->GetSingleWordInOperand(0) == var->result_id());
407   std::vector<Instruction*> work_list;
408   bool failed = !get_def_use_mgr()->WhileEachUser(
409       value->result_id(), [this, &work_list](Instruction* use) {
410         if (use->opcode() != spv::Op::OpCompositeExtract) {
411           context()->EmitErrorMessage(
412               "Variable cannot be replaced: invalid instruction", use);
413           return false;
414         }
415         work_list.push_back(use);
416         return true;
417       });
418 
419   if (failed) {
420     return false;
421   }
422 
423   for (Instruction* use : work_list) {
424     if (!ReplaceCompositeExtract(var, use)) {
425       return false;
426     }
427   }
428 
429   // All usages of the loaded value have been killed. We can kill the OpLoad.
430   context()->KillInst(value);
431   return true;
432 }
433 
ReplaceCompositeExtract(Instruction * var,Instruction * extract)434 bool DescriptorScalarReplacement::ReplaceCompositeExtract(
435     Instruction* var, Instruction* extract) {
436   assert(extract->opcode() == spv::Op::OpCompositeExtract);
437   // We're currently only supporting extractions of one index at a time. If we
438   // need to, we can handle cases with multiple indexes in the future.
439   if (extract->NumInOperands() != 2) {
440     context()->EmitErrorMessage(
441         "Variable cannot be replaced: invalid instruction", extract);
442     return false;
443   }
444 
445   uint32_t replacement_var =
446       GetReplacementVariable(var, extract->GetSingleWordInOperand(1));
447 
448   // The result type of the OpLoad is the same as the result type of the
449   // OpCompositeExtract.
450   uint32_t load_id = TakeNextId();
451   std::unique_ptr<Instruction> load(
452       new Instruction(context(), spv::Op::OpLoad, extract->type_id(), load_id,
453                       std::initializer_list<Operand>{
454                           {SPV_OPERAND_TYPE_ID, {replacement_var}}}));
455   Instruction* load_instr = load.get();
456   get_def_use_mgr()->AnalyzeInstDefUse(load_instr);
457   context()->set_instr_block(load_instr, context()->get_instr_block(extract));
458   extract->InsertBefore(std::move(load));
459   context()->ReplaceAllUsesWith(extract->result_id(), load_id);
460   context()->KillInst(extract);
461   return true;
462 }
463 
464 }  // namespace opt
465 }  // namespace spvtools
466