Coverage Report

Created: 2025-12-31 06:15

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/spirv-tools/source/val/function.cpp
Line
Count
Source
1
// Copyright (c) 2015-2016 The Khronos Group Inc.
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/val/function.h"
16
17
#include <algorithm>
18
#include <cassert>
19
#include <sstream>
20
#include <unordered_map>
21
#include <utility>
22
23
#include "source/cfa.h"
24
#include "source/val/basic_block.h"
25
#include "source/val/construct.h"
26
#include "source/val/validate.h"
27
28
namespace spvtools {
29
namespace val {
30
31
// Universal Limit of ResultID + 1
32
static const uint32_t kInvalidId = 0x400000;
33
34
Function::Function(uint32_t function_id, uint32_t result_type_id,
35
                   spv::FunctionControlMask function_control,
36
                   uint32_t function_type_id)
37
47.3k
    : id_(function_id),
38
47.3k
      function_type_id_(function_type_id),
39
47.3k
      result_type_id_(result_type_id),
40
47.3k
      function_control_(function_control),
41
47.3k
      declaration_type_(FunctionDecl::kFunctionDeclUnknown),
42
47.3k
      end_has_been_registered_(false),
43
47.3k
      blocks_(),
44
47.3k
      current_block_(nullptr),
45
47.3k
      pseudo_entry_block_(0),
46
47.3k
      pseudo_exit_block_(kInvalidId),
47
47.3k
      cfg_constructs_(),
48
47.3k
      variable_ids_(),
49
47.3k
      parameter_ids_() {}
50
51
523k
bool Function::IsFirstBlock(uint32_t block_id) const {
52
523k
  return !ordered_blocks_.empty() && *first_block() == block_id;
53
523k
}
54
55
spv_result_t Function::RegisterFunctionParameter(uint32_t parameter_id,
56
17.9k
                                                 uint32_t type_id) {
57
17.9k
  assert(current_block_ == nullptr &&
58
17.9k
         "RegisterFunctionParameter can only be called when parsing the binary "
59
17.9k
         "outside of a block");
60
  // TODO(umar): Validate function parameter type order and count
61
  // TODO(umar): Use these variables to validate parameter type
62
17.9k
  (void)parameter_id;
63
17.9k
  (void)type_id;
64
17.9k
  return SPV_SUCCESS;
65
17.9k
}
66
67
spv_result_t Function::RegisterLoopMerge(uint32_t merge_id,
68
77.8k
                                         uint32_t continue_id) {
69
77.8k
  RegisterBlock(merge_id, false);
70
77.8k
  RegisterBlock(continue_id, false);
71
77.8k
  BasicBlock& merge_block = blocks_.at(merge_id);
72
77.8k
  BasicBlock& continue_target_block = blocks_.at(continue_id);
73
77.8k
  assert(current_block_ &&
74
77.8k
         "RegisterLoopMerge must be called when called within a block");
75
77.8k
  current_block_->RegisterStructuralSuccessor(&merge_block);
76
77.8k
  current_block_->RegisterStructuralSuccessor(&continue_target_block);
77
78
77.8k
  current_block_->set_type(kBlockTypeLoop);
79
77.8k
  merge_block.set_type(kBlockTypeMerge);
80
77.8k
  continue_target_block.set_type(kBlockTypeContinue);
81
77.8k
  Construct& loop_construct =
82
77.8k
      AddConstruct({ConstructType::kLoop, current_block_, &merge_block});
83
77.8k
  Construct& continue_construct =
84
77.8k
      AddConstruct({ConstructType::kContinue, &continue_target_block});
85
86
77.8k
  continue_construct.set_corresponding_constructs({&loop_construct});
87
77.8k
  loop_construct.set_corresponding_constructs({&continue_construct});
88
77.8k
  merge_block_header_[&merge_block] = current_block_;
89
77.8k
  if (continue_target_headers_.find(&continue_target_block) ==
90
77.8k
      continue_target_headers_.end()) {
91
77.3k
    continue_target_headers_[&continue_target_block] = {current_block_};
92
77.3k
  } else {
93
461
    continue_target_headers_[&continue_target_block].push_back(current_block_);
94
461
  }
95
96
77.8k
  return SPV_SUCCESS;
97
77.8k
}
98
99
78.7k
spv_result_t Function::RegisterSelectionMerge(uint32_t merge_id) {
100
78.7k
  RegisterBlock(merge_id, false);
101
78.7k
  BasicBlock& merge_block = blocks_.at(merge_id);
102
78.7k
  current_block_->set_type(kBlockTypeSelection);
103
78.7k
  merge_block.set_type(kBlockTypeMerge);
104
78.7k
  merge_block_header_[&merge_block] = current_block_;
105
78.7k
  current_block_->RegisterStructuralSuccessor(&merge_block);
106
107
78.7k
  AddConstruct({ConstructType::kSelection, current_block(), &merge_block});
108
109
78.7k
  return SPV_SUCCESS;
110
78.7k
}
111
112
47.1k
spv_result_t Function::RegisterSetFunctionDeclType(FunctionDecl type) {
113
47.1k
  assert(declaration_type_ == FunctionDecl::kFunctionDeclUnknown);
114
47.1k
  declaration_type_ = type;
115
47.1k
  return SPV_SUCCESS;
116
47.1k
}
117
118
672k
spv_result_t Function::RegisterBlock(uint32_t block_id, bool is_definition) {
119
672k
  assert(
120
672k
      declaration_type_ == FunctionDecl::kFunctionDeclDefinition &&
121
672k
      "RegisterBlocks can only be called after declaration_type_ is defined");
122
123
672k
  std::unordered_map<uint32_t, BasicBlock>::iterator inserted_block;
124
672k
  bool success = false;
125
672k
  tie(inserted_block, success) =
126
672k
      blocks_.insert({block_id, BasicBlock(block_id)});
127
672k
  if (is_definition) {  // new block definition
128
438k
    assert(current_block_ == nullptr &&
129
438k
           "Register Block can only be called when parsing a binary outside of "
130
438k
           "a BasicBlock");
131
132
438k
    undefined_blocks_.erase(block_id);
133
438k
    current_block_ = &inserted_block->second;
134
438k
    ordered_blocks_.push_back(current_block_);
135
438k
  } else if (success) {  // Block doesn't exist but this is not a definition
136
227k
    undefined_blocks_.insert(block_id);
137
227k
  }
138
139
672k
  return SPV_SUCCESS;
140
672k
}
141
142
436k
void Function::RegisterBlockEnd(std::vector<uint32_t> next_list) {
143
436k
  assert(
144
436k
      current_block_ &&
145
436k
      "RegisterBlockEnd can only be called when parsing a binary in a block");
146
436k
  std::vector<BasicBlock*> next_blocks;
147
436k
  next_blocks.reserve(next_list.size());
148
149
436k
  std::unordered_map<uint32_t, BasicBlock>::iterator inserted_block;
150
436k
  bool success;
151
523k
  for (uint32_t successor_id : next_list) {
152
523k
    tie(inserted_block, success) =
153
523k
        blocks_.insert({successor_id, BasicBlock(successor_id)});
154
523k
    if (success) {
155
243k
      undefined_blocks_.insert(successor_id);
156
243k
    }
157
523k
    next_blocks.push_back(&inserted_block->second);
158
523k
  }
159
160
436k
  if (current_block_->is_type(kBlockTypeLoop)) {
161
    // For each loop header, record the set of its successors, and include
162
    // its continue target if the continue target is not the loop header
163
    // itself.
164
43.9k
    std::vector<BasicBlock*>& next_blocks_plus_continue_target =
165
43.9k
        loop_header_successors_plus_continue_target_map_[current_block_];
166
43.9k
    next_blocks_plus_continue_target = next_blocks;
167
43.9k
    auto continue_target =
168
43.9k
        FindConstructForEntryBlock(current_block_, ConstructType::kLoop)
169
43.9k
            .corresponding_constructs()
170
43.9k
            .back()
171
43.9k
            ->entry_block();
172
43.9k
    if (continue_target != current_block_) {
173
41.8k
      next_blocks_plus_continue_target.push_back(continue_target);
174
41.8k
    }
175
43.9k
  }
176
177
436k
  current_block_->RegisterSuccessors(next_blocks);
178
436k
  current_block_ = nullptr;
179
436k
  return;
180
436k
}
181
182
44.4k
void Function::RegisterFunctionEnd() {
183
44.4k
  if (!end_has_been_registered_) {
184
44.4k
    end_has_been_registered_ = true;
185
186
44.4k
    ComputeAugmentedCFG();
187
44.4k
  }
188
44.4k
}
189
190
91.9k
size_t Function::block_count() const { return blocks_.size(); }
191
192
32.2k
size_t Function::undefined_block_count() const {
193
32.2k
  return undefined_blocks_.size();
194
32.2k
}
195
196
0
const std::vector<BasicBlock*>& Function::ordered_blocks() const {
197
0
  return ordered_blocks_;
198
0
}
199
128k
std::vector<BasicBlock*>& Function::ordered_blocks() { return ordered_blocks_; }
200
201
5.28M
const BasicBlock* Function::current_block() const { return current_block_; }
202
3.58M
BasicBlock* Function::current_block() { return current_block_; }
203
204
0
const std::list<Construct>& Function::constructs() const {
205
0
  return cfg_constructs_;
206
0
}
207
63.5k
std::list<Construct>& Function::constructs() { return cfg_constructs_; }
208
209
523k
const BasicBlock* Function::first_block() const {
210
523k
  if (ordered_blocks_.empty()) return nullptr;
211
523k
  return ordered_blocks_[0];
212
523k
}
213
141k
BasicBlock* Function::first_block() {
214
141k
  if (ordered_blocks_.empty()) return nullptr;
215
138k
  return ordered_blocks_[0];
216
141k
}
217
218
195k
bool Function::IsBlockType(uint32_t merge_block_id, BlockType type) const {
219
195k
  bool ret = false;
220
195k
  const BasicBlock* block;
221
195k
  std::tie(block, std::ignore) = GetBlock(merge_block_id);
222
195k
  if (block) {
223
41.4k
    ret = block->is_type(type);
224
41.4k
  }
225
195k
  return ret;
226
195k
}
227
228
483k
std::pair<const BasicBlock*, bool> Function::GetBlock(uint32_t block_id) const {
229
483k
  const auto b = blocks_.find(block_id);
230
483k
  if (b != end(blocks_)) {
231
329k
    const BasicBlock* block = &(b->second);
232
329k
    bool defined =
233
329k
        undefined_blocks_.find(block->id()) == std::end(undefined_blocks_);
234
329k
    return std::make_pair(block, defined);
235
329k
  } else {
236
153k
    return std::make_pair(nullptr, false);
237
153k
  }
238
483k
}
239
240
164k
std::pair<BasicBlock*, bool> Function::GetBlock(uint32_t block_id) {
241
164k
  const BasicBlock* out;
242
164k
  bool defined;
243
164k
  std::tie(out, defined) =
244
164k
      const_cast<const Function*>(this)->GetBlock(block_id);
245
164k
  return std::make_pair(const_cast<BasicBlock*>(out), defined);
246
164k
}
247
248
31.3k
Function::GetBlocksFunction Function::AugmentedCFGSuccessorsFunction() const {
249
1.06M
  return [this](const BasicBlock* block) {
250
1.06M
    auto where = augmented_successors_map_.find(block);
251
1.06M
    return where == augmented_successors_map_.end() ? block->successors()
252
1.06M
                                                    : &(*where).second;
253
1.06M
  };
254
31.3k
}
255
256
31.3k
Function::GetBlocksFunction Function::AugmentedCFGPredecessorsFunction() const {
257
623k
  return [this](const BasicBlock* block) {
258
623k
    auto where = augmented_predecessors_map_.find(block);
259
623k
    return where == augmented_predecessors_map_.end() ? block->predecessors()
260
623k
                                                      : &(*where).second;
261
623k
  };
262
31.3k
}
263
264
Function::GetBlocksFunction Function::AugmentedStructuralCFGSuccessorsFunction()
265
93.7k
    const {
266
3.61M
  return [this](const BasicBlock* block) {
267
3.61M
    auto where = augmented_successors_map_.find(block);
268
3.61M
    return where == augmented_successors_map_.end()
269
3.61M
               ? block->structural_successors()
270
3.61M
               : &(*where).second;
271
3.61M
  };
272
93.7k
}
273
274
Function::GetBlocksFunction
275
62.4k
Function::AugmentedStructuralCFGPredecessorsFunction() const {
276
2.16M
  return [this](const BasicBlock* block) {
277
2.16M
    auto where = augmented_predecessors_map_.find(block);
278
2.16M
    return where == augmented_predecessors_map_.end()
279
2.16M
               ? block->structural_predecessors()
280
2.16M
               : &(*where).second;
281
2.16M
  };
282
62.4k
}
283
284
44.4k
void Function::ComputeAugmentedCFG() {
285
  // Compute the successors of the pseudo-entry block, and
286
  // the predecessors of the pseudo exit block.
287
3.08M
  auto succ_func = [](const BasicBlock* b) {
288
3.08M
    return b->structural_successors();
289
3.08M
  };
290
4.07M
  auto pred_func = [](const BasicBlock* b) {
291
4.07M
    return b->structural_predecessors();
292
4.07M
  };
293
44.4k
  CFA<BasicBlock>::ComputeAugmentedCFG(
294
44.4k
      ordered_blocks_, &pseudo_entry_block_, &pseudo_exit_block_,
295
44.4k
      &augmented_successors_map_, &augmented_predecessors_map_, succ_func,
296
44.4k
      pred_func);
297
44.4k
}
298
299
234k
Construct& Function::AddConstruct(const Construct& new_construct) {
300
234k
  cfg_constructs_.push_back(new_construct);
301
234k
  auto& result = cfg_constructs_.back();
302
234k
  entry_block_to_construct_[std::make_pair(new_construct.entry_block(),
303
234k
                                           new_construct.type())] = &result;
304
234k
  return result;
305
234k
}
306
307
Construct& Function::FindConstructForEntryBlock(const BasicBlock* entry_block,
308
43.9k
                                                ConstructType type) {
309
43.9k
  auto where =
310
43.9k
      entry_block_to_construct_.find(std::make_pair(entry_block, type));
311
43.9k
  assert(where != entry_block_to_construct_.end());
312
43.9k
  auto construct_ptr = (*where).second;
313
43.9k
  assert(construct_ptr);
314
43.9k
  return *construct_ptr;
315
43.9k
}
316
317
621k
int Function::GetBlockDepth(BasicBlock* bb) {
318
  // Guard against nullptr.
319
621k
  if (!bb) {
320
0
    return 0;
321
0
  }
322
  // Only calculate the depth if it's not already calculated.
323
  // This function uses memoization to avoid duplicate CFG depth calculations.
324
621k
  if (block_depth_.find(bb) != block_depth_.end()) {
325
267k
    return block_depth_[bb];
326
267k
  }
327
  // Avoid recursion. Something is wrong if the same block is encountered
328
  // multiple times.
329
353k
  block_depth_[bb] = 0;
330
331
353k
  BasicBlock* bb_dom = bb->immediate_dominator();
332
353k
  if (!bb_dom || bb == bb_dom) {
333
    // This block has no dominator, so it's at depth 0.
334
85.7k
    block_depth_[bb] = 0;
335
267k
  } else if (bb->is_type(kBlockTypeContinue)) {
336
    // This rule must precede the rule for merge blocks in order to set up
337
    // depths correctly. If a block is both a merge and continue then the merge
338
    // is nested within the continue's loop (or the graph is incorrect).
339
    // The depth of the continue block entry point is 1 + loop header depth.
340
28.9k
    Construct* continue_construct =
341
28.9k
        entry_block_to_construct_[std::make_pair(bb, ConstructType::kContinue)];
342
28.9k
    assert(continue_construct);
343
    // Continue construct has only 1 corresponding construct (loop header).
344
28.9k
    Construct* loop_construct =
345
28.9k
        continue_construct->corresponding_constructs()[0];
346
28.9k
    assert(loop_construct);
347
28.9k
    BasicBlock* loop_header = loop_construct->entry_block();
348
    // The continue target may be the loop itself (while 1).
349
    // In such cases, the depth of the continue block is: 1 + depth of the
350
    // loop's dominator block.
351
28.9k
    if (loop_header == bb) {
352
1.80k
      block_depth_[bb] = 1 + GetBlockDepth(bb_dom);
353
27.1k
    } else {
354
27.1k
      block_depth_[bb] = 1 + GetBlockDepth(loop_header);
355
27.1k
    }
356
238k
  } else if (bb->is_type(kBlockTypeMerge)) {
357
    // If this is a merge block, its depth is equal to the block before
358
    // branching.
359
80.9k
    BasicBlock* header = merge_block_header_[bb];
360
80.9k
    assert(header);
361
80.9k
    block_depth_[bb] = GetBlockDepth(header);
362
157k
  } else if (bb_dom->is_type(kBlockTypeSelection) ||
363
102k
             bb_dom->is_type(kBlockTypeLoop)) {
364
    // The dominator of the given block is a header block. So, the nesting
365
    // depth of this block is: 1 + nesting depth of the header.
366
102k
    block_depth_[bb] = 1 + GetBlockDepth(bb_dom);
367
102k
  } else {
368
55.4k
    block_depth_[bb] = GetBlockDepth(bb_dom);
369
55.4k
  }
370
353k
  return block_depth_[bb];
371
353k
}
372
373
void Function::RegisterExecutionModelLimitation(spv::ExecutionModel model,
374
12.5k
                                                const std::string& message) {
375
12.5k
  execution_model_limitations_.push_back(
376
12.5k
      [model, message](spv::ExecutionModel in_model, std::string* out_message) {
377
3.98k
        if (model != in_model) {
378
235
          if (out_message) {
379
235
            *out_message = message;
380
235
          }
381
235
          return false;
382
235
        }
383
3.74k
        return true;
384
3.98k
      });
385
12.5k
}
386
387
bool Function::IsCompatibleWithExecutionModel(spv::ExecutionModel model,
388
25.5k
                                              std::string* reason) const {
389
25.5k
  bool return_value = true;
390
25.5k
  std::stringstream ss_reason;
391
392
25.5k
  for (const auto& is_compatible : execution_model_limitations_) {
393
8.25k
    std::string message;
394
8.25k
    if (!is_compatible(model, &message)) {
395
516
      if (!reason) return false;
396
516
      return_value = false;
397
516
      if (!message.empty()) {
398
516
        ss_reason << message << "\n";
399
516
      }
400
516
    }
401
8.25k
  }
402
403
25.5k
  if (!return_value && reason) {
404
124
    *reason = ss_reason.str();
405
124
  }
406
407
25.5k
  return return_value;
408
25.5k
}
409
410
bool Function::CheckLimitations(const ValidationState_t& _,
411
                                const Function* entry_point,
412
25.0k
                                std::string* reason) const {
413
25.0k
  bool return_value = true;
414
25.0k
  std::stringstream ss_reason;
415
416
25.0k
  for (const auto& is_compatible : limitations_) {
417
3.14k
    std::string message;
418
3.14k
    if (!is_compatible(_, entry_point, &message)) {
419
22
      if (!reason) return false;
420
22
      return_value = false;
421
22
      if (!message.empty()) {
422
22
        ss_reason << message << "\n";
423
22
      }
424
22
    }
425
3.14k
  }
426
427
25.0k
  if (!return_value && reason) {
428
14
    *reason = ss_reason.str();
429
14
  }
430
431
25.0k
  return return_value;
432
25.0k
}
433
434
}  // namespace val
435
}  // namespace spvtools