1// Copyright (c) 2019 The Khronos Group Inc.
2// Copyright (c) 2019 Valve Corporation
3// Copyright (c) 2019 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_buff_addr_check_pass.h"
18
19namespace spvtools {
20namespace opt {
21
22uint32_t InstBuffAddrCheckPass::CloneOriginalReference(
23 Instruction* ref_inst, InstructionBuilder* builder) {
24 // Clone original ref with new result id (if load)
25 assert(
26 (ref_inst->opcode() == SpvOpLoad || ref_inst->opcode() == SpvOpStore) &&
27 "unexpected ref");
28 std::unique_ptr<Instruction> new_ref_inst(ref_inst->Clone(context()));
29 uint32_t ref_result_id = ref_inst->result_id();
30 uint32_t new_ref_id = 0;
31 if (ref_result_id != 0) {
32 new_ref_id = TakeNextId();
33 new_ref_inst->SetResultId(new_ref_id);
34 }
35 // Register new reference and add to new block
36 Instruction* added_inst = builder->AddInstruction(std::move(new_ref_inst));
37 uid2offset_[added_inst->unique_id()] = uid2offset_[ref_inst->unique_id()];
38 if (new_ref_id != 0)
39 get_decoration_mgr()->CloneDecorations(ref_result_id, new_ref_id);
40 return new_ref_id;
41}
42
43bool InstBuffAddrCheckPass::IsPhysicalBuffAddrReference(Instruction* ref_inst) {
44 if (ref_inst->opcode() != SpvOpLoad && ref_inst->opcode() != SpvOpStore)
45 return false;
46 uint32_t ptr_id = ref_inst->GetSingleWordInOperand(0);
47 analysis::DefUseManager* du_mgr = get_def_use_mgr();
48 Instruction* ptr_inst = du_mgr->GetDef(ptr_id);
49 if (ptr_inst->opcode() != SpvOpAccessChain) return false;
50 uint32_t ptr_ty_id = ptr_inst->type_id();
51 Instruction* ptr_ty_inst = du_mgr->GetDef(ptr_ty_id);
52 if (ptr_ty_inst->GetSingleWordInOperand(0) !=
53 SpvStorageClassPhysicalStorageBufferEXT)
54 return false;
55 return true;
56}
57
58// TODO(greg-lunarg): Refactor with InstBindlessCheckPass::GenCheckCode() ??
59void InstBuffAddrCheckPass::GenCheckCode(
60 uint32_t check_id, uint32_t error_id, uint32_t ref_uptr_id,
61 uint32_t stage_idx, Instruction* ref_inst,
62 std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
63 BasicBlock* back_blk_ptr = &*new_blocks->back();
64 InstructionBuilder builder(
65 context(), back_blk_ptr,
66 IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
67 // Gen conditional branch on check_id. Valid branch generates original
68 // reference. Invalid generates debug output and zero result (if needed).
69 uint32_t merge_blk_id = TakeNextId();
70 uint32_t valid_blk_id = TakeNextId();
71 uint32_t invalid_blk_id = TakeNextId();
72 std::unique_ptr<Instruction> merge_label(NewLabel(merge_blk_id));
73 std::unique_ptr<Instruction> valid_label(NewLabel(valid_blk_id));
74 std::unique_ptr<Instruction> invalid_label(NewLabel(invalid_blk_id));
75 (void)builder.AddConditionalBranch(check_id, valid_blk_id, invalid_blk_id,
76 merge_blk_id, SpvSelectionControlMaskNone);
77 // Gen valid branch
78 std::unique_ptr<BasicBlock> new_blk_ptr(
79 new BasicBlock(std::move(valid_label)));
80 builder.SetInsertPoint(&*new_blk_ptr);
81 uint32_t new_ref_id = CloneOriginalReference(ref_inst, &builder);
82 (void)builder.AddBranch(merge_blk_id);
83 new_blocks->push_back(std::move(new_blk_ptr));
84 // Gen invalid block
85 new_blk_ptr.reset(new BasicBlock(std::move(invalid_label)));
86 builder.SetInsertPoint(&*new_blk_ptr);
87 // Convert uptr from uint64 to 2 uint32
88 Instruction* lo_uptr_inst =
89 builder.AddUnaryOp(GetUintId(), SpvOpUConvert, ref_uptr_id);
90 Instruction* rshift_uptr_inst =
91 builder.AddBinaryOp(GetUint64Id(), SpvOpShiftRightLogical, ref_uptr_id,
92 builder.GetUintConstantId(32));
93 Instruction* hi_uptr_inst = builder.AddUnaryOp(GetUintId(), SpvOpUConvert,
94 rshift_uptr_inst->result_id());
95 GenDebugStreamWrite(
96 uid2offset_[ref_inst->unique_id()], stage_idx,
97 {error_id, lo_uptr_inst->result_id(), hi_uptr_inst->result_id()},
98 &builder);
99 // Gen zero for invalid load. If pointer type, need to convert uint64
100 // zero to pointer; cannot create ConstantNull of pointer type.
101 uint32_t null_id = 0;
102 if (new_ref_id != 0) {
103 uint32_t ref_type_id = ref_inst->type_id();
104 analysis::TypeManager* type_mgr = context()->get_type_mgr();
105 analysis::Type* ref_type = type_mgr->GetType(ref_type_id);
106 if (ref_type->AsPointer() != nullptr) {
107 uint32_t null_u64_id = GetNullId(GetUint64Id());
108 Instruction* null_ptr_inst =
109 builder.AddUnaryOp(ref_type_id, SpvOpConvertUToPtr, null_u64_id);
110 null_id = null_ptr_inst->result_id();
111 } else {
112 null_id = GetNullId(ref_type_id);
113 }
114 }
115 (void)builder.AddBranch(merge_blk_id);
116 new_blocks->push_back(std::move(new_blk_ptr));
117 // Gen merge block
118 new_blk_ptr.reset(new BasicBlock(std::move(merge_label)));
119 builder.SetInsertPoint(&*new_blk_ptr);
120 // Gen phi of new reference and zero, if necessary, and replace the
121 // result id of the original reference with that of the Phi. Kill original
122 // reference.
123 if (new_ref_id != 0) {
124 Instruction* phi_inst =
125 builder.AddPhi(ref_inst->type_id(),
126 {new_ref_id, valid_blk_id, null_id, invalid_blk_id});
127 context()->ReplaceAllUsesWith(ref_inst->result_id(), phi_inst->result_id());
128 }
129 new_blocks->push_back(std::move(new_blk_ptr));
130 context()->KillInst(ref_inst);
131}
132
133uint32_t InstBuffAddrCheckPass::GetTypeLength(uint32_t type_id) {
134 Instruction* type_inst = get_def_use_mgr()->GetDef(type_id);
135 switch (type_inst->opcode()) {
136 case SpvOpTypeFloat:
137 case SpvOpTypeInt:
138 return type_inst->GetSingleWordInOperand(0) / 8u;
139 case SpvOpTypeVector:
140 case SpvOpTypeMatrix:
141 return type_inst->GetSingleWordInOperand(1) *
142 GetTypeLength(type_inst->GetSingleWordInOperand(0));
143 case SpvOpTypePointer:
144 assert(type_inst->GetSingleWordInOperand(0) ==
145 SpvStorageClassPhysicalStorageBufferEXT &&
146 "unexpected pointer type");
147 return 8u;
148 default:
149 assert(false && "unexpected buffer reference type");
150 return 0;
151 }
152}
153
154void InstBuffAddrCheckPass::AddParam(uint32_t type_id,
155 std::vector<uint32_t>* param_vec,
156 std::unique_ptr<Function>* input_func) {
157 uint32_t pid = TakeNextId();
158 param_vec->push_back(pid);
159 std::unique_ptr<Instruction> param_inst(new Instruction(
160 get_module()->context(), SpvOpFunctionParameter, type_id, pid, {}));
161 get_def_use_mgr()->AnalyzeInstDefUse(&*param_inst);
162 (*input_func)->AddParameter(std::move(param_inst));
163}
164
165uint32_t InstBuffAddrCheckPass::GetSearchAndTestFuncId() {
166 if (search_test_func_id_ == 0) {
167 // Generate function "bool search_and_test(uint64_t ref_ptr, uint32_t len)"
168 // which searches input buffer for buffer which most likely contains the
169 // pointer value |ref_ptr| and verifies that the entire reference of
170 // length |len| bytes is contained in the buffer.
171 search_test_func_id_ = TakeNextId();
172 analysis::TypeManager* type_mgr = context()->get_type_mgr();
173 std::vector<const analysis::Type*> param_types = {
174 type_mgr->GetType(GetUint64Id()), type_mgr->GetType(GetUintId())};
175 analysis::Function func_ty(type_mgr->GetType(GetBoolId()), param_types);
176 analysis::Type* reg_func_ty = type_mgr->GetRegisteredType(&func_ty);
177 std::unique_ptr<Instruction> func_inst(
178 new Instruction(get_module()->context(), SpvOpFunction, GetBoolId(),
179 search_test_func_id_,
180 {{spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER,
181 {SpvFunctionControlMaskNone}},
182 {spv_operand_type_t::SPV_OPERAND_TYPE_ID,
183 {type_mgr->GetTypeInstruction(reg_func_ty)}}}));
184 get_def_use_mgr()->AnalyzeInstDefUse(&*func_inst);
185 std::unique_ptr<Function> input_func =
186 MakeUnique<Function>(std::move(func_inst));
187 std::vector<uint32_t> param_vec;
188 // Add ref_ptr and length parameters
189 AddParam(GetUint64Id(), &param_vec, &input_func);
190 AddParam(GetUintId(), &param_vec, &input_func);
191 // Empty first block.
192 uint32_t first_blk_id = TakeNextId();
193 std::unique_ptr<Instruction> first_blk_label(NewLabel(first_blk_id));
194 std::unique_ptr<BasicBlock> first_blk_ptr =
195 MakeUnique<BasicBlock>(std::move(first_blk_label));
196 InstructionBuilder builder(
197 context(), &*first_blk_ptr,
198 IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
199 uint32_t hdr_blk_id = TakeNextId();
200 // Branch to search loop header
201 std::unique_ptr<Instruction> hdr_blk_label(NewLabel(hdr_blk_id));
202 (void)builder.AddInstruction(MakeUnique<Instruction>(
203 context(), SpvOpBranch, 0, 0,
204 std::initializer_list<Operand>{{SPV_OPERAND_TYPE_ID, {hdr_blk_id}}}));
205 first_blk_ptr->SetParent(&*input_func);
206 input_func->AddBasicBlock(std::move(first_blk_ptr));
207 // Linear search loop header block
208 // TODO(greg-lunarg): Implement binary search
209 std::unique_ptr<BasicBlock> hdr_blk_ptr =
210 MakeUnique<BasicBlock>(std::move(hdr_blk_label));
211 builder.SetInsertPoint(&*hdr_blk_ptr);
212 // Phi for search index. Starts with 1.
213 uint32_t cont_blk_id = TakeNextId();
214 std::unique_ptr<Instruction> cont_blk_label(NewLabel(cont_blk_id));
215 // Deal with def-use cycle caused by search loop index computation.
216 // Create Add and Phi instructions first, then do Def analysis on Add.
217 // Add Phi and Add instructions and do Use analysis later.
218 uint32_t idx_phi_id = TakeNextId();
219 uint32_t idx_inc_id = TakeNextId();
220 std::unique_ptr<Instruction> idx_inc_inst(new Instruction(
221 context(), SpvOpIAdd, GetUintId(), idx_inc_id,
222 {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {idx_phi_id}},
223 {spv_operand_type_t::SPV_OPERAND_TYPE_ID,
224 {builder.GetUintConstantId(1u)}}}));
225 std::unique_ptr<Instruction> idx_phi_inst(new Instruction(
226 context(), SpvOpPhi, GetUintId(), idx_phi_id,
227 {{spv_operand_type_t::SPV_OPERAND_TYPE_ID,
228 {builder.GetUintConstantId(1u)}},
229 {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {first_blk_id}},
230 {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {idx_inc_id}},
231 {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {cont_blk_id}}}));
232 get_def_use_mgr()->AnalyzeInstDef(&*idx_inc_inst);
233 // Add (previously created) search index phi
234 (void)builder.AddInstruction(std::move(idx_phi_inst));
235 // LoopMerge
236 uint32_t bound_test_blk_id = TakeNextId();
237 std::unique_ptr<Instruction> bound_test_blk_label(
238 NewLabel(bound_test_blk_id));
239 (void)builder.AddInstruction(MakeUnique<Instruction>(
240 context(), SpvOpLoopMerge, 0, 0,
241 std::initializer_list<Operand>{
242 {SPV_OPERAND_TYPE_ID, {bound_test_blk_id}},
243 {SPV_OPERAND_TYPE_ID, {cont_blk_id}},
244 {SPV_OPERAND_TYPE_LITERAL_INTEGER, {SpvLoopControlMaskNone}}}));
245 // Branch to continue/work block
246 (void)builder.AddInstruction(MakeUnique<Instruction>(
247 context(), SpvOpBranch, 0, 0,
248 std::initializer_list<Operand>{{SPV_OPERAND_TYPE_ID, {cont_blk_id}}}));
249 hdr_blk_ptr->SetParent(&*input_func);
250 input_func->AddBasicBlock(std::move(hdr_blk_ptr));
251 // Continue/Work Block. Read next buffer pointer and break if greater
252 // than ref_ptr arg.
253 std::unique_ptr<BasicBlock> cont_blk_ptr =
254 MakeUnique<BasicBlock>(std::move(cont_blk_label));
255 builder.SetInsertPoint(&*cont_blk_ptr);
256 // Add (previously created) search index increment now.
257 (void)builder.AddInstruction(std::move(idx_inc_inst));
258 // Load next buffer address from debug input buffer
259 uint32_t ibuf_id = GetInputBufferId();
260 uint32_t ibuf_ptr_id = GetInputBufferPtrId();
261 Instruction* uptr_ac_inst = builder.AddTernaryOp(
262 ibuf_ptr_id, SpvOpAccessChain, ibuf_id,
263 builder.GetUintConstantId(kDebugInputDataOffset), idx_inc_id);
264 uint32_t ibuf_type_id = GetInputBufferTypeId();
265 Instruction* uptr_load_inst =
266 builder.AddUnaryOp(ibuf_type_id, SpvOpLoad, uptr_ac_inst->result_id());
267 // If loaded address greater than ref_ptr arg, break, else branch back to
268 // loop header
269 Instruction* uptr_test_inst =
270 builder.AddBinaryOp(GetBoolId(), SpvOpUGreaterThan,
271 uptr_load_inst->result_id(), param_vec[0]);
272 (void)builder.AddConditionalBranch(uptr_test_inst->result_id(),
273 bound_test_blk_id, hdr_blk_id,
274 kInvalidId, SpvSelectionControlMaskNone);
275 cont_blk_ptr->SetParent(&*input_func);
276 input_func->AddBasicBlock(std::move(cont_blk_ptr));
277 // Bounds test block. Read length of selected buffer and test that
278 // all len arg bytes are in buffer.
279 std::unique_ptr<BasicBlock> bound_test_blk_ptr =
280 MakeUnique<BasicBlock>(std::move(bound_test_blk_label));
281 builder.SetInsertPoint(&*bound_test_blk_ptr);
282 // Decrement index to point to previous/candidate buffer address
283 Instruction* cand_idx_inst = builder.AddBinaryOp(
284 GetUintId(), SpvOpISub, idx_inc_id, builder.GetUintConstantId(1u));
285 // Load candidate buffer address
286 Instruction* cand_ac_inst =
287 builder.AddTernaryOp(ibuf_ptr_id, SpvOpAccessChain, ibuf_id,
288 builder.GetUintConstantId(kDebugInputDataOffset),
289 cand_idx_inst->result_id());
290 Instruction* cand_load_inst =
291 builder.AddUnaryOp(ibuf_type_id, SpvOpLoad, cand_ac_inst->result_id());
292 // Compute offset of ref_ptr from candidate buffer address
293 Instruction* offset_inst = builder.AddBinaryOp(
294 ibuf_type_id, SpvOpISub, param_vec[0], cand_load_inst->result_id());
295 // Convert ref length to uint64
296 Instruction* ref_len_64_inst =
297 builder.AddUnaryOp(ibuf_type_id, SpvOpUConvert, param_vec[1]);
298 // Add ref length to ref offset to compute end of reference
299 Instruction* ref_end_inst =
300 builder.AddBinaryOp(ibuf_type_id, SpvOpIAdd, offset_inst->result_id(),
301 ref_len_64_inst->result_id());
302 // Load starting index of lengths in input buffer and convert to uint32
303 Instruction* len_start_ac_inst =
304 builder.AddTernaryOp(ibuf_ptr_id, SpvOpAccessChain, ibuf_id,
305 builder.GetUintConstantId(kDebugInputDataOffset),
306 builder.GetUintConstantId(0u));
307 Instruction* len_start_load_inst = builder.AddUnaryOp(
308 ibuf_type_id, SpvOpLoad, len_start_ac_inst->result_id());
309 Instruction* len_start_32_inst = builder.AddUnaryOp(
310 GetUintId(), SpvOpUConvert, len_start_load_inst->result_id());
311 // Decrement search index to get candidate buffer length index
312 Instruction* cand_len_idx_inst =
313 builder.AddBinaryOp(GetUintId(), SpvOpISub, cand_idx_inst->result_id(),
314 builder.GetUintConstantId(1u));
315 // Add candidate length index to start index
316 Instruction* len_idx_inst = builder.AddBinaryOp(
317 GetUintId(), SpvOpIAdd, cand_len_idx_inst->result_id(),
318 len_start_32_inst->result_id());
319 // Load candidate buffer length
320 Instruction* len_ac_inst =
321 builder.AddTernaryOp(ibuf_ptr_id, SpvOpAccessChain, ibuf_id,
322 builder.GetUintConstantId(kDebugInputDataOffset),
323 len_idx_inst->result_id());
324 Instruction* len_load_inst =
325 builder.AddUnaryOp(ibuf_type_id, SpvOpLoad, len_ac_inst->result_id());
326 // Test if reference end within candidate buffer length
327 Instruction* len_test_inst = builder.AddBinaryOp(
328 GetBoolId(), SpvOpULessThanEqual, ref_end_inst->result_id(),
329 len_load_inst->result_id());
330 // Return test result
331 (void)builder.AddInstruction(MakeUnique<Instruction>(
332 context(), SpvOpReturnValue, 0, 0,
333 std::initializer_list<Operand>{
334 {SPV_OPERAND_TYPE_ID, {len_test_inst->result_id()}}}));
335 // Close block
336 bound_test_blk_ptr->SetParent(&*input_func);
337 input_func->AddBasicBlock(std::move(bound_test_blk_ptr));
338 // Close function and add function to module
339 std::unique_ptr<Instruction> func_end_inst(
340 new Instruction(get_module()->context(), SpvOpFunctionEnd, 0, 0, {}));
341 get_def_use_mgr()->AnalyzeInstDefUse(&*func_end_inst);
342 input_func->SetFunctionEnd(std::move(func_end_inst));
343 context()->AddFunction(std::move(input_func));
344 }
345 return search_test_func_id_;
346}
347
348uint32_t InstBuffAddrCheckPass::GenSearchAndTest(Instruction* ref_inst,
349 InstructionBuilder* builder,
350 uint32_t* ref_uptr_id) {
351 // Enable Int64 if necessary
352 if (!get_feature_mgr()->HasCapability(SpvCapabilityInt64)) {
353 std::unique_ptr<Instruction> cap_int64_inst(new Instruction(
354 context(), SpvOpCapability, 0, 0,
355 std::initializer_list<Operand>{
356 {SPV_OPERAND_TYPE_CAPABILITY, {SpvCapabilityInt64}}}));
357 get_def_use_mgr()->AnalyzeInstDefUse(&*cap_int64_inst);
358 context()->AddCapability(std::move(cap_int64_inst));
359 }
360 // Convert reference pointer to uint64
361 uint32_t ref_ptr_id = ref_inst->GetSingleWordInOperand(0);
362 Instruction* ref_uptr_inst =
363 builder->AddUnaryOp(GetUint64Id(), SpvOpConvertPtrToU, ref_ptr_id);
364 *ref_uptr_id = ref_uptr_inst->result_id();
365 // Compute reference length in bytes
366 analysis::DefUseManager* du_mgr = get_def_use_mgr();
367 Instruction* ref_ptr_inst = du_mgr->GetDef(ref_ptr_id);
368 uint32_t ref_ptr_ty_id = ref_ptr_inst->type_id();
369 Instruction* ref_ptr_ty_inst = du_mgr->GetDef(ref_ptr_ty_id);
370 uint32_t ref_len = GetTypeLength(ref_ptr_ty_inst->GetSingleWordInOperand(1));
371 uint32_t ref_len_id = builder->GetUintConstantId(ref_len);
372 // Gen call to search and test function
373 const std::vector<uint32_t> args = {GetSearchAndTestFuncId(), *ref_uptr_id,
374 ref_len_id};
375 Instruction* call_inst =
376 builder->AddNaryOp(GetBoolId(), SpvOpFunctionCall, args);
377 uint32_t retval = call_inst->result_id();
378 return retval;
379}
380
381void InstBuffAddrCheckPass::GenBuffAddrCheckCode(
382 BasicBlock::iterator ref_inst_itr,
383 UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t stage_idx,
384 std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
385 // Look for reference through indexed descriptor. If found, analyze and
386 // save components. If not, return.
387 Instruction* ref_inst = &*ref_inst_itr;
388 if (!IsPhysicalBuffAddrReference(ref_inst)) return;
389 // Move original block's preceding instructions into first new block
390 std::unique_ptr<BasicBlock> new_blk_ptr;
391 MovePreludeCode(ref_inst_itr, ref_block_itr, &new_blk_ptr);
392 InstructionBuilder builder(
393 context(), &*new_blk_ptr,
394 IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
395 new_blocks->push_back(std::move(new_blk_ptr));
396 uint32_t error_id = builder.GetUintConstantId(kInstErrorBuffAddrUnallocRef);
397 // Generate code to do search and test if all bytes of reference
398 // are within a listed buffer. Return reference pointer converted to uint64.
399 uint32_t ref_uptr_id;
400 uint32_t valid_id = GenSearchAndTest(ref_inst, &builder, &ref_uptr_id);
401 // Generate test of search results with true branch
402 // being full reference and false branch being debug output and zero
403 // for the referenced value.
404 GenCheckCode(valid_id, error_id, ref_uptr_id, stage_idx, ref_inst,
405 new_blocks);
406 // Move original block's remaining code into remainder/merge block and add
407 // to new blocks
408 BasicBlock* back_blk_ptr = &*new_blocks->back();
409 MovePostludeCode(ref_block_itr, back_blk_ptr);
410}
411
412void InstBuffAddrCheckPass::InitInstBuffAddrCheck() {
413 // Initialize base class
414 InitializeInstrument();
415 // Initialize class
416 search_test_func_id_ = 0;
417}
418
419Pass::Status InstBuffAddrCheckPass::ProcessImpl() {
420 // Perform bindless bounds check on each entry point function in module
421 InstProcessFunction pfn =
422 [this](BasicBlock::iterator ref_inst_itr,
423 UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t stage_idx,
424 std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
425 return GenBuffAddrCheckCode(ref_inst_itr, ref_block_itr, stage_idx,
426 new_blocks);
427 };
428 bool modified = InstProcessEntryPointCallTree(pfn);
429 return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
430}
431
432Pass::Status InstBuffAddrCheckPass::Process() {
433 if (!get_feature_mgr()->HasCapability(
434 SpvCapabilityPhysicalStorageBufferAddressesEXT))
435 return Status::SuccessWithoutChange;
436 InitInstBuffAddrCheck();
437 return ProcessImpl();
438}
439
440} // namespace opt
441} // namespace spvtools
442