1 // Copyright (c) 2018 The Khronos Group Inc.
2 // Copyright (c) 2018 Valve Corporation
3 // Copyright (c) 2018 LunarG Inc.
4 //
5 // Licensed under the Apache License, Version 2.0 (the "License");
6 // you may not use this file except in compliance with the License.
7 // You may obtain a copy of the License at
8 //
9 // http://www.apache.org/licenses/LICENSE-2.0
10 //
11 // Unless required by applicable law or agreed to in writing, software
12 // distributed under the License is distributed on an "AS IS" BASIS,
13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 // See the License for the specific language governing permissions and
15 // limitations under the License.
16
17 #include "inst_bindless_check_pass.h"
18
19 namespace {
20
21 // Input Operand Indices
22 static const int kSpvImageSampleImageIdInIdx = 0;
23 static const int kSpvSampledImageImageIdInIdx = 0;
24 static const int kSpvSampledImageSamplerIdInIdx = 1;
25 static const int kSpvImageSampledImageIdInIdx = 0;
26 static const int kSpvCopyObjectOperandIdInIdx = 0;
27 static const int kSpvLoadPtrIdInIdx = 0;
28 static const int kSpvAccessChainBaseIdInIdx = 0;
29 static const int kSpvAccessChainIndex0IdInIdx = 1;
30 static const int kSpvTypeArrayTypeIdInIdx = 0;
31 static const int kSpvTypeArrayLengthIdInIdx = 1;
32 static const int kSpvConstantValueInIdx = 0;
33 static const int kSpvVariableStorageClassInIdx = 0;
34 static const int kSpvTypePtrTypeIdInIdx = 1;
35 static const int kSpvTypeImageDim = 1;
36 static const int kSpvTypeImageDepth = 2;
37 static const int kSpvTypeImageArrayed = 3;
38 static const int kSpvTypeImageMS = 4;
39 static const int kSpvTypeImageSampled = 5;
40 } // anonymous namespace
41
42 namespace spvtools {
43 namespace opt {
44
GenDebugReadLength(uint32_t var_id,InstructionBuilder * builder)45 uint32_t InstBindlessCheckPass::GenDebugReadLength(
46 uint32_t var_id, InstructionBuilder* builder) {
47 uint32_t desc_set_idx =
48 var2desc_set_[var_id] + kDebugInputBindlessOffsetLengths;
49 uint32_t desc_set_idx_id = builder->GetUintConstantId(desc_set_idx);
50 uint32_t binding_idx_id = builder->GetUintConstantId(var2binding_[var_id]);
51 return GenDebugDirectRead({desc_set_idx_id, binding_idx_id}, builder);
52 }
53
GenDebugReadInit(uint32_t var_id,uint32_t desc_idx_id,InstructionBuilder * builder)54 uint32_t InstBindlessCheckPass::GenDebugReadInit(uint32_t var_id,
55 uint32_t desc_idx_id,
56 InstructionBuilder* builder) {
57 uint32_t binding_idx_id = builder->GetUintConstantId(var2binding_[var_id]);
58 uint32_t u_desc_idx_id = GenUintCastCode(desc_idx_id, builder);
59 // If desc index checking is not enabled, we know the offset of initialization
60 // entries is 1, so we can avoid loading this value and just add 1 to the
61 // descriptor set.
62 if (!desc_idx_enabled_) {
63 uint32_t desc_set_idx_id =
64 builder->GetUintConstantId(var2desc_set_[var_id] + 1);
65 return GenDebugDirectRead({desc_set_idx_id, binding_idx_id, u_desc_idx_id},
66 builder);
67 } else {
68 uint32_t desc_set_base_id =
69 builder->GetUintConstantId(kDebugInputBindlessInitOffset);
70 uint32_t desc_set_idx_id =
71 builder->GetUintConstantId(var2desc_set_[var_id]);
72 return GenDebugDirectRead(
73 {desc_set_base_id, desc_set_idx_id, binding_idx_id, u_desc_idx_id},
74 builder);
75 }
76 }
77
CloneOriginalImage(uint32_t old_image_id,InstructionBuilder * builder)78 uint32_t InstBindlessCheckPass::CloneOriginalImage(
79 uint32_t old_image_id, InstructionBuilder* builder) {
80 Instruction* new_image_inst;
81 Instruction* old_image_inst = get_def_use_mgr()->GetDef(old_image_id);
82 if (old_image_inst->opcode() == SpvOpLoad) {
83 new_image_inst = builder->AddLoad(
84 old_image_inst->type_id(),
85 old_image_inst->GetSingleWordInOperand(kSpvLoadPtrIdInIdx));
86 } else if (old_image_inst->opcode() == SpvOp::SpvOpSampledImage) {
87 uint32_t clone_id = CloneOriginalImage(
88 old_image_inst->GetSingleWordInOperand(kSpvSampledImageImageIdInIdx),
89 builder);
90 new_image_inst = builder->AddBinaryOp(
91 old_image_inst->type_id(), SpvOpSampledImage, clone_id,
92 old_image_inst->GetSingleWordInOperand(kSpvSampledImageSamplerIdInIdx));
93 } else if (old_image_inst->opcode() == SpvOp::SpvOpImage) {
94 uint32_t clone_id = CloneOriginalImage(
95 old_image_inst->GetSingleWordInOperand(kSpvImageSampledImageIdInIdx),
96 builder);
97 new_image_inst =
98 builder->AddUnaryOp(old_image_inst->type_id(), SpvOpImage, clone_id);
99 } else {
100 assert(old_image_inst->opcode() == SpvOp::SpvOpCopyObject &&
101 "expecting OpCopyObject");
102 uint32_t clone_id = CloneOriginalImage(
103 old_image_inst->GetSingleWordInOperand(kSpvCopyObjectOperandIdInIdx),
104 builder);
105 // Since we are cloning, no need to create new copy
106 new_image_inst = get_def_use_mgr()->GetDef(clone_id);
107 }
108 uid2offset_[new_image_inst->unique_id()] =
109 uid2offset_[old_image_inst->unique_id()];
110 uint32_t new_image_id = new_image_inst->result_id();
111 get_decoration_mgr()->CloneDecorations(old_image_id, new_image_id);
112 return new_image_id;
113 }
114
CloneOriginalReference(RefAnalysis * ref,InstructionBuilder * builder)115 uint32_t InstBindlessCheckPass::CloneOriginalReference(
116 RefAnalysis* ref, InstructionBuilder* builder) {
117 // If original is image based, start by cloning descriptor load
118 uint32_t new_image_id = 0;
119 if (ref->desc_load_id != 0) {
120 uint32_t old_image_id =
121 ref->ref_inst->GetSingleWordInOperand(kSpvImageSampleImageIdInIdx);
122 new_image_id = CloneOriginalImage(old_image_id, builder);
123 }
124 // Clone original reference
125 std::unique_ptr<Instruction> new_ref_inst(ref->ref_inst->Clone(context()));
126 uint32_t ref_result_id = ref->ref_inst->result_id();
127 uint32_t new_ref_id = 0;
128 if (ref_result_id != 0) {
129 new_ref_id = TakeNextId();
130 new_ref_inst->SetResultId(new_ref_id);
131 }
132 // Update new ref with new image if created
133 if (new_image_id != 0)
134 new_ref_inst->SetInOperand(kSpvImageSampleImageIdInIdx, {new_image_id});
135 // Register new reference and add to new block
136 Instruction* added_inst = builder->AddInstruction(std::move(new_ref_inst));
137 uid2offset_[added_inst->unique_id()] =
138 uid2offset_[ref->ref_inst->unique_id()];
139 if (new_ref_id != 0)
140 get_decoration_mgr()->CloneDecorations(ref_result_id, new_ref_id);
141 return new_ref_id;
142 }
143
GetImageId(Instruction * inst)144 uint32_t InstBindlessCheckPass::GetImageId(Instruction* inst) {
145 switch (inst->opcode()) {
146 case SpvOp::SpvOpImageSampleImplicitLod:
147 case SpvOp::SpvOpImageSampleExplicitLod:
148 case SpvOp::SpvOpImageSampleDrefImplicitLod:
149 case SpvOp::SpvOpImageSampleDrefExplicitLod:
150 case SpvOp::SpvOpImageSampleProjImplicitLod:
151 case SpvOp::SpvOpImageSampleProjExplicitLod:
152 case SpvOp::SpvOpImageSampleProjDrefImplicitLod:
153 case SpvOp::SpvOpImageSampleProjDrefExplicitLod:
154 case SpvOp::SpvOpImageGather:
155 case SpvOp::SpvOpImageDrefGather:
156 case SpvOp::SpvOpImageQueryLod:
157 case SpvOp::SpvOpImageSparseSampleImplicitLod:
158 case SpvOp::SpvOpImageSparseSampleExplicitLod:
159 case SpvOp::SpvOpImageSparseSampleDrefImplicitLod:
160 case SpvOp::SpvOpImageSparseSampleDrefExplicitLod:
161 case SpvOp::SpvOpImageSparseSampleProjImplicitLod:
162 case SpvOp::SpvOpImageSparseSampleProjExplicitLod:
163 case SpvOp::SpvOpImageSparseSampleProjDrefImplicitLod:
164 case SpvOp::SpvOpImageSparseSampleProjDrefExplicitLod:
165 case SpvOp::SpvOpImageSparseGather:
166 case SpvOp::SpvOpImageSparseDrefGather:
167 case SpvOp::SpvOpImageFetch:
168 case SpvOp::SpvOpImageRead:
169 case SpvOp::SpvOpImageQueryFormat:
170 case SpvOp::SpvOpImageQueryOrder:
171 case SpvOp::SpvOpImageQuerySizeLod:
172 case SpvOp::SpvOpImageQuerySize:
173 case SpvOp::SpvOpImageQueryLevels:
174 case SpvOp::SpvOpImageQuerySamples:
175 case SpvOp::SpvOpImageSparseFetch:
176 case SpvOp::SpvOpImageSparseRead:
177 case SpvOp::SpvOpImageWrite:
178 return inst->GetSingleWordInOperand(kSpvImageSampleImageIdInIdx);
179 default:
180 break;
181 }
182 return 0;
183 }
184
GetPointeeTypeInst(Instruction * ptr_inst)185 Instruction* InstBindlessCheckPass::GetPointeeTypeInst(Instruction* ptr_inst) {
186 uint32_t pte_ty_id = GetPointeeTypeId(ptr_inst);
187 return get_def_use_mgr()->GetDef(pte_ty_id);
188 }
189
AnalyzeDescriptorReference(Instruction * ref_inst,RefAnalysis * ref)190 bool InstBindlessCheckPass::AnalyzeDescriptorReference(Instruction* ref_inst,
191 RefAnalysis* ref) {
192 ref->ref_inst = ref_inst;
193 if (ref_inst->opcode() == SpvOpLoad || ref_inst->opcode() == SpvOpStore) {
194 ref->desc_load_id = 0;
195 ref->ptr_id = ref_inst->GetSingleWordInOperand(kSpvLoadPtrIdInIdx);
196 Instruction* ptr_inst = get_def_use_mgr()->GetDef(ref->ptr_id);
197 if (ptr_inst->opcode() != SpvOp::SpvOpAccessChain) return false;
198 ref->var_id = ptr_inst->GetSingleWordInOperand(kSpvAccessChainBaseIdInIdx);
199 Instruction* var_inst = get_def_use_mgr()->GetDef(ref->var_id);
200 if (var_inst->opcode() != SpvOp::SpvOpVariable) return false;
201 uint32_t storage_class =
202 var_inst->GetSingleWordInOperand(kSpvVariableStorageClassInIdx);
203 switch (storage_class) {
204 case SpvStorageClassUniform:
205 case SpvStorageClassStorageBuffer:
206 break;
207 default:
208 return false;
209 break;
210 }
211 // Check for deprecated storage block form
212 if (storage_class == SpvStorageClassUniform) {
213 uint32_t var_ty_id = var_inst->type_id();
214 Instruction* var_ty_inst = get_def_use_mgr()->GetDef(var_ty_id);
215 uint32_t ptr_ty_id =
216 var_ty_inst->GetSingleWordInOperand(kSpvTypePtrTypeIdInIdx);
217 Instruction* ptr_ty_inst = get_def_use_mgr()->GetDef(ptr_ty_id);
218 SpvOp ptr_ty_op = ptr_ty_inst->opcode();
219 uint32_t block_ty_id =
220 (ptr_ty_op == SpvOpTypeArray || ptr_ty_op == SpvOpTypeRuntimeArray)
221 ? ptr_ty_inst->GetSingleWordInOperand(kSpvTypeArrayTypeIdInIdx)
222 : ptr_ty_id;
223 assert(get_def_use_mgr()->GetDef(block_ty_id)->opcode() ==
224 SpvOpTypeStruct &&
225 "unexpected block type");
226 bool block_found = get_decoration_mgr()->FindDecoration(
227 block_ty_id, SpvDecorationBlock,
228 [](const Instruction&) { return true; });
229 if (!block_found) {
230 // If block decoration not found, verify deprecated form of SSBO
231 bool buffer_block_found = get_decoration_mgr()->FindDecoration(
232 block_ty_id, SpvDecorationBufferBlock,
233 [](const Instruction&) { return true; });
234 USE_ASSERT(buffer_block_found && "block decoration not found");
235 storage_class = SpvStorageClassStorageBuffer;
236 }
237 }
238 ref->strg_class = storage_class;
239 Instruction* desc_type_inst = GetPointeeTypeInst(var_inst);
240 switch (desc_type_inst->opcode()) {
241 case SpvOpTypeArray:
242 case SpvOpTypeRuntimeArray:
243 // A load through a descriptor array will have at least 3 operands. We
244 // do not want to instrument loads of descriptors here which are part of
245 // an image-based reference.
246 if (ptr_inst->NumInOperands() < 3) return false;
247 ref->desc_idx_id =
248 ptr_inst->GetSingleWordInOperand(kSpvAccessChainIndex0IdInIdx);
249 break;
250 default:
251 ref->desc_idx_id = 0;
252 break;
253 }
254 return true;
255 }
256 // Reference is not load or store. If not an image-based reference, return.
257 ref->image_id = GetImageId(ref_inst);
258 if (ref->image_id == 0) return false;
259 // Search for descriptor load
260 uint32_t desc_load_id = ref->image_id;
261 Instruction* desc_load_inst;
262 for (;;) {
263 desc_load_inst = get_def_use_mgr()->GetDef(desc_load_id);
264 if (desc_load_inst->opcode() == SpvOp::SpvOpSampledImage)
265 desc_load_id =
266 desc_load_inst->GetSingleWordInOperand(kSpvSampledImageImageIdInIdx);
267 else if (desc_load_inst->opcode() == SpvOp::SpvOpImage)
268 desc_load_id =
269 desc_load_inst->GetSingleWordInOperand(kSpvImageSampledImageIdInIdx);
270 else if (desc_load_inst->opcode() == SpvOp::SpvOpCopyObject)
271 desc_load_id =
272 desc_load_inst->GetSingleWordInOperand(kSpvCopyObjectOperandIdInIdx);
273 else
274 break;
275 }
276 if (desc_load_inst->opcode() != SpvOp::SpvOpLoad) {
277 // TODO(greg-lunarg): Handle additional possibilities?
278 return false;
279 }
280 ref->desc_load_id = desc_load_id;
281 ref->ptr_id = desc_load_inst->GetSingleWordInOperand(kSpvLoadPtrIdInIdx);
282 Instruction* ptr_inst = get_def_use_mgr()->GetDef(ref->ptr_id);
283 if (ptr_inst->opcode() == SpvOp::SpvOpVariable) {
284 ref->desc_idx_id = 0;
285 ref->var_id = ref->ptr_id;
286 } else if (ptr_inst->opcode() == SpvOp::SpvOpAccessChain) {
287 if (ptr_inst->NumInOperands() != 2) {
288 assert(false && "unexpected bindless index number");
289 return false;
290 }
291 ref->desc_idx_id =
292 ptr_inst->GetSingleWordInOperand(kSpvAccessChainIndex0IdInIdx);
293 ref->var_id = ptr_inst->GetSingleWordInOperand(kSpvAccessChainBaseIdInIdx);
294 Instruction* var_inst = get_def_use_mgr()->GetDef(ref->var_id);
295 if (var_inst->opcode() != SpvOpVariable) {
296 assert(false && "unexpected bindless base");
297 return false;
298 }
299 } else {
300 // TODO(greg-lunarg): Handle additional possibilities?
301 return false;
302 }
303 return true;
304 }
305
FindStride(uint32_t ty_id,uint32_t stride_deco)306 uint32_t InstBindlessCheckPass::FindStride(uint32_t ty_id,
307 uint32_t stride_deco) {
308 uint32_t stride = 0xdeadbeef;
309 bool found = get_decoration_mgr()->FindDecoration(
310 ty_id, stride_deco, [&stride](const Instruction& deco_inst) {
311 stride = deco_inst.GetSingleWordInOperand(2u);
312 return true;
313 });
314 USE_ASSERT(found && "stride not found");
315 return stride;
316 }
317
ByteSize(uint32_t ty_id,uint32_t matrix_stride,bool col_major,bool in_matrix)318 uint32_t InstBindlessCheckPass::ByteSize(uint32_t ty_id, uint32_t matrix_stride,
319 bool col_major, bool in_matrix) {
320 analysis::TypeManager* type_mgr = context()->get_type_mgr();
321 const analysis::Type* sz_ty = type_mgr->GetType(ty_id);
322 if (sz_ty->kind() == analysis::Type::kPointer) {
323 // Assuming PhysicalStorageBuffer pointer
324 return 8;
325 }
326 if (sz_ty->kind() == analysis::Type::kMatrix) {
327 assert(matrix_stride != 0 && "missing matrix stride");
328 const analysis::Matrix* m_ty = sz_ty->AsMatrix();
329 if (col_major) {
330 return m_ty->element_count() * matrix_stride;
331 } else {
332 const analysis::Vector* v_ty = m_ty->element_type()->AsVector();
333 return v_ty->element_count() * matrix_stride;
334 }
335 }
336 uint32_t size = 1;
337 if (sz_ty->kind() == analysis::Type::kVector) {
338 const analysis::Vector* v_ty = sz_ty->AsVector();
339 size = v_ty->element_count();
340 const analysis::Type* comp_ty = v_ty->element_type();
341 // if vector in row major matrix, the vector is strided so return the
342 // number of bytes spanned by the vector
343 if (in_matrix && !col_major && matrix_stride > 0) {
344 uint32_t comp_ty_id = type_mgr->GetId(comp_ty);
345 return (size - 1) * matrix_stride + ByteSize(comp_ty_id, 0, false, false);
346 }
347 sz_ty = comp_ty;
348 }
349 switch (sz_ty->kind()) {
350 case analysis::Type::kFloat: {
351 const analysis::Float* f_ty = sz_ty->AsFloat();
352 size *= f_ty->width();
353 } break;
354 case analysis::Type::kInteger: {
355 const analysis::Integer* i_ty = sz_ty->AsInteger();
356 size *= i_ty->width();
357 } break;
358 default: { assert(false && "unexpected type"); } break;
359 }
360 size /= 8;
361 return size;
362 }
363
GenLastByteIdx(RefAnalysis * ref,InstructionBuilder * builder)364 uint32_t InstBindlessCheckPass::GenLastByteIdx(RefAnalysis* ref,
365 InstructionBuilder* builder) {
366 // Find outermost buffer type and its access chain index
367 Instruction* var_inst = get_def_use_mgr()->GetDef(ref->var_id);
368 Instruction* desc_ty_inst = GetPointeeTypeInst(var_inst);
369 uint32_t buff_ty_id;
370 uint32_t ac_in_idx = 1;
371 switch (desc_ty_inst->opcode()) {
372 case SpvOpTypeArray:
373 case SpvOpTypeRuntimeArray:
374 buff_ty_id = desc_ty_inst->GetSingleWordInOperand(0);
375 ++ac_in_idx;
376 break;
377 default:
378 assert(desc_ty_inst->opcode() == SpvOpTypeStruct &&
379 "unexpected descriptor type");
380 buff_ty_id = desc_ty_inst->result_id();
381 break;
382 }
383 // Process remaining access chain indices
384 Instruction* ac_inst = get_def_use_mgr()->GetDef(ref->ptr_id);
385 uint32_t curr_ty_id = buff_ty_id;
386 uint32_t sum_id = 0u;
387 uint32_t matrix_stride = 0u;
388 bool col_major = false;
389 uint32_t matrix_stride_id = 0u;
390 bool in_matrix = false;
391 while (ac_in_idx < ac_inst->NumInOperands()) {
392 uint32_t curr_idx_id = ac_inst->GetSingleWordInOperand(ac_in_idx);
393 Instruction* curr_ty_inst = get_def_use_mgr()->GetDef(curr_ty_id);
394 uint32_t curr_offset_id = 0;
395 switch (curr_ty_inst->opcode()) {
396 case SpvOpTypeArray:
397 case SpvOpTypeRuntimeArray: {
398 // Get array stride and multiply by current index
399 uint32_t arr_stride = FindStride(curr_ty_id, SpvDecorationArrayStride);
400 uint32_t arr_stride_id = builder->GetUintConstantId(arr_stride);
401 uint32_t curr_idx_32b_id = Gen32BitCvtCode(curr_idx_id, builder);
402 Instruction* curr_offset_inst = builder->AddBinaryOp(
403 GetUintId(), SpvOpIMul, arr_stride_id, curr_idx_32b_id);
404 curr_offset_id = curr_offset_inst->result_id();
405 // Get element type for next step
406 curr_ty_id = curr_ty_inst->GetSingleWordInOperand(0);
407 } break;
408 case SpvOpTypeMatrix: {
409 assert(matrix_stride != 0 && "missing matrix stride");
410 matrix_stride_id = builder->GetUintConstantId(matrix_stride);
411 uint32_t vec_ty_id = curr_ty_inst->GetSingleWordInOperand(0);
412 // If column major, multiply column index by matrix stride, otherwise
413 // by vector component size and save matrix stride for vector (row)
414 // index
415 uint32_t col_stride_id;
416 if (col_major) {
417 col_stride_id = matrix_stride_id;
418 } else {
419 Instruction* vec_ty_inst = get_def_use_mgr()->GetDef(vec_ty_id);
420 uint32_t comp_ty_id = vec_ty_inst->GetSingleWordInOperand(0u);
421 uint32_t col_stride = ByteSize(comp_ty_id, 0u, false, false);
422 col_stride_id = builder->GetUintConstantId(col_stride);
423 }
424 uint32_t curr_idx_32b_id = Gen32BitCvtCode(curr_idx_id, builder);
425 Instruction* curr_offset_inst = builder->AddBinaryOp(
426 GetUintId(), SpvOpIMul, col_stride_id, curr_idx_32b_id);
427 curr_offset_id = curr_offset_inst->result_id();
428 // Get element type for next step
429 curr_ty_id = vec_ty_id;
430 in_matrix = true;
431 } break;
432 case SpvOpTypeVector: {
433 // If inside a row major matrix type, multiply index by matrix stride,
434 // else multiply by component size
435 uint32_t comp_ty_id = curr_ty_inst->GetSingleWordInOperand(0u);
436 uint32_t curr_idx_32b_id = Gen32BitCvtCode(curr_idx_id, builder);
437 if (in_matrix && !col_major) {
438 Instruction* curr_offset_inst = builder->AddBinaryOp(
439 GetUintId(), SpvOpIMul, matrix_stride_id, curr_idx_32b_id);
440 curr_offset_id = curr_offset_inst->result_id();
441 } else {
442 uint32_t comp_ty_sz = ByteSize(comp_ty_id, 0u, false, false);
443 uint32_t comp_ty_sz_id = builder->GetUintConstantId(comp_ty_sz);
444 Instruction* curr_offset_inst = builder->AddBinaryOp(
445 GetUintId(), SpvOpIMul, comp_ty_sz_id, curr_idx_32b_id);
446 curr_offset_id = curr_offset_inst->result_id();
447 }
448 // Get element type for next step
449 curr_ty_id = comp_ty_id;
450 } break;
451 case SpvOpTypeStruct: {
452 // Get buffer byte offset for the referenced member
453 Instruction* curr_idx_inst = get_def_use_mgr()->GetDef(curr_idx_id);
454 assert(curr_idx_inst->opcode() == SpvOpConstant &&
455 "unexpected struct index");
456 uint32_t member_idx = curr_idx_inst->GetSingleWordInOperand(0);
457 uint32_t member_offset = 0xdeadbeef;
458 bool found = get_decoration_mgr()->FindDecoration(
459 curr_ty_id, SpvDecorationOffset,
460 [&member_idx, &member_offset](const Instruction& deco_inst) {
461 if (deco_inst.GetSingleWordInOperand(1u) != member_idx)
462 return false;
463 member_offset = deco_inst.GetSingleWordInOperand(3u);
464 return true;
465 });
466 USE_ASSERT(found && "member offset not found");
467 curr_offset_id = builder->GetUintConstantId(member_offset);
468 // Look for matrix stride for this member if there is one. The matrix
469 // stride is not on the matrix type, but in a OpMemberDecorate on the
470 // enclosing struct type at the member index. If none found, reset
471 // stride to 0.
472 found = get_decoration_mgr()->FindDecoration(
473 curr_ty_id, SpvDecorationMatrixStride,
474 [&member_idx, &matrix_stride](const Instruction& deco_inst) {
475 if (deco_inst.GetSingleWordInOperand(1u) != member_idx)
476 return false;
477 matrix_stride = deco_inst.GetSingleWordInOperand(3u);
478 return true;
479 });
480 if (!found) matrix_stride = 0;
481 // Look for column major decoration
482 found = get_decoration_mgr()->FindDecoration(
483 curr_ty_id, SpvDecorationColMajor,
484 [&member_idx, &col_major](const Instruction& deco_inst) {
485 if (deco_inst.GetSingleWordInOperand(1u) != member_idx)
486 return false;
487 col_major = true;
488 return true;
489 });
490 if (!found) col_major = false;
491 // Get element type for next step
492 curr_ty_id = curr_ty_inst->GetSingleWordInOperand(member_idx);
493 } break;
494 default: { assert(false && "unexpected non-composite type"); } break;
495 }
496 if (sum_id == 0)
497 sum_id = curr_offset_id;
498 else {
499 Instruction* sum_inst =
500 builder->AddBinaryOp(GetUintId(), SpvOpIAdd, sum_id, curr_offset_id);
501 sum_id = sum_inst->result_id();
502 }
503 ++ac_in_idx;
504 }
505 // Add in offset of last byte of referenced object
506 uint32_t bsize = ByteSize(curr_ty_id, matrix_stride, col_major, in_matrix);
507 uint32_t last = bsize - 1;
508 uint32_t last_id = builder->GetUintConstantId(last);
509 Instruction* sum_inst =
510 builder->AddBinaryOp(GetUintId(), SpvOpIAdd, sum_id, last_id);
511 return sum_inst->result_id();
512 }
513
GenCheckCode(uint32_t check_id,uint32_t error_id,uint32_t offset_id,uint32_t length_id,uint32_t stage_idx,RefAnalysis * ref,std::vector<std::unique_ptr<BasicBlock>> * new_blocks)514 void InstBindlessCheckPass::GenCheckCode(
515 uint32_t check_id, uint32_t error_id, uint32_t offset_id,
516 uint32_t length_id, uint32_t stage_idx, RefAnalysis* ref,
517 std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
518 BasicBlock* back_blk_ptr = &*new_blocks->back();
519 InstructionBuilder builder(
520 context(), back_blk_ptr,
521 IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
522 // Gen conditional branch on check_id. Valid branch generates original
523 // reference. Invalid generates debug output and zero result (if needed).
524 uint32_t merge_blk_id = TakeNextId();
525 uint32_t valid_blk_id = TakeNextId();
526 uint32_t invalid_blk_id = TakeNextId();
527 std::unique_ptr<Instruction> merge_label(NewLabel(merge_blk_id));
528 std::unique_ptr<Instruction> valid_label(NewLabel(valid_blk_id));
529 std::unique_ptr<Instruction> invalid_label(NewLabel(invalid_blk_id));
530 (void)builder.AddConditionalBranch(check_id, valid_blk_id, invalid_blk_id,
531 merge_blk_id, SpvSelectionControlMaskNone);
532 // Gen valid bounds branch
533 std::unique_ptr<BasicBlock> new_blk_ptr(
534 new BasicBlock(std::move(valid_label)));
535 builder.SetInsertPoint(&*new_blk_ptr);
536 uint32_t new_ref_id = CloneOriginalReference(ref, &builder);
537 (void)builder.AddBranch(merge_blk_id);
538 new_blocks->push_back(std::move(new_blk_ptr));
539 // Gen invalid block
540 new_blk_ptr.reset(new BasicBlock(std::move(invalid_label)));
541 builder.SetInsertPoint(&*new_blk_ptr);
542 uint32_t u_index_id = GenUintCastCode(ref->desc_idx_id, &builder);
543 if (offset_id != 0) {
544 // Buffer OOB
545 uint32_t u_offset_id = GenUintCastCode(offset_id, &builder);
546 uint32_t u_length_id = GenUintCastCode(length_id, &builder);
547 GenDebugStreamWrite(uid2offset_[ref->ref_inst->unique_id()], stage_idx,
548 {error_id, u_index_id, u_offset_id, u_length_id},
549 &builder);
550 } else if (buffer_bounds_enabled_ || texel_buffer_enabled_) {
551 // Uninitialized Descriptor - Return additional unused zero so all error
552 // modes will use same debug stream write function
553 uint32_t u_length_id = GenUintCastCode(length_id, &builder);
554 GenDebugStreamWrite(
555 uid2offset_[ref->ref_inst->unique_id()], stage_idx,
556 {error_id, u_index_id, u_length_id, builder.GetUintConstantId(0)},
557 &builder);
558 } else {
559 // Uninitialized Descriptor - Normal error return
560 uint32_t u_length_id = GenUintCastCode(length_id, &builder);
561 GenDebugStreamWrite(uid2offset_[ref->ref_inst->unique_id()], stage_idx,
562 {error_id, u_index_id, u_length_id}, &builder);
563 }
564 // Remember last invalid block id
565 uint32_t last_invalid_blk_id = new_blk_ptr->GetLabelInst()->result_id();
566 // Gen zero for invalid reference
567 uint32_t ref_type_id = ref->ref_inst->type_id();
568 (void)builder.AddBranch(merge_blk_id);
569 new_blocks->push_back(std::move(new_blk_ptr));
570 // Gen merge block
571 new_blk_ptr.reset(new BasicBlock(std::move(merge_label)));
572 builder.SetInsertPoint(&*new_blk_ptr);
573 // Gen phi of new reference and zero, if necessary, and replace the
574 // result id of the original reference with that of the Phi. Kill original
575 // reference.
576 if (new_ref_id != 0) {
577 Instruction* phi_inst = builder.AddPhi(
578 ref_type_id, {new_ref_id, valid_blk_id, GetNullId(ref_type_id),
579 last_invalid_blk_id});
580 context()->ReplaceAllUsesWith(ref->ref_inst->result_id(),
581 phi_inst->result_id());
582 }
583 new_blocks->push_back(std::move(new_blk_ptr));
584 context()->KillInst(ref->ref_inst);
585 }
586
GenDescIdxCheckCode(BasicBlock::iterator ref_inst_itr,UptrVectorIterator<BasicBlock> ref_block_itr,uint32_t stage_idx,std::vector<std::unique_ptr<BasicBlock>> * new_blocks)587 void InstBindlessCheckPass::GenDescIdxCheckCode(
588 BasicBlock::iterator ref_inst_itr,
589 UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t stage_idx,
590 std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
591 // Look for reference through indexed descriptor. If found, analyze and
592 // save components. If not, return.
593 RefAnalysis ref;
594 if (!AnalyzeDescriptorReference(&*ref_inst_itr, &ref)) return;
595 Instruction* ptr_inst = get_def_use_mgr()->GetDef(ref.ptr_id);
596 if (ptr_inst->opcode() != SpvOp::SpvOpAccessChain) return;
597 // If index and bound both compile-time constants and index < bound,
598 // return without changing
599 Instruction* var_inst = get_def_use_mgr()->GetDef(ref.var_id);
600 Instruction* desc_type_inst = GetPointeeTypeInst(var_inst);
601 uint32_t length_id = 0;
602 if (desc_type_inst->opcode() == SpvOpTypeArray) {
603 length_id =
604 desc_type_inst->GetSingleWordInOperand(kSpvTypeArrayLengthIdInIdx);
605 Instruction* index_inst = get_def_use_mgr()->GetDef(ref.desc_idx_id);
606 Instruction* length_inst = get_def_use_mgr()->GetDef(length_id);
607 if (index_inst->opcode() == SpvOpConstant &&
608 length_inst->opcode() == SpvOpConstant &&
609 index_inst->GetSingleWordInOperand(kSpvConstantValueInIdx) <
610 length_inst->GetSingleWordInOperand(kSpvConstantValueInIdx))
611 return;
612 } else if (!desc_idx_enabled_ ||
613 desc_type_inst->opcode() != SpvOpTypeRuntimeArray) {
614 return;
615 }
616 // Move original block's preceding instructions into first new block
617 std::unique_ptr<BasicBlock> new_blk_ptr;
618 MovePreludeCode(ref_inst_itr, ref_block_itr, &new_blk_ptr);
619 InstructionBuilder builder(
620 context(), &*new_blk_ptr,
621 IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
622 new_blocks->push_back(std::move(new_blk_ptr));
623 uint32_t error_id = builder.GetUintConstantId(kInstErrorBindlessBounds);
624 // If length id not yet set, descriptor array is runtime size so
625 // generate load of length from stage's debug input buffer.
626 if (length_id == 0) {
627 assert(desc_type_inst->opcode() == SpvOpTypeRuntimeArray &&
628 "unexpected bindless type");
629 length_id = GenDebugReadLength(ref.var_id, &builder);
630 }
631 // Generate full runtime bounds test code with true branch
632 // being full reference and false branch being debug output and zero
633 // for the referenced value.
634 uint32_t desc_idx_32b_id = Gen32BitCvtCode(ref.desc_idx_id, &builder);
635 uint32_t length_32b_id = Gen32BitCvtCode(length_id, &builder);
636 Instruction* ult_inst = builder.AddBinaryOp(GetBoolId(), SpvOpULessThan,
637 desc_idx_32b_id, length_32b_id);
638 ref.desc_idx_id = desc_idx_32b_id;
639 GenCheckCode(ult_inst->result_id(), error_id, 0u, length_id, stage_idx, &ref,
640 new_blocks);
641 // Move original block's remaining code into remainder/merge block and add
642 // to new blocks
643 BasicBlock* back_blk_ptr = &*new_blocks->back();
644 MovePostludeCode(ref_block_itr, back_blk_ptr);
645 }
646
GenDescInitCheckCode(BasicBlock::iterator ref_inst_itr,UptrVectorIterator<BasicBlock> ref_block_itr,uint32_t stage_idx,std::vector<std::unique_ptr<BasicBlock>> * new_blocks)647 void InstBindlessCheckPass::GenDescInitCheckCode(
648 BasicBlock::iterator ref_inst_itr,
649 UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t stage_idx,
650 std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
651 // Look for reference through descriptor. If not, return.
652 RefAnalysis ref;
653 if (!AnalyzeDescriptorReference(&*ref_inst_itr, &ref)) return;
654 // Determine if we can only do initialization check
655 bool init_check = false;
656 if (ref.desc_load_id != 0 || !buffer_bounds_enabled_) {
657 init_check = true;
658 } else {
659 // For now, only do bounds check for non-aggregate types. Otherwise
660 // just do descriptor initialization check.
661 // TODO(greg-lunarg): Do bounds check for aggregate loads and stores
662 Instruction* ref_ptr_inst = get_def_use_mgr()->GetDef(ref.ptr_id);
663 Instruction* pte_type_inst = GetPointeeTypeInst(ref_ptr_inst);
664 uint32_t pte_type_op = pte_type_inst->opcode();
665 if (pte_type_op == SpvOpTypeArray || pte_type_op == SpvOpTypeRuntimeArray ||
666 pte_type_op == SpvOpTypeStruct)
667 init_check = true;
668 }
669 // If initialization check and not enabled, return
670 if (init_check && !desc_init_enabled_) return;
671 // Move original block's preceding instructions into first new block
672 std::unique_ptr<BasicBlock> new_blk_ptr;
673 MovePreludeCode(ref_inst_itr, ref_block_itr, &new_blk_ptr);
674 InstructionBuilder builder(
675 context(), &*new_blk_ptr,
676 IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
677 new_blocks->push_back(std::move(new_blk_ptr));
678 // If initialization check, use reference value of zero.
679 // Else use the index of the last byte referenced.
680 uint32_t ref_id = init_check ? builder.GetUintConstantId(0u)
681 : GenLastByteIdx(&ref, &builder);
682 // Read initialization/bounds from debug input buffer. If index id not yet
683 // set, binding is single descriptor, so set index to constant 0.
684 if (ref.desc_idx_id == 0) ref.desc_idx_id = builder.GetUintConstantId(0u);
685 uint32_t init_id = GenDebugReadInit(ref.var_id, ref.desc_idx_id, &builder);
686 // Generate runtime initialization/bounds test code with true branch
687 // being full reference and false branch being debug output and zero
688 // for the referenced value.
689 Instruction* ult_inst =
690 builder.AddBinaryOp(GetBoolId(), SpvOpULessThan, ref_id, init_id);
691 uint32_t error = init_check ? kInstErrorBindlessUninit
692 : (ref.strg_class == SpvStorageClassUniform
693 ? kInstErrorBuffOOBUniform
694 : kInstErrorBuffOOBStorage);
695 uint32_t error_id = builder.GetUintConstantId(error);
696 GenCheckCode(ult_inst->result_id(), error_id, init_check ? 0 : ref_id,
697 init_check ? builder.GetUintConstantId(0u) : init_id, stage_idx,
698 &ref, new_blocks);
699 // Move original block's remaining code into remainder/merge block and add
700 // to new blocks
701 BasicBlock* back_blk_ptr = &*new_blocks->back();
702 MovePostludeCode(ref_block_itr, back_blk_ptr);
703 }
704
GenTexBuffCheckCode(BasicBlock::iterator ref_inst_itr,UptrVectorIterator<BasicBlock> ref_block_itr,uint32_t stage_idx,std::vector<std::unique_ptr<BasicBlock>> * new_blocks)705 void InstBindlessCheckPass::GenTexBuffCheckCode(
706 BasicBlock::iterator ref_inst_itr,
707 UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t stage_idx,
708 std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
709 // Only process OpImageRead and OpImageWrite with no optional operands
710 Instruction* ref_inst = &*ref_inst_itr;
711 SpvOp op = ref_inst->opcode();
712 uint32_t num_in_oprnds = ref_inst->NumInOperands();
713 if (!((op == SpvOpImageRead && num_in_oprnds == 2) ||
714 (op == SpvOpImageFetch && num_in_oprnds == 2) ||
715 (op == SpvOpImageWrite && num_in_oprnds == 3)))
716 return;
717 // Pull components from descriptor reference
718 RefAnalysis ref;
719 if (!AnalyzeDescriptorReference(ref_inst, &ref)) return;
720 // Only process if image is texel buffer
721 Instruction* image_inst = get_def_use_mgr()->GetDef(ref.image_id);
722 uint32_t image_ty_id = image_inst->type_id();
723 Instruction* image_ty_inst = get_def_use_mgr()->GetDef(image_ty_id);
724 if (image_ty_inst->GetSingleWordInOperand(kSpvTypeImageDim) != SpvDimBuffer)
725 return;
726 if (image_ty_inst->GetSingleWordInOperand(kSpvTypeImageDepth) != 0) return;
727 if (image_ty_inst->GetSingleWordInOperand(kSpvTypeImageArrayed) != 0) return;
728 if (image_ty_inst->GetSingleWordInOperand(kSpvTypeImageMS) != 0) return;
729 // Enable ImageQuery Capability if not yet enabled
730 if (!get_feature_mgr()->HasCapability(SpvCapabilityImageQuery)) {
731 std::unique_ptr<Instruction> cap_image_query_inst(new Instruction(
732 context(), SpvOpCapability, 0, 0,
733 std::initializer_list<Operand>{
734 {SPV_OPERAND_TYPE_CAPABILITY, {SpvCapabilityImageQuery}}}));
735 get_def_use_mgr()->AnalyzeInstDefUse(&*cap_image_query_inst);
736 context()->AddCapability(std::move(cap_image_query_inst));
737 }
738 // Move original block's preceding instructions into first new block
739 std::unique_ptr<BasicBlock> new_blk_ptr;
740 MovePreludeCode(ref_inst_itr, ref_block_itr, &new_blk_ptr);
741 InstructionBuilder builder(
742 context(), &*new_blk_ptr,
743 IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
744 new_blocks->push_back(std::move(new_blk_ptr));
745 // Get texel coordinate
746 uint32_t coord_id =
747 GenUintCastCode(ref_inst->GetSingleWordInOperand(1), &builder);
748 // If index id not yet set, binding is single descriptor, so set index to
749 // constant 0.
750 if (ref.desc_idx_id == 0) ref.desc_idx_id = builder.GetUintConstantId(0u);
751 // Get texel buffer size.
752 Instruction* size_inst =
753 builder.AddUnaryOp(GetUintId(), SpvOpImageQuerySize, ref.image_id);
754 uint32_t size_id = size_inst->result_id();
755 // Generate runtime initialization/bounds test code with true branch
756 // being full reference and false branch being debug output and zero
757 // for the referenced value.
758 Instruction* ult_inst =
759 builder.AddBinaryOp(GetBoolId(), SpvOpULessThan, coord_id, size_id);
760 uint32_t error =
761 (image_ty_inst->GetSingleWordInOperand(kSpvTypeImageSampled) == 2)
762 ? kInstErrorBuffOOBStorageTexel
763 : kInstErrorBuffOOBUniformTexel;
764 uint32_t error_id = builder.GetUintConstantId(error);
765 GenCheckCode(ult_inst->result_id(), error_id, coord_id, size_id, stage_idx,
766 &ref, new_blocks);
767 // Move original block's remaining code into remainder/merge block and add
768 // to new blocks
769 BasicBlock* back_blk_ptr = &*new_blocks->back();
770 MovePostludeCode(ref_block_itr, back_blk_ptr);
771 }
772
InitializeInstBindlessCheck()773 void InstBindlessCheckPass::InitializeInstBindlessCheck() {
774 // Initialize base class
775 InitializeInstrument();
776 // If runtime array length support or buffer bounds checking are enabled,
777 // create variable mappings. Length support is always enabled if descriptor
778 // init check is enabled.
779 if (desc_idx_enabled_ || buffer_bounds_enabled_ || texel_buffer_enabled_)
780 for (auto& anno : get_module()->annotations())
781 if (anno.opcode() == SpvOpDecorate) {
782 if (anno.GetSingleWordInOperand(1u) == SpvDecorationDescriptorSet)
783 var2desc_set_[anno.GetSingleWordInOperand(0u)] =
784 anno.GetSingleWordInOperand(2u);
785 else if (anno.GetSingleWordInOperand(1u) == SpvDecorationBinding)
786 var2binding_[anno.GetSingleWordInOperand(0u)] =
787 anno.GetSingleWordInOperand(2u);
788 }
789 }
790
ProcessImpl()791 Pass::Status InstBindlessCheckPass::ProcessImpl() {
792 // Perform bindless bounds check on each entry point function in module
793 InstProcessFunction pfn =
794 [this](BasicBlock::iterator ref_inst_itr,
795 UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t stage_idx,
796 std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
797 return GenDescIdxCheckCode(ref_inst_itr, ref_block_itr, stage_idx,
798 new_blocks);
799 };
800 bool modified = InstProcessEntryPointCallTree(pfn);
801 if (desc_init_enabled_ || buffer_bounds_enabled_) {
802 // Perform descriptor initialization and/or buffer bounds check on each
803 // entry point function in module
804 pfn = [this](BasicBlock::iterator ref_inst_itr,
805 UptrVectorIterator<BasicBlock> ref_block_itr,
806 uint32_t stage_idx,
807 std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
808 return GenDescInitCheckCode(ref_inst_itr, ref_block_itr, stage_idx,
809 new_blocks);
810 };
811 modified |= InstProcessEntryPointCallTree(pfn);
812 }
813 if (texel_buffer_enabled_) {
814 // Perform texel buffer bounds check on each entry point function in
815 // module. Generate after descriptor bounds and initialization checks.
816 pfn = [this](BasicBlock::iterator ref_inst_itr,
817 UptrVectorIterator<BasicBlock> ref_block_itr,
818 uint32_t stage_idx,
819 std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
820 return GenTexBuffCheckCode(ref_inst_itr, ref_block_itr, stage_idx,
821 new_blocks);
822 };
823 modified |= InstProcessEntryPointCallTree(pfn);
824 }
825 return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
826 }
827
Process()828 Pass::Status InstBindlessCheckPass::Process() {
829 InitializeInstBindlessCheck();
830 return ProcessImpl();
831 }
832
833 } // namespace opt
834 } // namespace spvtools
835