Coverage Report

Created: 2025-07-04 07:23

/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