/src/shaderc/third_party/spirv-tools/source/opt/liveness.cpp
Line | Count | Source (jump to first uncovered line) |
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 | | |
31 | 0 | LivenessManager::LivenessManager(IRContext* ctx) : ctx_(ctx), computed_(false) { |
32 | | // Liveness sets computed when queried |
33 | 0 | } |
34 | | |
35 | 0 | void LivenessManager::InitializeAnalysis() { |
36 | 0 | live_locs_.clear(); |
37 | 0 | live_builtins_.clear(); |
38 | | // Mark all builtins live for frag shader. |
39 | 0 | if (context()->GetStage() == spv::ExecutionModel::Fragment) { |
40 | 0 | live_builtins_.insert(uint32_t(spv::BuiltIn::PointSize)); |
41 | 0 | live_builtins_.insert(uint32_t(spv::BuiltIn::ClipDistance)); |
42 | 0 | live_builtins_.insert(uint32_t(spv::BuiltIn::CullDistance)); |
43 | 0 | } |
44 | 0 | } |
45 | | |
46 | 0 | 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 | 0 | const auto builtin = spv::BuiltIn(bi); |
51 | 0 | return builtin == spv::BuiltIn::PointSize || |
52 | 0 | builtin == spv::BuiltIn::ClipDistance || |
53 | 0 | builtin == spv::BuiltIn::CullDistance; |
54 | 0 | } |
55 | | |
56 | 0 | bool LivenessManager::AnalyzeBuiltIn(uint32_t id) { |
57 | 0 | auto deco_mgr = context()->get_decoration_mgr(); |
58 | 0 | bool saw_builtin = false; |
59 | | // Analyze all builtin decorations of |id|. |
60 | 0 | (void)deco_mgr->ForEachDecoration( |
61 | 0 | id, uint32_t(spv::Decoration::BuiltIn), |
62 | 0 | [this, &saw_builtin](const Instruction& deco_inst) { |
63 | 0 | saw_builtin = true; |
64 | | // No need to process builtins in frag shader. All assumed used. |
65 | 0 | if (context()->GetStage() == spv::ExecutionModel::Fragment) return; |
66 | 0 | uint32_t builtin = uint32_t(spv::BuiltIn::Max); |
67 | 0 | if (deco_inst.opcode() == spv::Op::OpDecorate) |
68 | 0 | builtin = |
69 | 0 | deco_inst.GetSingleWordInOperand(kOpDecorateBuiltInLiteralInIdx); |
70 | 0 | else if (deco_inst.opcode() == spv::Op::OpMemberDecorate) |
71 | 0 | builtin = deco_inst.GetSingleWordInOperand( |
72 | 0 | kOpDecorateMemberBuiltInLiteralInIdx); |
73 | 0 | else |
74 | 0 | assert(false && "unexpected decoration"); |
75 | 0 | if (IsAnalyzedBuiltin(builtin)) live_builtins_.insert(builtin); |
76 | 0 | }); |
77 | 0 | return saw_builtin; |
78 | 0 | } |
79 | | |
80 | 0 | void LivenessManager::MarkLocsLive(uint32_t start, uint32_t count) { |
81 | 0 | auto finish = start + count; |
82 | 0 | for (uint32_t u = start; u < finish; ++u) { |
83 | 0 | live_locs_.insert(u); |
84 | 0 | } |
85 | 0 | } |
86 | | |
87 | 0 | uint32_t LivenessManager::GetLocSize(const analysis::Type* type) const { |
88 | 0 | auto arr_type = type->AsArray(); |
89 | 0 | if (arr_type) { |
90 | 0 | auto comp_type = arr_type->element_type(); |
91 | 0 | auto len_info = arr_type->length_info(); |
92 | 0 | assert(len_info.words[0] == analysis::Array::LengthInfo::kConstant && |
93 | 0 | "unexpected array length"); |
94 | 0 | auto comp_len = len_info.words[1]; |
95 | 0 | return comp_len * GetLocSize(comp_type); |
96 | 0 | } |
97 | 0 | auto struct_type = type->AsStruct(); |
98 | 0 | if (struct_type) { |
99 | 0 | uint32_t size = 0u; |
100 | 0 | for (auto& el_type : struct_type->element_types()) |
101 | 0 | size += GetLocSize(el_type); |
102 | 0 | return size; |
103 | 0 | } |
104 | 0 | auto mat_type = type->AsMatrix(); |
105 | 0 | if (mat_type) { |
106 | 0 | auto cnt = mat_type->element_count(); |
107 | 0 | auto comp_type = mat_type->element_type(); |
108 | 0 | return cnt * GetLocSize(comp_type); |
109 | 0 | } |
110 | 0 | auto vec_type = type->AsVector(); |
111 | 0 | if (vec_type) { |
112 | 0 | auto comp_type = vec_type->element_type(); |
113 | 0 | if (comp_type->AsInteger()) return 1; |
114 | 0 | auto float_type = comp_type->AsFloat(); |
115 | 0 | assert(float_type && "unexpected vector component type"); |
116 | 0 | auto width = float_type->width(); |
117 | 0 | if (width == 32 || width == 16) return 1; |
118 | 0 | assert(width == 64 && "unexpected float type width"); |
119 | 0 | auto comp_cnt = vec_type->element_count(); |
120 | 0 | return (comp_cnt > 2) ? 2 : 1; |
121 | 0 | } |
122 | 0 | assert((type->AsInteger() || type->AsFloat()) && "unexpected input type"); |
123 | 0 | return 1; |
124 | 0 | } |
125 | | |
126 | | uint32_t LivenessManager::GetComponentType(uint32_t index, |
127 | 0 | uint32_t agg_type_id) const { |
128 | 0 | analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr(); |
129 | 0 | Instruction* agg_type_inst = def_use_mgr->GetDef(agg_type_id); |
130 | |
|
131 | 0 | const uint32_t kArrayElementInIdx = 0; |
132 | 0 | switch (agg_type_inst->opcode()) { |
133 | 0 | case spv::Op::OpTypeArray: |
134 | 0 | case spv::Op::OpTypeMatrix: |
135 | 0 | case spv::Op::OpTypeVector: |
136 | 0 | return agg_type_inst->GetSingleWordInOperand(kArrayElementInIdx); |
137 | 0 | case spv::Op::OpTypeStruct: |
138 | 0 | return agg_type_inst->GetSingleWordInOperand(index); |
139 | 0 | default: |
140 | 0 | assert(false && "unexpected aggregate type"); |
141 | 0 | return 0; |
142 | 0 | } |
143 | 0 | } |
144 | | |
145 | | uint32_t LivenessManager::GetLocOffset(uint32_t index, |
146 | 0 | uint32_t agg_type_id) const { |
147 | 0 | analysis::TypeManager* type_mgr = context()->get_type_mgr(); |
148 | 0 | const analysis::Type* agg_type = type_mgr->GetType(agg_type_id); |
149 | 0 | auto arr_type = agg_type->AsArray(); |
150 | 0 | if (arr_type) return index * GetLocSize(arr_type->element_type()); |
151 | 0 | auto struct_type = agg_type->AsStruct(); |
152 | 0 | if (struct_type) { |
153 | 0 | uint32_t offset = 0u; |
154 | 0 | uint32_t cnt = 0u; |
155 | 0 | for (auto& el_type : struct_type->element_types()) { |
156 | 0 | if (cnt == index) break; |
157 | 0 | offset += GetLocSize(el_type); |
158 | 0 | ++cnt; |
159 | 0 | } |
160 | 0 | return offset; |
161 | 0 | } |
162 | 0 | auto mat_type = agg_type->AsMatrix(); |
163 | 0 | if (mat_type) return index * GetLocSize(mat_type->element_type()); |
164 | 0 | auto vec_type = agg_type->AsVector(); |
165 | 0 | assert(vec_type && "unexpected non-aggregate type"); |
166 | 0 | auto comp_type = vec_type->element_type(); |
167 | 0 | auto flt_type = comp_type->AsFloat(); |
168 | 0 | if (flt_type && flt_type->width() == 64u && index >= 2u) return 1; |
169 | 0 | return 0; |
170 | 0 | } |
171 | | |
172 | | uint32_t LivenessManager::AnalyzeAccessChainLoc(const Instruction* ac, |
173 | | uint32_t curr_type_id, |
174 | | uint32_t* offset, bool* no_loc, |
175 | 0 | bool is_patch, bool input) { |
176 | 0 | analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr(); |
177 | 0 | 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 | 0 | auto stage = context()->GetStage(); |
181 | 0 | bool skip_first_index = false; |
182 | 0 | if ((input && (stage == spv::ExecutionModel::TessellationControl || |
183 | 0 | stage == spv::ExecutionModel::TessellationEvaluation || |
184 | 0 | stage == spv::ExecutionModel::Geometry)) || |
185 | 0 | (!input && stage == spv::ExecutionModel::TessellationControl)) |
186 | 0 | skip_first_index = !is_patch; |
187 | 0 | uint32_t ocnt = 0; |
188 | 0 | ac->WhileEachInOperand([this, &ocnt, def_use_mgr, deco_mgr, &curr_type_id, |
189 | 0 | offset, no_loc, |
190 | 0 | skip_first_index](const uint32_t* opnd) { |
191 | 0 | if (ocnt >= 1) { |
192 | | // Skip first index's contribution to offset if indicated |
193 | 0 | Instruction* curr_type_inst = def_use_mgr->GetDef(curr_type_id); |
194 | 0 | if (ocnt == 1 && skip_first_index) { |
195 | 0 | assert(curr_type_inst->opcode() == spv::Op::OpTypeArray && |
196 | 0 | "unexpected wrapper type"); |
197 | 0 | const uint32_t kArrayElementTypeInIdx = 0; |
198 | 0 | curr_type_id = |
199 | 0 | curr_type_inst->GetSingleWordInOperand(kArrayElementTypeInIdx); |
200 | 0 | ocnt++; |
201 | 0 | return true; |
202 | 0 | } |
203 | | // If any non-constant index, mark the entire current object and return. |
204 | 0 | auto idx_inst = def_use_mgr->GetDef(*opnd); |
205 | 0 | 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 | 0 | auto index = idx_inst->GetSingleWordInOperand(0); |
209 | 0 | if (curr_type_inst->opcode() == spv::Op::OpTypeStruct) { |
210 | 0 | uint32_t loc = 0; |
211 | 0 | bool no_mem_loc = deco_mgr->WhileEachDecoration( |
212 | 0 | curr_type_id, uint32_t(spv::Decoration::Location), |
213 | 0 | [&loc, index, no_loc](const Instruction& deco) { |
214 | 0 | assert(deco.opcode() == spv::Op::OpMemberDecorate && |
215 | 0 | "unexpected decoration"); |
216 | 0 | if (deco.GetSingleWordInOperand(kOpDecorateMemberMemberInIdx) == |
217 | 0 | index) { |
218 | 0 | loc = |
219 | 0 | deco.GetSingleWordInOperand(kOpDecorateMemberLocationInIdx); |
220 | 0 | *no_loc = false; |
221 | 0 | return false; |
222 | 0 | } |
223 | 0 | return true; |
224 | 0 | }); |
225 | 0 | if (!no_mem_loc) { |
226 | 0 | *offset = loc; |
227 | 0 | curr_type_id = curr_type_inst->GetSingleWordInOperand(index); |
228 | 0 | ocnt++; |
229 | 0 | return true; |
230 | 0 | } |
231 | 0 | } |
232 | | |
233 | | // Update offset and current type based on constant index. |
234 | 0 | *offset += GetLocOffset(index, curr_type_id); |
235 | 0 | curr_type_id = GetComponentType(index, curr_type_id); |
236 | 0 | } |
237 | 0 | ocnt++; |
238 | 0 | return true; |
239 | 0 | }); |
240 | 0 | return curr_type_id; |
241 | 0 | } |
242 | | |
243 | 0 | void LivenessManager::MarkRefLive(const Instruction* ref, Instruction* var) { |
244 | 0 | analysis::TypeManager* type_mgr = context()->get_type_mgr(); |
245 | 0 | analysis::DecorationManager* deco_mgr = context()->get_decoration_mgr(); |
246 | | // Find variable location if present. |
247 | 0 | uint32_t loc = 0; |
248 | 0 | auto var_id = var->result_id(); |
249 | 0 | bool no_loc = deco_mgr->WhileEachDecoration( |
250 | 0 | var_id, uint32_t(spv::Decoration::Location), |
251 | 0 | [&loc](const Instruction& deco) { |
252 | 0 | assert(deco.opcode() == spv::Op::OpDecorate && "unexpected decoration"); |
253 | 0 | loc = deco.GetSingleWordInOperand(kDecorationLocationInIdx); |
254 | 0 | return false; |
255 | 0 | }); |
256 | | // Find patch decoration if present |
257 | 0 | bool is_patch = !deco_mgr->WhileEachDecoration( |
258 | 0 | var_id, uint32_t(spv::Decoration::Patch), [](const Instruction& deco) { |
259 | 0 | if (deco.opcode() != spv::Op::OpDecorate) |
260 | 0 | assert(false && "unexpected decoration"); |
261 | 0 | return false; |
262 | 0 | }); |
263 | | // If use is a load, mark all locations of var |
264 | 0 | auto ptr_type = type_mgr->GetType(var->type_id())->AsPointer(); |
265 | 0 | assert(ptr_type && "unexpected var type"); |
266 | 0 | auto var_type = ptr_type->pointee_type(); |
267 | 0 | if (ref->opcode() == spv::Op::OpLoad) { |
268 | 0 | assert(!no_loc && "missing input variable location"); |
269 | 0 | MarkLocsLive(loc, GetLocSize(var_type)); |
270 | 0 | return; |
271 | 0 | } |
272 | | // Mark just those locations indicated by access chain |
273 | 0 | assert((ref->opcode() == spv::Op::OpAccessChain || |
274 | 0 | ref->opcode() == spv::Op::OpInBoundsAccessChain) && |
275 | 0 | "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 | 0 | uint32_t offset = loc; |
280 | 0 | Instruction* ptr_type_inst = |
281 | 0 | context()->get_def_use_mgr()->GetDef(var->type_id()); |
282 | 0 | assert(ptr_type && "unexpected var type"); |
283 | 0 | const uint32_t kPointerTypePointeeIdx = 1; |
284 | 0 | uint32_t var_type_id = |
285 | 0 | ptr_type_inst->GetSingleWordInOperand(kPointerTypePointeeIdx); |
286 | 0 | uint32_t curr_type_id = |
287 | 0 | AnalyzeAccessChainLoc(ref, var_type_id, &offset, &no_loc, is_patch); |
288 | 0 | auto curr_type = type_mgr->GetType(curr_type_id); |
289 | 0 | assert(!no_loc && "missing input variable location"); |
290 | 0 | MarkLocsLive(offset, GetLocSize(curr_type)); |
291 | 0 | } |
292 | | |
293 | 0 | void LivenessManager::ComputeLiveness() { |
294 | 0 | InitializeAnalysis(); |
295 | 0 | analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr(); |
296 | | // Process all input variables |
297 | 0 | for (auto& var : context()->types_values()) { |
298 | 0 | if (var.opcode() != spv::Op::OpVariable) { |
299 | 0 | continue; |
300 | 0 | } |
301 | 0 | Instruction* var_type_inst = def_use_mgr->GetDef(var.type_id()); |
302 | 0 | assert(var_type_inst->opcode() == spv::Op::OpTypePointer && |
303 | 0 | "Expected a pointer type"); |
304 | 0 | const uint32_t kPointerTypeStorageClassInIdx = 0; |
305 | 0 | spv::StorageClass sc = static_cast<spv::StorageClass>( |
306 | 0 | var_type_inst->GetSingleWordInOperand(kPointerTypeStorageClassInIdx)); |
307 | 0 | if (sc != spv::StorageClass::Input) { |
308 | 0 | continue; |
309 | 0 | } |
310 | | // If var is builtin, mark live if analyzed and continue to next variable |
311 | 0 | auto var_id = var.result_id(); |
312 | 0 | 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 | 0 | const uint32_t kPointerTypePointeeTypeInIdx = 1; |
318 | 0 | uint32_t pte_type_id = |
319 | 0 | var_type_inst->GetSingleWordInOperand(kPointerTypePointeeTypeInIdx); |
320 | 0 | Instruction* pte_type_inst = def_use_mgr->GetDef(pte_type_id); |
321 | 0 | if (pte_type_inst->opcode() == spv::Op::OpTypeArray) { |
322 | 0 | uint32_t array_elt_type_id = pte_type_inst->GetSingleWordInOperand(0); |
323 | 0 | Instruction* arr_elt_type = def_use_mgr->GetDef(array_elt_type_id); |
324 | 0 | if (arr_elt_type->opcode() == spv::Op::OpTypeStruct) { |
325 | 0 | if (AnalyzeBuiltIn(array_elt_type_id)) continue; |
326 | 0 | } |
327 | 0 | } |
328 | | // Mark all used locations of var live |
329 | 0 | def_use_mgr->ForEachUser(var_id, [this, &var](Instruction* user) { |
330 | 0 | auto op = user->opcode(); |
331 | 0 | if (op == spv::Op::OpEntryPoint || op == spv::Op::OpName || |
332 | 0 | op == spv::Op::OpDecorate || user->IsNonSemanticInstruction()) { |
333 | 0 | return; |
334 | 0 | } |
335 | 0 | MarkRefLive(user, &var); |
336 | 0 | }); |
337 | 0 | } |
338 | 0 | } |
339 | | |
340 | | void LivenessManager::GetLiveness(std::unordered_set<uint32_t>* live_locs, |
341 | 0 | std::unordered_set<uint32_t>* live_builtins) { |
342 | 0 | if (!computed_) { |
343 | 0 | ComputeLiveness(); |
344 | 0 | computed_ = true; |
345 | 0 | } |
346 | 0 | *live_locs = live_locs_; |
347 | 0 | *live_builtins = live_builtins_; |
348 | 0 | } |
349 | | |
350 | | } // namespace analysis |
351 | | } // namespace opt |
352 | | } // namespace spvtools |