• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2022 The Khronos Group Inc.
2 // Copyright (c) 2022 LunarG Inc.
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //     http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 
16 #include "source/opt/liveness.h"
17 
18 #include "source/opt/ir_context.h"
19 
20 namespace spvtools {
21 namespace opt {
22 namespace analysis {
23 namespace {
24 constexpr uint32_t kDecorationLocationInIdx = 2;
25 constexpr uint32_t kOpDecorateMemberMemberInIdx = 1;
26 constexpr uint32_t kOpDecorateMemberLocationInIdx = 3;
27 constexpr uint32_t kOpDecorateBuiltInLiteralInIdx = 2;
28 constexpr uint32_t kOpDecorateMemberBuiltInLiteralInIdx = 3;
29 }  // namespace
30 
LivenessManager(IRContext * ctx)31 LivenessManager::LivenessManager(IRContext* ctx) : ctx_(ctx), computed_(false) {
32   // Liveness sets computed when queried
33 }
34 
InitializeAnalysis()35 void LivenessManager::InitializeAnalysis() {
36   live_locs_.clear();
37   live_builtins_.clear();
38   // Mark all builtins live for frag shader.
39   if (context()->GetStage() == spv::ExecutionModel::Fragment) {
40     live_builtins_.insert(uint32_t(spv::BuiltIn::PointSize));
41     live_builtins_.insert(uint32_t(spv::BuiltIn::ClipDistance));
42     live_builtins_.insert(uint32_t(spv::BuiltIn::CullDistance));
43   }
44 }
45 
IsAnalyzedBuiltin(uint32_t bi)46 bool LivenessManager::IsAnalyzedBuiltin(uint32_t bi) {
47   // There are only three builtins that can be analyzed and removed between
48   // two stages: PointSize, ClipDistance and CullDistance. All others are
49   // always consumed implicitly by the downstream stage.
50   const auto builtin = spv::BuiltIn(bi);
51   return builtin == spv::BuiltIn::PointSize ||
52          builtin == spv::BuiltIn::ClipDistance ||
53          builtin == spv::BuiltIn::CullDistance;
54 }
55 
AnalyzeBuiltIn(uint32_t id)56 bool LivenessManager::AnalyzeBuiltIn(uint32_t id) {
57   auto deco_mgr = context()->get_decoration_mgr();
58   bool saw_builtin = false;
59   // Analyze all builtin decorations of |id|.
60   (void)deco_mgr->ForEachDecoration(
61       id, uint32_t(spv::Decoration::BuiltIn),
62       [this, &saw_builtin](const Instruction& deco_inst) {
63         saw_builtin = true;
64         // No need to process builtins in frag shader. All assumed used.
65         if (context()->GetStage() == spv::ExecutionModel::Fragment) return;
66         uint32_t builtin = uint32_t(spv::BuiltIn::Max);
67         if (deco_inst.opcode() == spv::Op::OpDecorate)
68           builtin =
69               deco_inst.GetSingleWordInOperand(kOpDecorateBuiltInLiteralInIdx);
70         else if (deco_inst.opcode() == spv::Op::OpMemberDecorate)
71           builtin = deco_inst.GetSingleWordInOperand(
72               kOpDecorateMemberBuiltInLiteralInIdx);
73         else
74           assert(false && "unexpected decoration");
75         if (IsAnalyzedBuiltin(builtin)) live_builtins_.insert(builtin);
76       });
77   return saw_builtin;
78 }
79 
MarkLocsLive(uint32_t start,uint32_t count)80 void LivenessManager::MarkLocsLive(uint32_t start, uint32_t count) {
81   auto finish = start + count;
82   for (uint32_t u = start; u < finish; ++u) {
83     live_locs_.insert(u);
84   }
85 }
86 
GetLocSize(const analysis::Type * type) const87 uint32_t LivenessManager::GetLocSize(const analysis::Type* type) const {
88   auto arr_type = type->AsArray();
89   if (arr_type) {
90     auto comp_type = arr_type->element_type();
91     auto len_info = arr_type->length_info();
92     assert(len_info.words[0] == analysis::Array::LengthInfo::kConstant &&
93            "unexpected array length");
94     auto comp_len = len_info.words[1];
95     return comp_len * GetLocSize(comp_type);
96   }
97   auto struct_type = type->AsStruct();
98   if (struct_type) {
99     uint32_t size = 0u;
100     for (auto& el_type : struct_type->element_types())
101       size += GetLocSize(el_type);
102     return size;
103   }
104   auto mat_type = type->AsMatrix();
105   if (mat_type) {
106     auto cnt = mat_type->element_count();
107     auto comp_type = mat_type->element_type();
108     return cnt * GetLocSize(comp_type);
109   }
110   auto vec_type = type->AsVector();
111   if (vec_type) {
112     auto comp_type = vec_type->element_type();
113     if (comp_type->AsInteger()) return 1;
114     auto float_type = comp_type->AsFloat();
115     assert(float_type && "unexpected vector component type");
116     auto width = float_type->width();
117     if (width == 32 || width == 16) return 1;
118     assert(width == 64 && "unexpected float type width");
119     auto comp_cnt = vec_type->element_count();
120     return (comp_cnt > 2) ? 2 : 1;
121   }
122   assert((type->AsInteger() || type->AsFloat()) && "unexpected input type");
123   return 1;
124 }
125 
GetComponentType(uint32_t index,uint32_t agg_type_id) const126 uint32_t LivenessManager::GetComponentType(uint32_t index,
127                                            uint32_t agg_type_id) const {
128   analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr();
129   Instruction* agg_type_inst = def_use_mgr->GetDef(agg_type_id);
130 
131   const uint32_t kArrayElementInIdx = 0;
132   switch (agg_type_inst->opcode()) {
133     case spv::Op::OpTypeArray:
134     case spv::Op::OpTypeMatrix:
135     case spv::Op::OpTypeVector:
136       return agg_type_inst->GetSingleWordInOperand(kArrayElementInIdx);
137     case spv::Op::OpTypeStruct:
138       return agg_type_inst->GetSingleWordInOperand(index);
139     default:
140       assert(false && "unexpected aggregate type");
141       return 0;
142   }
143 }
144 
GetLocOffset(uint32_t index,uint32_t agg_type_id) const145 uint32_t LivenessManager::GetLocOffset(uint32_t index,
146                                        uint32_t agg_type_id) const {
147   analysis::TypeManager* type_mgr = context()->get_type_mgr();
148   const analysis::Type* agg_type = type_mgr->GetType(agg_type_id);
149   auto arr_type = agg_type->AsArray();
150   if (arr_type) return index * GetLocSize(arr_type->element_type());
151   auto struct_type = agg_type->AsStruct();
152   if (struct_type) {
153     uint32_t offset = 0u;
154     uint32_t cnt = 0u;
155     for (auto& el_type : struct_type->element_types()) {
156       if (cnt == index) break;
157       offset += GetLocSize(el_type);
158       ++cnt;
159     }
160     return offset;
161   }
162   auto mat_type = agg_type->AsMatrix();
163   if (mat_type) return index * GetLocSize(mat_type->element_type());
164   auto vec_type = agg_type->AsVector();
165   assert(vec_type && "unexpected non-aggregate type");
166   auto comp_type = vec_type->element_type();
167   auto flt_type = comp_type->AsFloat();
168   if (flt_type && flt_type->width() == 64u && index >= 2u) return 1;
169   return 0;
170 }
171 
AnalyzeAccessChainLoc(const Instruction * ac,uint32_t curr_type_id,uint32_t * offset,bool * no_loc,bool is_patch,bool input)172 uint32_t LivenessManager::AnalyzeAccessChainLoc(const Instruction* ac,
173                                                 uint32_t curr_type_id,
174                                                 uint32_t* offset, bool* no_loc,
175                                                 bool is_patch, bool input) {
176   analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr();
177   analysis::DecorationManager* deco_mgr = context()->get_decoration_mgr();
178   // For tesc, tese and geom input variables, and tesc output variables,
179   // first array index does not contribute to offset.
180   auto stage = context()->GetStage();
181   bool skip_first_index = false;
182   if ((input && (stage == spv::ExecutionModel::TessellationControl ||
183                  stage == spv::ExecutionModel::TessellationEvaluation ||
184                  stage == spv::ExecutionModel::Geometry)) ||
185       (!input && stage == spv::ExecutionModel::TessellationControl))
186     skip_first_index = !is_patch;
187   uint32_t ocnt = 0;
188   ac->WhileEachInOperand([this, &ocnt, def_use_mgr, deco_mgr, &curr_type_id,
189                           offset, no_loc,
190                           skip_first_index](const uint32_t* opnd) {
191     if (ocnt >= 1) {
192       // Skip first index's contribution to offset if indicated
193       Instruction* curr_type_inst = def_use_mgr->GetDef(curr_type_id);
194       if (ocnt == 1 && skip_first_index) {
195         assert(curr_type_inst->opcode() == spv::Op::OpTypeArray &&
196                "unexpected wrapper type");
197         const uint32_t kArrayElementTypeInIdx = 0;
198         curr_type_id =
199             curr_type_inst->GetSingleWordInOperand(kArrayElementTypeInIdx);
200         ocnt++;
201         return true;
202       }
203       // If any non-constant index, mark the entire current object and return.
204       auto idx_inst = def_use_mgr->GetDef(*opnd);
205       if (idx_inst->opcode() != spv::Op::OpConstant) return false;
206       // If current type is struct, look for location decoration on member and
207       // reset offset if found.
208       auto index = idx_inst->GetSingleWordInOperand(0);
209       if (curr_type_inst->opcode() == spv::Op::OpTypeStruct) {
210         uint32_t loc = 0;
211         bool no_mem_loc = deco_mgr->WhileEachDecoration(
212             curr_type_id, uint32_t(spv::Decoration::Location),
213             [&loc, index, no_loc](const Instruction& deco) {
214               assert(deco.opcode() == spv::Op::OpMemberDecorate &&
215                      "unexpected decoration");
216               if (deco.GetSingleWordInOperand(kOpDecorateMemberMemberInIdx) ==
217                   index) {
218                 loc =
219                     deco.GetSingleWordInOperand(kOpDecorateMemberLocationInIdx);
220                 *no_loc = false;
221                 return false;
222               }
223               return true;
224             });
225         if (!no_mem_loc) {
226           *offset = loc;
227           curr_type_id = curr_type_inst->GetSingleWordInOperand(index);
228           ocnt++;
229           return true;
230         }
231       }
232 
233       // Update offset and current type based on constant index.
234       *offset += GetLocOffset(index, curr_type_id);
235       curr_type_id = GetComponentType(index, curr_type_id);
236     }
237     ocnt++;
238     return true;
239   });
240   return curr_type_id;
241 }
242 
MarkRefLive(const Instruction * ref,Instruction * var)243 void LivenessManager::MarkRefLive(const Instruction* ref, Instruction* var) {
244   analysis::TypeManager* type_mgr = context()->get_type_mgr();
245   analysis::DecorationManager* deco_mgr = context()->get_decoration_mgr();
246   // Find variable location if present.
247   uint32_t loc = 0;
248   auto var_id = var->result_id();
249   bool no_loc = deco_mgr->WhileEachDecoration(
250       var_id, uint32_t(spv::Decoration::Location),
251       [&loc](const Instruction& deco) {
252         assert(deco.opcode() == spv::Op::OpDecorate && "unexpected decoration");
253         loc = deco.GetSingleWordInOperand(kDecorationLocationInIdx);
254         return false;
255       });
256   // Find patch decoration if present
257   bool is_patch = !deco_mgr->WhileEachDecoration(
258       var_id, uint32_t(spv::Decoration::Patch), [](const Instruction& deco) {
259         if (deco.opcode() != spv::Op::OpDecorate)
260           assert(false && "unexpected decoration");
261         return false;
262       });
263   // If use is a load, mark all locations of var
264   auto ptr_type = type_mgr->GetType(var->type_id())->AsPointer();
265   assert(ptr_type && "unexpected var type");
266   auto var_type = ptr_type->pointee_type();
267   if (ref->opcode() == spv::Op::OpLoad) {
268     assert(!no_loc && "missing input variable location");
269     MarkLocsLive(loc, GetLocSize(var_type));
270     return;
271   }
272   // Mark just those locations indicated by access chain
273   assert((ref->opcode() == spv::Op::OpAccessChain ||
274           ref->opcode() == spv::Op::OpInBoundsAccessChain) &&
275          "unexpected use of input variable");
276   // Traverse access chain, compute location offset and type of reference
277   // through constant indices and mark those locs live. Assert if no location
278   // found.
279   uint32_t offset = loc;
280   Instruction* ptr_type_inst =
281       context()->get_def_use_mgr()->GetDef(var->type_id());
282   assert(ptr_type && "unexpected var type");
283   const uint32_t kPointerTypePointeeIdx = 1;
284   uint32_t var_type_id =
285       ptr_type_inst->GetSingleWordInOperand(kPointerTypePointeeIdx);
286   uint32_t curr_type_id =
287       AnalyzeAccessChainLoc(ref, var_type_id, &offset, &no_loc, is_patch);
288   auto curr_type = type_mgr->GetType(curr_type_id);
289   assert(!no_loc && "missing input variable location");
290   MarkLocsLive(offset, GetLocSize(curr_type));
291 }
292 
ComputeLiveness()293 void LivenessManager::ComputeLiveness() {
294   InitializeAnalysis();
295   analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr();
296   // Process all input variables
297   for (auto& var : context()->types_values()) {
298     if (var.opcode() != spv::Op::OpVariable) {
299       continue;
300     }
301     Instruction* var_type_inst = def_use_mgr->GetDef(var.type_id());
302     assert(var_type_inst->opcode() == spv::Op::OpTypePointer &&
303            "Expected a pointer type");
304     const uint32_t kPointerTypeStorageClassInIdx = 0;
305     spv::StorageClass sc = static_cast<spv::StorageClass>(
306         var_type_inst->GetSingleWordInOperand(kPointerTypeStorageClassInIdx));
307     if (sc != spv::StorageClass::Input) {
308       continue;
309     }
310     // If var is builtin, mark live if analyzed and continue to next variable
311     auto var_id = var.result_id();
312     if (AnalyzeBuiltIn(var_id)) continue;
313     // If interface block with builtin members, mark live if analyzed and
314     // continue to next variable. Input interface blocks will only appear
315     // in tesc, tese and geom shaders. Will need to strip off one level of
316     // arrayness to get to block type.
317     const uint32_t kPointerTypePointeeTypeInIdx = 1;
318     uint32_t pte_type_id =
319         var_type_inst->GetSingleWordInOperand(kPointerTypePointeeTypeInIdx);
320     Instruction* pte_type_inst = def_use_mgr->GetDef(pte_type_id);
321     if (pte_type_inst->opcode() == spv::Op::OpTypeArray) {
322       uint32_t array_elt_type_id = pte_type_inst->GetSingleWordInOperand(0);
323       Instruction* arr_elt_type = def_use_mgr->GetDef(array_elt_type_id);
324       if (arr_elt_type->opcode() == spv::Op::OpTypeStruct) {
325         if (AnalyzeBuiltIn(array_elt_type_id)) continue;
326       }
327     }
328     // Mark all used locations of var live
329     def_use_mgr->ForEachUser(var_id, [this, &var](Instruction* user) {
330       auto op = user->opcode();
331       if (op == spv::Op::OpEntryPoint || op == spv::Op::OpName ||
332           op == spv::Op::OpDecorate || user->IsNonSemanticInstruction()) {
333         return;
334       }
335       MarkRefLive(user, &var);
336     });
337   }
338 }
339 
GetLiveness(std::unordered_set<uint32_t> * live_locs,std::unordered_set<uint32_t> * live_builtins)340 void LivenessManager::GetLiveness(std::unordered_set<uint32_t>* live_locs,
341                                   std::unordered_set<uint32_t>* live_builtins) {
342   if (!computed_) {
343     ComputeLiveness();
344     computed_ = true;
345   }
346   *live_locs = live_locs_;
347   *live_builtins = live_builtins_;
348 }
349 
350 }  // namespace analysis
351 }  // namespace opt
352 }  // namespace spvtools
353