summaryrefslogtreecommitdiff
path: root/src/shader_recompiler/backend/spirv/emit_spirv.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/shader_recompiler/backend/spirv/emit_spirv.cpp')
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv.cpp541
1 files changed, 541 insertions, 0 deletions
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv.cpp b/src/shader_recompiler/backend/spirv/emit_spirv.cpp
new file mode 100644
index 000000000..d7a86e270
--- /dev/null
+++ b/src/shader_recompiler/backend/spirv/emit_spirv.cpp
@@ -0,0 +1,541 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <span>
6#include <tuple>
7#include <type_traits>
8#include <utility>
9#include <vector>
10
11#include "common/settings.h"
12#include "shader_recompiler/backend/spirv/emit_spirv.h"
13#include "shader_recompiler/backend/spirv/emit_spirv_instructions.h"
14#include "shader_recompiler/frontend/ir/basic_block.h"
15#include "shader_recompiler/frontend/ir/program.h"
16
17namespace Shader::Backend::SPIRV {
18namespace {
19template <class Func>
20struct FuncTraits {};
21
22template <class ReturnType_, class... Args>
23struct FuncTraits<ReturnType_ (*)(Args...)> {
24 using ReturnType = ReturnType_;
25
26 static constexpr size_t NUM_ARGS = sizeof...(Args);
27
28 template <size_t I>
29 using ArgType = std::tuple_element_t<I, std::tuple<Args...>>;
30};
31
32template <auto func, typename... Args>
33void SetDefinition(EmitContext& ctx, IR::Inst* inst, Args... args) {
34 inst->SetDefinition<Id>(func(ctx, std::forward<Args>(args)...));
35}
36
37template <typename ArgType>
38ArgType Arg(EmitContext& ctx, const IR::Value& arg) {
39 if constexpr (std::is_same_v<ArgType, Id>) {
40 return ctx.Def(arg);
41 } else if constexpr (std::is_same_v<ArgType, const IR::Value&>) {
42 return arg;
43 } else if constexpr (std::is_same_v<ArgType, u32>) {
44 return arg.U32();
45 } else if constexpr (std::is_same_v<ArgType, IR::Attribute>) {
46 return arg.Attribute();
47 } else if constexpr (std::is_same_v<ArgType, IR::Patch>) {
48 return arg.Patch();
49 } else if constexpr (std::is_same_v<ArgType, IR::Reg>) {
50 return arg.Reg();
51 }
52}
53
54template <auto func, bool is_first_arg_inst, size_t... I>
55void Invoke(EmitContext& ctx, IR::Inst* inst, std::index_sequence<I...>) {
56 using Traits = FuncTraits<decltype(func)>;
57 if constexpr (std::is_same_v<typename Traits::ReturnType, Id>) {
58 if constexpr (is_first_arg_inst) {
59 SetDefinition<func>(
60 ctx, inst, inst,
61 Arg<typename Traits::template ArgType<I + 2>>(ctx, inst->Arg(I))...);
62 } else {
63 SetDefinition<func>(
64 ctx, inst, Arg<typename Traits::template ArgType<I + 1>>(ctx, inst->Arg(I))...);
65 }
66 } else {
67 if constexpr (is_first_arg_inst) {
68 func(ctx, inst, Arg<typename Traits::template ArgType<I + 2>>(ctx, inst->Arg(I))...);
69 } else {
70 func(ctx, Arg<typename Traits::template ArgType<I + 1>>(ctx, inst->Arg(I))...);
71 }
72 }
73}
74
75template <auto func>
76void Invoke(EmitContext& ctx, IR::Inst* inst) {
77 using Traits = FuncTraits<decltype(func)>;
78 static_assert(Traits::NUM_ARGS >= 1, "Insufficient arguments");
79 if constexpr (Traits::NUM_ARGS == 1) {
80 Invoke<func, false>(ctx, inst, std::make_index_sequence<0>{});
81 } else {
82 using FirstArgType = typename Traits::template ArgType<1>;
83 static constexpr bool is_first_arg_inst = std::is_same_v<FirstArgType, IR::Inst*>;
84 using Indices = std::make_index_sequence<Traits::NUM_ARGS - (is_first_arg_inst ? 2 : 1)>;
85 Invoke<func, is_first_arg_inst>(ctx, inst, Indices{});
86 }
87}
88
89void EmitInst(EmitContext& ctx, IR::Inst* inst) {
90 switch (inst->GetOpcode()) {
91#define OPCODE(name, result_type, ...) \
92 case IR::Opcode::name: \
93 return Invoke<&Emit##name>(ctx, inst);
94#include "shader_recompiler/frontend/ir/opcodes.inc"
95#undef OPCODE
96 }
97 throw LogicError("Invalid opcode {}", inst->GetOpcode());
98}
99
100Id TypeId(const EmitContext& ctx, IR::Type type) {
101 switch (type) {
102 case IR::Type::U1:
103 return ctx.U1;
104 case IR::Type::U32:
105 return ctx.U32[1];
106 default:
107 throw NotImplementedException("Phi node type {}", type);
108 }
109}
110
111void Traverse(EmitContext& ctx, IR::Program& program) {
112 IR::Block* current_block{};
113 for (const IR::AbstractSyntaxNode& node : program.syntax_list) {
114 switch (node.type) {
115 case IR::AbstractSyntaxNode::Type::Block: {
116 const Id label{node.data.block->Definition<Id>()};
117 if (current_block) {
118 ctx.OpBranch(label);
119 }
120 current_block = node.data.block;
121 ctx.AddLabel(label);
122 for (IR::Inst& inst : node.data.block->Instructions()) {
123 EmitInst(ctx, &inst);
124 }
125 break;
126 }
127 case IR::AbstractSyntaxNode::Type::If: {
128 const Id if_label{node.data.if_node.body->Definition<Id>()};
129 const Id endif_label{node.data.if_node.merge->Definition<Id>()};
130 ctx.OpSelectionMerge(endif_label, spv::SelectionControlMask::MaskNone);
131 ctx.OpBranchConditional(ctx.Def(node.data.if_node.cond), if_label, endif_label);
132 break;
133 }
134 case IR::AbstractSyntaxNode::Type::Loop: {
135 const Id body_label{node.data.loop.body->Definition<Id>()};
136 const Id continue_label{node.data.loop.continue_block->Definition<Id>()};
137 const Id endloop_label{node.data.loop.merge->Definition<Id>()};
138
139 ctx.OpLoopMerge(endloop_label, continue_label, spv::LoopControlMask::MaskNone);
140 ctx.OpBranch(body_label);
141 break;
142 }
143 case IR::AbstractSyntaxNode::Type::Break: {
144 const Id break_label{node.data.break_node.merge->Definition<Id>()};
145 const Id skip_label{node.data.break_node.skip->Definition<Id>()};
146 ctx.OpBranchConditional(ctx.Def(node.data.break_node.cond), break_label, skip_label);
147 break;
148 }
149 case IR::AbstractSyntaxNode::Type::EndIf:
150 if (current_block) {
151 ctx.OpBranch(node.data.end_if.merge->Definition<Id>());
152 }
153 break;
154 case IR::AbstractSyntaxNode::Type::Repeat: {
155 Id cond{ctx.Def(node.data.repeat.cond)};
156 if (!Settings::values.disable_shader_loop_safety_checks) {
157 const Id pointer_type{ctx.TypePointer(spv::StorageClass::Private, ctx.U32[1])};
158 const Id safety_counter{ctx.AddGlobalVariable(
159 pointer_type, spv::StorageClass::Private, ctx.Const(0x2000u))};
160 if (ctx.profile.supported_spirv >= 0x00010400) {
161 ctx.interfaces.push_back(safety_counter);
162 }
163 const Id old_counter{ctx.OpLoad(ctx.U32[1], safety_counter)};
164 const Id new_counter{ctx.OpISub(ctx.U32[1], old_counter, ctx.Const(1u))};
165 ctx.OpStore(safety_counter, new_counter);
166
167 const Id safety_cond{
168 ctx.OpSGreaterThanEqual(ctx.U1, new_counter, ctx.u32_zero_value)};
169 cond = ctx.OpLogicalAnd(ctx.U1, cond, safety_cond);
170 }
171 const Id loop_header_label{node.data.repeat.loop_header->Definition<Id>()};
172 const Id merge_label{node.data.repeat.merge->Definition<Id>()};
173 ctx.OpBranchConditional(cond, loop_header_label, merge_label);
174 break;
175 }
176 case IR::AbstractSyntaxNode::Type::Return:
177 ctx.OpReturn();
178 break;
179 case IR::AbstractSyntaxNode::Type::Unreachable:
180 ctx.OpUnreachable();
181 break;
182 }
183 if (node.type != IR::AbstractSyntaxNode::Type::Block) {
184 current_block = nullptr;
185 }
186 }
187}
188
189Id DefineMain(EmitContext& ctx, IR::Program& program) {
190 const Id void_function{ctx.TypeFunction(ctx.void_id)};
191 const Id main{ctx.OpFunction(ctx.void_id, spv::FunctionControlMask::MaskNone, void_function)};
192 for (IR::Block* const block : program.blocks) {
193 block->SetDefinition(ctx.OpLabel());
194 }
195 Traverse(ctx, program);
196 ctx.OpFunctionEnd();
197 return main;
198}
199
200spv::ExecutionMode ExecutionMode(TessPrimitive primitive) {
201 switch (primitive) {
202 case TessPrimitive::Isolines:
203 return spv::ExecutionMode::Isolines;
204 case TessPrimitive::Triangles:
205 return spv::ExecutionMode::Triangles;
206 case TessPrimitive::Quads:
207 return spv::ExecutionMode::Quads;
208 }
209 throw InvalidArgument("Tessellation primitive {}", primitive);
210}
211
212spv::ExecutionMode ExecutionMode(TessSpacing spacing) {
213 switch (spacing) {
214 case TessSpacing::Equal:
215 return spv::ExecutionMode::SpacingEqual;
216 case TessSpacing::FractionalOdd:
217 return spv::ExecutionMode::SpacingFractionalOdd;
218 case TessSpacing::FractionalEven:
219 return spv::ExecutionMode::SpacingFractionalEven;
220 }
221 throw InvalidArgument("Tessellation spacing {}", spacing);
222}
223
224void DefineEntryPoint(const IR::Program& program, EmitContext& ctx, Id main) {
225 const std::span interfaces(ctx.interfaces.data(), ctx.interfaces.size());
226 spv::ExecutionModel execution_model{};
227 switch (program.stage) {
228 case Stage::Compute: {
229 const std::array<u32, 3> workgroup_size{program.workgroup_size};
230 execution_model = spv::ExecutionModel::GLCompute;
231 ctx.AddExecutionMode(main, spv::ExecutionMode::LocalSize, workgroup_size[0],
232 workgroup_size[1], workgroup_size[2]);
233 break;
234 }
235 case Stage::VertexB:
236 execution_model = spv::ExecutionModel::Vertex;
237 break;
238 case Stage::TessellationControl:
239 execution_model = spv::ExecutionModel::TessellationControl;
240 ctx.AddCapability(spv::Capability::Tessellation);
241 ctx.AddExecutionMode(main, spv::ExecutionMode::OutputVertices, program.invocations);
242 break;
243 case Stage::TessellationEval:
244 execution_model = spv::ExecutionModel::TessellationEvaluation;
245 ctx.AddCapability(spv::Capability::Tessellation);
246 ctx.AddExecutionMode(main, ExecutionMode(ctx.runtime_info.tess_primitive));
247 ctx.AddExecutionMode(main, ExecutionMode(ctx.runtime_info.tess_spacing));
248 ctx.AddExecutionMode(main, ctx.runtime_info.tess_clockwise
249 ? spv::ExecutionMode::VertexOrderCw
250 : spv::ExecutionMode::VertexOrderCcw);
251 break;
252 case Stage::Geometry:
253 execution_model = spv::ExecutionModel::Geometry;
254 ctx.AddCapability(spv::Capability::Geometry);
255 ctx.AddCapability(spv::Capability::GeometryStreams);
256 switch (ctx.runtime_info.input_topology) {
257 case InputTopology::Points:
258 ctx.AddExecutionMode(main, spv::ExecutionMode::InputPoints);
259 break;
260 case InputTopology::Lines:
261 ctx.AddExecutionMode(main, spv::ExecutionMode::InputLines);
262 break;
263 case InputTopology::LinesAdjacency:
264 ctx.AddExecutionMode(main, spv::ExecutionMode::InputLinesAdjacency);
265 break;
266 case InputTopology::Triangles:
267 ctx.AddExecutionMode(main, spv::ExecutionMode::Triangles);
268 break;
269 case InputTopology::TrianglesAdjacency:
270 ctx.AddExecutionMode(main, spv::ExecutionMode::InputTrianglesAdjacency);
271 break;
272 }
273 switch (program.output_topology) {
274 case OutputTopology::PointList:
275 ctx.AddExecutionMode(main, spv::ExecutionMode::OutputPoints);
276 break;
277 case OutputTopology::LineStrip:
278 ctx.AddExecutionMode(main, spv::ExecutionMode::OutputLineStrip);
279 break;
280 case OutputTopology::TriangleStrip:
281 ctx.AddExecutionMode(main, spv::ExecutionMode::OutputTriangleStrip);
282 break;
283 }
284 if (program.info.stores[IR::Attribute::PointSize]) {
285 ctx.AddCapability(spv::Capability::GeometryPointSize);
286 }
287 ctx.AddExecutionMode(main, spv::ExecutionMode::OutputVertices, program.output_vertices);
288 ctx.AddExecutionMode(main, spv::ExecutionMode::Invocations, program.invocations);
289 if (program.is_geometry_passthrough) {
290 if (ctx.profile.support_geometry_shader_passthrough) {
291 ctx.AddExtension("SPV_NV_geometry_shader_passthrough");
292 ctx.AddCapability(spv::Capability::GeometryShaderPassthroughNV);
293 } else {
294 LOG_WARNING(Shader_SPIRV, "Geometry shader passthrough used with no support");
295 }
296 }
297 break;
298 case Stage::Fragment:
299 execution_model = spv::ExecutionModel::Fragment;
300 if (ctx.profile.lower_left_origin_mode) {
301 ctx.AddExecutionMode(main, spv::ExecutionMode::OriginLowerLeft);
302 } else {
303 ctx.AddExecutionMode(main, spv::ExecutionMode::OriginUpperLeft);
304 }
305 if (program.info.stores_frag_depth) {
306 ctx.AddExecutionMode(main, spv::ExecutionMode::DepthReplacing);
307 }
308 if (ctx.runtime_info.force_early_z) {
309 ctx.AddExecutionMode(main, spv::ExecutionMode::EarlyFragmentTests);
310 }
311 break;
312 default:
313 throw NotImplementedException("Stage {}", program.stage);
314 }
315 ctx.AddEntryPoint(execution_model, main, "main", interfaces);
316}
317
318void SetupDenormControl(const Profile& profile, const IR::Program& program, EmitContext& ctx,
319 Id main_func) {
320 const Info& info{program.info};
321 if (info.uses_fp32_denorms_flush && info.uses_fp32_denorms_preserve) {
322 LOG_DEBUG(Shader_SPIRV, "Fp32 denorm flush and preserve on the same shader");
323 } else if (info.uses_fp32_denorms_flush) {
324 if (profile.support_fp32_denorm_flush) {
325 ctx.AddCapability(spv::Capability::DenormFlushToZero);
326 ctx.AddExecutionMode(main_func, spv::ExecutionMode::DenormFlushToZero, 32U);
327 } else {
328 // Drivers will most likely flush denorms by default, no need to warn
329 }
330 } else if (info.uses_fp32_denorms_preserve) {
331 if (profile.support_fp32_denorm_preserve) {
332 ctx.AddCapability(spv::Capability::DenormPreserve);
333 ctx.AddExecutionMode(main_func, spv::ExecutionMode::DenormPreserve, 32U);
334 } else {
335 LOG_DEBUG(Shader_SPIRV, "Fp32 denorm preserve used in shader without host support");
336 }
337 }
338 if (!profile.support_separate_denorm_behavior || profile.has_broken_fp16_float_controls) {
339 // No separate denorm behavior
340 return;
341 }
342 if (info.uses_fp16_denorms_flush && info.uses_fp16_denorms_preserve) {
343 LOG_DEBUG(Shader_SPIRV, "Fp16 denorm flush and preserve on the same shader");
344 } else if (info.uses_fp16_denorms_flush) {
345 if (profile.support_fp16_denorm_flush) {
346 ctx.AddCapability(spv::Capability::DenormFlushToZero);
347 ctx.AddExecutionMode(main_func, spv::ExecutionMode::DenormFlushToZero, 16U);
348 } else {
349 // Same as fp32, no need to warn as most drivers will flush by default
350 }
351 } else if (info.uses_fp16_denorms_preserve) {
352 if (profile.support_fp16_denorm_preserve) {
353 ctx.AddCapability(spv::Capability::DenormPreserve);
354 ctx.AddExecutionMode(main_func, spv::ExecutionMode::DenormPreserve, 16U);
355 } else {
356 LOG_DEBUG(Shader_SPIRV, "Fp16 denorm preserve used in shader without host support");
357 }
358 }
359}
360
361void SetupSignedNanCapabilities(const Profile& profile, const IR::Program& program,
362 EmitContext& ctx, Id main_func) {
363 if (profile.has_broken_fp16_float_controls && program.info.uses_fp16) {
364 return;
365 }
366 if (program.info.uses_fp16 && profile.support_fp16_signed_zero_nan_preserve) {
367 ctx.AddCapability(spv::Capability::SignedZeroInfNanPreserve);
368 ctx.AddExecutionMode(main_func, spv::ExecutionMode::SignedZeroInfNanPreserve, 16U);
369 }
370 if (profile.support_fp32_signed_zero_nan_preserve) {
371 ctx.AddCapability(spv::Capability::SignedZeroInfNanPreserve);
372 ctx.AddExecutionMode(main_func, spv::ExecutionMode::SignedZeroInfNanPreserve, 32U);
373 }
374 if (program.info.uses_fp64 && profile.support_fp64_signed_zero_nan_preserve) {
375 ctx.AddCapability(spv::Capability::SignedZeroInfNanPreserve);
376 ctx.AddExecutionMode(main_func, spv::ExecutionMode::SignedZeroInfNanPreserve, 64U);
377 }
378}
379
380void SetupCapabilities(const Profile& profile, const Info& info, EmitContext& ctx) {
381 if (info.uses_sampled_1d) {
382 ctx.AddCapability(spv::Capability::Sampled1D);
383 }
384 if (info.uses_sparse_residency) {
385 ctx.AddCapability(spv::Capability::SparseResidency);
386 }
387 if (info.uses_demote_to_helper_invocation && profile.support_demote_to_helper_invocation) {
388 ctx.AddExtension("SPV_EXT_demote_to_helper_invocation");
389 ctx.AddCapability(spv::Capability::DemoteToHelperInvocationEXT);
390 }
391 if (info.stores[IR::Attribute::ViewportIndex]) {
392 ctx.AddCapability(spv::Capability::MultiViewport);
393 }
394 if (info.stores[IR::Attribute::ViewportMask] && profile.support_viewport_mask) {
395 ctx.AddExtension("SPV_NV_viewport_array2");
396 ctx.AddCapability(spv::Capability::ShaderViewportMaskNV);
397 }
398 if (info.stores[IR::Attribute::Layer] || info.stores[IR::Attribute::ViewportIndex]) {
399 if (profile.support_viewport_index_layer_non_geometry && ctx.stage != Stage::Geometry) {
400 ctx.AddExtension("SPV_EXT_shader_viewport_index_layer");
401 ctx.AddCapability(spv::Capability::ShaderViewportIndexLayerEXT);
402 }
403 }
404 if (!profile.support_vertex_instance_id &&
405 (info.loads[IR::Attribute::InstanceId] || info.loads[IR::Attribute::VertexId])) {
406 ctx.AddExtension("SPV_KHR_shader_draw_parameters");
407 ctx.AddCapability(spv::Capability::DrawParameters);
408 }
409 if ((info.uses_subgroup_vote || info.uses_subgroup_invocation_id ||
410 info.uses_subgroup_shuffles) &&
411 profile.support_vote) {
412 ctx.AddExtension("SPV_KHR_shader_ballot");
413 ctx.AddCapability(spv::Capability::SubgroupBallotKHR);
414 if (!profile.warp_size_potentially_larger_than_guest) {
415 // vote ops are only used when not taking the long path
416 ctx.AddExtension("SPV_KHR_subgroup_vote");
417 ctx.AddCapability(spv::Capability::SubgroupVoteKHR);
418 }
419 }
420 if (info.uses_int64_bit_atomics && profile.support_int64_atomics) {
421 ctx.AddCapability(spv::Capability::Int64Atomics);
422 }
423 if (info.uses_typeless_image_reads && profile.support_typeless_image_loads) {
424 ctx.AddCapability(spv::Capability::StorageImageReadWithoutFormat);
425 }
426 if (info.uses_typeless_image_writes) {
427 ctx.AddCapability(spv::Capability::StorageImageWriteWithoutFormat);
428 }
429 if (info.uses_image_buffers) {
430 ctx.AddCapability(spv::Capability::ImageBuffer);
431 }
432 if (info.uses_sample_id) {
433 ctx.AddCapability(spv::Capability::SampleRateShading);
434 }
435 if (!ctx.runtime_info.xfb_varyings.empty()) {
436 ctx.AddCapability(spv::Capability::TransformFeedback);
437 }
438 if (info.uses_derivatives) {
439 ctx.AddCapability(spv::Capability::DerivativeControl);
440 }
441 // TODO: Track this usage
442 ctx.AddCapability(spv::Capability::ImageGatherExtended);
443 ctx.AddCapability(spv::Capability::ImageQuery);
444 ctx.AddCapability(spv::Capability::SampledBuffer);
445}
446
447void PatchPhiNodes(IR::Program& program, EmitContext& ctx) {
448 auto inst{program.blocks.front()->begin()};
449 size_t block_index{0};
450 ctx.PatchDeferredPhi([&](size_t phi_arg) {
451 if (phi_arg == 0) {
452 ++inst;
453 if (inst == program.blocks[block_index]->end() ||
454 inst->GetOpcode() != IR::Opcode::Phi) {
455 do {
456 ++block_index;
457 inst = program.blocks[block_index]->begin();
458 } while (inst->GetOpcode() != IR::Opcode::Phi);
459 }
460 }
461 return ctx.Def(inst->Arg(phi_arg));
462 });
463}
464} // Anonymous namespace
465
466std::vector<u32> EmitSPIRV(const Profile& profile, const RuntimeInfo& runtime_info,
467 IR::Program& program, Bindings& bindings) {
468 EmitContext ctx{profile, runtime_info, program, bindings};
469 const Id main{DefineMain(ctx, program)};
470 DefineEntryPoint(program, ctx, main);
471 if (profile.support_float_controls) {
472 ctx.AddExtension("SPV_KHR_float_controls");
473 SetupDenormControl(profile, program, ctx, main);
474 SetupSignedNanCapabilities(profile, program, ctx, main);
475 }
476 SetupCapabilities(profile, program.info, ctx);
477 PatchPhiNodes(program, ctx);
478 return ctx.Assemble();
479}
480
481Id EmitPhi(EmitContext& ctx, IR::Inst* inst) {
482 const size_t num_args{inst->NumArgs()};
483 boost::container::small_vector<Id, 32> blocks;
484 blocks.reserve(num_args);
485 for (size_t index = 0; index < num_args; ++index) {
486 blocks.push_back(inst->PhiBlock(index)->Definition<Id>());
487 }
488 // The type of a phi instruction is stored in its flags
489 const Id result_type{TypeId(ctx, inst->Flags<IR::Type>())};
490 return ctx.DeferredOpPhi(result_type, std::span(blocks.data(), blocks.size()));
491}
492
493void EmitVoid(EmitContext&) {}
494
495Id EmitIdentity(EmitContext& ctx, const IR::Value& value) {
496 const Id id{ctx.Def(value)};
497 if (!Sirit::ValidId(id)) {
498 throw NotImplementedException("Forward identity declaration");
499 }
500 return id;
501}
502
503Id EmitConditionRef(EmitContext& ctx, const IR::Value& value) {
504 const Id id{ctx.Def(value)};
505 if (!Sirit::ValidId(id)) {
506 throw NotImplementedException("Forward identity declaration");
507 }
508 return id;
509}
510
511void EmitReference(EmitContext&) {}
512
513void EmitPhiMove(EmitContext&) {
514 throw LogicError("Unreachable instruction");
515}
516
517void EmitGetZeroFromOp(EmitContext&) {
518 throw LogicError("Unreachable instruction");
519}
520
521void EmitGetSignFromOp(EmitContext&) {
522 throw LogicError("Unreachable instruction");
523}
524
525void EmitGetCarryFromOp(EmitContext&) {
526 throw LogicError("Unreachable instruction");
527}
528
529void EmitGetOverflowFromOp(EmitContext&) {
530 throw LogicError("Unreachable instruction");
531}
532
533void EmitGetSparseFromOp(EmitContext&) {
534 throw LogicError("Unreachable instruction");
535}
536
537void EmitGetInBoundsFromOp(EmitContext&) {
538 throw LogicError("Unreachable instruction");
539}
540
541} // namespace Shader::Backend::SPIRV