Coverage Report

Created: 2026-11-12 06:47

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/spirv-tools/source/val/validate_pipe.cpp
Line
Count
Source
1
// Copyright (c) 2026 LunarG 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
// Validates correctness of Pipe SPIR-V instructions.
16
17
#include "source/val/instruction.h"
18
#include "source/val/validate.h"
19
#include "source/val/validate_scopes.h"
20
#include "source/val/validation_state.h"
21
#include "spirv/unified1/spirv.hpp11"
22
23
namespace spvtools {
24
namespace val {
25
namespace {
26
27
enum class ValidPipeType {
28
  READ_ONLY,
29
  WRITE_ONLY,
30
  READ_OR_WRITE,  // still excludes Read AND Write
31
};
32
33
spv_result_t ValidatePipeType(ValidationState_t& _, const Instruction* inst,
34
0
                              uint32_t operand, ValidPipeType valid_pt) {
35
0
  const Instruction* pipe_type = _.FindDef(_.GetOperandTypeId(inst, operand));
36
0
  if (pipe_type->opcode() != spv::Op::OpTypePipe) {
37
0
    return _.diag(SPV_ERROR_INVALID_DATA, inst)
38
0
           << "Pipe must be a type of OpTypePipe.";
39
0
  }
40
41
0
  const auto access_qualifier =
42
0
      pipe_type->GetOperandAs<spv::AccessQualifier>(1);
43
0
  if (valid_pt == ValidPipeType::READ_ONLY) {
44
0
    if (access_qualifier != spv::AccessQualifier::ReadOnly) {
45
0
      return _.diag(SPV_ERROR_INVALID_DATA, inst)
46
0
             << "Pipe must have a OpTypePipe with ReadOnly access qualifier.";
47
0
    }
48
0
  } else if (valid_pt == ValidPipeType::WRITE_ONLY) {
49
0
    if (access_qualifier != spv::AccessQualifier::WriteOnly) {
50
0
      return _.diag(SPV_ERROR_INVALID_DATA, inst)
51
0
             << "Pipe must have a OpTypePipe with WriteOnly access qualifier.";
52
0
    }
53
0
  } else if (valid_pt == ValidPipeType::READ_OR_WRITE) {
54
0
    if (access_qualifier != spv::AccessQualifier::ReadOnly &&
55
0
        access_qualifier != spv::AccessQualifier::WriteOnly) {
56
0
      return _.diag(SPV_ERROR_INVALID_DATA, inst)
57
0
             << "Pipe must have a OpTypePipe with ReadOnly or WriteOnly access "
58
0
                "qualifier.";
59
0
    }
60
0
  }
61
62
0
  return SPV_SUCCESS;
63
0
}
64
65
spv_result_t ValidatePacketSizeAlign(ValidationState_t& _,
66
                                     const Instruction* inst,
67
                                     uint32_t size_operand,
68
0
                                     uint32_t alignment_operand) {
69
0
  const uint32_t packet_size_id = _.GetOperandTypeId(inst, size_operand);
70
0
  if (!_.IsIntScalarType(packet_size_id, 32)) {
71
0
    return _.diag(SPV_ERROR_INVALID_DATA, inst)
72
0
           << "Packet Size must be a 32-bit scalar integer.";
73
0
  }
74
75
0
  const uint32_t packet_alignment_id =
76
0
      _.GetOperandTypeId(inst, alignment_operand);
77
0
  if (!_.IsIntScalarType(packet_alignment_id, 32)) {
78
0
    return _.diag(SPV_ERROR_INVALID_DATA, inst)
79
0
           << "Packet Alignment must be a 32-bit scalar integer.";
80
0
  }
81
82
0
  return SPV_SUCCESS;
83
0
}
84
85
spv_result_t ValidateReadWritePipe(ValidationState_t& _,
86
0
                                   const Instruction* inst) {
87
0
  const uint32_t result_type = inst->type_id();
88
0
  if (!_.IsIntScalarType(result_type, 32)) {
89
0
    return _.diag(SPV_ERROR_INVALID_DATA, inst)
90
0
           << "Result Type must be a 32-bit int scalar.";
91
0
  }
92
93
0
  if (inst->opcode() == spv::Op::OpReadPipe) {
94
0
    if (auto error = ValidatePipeType(_, inst, 2, ValidPipeType::READ_ONLY))
95
0
      return error;
96
0
  } else if (inst->opcode() == spv::Op::OpWritePipe) {
97
0
    if (auto error = ValidatePipeType(_, inst, 2, ValidPipeType::WRITE_ONLY))
98
0
      return error;
99
0
  }
100
101
0
  const Instruction* pointer_type = _.FindDef(_.GetOperandTypeId(inst, 3));
102
0
  if (pointer_type->opcode() != spv::Op::OpTypePointer) {
103
0
    return _.diag(SPV_ERROR_INVALID_DATA, inst)
104
0
           << "Pointer must be a type of OpTypePointer.";
105
0
  }
106
0
  if (pointer_type->GetOperandAs<spv::StorageClass>(1) !=
107
0
      spv::StorageClass::Generic) {
108
0
    return _.diag(SPV_ERROR_INVALID_DATA, inst)
109
0
           << "Pointer must be a OpTypePointer with a Generic storage class.";
110
0
  }
111
112
0
  if (auto error = ValidatePacketSizeAlign(_, inst, 4, 5)) return error;
113
114
0
  return SPV_SUCCESS;
115
0
}
116
117
spv_result_t ValidateReservedReadWritePipe(ValidationState_t& _,
118
0
                                           const Instruction* inst) {
119
0
  const uint32_t result_type = inst->type_id();
120
0
  if (!_.IsIntScalarType(result_type, 32)) {
121
0
    return _.diag(SPV_ERROR_INVALID_DATA, inst)
122
0
           << "Result Type must be a 32-bit int scalar.";
123
0
  }
124
125
0
  if (inst->opcode() == spv::Op::OpReservedReadPipe) {
126
0
    if (auto error = ValidatePipeType(_, inst, 2, ValidPipeType::READ_ONLY))
127
0
      return error;
128
0
  } else if (inst->opcode() == spv::Op::OpReservedWritePipe) {
129
0
    if (auto error = ValidatePipeType(_, inst, 2, ValidPipeType::WRITE_ONLY))
130
0
      return error;
131
0
  }
132
133
0
  const Instruction* reserve_id = _.FindDef(_.GetOperandTypeId(inst, 3));
134
0
  if (reserve_id->opcode() != spv::Op::OpTypeReserveId) {
135
0
    return _.diag(SPV_ERROR_INVALID_DATA, inst)
136
0
           << "Reserve Id type must be OpTypeReserveId.";
137
0
  }
138
139
0
  const uint32_t index_id = _.GetOperandTypeId(inst, 4);
140
0
  if (!_.IsIntScalarType(index_id, 32)) {
141
0
    return _.diag(SPV_ERROR_INVALID_DATA, inst)
142
0
           << "Index must be a 32-bit scalar integer.";
143
0
  }
144
145
0
  const Instruction* pointer_type = _.FindDef(_.GetOperandTypeId(inst, 5));
146
0
  if (pointer_type->opcode() != spv::Op::OpTypePointer) {
147
0
    return _.diag(SPV_ERROR_INVALID_DATA, inst)
148
0
           << "Pointer must be a type of OpTypePointer.";
149
0
  }
150
0
  if (pointer_type->GetOperandAs<spv::StorageClass>(1) !=
151
0
      spv::StorageClass::Generic) {
152
0
    return _.diag(SPV_ERROR_INVALID_DATA, inst)
153
0
           << "Pointer must be a OpTypePointer with a Generic storage class.";
154
0
  }
155
156
0
  if (auto error = ValidatePacketSizeAlign(_, inst, 6, 7)) return error;
157
158
0
  return SPV_SUCCESS;
159
0
}
160
161
spv_result_t ValidateReservePackets(ValidationState_t& _,
162
0
                                    const Instruction* inst) {
163
0
  const Instruction* result_type = _.FindDef(inst->type_id());
164
0
  if (result_type->opcode() != spv::Op::OpTypeReserveId) {
165
0
    return _.diag(SPV_ERROR_INVALID_DATA, inst)
166
0
           << "Result Type must be OpTypeReserveId.";
167
0
  }
168
169
0
  if (inst->opcode() == spv::Op::OpReserveReadPipePackets) {
170
0
    if (auto error = ValidatePipeType(_, inst, 2, ValidPipeType::READ_ONLY))
171
0
      return error;
172
0
  } else if (inst->opcode() == spv::Op::OpReserveWritePipePackets) {
173
0
    if (auto error = ValidatePipeType(_, inst, 2, ValidPipeType::WRITE_ONLY))
174
0
      return error;
175
0
  }
176
177
0
  const uint32_t num_packets_id = _.GetOperandTypeId(inst, 3);
178
0
  if (!_.IsIntScalarType(num_packets_id, 32)) {
179
0
    return _.diag(SPV_ERROR_INVALID_DATA, inst)
180
0
           << "Num Packets must be a 32-bit scalar integer.";
181
0
  }
182
183
0
  if (auto error = ValidatePacketSizeAlign(_, inst, 4, 5)) return error;
184
185
0
  return SPV_SUCCESS;
186
0
}
187
188
spv_result_t ValidateGroupReservePackets(ValidationState_t& _,
189
0
                                         const Instruction* inst) {
190
0
  const Instruction* result_type = _.FindDef(inst->type_id());
191
0
  if (result_type->opcode() != spv::Op::OpTypeReserveId) {
192
0
    return _.diag(SPV_ERROR_INVALID_DATA, inst)
193
0
           << "Result Type must be OpTypeReserveId.";
194
0
  }
195
196
0
  if (inst->opcode() == spv::Op::OpGroupReserveReadPipePackets) {
197
0
    if (auto error = ValidatePipeType(_, inst, 3, ValidPipeType::READ_ONLY))
198
0
      return error;
199
0
  } else if (inst->opcode() == spv::Op::OpGroupReserveWritePipePackets) {
200
0
    if (auto error = ValidatePipeType(_, inst, 3, ValidPipeType::WRITE_ONLY))
201
0
      return error;
202
0
  }
203
204
0
  const uint32_t num_packets_id = _.GetOperandTypeId(inst, 4);
205
0
  if (!_.IsIntScalarType(num_packets_id, 32)) {
206
0
    return _.diag(SPV_ERROR_INVALID_DATA, inst)
207
0
           << "Num Packets must be a 32-bit scalar integer.";
208
0
  }
209
210
0
  if (auto error = ValidatePacketSizeAlign(_, inst, 5, 6)) return error;
211
212
0
  return SPV_SUCCESS;
213
0
}
214
215
0
spv_result_t ValidateCommitPipe(ValidationState_t& _, const Instruction* inst) {
216
0
  if (inst->opcode() == spv::Op::OpCommitReadPipe) {
217
0
    if (auto error = ValidatePipeType(_, inst, 0, ValidPipeType::READ_ONLY))
218
0
      return error;
219
0
  } else if (inst->opcode() == spv::Op::OpCommitWritePipe) {
220
0
    if (auto error = ValidatePipeType(_, inst, 0, ValidPipeType::WRITE_ONLY))
221
0
      return error;
222
0
  }
223
224
0
  const Instruction* reserve_id = _.FindDef(_.GetOperandTypeId(inst, 1));
225
0
  if (reserve_id->opcode() != spv::Op::OpTypeReserveId) {
226
0
    return _.diag(SPV_ERROR_INVALID_DATA, inst)
227
0
           << "Reserve Id type must be OpTypeReserveId.";
228
0
  }
229
230
0
  if (auto error = ValidatePacketSizeAlign(_, inst, 2, 3)) return error;
231
232
0
  return SPV_SUCCESS;
233
0
}
234
235
spv_result_t ValidateGroupCommitPipe(ValidationState_t& _,
236
0
                                     const Instruction* inst) {
237
0
  if (inst->opcode() == spv::Op::OpGroupCommitReadPipe) {
238
0
    if (auto error = ValidatePipeType(_, inst, 1, ValidPipeType::READ_ONLY))
239
0
      return error;
240
0
  } else if (inst->opcode() == spv::Op::OpGroupCommitWritePipe) {
241
0
    if (auto error = ValidatePipeType(_, inst, 1, ValidPipeType::WRITE_ONLY))
242
0
      return error;
243
0
  }
244
245
0
  const Instruction* reserve_id = _.FindDef(_.GetOperandTypeId(inst, 2));
246
0
  if (reserve_id->opcode() != spv::Op::OpTypeReserveId) {
247
0
    return _.diag(SPV_ERROR_INVALID_DATA, inst)
248
0
           << "Reserve Id type must be OpTypeReserveId.";
249
0
  }
250
251
0
  if (auto error = ValidatePacketSizeAlign(_, inst, 3, 4)) return error;
252
253
0
  return SPV_SUCCESS;
254
0
}
255
256
spv_result_t ValidatePipePacketsQuery(ValidationState_t& _,
257
0
                                      const Instruction* inst) {
258
0
  const uint32_t result_type = inst->type_id();
259
0
  if (!_.IsIntScalarType(result_type, 32)) {
260
0
    return _.diag(SPV_ERROR_INVALID_DATA, inst)
261
0
           << "Result Type must be a 32-bit int scalar.";
262
0
  }
263
264
0
  if (auto error = ValidatePipeType(_, inst, 2, ValidPipeType::READ_OR_WRITE))
265
0
    return error;
266
267
0
  if (auto error = ValidatePacketSizeAlign(_, inst, 3, 4)) return error;
268
269
0
  return SPV_SUCCESS;
270
0
}
271
272
spv_result_t ValidateIsValidReserveId(ValidationState_t& _,
273
0
                                      const Instruction* inst) {
274
0
  if (!_.IsBoolScalarType(inst->type_id())) {
275
0
    return _.diag(SPV_ERROR_INVALID_DATA, inst)
276
0
           << "Result Type must be a bool scalar";
277
0
  }
278
279
0
  const Instruction* reserve_id = _.FindDef(_.GetOperandTypeId(inst, 2));
280
0
  if (reserve_id->opcode() != spv::Op::OpTypeReserveId) {
281
0
    return _.diag(SPV_ERROR_INVALID_DATA, inst)
282
0
           << "Reserve Id type must be OpTypeReserveId.";
283
0
  }
284
285
0
  return SPV_SUCCESS;
286
0
}
287
288
spv_result_t ValidateCreatePipeFromPipeStorage(ValidationState_t& _,
289
0
                                               const Instruction* inst) {
290
0
  const Instruction* result_type = _.FindDef(inst->type_id());
291
0
  if (result_type->opcode() != spv::Op::OpTypePipe) {
292
0
    return _.diag(SPV_ERROR_INVALID_DATA, inst)
293
0
           << "Result Type must be OpTypePipe.";
294
0
  }
295
296
  // TODO - Need to check OpTypeStorage is from OpConstantPipeStorage
297
0
  return SPV_SUCCESS;
298
0
}
299
300
spv_result_t ValidateConstantPipeStorage(ValidationState_t& _,
301
0
                                         const Instruction* inst) {
302
0
  const Instruction* result_type = _.FindDef(inst->type_id());
303
0
  if (result_type->opcode() != spv::Op::OpTypePipeStorage) {
304
0
    return _.diag(SPV_ERROR_INVALID_DATA, inst)
305
0
           << "Result Type must be OpTypePipeStorage.";
306
0
  }
307
308
  // TODO - Should we validate the literal values?
309
  // https://gitlab.khronos.org/spirv/SPIR-V/-/issues/914
310
0
  return SPV_SUCCESS;
311
0
}
312
313
}  // namespace
314
315
// Validates correctness of pipe instructions.
316
14.1M
spv_result_t PipePass(ValidationState_t& _, const Instruction* inst) {
317
14.1M
  switch (inst->opcode()) {
318
0
    case spv::Op::OpReadPipe:
319
0
    case spv::Op::OpWritePipe:
320
0
      return ValidateReadWritePipe(_, inst);
321
0
    case spv::Op::OpReservedReadPipe:
322
0
    case spv::Op::OpReservedWritePipe:
323
0
      return ValidateReservedReadWritePipe(_, inst);
324
0
    case spv::Op::OpReserveReadPipePackets:
325
0
    case spv::Op::OpReserveWritePipePackets:
326
0
      return ValidateReservePackets(_, inst);
327
0
    case spv::Op::OpGroupReserveReadPipePackets:
328
0
    case spv::Op::OpGroupReserveWritePipePackets:
329
0
      return ValidateGroupReservePackets(_, inst);
330
0
    case spv::Op::OpCommitReadPipe:
331
0
    case spv::Op::OpCommitWritePipe:
332
0
      return ValidateCommitPipe(_, inst);
333
0
    case spv::Op::OpGroupCommitReadPipe:
334
0
    case spv::Op::OpGroupCommitWritePipe:
335
0
      return ValidateGroupCommitPipe(_, inst);
336
0
    case spv::Op::OpGetNumPipePackets:
337
0
    case spv::Op::OpGetMaxPipePackets:
338
0
      return ValidatePipePacketsQuery(_, inst);
339
0
    case spv::Op::OpIsValidReserveId:
340
0
      return ValidateIsValidReserveId(_, inst);
341
0
    case spv::Op::OpCreatePipeFromPipeStorage:
342
0
      return ValidateCreatePipeFromPipeStorage(_, inst);
343
0
    case spv::Op::OpConstantPipeStorage:
344
0
      return ValidateConstantPipeStorage(_, inst);
345
14.1M
    default:
346
14.1M
      break;
347
14.1M
  }
348
349
14.1M
  return SPV_SUCCESS;
350
14.1M
}
351
352
}  // namespace val
353
}  // namespace spvtools