File: available_instructions.cpp

package info (click to toggle)
spirv-tools 2023.1-2
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 25,608 kB
  • sloc: cpp: 408,882; javascript: 5,890; python: 2,847; ansic: 403; sh: 385; ruby: 88; makefile: 17; lisp: 9
file content (191 lines) | stat: -rw-r--r-- 8,226 bytes parent folder | download | duplicates (15)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
// Copyright (c) 2021 Alastair F. Donaldson
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "source/fuzz/available_instructions.h"
#include "source/fuzz/fuzzer_util.h"

namespace spvtools {
namespace fuzz {

AvailableInstructions::AvailableInstructions(
    opt::IRContext* ir_context,
    const std::function<bool(opt::IRContext*, opt::Instruction*)>& predicate)
    : ir_context_(ir_context) {
  // Consider all global declarations
  for (auto& global : ir_context->module()->types_values()) {
    if (predicate(ir_context, &global)) {
      available_globals_.push_back(&global);
    }
  }

  // Consider every function
  for (auto& function : *ir_context->module()) {
    // Identify those function parameters that satisfy the predicate.
    std::vector<opt::Instruction*> available_params_for_function;
    function.ForEachParam(
        [&predicate, ir_context,
         &available_params_for_function](opt::Instruction* param) {
          if (predicate(ir_context, param)) {
            available_params_for_function.push_back(param);
          }
        });

    // Consider every reachable block in the function.
    auto dominator_analysis = ir_context->GetDominatorAnalysis(&function);
    for (auto& block : function) {
      if (!ir_context->IsReachable(block)) {
        // The block is not reachable.
        continue;
      }
      if (&block == &*function.begin()) {
        // The function entry block is special: only the relevant globals and
        // function parameters are available at its entry point.
        num_available_at_block_entry_.insert(
            {&block,
             static_cast<uint32_t>(available_params_for_function.size() +
                                   available_globals_.size())});
      } else {
        // |block| is not the entry block and is reachable, so it must have an
        // immediate dominator. The number of instructions available on entry to
        // |block| is thus the number of instructions available on entry to the
        // immediate dominator + the number of instructions generated_by_block
        // by the immediate dominator.
        auto immediate_dominator =
            dominator_analysis->ImmediateDominator(&block);
        assert(immediate_dominator != nullptr &&
               "The block is reachable so should have an immediate dominator.");
        assert(generated_by_block_.count(immediate_dominator) != 0 &&
               "Immediate dominator should have already been processed.");
        assert(num_available_at_block_entry_.count(immediate_dominator) != 0 &&
               "Immediate dominator should have already been processed.");
        num_available_at_block_entry_.insert(
            {&block,
             static_cast<uint32_t>(
                 generated_by_block_.at(immediate_dominator).size()) +
                 num_available_at_block_entry_.at(immediate_dominator)});
      }
      // Now consider each instruction in the block.
      std::vector<opt::Instruction*> generated_by_block;
      for (auto& inst : block) {
        assert(num_available_at_block_entry_.count(&block) != 0 &&
               "Block should have already been processed.");
        // The number of available instructions before |inst| is the number
        // available at the start of the block + the number of relevant
        // instructions generated by the block so far.
        num_available_before_instruction_.insert(
            {&inst, num_available_at_block_entry_.at(&block) +
                        static_cast<uint32_t>(generated_by_block.size())});
        if (predicate(ir_context, &inst)) {
          // This instruction satisfies the predicate, so note that it is
          // generated by |block|.
          generated_by_block.push_back(&inst);
        }
      }
      generated_by_block_.emplace(&block, std::move(generated_by_block));
    }
    available_params_.emplace(&function,
                              std::move(available_params_for_function));
  }
}

AvailableInstructions::AvailableBeforeInstruction
AvailableInstructions::GetAvailableBeforeInstruction(
    opt::Instruction* inst) const {
  assert(num_available_before_instruction_.count(inst) != 0 &&
         "Availability can only be queried for reachable instructions.");
  return {*this, inst};
}

AvailableInstructions::AvailableBeforeInstruction::AvailableBeforeInstruction(
    const AvailableInstructions& available_instructions, opt::Instruction* inst)
    : available_instructions_(available_instructions), inst_(inst) {}

uint32_t AvailableInstructions::AvailableBeforeInstruction::size() const {
  return available_instructions_.num_available_before_instruction_.at(inst_);
}

bool AvailableInstructions::AvailableBeforeInstruction::empty() const {
  return size() == 0;
}

opt::Instruction* AvailableInstructions::AvailableBeforeInstruction::operator[](
    uint32_t index) const {
  assert(index < size() && "Index out of bounds.");

  // First, check the cache to see whether we can return the available
  // instruction in constant time.
  auto cached_result = index_cache.find(index);
  if (cached_result != index_cache.end()) {
    return cached_result->second;
  }

  // Next check whether the index falls into the global region.
  if (index < available_instructions_.available_globals_.size()) {
    auto result = available_instructions_.available_globals_[index];
    index_cache.insert({index, result});
    return result;
  }

  auto block = available_instructions_.ir_context_->get_instr_block(inst_);
  auto function = block->GetParent();

  // Next check whether the index falls into the available instructions that
  // correspond to function parameters.
  if (index <
      available_instructions_.available_globals_.size() +
          available_instructions_.available_params_.at(function).size()) {
    auto result = available_instructions_.available_params_.at(
        function)[index - available_instructions_.available_globals_.size()];
    index_cache.insert({index, result});
    return result;
  }

  auto dominator_analysis =
      available_instructions_.ir_context_->GetDominatorAnalysis(function);

  // Now the expensive part (which is why we have the cache): walk the dominator
  // tree backwards starting from the block containing |inst_| until we get to
  // the block in which the instruction corresponding to |index| exists.
  for (auto* ancestor = block; true;
       ancestor = dominator_analysis->ImmediateDominator(ancestor)) {
    uint32_t num_available_at_ancestor_entry =
        available_instructions_.num_available_at_block_entry_.at(ancestor);
    if (index_cache.count(num_available_at_ancestor_entry) == 0) {
      // This is the first time we have traversed this block, so we populate the
      // cache with the index of each instruction, so that if a future index
      // query relates to indices associated with this block we can return the
      // result in constant time.
      auto& generated_by_ancestor =
          available_instructions_.generated_by_block_.at(ancestor);
      for (uint32_t local_index = 0; local_index < generated_by_ancestor.size();
           local_index++) {
        index_cache.insert({num_available_at_ancestor_entry + local_index,
                            generated_by_ancestor[local_index]});
      }
    }
    if (index >= num_available_at_ancestor_entry) {
      // This block contains the instruction we want, so by now it will be in
      // the cache.
      return index_cache.at(index);
    }
    assert(ancestor != &*function->begin() &&
           "By construction we should find a block associated with the index.");
  }

  assert(false && "Unreachable.");
  return nullptr;
}

}  // namespace fuzz
}  // namespace spvtools