1 // Copyright (c) 2020 The Khronos Group Inc.
2 // Copyright (c) 2020 Valve Corporation
3 // Copyright (c) 2020 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_debug_printf_pass.h"
18
19 #include "source/spirv_constant.h"
20 #include "source/util/string_utils.h"
21 #include "spirv/unified1/NonSemanticDebugPrintf.h"
22
23 namespace spvtools {
24 namespace opt {
25
GenOutputValues(Instruction * val_inst,std::vector<uint32_t> * val_ids,InstructionBuilder * builder)26 void InstDebugPrintfPass::GenOutputValues(Instruction* val_inst,
27 std::vector<uint32_t>* val_ids,
28 InstructionBuilder* builder) {
29 uint32_t val_ty_id = val_inst->type_id();
30 analysis::TypeManager* type_mgr = context()->get_type_mgr();
31 analysis::Type* val_ty = type_mgr->GetType(val_ty_id);
32 switch (val_ty->kind()) {
33 case analysis::Type::kVector: {
34 analysis::Vector* v_ty = val_ty->AsVector();
35 const analysis::Type* c_ty = v_ty->element_type();
36 uint32_t c_ty_id = type_mgr->GetId(c_ty);
37 for (uint32_t c = 0; c < v_ty->element_count(); ++c) {
38 Instruction* c_inst =
39 builder->AddCompositeExtract(c_ty_id, val_inst->result_id(), {c});
40 GenOutputValues(c_inst, val_ids, builder);
41 }
42 return;
43 }
44 case analysis::Type::kBool: {
45 // Select between uint32 zero or one
46 uint32_t zero_id = builder->GetUintConstantId(0);
47 uint32_t one_id = builder->GetUintConstantId(1);
48 Instruction* sel_inst = builder->AddSelect(
49 GetUintId(), val_inst->result_id(), one_id, zero_id);
50 val_ids->push_back(sel_inst->result_id());
51 return;
52 }
53 case analysis::Type::kFloat: {
54 analysis::Float* f_ty = val_ty->AsFloat();
55 switch (f_ty->width()) {
56 case 16: {
57 // Convert float16 to float32 and recurse
58 Instruction* f32_inst = builder->AddUnaryOp(
59 GetFloatId(), spv::Op::OpFConvert, val_inst->result_id());
60 GenOutputValues(f32_inst, val_ids, builder);
61 return;
62 }
63 case 64: {
64 // Bitcast float64 to uint64 and recurse
65 Instruction* ui64_inst = builder->AddUnaryOp(
66 GetUint64Id(), spv::Op::OpBitcast, val_inst->result_id());
67 GenOutputValues(ui64_inst, val_ids, builder);
68 return;
69 }
70 case 32: {
71 // Bitcase float32 to uint32
72 Instruction* bc_inst = builder->AddUnaryOp(
73 GetUintId(), spv::Op::OpBitcast, val_inst->result_id());
74 val_ids->push_back(bc_inst->result_id());
75 return;
76 }
77 default:
78 assert(false && "unsupported float width");
79 return;
80 }
81 }
82 case analysis::Type::kInteger: {
83 analysis::Integer* i_ty = val_ty->AsInteger();
84 switch (i_ty->width()) {
85 case 64: {
86 Instruction* ui64_inst = val_inst;
87 if (i_ty->IsSigned()) {
88 // Bitcast sint64 to uint64
89 ui64_inst = builder->AddUnaryOp(GetUint64Id(), spv::Op::OpBitcast,
90 val_inst->result_id());
91 }
92 // Break uint64 into 2x uint32
93 Instruction* lo_ui64_inst = builder->AddUnaryOp(
94 GetUintId(), spv::Op::OpUConvert, ui64_inst->result_id());
95 Instruction* rshift_ui64_inst = builder->AddBinaryOp(
96 GetUint64Id(), spv::Op::OpShiftRightLogical,
97 ui64_inst->result_id(), builder->GetUintConstantId(32));
98 Instruction* hi_ui64_inst = builder->AddUnaryOp(
99 GetUintId(), spv::Op::OpUConvert, rshift_ui64_inst->result_id());
100 val_ids->push_back(lo_ui64_inst->result_id());
101 val_ids->push_back(hi_ui64_inst->result_id());
102 return;
103 }
104 case 8: {
105 Instruction* ui8_inst = val_inst;
106 if (i_ty->IsSigned()) {
107 // Bitcast sint8 to uint8
108 ui8_inst = builder->AddUnaryOp(GetUint8Id(), spv::Op::OpBitcast,
109 val_inst->result_id());
110 }
111 // Convert uint8 to uint32
112 Instruction* ui32_inst = builder->AddUnaryOp(
113 GetUintId(), spv::Op::OpUConvert, ui8_inst->result_id());
114 val_ids->push_back(ui32_inst->result_id());
115 return;
116 }
117 case 32: {
118 Instruction* ui32_inst = val_inst;
119 if (i_ty->IsSigned()) {
120 // Bitcast sint32 to uint32
121 ui32_inst = builder->AddUnaryOp(GetUintId(), spv::Op::OpBitcast,
122 val_inst->result_id());
123 }
124 // uint32 needs no further processing
125 val_ids->push_back(ui32_inst->result_id());
126 return;
127 }
128 default:
129 // TODO(greg-lunarg): Support non-32-bit int
130 assert(false && "unsupported int width");
131 return;
132 }
133 }
134 default:
135 assert(false && "unsupported type");
136 return;
137 }
138 }
139
GenOutputCode(Instruction * printf_inst,std::vector<std::unique_ptr<BasicBlock>> * new_blocks)140 void InstDebugPrintfPass::GenOutputCode(
141 Instruction* printf_inst,
142 std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
143 BasicBlock* back_blk_ptr = &*new_blocks->back();
144 InstructionBuilder builder(
145 context(), back_blk_ptr,
146 IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
147 // Gen debug printf record validation-specific values. The format string
148 // will have its id written. Vectors will need to be broken down into
149 // component values. float16 will need to be converted to float32. Pointer
150 // and uint64 will need to be converted to two uint32 values. float32 will
151 // need to be bitcast to uint32. int32 will need to be bitcast to uint32.
152 std::vector<uint32_t> val_ids;
153 bool is_first_operand = false;
154 printf_inst->ForEachInId(
155 [&is_first_operand, &val_ids, &builder, this](const uint32_t* iid) {
156 // skip set operand
157 if (!is_first_operand) {
158 is_first_operand = true;
159 return;
160 }
161 Instruction* opnd_inst = get_def_use_mgr()->GetDef(*iid);
162 if (opnd_inst->opcode() == spv::Op::OpString) {
163 uint32_t string_id_id = builder.GetUintConstantId(*iid);
164 val_ids.push_back(string_id_id);
165 } else {
166 GenOutputValues(opnd_inst, &val_ids, &builder);
167 }
168 });
169 GenDebugStreamWrite(
170 builder.GetUintConstantId(shader_id_),
171 builder.GetUintConstantId(uid2offset_[printf_inst->unique_id()]), val_ids,
172 &builder);
173 context()->KillInst(printf_inst);
174 }
175
GenDebugPrintfCode(BasicBlock::iterator ref_inst_itr,UptrVectorIterator<BasicBlock> ref_block_itr,std::vector<std::unique_ptr<BasicBlock>> * new_blocks)176 void InstDebugPrintfPass::GenDebugPrintfCode(
177 BasicBlock::iterator ref_inst_itr,
178 UptrVectorIterator<BasicBlock> ref_block_itr,
179 std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
180 // If not DebugPrintf OpExtInst, return.
181 Instruction* printf_inst = &*ref_inst_itr;
182 if (printf_inst->opcode() != spv::Op::OpExtInst) return;
183 if (printf_inst->GetSingleWordInOperand(0) != ext_inst_printf_id_) return;
184 if (printf_inst->GetSingleWordInOperand(1) !=
185 NonSemanticDebugPrintfDebugPrintf)
186 return;
187 // Initialize DefUse manager before dismantling module
188 (void)get_def_use_mgr();
189 // Move original block's preceding instructions into first new block
190 std::unique_ptr<BasicBlock> new_blk_ptr;
191 MovePreludeCode(ref_inst_itr, ref_block_itr, &new_blk_ptr);
192 new_blocks->push_back(std::move(new_blk_ptr));
193 // Generate instructions to output printf args to printf buffer
194 GenOutputCode(printf_inst, new_blocks);
195 // Caller expects at least two blocks with last block containing remaining
196 // code, so end block after instrumentation, create remainder block, and
197 // branch to it
198 uint32_t rem_blk_id = TakeNextId();
199 std::unique_ptr<Instruction> rem_label(NewLabel(rem_blk_id));
200 BasicBlock* back_blk_ptr = &*new_blocks->back();
201 InstructionBuilder builder(
202 context(), back_blk_ptr,
203 IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
204 (void)builder.AddBranch(rem_blk_id);
205 // Gen remainder block
206 new_blk_ptr.reset(new BasicBlock(std::move(rem_label)));
207 builder.SetInsertPoint(&*new_blk_ptr);
208 // Move original block's remaining code into remainder block and add
209 // to new blocks
210 MovePostludeCode(ref_block_itr, &*new_blk_ptr);
211 new_blocks->push_back(std::move(new_blk_ptr));
212 }
213
214 // Return id for output buffer
GetOutputBufferId()215 uint32_t InstDebugPrintfPass::GetOutputBufferId() {
216 if (output_buffer_id_ == 0) {
217 // If not created yet, create one
218 analysis::DecorationManager* deco_mgr = get_decoration_mgr();
219 analysis::TypeManager* type_mgr = context()->get_type_mgr();
220 analysis::RuntimeArray* reg_uint_rarr_ty = GetUintRuntimeArrayType(32);
221 analysis::Integer* reg_uint_ty = GetInteger(32, false);
222 analysis::Type* reg_buf_ty =
223 GetStruct({reg_uint_ty, reg_uint_ty, reg_uint_rarr_ty});
224 uint32_t obufTyId = type_mgr->GetTypeInstruction(reg_buf_ty);
225 // By the Vulkan spec, a pre-existing struct containing a RuntimeArray
226 // must be a block, and will therefore be decorated with Block. Therefore
227 // the undecorated type returned here will not be pre-existing and can
228 // safely be decorated. Since this type is now decorated, it is out of
229 // sync with the TypeManager and therefore the TypeManager must be
230 // invalidated after this pass.
231 assert(context()->get_def_use_mgr()->NumUses(obufTyId) == 0 &&
232 "used struct type returned");
233 deco_mgr->AddDecoration(obufTyId, uint32_t(spv::Decoration::Block));
234 deco_mgr->AddMemberDecoration(obufTyId, kDebugOutputFlagsOffset,
235 uint32_t(spv::Decoration::Offset), 0);
236 deco_mgr->AddMemberDecoration(obufTyId, kDebugOutputSizeOffset,
237 uint32_t(spv::Decoration::Offset), 4);
238 deco_mgr->AddMemberDecoration(obufTyId, kDebugOutputDataOffset,
239 uint32_t(spv::Decoration::Offset), 8);
240 uint32_t obufTyPtrId_ =
241 type_mgr->FindPointerToType(obufTyId, spv::StorageClass::StorageBuffer);
242 output_buffer_id_ = TakeNextId();
243 std::unique_ptr<Instruction> newVarOp(new Instruction(
244 context(), spv::Op::OpVariable, obufTyPtrId_, output_buffer_id_,
245 {{spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER,
246 {uint32_t(spv::StorageClass::StorageBuffer)}}}));
247 context()->AddGlobalValue(std::move(newVarOp));
248 context()->AddDebug2Inst(NewGlobalName(obufTyId, "OutputBuffer"));
249 context()->AddDebug2Inst(NewMemberName(obufTyId, 0, "flags"));
250 context()->AddDebug2Inst(NewMemberName(obufTyId, 1, "written_count"));
251 context()->AddDebug2Inst(NewMemberName(obufTyId, 2, "data"));
252 context()->AddDebug2Inst(NewGlobalName(output_buffer_id_, "output_buffer"));
253 deco_mgr->AddDecorationVal(
254 output_buffer_id_, uint32_t(spv::Decoration::DescriptorSet), desc_set_);
255 deco_mgr->AddDecorationVal(output_buffer_id_,
256 uint32_t(spv::Decoration::Binding),
257 GetOutputBufferBinding());
258 AddStorageBufferExt();
259 if (get_module()->version() >= SPV_SPIRV_VERSION_WORD(1, 4)) {
260 // Add the new buffer to all entry points.
261 for (auto& entry : get_module()->entry_points()) {
262 entry.AddOperand({SPV_OPERAND_TYPE_ID, {output_buffer_id_}});
263 context()->AnalyzeUses(&entry);
264 }
265 }
266 }
267 return output_buffer_id_;
268 }
269
GetOutputBufferPtrId()270 uint32_t InstDebugPrintfPass::GetOutputBufferPtrId() {
271 if (output_buffer_ptr_id_ == 0) {
272 output_buffer_ptr_id_ = context()->get_type_mgr()->FindPointerToType(
273 GetUintId(), spv::StorageClass::StorageBuffer);
274 }
275 return output_buffer_ptr_id_;
276 }
277
GetOutputBufferBinding()278 uint32_t InstDebugPrintfPass::GetOutputBufferBinding() {
279 return kDebugOutputPrintfStream;
280 }
281
GenDebugOutputFieldCode(uint32_t base_offset_id,uint32_t field_offset,uint32_t field_value_id,InstructionBuilder * builder)282 void InstDebugPrintfPass::GenDebugOutputFieldCode(uint32_t base_offset_id,
283 uint32_t field_offset,
284 uint32_t field_value_id,
285 InstructionBuilder* builder) {
286 // Cast value to 32-bit unsigned if necessary
287 uint32_t val_id = GenUintCastCode(field_value_id, builder);
288 // Store value
289 Instruction* data_idx_inst = builder->AddIAdd(
290 GetUintId(), base_offset_id, builder->GetUintConstantId(field_offset));
291 uint32_t buf_id = GetOutputBufferId();
292 uint32_t buf_uint_ptr_id = GetOutputBufferPtrId();
293 Instruction* achain_inst = builder->AddAccessChain(
294 buf_uint_ptr_id, buf_id,
295 {builder->GetUintConstantId(kDebugOutputDataOffset),
296 data_idx_inst->result_id()});
297 (void)builder->AddStore(achain_inst->result_id(), val_id);
298 }
299
GetStreamWriteFunctionId(uint32_t param_cnt)300 uint32_t InstDebugPrintfPass::GetStreamWriteFunctionId(uint32_t param_cnt) {
301 enum {
302 kShaderId = 0,
303 kInstructionIndex = 1,
304 kFirstParam = 2,
305 };
306 // Total param count is common params plus validation-specific
307 // params
308 if (param2output_func_id_[param_cnt] == 0) {
309 // Create function
310 param2output_func_id_[param_cnt] = TakeNextId();
311 analysis::TypeManager* type_mgr = context()->get_type_mgr();
312
313 const analysis::Type* uint_type = GetInteger(32, false);
314
315 std::vector<const analysis::Type*> param_types(kFirstParam + param_cnt,
316 uint_type);
317 std::unique_ptr<Function> output_func = StartFunction(
318 param2output_func_id_[param_cnt], type_mgr->GetVoidType(), param_types);
319
320 std::vector<uint32_t> param_ids = AddParameters(*output_func, param_types);
321
322 // Create first block
323 auto new_blk_ptr = MakeUnique<BasicBlock>(NewLabel(TakeNextId()));
324
325 InstructionBuilder builder(
326 context(), &*new_blk_ptr,
327 IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
328 // Gen test if debug output buffer size will not be exceeded.
329 const uint32_t first_param_offset = kInstCommonOutInstructionIdx + 1;
330 const uint32_t obuf_record_sz = first_param_offset + param_cnt;
331 const uint32_t buf_id = GetOutputBufferId();
332 const uint32_t buf_uint_ptr_id = GetOutputBufferPtrId();
333 Instruction* obuf_curr_sz_ac_inst = builder.AddAccessChain(
334 buf_uint_ptr_id, buf_id,
335 {builder.GetUintConstantId(kDebugOutputSizeOffset)});
336 // Fetch the current debug buffer written size atomically, adding the
337 // size of the record to be written.
338 uint32_t obuf_record_sz_id = builder.GetUintConstantId(obuf_record_sz);
339 uint32_t mask_none_id =
340 builder.GetUintConstantId(uint32_t(spv::MemoryAccessMask::MaskNone));
341 uint32_t scope_invok_id =
342 builder.GetUintConstantId(uint32_t(spv::Scope::Invocation));
343 Instruction* obuf_curr_sz_inst = builder.AddQuadOp(
344 GetUintId(), spv::Op::OpAtomicIAdd, obuf_curr_sz_ac_inst->result_id(),
345 scope_invok_id, mask_none_id, obuf_record_sz_id);
346 uint32_t obuf_curr_sz_id = obuf_curr_sz_inst->result_id();
347 // Compute new written size
348 Instruction* obuf_new_sz_inst =
349 builder.AddIAdd(GetUintId(), obuf_curr_sz_id,
350 builder.GetUintConstantId(obuf_record_sz));
351 // Fetch the data bound
352 Instruction* obuf_bnd_inst =
353 builder.AddIdLiteralOp(GetUintId(), spv::Op::OpArrayLength,
354 GetOutputBufferId(), kDebugOutputDataOffset);
355 // Test that new written size is less than or equal to debug output
356 // data bound
357 Instruction* obuf_safe_inst = builder.AddBinaryOp(
358 GetBoolId(), spv::Op::OpULessThanEqual, obuf_new_sz_inst->result_id(),
359 obuf_bnd_inst->result_id());
360 uint32_t merge_blk_id = TakeNextId();
361 uint32_t write_blk_id = TakeNextId();
362 std::unique_ptr<Instruction> merge_label(NewLabel(merge_blk_id));
363 std::unique_ptr<Instruction> write_label(NewLabel(write_blk_id));
364 (void)builder.AddConditionalBranch(
365 obuf_safe_inst->result_id(), write_blk_id, merge_blk_id, merge_blk_id,
366 uint32_t(spv::SelectionControlMask::MaskNone));
367 // Close safety test block and gen write block
368 output_func->AddBasicBlock(std::move(new_blk_ptr));
369 new_blk_ptr = MakeUnique<BasicBlock>(std::move(write_label));
370 builder.SetInsertPoint(&*new_blk_ptr);
371 // Generate common and stage-specific debug record members
372 GenDebugOutputFieldCode(obuf_curr_sz_id, kInstCommonOutSize,
373 builder.GetUintConstantId(obuf_record_sz),
374 &builder);
375 // Store Shader Id
376 GenDebugOutputFieldCode(obuf_curr_sz_id, kInstCommonOutShaderId,
377 param_ids[kShaderId], &builder);
378 // Store Instruction Idx
379 GenDebugOutputFieldCode(obuf_curr_sz_id, kInstCommonOutInstructionIdx,
380 param_ids[kInstructionIndex], &builder);
381 // Gen writes of validation specific data
382 for (uint32_t i = 0; i < param_cnt; ++i) {
383 GenDebugOutputFieldCode(obuf_curr_sz_id, first_param_offset + i,
384 param_ids[kFirstParam + i], &builder);
385 }
386 // Close write block and gen merge block
387 (void)builder.AddBranch(merge_blk_id);
388 output_func->AddBasicBlock(std::move(new_blk_ptr));
389 new_blk_ptr = MakeUnique<BasicBlock>(std::move(merge_label));
390 builder.SetInsertPoint(&*new_blk_ptr);
391 // Close merge block and function and add function to module
392 (void)builder.AddNullaryOp(0, spv::Op::OpReturn);
393
394 output_func->AddBasicBlock(std::move(new_blk_ptr));
395 output_func->SetFunctionEnd(EndFunction());
396 context()->AddFunction(std::move(output_func));
397
398 std::string name("stream_write_");
399 name += std::to_string(param_cnt);
400
401 context()->AddDebug2Inst(
402 NewGlobalName(param2output_func_id_[param_cnt], name));
403 }
404 return param2output_func_id_[param_cnt];
405 }
406
GenDebugStreamWrite(uint32_t shader_id,uint32_t instruction_idx_id,const std::vector<uint32_t> & validation_ids,InstructionBuilder * builder)407 void InstDebugPrintfPass::GenDebugStreamWrite(
408 uint32_t shader_id, uint32_t instruction_idx_id,
409 const std::vector<uint32_t>& validation_ids, InstructionBuilder* builder) {
410 // Call debug output function. Pass func_idx, instruction_idx and
411 // validation ids as args.
412 uint32_t val_id_cnt = static_cast<uint32_t>(validation_ids.size());
413 std::vector<uint32_t> args = {shader_id, instruction_idx_id};
414 (void)args.insert(args.end(), validation_ids.begin(), validation_ids.end());
415 (void)builder->AddFunctionCall(GetVoidId(),
416 GetStreamWriteFunctionId(val_id_cnt), args);
417 }
418
NewGlobalName(uint32_t id,const std::string & name_str)419 std::unique_ptr<Instruction> InstDebugPrintfPass::NewGlobalName(
420 uint32_t id, const std::string& name_str) {
421 std::string prefixed_name{"inst_printf_"};
422 prefixed_name += name_str;
423 return NewName(id, prefixed_name);
424 }
425
NewMemberName(uint32_t id,uint32_t member_index,const std::string & name_str)426 std::unique_ptr<Instruction> InstDebugPrintfPass::NewMemberName(
427 uint32_t id, uint32_t member_index, const std::string& name_str) {
428 return MakeUnique<Instruction>(
429 context(), spv::Op::OpMemberName, 0, 0,
430 std::initializer_list<Operand>{
431 {SPV_OPERAND_TYPE_ID, {id}},
432 {SPV_OPERAND_TYPE_LITERAL_INTEGER, {member_index}},
433 {SPV_OPERAND_TYPE_LITERAL_STRING, utils::MakeVector(name_str)}});
434 }
435
InitializeInstDebugPrintf()436 void InstDebugPrintfPass::InitializeInstDebugPrintf() {
437 // Initialize base class
438 InitializeInstrument();
439 output_buffer_id_ = 0;
440 output_buffer_ptr_id_ = 0;
441 }
442
ProcessImpl()443 Pass::Status InstDebugPrintfPass::ProcessImpl() {
444 // Perform printf instrumentation on each entry point function in module
445 InstProcessFunction pfn =
446 [this](BasicBlock::iterator ref_inst_itr,
447 UptrVectorIterator<BasicBlock> ref_block_itr,
448 [[maybe_unused]] uint32_t stage_idx,
449 std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
450 return GenDebugPrintfCode(ref_inst_itr, ref_block_itr, new_blocks);
451 };
452 (void)InstProcessEntryPointCallTree(pfn);
453 // Remove DebugPrintf OpExtInstImport instruction
454 Instruction* ext_inst_import_inst =
455 get_def_use_mgr()->GetDef(ext_inst_printf_id_);
456 context()->KillInst(ext_inst_import_inst);
457 // If no remaining non-semantic instruction sets, remove non-semantic debug
458 // info extension from module and feature manager
459 bool non_sem_set_seen = false;
460 for (auto c_itr = context()->module()->ext_inst_import_begin();
461 c_itr != context()->module()->ext_inst_import_end(); ++c_itr) {
462 const std::string set_name = c_itr->GetInOperand(0).AsString();
463 if (spvtools::utils::starts_with(set_name, "NonSemantic.")) {
464 non_sem_set_seen = true;
465 break;
466 }
467 }
468 if (!non_sem_set_seen) {
469 context()->RemoveExtension(kSPV_KHR_non_semantic_info);
470 }
471 return Status::SuccessWithChange;
472 }
473
Process()474 Pass::Status InstDebugPrintfPass::Process() {
475 ext_inst_printf_id_ =
476 get_module()->GetExtInstImportId("NonSemantic.DebugPrintf");
477 if (ext_inst_printf_id_ == 0) return Status::SuccessWithoutChange;
478 InitializeInstDebugPrintf();
479 return ProcessImpl();
480 }
481
482 } // namespace opt
483 } // namespace spvtools
484