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,const analysis::Type * agg_type) const126 const analysis::Type* LivenessManager::GetComponentType(
127 uint32_t index, const analysis::Type* agg_type) const {
128 auto arr_type = agg_type->AsArray();
129 if (arr_type) return arr_type->element_type();
130 auto struct_type = agg_type->AsStruct();
131 if (struct_type) return struct_type->element_types()[index];
132 auto mat_type = agg_type->AsMatrix();
133 if (mat_type) return mat_type->element_type();
134 auto vec_type = agg_type->AsVector();
135 assert(vec_type && "unexpected non-aggregate type");
136 return vec_type->element_type();
137 }
138
GetLocOffset(uint32_t index,const analysis::Type * agg_type) const139 uint32_t LivenessManager::GetLocOffset(uint32_t index,
140 const analysis::Type* agg_type) const {
141 auto arr_type = agg_type->AsArray();
142 if (arr_type) return index * GetLocSize(arr_type->element_type());
143 auto struct_type = agg_type->AsStruct();
144 if (struct_type) {
145 uint32_t offset = 0u;
146 uint32_t cnt = 0u;
147 for (auto& el_type : struct_type->element_types()) {
148 if (cnt == index) break;
149 offset += GetLocSize(el_type);
150 ++cnt;
151 }
152 return offset;
153 }
154 auto mat_type = agg_type->AsMatrix();
155 if (mat_type) return index * GetLocSize(mat_type->element_type());
156 auto vec_type = agg_type->AsVector();
157 assert(vec_type && "unexpected non-aggregate type");
158 auto comp_type = vec_type->element_type();
159 auto flt_type = comp_type->AsFloat();
160 if (flt_type && flt_type->width() == 64u && index >= 2u) return 1;
161 return 0;
162 }
163
AnalyzeAccessChainLoc(const Instruction * ac,const analysis::Type ** curr_type,uint32_t * offset,bool * no_loc,bool is_patch,bool input)164 void LivenessManager::AnalyzeAccessChainLoc(const Instruction* ac,
165 const analysis::Type** curr_type,
166 uint32_t* offset, bool* no_loc,
167 bool is_patch, bool input) {
168 analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr();
169 analysis::TypeManager* type_mgr = context()->get_type_mgr();
170 analysis::DecorationManager* deco_mgr = context()->get_decoration_mgr();
171 // For tesc, tese and geom input variables, and tesc output variables,
172 // first array index does not contribute to offset.
173 auto stage = context()->GetStage();
174 bool skip_first_index = false;
175 if ((input && (stage == spv::ExecutionModel::TessellationControl ||
176 stage == spv::ExecutionModel::TessellationEvaluation ||
177 stage == spv::ExecutionModel::Geometry)) ||
178 (!input && stage == spv::ExecutionModel::TessellationControl))
179 skip_first_index = !is_patch;
180 uint32_t ocnt = 0;
181 ac->WhileEachInOperand([this, &ocnt, def_use_mgr, type_mgr, deco_mgr,
182 curr_type, offset, no_loc,
183 skip_first_index](const uint32_t* opnd) {
184 if (ocnt >= 1) {
185 // Skip first index's contribution to offset if indicated
186 if (ocnt == 1 && skip_first_index) {
187 auto arr_type = (*curr_type)->AsArray();
188 assert(arr_type && "unexpected wrapper type");
189 *curr_type = arr_type->element_type();
190 ocnt++;
191 return true;
192 }
193 // If any non-constant index, mark the entire current object and return.
194 auto idx_inst = def_use_mgr->GetDef(*opnd);
195 if (idx_inst->opcode() != spv::Op::OpConstant) return false;
196 // If current type is struct, look for location decoration on member and
197 // reset offset if found.
198 auto index = idx_inst->GetSingleWordInOperand(0);
199 auto str_type = (*curr_type)->AsStruct();
200 if (str_type) {
201 uint32_t loc = 0;
202 auto str_type_id = type_mgr->GetId(str_type);
203 bool no_mem_loc = deco_mgr->WhileEachDecoration(
204 str_type_id, uint32_t(spv::Decoration::Location),
205 [&loc, index, no_loc](const Instruction& deco) {
206 assert(deco.opcode() == spv::Op::OpMemberDecorate &&
207 "unexpected decoration");
208 if (deco.GetSingleWordInOperand(kOpDecorateMemberMemberInIdx) ==
209 index) {
210 loc =
211 deco.GetSingleWordInOperand(kOpDecorateMemberLocationInIdx);
212 *no_loc = false;
213 return false;
214 }
215 return true;
216 });
217 if (!no_mem_loc) {
218 *offset = loc;
219 *curr_type = GetComponentType(index, *curr_type);
220 ocnt++;
221 return true;
222 }
223 }
224
225 // Update offset and current type based on constant index.
226 *offset += GetLocOffset(index, *curr_type);
227 *curr_type = GetComponentType(index, *curr_type);
228 }
229 ocnt++;
230 return true;
231 });
232 }
233
MarkRefLive(const Instruction * ref,Instruction * var)234 void LivenessManager::MarkRefLive(const Instruction* ref, Instruction* var) {
235 analysis::TypeManager* type_mgr = context()->get_type_mgr();
236 analysis::DecorationManager* deco_mgr = context()->get_decoration_mgr();
237 // Find variable location if present.
238 uint32_t loc = 0;
239 auto var_id = var->result_id();
240 bool no_loc = deco_mgr->WhileEachDecoration(
241 var_id, uint32_t(spv::Decoration::Location),
242 [&loc](const Instruction& deco) {
243 assert(deco.opcode() == spv::Op::OpDecorate && "unexpected decoration");
244 loc = deco.GetSingleWordInOperand(kDecorationLocationInIdx);
245 return false;
246 });
247 // Find patch decoration if present
248 bool is_patch = !deco_mgr->WhileEachDecoration(
249 var_id, uint32_t(spv::Decoration::Patch), [](const Instruction& deco) {
250 if (deco.opcode() != spv::Op::OpDecorate)
251 assert(false && "unexpected decoration");
252 return false;
253 });
254 // If use is a load, mark all locations of var
255 auto ptr_type = type_mgr->GetType(var->type_id())->AsPointer();
256 assert(ptr_type && "unexpected var type");
257 auto var_type = ptr_type->pointee_type();
258 if (ref->opcode() == spv::Op::OpLoad) {
259 assert(!no_loc && "missing input variable location");
260 MarkLocsLive(loc, GetLocSize(var_type));
261 return;
262 }
263 // Mark just those locations indicated by access chain
264 assert((ref->opcode() == spv::Op::OpAccessChain ||
265 ref->opcode() == spv::Op::OpInBoundsAccessChain) &&
266 "unexpected use of input variable");
267 // Traverse access chain, compute location offset and type of reference
268 // through constant indices and mark those locs live. Assert if no location
269 // found.
270 uint32_t offset = loc;
271 auto curr_type = var_type;
272 AnalyzeAccessChainLoc(ref, &curr_type, &offset, &no_loc, is_patch);
273 assert(!no_loc && "missing input variable location");
274 MarkLocsLive(offset, GetLocSize(curr_type));
275 }
276
ComputeLiveness()277 void LivenessManager::ComputeLiveness() {
278 InitializeAnalysis();
279 analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr();
280 analysis::TypeManager* type_mgr = context()->get_type_mgr();
281 // Process all input variables
282 for (auto& var : context()->types_values()) {
283 if (var.opcode() != spv::Op::OpVariable) {
284 continue;
285 }
286 analysis::Type* var_type = type_mgr->GetType(var.type_id());
287 analysis::Pointer* ptr_type = var_type->AsPointer();
288 if (ptr_type->storage_class() != spv::StorageClass::Input) {
289 continue;
290 }
291 // If var is builtin, mark live if analyzed and continue to next variable
292 auto var_id = var.result_id();
293 if (AnalyzeBuiltIn(var_id)) continue;
294 // If interface block with builtin members, mark live if analyzed and
295 // continue to next variable. Input interface blocks will only appear
296 // in tesc, tese and geom shaders. Will need to strip off one level of
297 // arrayness to get to block type.
298 auto pte_type = ptr_type->pointee_type();
299 auto arr_type = pte_type->AsArray();
300 if (arr_type) {
301 auto elt_type = arr_type->element_type();
302 auto str_type = elt_type->AsStruct();
303 if (str_type) {
304 auto str_type_id = type_mgr->GetId(str_type);
305 if (AnalyzeBuiltIn(str_type_id)) continue;
306 }
307 }
308 // Mark all used locations of var live
309 def_use_mgr->ForEachUser(var_id, [this, &var](Instruction* user) {
310 auto op = user->opcode();
311 if (op == spv::Op::OpEntryPoint || op == spv::Op::OpName ||
312 op == spv::Op::OpDecorate || user->IsNonSemanticInstruction()) {
313 return;
314 }
315 MarkRefLive(user, &var);
316 });
317 }
318 }
319
GetLiveness(std::unordered_set<uint32_t> * live_locs,std::unordered_set<uint32_t> * live_builtins)320 void LivenessManager::GetLiveness(std::unordered_set<uint32_t>* live_locs,
321 std::unordered_set<uint32_t>* live_builtins) {
322 if (!computed_) {
323 ComputeLiveness();
324 computed_ = true;
325 }
326 *live_locs = live_locs_;
327 *live_builtins = live_builtins_;
328 }
329
330 } // namespace analysis
331 } // namespace opt
332 } // namespace spvtools
333