Coverage Report

Created: 2026-06-30 06:51

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/spirv-tools/source/val/validate_id.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 <unordered_set>
16
#include <vector>
17
18
#include "source/instruction.h"
19
#include "source/opcode.h"
20
#include "source/operand.h"
21
#include "source/val/function.h"
22
#include "source/val/validate.h"
23
#include "source/val/validation_state.h"
24
#include "spirv-tools/libspirv.h"
25
26
namespace spvtools {
27
namespace val {
28
29
14.7M
spv_result_t UpdateIdUse(ValidationState_t& _, const Instruction* inst) {
30
22.2M
  for (auto& operand : inst->operands()) {
31
22.2M
    const spv_operand_type_t& type = operand.type;
32
22.2M
    const uint32_t operand_id = inst->word(operand.offset);
33
22.2M
    if (spvIsIdType(type) && type != SPV_OPERAND_TYPE_RESULT_ID) {
34
17.3M
      if (auto def = _.FindDef(operand_id))
35
17.3M
        def->RegisterUse(inst, operand.offset);
36
17.3M
    }
37
22.2M
  }
38
39
14.7M
  return SPV_SUCCESS;
40
14.7M
}
41
42
/// This function checks all ID definitions dominate their use in the CFG.
43
///
44
/// This function will iterate over all ID definitions that are defined in the
45
/// functions of a module and make sure that the definitions appear in a
46
/// block that dominates their use.
47
///
48
/// NOTE: This function does NOT check module scoped functions which are
49
/// checked during the initial binary parse in the IdPass below
50
26.2k
spv_result_t CheckIdDefinitionDominateUse(ValidationState_t& _) {
51
26.2k
  std::vector<const Instruction*> phi_instructions;
52
26.2k
  std::unordered_set<uint32_t> phi_ids;
53
13.9M
  for (const auto& inst : _.ordered_instructions()) {
54
13.9M
    if (inst.id() == 0) continue;
55
2.19M
    if (const Function* func = inst.function()) {
56
1.59M
      if (const BasicBlock* block = inst.block()) {
57
        // If the Id is defined within a block then make sure all references to
58
        // that Id appear in a blocks that are dominated by the defining block
59
1.84M
        for (auto& use_index_pair : inst.uses()) {
60
1.84M
          const Instruction* use = use_index_pair.first;
61
1.84M
          if (const BasicBlock* use_block = use->block()) {
62
1.72M
            if (use_block->reachable() == false) continue;
63
1.64M
            if (use->opcode() == spv::Op::OpPhi) {
64
87.5k
              if (phi_ids.insert(use->id()).second) {
65
57.3k
                phi_instructions.push_back(use);
66
57.3k
              }
67
1.55M
            } else if (!block->dominates(*use->block())) {
68
72
              return _.diag(SPV_ERROR_INVALID_ID, use_block->label())
69
72
                     << "ID " << _.getIdName(inst.id()) << " defined in block "
70
72
                     << _.getIdName(block->id())
71
72
                     << " does not dominate its use in block "
72
72
                     << _.getIdName(use_block->id());
73
72
            }
74
1.64M
          }
75
1.84M
        }
76
1.20M
      } else {
77
        // If the Ids defined within a function but not in a block(i.e. function
78
        // parameters, block ids), then make sure all references to that Id
79
        // appear within the same function
80
738k
        for (auto use : inst.uses()) {
81
738k
          const Instruction* user = use.first;
82
738k
          if (user->function() && user->function() != func) {
83
15
            return _.diag(SPV_ERROR_INVALID_ID, _.FindDef(func->id()))
84
15
                   << "ID " << _.getIdName(inst.id()) << " used in function "
85
15
                   << _.getIdName(user->function()->id())
86
15
                   << " is used outside of it's defining function "
87
15
                   << _.getIdName(func->id());
88
15
          }
89
738k
        }
90
394k
      }
91
1.59M
    }
92
    // NOTE: Ids defined outside of functions must appear before they are used
93
    // This check is being performed in the IdPass function
94
2.19M
  }
95
96
  // Check all OpPhi parent blocks are dominated by the variable's defining
97
  // blocks
98
57.1k
  for (const Instruction* phi : phi_instructions) {
99
57.1k
    if (phi->block()->reachable() == false) continue;
100
175k
    for (size_t i = 3; i < phi->operands().size(); i += 2) {
101
118k
      const Instruction* variable = _.FindDef(phi->word(i));
102
118k
      const BasicBlock* parent =
103
118k
          phi->function()->GetBlock(phi->word(i + 1)).first;
104
118k
      if (variable->block() && parent->reachable() &&
105
84.9k
          !variable->block()->dominates(*parent)) {
106
31
        return _.diag(SPV_ERROR_INVALID_ID, phi)
107
31
               << "In OpPhi instruction " << _.getIdName(phi->id()) << ", ID "
108
31
               << _.getIdName(variable->id())
109
31
               << " definition does not dominate its parent "
110
31
               << _.getIdName(parent->id());
111
31
      }
112
118k
    }
113
57.1k
  }
114
115
26.0k
  return SPV_SUCCESS;
116
26.1k
}
117
118
334k
bool InstructionCanHaveTypeOperand(const Instruction* inst) {
119
334k
  static std::unordered_set<spv::Op> instruction_allow_set{
120
334k
      spv::Op::OpSizeOf,
121
334k
      spv::Op::OpCooperativeMatrixLengthNV,
122
334k
      spv::Op::OpCooperativeMatrixLengthKHR,
123
334k
      spv::Op::OpUntypedArrayLengthKHR,
124
334k
      spv::Op::OpFunction,
125
334k
      spv::Op::OpAsmINTEL,
126
334k
      spv::Op::OpConstantSizeOfEXT,
127
334k
      spv::Op::OpBufferPointerEXT,
128
334k
      spv::Op::OpUntypedImageTexelPointerEXT,
129
334k
      spv::Op::OpAbortKHR,
130
334k
  };
131
334k
  const auto opcode = inst->opcode();
132
334k
  bool type_instruction = spvOpcodeGeneratesType(opcode);
133
334k
  bool debug_instruction = spvOpcodeIsDebug(opcode) || inst->IsDebugInfo();
134
334k
  bool coop_matrix_spec_constant_op_length =
135
334k
      (opcode == spv::Op::OpSpecConstantOp) &&
136
21
      (spv::Op(inst->word(3)) == spv::Op::OpCooperativeMatrixLengthNV ||
137
6
       spv::Op(inst->word(3)) == spv::Op::OpCooperativeMatrixLengthKHR);
138
334k
  return type_instruction || debug_instruction || inst->IsNonSemantic() ||
139
55.1k
         spvOpcodeIsDecoration(opcode) || instruction_allow_set.count(opcode) ||
140
141
         spvOpcodeGeneratesUntypedPointer(opcode) ||
141
110
         coop_matrix_spec_constant_op_length;
142
334k
}
143
144
11.2M
bool InstructionRequiresTypeOperand(const Instruction* inst) {
145
11.2M
  static std::unordered_set<spv::Op> instruction_deny_set{
146
11.2M
      spv::Op::OpExtInst,
147
11.2M
      spv::Op::OpExtInstWithForwardRefsKHR,
148
11.2M
      spv::Op::OpExtInstImport,
149
11.2M
      spv::Op::OpSelectionMerge,
150
11.2M
      spv::Op::OpLoopMerge,
151
11.2M
      spv::Op::OpFunction,
152
11.2M
      spv::Op::OpSizeOf,
153
11.2M
      spv::Op::OpCooperativeMatrixLengthNV,
154
11.2M
      spv::Op::OpCooperativeMatrixLengthKHR,
155
11.2M
      spv::Op::OpPhi,
156
11.2M
      spv::Op::OpUntypedArrayLengthKHR,
157
11.2M
      spv::Op::OpAsmINTEL,
158
11.2M
      spv::Op::OpAliasScopeDeclINTEL,
159
11.2M
      spv::Op::OpAliasScopeListDeclINTEL,
160
11.2M
      spv::Op::OpAbortKHR,
161
11.2M
  };
162
11.2M
  const auto opcode = inst->opcode();
163
11.2M
  bool debug_instruction = spvOpcodeIsDebug(opcode) || inst->IsDebugInfo();
164
11.2M
  bool coop_matrix_spec_constant_op_length =
165
11.2M
      opcode == spv::Op::OpSpecConstantOp &&
166
3
      (spv::Op(inst->word(3)) == spv::Op::OpCooperativeMatrixLengthNV ||
167
3
       spv::Op(inst->word(3)) == spv::Op::OpCooperativeMatrixLengthKHR);
168
169
11.2M
  return !debug_instruction && !inst->IsNonSemantic() &&
170
11.2M
         !spvOpcodeIsDecoration(opcode) && !spvOpcodeIsBranch(opcode) &&
171
219k
         !instruction_deny_set.count(opcode) &&
172
535
         !spvOpcodeGeneratesUntypedPointer(opcode) &&
173
518
         !coop_matrix_spec_constant_op_length;
174
11.2M
}
175
176
// Performs SSA validation on the IDs of an instruction. The
177
// can_have_forward_declared_ids  functor should return true if the
178
// instruction operand's ID can be forward referenced.
179
15.3M
spv_result_t IdPass(ValidationState_t& _, Instruction* inst) {
180
15.3M
  auto can_have_forward_declared_ids =
181
15.3M
      spvIsExtendedInstruction(inst->opcode()) &&
182
64.3k
              spvExtInstIsDebugInfo(inst->ext_inst_type())
183
15.3M
          ? spvDbgInfoExtOperandCanBeForwardDeclaredFunction(
184
99
                inst->opcode(), inst->ext_inst_type(), inst->word(4))
185
15.3M
          : spvOperandCanBeForwardDeclaredFunction(inst->opcode());
186
187
  // Keep track of a result id defined by this instruction.  0 means it
188
  // does not define an id.
189
15.3M
  uint32_t result_id = 0;
190
15.3M
  bool has_forward_declared_ids = false;
191
192
41.1M
  for (unsigned i = 0; i < inst->operands().size(); i++) {
193
25.7M
    const spv_parsed_operand_t& operand = inst->operand(i);
194
25.7M
    const spv_operand_type_t& type = operand.type;
195
    // We only care about Id operands, which are a single word.
196
25.7M
    const uint32_t operand_word = inst->word(operand.offset);
197
198
25.7M
    auto ret = SPV_ERROR_INTERNAL;
199
25.7M
    switch (type) {
200
2.85M
      case SPV_OPERAND_TYPE_RESULT_ID:
201
        // NOTE: Multiple Id definitions are being checked by the binary parser.
202
        //
203
        // Defer undefined-forward-reference removal until after we've analyzed
204
        // the remaining operands to this instruction.  Deferral only matters
205
        // for OpPhi since it's the only case where it defines its own forward
206
        // reference.  Other instructions that can have forward references
207
        // either don't define a value or the forward reference is to a function
208
        // Id (and hence defined outside of a function body).
209
2.85M
        result_id = operand_word;
210
        // NOTE: The result Id is added (in RegisterInstruction) *after* all of
211
        // the other Ids have been checked to avoid premature use in the same
212
        // instruction.
213
2.85M
        ret = SPV_SUCCESS;
214
2.85M
        break;
215
18.5M
      case SPV_OPERAND_TYPE_ID:
216
18.5M
      case SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID:
217
18.5M
      case SPV_OPERAND_TYPE_SCOPE_ID:
218
18.5M
        if (const auto def = _.FindDef(operand_word)) {
219
14.7M
          if (spvOpcodeGeneratesType(def->opcode()) &&
220
334k
              !InstructionCanHaveTypeOperand(inst)) {
221
95
            return _.diag(SPV_ERROR_INVALID_ID, inst)
222
95
                   << "Operand " << _.getIdName(operand_word)
223
95
                   << " cannot be a type";
224
14.7M
          } else if (def->type_id() == 0 &&
225
11.5M
                     !spvOpcodeGeneratesType(def->opcode()) &&
226
11.2M
                     InstructionRequiresTypeOperand(inst) &&
227
483
                     InstructionRequiresTypeOperand(def)) {
228
35
            return _.diag(SPV_ERROR_INVALID_ID, inst)
229
35
                   << "Operand " << _.getIdName(operand_word)
230
35
                   << " requires a type";
231
14.7M
          } else if (def->IsNonSemantic() && !inst->IsNonSemantic()) {
232
10
            return _.diag(SPV_ERROR_INVALID_ID, inst)
233
10
                   << "Operand " << _.getIdName(operand_word)
234
10
                   << " in semantic instruction cannot be a non-semantic "
235
10
                      "instruction";
236
14.7M
          } else {
237
14.7M
            ret = SPV_SUCCESS;
238
14.7M
          }
239
14.7M
        } else if (can_have_forward_declared_ids(i)) {
240
3.79M
          has_forward_declared_ids = true;
241
3.79M
          if (spvOpcodeGeneratesType(inst->opcode()) &&
242
1.05k
              !_.IsForwardPointer(operand_word)) {
243
671
            ret = _.diag(SPV_ERROR_INVALID_ID, inst)
244
671
                  << "Operand " << _.getIdName(operand_word)
245
671
                  << " requires a previous definition";
246
3.79M
          } else {
247
3.79M
            ret = _.ForwardDeclareId(operand_word);
248
3.79M
          }
249
3.79M
        } else {
250
559
          ret = _.diag(SPV_ERROR_INVALID_ID, inst)
251
559
                << "ID " << _.getIdName(operand_word)
252
559
                << " has not been defined";
253
559
        }
254
18.5M
        break;
255
18.5M
      case SPV_OPERAND_TYPE_TYPE_ID:
256
1.94M
        if (_.IsDefinedId(operand_word)) {
257
1.94M
          auto* def = _.FindDef(operand_word);
258
1.94M
          if (!spvOpcodeGeneratesType(def->opcode())) {
259
12
            ret = _.diag(SPV_ERROR_INVALID_ID, inst)
260
12
                  << "ID " << _.getIdName(operand_word) << " is not a type id";
261
1.94M
          } else {
262
1.94M
            ret = SPV_SUCCESS;
263
1.94M
          }
264
1.94M
        } else {
265
941
          ret = _.diag(SPV_ERROR_INVALID_ID, inst)
266
941
                << "ID " << _.getIdName(operand_word)
267
941
                << " has not been defined";
268
941
        }
269
1.94M
        break;
270
64.1k
      case SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER:
271
        // Ideally, this check would live in validate_extensions.cpp. But since
272
        // forward references are only allowed on non-semantic instructions, and
273
        // ID validation is done first, we would fail with a "ID had not been
274
        // defined" error before we could give a more helpful message. For this
275
        // reason, this test is done here, so we can be more helpful to the
276
        // user.
277
64.1k
        if (inst->opcode() == spv::Op::OpExtInstWithForwardRefsKHR &&
278
6
            !inst->IsNonSemantic())
279
4
          return _.diag(SPV_ERROR_INVALID_DATA, inst)
280
4
                 << "OpExtInstWithForwardRefsKHR is only allowed with "
281
4
                    "non-semantic instructions.";
282
64.1k
        ret = SPV_SUCCESS;
283
64.1k
        break;
284
2.36M
      default:
285
2.36M
        ret = SPV_SUCCESS;
286
2.36M
        break;
287
25.7M
    }
288
25.7M
    if (SPV_SUCCESS != ret) return ret;
289
25.7M
  }
290
15.3M
  const bool must_have_forward_declared_ids =
291
15.3M
      inst->opcode() == spv::Op::OpExtInstWithForwardRefsKHR;
292
15.3M
  if (must_have_forward_declared_ids && !has_forward_declared_ids) {
293
1
    return _.diag(SPV_ERROR_INVALID_ID, inst)
294
1
           << "Opcode OpExtInstWithForwardRefsKHR must have at least one "
295
1
              "forward "
296
1
              "declared ID.";
297
1
  }
298
299
15.3M
  if (result_id) _.RemoveIfForwardDeclared(result_id);
300
301
15.3M
  return SPV_SUCCESS;
302
15.3M
}
303
304
}  // namespace val
305
}  // namespace spvtools