diff --git a/include/sirit/sirit.h b/include/sirit/sirit.h index 6ced1a8..b0b4f5c 100644 --- a/include/sirit/sirit.h +++ b/include/sirit/sirit.h @@ -365,6 +365,19 @@ class Module { /// Make a copy of a vector, with a single, variably selected, component modified. Id OpVectorInsertDynamic(Id result_type, Id vector, Id component, Id index); + /// Select arbitrary components from two vectors to make a new vector. + Id OpVectorShuffle(Id result_type, Id vector_1, Id vector_2, + std::span components); + + /// Select arbitrary components from two vectors to make a new vector. + template + requires(...&& std::is_convertible_v) Id + OpVectorShuffle(Id result_type, Id vector_1, Id vector_2, Ts&&... components) { + const Literal stack_components[] = {std::forward(components)...}; + return OpVectorShuffle(result_type, vector_1, vector_2, + std::span{stack_components}); + } + /// Make a copy of a composite object, while modifying one part of it. Id OpCompositeInsert(Id result_type, Id object, Id composite, std::span indexes = {}); @@ -576,6 +589,12 @@ class Module { /// Convert (value preserving) from unsigned integer to floating point. Id OpConvertUToF(Id result_type, Id operand); + /// Convert (value preserving) from unsigned integer to physical pointer. + Id OpConvertUToPtr(Id result_type, Id operand); + + /// Convert (value preserving) from physical pointer to unsigned integer. + Id OpConvertPtrToU(Id result_type, Id operand); + /// Convert (value preserving) unsigned width. This is either a truncate or a zero extend. Id OpUConvert(Id result_type, Id operand); @@ -656,6 +675,14 @@ class Module { /// Integer multiplication of Operand 1 and Operand 2. Id OpIMul(Id result_type, Id operand_1, Id operand_2); + /// Result is the unsigned-integer multiplication of Operand 1 and Operand 2 producing a + /// two-component struct with the low- and high-order halves of the product. + Id OpUMulExtended(Id result_type, Id operand_1, Id operand_2); + + /// Result is the signed-integer multiplication of Operand 1 and Operand 2 producing a + /// two-component struct with the low- and high-order halves of the product. + Id OpSMulExtended(Id result_type, Id operand_1, Id operand_2); + /// Floating-point multiplication of Operand 1 and Operand 2. Id OpFMul(Id result_type, Id operand_1, Id operand_2); @@ -686,6 +713,12 @@ class Module { /// Result is the unsigned integer addition of Operand 1 and Operand 2, including its carry. Id OpIAddCarry(Id result_type, Id operand_1, Id operand_2); + /// Scale a floating-point vector by a floating-point scalar. + Id OpVectorTimesScalar(Id result_type, Id vector, Id scalar); + + /// Dot product of two floating-point vectors. + Id OpDot(Id result_type, Id vector_1, Id vector_2); + // Extensions /// Execute an instruction in an imported set of extended instructions. @@ -768,6 +801,10 @@ class Module { /// Result is the reciprocal of sqrt x. Result is undefined if x <= 0. Id OpInverseSqrt(Id result_type, Id x); + /// Builds a floating-point number from x and the corresponding integral exponent of two in + /// exp. + Id OpLdexp(Id result_type, Id x, Id exp); + /// Result is y if y < x; otherwise result is x. Which operand is the result is undefined if one /// of the operands is a NaN. Id OpFMin(Id result_type, Id x, Id y); @@ -780,6 +817,10 @@ class Module { /// integers. Id OpSMin(Id result_type, Id x, Id y); + /// IEEE-754 minNum-style minimum: NaN-aware, returns the non-NaN operand if exactly one + /// is a NaN. + Id OpNMin(Id result_type, Id x, Id y); + /// Result is y if x < y; otherwise result is x. Which operand is the result is undefined if one /// of the operands is a NaN. Id OpFMax(Id result_type, Id x, Id y); @@ -792,6 +833,10 @@ class Module { /// integers. Id OpSMax(Id result_type, Id x, Id y); + /// IEEE-754 maxNum-style maximum: NaN-aware, returns the non-NaN operand if exactly one + /// is a NaN. + Id OpNMax(Id result_type, Id x, Id y); + /// Result is min(max(x, minVal), maxVal). Result is undefined if minVal > maxVal.The semantics /// used by min() and max() are those of FMin and FMax. Id OpFClamp(Id result_type, Id x, Id min_val, Id max_val); @@ -807,6 +852,10 @@ class Module { /// Computes a * b + c. Id OpFma(Id result_type, Id a, Id b, Id c); + /// Splits x into a normalized fraction in [0.5, 1) and an integral power of 2, returned as + /// a two-component struct {float significand, int exponent}. + Id OpFrexpStruct(Id result_type, Id x); + /// Result is the unsigned integer obtained by converting the components of a two-component /// floating-point vector to the 16-bit OpTypeFloat, and then packing these two 16-bit integers /// into a 32-bit unsigned integer. @@ -816,6 +865,30 @@ class Module { /// 32-bit unsigned integer into a pair of 16-bit values. Id OpUnpackHalf2x16(Id result_type, Id v); + /// Pack two normalized [0,1] floats into a 32-bit unsigned integer. + Id OpPackUnorm2x16(Id result_type, Id v); + + /// Unpack a 32-bit unsigned integer into two normalized [0,1] floats. + Id OpUnpackUnorm2x16(Id result_type, Id v); + + /// Pack two normalized [-1,1] floats into a 32-bit unsigned integer. + Id OpPackSnorm2x16(Id result_type, Id v); + + /// Unpack a 32-bit unsigned integer into two normalized [-1,1] floats. + Id OpUnpackSnorm2x16(Id result_type, Id v); + + /// Pack four normalized [0,1] floats into a 32-bit unsigned integer. + Id OpPackUnorm4x8(Id result_type, Id v); + + /// Unpack a 32-bit unsigned integer into four normalized [0,1] floats. + Id OpUnpackUnorm4x8(Id result_type, Id v); + + /// Pack four normalized [-1,1] floats into a 32-bit unsigned integer. + Id OpPackSnorm4x8(Id result_type, Id v); + + /// Unpack a 32-bit unsigned integer into four normalized [-1,1] floats. + Id OpUnpackSnorm4x8(Id result_type, Id v); + /// Integer least-significant bit. Id OpFindILsb(Id result_type, Id value); @@ -837,6 +910,55 @@ class Module { /// of the pixel specified by offset. Id OpInterpolateAtOffset(Id result_type, Id interpolant, Id offset); + /// Result is x normalized to a unit-length vector. + Id OpNormalize(Id result_type, Id x); + + /// Result is the cross product of two 3-component vectors. + Id OpCross(Id result_type, Id x, Id y); + + /// Result is the length of vector x. + Id OpLength(Id result_type, Id x); + + /// Linear interpolation of x and y by a (= x*(1-a) + y*a, componentwise). + Id OpFMix(Id result_type, Id x, Id y, Id a); + + // AMD extensions + + /// Returns the cube map face coordinate from a 3D direction (SPV_AMD_gcn_shader). + Id OpCubeFaceCoordAMD(Id result_type, Id cube_coord); + + /// Returns the cube map face index from a 3D direction (SPV_AMD_gcn_shader). + Id OpCubeFaceIndexAMD(Id result_type, Id cube_coord); + + /// Returns the current GPU clock as a 64-bit value (SPV_AMD_gcn_shader). + Id OpTimeAMD(Id result_type); + + /// Three-input minimum / maximum / median (SPV_AMD_shader_trinary_minmax). + Id OpFMin3AMD(Id result_type, Id x, Id y, Id z); + Id OpUMin3AMD(Id result_type, Id x, Id y, Id z); + Id OpSMin3AMD(Id result_type, Id x, Id y, Id z); + Id OpFMax3AMD(Id result_type, Id x, Id y, Id z); + Id OpUMax3AMD(Id result_type, Id x, Id y, Id z); + Id OpSMax3AMD(Id result_type, Id x, Id y, Id z); + Id OpFMid3AMD(Id result_type, Id x, Id y, Id z); + Id OpUMid3AMD(Id result_type, Id x, Id y, Id z); + Id OpSMid3AMD(Id result_type, Id x, Id y, Id z); + + /// Sample interpolant at a specific provoking vertex + /// (SPV_AMD_shader_explicit_vertex_parameter). + Id OpInterpolateAtVertexAMD(Id result_type, Id interpolant, Id vertex_idx); + + // Non-semantic extensions + + /// Print formatted text from a shader (NonSemantic.DebugPrintf). + Id OpDebugPrintf(Id format, std::span fmt_args); + + /// Print formatted text from a shader (NonSemantic.DebugPrintf). + template + requires(...&& std::is_convertible_v) Id OpDebugPrintf(Id format, Ts&&... fmt_args) { + return OpDebugPrintf(format, std::span({fmt_args...})); + } + // Derivatives /// Same result as either OpDPdxFine or OpDPdxCoarse on the input. @@ -1193,6 +1315,27 @@ class Module { /// otherwise, it is set to zero. Id OpGroupNonUniformBallot(Id result_type, Id scope, Id predicate); + /// Result is the value of the active invocation with the lowest id in the group. + Id OpGroupNonUniformBroadcastFirst(Id result_type, Id scope, Id value); + + /// Returns true only in the active invocation with the lowest id in the group, false in + /// other active invocations. + Id OpGroupNonUniformElect(Id result_type, Id scope); + + /// Returns true if the bit in Value corresponding to the executing invocation is set. + Id OpGroupNonUniformInverseBallot(Id result_type, Id scope, Id value); + + /// Counts the number of bits set in Value (per the GroupOperation: Reduce / InclusiveScan + /// / ExclusiveScan). + Id OpGroupNonUniformBallotBitCount(Id result_type, Id scope, spv::GroupOperation group_op, + Id value); + + /// Returns the smallest invocation id (LSB) set in Value. + Id OpGroupNonUniformBallotFindLSB(Id result_type, Id scope, Id value); + + /// Broadcast a value from one invocation in the quad to all active invocations in the quad. + Id OpGroupNonUniformQuadBroadcast(Id result_type, Id scope, Id value, Id index); + // Atomic /// Atomically load through Pointer using the given Semantics. All subparts of the value that is @@ -1274,6 +1417,12 @@ class Module { /// 3) store the New Value back through Pointer. Id OpAtomicUMax(Id result_type, Id pointer, Id memory, Id semantics, Id value); + /// Atomic floating-point maximum (SPV_EXT_shader_atomic_float_min_max). + Id OpAtomicFMax(Id result_type, Id pointer, Id memory, Id semantics, Id value); + + /// Atomic floating-point minimum (SPV_EXT_shader_atomic_float_min_max). + Id OpAtomicFMin(Id result_type, Id pointer, Id memory, Id semantics, Id value); + /// Perform the following steps atomically with respect to any other atomic accesses within /// Scope to the same location: /// 1) load through Pointer to get an Original Value, @@ -1295,8 +1444,942 @@ class Module { /// 3) store the New Value back through Pointer. Id OpAtomicXor(Id result_type, Id pointer, Id memory, Id semantics, Id value); + // === Auto-scaffolded extras (tools/scaffold_missing_methods.py) === + // + // Declarations below were auto-generated from spirv.core.grammar.json + // for opcodes that previously had no public API. Operand names are + // snake_cased from the grammar; types follow the standard kind->C++ + // mapping. Reorganize into per-class sections at your leisure. + + // === Annotation === + /// OpDecorationGroup + Id DecorationGroup(); + + /// OpGroupDecorate + Id GroupDecorate(Id decoration_group, std::span targets); + + + // === Arithmetic === + /// OpMatrixTimesScalar + Id OpMatrixTimesScalar(Id result_type, Id matrix, Id scalar); + + /// OpVectorTimesMatrix + Id OpVectorTimesMatrix(Id result_type, Id vector, Id matrix); + + /// OpMatrixTimesVector + Id OpMatrixTimesVector(Id result_type, Id matrix, Id vector); + + /// OpMatrixTimesMatrix + Id OpMatrixTimesMatrix(Id result_type, Id left_matrix, Id right_matrix); + + /// OpOuterProduct + Id OpOuterProduct(Id result_type, Id vector_1, Id vector_2); + + /// OpISubBorrow + Id OpISubBorrow(Id result_type, Id operand_1, Id operand_2); + + /// OpFmaKHR + Id OpFmaKHR(Id result_type, Id operand_1, Id operand_2, Id operand_3); + + /// OpSDot + Id OpSDot(Id result_type, Id vector_1, Id vector_2, std::optional packed_vector_format); + + /// OpUDot + Id OpUDot(Id result_type, Id vector_1, Id vector_2, std::optional packed_vector_format); + + /// OpSUDot + Id OpSUDot(Id result_type, Id vector_1, Id vector_2, std::optional packed_vector_format); + + /// OpSDotAccSat + Id OpSDotAccSat(Id result_type, Id vector_1, Id vector_2, Id accumulator, std::optional packed_vector_format); + + /// OpUDotAccSat + Id OpUDotAccSat(Id result_type, Id vector_1, Id vector_2, Id accumulator, std::optional packed_vector_format); + + /// OpSUDotAccSat + Id OpSUDotAccSat(Id result_type, Id vector_1, Id vector_2, Id accumulator, std::optional packed_vector_format); + + + // === Atomic === + /// OpAtomicCompareExchangeWeak + Id OpAtomicCompareExchangeWeak(Id result_type, Id pointer, Id memory, Id equal, Id unequal, Id value, Id comparator); + + /// OpAtomicFAddEXT + Id OpAtomicFAddEXT(Id result_type, Id pointer, Id memory, Id semantics, Id value); + + + // === Barrier === + /// OpNamedBarrierInitialize + Id OpNamedBarrierInitialize(Id result_type, Id subgroup_count); + + /// OpMemoryNamedBarrier + void OpMemoryNamedBarrier(Id named_barrier, Id memory, Id semantics); + + + // === Composite === + /// OpCopyObject + Id OpCopyObject(Id result_type, Id operand); + + /// OpTranspose + Id OpTranspose(Id result_type, Id matrix); + + /// OpCopyLogical + Id OpCopyLogical(Id result_type, Id operand); + + + // === Constant-Creation === + /// OpSpecConstantTrue + Id SpecConstantTrue(Id result_type); + + /// OpSpecConstantFalse + Id SpecConstantFalse(Id result_type); + + /// OpSpecConstant + Id SpecConstant(Id result_type, Literal value); + + /// OpSpecConstantComposite + Id SpecConstantComposite(Id result_type, std::span constituents); + + + // === Control-Flow === + /// OpLifetimeStart + void OpLifetimeStart(Id pointer, std::uint32_t size); + + /// OpLifetimeStop + void OpLifetimeStop(Id pointer, std::uint32_t size); + + + // === Debug === + /// OpSourceContinued + void SourceContinued(std::string_view continued_source); + + /// OpSource + void Source(spv::SourceLanguage source_language, std::uint32_t source_version, std::optional file, std::optional source); + + /// OpSourceExtension + void SourceExtension(std::string_view extension); + + /// OpNoLine + void NoLine(); + + /// OpModuleProcessed + void ModuleProcessed(std::string_view process); + + + // === Group === + /// OpGroupAsyncCopy + Id OpGroupAsyncCopy(Id result_type, Id execution, Id destination, Id source, Id num_elements, Id stride, Id event); + + /// OpGroupWaitEvents + void OpGroupWaitEvents(Id execution, Id num_events, Id events_list); + + /// OpGroupAll + Id OpGroupAll(Id result_type, Id execution, Id predicate); + + /// OpGroupAny + Id OpGroupAny(Id result_type, Id execution, Id predicate); + + /// OpGroupBroadcast + Id OpGroupBroadcast(Id result_type, Id execution, Id value, Id local_id); + + /// OpGroupIAdd + Id OpGroupIAdd(Id result_type, Id execution, spv::GroupOperation operation, Id x); + + /// OpGroupFAdd + Id OpGroupFAdd(Id result_type, Id execution, spv::GroupOperation operation, Id x); + + /// OpGroupFMin + Id OpGroupFMin(Id result_type, Id execution, spv::GroupOperation operation, Id x); + + /// OpGroupUMin + Id OpGroupUMin(Id result_type, Id execution, spv::GroupOperation operation, Id x); + + /// OpGroupSMin + Id OpGroupSMin(Id result_type, Id execution, spv::GroupOperation operation, Id x); + + /// OpGroupFMax + Id OpGroupFMax(Id result_type, Id execution, spv::GroupOperation operation, Id x); + + /// OpGroupUMax + Id OpGroupUMax(Id result_type, Id execution, spv::GroupOperation operation, Id x); + + /// OpGroupSMax + Id OpGroupSMax(Id result_type, Id execution, spv::GroupOperation operation, Id x); + + /// OpSubgroupFirstInvocationKHR + Id OpSubgroupFirstInvocationKHR(Id result_type, Id value); + + /// OpGroupNonUniformRotateKHR + Id OpGroupNonUniformRotateKHR(Id result_type, Id execution, Id value, Id delta, std::optional cluster_size); + + /// OpUntypedGroupAsyncCopyKHR + Id OpUntypedGroupAsyncCopyKHR(Id result_type, Id execution, Id destination, Id source, Id element_num_bytes, Id num_elements, Id stride, Id event, std::optional destination_memory_operands, std::optional source_memory_operands); + + /// OpGroupIMulKHR + Id OpGroupIMulKHR(Id result_type, Id execution, spv::GroupOperation operation, Id x); + + /// OpGroupFMulKHR + Id OpGroupFMulKHR(Id result_type, Id execution, spv::GroupOperation operation, Id x); + + /// OpGroupBitwiseAndKHR + Id OpGroupBitwiseAndKHR(Id result_type, Id execution, spv::GroupOperation operation, Id x); + + /// OpGroupBitwiseOrKHR + Id OpGroupBitwiseOrKHR(Id result_type, Id execution, spv::GroupOperation operation, Id x); + + /// OpGroupBitwiseXorKHR + Id OpGroupBitwiseXorKHR(Id result_type, Id execution, spv::GroupOperation operation, Id x); + + /// OpGroupLogicalAndKHR + Id OpGroupLogicalAndKHR(Id result_type, Id execution, spv::GroupOperation operation, Id x); + + /// OpGroupLogicalOrKHR + Id OpGroupLogicalOrKHR(Id result_type, Id execution, spv::GroupOperation operation, Id x); + + /// OpGroupLogicalXorKHR + Id OpGroupLogicalXorKHR(Id result_type, Id execution, spv::GroupOperation operation, Id x); + + + // === Image === + /// OpImageQueryFormat + Id OpImageQueryFormat(Id result_type, Id image); + + /// OpImageQueryOrder + Id OpImageQueryOrder(Id result_type, Id image); + + /// OpImageSparseSampleProjImplicitLod + Id OpImageSparseSampleProjImplicitLod(Id result_type, Id sampled_image, Id coordinate, std::optional image_operands); + + /// OpImageSparseSampleProjExplicitLod + Id OpImageSparseSampleProjExplicitLod(Id result_type, Id sampled_image, Id coordinate, spv::ImageOperandsMask image_operands); + + /// OpImageSparseSampleProjDrefImplicitLod + Id OpImageSparseSampleProjDrefImplicitLod(Id result_type, Id sampled_image, Id coordinate, Id dref, std::optional image_operands); + + /// OpImageSparseSampleProjDrefExplicitLod + Id OpImageSparseSampleProjDrefExplicitLod(Id result_type, Id sampled_image, Id coordinate, Id dref, spv::ImageOperandsMask image_operands); + + /// OpColorAttachmentReadEXT + Id OpColorAttachmentReadEXT(Id result_type, Id attachment, std::optional sample); + + /// OpDepthAttachmentReadEXT + Id OpDepthAttachmentReadEXT(Id result_type, std::optional sample); + + /// OpStencilAttachmentReadEXT + Id OpStencilAttachmentReadEXT(Id result_type, std::optional sample); + + + // === Memory === + /// OpCopyMemory + void OpCopyMemory(Id target, Id source, std::optional memory_access, std::optional memory_access_2); + + /// OpCopyMemorySized + void OpCopyMemorySized(Id target, Id source, Id size, std::optional memory_access, std::optional memory_access_2); + + /// OpInBoundsAccessChain + Id OpInBoundsAccessChain(Id result_type, Id base, std::span indexes); + + /// OpPtrAccessChain + Id OpPtrAccessChain(Id result_type, Id base, Id element, std::span indexes); + + /// OpArrayLength + Id OpArrayLength(Id result_type, Id structure, std::uint32_t array_member); + + /// OpInBoundsPtrAccessChain + Id OpInBoundsPtrAccessChain(Id result_type, Id base, Id element, std::span indexes); + + /// OpPtrEqual + Id OpPtrEqual(Id result_type, Id operand_1, Id operand_2); + + /// OpPtrNotEqual + Id OpPtrNotEqual(Id result_type, Id operand_1, Id operand_2); + + /// OpPtrDiff + Id OpPtrDiff(Id result_type, Id operand_1, Id operand_2); + + + // === Miscellaneous === + /// OpNop + void OpNop(); + + /// OpSizeOf + Id OpSizeOf(Id result_type, Id pointer); + + /// OpPoisonKHR + Id OpPoisonKHR(Id result_type); + + /// OpFreezeKHR + Id OpFreezeKHR(Id result_type, Id value); + + /// OpAssumeTrueKHR + void OpAssumeTrueKHR(Id condition); + + /// OpExpectKHR + Id OpExpectKHR(Id result_type, Id value, Id expected_value); + + /// OpArithmeticFenceEXT + Id OpArithmeticFenceEXT(Id result_type, Id target); + + + // === Non-Uniform === + /// OpGroupNonUniformBallotBitExtract + Id OpGroupNonUniformBallotBitExtract(Id result_type, Id execution, Id value, Id index); + + /// OpGroupNonUniformBallotFindMSB + Id OpGroupNonUniformBallotFindMSB(Id result_type, Id execution, Id value); + + /// OpGroupNonUniformShuffleUp + Id OpGroupNonUniformShuffleUp(Id result_type, Id execution, Id value, Id delta); + + /// OpGroupNonUniformShuffleDown + Id OpGroupNonUniformShuffleDown(Id result_type, Id execution, Id value, Id delta); + + /// OpGroupNonUniformIAdd + Id OpGroupNonUniformIAdd(Id result_type, Id execution, spv::GroupOperation operation, Id value, std::optional cluster_size); + + /// OpGroupNonUniformFAdd + Id OpGroupNonUniformFAdd(Id result_type, Id execution, spv::GroupOperation operation, Id value, std::optional cluster_size); + + /// OpGroupNonUniformIMul + Id OpGroupNonUniformIMul(Id result_type, Id execution, spv::GroupOperation operation, Id value, std::optional cluster_size); + + /// OpGroupNonUniformFMul + Id OpGroupNonUniformFMul(Id result_type, Id execution, spv::GroupOperation operation, Id value, std::optional cluster_size); + + /// OpGroupNonUniformSMin + Id OpGroupNonUniformSMin(Id result_type, Id execution, spv::GroupOperation operation, Id value, std::optional cluster_size); + + /// OpGroupNonUniformUMin + Id OpGroupNonUniformUMin(Id result_type, Id execution, spv::GroupOperation operation, Id value, std::optional cluster_size); + + /// OpGroupNonUniformFMin + Id OpGroupNonUniformFMin(Id result_type, Id execution, spv::GroupOperation operation, Id value, std::optional cluster_size); + + /// OpGroupNonUniformSMax + Id OpGroupNonUniformSMax(Id result_type, Id execution, spv::GroupOperation operation, Id value, std::optional cluster_size); + + /// OpGroupNonUniformUMax + Id OpGroupNonUniformUMax(Id result_type, Id execution, spv::GroupOperation operation, Id value, std::optional cluster_size); + + /// OpGroupNonUniformFMax + Id OpGroupNonUniformFMax(Id result_type, Id execution, spv::GroupOperation operation, Id value, std::optional cluster_size); + + /// OpGroupNonUniformBitwiseAnd + Id OpGroupNonUniformBitwiseAnd(Id result_type, Id execution, spv::GroupOperation operation, Id value, std::optional cluster_size); + + /// OpGroupNonUniformBitwiseOr + Id OpGroupNonUniformBitwiseOr(Id result_type, Id execution, spv::GroupOperation operation, Id value, std::optional cluster_size); + + /// OpGroupNonUniformBitwiseXor + Id OpGroupNonUniformBitwiseXor(Id result_type, Id execution, spv::GroupOperation operation, Id value, std::optional cluster_size); + + /// OpGroupNonUniformLogicalAnd + Id OpGroupNonUniformLogicalAnd(Id result_type, Id execution, spv::GroupOperation operation, Id value, std::optional cluster_size); + + /// OpGroupNonUniformLogicalOr + Id OpGroupNonUniformLogicalOr(Id result_type, Id execution, spv::GroupOperation operation, Id value, std::optional cluster_size); + + /// OpGroupNonUniformLogicalXor + Id OpGroupNonUniformLogicalXor(Id result_type, Id execution, spv::GroupOperation operation, Id value, std::optional cluster_size); + + /// OpGroupNonUniformQuadSwap + Id OpGroupNonUniformQuadSwap(Id result_type, Id execution, Id value, Id direction); + + /// OpGroupNonUniformQuadAllKHR + Id OpGroupNonUniformQuadAllKHR(Id result_type, Id predicate); + + /// OpGroupNonUniformQuadAnyKHR + Id OpGroupNonUniformQuadAnyKHR(Id result_type, Id predicate); + + /// OpGroupNonUniformPartitionEXT + Id OpGroupNonUniformPartitionEXT(Id result_type, Id value); + + + // === Relational_and_Logical === + /// OpIsFinite + Id OpIsFinite(Id result_type, Id x); + + /// OpIsNormal + Id OpIsNormal(Id result_type, Id x); + + /// OpSignBitSet + Id OpSignBitSet(Id result_type, Id x); + + /// OpLessOrGreater + Id OpLessOrGreater(Id result_type, Id x, Id y); + + /// OpOrdered + Id OpOrdered(Id result_type, Id x, Id y); + + /// OpUnordered + Id OpUnordered(Id result_type, Id x, Id y); + + + // === Reserved === + /// OpTraceRayKHR + void OpTraceRayKHR(Id accel, Id ray_flags, Id cull_mask, Id sbt_offset, Id sbt_stride, Id miss_index, Id ray_origin, Id ray_tmin, Id ray_direction, Id ray_tmax, Id payload); + + /// OpExecuteCallableKHR + void OpExecuteCallableKHR(Id sbt_index, Id callable_data); + + /// OpConvertUToAccelerationStructureKHR + Id OpConvertUToAccelerationStructureKHR(Id result_type, Id accel); + + /// OpIgnoreIntersectionKHR + void OpIgnoreIntersectionKHR(); + + /// OpTerminateRayKHR + void OpTerminateRayKHR(); + + /// OpRayQueryInitializeKHR + void OpRayQueryInitializeKHR(Id ray_query, Id accel, Id ray_flags, Id cull_mask, Id ray_origin, Id ray_t_min, Id ray_direction, Id ray_t_max); + + /// OpRayQueryTerminateKHR + void OpRayQueryTerminateKHR(Id ray_query); + + /// OpRayQueryGenerateIntersectionKHR + void OpRayQueryGenerateIntersectionKHR(Id ray_query, Id hit_t); + + /// OpRayQueryConfirmIntersectionKHR + void OpRayQueryConfirmIntersectionKHR(Id ray_query); + + /// OpRayQueryProceedKHR + Id OpRayQueryProceedKHR(Id result_type, Id ray_query); + + /// OpRayQueryGetIntersectionTypeKHR + Id OpRayQueryGetIntersectionTypeKHR(Id result_type, Id ray_query, Id intersection); + + /// OpReadClockKHR + Id OpReadClockKHR(Id result_type, Id scope); + + /// OpEmitMeshTasksEXT + void OpEmitMeshTasksEXT(Id group_count_x, Id group_count_y, Id group_count_z, std::optional payload); + + /// OpSetMeshOutputsEXT + void OpSetMeshOutputsEXT(Id vertex_count, Id primitive_count); + + /// OpReportIntersectionKHR + Id OpReportIntersectionKHR(Id result_type, Id hit, Id hit_kind); + + /// OpRayQueryGetIntersectionTriangleVertexPositionsKHR + Id OpRayQueryGetIntersectionTriangleVertexPositionsKHR(Id result_type, Id ray_query, Id intersection); + + /// OpBeginInvocationInterlockEXT + void OpBeginInvocationInterlockEXT(); + + /// OpEndInvocationInterlockEXT + void OpEndInvocationInterlockEXT(); + + /// OpIsHelperInvocationEXT + Id OpIsHelperInvocationEXT(Id result_type); + + /// OpRayQueryGetRayTMinKHR + Id OpRayQueryGetRayTMinKHR(Id result_type, Id ray_query); + + /// OpRayQueryGetRayFlagsKHR + Id OpRayQueryGetRayFlagsKHR(Id result_type, Id ray_query); + + /// OpRayQueryGetIntersectionTKHR + Id OpRayQueryGetIntersectionTKHR(Id result_type, Id ray_query, Id intersection); + + /// OpRayQueryGetIntersectionInstanceCustomIndexKHR + Id OpRayQueryGetIntersectionInstanceCustomIndexKHR(Id result_type, Id ray_query, Id intersection); + + /// OpRayQueryGetIntersectionInstanceIdKHR + Id OpRayQueryGetIntersectionInstanceIdKHR(Id result_type, Id ray_query, Id intersection); + + /// OpRayQueryGetIntersectionInstanceShaderBindingTableRecordOffsetKHR + Id OpRayQueryGetIntersectionInstanceShaderBindingTableRecordOffsetKHR(Id result_type, Id ray_query, Id intersection); + + /// OpRayQueryGetIntersectionGeometryIndexKHR + Id OpRayQueryGetIntersectionGeometryIndexKHR(Id result_type, Id ray_query, Id intersection); + + /// OpRayQueryGetIntersectionPrimitiveIndexKHR + Id OpRayQueryGetIntersectionPrimitiveIndexKHR(Id result_type, Id ray_query, Id intersection); + + /// OpRayQueryGetIntersectionBarycentricsKHR + Id OpRayQueryGetIntersectionBarycentricsKHR(Id result_type, Id ray_query, Id intersection); + + /// OpRayQueryGetIntersectionFrontFaceKHR + Id OpRayQueryGetIntersectionFrontFaceKHR(Id result_type, Id ray_query, Id intersection); + + /// OpRayQueryGetIntersectionCandidateAABBOpaqueKHR + Id OpRayQueryGetIntersectionCandidateAABBOpaqueKHR(Id result_type, Id ray_query); + + /// OpRayQueryGetIntersectionObjectRayDirectionKHR + Id OpRayQueryGetIntersectionObjectRayDirectionKHR(Id result_type, Id ray_query, Id intersection); + + /// OpRayQueryGetIntersectionObjectRayOriginKHR + Id OpRayQueryGetIntersectionObjectRayOriginKHR(Id result_type, Id ray_query, Id intersection); + + /// OpRayQueryGetWorldRayDirectionKHR + Id OpRayQueryGetWorldRayDirectionKHR(Id result_type, Id ray_query); + + /// OpRayQueryGetWorldRayOriginKHR + Id OpRayQueryGetWorldRayOriginKHR(Id result_type, Id ray_query); + + /// OpRayQueryGetIntersectionObjectToWorldKHR + Id OpRayQueryGetIntersectionObjectToWorldKHR(Id result_type, Id ray_query, Id intersection); + + /// OpRayQueryGetIntersectionWorldToObjectKHR + Id OpRayQueryGetIntersectionWorldToObjectKHR(Id result_type, Id ray_query, Id intersection); + + + // === Type-Declaration === + // OpTypeForwardPointer skipped: it is a void Type-Declaration whose + // emission needs an extension to the Declarations stream that allows + // un-deduped writes via the `spv::Op` overload (currently deleted). + // Keep hand-written if needed. + + /// OpTypePipeStorage + Id TypePipeStorage(); + + /// OpTypeNamedBarrier + Id TypeNamedBarrier(); + + /// OpTypeRayQueryKHR + Id TypeRayQueryKHR(); + + /// OpTypeAccelerationStructureKHR + Id TypeAccelerationStructureKHR(); + + + + + // === Auto-scaffolded NV / cooperative-matrix extras === + + // === Arithmetic === + /// OpCooperativeMatrixMulAddKHR + Id OpCooperativeMatrixMulAddKHR(Id result_type, Id a, Id b, Id c, std::optional cooperative_matrix_operands); + + /// OpCooperativeMatrixReduceNV + Id OpCooperativeMatrixReduceNV(Id result_type, Id matrix, spv::CooperativeMatrixReduceMask reduce, Id combine_func); + + + // === Conversion === + /// OpCooperativeMatrixConvertNV + Id OpCooperativeMatrixConvertNV(Id result_type, Id matrix); + + /// OpCooperativeMatrixTransposeNV + Id OpCooperativeMatrixTransposeNV(Id result_type, Id matrix); + + + // === Function === + /// OpCooperativeMatrixPerElementOpNV + Id OpCooperativeMatrixPerElementOpNV(Id result_type, Id matrix, Id func, std::span operands); + + + // === Image === + /// OpImageSampleFootprintNV + Id OpImageSampleFootprintNV(Id result_type, Id sampled_image, Id coordinate, Id granularity, Id coarse, std::optional image_operands); + + + // === Memory === + /// OpCooperativeMatrixLoadKHR + Id OpCooperativeMatrixLoadKHR(Id result_type, Id pointer, Id memory_layout, std::optional stride, std::optional memory_operand); + + /// OpCooperativeMatrixStoreKHR + void OpCooperativeMatrixStoreKHR(Id pointer, Id object, Id memory_layout, std::optional stride, std::optional memory_operand); + + /// OpCooperativeVectorLoadNV + Id OpCooperativeVectorLoadNV(Id result_type, Id pointer, Id offset, std::optional memory_access); + + /// OpCooperativeVectorStoreNV + void OpCooperativeVectorStoreNV(Id pointer, Id offset, Id object, std::optional memory_access); + + /// OpCooperativeMatrixLoadTensorNV + Id OpCooperativeMatrixLoadTensorNV(Id result_type, Id pointer, Id object, Id tensor_layout, spv::MemoryAccessMask memory_operand, spv::TensorAddressingOperandsMask tensor_addressing_operands); + + /// OpCooperativeMatrixStoreTensorNV + void OpCooperativeMatrixStoreTensorNV(Id pointer, Id object, Id tensor_layout, spv::MemoryAccessMask memory_operand, spv::TensorAddressingOperandsMask tensor_addressing_operands); + + /// OpRawAccessChainNV + Id OpRawAccessChainNV(Id result_type, Id base, Id byte_stride, Id element_index, Id byte_offset, std::optional raw_access_chain_operands); + + + // === Miscellaneous === + /// OpCooperativeMatrixLengthKHR + Id OpCooperativeMatrixLengthKHR(Id result_type, Id type); + + + // === Reserved === + /// OpHitObjectRecordHitMotionNV + void OpHitObjectRecordHitMotionNV(Id hit_object, Id acceleration_structure, Id instance_id, Id primitive_id, Id geometry_index, Id hit_kind, Id sbt_record_offset, Id sbt_record_stride, Id origin, Id t_min, Id direction, Id t_max, Id current_time, Id hit_object_attributes); + + /// OpHitObjectRecordHitWithIndexMotionNV + void OpHitObjectRecordHitWithIndexMotionNV(Id hit_object, Id acceleration_structure, Id instance_id, Id primitive_id, Id geometry_index, Id hit_kind, Id sbt_record_index, Id origin, Id t_min, Id direction, Id t_max, Id current_time, Id hit_object_attributes); + + /// OpHitObjectRecordMissMotionNV + void OpHitObjectRecordMissMotionNV(Id hit_object, Id sbt_index, Id origin, Id t_min, Id direction, Id t_max, Id current_time); + + /// OpHitObjectGetWorldToObjectNV + Id OpHitObjectGetWorldToObjectNV(Id result_type, Id hit_object); + + /// OpHitObjectGetObjectToWorldNV + Id OpHitObjectGetObjectToWorldNV(Id result_type, Id hit_object); + + /// OpHitObjectGetObjectRayDirectionNV + Id OpHitObjectGetObjectRayDirectionNV(Id result_type, Id hit_object); + + /// OpHitObjectGetObjectRayOriginNV + Id OpHitObjectGetObjectRayOriginNV(Id result_type, Id hit_object); + + /// OpHitObjectTraceRayMotionNV + void OpHitObjectTraceRayMotionNV(Id hit_object, Id acceleration_structure, Id ray_flags, Id cullmask, Id sbt_record_offset, Id sbt_record_stride, Id miss_index, Id origin, Id t_min, Id direction, Id t_max, Id time, Id payload); + + /// OpHitObjectGetShaderRecordBufferHandleNV + Id OpHitObjectGetShaderRecordBufferHandleNV(Id result_type, Id hit_object); + + /// OpHitObjectGetShaderBindingTableRecordIndexNV + Id OpHitObjectGetShaderBindingTableRecordIndexNV(Id result_type, Id hit_object); + + /// OpHitObjectRecordEmptyNV + void OpHitObjectRecordEmptyNV(Id hit_object); + + /// OpHitObjectTraceRayNV + void OpHitObjectTraceRayNV(Id hit_object, Id acceleration_structure, Id ray_flags, Id cullmask, Id sbt_record_offset, Id sbt_record_stride, Id miss_index, Id origin, Id t_min, Id direction, Id t_max, Id payload); + + /// OpHitObjectRecordHitNV + void OpHitObjectRecordHitNV(Id hit_object, Id acceleration_structure, Id instance_id, Id primitive_id, Id geometry_index, Id hit_kind, Id sbt_record_offset, Id sbt_record_stride, Id origin, Id t_min, Id direction, Id t_max, Id hit_object_attributes); + + /// OpHitObjectRecordHitWithIndexNV + void OpHitObjectRecordHitWithIndexNV(Id hit_object, Id acceleration_structure, Id instance_id, Id primitive_id, Id geometry_index, Id hit_kind, Id sbt_record_index, Id origin, Id t_min, Id direction, Id t_max, Id hit_object_attributes); + + /// OpHitObjectRecordMissNV + void OpHitObjectRecordMissNV(Id hit_object, Id sbt_index, Id origin, Id t_min, Id direction, Id t_max); + + /// OpHitObjectExecuteShaderNV + void OpHitObjectExecuteShaderNV(Id hit_object, Id payload); + + /// OpHitObjectGetCurrentTimeNV + Id OpHitObjectGetCurrentTimeNV(Id result_type, Id hit_object); + + /// OpHitObjectGetAttributesNV + void OpHitObjectGetAttributesNV(Id hit_object, Id hit_object_attribute); + + /// OpHitObjectGetHitKindNV + Id OpHitObjectGetHitKindNV(Id result_type, Id hit_object); + + /// OpHitObjectGetPrimitiveIndexNV + Id OpHitObjectGetPrimitiveIndexNV(Id result_type, Id hit_object); + + /// OpHitObjectGetGeometryIndexNV + Id OpHitObjectGetGeometryIndexNV(Id result_type, Id hit_object); + + /// OpHitObjectGetInstanceIdNV + Id OpHitObjectGetInstanceIdNV(Id result_type, Id hit_object); + + /// OpHitObjectGetInstanceCustomIndexNV + Id OpHitObjectGetInstanceCustomIndexNV(Id result_type, Id hit_object); + + /// OpHitObjectGetWorldRayDirectionNV + Id OpHitObjectGetWorldRayDirectionNV(Id result_type, Id hit_object); + + /// OpHitObjectGetWorldRayOriginNV + Id OpHitObjectGetWorldRayOriginNV(Id result_type, Id hit_object); + + /// OpHitObjectGetRayTMaxNV + Id OpHitObjectGetRayTMaxNV(Id result_type, Id hit_object); + + /// OpHitObjectGetRayTMinNV + Id OpHitObjectGetRayTMinNV(Id result_type, Id hit_object); + + /// OpHitObjectIsEmptyNV + Id OpHitObjectIsEmptyNV(Id result_type, Id hit_object); + + /// OpHitObjectIsHitNV + Id OpHitObjectIsHitNV(Id result_type, Id hit_object); + + /// OpHitObjectIsMissNV + Id OpHitObjectIsMissNV(Id result_type, Id hit_object); + + /// OpReorderThreadWithHitObjectNV + void OpReorderThreadWithHitObjectNV(Id hit_object, std::optional hint, std::optional bits); + + /// OpReorderThreadWithHintNV + void OpReorderThreadWithHintNV(Id hint, Id bits); + + /// OpCooperativeVectorMatrixMulNV + Id OpCooperativeVectorMatrixMulNV(Id result_type, Id input, Id input_interpretation, Id matrix, Id matrix_offset, Id matrix_interpretation, Id m, Id k, Id memory_layout, Id transpose, std::optional matrix_stride, std::optional cooperative_matrix_operands); + + /// OpCooperativeVectorOuterProductAccumulateNV + void OpCooperativeVectorOuterProductAccumulateNV(Id pointer, Id offset, Id a, Id b, Id memory_layout, Id matrix_interpretation, std::optional matrix_stride); + + /// OpCooperativeVectorReduceSumAccumulateNV + void OpCooperativeVectorReduceSumAccumulateNV(Id pointer, Id offset, Id v); + + /// OpCooperativeVectorMatrixMulAddNV + Id OpCooperativeVectorMatrixMulAddNV(Id result_type, Id input, Id input_interpretation, Id matrix, Id matrix_offset, Id matrix_interpretation, Id bias, Id bias_offset, Id bias_interpretation, Id m, Id k, Id memory_layout, Id transpose, std::optional matrix_stride, std::optional cooperative_matrix_operands); + + /// OpWritePackedPrimitiveIndices4x8NV + void OpWritePackedPrimitiveIndices4x8NV(Id index_offset, Id packed_indices); + + /// OpFetchMicroTriangleVertexPositionNV + Id OpFetchMicroTriangleVertexPositionNV(Id result_type, Id accel, Id instance_id, Id geometry_index, Id primitive_index, Id barycentric); + + /// OpFetchMicroTriangleVertexBarycentricNV + Id OpFetchMicroTriangleVertexBarycentricNV(Id result_type, Id accel, Id instance_id, Id geometry_index, Id primitive_index, Id barycentric); + + /// OpHitObjectRecordFromQueryEXT + void OpHitObjectRecordFromQueryEXT(Id hit_object, Id ray_query, Id sbt_record_index, Id hit_object_attributes, std::optional hit_kind); + + /// OpHitObjectRecordMissEXT + void OpHitObjectRecordMissEXT(Id hit_object, Id ray_flags, Id miss_index, Id ray_origin, Id ray_tmin, Id ray_direction, Id ray_tmax); + + /// OpHitObjectRecordMissMotionEXT + void OpHitObjectRecordMissMotionEXT(Id hit_object, Id ray_flags, Id miss_index, Id ray_origin, Id ray_tmin, Id ray_direction, Id ray_tmax, Id current_time); + + /// OpHitObjectGetIntersectionTriangleVertexPositionsEXT + Id OpHitObjectGetIntersectionTriangleVertexPositionsEXT(Id result_type, Id hit_object); + + /// OpHitObjectGetRayFlagsEXT + Id OpHitObjectGetRayFlagsEXT(Id result_type, Id hit_object); + + /// OpHitObjectSetShaderBindingTableRecordIndexEXT + void OpHitObjectSetShaderBindingTableRecordIndexEXT(Id hit_object, Id sbt_record_index); + + /// OpHitObjectReorderExecuteShaderEXT + void OpHitObjectReorderExecuteShaderEXT(Id hit_object, Id payload, std::optional hint, std::optional bits); + + /// OpHitObjectTraceReorderExecuteEXT + void OpHitObjectTraceReorderExecuteEXT(Id hit_object, Id acceleration_structure, Id ray_flags, Id cull_mask, Id sbt_offset, Id sbt_stride, Id miss_index, Id ray_origin, Id ray_tmin, Id ray_direction, Id ray_tmax, Id payload, std::optional hint, std::optional bits); + + /// OpHitObjectTraceMotionReorderExecuteEXT + void OpHitObjectTraceMotionReorderExecuteEXT(Id hit_object, Id acceleration_structure, Id ray_flags, Id cull_mask, Id sbt_offset, Id sbt_stride, Id miss_index, Id ray_origin, Id ray_tmin, Id ray_direction, Id ray_tmax, Id current_time, Id payload, std::optional hint, std::optional bits); + + /// OpReorderThreadWithHintEXT + void OpReorderThreadWithHintEXT(Id hint, Id bits); + + /// OpReorderThreadWithHitObjectEXT + void OpReorderThreadWithHitObjectEXT(Id hit_object, std::optional hint, std::optional bits); + + /// OpHitObjectTraceRayEXT + void OpHitObjectTraceRayEXT(Id hit_object, Id acceleration_structure, Id ray_flags, Id cull_mask, Id sbt_offset, Id sbt_stride, Id miss_index, Id ray_origin, Id ray_tmin, Id ray_direction, Id ray_tmax, Id payload); + + /// OpHitObjectTraceRayMotionEXT + void OpHitObjectTraceRayMotionEXT(Id hit_object, Id acceleration_structure, Id ray_flags, Id cull_mask, Id sbt_offset, Id sbt_stride, Id miss_index, Id ray_origin, Id ray_tmin, Id ray_direction, Id ray_tmax, Id current_time, Id payload); + + /// OpHitObjectRecordEmptyEXT + void OpHitObjectRecordEmptyEXT(Id hit_object); + + /// OpHitObjectExecuteShaderEXT + void OpHitObjectExecuteShaderEXT(Id hit_object, Id payload); + + /// OpHitObjectGetCurrentTimeEXT + Id OpHitObjectGetCurrentTimeEXT(Id result_type, Id hit_object); + + /// OpHitObjectGetAttributesEXT + void OpHitObjectGetAttributesEXT(Id hit_object, Id hit_object_attribute); + + /// OpHitObjectGetHitKindEXT + Id OpHitObjectGetHitKindEXT(Id result_type, Id hit_object); + + /// OpHitObjectGetPrimitiveIndexEXT + Id OpHitObjectGetPrimitiveIndexEXT(Id result_type, Id hit_object); + + /// OpHitObjectGetGeometryIndexEXT + Id OpHitObjectGetGeometryIndexEXT(Id result_type, Id hit_object); + + /// OpHitObjectGetInstanceIdEXT + Id OpHitObjectGetInstanceIdEXT(Id result_type, Id hit_object); + + /// OpHitObjectGetInstanceCustomIndexEXT + Id OpHitObjectGetInstanceCustomIndexEXT(Id result_type, Id hit_object); + + /// OpHitObjectGetObjectRayOriginEXT + Id OpHitObjectGetObjectRayOriginEXT(Id result_type, Id hit_object); + + /// OpHitObjectGetObjectRayDirectionEXT + Id OpHitObjectGetObjectRayDirectionEXT(Id result_type, Id hit_object); + + /// OpHitObjectGetWorldRayDirectionEXT + Id OpHitObjectGetWorldRayDirectionEXT(Id result_type, Id hit_object); + + /// OpHitObjectGetWorldRayOriginEXT + Id OpHitObjectGetWorldRayOriginEXT(Id result_type, Id hit_object); + + /// OpHitObjectGetObjectToWorldEXT + Id OpHitObjectGetObjectToWorldEXT(Id result_type, Id hit_object); + + /// OpHitObjectGetWorldToObjectEXT + Id OpHitObjectGetWorldToObjectEXT(Id result_type, Id hit_object); + + /// OpHitObjectGetRayTMaxEXT + Id OpHitObjectGetRayTMaxEXT(Id result_type, Id hit_object); + + /// OpIgnoreIntersectionNV + void OpIgnoreIntersectionNV(); + + /// OpTerminateRayNV + void OpTerminateRayNV(); + + /// OpTraceNV + void OpTraceNV(Id accel, Id ray_flags, Id cull_mask, Id sbt_offset, Id sbt_stride, Id miss_index, Id ray_origin, Id ray_tmin, Id ray_direction, Id ray_tmax, Id payload_id); + + /// OpTraceMotionNV + void OpTraceMotionNV(Id accel, Id ray_flags, Id cull_mask, Id sbt_offset, Id sbt_stride, Id miss_index, Id ray_origin, Id ray_tmin, Id ray_direction, Id ray_tmax, Id time, Id payload_id); + + /// OpTraceRayMotionNV + void OpTraceRayMotionNV(Id accel, Id ray_flags, Id cull_mask, Id sbt_offset, Id sbt_stride, Id miss_index, Id ray_origin, Id ray_tmin, Id ray_direction, Id ray_tmax, Id time, Id payload); + + /// OpExecuteCallableNV + void OpExecuteCallableNV(Id sbt_index, Id callable_data_id); + + /// OpRayQueryGetIntersectionClusterIdNV + Id OpRayQueryGetIntersectionClusterIdNV(Id result_type, Id ray_query, Id intersection); + + /// OpHitObjectGetClusterIdNV + Id OpHitObjectGetClusterIdNV(Id result_type, Id hit_object); + + /// OpHitObjectGetRayTMinEXT + Id OpHitObjectGetRayTMinEXT(Id result_type, Id hit_object); + + /// OpHitObjectGetShaderBindingTableRecordIndexEXT + Id OpHitObjectGetShaderBindingTableRecordIndexEXT(Id result_type, Id hit_object); + + /// OpHitObjectGetShaderRecordBufferHandleEXT + Id OpHitObjectGetShaderRecordBufferHandleEXT(Id result_type, Id hit_object); + + /// OpHitObjectIsEmptyEXT + Id OpHitObjectIsEmptyEXT(Id result_type, Id hit_object); + + /// OpHitObjectIsHitEXT + Id OpHitObjectIsHitEXT(Id result_type, Id hit_object); + + /// OpHitObjectIsMissEXT + Id OpHitObjectIsMissEXT(Id result_type, Id hit_object); + + /// OpCooperativeMatrixLoadNV + Id OpCooperativeMatrixLoadNV(Id result_type, Id pointer, Id stride, Id column_major, std::optional memory_access); + + /// OpCooperativeMatrixStoreNV + void OpCooperativeMatrixStoreNV(Id pointer, Id object, Id stride, Id column_major, std::optional memory_access); + + /// OpCooperativeMatrixMulAddNV + Id OpCooperativeMatrixMulAddNV(Id result_type, Id a, Id b, Id c); + + /// OpCooperativeMatrixLengthNV + Id OpCooperativeMatrixLengthNV(Id result_type, Id type); + + /// OpCreateTensorLayoutNV + Id OpCreateTensorLayoutNV(Id result_type); + + /// OpTensorLayoutSetDimensionNV + Id OpTensorLayoutSetDimensionNV(Id result_type, Id tensor_layout, std::span dim); + + /// OpTensorLayoutSetStrideNV + Id OpTensorLayoutSetStrideNV(Id result_type, Id tensor_layout, std::span stride); + + /// OpTensorLayoutSliceNV + Id OpTensorLayoutSliceNV(Id result_type, Id tensor_layout, std::span operands); + + /// OpTensorLayoutSetClampValueNV + Id OpTensorLayoutSetClampValueNV(Id result_type, Id tensor_layout, Id value); + + /// OpCreateTensorViewNV + Id OpCreateTensorViewNV(Id result_type); + + /// OpTensorViewSetDimensionNV + Id OpTensorViewSetDimensionNV(Id result_type, Id tensor_view, std::span dim); + + /// OpTensorViewSetStrideNV + Id OpTensorViewSetStrideNV(Id result_type, Id tensor_view, std::span stride); + + /// OpTensorViewSetClipNV + Id OpTensorViewSetClipNV(Id result_type, Id tensor_view, Id clip_row_offset, Id clip_row_span, Id clip_col_offset, Id clip_col_span); + + /// OpTensorLayoutSetBlockSizeNV + Id OpTensorLayoutSetBlockSizeNV(Id result_type, Id tensor_layout, std::span block_size); + + /// OpConvertUToImageNV + Id OpConvertUToImageNV(Id result_type, Id operand); + + /// OpConvertUToSamplerNV + Id OpConvertUToSamplerNV(Id result_type, Id operand); + + /// OpConvertImageToUNV + Id OpConvertImageToUNV(Id result_type, Id operand); + + /// OpConvertSamplerToUNV + Id OpConvertSamplerToUNV(Id result_type, Id operand); + + /// OpConvertUToSampledImageNV + Id OpConvertUToSampledImageNV(Id result_type, Id operand); + + /// OpConvertSampledImageToUNV + Id OpConvertSampledImageToUNV(Id result_type, Id operand); + + /// OpSamplerImageAddressingModeNV + void OpSamplerImageAddressingModeNV(std::uint32_t bit_width); + + /// OpRayQueryGetIntersectionSpherePositionNV + Id OpRayQueryGetIntersectionSpherePositionNV(Id result_type, Id ray_query, Id intersection); + + /// OpRayQueryGetIntersectionSphereRadiusNV + Id OpRayQueryGetIntersectionSphereRadiusNV(Id result_type, Id ray_query, Id intersection); + + /// OpRayQueryGetIntersectionLSSPositionsNV + Id OpRayQueryGetIntersectionLSSPositionsNV(Id result_type, Id ray_query, Id intersection); + + /// OpRayQueryGetIntersectionLSSRadiiNV + Id OpRayQueryGetIntersectionLSSRadiiNV(Id result_type, Id ray_query, Id intersection); + + /// OpRayQueryGetIntersectionLSSHitValueNV + Id OpRayQueryGetIntersectionLSSHitValueNV(Id result_type, Id ray_query, Id intersection); + + /// OpHitObjectGetSpherePositionNV + Id OpHitObjectGetSpherePositionNV(Id result_type, Id hit_object); + + /// OpHitObjectGetSphereRadiusNV + Id OpHitObjectGetSphereRadiusNV(Id result_type, Id hit_object); + + /// OpHitObjectGetLSSPositionsNV + Id OpHitObjectGetLSSPositionsNV(Id result_type, Id hit_object); + + /// OpHitObjectGetLSSRadiiNV + Id OpHitObjectGetLSSRadiiNV(Id result_type, Id hit_object); + + /// OpHitObjectIsSphereHitNV + Id OpHitObjectIsSphereHitNV(Id result_type, Id hit_object); + + /// OpHitObjectIsLSSHitNV + Id OpHitObjectIsLSSHitNV(Id result_type, Id hit_object); + + /// OpRayQueryIsSphereHitNV + Id OpRayQueryIsSphereHitNV(Id result_type, Id ray_query, Id intersection); + + /// OpRayQueryIsLSSHitNV + Id OpRayQueryIsLSSHitNV(Id result_type, Id ray_query, Id intersection); + + + // === Type-Declaration === + /// OpTypeCooperativeMatrixKHR + Id TypeCooperativeMatrixKHR(Id component_type, Id scope, Id rows, Id columns, Id use); + + /// OpTypeHitObjectNV + Id TypeHitObjectNV(); + + /// OpTypeHitObjectEXT + Id TypeHitObjectEXT(); + + /// OpTypeCooperativeMatrixNV + Id TypeCooperativeMatrixNV(Id component_type, Id execution, Id rows, Id columns); + + /// OpTypeTensorLayoutNV + Id TypeTensorLayoutNV(Id dim, Id clamp_mode); + + /// OpTypeTensorViewNV + Id TypeTensorViewNV(Id dim, Id has_dimensions, std::span p); + + + + private: Id GetGLSLstd450(); + Id GetNonSemanticDebugPrintf(); + Id GetAmdGcnShader(); + Id GetAmdShaderTrinaryMinMax(); + Id GetAmdExplicitVertexParameter(); std::uint32_t version{}; std::uint32_t bound{}; @@ -1304,6 +2387,10 @@ class Module { std::unordered_set extensions; std::unordered_set capabilities; std::optional glsl_std_450; + std::optional non_semantic_debug_printf; + std::optional amd_gcn_shader; + std::optional amd_shader_trinary_minmax; + std::optional amd_explicit_vertex_parameter; spv::AddressingModel addressing_model{spv::AddressingModel::Logical}; spv::MemoryModel memory_model{spv::MemoryModel::GLSL450}; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 18f3e1b..3c1c7fc 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -3,24 +3,15 @@ add_library(sirit sirit.cpp stream.h common_types.h - instructions/type.cpp - instructions/constant.cpp - instructions/function.cpp + # The bulk of instruction emitters are auto-generated by + # tools/generate_instructions.py from sirit.h + the SPIR-V grammar. + # See that script's docstring for what's intentionally NOT generated. + instructions/_generated.cpp + # Hand-written: multi-statement emit chains, methods that return their + # input id (Name/MemberName), the OpDebugPrintf helper, and OpLabel. instructions/flow.cpp instructions/debug.cpp - instructions/derivatives.cpp - instructions/memory.cpp - instructions/annotation.cpp - instructions/misc.cpp - instructions/logical.cpp - instructions/conversion.cpp - instructions/bit.cpp - instructions/arithmetic.cpp instructions/extension.cpp - instructions/image.cpp - instructions/group.cpp - instructions/barrier.cpp - instructions/atomic.cpp ) target_compile_options(sirit PRIVATE ${SIRIT_CXX_FLAGS}) diff --git a/src/instructions/_generated.cpp b/src/instructions/_generated.cpp new file mode 100644 index 0000000..e20a2e9 --- /dev/null +++ b/src/instructions/_generated.cpp @@ -0,0 +1,2787 @@ +/* This file is auto-generated by tools/generate_instructions.py. + * Do not edit by hand. Regenerate after changes to the public header + * include/sirit/sirit.h or to the SPIR-V grammar files + * (externals/SPIRV-Headers/include/spirv/unified1/spirv.core.grammar.json + * and the bundled extinst.*.grammar.json files). + */ + +#include +#include +#include +#include +#include + +#include "sirit/sirit.h" + +#include "stream.h" + +namespace Sirit { + +Id Module::TypeVoid() { + declarations->Reserve(2); + return *declarations << OpId{spv::Op::OpTypeVoid} << EndOp{}; +} + +Id Module::TypeBool() { + declarations->Reserve(2); + return *declarations << OpId{spv::Op::OpTypeBool} << EndOp{}; +} + +Id Module::TypeInt(int width, bool is_signed) { + declarations->Reserve(4); + return *declarations << OpId{spv::Op::OpTypeInt} << width << is_signed << EndOp{}; +} + +Id Module::TypeFloat(int width) { + declarations->Reserve(3); + return *declarations << OpId{spv::Op::OpTypeFloat} << width << EndOp{}; +} + +Id Module::TypeVector(Id component_type, int component_count) { + declarations->Reserve(4); + return *declarations << OpId{spv::Op::OpTypeVector} << component_type << component_count << EndOp{}; +} + +Id Module::TypeMatrix(Id column_type, int column_count) { + declarations->Reserve(4); + return *declarations << OpId{spv::Op::OpTypeMatrix} << column_type << column_count << EndOp{}; +} + +Id Module::TypeImage(Id sampled_type, spv::Dim dim, int depth, bool arrayed, bool ms, int sampled, spv::ImageFormat image_format, std::optional access_qualifier) { + declarations->Reserve(9 + (access_qualifier.has_value() ? 1 : 0)); + return *declarations << OpId{spv::Op::OpTypeImage} << sampled_type << dim << depth << arrayed << ms << sampled << image_format << access_qualifier << EndOp{}; +} + +Id Module::TypeSampler() { + declarations->Reserve(2); + return *declarations << OpId{spv::Op::OpTypeSampler} << EndOp{}; +} + +Id Module::TypeSampledImage(Id image_type) { + declarations->Reserve(3); + return *declarations << OpId{spv::Op::OpTypeSampledImage} << image_type << EndOp{}; +} + +Id Module::TypeArray(Id element_type, Id length) { + declarations->Reserve(4); + return *declarations << OpId{spv::Op::OpTypeArray} << element_type << length << EndOp{}; +} + +Id Module::TypeRuntimeArray(Id element_type) { + declarations->Reserve(3); + return *declarations << OpId{spv::Op::OpTypeRuntimeArray} << element_type << EndOp{}; +} + +Id Module::TypeStruct(std::span members) { + declarations->Reserve(2 + members.size()); + return *declarations << OpId{spv::Op::OpTypeStruct} << members << EndOp{}; +} + +Id Module::TypeOpaque(std::string_view name) { + declarations->Reserve(2 + WordsInString(name)); + return *declarations << OpId{spv::Op::OpTypeOpaque} << name << EndOp{}; +} + +Id Module::TypePointer(spv::StorageClass storage_class, Id type) { + declarations->Reserve(4); + return *declarations << OpId{spv::Op::OpTypePointer} << storage_class << type << EndOp{}; +} + +Id Module::TypeFunction(Id return_type, std::span arguments) { + declarations->Reserve(3 + arguments.size()); + return *declarations << OpId{spv::Op::OpTypeFunction} << return_type << arguments << EndOp{}; +} + +Id Module::TypeEvent() { + declarations->Reserve(2); + return *declarations << OpId{spv::Op::OpTypeEvent} << EndOp{}; +} + +Id Module::TypeDeviceEvent() { + declarations->Reserve(2); + return *declarations << OpId{spv::Op::OpTypeDeviceEvent} << EndOp{}; +} + +Id Module::TypeReserveId() { + declarations->Reserve(2); + return *declarations << OpId{spv::Op::OpTypeReserveId} << EndOp{}; +} + +Id Module::TypeQueue() { + declarations->Reserve(2); + return *declarations << OpId{spv::Op::OpTypeQueue} << EndOp{}; +} + +Id Module::TypePipe(spv::AccessQualifier access_qualifier) { + declarations->Reserve(3); + return *declarations << OpId{spv::Op::OpTypePipe} << access_qualifier << EndOp{}; +} + +Id Module::ConstantTrue(Id result_type) { + declarations->Reserve(3); + return *declarations << OpId{spv::Op::OpConstantTrue, result_type} << EndOp{}; +} + +Id Module::ConstantFalse(Id result_type) { + declarations->Reserve(3); + return *declarations << OpId{spv::Op::OpConstantFalse, result_type} << EndOp{}; +} + +Id Module::Constant(Id result_type, const Literal& literal) { + declarations->Reserve(5); + return *declarations << OpId{spv::Op::OpConstant, result_type} << literal << EndOp{}; +} + +Id Module::ConstantComposite(Id result_type, std::span constituents) { + declarations->Reserve(3 + constituents.size()); + return *declarations << OpId{spv::Op::OpConstantComposite, result_type} << constituents << EndOp{}; +} + +Id Module::ConstantSampler(Id result_type, spv::SamplerAddressingMode addressing_mode, bool normalized, spv::SamplerFilterMode filter_mode) { + declarations->Reserve(6); + return *declarations << OpId{spv::Op::OpConstantSampler, result_type} << addressing_mode << normalized << filter_mode << EndOp{}; +} + +Id Module::ConstantNull(Id result_type) { + declarations->Reserve(3); + return *declarations << OpId{spv::Op::OpConstantNull, result_type} << EndOp{}; +} + +Id Module::OpFunction(Id result_type, spv::FunctionControlMask function_control, Id function_type) { + code->Reserve(5); + return *code << OpId{spv::Op::OpFunction, result_type} << function_control << function_type << EndOp{}; +} + +void Module::OpFunctionEnd() { + code->Reserve(1); + *code << spv::Op::OpFunctionEnd << EndOp{}; +} + +Id Module::OpFunctionCall(Id result_type, Id function, std::span arguments) { + code->Reserve(4 + arguments.size()); + return *code << OpId{spv::Op::OpFunctionCall, result_type} << function << arguments << EndOp{}; +} + +Id Module::OpFunctionParameter(Id result_type) { + code->Reserve(3); + return *code << OpId{spv::Op::OpFunctionParameter, result_type} << EndOp{}; +} + +Id Module::OpPhi(Id result_type, std::span operands) { + code->Reserve(3 + operands.size()); + return *code << OpId{spv::Op::OpPhi, result_type} << operands << EndOp{}; +} + +Id Module::OpLoopMerge(Id merge_block, Id continue_target, spv::LoopControlMask loop_control, std::span literals) { + code->Reserve(4 + literals.size()); + return *code << spv::Op::OpLoopMerge << merge_block << continue_target << loop_control << literals << EndOp{}; +} + +Id Module::OpSelectionMerge(Id merge_block, spv::SelectionControlMask selection_control) { + code->Reserve(3); + return *code << spv::Op::OpSelectionMerge << merge_block << selection_control << EndOp{}; +} + +Id Module::OpBranch(Id target_label) { + code->Reserve(2); + return *code << spv::Op::OpBranch << target_label << EndOp{}; +} + +void Module::OpReturn() { + code->Reserve(1); + *code << spv::Op::OpReturn << EndOp{}; +} + +void Module::OpUnreachable() { + code->Reserve(1); + *code << spv::Op::OpUnreachable << EndOp{}; +} + +Id Module::OpReturnValue(Id value) { + code->Reserve(2); + return *code << spv::Op::OpReturnValue << value << EndOp{}; +} + +void Module::OpKill() { + code->Reserve(1); + *code << spv::Op::OpKill << EndOp{}; +} + +void Module::OpDemoteToHelperInvocation() { + code->Reserve(1); + *code << spv::Op::OpDemoteToHelperInvocation << EndOp{}; +} + +void Module::OpTerminateInvocation() { + code->Reserve(1); + *code << spv::Op::OpTerminateInvocation << EndOp{}; +} + +Id Module::String(std::string_view string) { + debug->Reserve(2 + WordsInString(string)); + return *debug << OpId{spv::Op::OpString} << string << EndOp{}; +} + +Id Module::OpLine(Id file, Literal line, Literal column) { + debug->Reserve(4); + return *debug << spv::Op::OpLine << file << line << column << EndOp{}; +} + +Id Module::OpImageTexelPointer(Id result_type, Id image, Id coordinate, Id sample) { + code->Reserve(6); + return *code << OpId{spv::Op::OpImageTexelPointer, result_type} << image << coordinate << sample << EndOp{}; +} + +Id Module::OpLoad(Id result_type, Id pointer, std::optional memory_access) { + code->Reserve(4 + (memory_access.has_value() ? 1 : 0)); + return *code << OpId{spv::Op::OpLoad, result_type} << pointer << memory_access << EndOp{}; +} + +Id Module::OpStore(Id pointer, Id object, std::optional memory_access) { + code->Reserve(3 + (memory_access.has_value() ? 1 : 0)); + return *code << spv::Op::OpStore << pointer << object << memory_access << EndOp{}; +} + +Id Module::OpAccessChain(Id result_type, Id base, std::span indexes) { + code->Reserve(4 + indexes.size()); + return *code << OpId{spv::Op::OpAccessChain, result_type} << base << indexes << EndOp{}; +} + +Id Module::OpVectorExtractDynamic(Id result_type, Id vector, Id index) { + code->Reserve(5); + return *code << OpId{spv::Op::OpVectorExtractDynamic, result_type} << vector << index << EndOp{}; +} + +Id Module::OpVectorInsertDynamic(Id result_type, Id vector, Id component, Id index) { + code->Reserve(6); + return *code << OpId{spv::Op::OpVectorInsertDynamic, result_type} << vector << component << index << EndOp{}; +} + +Id Module::OpVectorShuffle(Id result_type, Id vector_1, Id vector_2, std::span components) { + code->Reserve(5 + components.size()); + return *code << OpId{spv::Op::OpVectorShuffle, result_type} << vector_1 << vector_2 << components << EndOp{}; +} + +Id Module::OpCompositeInsert(Id result_type, Id object, Id composite, std::span indexes) { + code->Reserve(5 + indexes.size()); + return *code << OpId{spv::Op::OpCompositeInsert, result_type} << object << composite << indexes << EndOp{}; +} + +Id Module::OpCompositeExtract(Id result_type, Id composite, std::span indexes) { + code->Reserve(4 + indexes.size()); + return *code << OpId{spv::Op::OpCompositeExtract, result_type} << composite << indexes << EndOp{}; +} + +Id Module::OpCompositeConstruct(Id result_type, std::span ids) { + code->Reserve(3 + ids.size()); + return *code << OpId{spv::Op::OpCompositeConstruct, result_type} << ids << EndOp{}; +} + +Id Module::Decorate(Id target, spv::Decoration decoration, std::span literals) { + annotations->Reserve(3 + literals.size()); + return *annotations << spv::Op::OpDecorate << target << decoration << literals << EndOp{}; +} + +Id Module::MemberDecorate(Id structure_type, Literal member, spv::Decoration decoration, std::span literals) { + annotations->Reserve(4 + literals.size()); + return *annotations << spv::Op::OpMemberDecorate << structure_type << member << decoration << literals << EndOp{}; +} + +Id Module::OpUndef(Id result_type) { + code->Reserve(3); + return *code << OpId{spv::Op::OpUndef, result_type} << EndOp{}; +} + +void Module::OpEmitVertex() { + code->Reserve(1); + *code << spv::Op::OpEmitVertex << EndOp{}; +} + +void Module::OpEndPrimitive() { + code->Reserve(1); + *code << spv::Op::OpEndPrimitive << EndOp{}; +} + +void Module::OpEmitStreamVertex(Id stream) { + code->Reserve(2); + *code << spv::Op::OpEmitStreamVertex << stream << EndOp{}; +} + +void Module::OpEndStreamPrimitive(Id stream) { + code->Reserve(2); + *code << spv::Op::OpEndStreamPrimitive << stream << EndOp{}; +} + +Id Module::OpControlBarrier(Id execution, Id memory, Id semantics) { + code->Reserve(4); + return *code << spv::Op::OpControlBarrier << execution << memory << semantics << EndOp{}; +} + +Id Module::OpMemoryBarrier(Id scope, Id semantics) { + code->Reserve(3); + return *code << spv::Op::OpMemoryBarrier << scope << semantics << EndOp{}; +} + +Id Module::OpAny(Id result_type, Id vector) { + code->Reserve(4); + return *code << OpId{spv::Op::OpAny, result_type} << vector << EndOp{}; +} + +Id Module::OpAll(Id result_type, Id vector) { + code->Reserve(4); + return *code << OpId{spv::Op::OpAll, result_type} << vector << EndOp{}; +} + +Id Module::OpIsNan(Id result_type, Id operand) { + code->Reserve(4); + return *code << OpId{spv::Op::OpIsNan, result_type} << operand << EndOp{}; +} + +Id Module::OpIsInf(Id result_type, Id operand) { + code->Reserve(4); + return *code << OpId{spv::Op::OpIsInf, result_type} << operand << EndOp{}; +} + +Id Module::OpLogicalEqual(Id result_type, Id operand_1, Id operand_2) { + code->Reserve(5); + return *code << OpId{spv::Op::OpLogicalEqual, result_type} << operand_1 << operand_2 << EndOp{}; +} + +Id Module::OpLogicalNotEqual(Id result_type, Id operand_1, Id operand_2) { + code->Reserve(5); + return *code << OpId{spv::Op::OpLogicalNotEqual, result_type} << operand_1 << operand_2 << EndOp{}; +} + +Id Module::OpLogicalOr(Id result_type, Id operand_1, Id operand_2) { + code->Reserve(5); + return *code << OpId{spv::Op::OpLogicalOr, result_type} << operand_1 << operand_2 << EndOp{}; +} + +Id Module::OpLogicalAnd(Id result_type, Id operand_1, Id operand_2) { + code->Reserve(5); + return *code << OpId{spv::Op::OpLogicalAnd, result_type} << operand_1 << operand_2 << EndOp{}; +} + +Id Module::OpLogicalNot(Id result_type, Id operand) { + code->Reserve(4); + return *code << OpId{spv::Op::OpLogicalNot, result_type} << operand << EndOp{}; +} + +Id Module::OpSelect(Id result_type, Id condition, Id operand_1, Id operand_2) { + code->Reserve(6); + return *code << OpId{spv::Op::OpSelect, result_type} << condition << operand_1 << operand_2 << EndOp{}; +} + +Id Module::OpIEqual(Id result_type, Id operand_1, Id operand_2) { + code->Reserve(5); + return *code << OpId{spv::Op::OpIEqual, result_type} << operand_1 << operand_2 << EndOp{}; +} + +Id Module::OpINotEqual(Id result_type, Id operand_1, Id operand_2) { + code->Reserve(5); + return *code << OpId{spv::Op::OpINotEqual, result_type} << operand_1 << operand_2 << EndOp{}; +} + +Id Module::OpUGreaterThan(Id result_type, Id operand_1, Id operand_2) { + code->Reserve(5); + return *code << OpId{spv::Op::OpUGreaterThan, result_type} << operand_1 << operand_2 << EndOp{}; +} + +Id Module::OpSGreaterThan(Id result_type, Id operand_1, Id operand_2) { + code->Reserve(5); + return *code << OpId{spv::Op::OpSGreaterThan, result_type} << operand_1 << operand_2 << EndOp{}; +} + +Id Module::OpUGreaterThanEqual(Id result_type, Id operand_1, Id operand_2) { + code->Reserve(5); + return *code << OpId{spv::Op::OpUGreaterThanEqual, result_type} << operand_1 << operand_2 << EndOp{}; +} + +Id Module::OpSGreaterThanEqual(Id result_type, Id operand_1, Id operand_2) { + code->Reserve(5); + return *code << OpId{spv::Op::OpSGreaterThanEqual, result_type} << operand_1 << operand_2 << EndOp{}; +} + +Id Module::OpULessThan(Id result_type, Id operand_1, Id operand_2) { + code->Reserve(5); + return *code << OpId{spv::Op::OpULessThan, result_type} << operand_1 << operand_2 << EndOp{}; +} + +Id Module::OpSLessThan(Id result_type, Id operand_1, Id operand_2) { + code->Reserve(5); + return *code << OpId{spv::Op::OpSLessThan, result_type} << operand_1 << operand_2 << EndOp{}; +} + +Id Module::OpULessThanEqual(Id result_type, Id operand_1, Id operand_2) { + code->Reserve(5); + return *code << OpId{spv::Op::OpULessThanEqual, result_type} << operand_1 << operand_2 << EndOp{}; +} + +Id Module::OpSLessThanEqual(Id result_type, Id operand_1, Id operand_2) { + code->Reserve(5); + return *code << OpId{spv::Op::OpSLessThanEqual, result_type} << operand_1 << operand_2 << EndOp{}; +} + +Id Module::OpFOrdEqual(Id result_type, Id operand_1, Id operand_2) { + code->Reserve(5); + return *code << OpId{spv::Op::OpFOrdEqual, result_type} << operand_1 << operand_2 << EndOp{}; +} + +Id Module::OpFUnordEqual(Id result_type, Id operand_1, Id operand_2) { + code->Reserve(5); + return *code << OpId{spv::Op::OpFUnordEqual, result_type} << operand_1 << operand_2 << EndOp{}; +} + +Id Module::OpFOrdNotEqual(Id result_type, Id operand_1, Id operand_2) { + code->Reserve(5); + return *code << OpId{spv::Op::OpFOrdNotEqual, result_type} << operand_1 << operand_2 << EndOp{}; +} + +Id Module::OpFUnordNotEqual(Id result_type, Id operand_1, Id operand_2) { + code->Reserve(5); + return *code << OpId{spv::Op::OpFUnordNotEqual, result_type} << operand_1 << operand_2 << EndOp{}; +} + +Id Module::OpFOrdLessThan(Id result_type, Id operand_1, Id operand_2) { + code->Reserve(5); + return *code << OpId{spv::Op::OpFOrdLessThan, result_type} << operand_1 << operand_2 << EndOp{}; +} + +Id Module::OpFUnordLessThan(Id result_type, Id operand_1, Id operand_2) { + code->Reserve(5); + return *code << OpId{spv::Op::OpFUnordLessThan, result_type} << operand_1 << operand_2 << EndOp{}; +} + +Id Module::OpFOrdGreaterThan(Id result_type, Id operand_1, Id operand_2) { + code->Reserve(5); + return *code << OpId{spv::Op::OpFOrdGreaterThan, result_type} << operand_1 << operand_2 << EndOp{}; +} + +Id Module::OpFUnordGreaterThan(Id result_type, Id operand_1, Id operand_2) { + code->Reserve(5); + return *code << OpId{spv::Op::OpFUnordGreaterThan, result_type} << operand_1 << operand_2 << EndOp{}; +} + +Id Module::OpFOrdLessThanEqual(Id result_type, Id operand_1, Id operand_2) { + code->Reserve(5); + return *code << OpId{spv::Op::OpFOrdLessThanEqual, result_type} << operand_1 << operand_2 << EndOp{}; +} + +Id Module::OpFUnordLessThanEqual(Id result_type, Id operand_1, Id operand_2) { + code->Reserve(5); + return *code << OpId{spv::Op::OpFUnordLessThanEqual, result_type} << operand_1 << operand_2 << EndOp{}; +} + +Id Module::OpFOrdGreaterThanEqual(Id result_type, Id operand_1, Id operand_2) { + code->Reserve(5); + return *code << OpId{spv::Op::OpFOrdGreaterThanEqual, result_type} << operand_1 << operand_2 << EndOp{}; +} + +Id Module::OpFUnordGreaterThanEqual(Id result_type, Id operand_1, Id operand_2) { + code->Reserve(5); + return *code << OpId{spv::Op::OpFUnordGreaterThanEqual, result_type} << operand_1 << operand_2 << EndOp{}; +} + +Id Module::OpConvertFToU(Id result_type, Id operand) { + code->Reserve(4); + return *code << OpId{spv::Op::OpConvertFToU, result_type} << operand << EndOp{}; +} + +Id Module::OpConvertFToS(Id result_type, Id operand) { + code->Reserve(4); + return *code << OpId{spv::Op::OpConvertFToS, result_type} << operand << EndOp{}; +} + +Id Module::OpConvertSToF(Id result_type, Id operand) { + code->Reserve(4); + return *code << OpId{spv::Op::OpConvertSToF, result_type} << operand << EndOp{}; +} + +Id Module::OpConvertUToF(Id result_type, Id operand) { + code->Reserve(4); + return *code << OpId{spv::Op::OpConvertUToF, result_type} << operand << EndOp{}; +} + +Id Module::OpConvertUToPtr(Id result_type, Id operand) { + code->Reserve(4); + return *code << OpId{spv::Op::OpConvertUToPtr, result_type} << operand << EndOp{}; +} + +Id Module::OpConvertPtrToU(Id result_type, Id operand) { + code->Reserve(4); + return *code << OpId{spv::Op::OpConvertPtrToU, result_type} << operand << EndOp{}; +} + +Id Module::OpUConvert(Id result_type, Id operand) { + code->Reserve(4); + return *code << OpId{spv::Op::OpUConvert, result_type} << operand << EndOp{}; +} + +Id Module::OpSConvert(Id result_type, Id operand) { + code->Reserve(4); + return *code << OpId{spv::Op::OpSConvert, result_type} << operand << EndOp{}; +} + +Id Module::OpFConvert(Id result_type, Id operand) { + code->Reserve(4); + return *code << OpId{spv::Op::OpFConvert, result_type} << operand << EndOp{}; +} + +Id Module::OpQuantizeToF16(Id result_type, Id operand) { + code->Reserve(4); + return *code << OpId{spv::Op::OpQuantizeToF16, result_type} << operand << EndOp{}; +} + +Id Module::OpBitcast(Id result_type, Id operand) { + code->Reserve(4); + return *code << OpId{spv::Op::OpBitcast, result_type} << operand << EndOp{}; +} + +Id Module::OpShiftRightLogical(Id result_type, Id base, Id shift) { + code->Reserve(5); + return *code << OpId{spv::Op::OpShiftRightLogical, result_type} << base << shift << EndOp{}; +} + +Id Module::OpShiftRightArithmetic(Id result_type, Id base, Id shift) { + code->Reserve(5); + return *code << OpId{spv::Op::OpShiftRightArithmetic, result_type} << base << shift << EndOp{}; +} + +Id Module::OpShiftLeftLogical(Id result_type, Id base, Id shift) { + code->Reserve(5); + return *code << OpId{spv::Op::OpShiftLeftLogical, result_type} << base << shift << EndOp{}; +} + +Id Module::OpBitwiseOr(Id result_type, Id operand_1, Id operand_2) { + code->Reserve(5); + return *code << OpId{spv::Op::OpBitwiseOr, result_type} << operand_1 << operand_2 << EndOp{}; +} + +Id Module::OpBitwiseXor(Id result_type, Id operand_1, Id operand_2) { + code->Reserve(5); + return *code << OpId{spv::Op::OpBitwiseXor, result_type} << operand_1 << operand_2 << EndOp{}; +} + +Id Module::OpBitwiseAnd(Id result_type, Id operand_1, Id operand_2) { + code->Reserve(5); + return *code << OpId{spv::Op::OpBitwiseAnd, result_type} << operand_1 << operand_2 << EndOp{}; +} + +Id Module::OpNot(Id result_type, Id operand) { + code->Reserve(4); + return *code << OpId{spv::Op::OpNot, result_type} << operand << EndOp{}; +} + +Id Module::OpBitFieldInsert(Id result_type, Id base, Id insert, Id offset, Id count) { + code->Reserve(7); + return *code << OpId{spv::Op::OpBitFieldInsert, result_type} << base << insert << offset << count << EndOp{}; +} + +Id Module::OpBitFieldSExtract(Id result_type, Id base, Id offset, Id count) { + code->Reserve(6); + return *code << OpId{spv::Op::OpBitFieldSExtract, result_type} << base << offset << count << EndOp{}; +} + +Id Module::OpBitFieldUExtract(Id result_type, Id base, Id offset, Id count) { + code->Reserve(6); + return *code << OpId{spv::Op::OpBitFieldUExtract, result_type} << base << offset << count << EndOp{}; +} + +Id Module::OpBitReverse(Id result_type, Id base) { + code->Reserve(4); + return *code << OpId{spv::Op::OpBitReverse, result_type} << base << EndOp{}; +} + +Id Module::OpBitCount(Id result_type, Id base) { + code->Reserve(4); + return *code << OpId{spv::Op::OpBitCount, result_type} << base << EndOp{}; +} + +Id Module::OpSNegate(Id result_type, Id operand) { + code->Reserve(4); + return *code << OpId{spv::Op::OpSNegate, result_type} << operand << EndOp{}; +} + +Id Module::OpFNegate(Id result_type, Id operand) { + code->Reserve(4); + return *code << OpId{spv::Op::OpFNegate, result_type} << operand << EndOp{}; +} + +Id Module::OpIAdd(Id result_type, Id operand_1, Id operand_2) { + code->Reserve(5); + return *code << OpId{spv::Op::OpIAdd, result_type} << operand_1 << operand_2 << EndOp{}; +} + +Id Module::OpFAdd(Id result_type, Id operand_1, Id operand_2) { + code->Reserve(5); + return *code << OpId{spv::Op::OpFAdd, result_type} << operand_1 << operand_2 << EndOp{}; +} + +Id Module::OpISub(Id result_type, Id operand_1, Id operand_2) { + code->Reserve(5); + return *code << OpId{spv::Op::OpISub, result_type} << operand_1 << operand_2 << EndOp{}; +} + +Id Module::OpFSub(Id result_type, Id operand_1, Id operand_2) { + code->Reserve(5); + return *code << OpId{spv::Op::OpFSub, result_type} << operand_1 << operand_2 << EndOp{}; +} + +Id Module::OpIMul(Id result_type, Id operand_1, Id operand_2) { + code->Reserve(5); + return *code << OpId{spv::Op::OpIMul, result_type} << operand_1 << operand_2 << EndOp{}; +} + +Id Module::OpUMulExtended(Id result_type, Id operand_1, Id operand_2) { + code->Reserve(5); + return *code << OpId{spv::Op::OpUMulExtended, result_type} << operand_1 << operand_2 << EndOp{}; +} + +Id Module::OpSMulExtended(Id result_type, Id operand_1, Id operand_2) { + code->Reserve(5); + return *code << OpId{spv::Op::OpSMulExtended, result_type} << operand_1 << operand_2 << EndOp{}; +} + +Id Module::OpFMul(Id result_type, Id operand_1, Id operand_2) { + code->Reserve(5); + return *code << OpId{spv::Op::OpFMul, result_type} << operand_1 << operand_2 << EndOp{}; +} + +Id Module::OpUDiv(Id result_type, Id operand_1, Id operand_2) { + code->Reserve(5); + return *code << OpId{spv::Op::OpUDiv, result_type} << operand_1 << operand_2 << EndOp{}; +} + +Id Module::OpSDiv(Id result_type, Id operand_1, Id operand_2) { + code->Reserve(5); + return *code << OpId{spv::Op::OpSDiv, result_type} << operand_1 << operand_2 << EndOp{}; +} + +Id Module::OpFDiv(Id result_type, Id operand_1, Id operand_2) { + code->Reserve(5); + return *code << OpId{spv::Op::OpFDiv, result_type} << operand_1 << operand_2 << EndOp{}; +} + +Id Module::OpUMod(Id result_type, Id operand_1, Id operand_2) { + code->Reserve(5); + return *code << OpId{spv::Op::OpUMod, result_type} << operand_1 << operand_2 << EndOp{}; +} + +Id Module::OpSMod(Id result_type, Id operand_1, Id operand_2) { + code->Reserve(5); + return *code << OpId{spv::Op::OpSMod, result_type} << operand_1 << operand_2 << EndOp{}; +} + +Id Module::OpFMod(Id result_type, Id operand_1, Id operand_2) { + code->Reserve(5); + return *code << OpId{spv::Op::OpFMod, result_type} << operand_1 << operand_2 << EndOp{}; +} + +Id Module::OpSRem(Id result_type, Id operand_1, Id operand_2) { + code->Reserve(5); + return *code << OpId{spv::Op::OpSRem, result_type} << operand_1 << operand_2 << EndOp{}; +} + +Id Module::OpFRem(Id result_type, Id operand_1, Id operand_2) { + code->Reserve(5); + return *code << OpId{spv::Op::OpFRem, result_type} << operand_1 << operand_2 << EndOp{}; +} + +Id Module::OpIAddCarry(Id result_type, Id operand_1, Id operand_2) { + code->Reserve(5); + return *code << OpId{spv::Op::OpIAddCarry, result_type} << operand_1 << operand_2 << EndOp{}; +} + +Id Module::OpVectorTimesScalar(Id result_type, Id vector, Id scalar) { + code->Reserve(5); + return *code << OpId{spv::Op::OpVectorTimesScalar, result_type} << vector << scalar << EndOp{}; +} + +Id Module::OpDot(Id result_type, Id vector_1, Id vector_2) { + code->Reserve(5); + return *code << OpId{spv::Op::OpDot, result_type} << vector_1 << vector_2 << EndOp{}; +} + +Id Module::OpExtInst(Id result_type, Id set, std::uint32_t instruction, std::span operands) { + code->Reserve(5 + operands.size()); + return *code << OpId{spv::Op::OpExtInst, result_type} << set << instruction << operands << EndOp{}; +} + +Id Module::OpFAbs(Id result_type, Id x) { + return OpExtInst(result_type, GetGLSLstd450(), GLSLstd450FAbs, x); +} + +Id Module::OpSAbs(Id result_type, Id x) { + return OpExtInst(result_type, GetGLSLstd450(), GLSLstd450SAbs, x); +} + +Id Module::OpRound(Id result_type, Id x) { + return OpExtInst(result_type, GetGLSLstd450(), GLSLstd450Round, x); +} + +Id Module::OpRoundEven(Id result_type, Id x) { + return OpExtInst(result_type, GetGLSLstd450(), GLSLstd450RoundEven, x); +} + +Id Module::OpTrunc(Id result_type, Id x) { + return OpExtInst(result_type, GetGLSLstd450(), GLSLstd450Trunc, x); +} + +Id Module::OpFSign(Id result_type, Id x) { + return OpExtInst(result_type, GetGLSLstd450(), GLSLstd450FSign, x); +} + +Id Module::OpSSign(Id result_type, Id x) { + return OpExtInst(result_type, GetGLSLstd450(), GLSLstd450SSign, x); +} + +Id Module::OpFloor(Id result_type, Id x) { + return OpExtInst(result_type, GetGLSLstd450(), GLSLstd450Floor, x); +} + +Id Module::OpCeil(Id result_type, Id x) { + return OpExtInst(result_type, GetGLSLstd450(), GLSLstd450Ceil, x); +} + +Id Module::OpFract(Id result_type, Id x) { + return OpExtInst(result_type, GetGLSLstd450(), GLSLstd450Fract, x); +} + +Id Module::OpSin(Id result_type, Id x) { + return OpExtInst(result_type, GetGLSLstd450(), GLSLstd450Sin, x); +} + +Id Module::OpCos(Id result_type, Id x) { + return OpExtInst(result_type, GetGLSLstd450(), GLSLstd450Cos, x); +} + +Id Module::OpAsin(Id result_type, Id x) { + return OpExtInst(result_type, GetGLSLstd450(), GLSLstd450Asin, x); +} + +Id Module::OpAcos(Id result_type, Id x) { + return OpExtInst(result_type, GetGLSLstd450(), GLSLstd450Acos, x); +} + +Id Module::OpPow(Id result_type, Id x, Id y) { + return OpExtInst(result_type, GetGLSLstd450(), GLSLstd450Pow, x, y); +} + +Id Module::OpExp(Id result_type, Id x) { + return OpExtInst(result_type, GetGLSLstd450(), GLSLstd450Exp, x); +} + +Id Module::OpLog(Id result_type, Id x) { + return OpExtInst(result_type, GetGLSLstd450(), GLSLstd450Log, x); +} + +Id Module::OpExp2(Id result_type, Id x) { + return OpExtInst(result_type, GetGLSLstd450(), GLSLstd450Exp2, x); +} + +Id Module::OpLog2(Id result_type, Id x) { + return OpExtInst(result_type, GetGLSLstd450(), GLSLstd450Log2, x); +} + +Id Module::OpSqrt(Id result_type, Id x) { + return OpExtInst(result_type, GetGLSLstd450(), GLSLstd450Sqrt, x); +} + +Id Module::OpInverseSqrt(Id result_type, Id x) { + return OpExtInst(result_type, GetGLSLstd450(), GLSLstd450InverseSqrt, x); +} + +Id Module::OpLdexp(Id result_type, Id x, Id exp) { + return OpExtInst(result_type, GetGLSLstd450(), GLSLstd450Ldexp, x, exp); +} + +Id Module::OpFMin(Id result_type, Id x, Id y) { + return OpExtInst(result_type, GetGLSLstd450(), GLSLstd450FMin, x, y); +} + +Id Module::OpUMin(Id result_type, Id x, Id y) { + return OpExtInst(result_type, GetGLSLstd450(), GLSLstd450UMin, x, y); +} + +Id Module::OpSMin(Id result_type, Id x, Id y) { + return OpExtInst(result_type, GetGLSLstd450(), GLSLstd450SMin, x, y); +} + +Id Module::OpNMin(Id result_type, Id x, Id y) { + return OpExtInst(result_type, GetGLSLstd450(), GLSLstd450NMin, x, y); +} + +Id Module::OpFMax(Id result_type, Id x, Id y) { + return OpExtInst(result_type, GetGLSLstd450(), GLSLstd450FMax, x, y); +} + +Id Module::OpUMax(Id result_type, Id x, Id y) { + return OpExtInst(result_type, GetGLSLstd450(), GLSLstd450UMax, x, y); +} + +Id Module::OpSMax(Id result_type, Id x, Id y) { + return OpExtInst(result_type, GetGLSLstd450(), GLSLstd450SMax, x, y); +} + +Id Module::OpNMax(Id result_type, Id x, Id y) { + return OpExtInst(result_type, GetGLSLstd450(), GLSLstd450NMax, x, y); +} + +Id Module::OpFClamp(Id result_type, Id x, Id min_val, Id max_val) { + return OpExtInst(result_type, GetGLSLstd450(), GLSLstd450FClamp, x, min_val, max_val); +} + +Id Module::OpUClamp(Id result_type, Id x, Id min_val, Id max_val) { + return OpExtInst(result_type, GetGLSLstd450(), GLSLstd450UClamp, x, min_val, max_val); +} + +Id Module::OpSClamp(Id result_type, Id x, Id min_val, Id max_val) { + return OpExtInst(result_type, GetGLSLstd450(), GLSLstd450SClamp, x, min_val, max_val); +} + +Id Module::OpFma(Id result_type, Id a, Id b, Id c) { + return OpExtInst(result_type, GetGLSLstd450(), GLSLstd450Fma, a, b, c); +} + +Id Module::OpFrexpStruct(Id result_type, Id x) { + return OpExtInst(result_type, GetGLSLstd450(), GLSLstd450FrexpStruct, x); +} + +Id Module::OpPackHalf2x16(Id result_type, Id v) { + return OpExtInst(result_type, GetGLSLstd450(), GLSLstd450PackHalf2x16, v); +} + +Id Module::OpUnpackHalf2x16(Id result_type, Id v) { + return OpExtInst(result_type, GetGLSLstd450(), GLSLstd450UnpackHalf2x16, v); +} + +Id Module::OpPackUnorm2x16(Id result_type, Id v) { + return OpExtInst(result_type, GetGLSLstd450(), GLSLstd450PackUnorm2x16, v); +} + +Id Module::OpUnpackUnorm2x16(Id result_type, Id v) { + return OpExtInst(result_type, GetGLSLstd450(), GLSLstd450UnpackUnorm2x16, v); +} + +Id Module::OpPackSnorm2x16(Id result_type, Id v) { + return OpExtInst(result_type, GetGLSLstd450(), GLSLstd450PackSnorm2x16, v); +} + +Id Module::OpUnpackSnorm2x16(Id result_type, Id v) { + return OpExtInst(result_type, GetGLSLstd450(), GLSLstd450UnpackSnorm2x16, v); +} + +Id Module::OpPackUnorm4x8(Id result_type, Id v) { + return OpExtInst(result_type, GetGLSLstd450(), GLSLstd450PackUnorm4x8, v); +} + +Id Module::OpUnpackUnorm4x8(Id result_type, Id v) { + return OpExtInst(result_type, GetGLSLstd450(), GLSLstd450UnpackUnorm4x8, v); +} + +Id Module::OpPackSnorm4x8(Id result_type, Id v) { + return OpExtInst(result_type, GetGLSLstd450(), GLSLstd450PackSnorm4x8, v); +} + +Id Module::OpUnpackSnorm4x8(Id result_type, Id v) { + return OpExtInst(result_type, GetGLSLstd450(), GLSLstd450UnpackSnorm4x8, v); +} + +Id Module::OpFindILsb(Id result_type, Id value) { + return OpExtInst(result_type, GetGLSLstd450(), GLSLstd450FindILsb, value); +} + +Id Module::OpFindSMsb(Id result_type, Id value) { + return OpExtInst(result_type, GetGLSLstd450(), GLSLstd450FindSMsb, value); +} + +Id Module::OpFindUMsb(Id result_type, Id value) { + return OpExtInst(result_type, GetGLSLstd450(), GLSLstd450FindUMsb, value); +} + +Id Module::OpInterpolateAtCentroid(Id result_type, Id interpolant) { + return OpExtInst(result_type, GetGLSLstd450(), GLSLstd450InterpolateAtCentroid, interpolant); +} + +Id Module::OpInterpolateAtSample(Id result_type, Id interpolant, Id sample) { + return OpExtInst(result_type, GetGLSLstd450(), GLSLstd450InterpolateAtSample, interpolant, sample); +} + +Id Module::OpInterpolateAtOffset(Id result_type, Id interpolant, Id offset) { + return OpExtInst(result_type, GetGLSLstd450(), GLSLstd450InterpolateAtOffset, interpolant, offset); +} + +Id Module::OpNormalize(Id result_type, Id x) { + return OpExtInst(result_type, GetGLSLstd450(), GLSLstd450Normalize, x); +} + +Id Module::OpCross(Id result_type, Id x, Id y) { + return OpExtInst(result_type, GetGLSLstd450(), GLSLstd450Cross, x, y); +} + +Id Module::OpLength(Id result_type, Id x) { + return OpExtInst(result_type, GetGLSLstd450(), GLSLstd450Length, x); +} + +Id Module::OpFMix(Id result_type, Id x, Id y, Id a) { + return OpExtInst(result_type, GetGLSLstd450(), GLSLstd450FMix, x, y, a); +} + +Id Module::OpCubeFaceCoordAMD(Id result_type, Id cube_coord) { + return OpExtInst(result_type, GetAmdGcnShader(), AMD_gcn_shaderCubeFaceCoordAMD, cube_coord); +} + +Id Module::OpCubeFaceIndexAMD(Id result_type, Id cube_coord) { + return OpExtInst(result_type, GetAmdGcnShader(), AMD_gcn_shaderCubeFaceIndexAMD, cube_coord); +} + +Id Module::OpTimeAMD(Id result_type) { + return OpExtInst(result_type, GetAmdGcnShader(), AMD_gcn_shaderTimeAMD); +} + +Id Module::OpFMin3AMD(Id result_type, Id x, Id y, Id z) { + return OpExtInst(result_type, GetAmdShaderTrinaryMinMax(), AMD_shader_trinary_minmaxFMin3AMD, x, y, z); +} + +Id Module::OpUMin3AMD(Id result_type, Id x, Id y, Id z) { + return OpExtInst(result_type, GetAmdShaderTrinaryMinMax(), AMD_shader_trinary_minmaxUMin3AMD, x, y, z); +} + +Id Module::OpSMin3AMD(Id result_type, Id x, Id y, Id z) { + return OpExtInst(result_type, GetAmdShaderTrinaryMinMax(), AMD_shader_trinary_minmaxSMin3AMD, x, y, z); +} + +Id Module::OpFMax3AMD(Id result_type, Id x, Id y, Id z) { + return OpExtInst(result_type, GetAmdShaderTrinaryMinMax(), AMD_shader_trinary_minmaxFMax3AMD, x, y, z); +} + +Id Module::OpUMax3AMD(Id result_type, Id x, Id y, Id z) { + return OpExtInst(result_type, GetAmdShaderTrinaryMinMax(), AMD_shader_trinary_minmaxUMax3AMD, x, y, z); +} + +Id Module::OpSMax3AMD(Id result_type, Id x, Id y, Id z) { + return OpExtInst(result_type, GetAmdShaderTrinaryMinMax(), AMD_shader_trinary_minmaxSMax3AMD, x, y, z); +} + +Id Module::OpFMid3AMD(Id result_type, Id x, Id y, Id z) { + return OpExtInst(result_type, GetAmdShaderTrinaryMinMax(), AMD_shader_trinary_minmaxFMid3AMD, x, y, z); +} + +Id Module::OpUMid3AMD(Id result_type, Id x, Id y, Id z) { + return OpExtInst(result_type, GetAmdShaderTrinaryMinMax(), AMD_shader_trinary_minmaxUMid3AMD, x, y, z); +} + +Id Module::OpSMid3AMD(Id result_type, Id x, Id y, Id z) { + return OpExtInst(result_type, GetAmdShaderTrinaryMinMax(), AMD_shader_trinary_minmaxSMid3AMD, x, y, z); +} + +Id Module::OpInterpolateAtVertexAMD(Id result_type, Id interpolant, Id vertex_idx) { + return OpExtInst(result_type, GetAmdExplicitVertexParameter(), AMD_shader_explicit_vertex_parameterInterpolateAtVertexAMD, interpolant, vertex_idx); +} + +Id Module::OpDPdx(Id result_type, Id operand) { + code->Reserve(4); + return *code << OpId{spv::Op::OpDPdx, result_type} << operand << EndOp{}; +} + +Id Module::OpDPdy(Id result_type, Id operand) { + code->Reserve(4); + return *code << OpId{spv::Op::OpDPdy, result_type} << operand << EndOp{}; +} + +Id Module::OpFwidth(Id result_type, Id operand) { + code->Reserve(4); + return *code << OpId{spv::Op::OpFwidth, result_type} << operand << EndOp{}; +} + +Id Module::OpDPdxFine(Id result_type, Id operand) { + code->Reserve(4); + return *code << OpId{spv::Op::OpDPdxFine, result_type} << operand << EndOp{}; +} + +Id Module::OpDPdyFine(Id result_type, Id operand) { + code->Reserve(4); + return *code << OpId{spv::Op::OpDPdyFine, result_type} << operand << EndOp{}; +} + +Id Module::OpFwidthFine(Id result_type, Id operand) { + code->Reserve(4); + return *code << OpId{spv::Op::OpFwidthFine, result_type} << operand << EndOp{}; +} + +Id Module::OpDPdxCoarse(Id result_type, Id operand) { + code->Reserve(4); + return *code << OpId{spv::Op::OpDPdxCoarse, result_type} << operand << EndOp{}; +} + +Id Module::OpDPdyCoarse(Id result_type, Id operand) { + code->Reserve(4); + return *code << OpId{spv::Op::OpDPdyCoarse, result_type} << operand << EndOp{}; +} + +Id Module::OpFwidthCoarse(Id result_type, Id operand) { + code->Reserve(4); + return *code << OpId{spv::Op::OpFwidthCoarse, result_type} << operand << EndOp{}; +} + +Id Module::OpSampledImage(Id result_type, Id image, Id sampler) { + code->Reserve(5); + return *code << OpId{spv::Op::OpSampledImage, result_type} << image << sampler << EndOp{}; +} + +Id Module::OpImageSampleImplicitLod(Id result_type, Id sampled_image, Id coordinate, std::optional image_operands, std::span operands) { + code->Reserve(5 + (image_operands.has_value() ? 1 : 0) + operands.size()); + return *code << OpId{spv::Op::OpImageSampleImplicitLod, result_type} << sampled_image << coordinate << image_operands << operands << EndOp{}; +} + +Id Module::OpImageSampleExplicitLod(Id result_type, Id sampled_image, Id coordinate, spv::ImageOperandsMask image_operands, std::span operands) { + code->Reserve(6 + operands.size()); + return *code << OpId{spv::Op::OpImageSampleExplicitLod, result_type} << sampled_image << coordinate << image_operands << operands << EndOp{}; +} + +Id Module::OpImageSampleDrefImplicitLod(Id result_type, Id sampled_image, Id coordinate, Id dref, std::optional image_operands, std::span operands) { + code->Reserve(6 + (image_operands.has_value() ? 1 : 0) + operands.size()); + return *code << OpId{spv::Op::OpImageSampleDrefImplicitLod, result_type} << sampled_image << coordinate << dref << image_operands << operands << EndOp{}; +} + +Id Module::OpImageSampleDrefExplicitLod(Id result_type, Id sampled_image, Id coordinate, Id dref, spv::ImageOperandsMask image_operands, std::span operands) { + code->Reserve(7 + operands.size()); + return *code << OpId{spv::Op::OpImageSampleDrefExplicitLod, result_type} << sampled_image << coordinate << dref << image_operands << operands << EndOp{}; +} + +Id Module::OpImageSampleProjImplicitLod(Id result_type, Id sampled_image, Id coordinate, std::optional image_operands, std::span operands) { + code->Reserve(5 + (image_operands.has_value() ? 1 : 0) + operands.size()); + return *code << OpId{spv::Op::OpImageSampleProjImplicitLod, result_type} << sampled_image << coordinate << image_operands << operands << EndOp{}; +} + +Id Module::OpImageSampleProjExplicitLod(Id result_type, Id sampled_image, Id coordinate, spv::ImageOperandsMask image_operands, std::span operands) { + code->Reserve(6 + operands.size()); + return *code << OpId{spv::Op::OpImageSampleProjExplicitLod, result_type} << sampled_image << coordinate << image_operands << operands << EndOp{}; +} + +Id Module::OpImageSampleProjDrefImplicitLod(Id result_type, Id sampled_image, Id coordinate, Id dref, std::optional image_operands, std::span operands) { + code->Reserve(6 + (image_operands.has_value() ? 1 : 0) + operands.size()); + return *code << OpId{spv::Op::OpImageSampleProjDrefImplicitLod, result_type} << sampled_image << coordinate << dref << image_operands << operands << EndOp{}; +} + +Id Module::OpImageSampleProjDrefExplicitLod(Id result_type, Id sampled_image, Id coordinate, Id dref, spv::ImageOperandsMask image_operands, std::span operands) { + code->Reserve(7 + operands.size()); + return *code << OpId{spv::Op::OpImageSampleProjDrefExplicitLod, result_type} << sampled_image << coordinate << dref << image_operands << operands << EndOp{}; +} + +Id Module::OpImageFetch(Id result_type, Id sampled_image, Id coordinate, std::optional image_operands, std::span operands) { + code->Reserve(5 + (image_operands.has_value() ? 1 : 0) + operands.size()); + return *code << OpId{spv::Op::OpImageFetch, result_type} << sampled_image << coordinate << image_operands << operands << EndOp{}; +} + +Id Module::OpImageGather(Id result_type, Id sampled_image, Id coordinate, Id component, std::optional image_operands, std::span operands) { + code->Reserve(6 + (image_operands.has_value() ? 1 : 0) + operands.size()); + return *code << OpId{spv::Op::OpImageGather, result_type} << sampled_image << coordinate << component << image_operands << operands << EndOp{}; +} + +Id Module::OpImageDrefGather(Id result_type, Id sampled_image, Id coordinate, Id dref, std::optional image_operands, std::span operands) { + code->Reserve(6 + (image_operands.has_value() ? 1 : 0) + operands.size()); + return *code << OpId{spv::Op::OpImageDrefGather, result_type} << sampled_image << coordinate << dref << image_operands << operands << EndOp{}; +} + +Id Module::OpImageRead(Id result_type, Id sampled_image, Id coordinate, std::optional image_operands, std::span operands) { + code->Reserve(5 + (image_operands.has_value() ? 1 : 0) + operands.size()); + return *code << OpId{spv::Op::OpImageRead, result_type} << sampled_image << coordinate << image_operands << operands << EndOp{}; +} + +Id Module::OpImageWrite(Id image, Id coordinate, Id texel, std::optional image_operands, std::span operands) { + code->Reserve(4 + (image_operands.has_value() ? 1 : 0) + operands.size()); + return *code << spv::Op::OpImageWrite << image << coordinate << texel << image_operands << operands << EndOp{}; +} + +Id Module::OpImage(Id result_type, Id sampled_image) { + code->Reserve(4); + return *code << OpId{spv::Op::OpImage, result_type} << sampled_image << EndOp{}; +} + +Id Module::OpImageQuerySizeLod(Id result_type, Id image, Id level_of_detail) { + code->Reserve(5); + return *code << OpId{spv::Op::OpImageQuerySizeLod, result_type} << image << level_of_detail << EndOp{}; +} + +Id Module::OpImageQuerySize(Id result_type, Id image) { + code->Reserve(4); + return *code << OpId{spv::Op::OpImageQuerySize, result_type} << image << EndOp{}; +} + +Id Module::OpImageQueryLod(Id result_type, Id image, Id coordinate) { + code->Reserve(5); + return *code << OpId{spv::Op::OpImageQueryLod, result_type} << image << coordinate << EndOp{}; +} + +Id Module::OpImageQueryLevels(Id result_type, Id image) { + code->Reserve(4); + return *code << OpId{spv::Op::OpImageQueryLevels, result_type} << image << EndOp{}; +} + +Id Module::OpImageQuerySamples(Id result_type, Id image) { + code->Reserve(4); + return *code << OpId{spv::Op::OpImageQuerySamples, result_type} << image << EndOp{}; +} + +Id Module::OpImageSparseSampleImplicitLod(Id result_type, Id sampled_image, Id coordinate, std::optional image_operands, std::span operands) { + code->Reserve(5 + (image_operands.has_value() ? 1 : 0) + operands.size()); + return *code << OpId{spv::Op::OpImageSparseSampleImplicitLod, result_type} << sampled_image << coordinate << image_operands << operands << EndOp{}; +} + +Id Module::OpImageSparseSampleExplicitLod(Id result_type, Id sampled_image, Id coordinate, spv::ImageOperandsMask image_operands, std::span operands) { + code->Reserve(6 + operands.size()); + return *code << OpId{spv::Op::OpImageSparseSampleExplicitLod, result_type} << sampled_image << coordinate << image_operands << operands << EndOp{}; +} + +Id Module::OpImageSparseSampleDrefImplicitLod(Id result_type, Id sampled_image, Id coordinate, Id dref, std::optional image_operands, std::span operands) { + code->Reserve(6 + (image_operands.has_value() ? 1 : 0) + operands.size()); + return *code << OpId{spv::Op::OpImageSparseSampleDrefImplicitLod, result_type} << sampled_image << coordinate << dref << image_operands << operands << EndOp{}; +} + +Id Module::OpImageSparseSampleDrefExplicitLod(Id result_type, Id sampled_image, Id coordinate, Id dref, spv::ImageOperandsMask image_operands, std::span operands) { + code->Reserve(7 + operands.size()); + return *code << OpId{spv::Op::OpImageSparseSampleDrefExplicitLod, result_type} << sampled_image << coordinate << dref << image_operands << operands << EndOp{}; +} + +Id Module::OpImageSparseFetch(Id result_type, Id image, Id coordinate, std::optional image_operands, std::span operands) { + code->Reserve(5 + (image_operands.has_value() ? 1 : 0) + operands.size()); + return *code << OpId{spv::Op::OpImageSparseFetch, result_type} << image << coordinate << image_operands << operands << EndOp{}; +} + +Id Module::OpImageSparseGather(Id result_type, Id sampled_image, Id coordinate, Id component, std::optional image_operands, std::span operands) { + code->Reserve(6 + (image_operands.has_value() ? 1 : 0) + operands.size()); + return *code << OpId{spv::Op::OpImageSparseGather, result_type} << sampled_image << coordinate << component << image_operands << operands << EndOp{}; +} + +Id Module::OpImageSparseDrefGather(Id result_type, Id sampled_image, Id coordinate, Id dref, std::optional image_operands, std::span operands) { + code->Reserve(6 + (image_operands.has_value() ? 1 : 0) + operands.size()); + return *code << OpId{spv::Op::OpImageSparseDrefGather, result_type} << sampled_image << coordinate << dref << image_operands << operands << EndOp{}; +} + +Id Module::OpImageSparseTexelsResident(Id result_type, Id resident_code) { + code->Reserve(4); + return *code << OpId{spv::Op::OpImageSparseTexelsResident, result_type} << resident_code << EndOp{}; +} + +Id Module::OpImageSparseRead(Id result_type, Id image, Id coordinate, std::optional image_operands, std::span operands) { + code->Reserve(5 + (image_operands.has_value() ? 1 : 0) + operands.size()); + return *code << OpId{spv::Op::OpImageSparseRead, result_type} << image << coordinate << image_operands << operands << EndOp{}; +} + +Id Module::OpSubgroupBallotKHR(Id result_type, Id predicate) { + code->Reserve(4); + return *code << OpId{spv::Op::OpSubgroupBallotKHR, result_type} << predicate << EndOp{}; +} + +Id Module::OpSubgroupReadInvocationKHR(Id result_type, Id value, Id index) { + code->Reserve(5); + return *code << OpId{spv::Op::OpSubgroupReadInvocationKHR, result_type} << value << index << EndOp{}; +} + +Id Module::OpSubgroupAllKHR(Id result_type, Id predicate) { + code->Reserve(4); + return *code << OpId{spv::Op::OpSubgroupAllKHR, result_type} << predicate << EndOp{}; +} + +Id Module::OpSubgroupAnyKHR(Id result_type, Id predicate) { + code->Reserve(4); + return *code << OpId{spv::Op::OpSubgroupAnyKHR, result_type} << predicate << EndOp{}; +} + +Id Module::OpSubgroupAllEqualKHR(Id result_type, Id predicate) { + code->Reserve(4); + return *code << OpId{spv::Op::OpSubgroupAllEqualKHR, result_type} << predicate << EndOp{}; +} + +Id Module::OpGroupNonUniformBroadcast(Id result_type, Id scope, Id value, Id id) { + code->Reserve(6); + return *code << OpId{spv::Op::OpGroupNonUniformBroadcast, result_type} << scope << value << id << EndOp{}; +} + +Id Module::OpGroupNonUniformShuffle(Id result_type, Id scope, Id value, Id id) { + code->Reserve(6); + return *code << OpId{spv::Op::OpGroupNonUniformShuffle, result_type} << scope << value << id << EndOp{}; +} + +Id Module::OpGroupNonUniformShuffleXor(Id result_type, Id scope, Id value, Id mask) { + code->Reserve(6); + return *code << OpId{spv::Op::OpGroupNonUniformShuffleXor, result_type} << scope << value << mask << EndOp{}; +} + +Id Module::OpGroupNonUniformAll(Id result_type, Id scope, Id predicate) { + code->Reserve(5); + return *code << OpId{spv::Op::OpGroupNonUniformAll, result_type} << scope << predicate << EndOp{}; +} + +Id Module::OpGroupNonUniformAny(Id result_type, Id scope, Id predicate) { + code->Reserve(5); + return *code << OpId{spv::Op::OpGroupNonUniformAny, result_type} << scope << predicate << EndOp{}; +} + +Id Module::OpGroupNonUniformAllEqual(Id result_type, Id scope, Id value) { + code->Reserve(5); + return *code << OpId{spv::Op::OpGroupNonUniformAllEqual, result_type} << scope << value << EndOp{}; +} + +Id Module::OpGroupNonUniformBallot(Id result_type, Id scope, Id predicate) { + code->Reserve(5); + return *code << OpId{spv::Op::OpGroupNonUniformBallot, result_type} << scope << predicate << EndOp{}; +} + +Id Module::OpGroupNonUniformBroadcastFirst(Id result_type, Id scope, Id value) { + code->Reserve(5); + return *code << OpId{spv::Op::OpGroupNonUniformBroadcastFirst, result_type} << scope << value << EndOp{}; +} + +Id Module::OpGroupNonUniformElect(Id result_type, Id scope) { + code->Reserve(4); + return *code << OpId{spv::Op::OpGroupNonUniformElect, result_type} << scope << EndOp{}; +} + +Id Module::OpGroupNonUniformInverseBallot(Id result_type, Id scope, Id value) { + code->Reserve(5); + return *code << OpId{spv::Op::OpGroupNonUniformInverseBallot, result_type} << scope << value << EndOp{}; +} + +Id Module::OpGroupNonUniformBallotBitCount(Id result_type, Id scope, spv::GroupOperation group_op, Id value) { + code->Reserve(6); + return *code << OpId{spv::Op::OpGroupNonUniformBallotBitCount, result_type} << scope << group_op << value << EndOp{}; +} + +Id Module::OpGroupNonUniformBallotFindLSB(Id result_type, Id scope, Id value) { + code->Reserve(5); + return *code << OpId{spv::Op::OpGroupNonUniformBallotFindLSB, result_type} << scope << value << EndOp{}; +} + +Id Module::OpGroupNonUniformQuadBroadcast(Id result_type, Id scope, Id value, Id index) { + code->Reserve(6); + return *code << OpId{spv::Op::OpGroupNonUniformQuadBroadcast, result_type} << scope << value << index << EndOp{}; +} + +Id Module::OpAtomicLoad(Id result_type, Id pointer, Id memory, Id semantics) { + code->Reserve(6); + return *code << OpId{spv::Op::OpAtomicLoad, result_type} << pointer << memory << semantics << EndOp{}; +} + +Id Module::OpAtomicStore(Id pointer, Id memory, Id semantics, Id value) { + code->Reserve(5); + return *code << spv::Op::OpAtomicStore << pointer << memory << semantics << value << EndOp{}; +} + +Id Module::OpAtomicExchange(Id result_type, Id pointer, Id memory, Id semantics, Id value) { + code->Reserve(7); + return *code << OpId{spv::Op::OpAtomicExchange, result_type} << pointer << memory << semantics << value << EndOp{}; +} + +Id Module::OpAtomicCompareExchange(Id result_type, Id pointer, Id memory, Id equal, Id unequal, Id value, Id comparator) { + code->Reserve(9); + return *code << OpId{spv::Op::OpAtomicCompareExchange, result_type} << pointer << memory << equal << unequal << value << comparator << EndOp{}; +} + +Id Module::OpAtomicIIncrement(Id result_type, Id pointer, Id memory, Id semantics) { + code->Reserve(6); + return *code << OpId{spv::Op::OpAtomicIIncrement, result_type} << pointer << memory << semantics << EndOp{}; +} + +Id Module::OpAtomicIDecrement(Id result_type, Id pointer, Id memory, Id semantics) { + code->Reserve(6); + return *code << OpId{spv::Op::OpAtomicIDecrement, result_type} << pointer << memory << semantics << EndOp{}; +} + +Id Module::OpAtomicIAdd(Id result_type, Id pointer, Id memory, Id semantics, Id value) { + code->Reserve(7); + return *code << OpId{spv::Op::OpAtomicIAdd, result_type} << pointer << memory << semantics << value << EndOp{}; +} + +Id Module::OpAtomicISub(Id result_type, Id pointer, Id memory, Id semantics, Id value) { + code->Reserve(7); + return *code << OpId{spv::Op::OpAtomicISub, result_type} << pointer << memory << semantics << value << EndOp{}; +} + +Id Module::OpAtomicSMin(Id result_type, Id pointer, Id memory, Id semantics, Id value) { + code->Reserve(7); + return *code << OpId{spv::Op::OpAtomicSMin, result_type} << pointer << memory << semantics << value << EndOp{}; +} + +Id Module::OpAtomicUMin(Id result_type, Id pointer, Id memory, Id semantics, Id value) { + code->Reserve(7); + return *code << OpId{spv::Op::OpAtomicUMin, result_type} << pointer << memory << semantics << value << EndOp{}; +} + +Id Module::OpAtomicSMax(Id result_type, Id pointer, Id memory, Id semantics, Id value) { + code->Reserve(7); + return *code << OpId{spv::Op::OpAtomicSMax, result_type} << pointer << memory << semantics << value << EndOp{}; +} + +Id Module::OpAtomicUMax(Id result_type, Id pointer, Id memory, Id semantics, Id value) { + code->Reserve(7); + return *code << OpId{spv::Op::OpAtomicUMax, result_type} << pointer << memory << semantics << value << EndOp{}; +} + +Id Module::OpAtomicFMax(Id result_type, Id pointer, Id memory, Id semantics, Id value) { + code->Reserve(7); + return *code << OpId{spv::Op::OpAtomicFMaxEXT, result_type} << pointer << memory << semantics << value << EndOp{}; +} + +Id Module::OpAtomicFMin(Id result_type, Id pointer, Id memory, Id semantics, Id value) { + code->Reserve(7); + return *code << OpId{spv::Op::OpAtomicFMinEXT, result_type} << pointer << memory << semantics << value << EndOp{}; +} + +Id Module::OpAtomicAnd(Id result_type, Id pointer, Id memory, Id semantics, Id value) { + code->Reserve(7); + return *code << OpId{spv::Op::OpAtomicAnd, result_type} << pointer << memory << semantics << value << EndOp{}; +} + +Id Module::OpAtomicOr(Id result_type, Id pointer, Id memory, Id semantics, Id value) { + code->Reserve(7); + return *code << OpId{spv::Op::OpAtomicOr, result_type} << pointer << memory << semantics << value << EndOp{}; +} + +Id Module::OpAtomicXor(Id result_type, Id pointer, Id memory, Id semantics, Id value) { + code->Reserve(7); + return *code << OpId{spv::Op::OpAtomicXor, result_type} << pointer << memory << semantics << value << EndOp{}; +} + +Id Module::DecorationGroup() { + annotations->Reserve(2); + return *annotations << OpId{spv::Op::OpDecorationGroup} << EndOp{}; +} + +Id Module::GroupDecorate(Id decoration_group, std::span targets) { + annotations->Reserve(2 + targets.size()); + return *annotations << spv::Op::OpGroupDecorate << decoration_group << targets << EndOp{}; +} + +Id Module::OpMatrixTimesScalar(Id result_type, Id matrix, Id scalar) { + code->Reserve(5); + return *code << OpId{spv::Op::OpMatrixTimesScalar, result_type} << matrix << scalar << EndOp{}; +} + +Id Module::OpVectorTimesMatrix(Id result_type, Id vector, Id matrix) { + code->Reserve(5); + return *code << OpId{spv::Op::OpVectorTimesMatrix, result_type} << vector << matrix << EndOp{}; +} + +Id Module::OpMatrixTimesVector(Id result_type, Id matrix, Id vector) { + code->Reserve(5); + return *code << OpId{spv::Op::OpMatrixTimesVector, result_type} << matrix << vector << EndOp{}; +} + +Id Module::OpMatrixTimesMatrix(Id result_type, Id left_matrix, Id right_matrix) { + code->Reserve(5); + return *code << OpId{spv::Op::OpMatrixTimesMatrix, result_type} << left_matrix << right_matrix << EndOp{}; +} + +Id Module::OpOuterProduct(Id result_type, Id vector_1, Id vector_2) { + code->Reserve(5); + return *code << OpId{spv::Op::OpOuterProduct, result_type} << vector_1 << vector_2 << EndOp{}; +} + +Id Module::OpISubBorrow(Id result_type, Id operand_1, Id operand_2) { + code->Reserve(5); + return *code << OpId{spv::Op::OpISubBorrow, result_type} << operand_1 << operand_2 << EndOp{}; +} + +Id Module::OpFmaKHR(Id result_type, Id operand_1, Id operand_2, Id operand_3) { + code->Reserve(6); + return *code << OpId{spv::Op::OpFmaKHR, result_type} << operand_1 << operand_2 << operand_3 << EndOp{}; +} + +Id Module::OpSDot(Id result_type, Id vector_1, Id vector_2, std::optional packed_vector_format) { + code->Reserve(5 + (packed_vector_format.has_value() ? 1 : 0)); + return *code << OpId{spv::Op::OpSDot, result_type} << vector_1 << vector_2 << packed_vector_format << EndOp{}; +} + +Id Module::OpUDot(Id result_type, Id vector_1, Id vector_2, std::optional packed_vector_format) { + code->Reserve(5 + (packed_vector_format.has_value() ? 1 : 0)); + return *code << OpId{spv::Op::OpUDot, result_type} << vector_1 << vector_2 << packed_vector_format << EndOp{}; +} + +Id Module::OpSUDot(Id result_type, Id vector_1, Id vector_2, std::optional packed_vector_format) { + code->Reserve(5 + (packed_vector_format.has_value() ? 1 : 0)); + return *code << OpId{spv::Op::OpSUDot, result_type} << vector_1 << vector_2 << packed_vector_format << EndOp{}; +} + +Id Module::OpSDotAccSat(Id result_type, Id vector_1, Id vector_2, Id accumulator, std::optional packed_vector_format) { + code->Reserve(6 + (packed_vector_format.has_value() ? 1 : 0)); + return *code << OpId{spv::Op::OpSDotAccSat, result_type} << vector_1 << vector_2 << accumulator << packed_vector_format << EndOp{}; +} + +Id Module::OpUDotAccSat(Id result_type, Id vector_1, Id vector_2, Id accumulator, std::optional packed_vector_format) { + code->Reserve(6 + (packed_vector_format.has_value() ? 1 : 0)); + return *code << OpId{spv::Op::OpUDotAccSat, result_type} << vector_1 << vector_2 << accumulator << packed_vector_format << EndOp{}; +} + +Id Module::OpSUDotAccSat(Id result_type, Id vector_1, Id vector_2, Id accumulator, std::optional packed_vector_format) { + code->Reserve(6 + (packed_vector_format.has_value() ? 1 : 0)); + return *code << OpId{spv::Op::OpSUDotAccSat, result_type} << vector_1 << vector_2 << accumulator << packed_vector_format << EndOp{}; +} + +Id Module::OpAtomicCompareExchangeWeak(Id result_type, Id pointer, Id memory, Id equal, Id unequal, Id value, Id comparator) { + code->Reserve(9); + return *code << OpId{spv::Op::OpAtomicCompareExchangeWeak, result_type} << pointer << memory << equal << unequal << value << comparator << EndOp{}; +} + +Id Module::OpAtomicFAddEXT(Id result_type, Id pointer, Id memory, Id semantics, Id value) { + code->Reserve(7); + return *code << OpId{spv::Op::OpAtomicFAddEXT, result_type} << pointer << memory << semantics << value << EndOp{}; +} + +Id Module::OpNamedBarrierInitialize(Id result_type, Id subgroup_count) { + code->Reserve(4); + return *code << OpId{spv::Op::OpNamedBarrierInitialize, result_type} << subgroup_count << EndOp{}; +} + +void Module::OpMemoryNamedBarrier(Id named_barrier, Id memory, Id semantics) { + code->Reserve(4); + *code << spv::Op::OpMemoryNamedBarrier << named_barrier << memory << semantics << EndOp{}; +} + +Id Module::OpCopyObject(Id result_type, Id operand) { + code->Reserve(4); + return *code << OpId{spv::Op::OpCopyObject, result_type} << operand << EndOp{}; +} + +Id Module::OpTranspose(Id result_type, Id matrix) { + code->Reserve(4); + return *code << OpId{spv::Op::OpTranspose, result_type} << matrix << EndOp{}; +} + +Id Module::OpCopyLogical(Id result_type, Id operand) { + code->Reserve(4); + return *code << OpId{spv::Op::OpCopyLogical, result_type} << operand << EndOp{}; +} + +Id Module::SpecConstantTrue(Id result_type) { + declarations->Reserve(3); + return *declarations << OpId{spv::Op::OpSpecConstantTrue, result_type} << EndOp{}; +} + +Id Module::SpecConstantFalse(Id result_type) { + declarations->Reserve(3); + return *declarations << OpId{spv::Op::OpSpecConstantFalse, result_type} << EndOp{}; +} + +Id Module::SpecConstant(Id result_type, Literal value) { + declarations->Reserve(5); + return *declarations << OpId{spv::Op::OpSpecConstant, result_type} << value << EndOp{}; +} + +Id Module::SpecConstantComposite(Id result_type, std::span constituents) { + declarations->Reserve(3 + constituents.size()); + return *declarations << OpId{spv::Op::OpSpecConstantComposite, result_type} << constituents << EndOp{}; +} + +void Module::OpLifetimeStart(Id pointer, std::uint32_t size) { + code->Reserve(3); + *code << spv::Op::OpLifetimeStart << pointer << size << EndOp{}; +} + +void Module::OpLifetimeStop(Id pointer, std::uint32_t size) { + code->Reserve(3); + *code << spv::Op::OpLifetimeStop << pointer << size << EndOp{}; +} + +void Module::SourceContinued(std::string_view continued_source) { + debug->Reserve(1 + WordsInString(continued_source)); + *debug << spv::Op::OpSourceContinued << continued_source << EndOp{}; +} + +void Module::Source(spv::SourceLanguage source_language, std::uint32_t source_version, std::optional file, std::optional source) { + debug->Reserve(3 + (file.has_value() ? 1 : 0) + (source.has_value() ? 1 : 0)); + *debug << spv::Op::OpSource << source_language << source_version << file << source << EndOp{}; +} + +void Module::SourceExtension(std::string_view extension) { + debug->Reserve(1 + WordsInString(extension)); + *debug << spv::Op::OpSourceExtension << extension << EndOp{}; +} + +void Module::NoLine() { + debug->Reserve(1); + *debug << spv::Op::OpNoLine << EndOp{}; +} + +void Module::ModuleProcessed(std::string_view process) { + debug->Reserve(1 + WordsInString(process)); + *debug << spv::Op::OpModuleProcessed << process << EndOp{}; +} + +Id Module::OpGroupAsyncCopy(Id result_type, Id execution, Id destination, Id source, Id num_elements, Id stride, Id event) { + code->Reserve(9); + return *code << OpId{spv::Op::OpGroupAsyncCopy, result_type} << execution << destination << source << num_elements << stride << event << EndOp{}; +} + +void Module::OpGroupWaitEvents(Id execution, Id num_events, Id events_list) { + code->Reserve(4); + *code << spv::Op::OpGroupWaitEvents << execution << num_events << events_list << EndOp{}; +} + +Id Module::OpGroupAll(Id result_type, Id execution, Id predicate) { + code->Reserve(5); + return *code << OpId{spv::Op::OpGroupAll, result_type} << execution << predicate << EndOp{}; +} + +Id Module::OpGroupAny(Id result_type, Id execution, Id predicate) { + code->Reserve(5); + return *code << OpId{spv::Op::OpGroupAny, result_type} << execution << predicate << EndOp{}; +} + +Id Module::OpGroupBroadcast(Id result_type, Id execution, Id value, Id local_id) { + code->Reserve(6); + return *code << OpId{spv::Op::OpGroupBroadcast, result_type} << execution << value << local_id << EndOp{}; +} + +Id Module::OpGroupIAdd(Id result_type, Id execution, spv::GroupOperation operation, Id x) { + code->Reserve(6); + return *code << OpId{spv::Op::OpGroupIAdd, result_type} << execution << operation << x << EndOp{}; +} + +Id Module::OpGroupFAdd(Id result_type, Id execution, spv::GroupOperation operation, Id x) { + code->Reserve(6); + return *code << OpId{spv::Op::OpGroupFAdd, result_type} << execution << operation << x << EndOp{}; +} + +Id Module::OpGroupFMin(Id result_type, Id execution, spv::GroupOperation operation, Id x) { + code->Reserve(6); + return *code << OpId{spv::Op::OpGroupFMin, result_type} << execution << operation << x << EndOp{}; +} + +Id Module::OpGroupUMin(Id result_type, Id execution, spv::GroupOperation operation, Id x) { + code->Reserve(6); + return *code << OpId{spv::Op::OpGroupUMin, result_type} << execution << operation << x << EndOp{}; +} + +Id Module::OpGroupSMin(Id result_type, Id execution, spv::GroupOperation operation, Id x) { + code->Reserve(6); + return *code << OpId{spv::Op::OpGroupSMin, result_type} << execution << operation << x << EndOp{}; +} + +Id Module::OpGroupFMax(Id result_type, Id execution, spv::GroupOperation operation, Id x) { + code->Reserve(6); + return *code << OpId{spv::Op::OpGroupFMax, result_type} << execution << operation << x << EndOp{}; +} + +Id Module::OpGroupUMax(Id result_type, Id execution, spv::GroupOperation operation, Id x) { + code->Reserve(6); + return *code << OpId{spv::Op::OpGroupUMax, result_type} << execution << operation << x << EndOp{}; +} + +Id Module::OpGroupSMax(Id result_type, Id execution, spv::GroupOperation operation, Id x) { + code->Reserve(6); + return *code << OpId{spv::Op::OpGroupSMax, result_type} << execution << operation << x << EndOp{}; +} + +Id Module::OpSubgroupFirstInvocationKHR(Id result_type, Id value) { + code->Reserve(4); + return *code << OpId{spv::Op::OpSubgroupFirstInvocationKHR, result_type} << value << EndOp{}; +} + +Id Module::OpGroupNonUniformRotateKHR(Id result_type, Id execution, Id value, Id delta, std::optional cluster_size) { + code->Reserve(6 + (cluster_size.has_value() ? 1 : 0)); + return *code << OpId{spv::Op::OpGroupNonUniformRotateKHR, result_type} << execution << value << delta << cluster_size << EndOp{}; +} + +Id Module::OpUntypedGroupAsyncCopyKHR(Id result_type, Id execution, Id destination, Id source, Id element_num_bytes, Id num_elements, Id stride, Id event, std::optional destination_memory_operands, std::optional source_memory_operands) { + code->Reserve(10 + (destination_memory_operands.has_value() ? 1 : 0) + (source_memory_operands.has_value() ? 1 : 0)); + return *code << OpId{spv::Op::OpUntypedGroupAsyncCopyKHR, result_type} << execution << destination << source << element_num_bytes << num_elements << stride << event << destination_memory_operands << source_memory_operands << EndOp{}; +} + +Id Module::OpGroupIMulKHR(Id result_type, Id execution, spv::GroupOperation operation, Id x) { + code->Reserve(6); + return *code << OpId{spv::Op::OpGroupIMulKHR, result_type} << execution << operation << x << EndOp{}; +} + +Id Module::OpGroupFMulKHR(Id result_type, Id execution, spv::GroupOperation operation, Id x) { + code->Reserve(6); + return *code << OpId{spv::Op::OpGroupFMulKHR, result_type} << execution << operation << x << EndOp{}; +} + +Id Module::OpGroupBitwiseAndKHR(Id result_type, Id execution, spv::GroupOperation operation, Id x) { + code->Reserve(6); + return *code << OpId{spv::Op::OpGroupBitwiseAndKHR, result_type} << execution << operation << x << EndOp{}; +} + +Id Module::OpGroupBitwiseOrKHR(Id result_type, Id execution, spv::GroupOperation operation, Id x) { + code->Reserve(6); + return *code << OpId{spv::Op::OpGroupBitwiseOrKHR, result_type} << execution << operation << x << EndOp{}; +} + +Id Module::OpGroupBitwiseXorKHR(Id result_type, Id execution, spv::GroupOperation operation, Id x) { + code->Reserve(6); + return *code << OpId{spv::Op::OpGroupBitwiseXorKHR, result_type} << execution << operation << x << EndOp{}; +} + +Id Module::OpGroupLogicalAndKHR(Id result_type, Id execution, spv::GroupOperation operation, Id x) { + code->Reserve(6); + return *code << OpId{spv::Op::OpGroupLogicalAndKHR, result_type} << execution << operation << x << EndOp{}; +} + +Id Module::OpGroupLogicalOrKHR(Id result_type, Id execution, spv::GroupOperation operation, Id x) { + code->Reserve(6); + return *code << OpId{spv::Op::OpGroupLogicalOrKHR, result_type} << execution << operation << x << EndOp{}; +} + +Id Module::OpGroupLogicalXorKHR(Id result_type, Id execution, spv::GroupOperation operation, Id x) { + code->Reserve(6); + return *code << OpId{spv::Op::OpGroupLogicalXorKHR, result_type} << execution << operation << x << EndOp{}; +} + +Id Module::OpImageQueryFormat(Id result_type, Id image) { + code->Reserve(4); + return *code << OpId{spv::Op::OpImageQueryFormat, result_type} << image << EndOp{}; +} + +Id Module::OpImageQueryOrder(Id result_type, Id image) { + code->Reserve(4); + return *code << OpId{spv::Op::OpImageQueryOrder, result_type} << image << EndOp{}; +} + +Id Module::OpImageSparseSampleProjImplicitLod(Id result_type, Id sampled_image, Id coordinate, std::optional image_operands) { + code->Reserve(5 + (image_operands.has_value() ? 1 : 0)); + return *code << OpId{spv::Op::OpImageSparseSampleProjImplicitLod, result_type} << sampled_image << coordinate << image_operands << EndOp{}; +} + +Id Module::OpImageSparseSampleProjExplicitLod(Id result_type, Id sampled_image, Id coordinate, spv::ImageOperandsMask image_operands) { + code->Reserve(6); + return *code << OpId{spv::Op::OpImageSparseSampleProjExplicitLod, result_type} << sampled_image << coordinate << image_operands << EndOp{}; +} + +Id Module::OpImageSparseSampleProjDrefImplicitLod(Id result_type, Id sampled_image, Id coordinate, Id dref, std::optional image_operands) { + code->Reserve(6 + (image_operands.has_value() ? 1 : 0)); + return *code << OpId{spv::Op::OpImageSparseSampleProjDrefImplicitLod, result_type} << sampled_image << coordinate << dref << image_operands << EndOp{}; +} + +Id Module::OpImageSparseSampleProjDrefExplicitLod(Id result_type, Id sampled_image, Id coordinate, Id dref, spv::ImageOperandsMask image_operands) { + code->Reserve(7); + return *code << OpId{spv::Op::OpImageSparseSampleProjDrefExplicitLod, result_type} << sampled_image << coordinate << dref << image_operands << EndOp{}; +} + +Id Module::OpColorAttachmentReadEXT(Id result_type, Id attachment, std::optional sample) { + code->Reserve(4 + (sample.has_value() ? 1 : 0)); + return *code << OpId{spv::Op::OpColorAttachmentReadEXT, result_type} << attachment << sample << EndOp{}; +} + +Id Module::OpDepthAttachmentReadEXT(Id result_type, std::optional sample) { + code->Reserve(3 + (sample.has_value() ? 1 : 0)); + return *code << OpId{spv::Op::OpDepthAttachmentReadEXT, result_type} << sample << EndOp{}; +} + +Id Module::OpStencilAttachmentReadEXT(Id result_type, std::optional sample) { + code->Reserve(3 + (sample.has_value() ? 1 : 0)); + return *code << OpId{spv::Op::OpStencilAttachmentReadEXT, result_type} << sample << EndOp{}; +} + +void Module::OpCopyMemory(Id target, Id source, std::optional memory_access, std::optional memory_access_2) { + code->Reserve(3 + (memory_access.has_value() ? 1 : 0) + (memory_access_2.has_value() ? 1 : 0)); + *code << spv::Op::OpCopyMemory << target << source << memory_access << memory_access_2 << EndOp{}; +} + +void Module::OpCopyMemorySized(Id target, Id source, Id size, std::optional memory_access, std::optional memory_access_2) { + code->Reserve(4 + (memory_access.has_value() ? 1 : 0) + (memory_access_2.has_value() ? 1 : 0)); + *code << spv::Op::OpCopyMemorySized << target << source << size << memory_access << memory_access_2 << EndOp{}; +} + +Id Module::OpInBoundsAccessChain(Id result_type, Id base, std::span indexes) { + code->Reserve(4 + indexes.size()); + return *code << OpId{spv::Op::OpInBoundsAccessChain, result_type} << base << indexes << EndOp{}; +} + +Id Module::OpPtrAccessChain(Id result_type, Id base, Id element, std::span indexes) { + code->Reserve(5 + indexes.size()); + return *code << OpId{spv::Op::OpPtrAccessChain, result_type} << base << element << indexes << EndOp{}; +} + +Id Module::OpArrayLength(Id result_type, Id structure, std::uint32_t array_member) { + code->Reserve(5); + return *code << OpId{spv::Op::OpArrayLength, result_type} << structure << array_member << EndOp{}; +} + +Id Module::OpInBoundsPtrAccessChain(Id result_type, Id base, Id element, std::span indexes) { + code->Reserve(5 + indexes.size()); + return *code << OpId{spv::Op::OpInBoundsPtrAccessChain, result_type} << base << element << indexes << EndOp{}; +} + +Id Module::OpPtrEqual(Id result_type, Id operand_1, Id operand_2) { + code->Reserve(5); + return *code << OpId{spv::Op::OpPtrEqual, result_type} << operand_1 << operand_2 << EndOp{}; +} + +Id Module::OpPtrNotEqual(Id result_type, Id operand_1, Id operand_2) { + code->Reserve(5); + return *code << OpId{spv::Op::OpPtrNotEqual, result_type} << operand_1 << operand_2 << EndOp{}; +} + +Id Module::OpPtrDiff(Id result_type, Id operand_1, Id operand_2) { + code->Reserve(5); + return *code << OpId{spv::Op::OpPtrDiff, result_type} << operand_1 << operand_2 << EndOp{}; +} + +void Module::OpNop() { + code->Reserve(1); + *code << spv::Op::OpNop << EndOp{}; +} + +Id Module::OpSizeOf(Id result_type, Id pointer) { + code->Reserve(4); + return *code << OpId{spv::Op::OpSizeOf, result_type} << pointer << EndOp{}; +} + +Id Module::OpPoisonKHR(Id result_type) { + code->Reserve(3); + return *code << OpId{spv::Op::OpPoisonKHR, result_type} << EndOp{}; +} + +Id Module::OpFreezeKHR(Id result_type, Id value) { + code->Reserve(4); + return *code << OpId{spv::Op::OpFreezeKHR, result_type} << value << EndOp{}; +} + +void Module::OpAssumeTrueKHR(Id condition) { + code->Reserve(2); + *code << spv::Op::OpAssumeTrueKHR << condition << EndOp{}; +} + +Id Module::OpExpectKHR(Id result_type, Id value, Id expected_value) { + code->Reserve(5); + return *code << OpId{spv::Op::OpExpectKHR, result_type} << value << expected_value << EndOp{}; +} + +Id Module::OpArithmeticFenceEXT(Id result_type, Id target) { + code->Reserve(4); + return *code << OpId{spv::Op::OpArithmeticFenceEXT, result_type} << target << EndOp{}; +} + +Id Module::OpGroupNonUniformBallotBitExtract(Id result_type, Id execution, Id value, Id index) { + code->Reserve(6); + return *code << OpId{spv::Op::OpGroupNonUniformBallotBitExtract, result_type} << execution << value << index << EndOp{}; +} + +Id Module::OpGroupNonUniformBallotFindMSB(Id result_type, Id execution, Id value) { + code->Reserve(5); + return *code << OpId{spv::Op::OpGroupNonUniformBallotFindMSB, result_type} << execution << value << EndOp{}; +} + +Id Module::OpGroupNonUniformShuffleUp(Id result_type, Id execution, Id value, Id delta) { + code->Reserve(6); + return *code << OpId{spv::Op::OpGroupNonUniformShuffleUp, result_type} << execution << value << delta << EndOp{}; +} + +Id Module::OpGroupNonUniformShuffleDown(Id result_type, Id execution, Id value, Id delta) { + code->Reserve(6); + return *code << OpId{spv::Op::OpGroupNonUniformShuffleDown, result_type} << execution << value << delta << EndOp{}; +} + +Id Module::OpGroupNonUniformIAdd(Id result_type, Id execution, spv::GroupOperation operation, Id value, std::optional cluster_size) { + code->Reserve(6 + (cluster_size.has_value() ? 1 : 0)); + return *code << OpId{spv::Op::OpGroupNonUniformIAdd, result_type} << execution << operation << value << cluster_size << EndOp{}; +} + +Id Module::OpGroupNonUniformFAdd(Id result_type, Id execution, spv::GroupOperation operation, Id value, std::optional cluster_size) { + code->Reserve(6 + (cluster_size.has_value() ? 1 : 0)); + return *code << OpId{spv::Op::OpGroupNonUniformFAdd, result_type} << execution << operation << value << cluster_size << EndOp{}; +} + +Id Module::OpGroupNonUniformIMul(Id result_type, Id execution, spv::GroupOperation operation, Id value, std::optional cluster_size) { + code->Reserve(6 + (cluster_size.has_value() ? 1 : 0)); + return *code << OpId{spv::Op::OpGroupNonUniformIMul, result_type} << execution << operation << value << cluster_size << EndOp{}; +} + +Id Module::OpGroupNonUniformFMul(Id result_type, Id execution, spv::GroupOperation operation, Id value, std::optional cluster_size) { + code->Reserve(6 + (cluster_size.has_value() ? 1 : 0)); + return *code << OpId{spv::Op::OpGroupNonUniformFMul, result_type} << execution << operation << value << cluster_size << EndOp{}; +} + +Id Module::OpGroupNonUniformSMin(Id result_type, Id execution, spv::GroupOperation operation, Id value, std::optional cluster_size) { + code->Reserve(6 + (cluster_size.has_value() ? 1 : 0)); + return *code << OpId{spv::Op::OpGroupNonUniformSMin, result_type} << execution << operation << value << cluster_size << EndOp{}; +} + +Id Module::OpGroupNonUniformUMin(Id result_type, Id execution, spv::GroupOperation operation, Id value, std::optional cluster_size) { + code->Reserve(6 + (cluster_size.has_value() ? 1 : 0)); + return *code << OpId{spv::Op::OpGroupNonUniformUMin, result_type} << execution << operation << value << cluster_size << EndOp{}; +} + +Id Module::OpGroupNonUniformFMin(Id result_type, Id execution, spv::GroupOperation operation, Id value, std::optional cluster_size) { + code->Reserve(6 + (cluster_size.has_value() ? 1 : 0)); + return *code << OpId{spv::Op::OpGroupNonUniformFMin, result_type} << execution << operation << value << cluster_size << EndOp{}; +} + +Id Module::OpGroupNonUniformSMax(Id result_type, Id execution, spv::GroupOperation operation, Id value, std::optional cluster_size) { + code->Reserve(6 + (cluster_size.has_value() ? 1 : 0)); + return *code << OpId{spv::Op::OpGroupNonUniformSMax, result_type} << execution << operation << value << cluster_size << EndOp{}; +} + +Id Module::OpGroupNonUniformUMax(Id result_type, Id execution, spv::GroupOperation operation, Id value, std::optional cluster_size) { + code->Reserve(6 + (cluster_size.has_value() ? 1 : 0)); + return *code << OpId{spv::Op::OpGroupNonUniformUMax, result_type} << execution << operation << value << cluster_size << EndOp{}; +} + +Id Module::OpGroupNonUniformFMax(Id result_type, Id execution, spv::GroupOperation operation, Id value, std::optional cluster_size) { + code->Reserve(6 + (cluster_size.has_value() ? 1 : 0)); + return *code << OpId{spv::Op::OpGroupNonUniformFMax, result_type} << execution << operation << value << cluster_size << EndOp{}; +} + +Id Module::OpGroupNonUniformBitwiseAnd(Id result_type, Id execution, spv::GroupOperation operation, Id value, std::optional cluster_size) { + code->Reserve(6 + (cluster_size.has_value() ? 1 : 0)); + return *code << OpId{spv::Op::OpGroupNonUniformBitwiseAnd, result_type} << execution << operation << value << cluster_size << EndOp{}; +} + +Id Module::OpGroupNonUniformBitwiseOr(Id result_type, Id execution, spv::GroupOperation operation, Id value, std::optional cluster_size) { + code->Reserve(6 + (cluster_size.has_value() ? 1 : 0)); + return *code << OpId{spv::Op::OpGroupNonUniformBitwiseOr, result_type} << execution << operation << value << cluster_size << EndOp{}; +} + +Id Module::OpGroupNonUniformBitwiseXor(Id result_type, Id execution, spv::GroupOperation operation, Id value, std::optional cluster_size) { + code->Reserve(6 + (cluster_size.has_value() ? 1 : 0)); + return *code << OpId{spv::Op::OpGroupNonUniformBitwiseXor, result_type} << execution << operation << value << cluster_size << EndOp{}; +} + +Id Module::OpGroupNonUniformLogicalAnd(Id result_type, Id execution, spv::GroupOperation operation, Id value, std::optional cluster_size) { + code->Reserve(6 + (cluster_size.has_value() ? 1 : 0)); + return *code << OpId{spv::Op::OpGroupNonUniformLogicalAnd, result_type} << execution << operation << value << cluster_size << EndOp{}; +} + +Id Module::OpGroupNonUniformLogicalOr(Id result_type, Id execution, spv::GroupOperation operation, Id value, std::optional cluster_size) { + code->Reserve(6 + (cluster_size.has_value() ? 1 : 0)); + return *code << OpId{spv::Op::OpGroupNonUniformLogicalOr, result_type} << execution << operation << value << cluster_size << EndOp{}; +} + +Id Module::OpGroupNonUniformLogicalXor(Id result_type, Id execution, spv::GroupOperation operation, Id value, std::optional cluster_size) { + code->Reserve(6 + (cluster_size.has_value() ? 1 : 0)); + return *code << OpId{spv::Op::OpGroupNonUniformLogicalXor, result_type} << execution << operation << value << cluster_size << EndOp{}; +} + +Id Module::OpGroupNonUniformQuadSwap(Id result_type, Id execution, Id value, Id direction) { + code->Reserve(6); + return *code << OpId{spv::Op::OpGroupNonUniformQuadSwap, result_type} << execution << value << direction << EndOp{}; +} + +Id Module::OpGroupNonUniformQuadAllKHR(Id result_type, Id predicate) { + code->Reserve(4); + return *code << OpId{spv::Op::OpGroupNonUniformQuadAllKHR, result_type} << predicate << EndOp{}; +} + +Id Module::OpGroupNonUniformQuadAnyKHR(Id result_type, Id predicate) { + code->Reserve(4); + return *code << OpId{spv::Op::OpGroupNonUniformQuadAnyKHR, result_type} << predicate << EndOp{}; +} + +Id Module::OpGroupNonUniformPartitionEXT(Id result_type, Id value) { + code->Reserve(4); + return *code << OpId{spv::Op::OpGroupNonUniformPartitionEXT, result_type} << value << EndOp{}; +} + +Id Module::OpIsFinite(Id result_type, Id x) { + code->Reserve(4); + return *code << OpId{spv::Op::OpIsFinite, result_type} << x << EndOp{}; +} + +Id Module::OpIsNormal(Id result_type, Id x) { + code->Reserve(4); + return *code << OpId{spv::Op::OpIsNormal, result_type} << x << EndOp{}; +} + +Id Module::OpSignBitSet(Id result_type, Id x) { + code->Reserve(4); + return *code << OpId{spv::Op::OpSignBitSet, result_type} << x << EndOp{}; +} + +Id Module::OpLessOrGreater(Id result_type, Id x, Id y) { + code->Reserve(5); + return *code << OpId{spv::Op::OpLessOrGreater, result_type} << x << y << EndOp{}; +} + +Id Module::OpOrdered(Id result_type, Id x, Id y) { + code->Reserve(5); + return *code << OpId{spv::Op::OpOrdered, result_type} << x << y << EndOp{}; +} + +Id Module::OpUnordered(Id result_type, Id x, Id y) { + code->Reserve(5); + return *code << OpId{spv::Op::OpUnordered, result_type} << x << y << EndOp{}; +} + +void Module::OpTraceRayKHR(Id accel, Id ray_flags, Id cull_mask, Id sbt_offset, Id sbt_stride, Id miss_index, Id ray_origin, Id ray_tmin, Id ray_direction, Id ray_tmax, Id payload) { + code->Reserve(12); + *code << spv::Op::OpTraceRayKHR << accel << ray_flags << cull_mask << sbt_offset << sbt_stride << miss_index << ray_origin << ray_tmin << ray_direction << ray_tmax << payload << EndOp{}; +} + +void Module::OpExecuteCallableKHR(Id sbt_index, Id callable_data) { + code->Reserve(3); + *code << spv::Op::OpExecuteCallableKHR << sbt_index << callable_data << EndOp{}; +} + +Id Module::OpConvertUToAccelerationStructureKHR(Id result_type, Id accel) { + code->Reserve(4); + return *code << OpId{spv::Op::OpConvertUToAccelerationStructureKHR, result_type} << accel << EndOp{}; +} + +void Module::OpIgnoreIntersectionKHR() { + code->Reserve(1); + *code << spv::Op::OpIgnoreIntersectionKHR << EndOp{}; +} + +void Module::OpTerminateRayKHR() { + code->Reserve(1); + *code << spv::Op::OpTerminateRayKHR << EndOp{}; +} + +void Module::OpRayQueryInitializeKHR(Id ray_query, Id accel, Id ray_flags, Id cull_mask, Id ray_origin, Id ray_t_min, Id ray_direction, Id ray_t_max) { + code->Reserve(9); + *code << spv::Op::OpRayQueryInitializeKHR << ray_query << accel << ray_flags << cull_mask << ray_origin << ray_t_min << ray_direction << ray_t_max << EndOp{}; +} + +void Module::OpRayQueryTerminateKHR(Id ray_query) { + code->Reserve(2); + *code << spv::Op::OpRayQueryTerminateKHR << ray_query << EndOp{}; +} + +void Module::OpRayQueryGenerateIntersectionKHR(Id ray_query, Id hit_t) { + code->Reserve(3); + *code << spv::Op::OpRayQueryGenerateIntersectionKHR << ray_query << hit_t << EndOp{}; +} + +void Module::OpRayQueryConfirmIntersectionKHR(Id ray_query) { + code->Reserve(2); + *code << spv::Op::OpRayQueryConfirmIntersectionKHR << ray_query << EndOp{}; +} + +Id Module::OpRayQueryProceedKHR(Id result_type, Id ray_query) { + code->Reserve(4); + return *code << OpId{spv::Op::OpRayQueryProceedKHR, result_type} << ray_query << EndOp{}; +} + +Id Module::OpRayQueryGetIntersectionTypeKHR(Id result_type, Id ray_query, Id intersection) { + code->Reserve(5); + return *code << OpId{spv::Op::OpRayQueryGetIntersectionTypeKHR, result_type} << ray_query << intersection << EndOp{}; +} + +Id Module::OpReadClockKHR(Id result_type, Id scope) { + code->Reserve(4); + return *code << OpId{spv::Op::OpReadClockKHR, result_type} << scope << EndOp{}; +} + +void Module::OpEmitMeshTasksEXT(Id group_count_x, Id group_count_y, Id group_count_z, std::optional payload) { + code->Reserve(4 + (payload.has_value() ? 1 : 0)); + *code << spv::Op::OpEmitMeshTasksEXT << group_count_x << group_count_y << group_count_z << payload << EndOp{}; +} + +void Module::OpSetMeshOutputsEXT(Id vertex_count, Id primitive_count) { + code->Reserve(3); + *code << spv::Op::OpSetMeshOutputsEXT << vertex_count << primitive_count << EndOp{}; +} + +Id Module::OpReportIntersectionKHR(Id result_type, Id hit, Id hit_kind) { + code->Reserve(5); + return *code << OpId{spv::Op::OpReportIntersectionKHR, result_type} << hit << hit_kind << EndOp{}; +} + +Id Module::OpRayQueryGetIntersectionTriangleVertexPositionsKHR(Id result_type, Id ray_query, Id intersection) { + code->Reserve(5); + return *code << OpId{spv::Op::OpRayQueryGetIntersectionTriangleVertexPositionsKHR, result_type} << ray_query << intersection << EndOp{}; +} + +void Module::OpBeginInvocationInterlockEXT() { + code->Reserve(1); + *code << spv::Op::OpBeginInvocationInterlockEXT << EndOp{}; +} + +void Module::OpEndInvocationInterlockEXT() { + code->Reserve(1); + *code << spv::Op::OpEndInvocationInterlockEXT << EndOp{}; +} + +Id Module::OpIsHelperInvocationEXT(Id result_type) { + code->Reserve(3); + return *code << OpId{spv::Op::OpIsHelperInvocationEXT, result_type} << EndOp{}; +} + +Id Module::OpRayQueryGetRayTMinKHR(Id result_type, Id ray_query) { + code->Reserve(4); + return *code << OpId{spv::Op::OpRayQueryGetRayTMinKHR, result_type} << ray_query << EndOp{}; +} + +Id Module::OpRayQueryGetRayFlagsKHR(Id result_type, Id ray_query) { + code->Reserve(4); + return *code << OpId{spv::Op::OpRayQueryGetRayFlagsKHR, result_type} << ray_query << EndOp{}; +} + +Id Module::OpRayQueryGetIntersectionTKHR(Id result_type, Id ray_query, Id intersection) { + code->Reserve(5); + return *code << OpId{spv::Op::OpRayQueryGetIntersectionTKHR, result_type} << ray_query << intersection << EndOp{}; +} + +Id Module::OpRayQueryGetIntersectionInstanceCustomIndexKHR(Id result_type, Id ray_query, Id intersection) { + code->Reserve(5); + return *code << OpId{spv::Op::OpRayQueryGetIntersectionInstanceCustomIndexKHR, result_type} << ray_query << intersection << EndOp{}; +} + +Id Module::OpRayQueryGetIntersectionInstanceIdKHR(Id result_type, Id ray_query, Id intersection) { + code->Reserve(5); + return *code << OpId{spv::Op::OpRayQueryGetIntersectionInstanceIdKHR, result_type} << ray_query << intersection << EndOp{}; +} + +Id Module::OpRayQueryGetIntersectionInstanceShaderBindingTableRecordOffsetKHR(Id result_type, Id ray_query, Id intersection) { + code->Reserve(5); + return *code << OpId{spv::Op::OpRayQueryGetIntersectionInstanceShaderBindingTableRecordOffsetKHR, result_type} << ray_query << intersection << EndOp{}; +} + +Id Module::OpRayQueryGetIntersectionGeometryIndexKHR(Id result_type, Id ray_query, Id intersection) { + code->Reserve(5); + return *code << OpId{spv::Op::OpRayQueryGetIntersectionGeometryIndexKHR, result_type} << ray_query << intersection << EndOp{}; +} + +Id Module::OpRayQueryGetIntersectionPrimitiveIndexKHR(Id result_type, Id ray_query, Id intersection) { + code->Reserve(5); + return *code << OpId{spv::Op::OpRayQueryGetIntersectionPrimitiveIndexKHR, result_type} << ray_query << intersection << EndOp{}; +} + +Id Module::OpRayQueryGetIntersectionBarycentricsKHR(Id result_type, Id ray_query, Id intersection) { + code->Reserve(5); + return *code << OpId{spv::Op::OpRayQueryGetIntersectionBarycentricsKHR, result_type} << ray_query << intersection << EndOp{}; +} + +Id Module::OpRayQueryGetIntersectionFrontFaceKHR(Id result_type, Id ray_query, Id intersection) { + code->Reserve(5); + return *code << OpId{spv::Op::OpRayQueryGetIntersectionFrontFaceKHR, result_type} << ray_query << intersection << EndOp{}; +} + +Id Module::OpRayQueryGetIntersectionCandidateAABBOpaqueKHR(Id result_type, Id ray_query) { + code->Reserve(4); + return *code << OpId{spv::Op::OpRayQueryGetIntersectionCandidateAABBOpaqueKHR, result_type} << ray_query << EndOp{}; +} + +Id Module::OpRayQueryGetIntersectionObjectRayDirectionKHR(Id result_type, Id ray_query, Id intersection) { + code->Reserve(5); + return *code << OpId{spv::Op::OpRayQueryGetIntersectionObjectRayDirectionKHR, result_type} << ray_query << intersection << EndOp{}; +} + +Id Module::OpRayQueryGetIntersectionObjectRayOriginKHR(Id result_type, Id ray_query, Id intersection) { + code->Reserve(5); + return *code << OpId{spv::Op::OpRayQueryGetIntersectionObjectRayOriginKHR, result_type} << ray_query << intersection << EndOp{}; +} + +Id Module::OpRayQueryGetWorldRayDirectionKHR(Id result_type, Id ray_query) { + code->Reserve(4); + return *code << OpId{spv::Op::OpRayQueryGetWorldRayDirectionKHR, result_type} << ray_query << EndOp{}; +} + +Id Module::OpRayQueryGetWorldRayOriginKHR(Id result_type, Id ray_query) { + code->Reserve(4); + return *code << OpId{spv::Op::OpRayQueryGetWorldRayOriginKHR, result_type} << ray_query << EndOp{}; +} + +Id Module::OpRayQueryGetIntersectionObjectToWorldKHR(Id result_type, Id ray_query, Id intersection) { + code->Reserve(5); + return *code << OpId{spv::Op::OpRayQueryGetIntersectionObjectToWorldKHR, result_type} << ray_query << intersection << EndOp{}; +} + +Id Module::OpRayQueryGetIntersectionWorldToObjectKHR(Id result_type, Id ray_query, Id intersection) { + code->Reserve(5); + return *code << OpId{spv::Op::OpRayQueryGetIntersectionWorldToObjectKHR, result_type} << ray_query << intersection << EndOp{}; +} + +Id Module::TypePipeStorage() { + declarations->Reserve(2); + return *declarations << OpId{spv::Op::OpTypePipeStorage} << EndOp{}; +} + +Id Module::TypeNamedBarrier() { + declarations->Reserve(2); + return *declarations << OpId{spv::Op::OpTypeNamedBarrier} << EndOp{}; +} + +Id Module::TypeRayQueryKHR() { + declarations->Reserve(2); + return *declarations << OpId{spv::Op::OpTypeRayQueryKHR} << EndOp{}; +} + +Id Module::TypeAccelerationStructureKHR() { + declarations->Reserve(2); + return *declarations << OpId{spv::Op::OpTypeAccelerationStructureKHR} << EndOp{}; +} + +Id Module::OpCooperativeMatrixMulAddKHR(Id result_type, Id a, Id b, Id c, std::optional cooperative_matrix_operands) { + code->Reserve(6 + (cooperative_matrix_operands.has_value() ? 1 : 0)); + return *code << OpId{spv::Op::OpCooperativeMatrixMulAddKHR, result_type} << a << b << c << cooperative_matrix_operands << EndOp{}; +} + +Id Module::OpCooperativeMatrixReduceNV(Id result_type, Id matrix, spv::CooperativeMatrixReduceMask reduce, Id combine_func) { + code->Reserve(6); + return *code << OpId{spv::Op::OpCooperativeMatrixReduceNV, result_type} << matrix << reduce << combine_func << EndOp{}; +} + +Id Module::OpCooperativeMatrixConvertNV(Id result_type, Id matrix) { + code->Reserve(4); + return *code << OpId{spv::Op::OpCooperativeMatrixConvertNV, result_type} << matrix << EndOp{}; +} + +Id Module::OpCooperativeMatrixTransposeNV(Id result_type, Id matrix) { + code->Reserve(4); + return *code << OpId{spv::Op::OpCooperativeMatrixTransposeNV, result_type} << matrix << EndOp{}; +} + +Id Module::OpCooperativeMatrixPerElementOpNV(Id result_type, Id matrix, Id func, std::span operands) { + code->Reserve(5 + operands.size()); + return *code << OpId{spv::Op::OpCooperativeMatrixPerElementOpNV, result_type} << matrix << func << operands << EndOp{}; +} + +Id Module::OpImageSampleFootprintNV(Id result_type, Id sampled_image, Id coordinate, Id granularity, Id coarse, std::optional image_operands) { + code->Reserve(7 + (image_operands.has_value() ? 1 : 0)); + return *code << OpId{spv::Op::OpImageSampleFootprintNV, result_type} << sampled_image << coordinate << granularity << coarse << image_operands << EndOp{}; +} + +Id Module::OpCooperativeMatrixLoadKHR(Id result_type, Id pointer, Id memory_layout, std::optional stride, std::optional memory_operand) { + code->Reserve(5 + (stride.has_value() ? 1 : 0) + (memory_operand.has_value() ? 1 : 0)); + return *code << OpId{spv::Op::OpCooperativeMatrixLoadKHR, result_type} << pointer << memory_layout << stride << memory_operand << EndOp{}; +} + +void Module::OpCooperativeMatrixStoreKHR(Id pointer, Id object, Id memory_layout, std::optional stride, std::optional memory_operand) { + code->Reserve(4 + (stride.has_value() ? 1 : 0) + (memory_operand.has_value() ? 1 : 0)); + *code << spv::Op::OpCooperativeMatrixStoreKHR << pointer << object << memory_layout << stride << memory_operand << EndOp{}; +} + +Id Module::OpCooperativeVectorLoadNV(Id result_type, Id pointer, Id offset, std::optional memory_access) { + code->Reserve(5 + (memory_access.has_value() ? 1 : 0)); + return *code << OpId{spv::Op::OpCooperativeVectorLoadNV, result_type} << pointer << offset << memory_access << EndOp{}; +} + +void Module::OpCooperativeVectorStoreNV(Id pointer, Id offset, Id object, std::optional memory_access) { + code->Reserve(4 + (memory_access.has_value() ? 1 : 0)); + *code << spv::Op::OpCooperativeVectorStoreNV << pointer << offset << object << memory_access << EndOp{}; +} + +Id Module::OpCooperativeMatrixLoadTensorNV(Id result_type, Id pointer, Id object, Id tensor_layout, spv::MemoryAccessMask memory_operand, spv::TensorAddressingOperandsMask tensor_addressing_operands) { + code->Reserve(8); + return *code << OpId{spv::Op::OpCooperativeMatrixLoadTensorNV, result_type} << pointer << object << tensor_layout << memory_operand << tensor_addressing_operands << EndOp{}; +} + +void Module::OpCooperativeMatrixStoreTensorNV(Id pointer, Id object, Id tensor_layout, spv::MemoryAccessMask memory_operand, spv::TensorAddressingOperandsMask tensor_addressing_operands) { + code->Reserve(6); + *code << spv::Op::OpCooperativeMatrixStoreTensorNV << pointer << object << tensor_layout << memory_operand << tensor_addressing_operands << EndOp{}; +} + +Id Module::OpRawAccessChainNV(Id result_type, Id base, Id byte_stride, Id element_index, Id byte_offset, std::optional raw_access_chain_operands) { + code->Reserve(7 + (raw_access_chain_operands.has_value() ? 1 : 0)); + return *code << OpId{spv::Op::OpRawAccessChainNV, result_type} << base << byte_stride << element_index << byte_offset << raw_access_chain_operands << EndOp{}; +} + +Id Module::OpCooperativeMatrixLengthKHR(Id result_type, Id type) { + code->Reserve(4); + return *code << OpId{spv::Op::OpCooperativeMatrixLengthKHR, result_type} << type << EndOp{}; +} + +void Module::OpHitObjectRecordHitMotionNV(Id hit_object, Id acceleration_structure, Id instance_id, Id primitive_id, Id geometry_index, Id hit_kind, Id sbt_record_offset, Id sbt_record_stride, Id origin, Id t_min, Id direction, Id t_max, Id current_time, Id hit_object_attributes) { + code->Reserve(15); + *code << spv::Op::OpHitObjectRecordHitMotionNV << hit_object << acceleration_structure << instance_id << primitive_id << geometry_index << hit_kind << sbt_record_offset << sbt_record_stride << origin << t_min << direction << t_max << current_time << hit_object_attributes << EndOp{}; +} + +void Module::OpHitObjectRecordHitWithIndexMotionNV(Id hit_object, Id acceleration_structure, Id instance_id, Id primitive_id, Id geometry_index, Id hit_kind, Id sbt_record_index, Id origin, Id t_min, Id direction, Id t_max, Id current_time, Id hit_object_attributes) { + code->Reserve(14); + *code << spv::Op::OpHitObjectRecordHitWithIndexMotionNV << hit_object << acceleration_structure << instance_id << primitive_id << geometry_index << hit_kind << sbt_record_index << origin << t_min << direction << t_max << current_time << hit_object_attributes << EndOp{}; +} + +void Module::OpHitObjectRecordMissMotionNV(Id hit_object, Id sbt_index, Id origin, Id t_min, Id direction, Id t_max, Id current_time) { + code->Reserve(8); + *code << spv::Op::OpHitObjectRecordMissMotionNV << hit_object << sbt_index << origin << t_min << direction << t_max << current_time << EndOp{}; +} + +Id Module::OpHitObjectGetWorldToObjectNV(Id result_type, Id hit_object) { + code->Reserve(4); + return *code << OpId{spv::Op::OpHitObjectGetWorldToObjectNV, result_type} << hit_object << EndOp{}; +} + +Id Module::OpHitObjectGetObjectToWorldNV(Id result_type, Id hit_object) { + code->Reserve(4); + return *code << OpId{spv::Op::OpHitObjectGetObjectToWorldNV, result_type} << hit_object << EndOp{}; +} + +Id Module::OpHitObjectGetObjectRayDirectionNV(Id result_type, Id hit_object) { + code->Reserve(4); + return *code << OpId{spv::Op::OpHitObjectGetObjectRayDirectionNV, result_type} << hit_object << EndOp{}; +} + +Id Module::OpHitObjectGetObjectRayOriginNV(Id result_type, Id hit_object) { + code->Reserve(4); + return *code << OpId{spv::Op::OpHitObjectGetObjectRayOriginNV, result_type} << hit_object << EndOp{}; +} + +void Module::OpHitObjectTraceRayMotionNV(Id hit_object, Id acceleration_structure, Id ray_flags, Id cullmask, Id sbt_record_offset, Id sbt_record_stride, Id miss_index, Id origin, Id t_min, Id direction, Id t_max, Id time, Id payload) { + code->Reserve(14); + *code << spv::Op::OpHitObjectTraceRayMotionNV << hit_object << acceleration_structure << ray_flags << cullmask << sbt_record_offset << sbt_record_stride << miss_index << origin << t_min << direction << t_max << time << payload << EndOp{}; +} + +Id Module::OpHitObjectGetShaderRecordBufferHandleNV(Id result_type, Id hit_object) { + code->Reserve(4); + return *code << OpId{spv::Op::OpHitObjectGetShaderRecordBufferHandleNV, result_type} << hit_object << EndOp{}; +} + +Id Module::OpHitObjectGetShaderBindingTableRecordIndexNV(Id result_type, Id hit_object) { + code->Reserve(4); + return *code << OpId{spv::Op::OpHitObjectGetShaderBindingTableRecordIndexNV, result_type} << hit_object << EndOp{}; +} + +void Module::OpHitObjectRecordEmptyNV(Id hit_object) { + code->Reserve(2); + *code << spv::Op::OpHitObjectRecordEmptyNV << hit_object << EndOp{}; +} + +void Module::OpHitObjectTraceRayNV(Id hit_object, Id acceleration_structure, Id ray_flags, Id cullmask, Id sbt_record_offset, Id sbt_record_stride, Id miss_index, Id origin, Id t_min, Id direction, Id t_max, Id payload) { + code->Reserve(13); + *code << spv::Op::OpHitObjectTraceRayNV << hit_object << acceleration_structure << ray_flags << cullmask << sbt_record_offset << sbt_record_stride << miss_index << origin << t_min << direction << t_max << payload << EndOp{}; +} + +void Module::OpHitObjectRecordHitNV(Id hit_object, Id acceleration_structure, Id instance_id, Id primitive_id, Id geometry_index, Id hit_kind, Id sbt_record_offset, Id sbt_record_stride, Id origin, Id t_min, Id direction, Id t_max, Id hit_object_attributes) { + code->Reserve(14); + *code << spv::Op::OpHitObjectRecordHitNV << hit_object << acceleration_structure << instance_id << primitive_id << geometry_index << hit_kind << sbt_record_offset << sbt_record_stride << origin << t_min << direction << t_max << hit_object_attributes << EndOp{}; +} + +void Module::OpHitObjectRecordHitWithIndexNV(Id hit_object, Id acceleration_structure, Id instance_id, Id primitive_id, Id geometry_index, Id hit_kind, Id sbt_record_index, Id origin, Id t_min, Id direction, Id t_max, Id hit_object_attributes) { + code->Reserve(13); + *code << spv::Op::OpHitObjectRecordHitWithIndexNV << hit_object << acceleration_structure << instance_id << primitive_id << geometry_index << hit_kind << sbt_record_index << origin << t_min << direction << t_max << hit_object_attributes << EndOp{}; +} + +void Module::OpHitObjectRecordMissNV(Id hit_object, Id sbt_index, Id origin, Id t_min, Id direction, Id t_max) { + code->Reserve(7); + *code << spv::Op::OpHitObjectRecordMissNV << hit_object << sbt_index << origin << t_min << direction << t_max << EndOp{}; +} + +void Module::OpHitObjectExecuteShaderNV(Id hit_object, Id payload) { + code->Reserve(3); + *code << spv::Op::OpHitObjectExecuteShaderNV << hit_object << payload << EndOp{}; +} + +Id Module::OpHitObjectGetCurrentTimeNV(Id result_type, Id hit_object) { + code->Reserve(4); + return *code << OpId{spv::Op::OpHitObjectGetCurrentTimeNV, result_type} << hit_object << EndOp{}; +} + +void Module::OpHitObjectGetAttributesNV(Id hit_object, Id hit_object_attribute) { + code->Reserve(3); + *code << spv::Op::OpHitObjectGetAttributesNV << hit_object << hit_object_attribute << EndOp{}; +} + +Id Module::OpHitObjectGetHitKindNV(Id result_type, Id hit_object) { + code->Reserve(4); + return *code << OpId{spv::Op::OpHitObjectGetHitKindNV, result_type} << hit_object << EndOp{}; +} + +Id Module::OpHitObjectGetPrimitiveIndexNV(Id result_type, Id hit_object) { + code->Reserve(4); + return *code << OpId{spv::Op::OpHitObjectGetPrimitiveIndexNV, result_type} << hit_object << EndOp{}; +} + +Id Module::OpHitObjectGetGeometryIndexNV(Id result_type, Id hit_object) { + code->Reserve(4); + return *code << OpId{spv::Op::OpHitObjectGetGeometryIndexNV, result_type} << hit_object << EndOp{}; +} + +Id Module::OpHitObjectGetInstanceIdNV(Id result_type, Id hit_object) { + code->Reserve(4); + return *code << OpId{spv::Op::OpHitObjectGetInstanceIdNV, result_type} << hit_object << EndOp{}; +} + +Id Module::OpHitObjectGetInstanceCustomIndexNV(Id result_type, Id hit_object) { + code->Reserve(4); + return *code << OpId{spv::Op::OpHitObjectGetInstanceCustomIndexNV, result_type} << hit_object << EndOp{}; +} + +Id Module::OpHitObjectGetWorldRayDirectionNV(Id result_type, Id hit_object) { + code->Reserve(4); + return *code << OpId{spv::Op::OpHitObjectGetWorldRayDirectionNV, result_type} << hit_object << EndOp{}; +} + +Id Module::OpHitObjectGetWorldRayOriginNV(Id result_type, Id hit_object) { + code->Reserve(4); + return *code << OpId{spv::Op::OpHitObjectGetWorldRayOriginNV, result_type} << hit_object << EndOp{}; +} + +Id Module::OpHitObjectGetRayTMaxNV(Id result_type, Id hit_object) { + code->Reserve(4); + return *code << OpId{spv::Op::OpHitObjectGetRayTMaxNV, result_type} << hit_object << EndOp{}; +} + +Id Module::OpHitObjectGetRayTMinNV(Id result_type, Id hit_object) { + code->Reserve(4); + return *code << OpId{spv::Op::OpHitObjectGetRayTMinNV, result_type} << hit_object << EndOp{}; +} + +Id Module::OpHitObjectIsEmptyNV(Id result_type, Id hit_object) { + code->Reserve(4); + return *code << OpId{spv::Op::OpHitObjectIsEmptyNV, result_type} << hit_object << EndOp{}; +} + +Id Module::OpHitObjectIsHitNV(Id result_type, Id hit_object) { + code->Reserve(4); + return *code << OpId{spv::Op::OpHitObjectIsHitNV, result_type} << hit_object << EndOp{}; +} + +Id Module::OpHitObjectIsMissNV(Id result_type, Id hit_object) { + code->Reserve(4); + return *code << OpId{spv::Op::OpHitObjectIsMissNV, result_type} << hit_object << EndOp{}; +} + +void Module::OpReorderThreadWithHitObjectNV(Id hit_object, std::optional hint, std::optional bits) { + code->Reserve(2 + (hint.has_value() ? 1 : 0) + (bits.has_value() ? 1 : 0)); + *code << spv::Op::OpReorderThreadWithHitObjectNV << hit_object << hint << bits << EndOp{}; +} + +void Module::OpReorderThreadWithHintNV(Id hint, Id bits) { + code->Reserve(3); + *code << spv::Op::OpReorderThreadWithHintNV << hint << bits << EndOp{}; +} + +Id Module::OpCooperativeVectorMatrixMulNV(Id result_type, Id input, Id input_interpretation, Id matrix, Id matrix_offset, Id matrix_interpretation, Id m, Id k, Id memory_layout, Id transpose, std::optional matrix_stride, std::optional cooperative_matrix_operands) { + code->Reserve(12 + (matrix_stride.has_value() ? 1 : 0) + (cooperative_matrix_operands.has_value() ? 1 : 0)); + return *code << OpId{spv::Op::OpCooperativeVectorMatrixMulNV, result_type} << input << input_interpretation << matrix << matrix_offset << matrix_interpretation << m << k << memory_layout << transpose << matrix_stride << cooperative_matrix_operands << EndOp{}; +} + +void Module::OpCooperativeVectorOuterProductAccumulateNV(Id pointer, Id offset, Id a, Id b, Id memory_layout, Id matrix_interpretation, std::optional matrix_stride) { + code->Reserve(7 + (matrix_stride.has_value() ? 1 : 0)); + *code << spv::Op::OpCooperativeVectorOuterProductAccumulateNV << pointer << offset << a << b << memory_layout << matrix_interpretation << matrix_stride << EndOp{}; +} + +void Module::OpCooperativeVectorReduceSumAccumulateNV(Id pointer, Id offset, Id v) { + code->Reserve(4); + *code << spv::Op::OpCooperativeVectorReduceSumAccumulateNV << pointer << offset << v << EndOp{}; +} + +Id Module::OpCooperativeVectorMatrixMulAddNV(Id result_type, Id input, Id input_interpretation, Id matrix, Id matrix_offset, Id matrix_interpretation, Id bias, Id bias_offset, Id bias_interpretation, Id m, Id k, Id memory_layout, Id transpose, std::optional matrix_stride, std::optional cooperative_matrix_operands) { + code->Reserve(15 + (matrix_stride.has_value() ? 1 : 0) + (cooperative_matrix_operands.has_value() ? 1 : 0)); + return *code << OpId{spv::Op::OpCooperativeVectorMatrixMulAddNV, result_type} << input << input_interpretation << matrix << matrix_offset << matrix_interpretation << bias << bias_offset << bias_interpretation << m << k << memory_layout << transpose << matrix_stride << cooperative_matrix_operands << EndOp{}; +} + +void Module::OpWritePackedPrimitiveIndices4x8NV(Id index_offset, Id packed_indices) { + code->Reserve(3); + *code << spv::Op::OpWritePackedPrimitiveIndices4x8NV << index_offset << packed_indices << EndOp{}; +} + +Id Module::OpFetchMicroTriangleVertexPositionNV(Id result_type, Id accel, Id instance_id, Id geometry_index, Id primitive_index, Id barycentric) { + code->Reserve(8); + return *code << OpId{spv::Op::OpFetchMicroTriangleVertexPositionNV, result_type} << accel << instance_id << geometry_index << primitive_index << barycentric << EndOp{}; +} + +Id Module::OpFetchMicroTriangleVertexBarycentricNV(Id result_type, Id accel, Id instance_id, Id geometry_index, Id primitive_index, Id barycentric) { + code->Reserve(8); + return *code << OpId{spv::Op::OpFetchMicroTriangleVertexBarycentricNV, result_type} << accel << instance_id << geometry_index << primitive_index << barycentric << EndOp{}; +} + +void Module::OpHitObjectRecordFromQueryEXT(Id hit_object, Id ray_query, Id sbt_record_index, Id hit_object_attributes, std::optional hit_kind) { + code->Reserve(5 + (hit_kind.has_value() ? 1 : 0)); + *code << spv::Op::OpHitObjectRecordFromQueryEXT << hit_object << ray_query << sbt_record_index << hit_object_attributes << hit_kind << EndOp{}; +} + +void Module::OpHitObjectRecordMissEXT(Id hit_object, Id ray_flags, Id miss_index, Id ray_origin, Id ray_tmin, Id ray_direction, Id ray_tmax) { + code->Reserve(8); + *code << spv::Op::OpHitObjectRecordMissEXT << hit_object << ray_flags << miss_index << ray_origin << ray_tmin << ray_direction << ray_tmax << EndOp{}; +} + +void Module::OpHitObjectRecordMissMotionEXT(Id hit_object, Id ray_flags, Id miss_index, Id ray_origin, Id ray_tmin, Id ray_direction, Id ray_tmax, Id current_time) { + code->Reserve(9); + *code << spv::Op::OpHitObjectRecordMissMotionEXT << hit_object << ray_flags << miss_index << ray_origin << ray_tmin << ray_direction << ray_tmax << current_time << EndOp{}; +} + +Id Module::OpHitObjectGetIntersectionTriangleVertexPositionsEXT(Id result_type, Id hit_object) { + code->Reserve(4); + return *code << OpId{spv::Op::OpHitObjectGetIntersectionTriangleVertexPositionsEXT, result_type} << hit_object << EndOp{}; +} + +Id Module::OpHitObjectGetRayFlagsEXT(Id result_type, Id hit_object) { + code->Reserve(4); + return *code << OpId{spv::Op::OpHitObjectGetRayFlagsEXT, result_type} << hit_object << EndOp{}; +} + +void Module::OpHitObjectSetShaderBindingTableRecordIndexEXT(Id hit_object, Id sbt_record_index) { + code->Reserve(3); + *code << spv::Op::OpHitObjectSetShaderBindingTableRecordIndexEXT << hit_object << sbt_record_index << EndOp{}; +} + +void Module::OpHitObjectReorderExecuteShaderEXT(Id hit_object, Id payload, std::optional hint, std::optional bits) { + code->Reserve(3 + (hint.has_value() ? 1 : 0) + (bits.has_value() ? 1 : 0)); + *code << spv::Op::OpHitObjectReorderExecuteShaderEXT << hit_object << payload << hint << bits << EndOp{}; +} + +void Module::OpHitObjectTraceReorderExecuteEXT(Id hit_object, Id acceleration_structure, Id ray_flags, Id cull_mask, Id sbt_offset, Id sbt_stride, Id miss_index, Id ray_origin, Id ray_tmin, Id ray_direction, Id ray_tmax, Id payload, std::optional hint, std::optional bits) { + code->Reserve(13 + (hint.has_value() ? 1 : 0) + (bits.has_value() ? 1 : 0)); + *code << spv::Op::OpHitObjectTraceReorderExecuteEXT << hit_object << acceleration_structure << ray_flags << cull_mask << sbt_offset << sbt_stride << miss_index << ray_origin << ray_tmin << ray_direction << ray_tmax << payload << hint << bits << EndOp{}; +} + +void Module::OpHitObjectTraceMotionReorderExecuteEXT(Id hit_object, Id acceleration_structure, Id ray_flags, Id cull_mask, Id sbt_offset, Id sbt_stride, Id miss_index, Id ray_origin, Id ray_tmin, Id ray_direction, Id ray_tmax, Id current_time, Id payload, std::optional hint, std::optional bits) { + code->Reserve(14 + (hint.has_value() ? 1 : 0) + (bits.has_value() ? 1 : 0)); + *code << spv::Op::OpHitObjectTraceMotionReorderExecuteEXT << hit_object << acceleration_structure << ray_flags << cull_mask << sbt_offset << sbt_stride << miss_index << ray_origin << ray_tmin << ray_direction << ray_tmax << current_time << payload << hint << bits << EndOp{}; +} + +void Module::OpReorderThreadWithHintEXT(Id hint, Id bits) { + code->Reserve(3); + *code << spv::Op::OpReorderThreadWithHintEXT << hint << bits << EndOp{}; +} + +void Module::OpReorderThreadWithHitObjectEXT(Id hit_object, std::optional hint, std::optional bits) { + code->Reserve(2 + (hint.has_value() ? 1 : 0) + (bits.has_value() ? 1 : 0)); + *code << spv::Op::OpReorderThreadWithHitObjectEXT << hit_object << hint << bits << EndOp{}; +} + +void Module::OpHitObjectTraceRayEXT(Id hit_object, Id acceleration_structure, Id ray_flags, Id cull_mask, Id sbt_offset, Id sbt_stride, Id miss_index, Id ray_origin, Id ray_tmin, Id ray_direction, Id ray_tmax, Id payload) { + code->Reserve(13); + *code << spv::Op::OpHitObjectTraceRayEXT << hit_object << acceleration_structure << ray_flags << cull_mask << sbt_offset << sbt_stride << miss_index << ray_origin << ray_tmin << ray_direction << ray_tmax << payload << EndOp{}; +} + +void Module::OpHitObjectTraceRayMotionEXT(Id hit_object, Id acceleration_structure, Id ray_flags, Id cull_mask, Id sbt_offset, Id sbt_stride, Id miss_index, Id ray_origin, Id ray_tmin, Id ray_direction, Id ray_tmax, Id current_time, Id payload) { + code->Reserve(14); + *code << spv::Op::OpHitObjectTraceRayMotionEXT << hit_object << acceleration_structure << ray_flags << cull_mask << sbt_offset << sbt_stride << miss_index << ray_origin << ray_tmin << ray_direction << ray_tmax << current_time << payload << EndOp{}; +} + +void Module::OpHitObjectRecordEmptyEXT(Id hit_object) { + code->Reserve(2); + *code << spv::Op::OpHitObjectRecordEmptyEXT << hit_object << EndOp{}; +} + +void Module::OpHitObjectExecuteShaderEXT(Id hit_object, Id payload) { + code->Reserve(3); + *code << spv::Op::OpHitObjectExecuteShaderEXT << hit_object << payload << EndOp{}; +} + +Id Module::OpHitObjectGetCurrentTimeEXT(Id result_type, Id hit_object) { + code->Reserve(4); + return *code << OpId{spv::Op::OpHitObjectGetCurrentTimeEXT, result_type} << hit_object << EndOp{}; +} + +void Module::OpHitObjectGetAttributesEXT(Id hit_object, Id hit_object_attribute) { + code->Reserve(3); + *code << spv::Op::OpHitObjectGetAttributesEXT << hit_object << hit_object_attribute << EndOp{}; +} + +Id Module::OpHitObjectGetHitKindEXT(Id result_type, Id hit_object) { + code->Reserve(4); + return *code << OpId{spv::Op::OpHitObjectGetHitKindEXT, result_type} << hit_object << EndOp{}; +} + +Id Module::OpHitObjectGetPrimitiveIndexEXT(Id result_type, Id hit_object) { + code->Reserve(4); + return *code << OpId{spv::Op::OpHitObjectGetPrimitiveIndexEXT, result_type} << hit_object << EndOp{}; +} + +Id Module::OpHitObjectGetGeometryIndexEXT(Id result_type, Id hit_object) { + code->Reserve(4); + return *code << OpId{spv::Op::OpHitObjectGetGeometryIndexEXT, result_type} << hit_object << EndOp{}; +} + +Id Module::OpHitObjectGetInstanceIdEXT(Id result_type, Id hit_object) { + code->Reserve(4); + return *code << OpId{spv::Op::OpHitObjectGetInstanceIdEXT, result_type} << hit_object << EndOp{}; +} + +Id Module::OpHitObjectGetInstanceCustomIndexEXT(Id result_type, Id hit_object) { + code->Reserve(4); + return *code << OpId{spv::Op::OpHitObjectGetInstanceCustomIndexEXT, result_type} << hit_object << EndOp{}; +} + +Id Module::OpHitObjectGetObjectRayOriginEXT(Id result_type, Id hit_object) { + code->Reserve(4); + return *code << OpId{spv::Op::OpHitObjectGetObjectRayOriginEXT, result_type} << hit_object << EndOp{}; +} + +Id Module::OpHitObjectGetObjectRayDirectionEXT(Id result_type, Id hit_object) { + code->Reserve(4); + return *code << OpId{spv::Op::OpHitObjectGetObjectRayDirectionEXT, result_type} << hit_object << EndOp{}; +} + +Id Module::OpHitObjectGetWorldRayDirectionEXT(Id result_type, Id hit_object) { + code->Reserve(4); + return *code << OpId{spv::Op::OpHitObjectGetWorldRayDirectionEXT, result_type} << hit_object << EndOp{}; +} + +Id Module::OpHitObjectGetWorldRayOriginEXT(Id result_type, Id hit_object) { + code->Reserve(4); + return *code << OpId{spv::Op::OpHitObjectGetWorldRayOriginEXT, result_type} << hit_object << EndOp{}; +} + +Id Module::OpHitObjectGetObjectToWorldEXT(Id result_type, Id hit_object) { + code->Reserve(4); + return *code << OpId{spv::Op::OpHitObjectGetObjectToWorldEXT, result_type} << hit_object << EndOp{}; +} + +Id Module::OpHitObjectGetWorldToObjectEXT(Id result_type, Id hit_object) { + code->Reserve(4); + return *code << OpId{spv::Op::OpHitObjectGetWorldToObjectEXT, result_type} << hit_object << EndOp{}; +} + +Id Module::OpHitObjectGetRayTMaxEXT(Id result_type, Id hit_object) { + code->Reserve(4); + return *code << OpId{spv::Op::OpHitObjectGetRayTMaxEXT, result_type} << hit_object << EndOp{}; +} + +void Module::OpIgnoreIntersectionNV() { + code->Reserve(1); + *code << spv::Op::OpIgnoreIntersectionNV << EndOp{}; +} + +void Module::OpTerminateRayNV() { + code->Reserve(1); + *code << spv::Op::OpTerminateRayNV << EndOp{}; +} + +void Module::OpTraceNV(Id accel, Id ray_flags, Id cull_mask, Id sbt_offset, Id sbt_stride, Id miss_index, Id ray_origin, Id ray_tmin, Id ray_direction, Id ray_tmax, Id payload_id) { + code->Reserve(12); + *code << spv::Op::OpTraceNV << accel << ray_flags << cull_mask << sbt_offset << sbt_stride << miss_index << ray_origin << ray_tmin << ray_direction << ray_tmax << payload_id << EndOp{}; +} + +void Module::OpTraceMotionNV(Id accel, Id ray_flags, Id cull_mask, Id sbt_offset, Id sbt_stride, Id miss_index, Id ray_origin, Id ray_tmin, Id ray_direction, Id ray_tmax, Id time, Id payload_id) { + code->Reserve(13); + *code << spv::Op::OpTraceMotionNV << accel << ray_flags << cull_mask << sbt_offset << sbt_stride << miss_index << ray_origin << ray_tmin << ray_direction << ray_tmax << time << payload_id << EndOp{}; +} + +void Module::OpTraceRayMotionNV(Id accel, Id ray_flags, Id cull_mask, Id sbt_offset, Id sbt_stride, Id miss_index, Id ray_origin, Id ray_tmin, Id ray_direction, Id ray_tmax, Id time, Id payload) { + code->Reserve(13); + *code << spv::Op::OpTraceRayMotionNV << accel << ray_flags << cull_mask << sbt_offset << sbt_stride << miss_index << ray_origin << ray_tmin << ray_direction << ray_tmax << time << payload << EndOp{}; +} + +void Module::OpExecuteCallableNV(Id sbt_index, Id callable_data_id) { + code->Reserve(3); + *code << spv::Op::OpExecuteCallableNV << sbt_index << callable_data_id << EndOp{}; +} + +Id Module::OpRayQueryGetIntersectionClusterIdNV(Id result_type, Id ray_query, Id intersection) { + code->Reserve(5); + return *code << OpId{spv::Op::OpRayQueryGetIntersectionClusterIdNV, result_type} << ray_query << intersection << EndOp{}; +} + +Id Module::OpHitObjectGetClusterIdNV(Id result_type, Id hit_object) { + code->Reserve(4); + return *code << OpId{spv::Op::OpHitObjectGetClusterIdNV, result_type} << hit_object << EndOp{}; +} + +Id Module::OpHitObjectGetRayTMinEXT(Id result_type, Id hit_object) { + code->Reserve(4); + return *code << OpId{spv::Op::OpHitObjectGetRayTMinEXT, result_type} << hit_object << EndOp{}; +} + +Id Module::OpHitObjectGetShaderBindingTableRecordIndexEXT(Id result_type, Id hit_object) { + code->Reserve(4); + return *code << OpId{spv::Op::OpHitObjectGetShaderBindingTableRecordIndexEXT, result_type} << hit_object << EndOp{}; +} + +Id Module::OpHitObjectGetShaderRecordBufferHandleEXT(Id result_type, Id hit_object) { + code->Reserve(4); + return *code << OpId{spv::Op::OpHitObjectGetShaderRecordBufferHandleEXT, result_type} << hit_object << EndOp{}; +} + +Id Module::OpHitObjectIsEmptyEXT(Id result_type, Id hit_object) { + code->Reserve(4); + return *code << OpId{spv::Op::OpHitObjectIsEmptyEXT, result_type} << hit_object << EndOp{}; +} + +Id Module::OpHitObjectIsHitEXT(Id result_type, Id hit_object) { + code->Reserve(4); + return *code << OpId{spv::Op::OpHitObjectIsHitEXT, result_type} << hit_object << EndOp{}; +} + +Id Module::OpHitObjectIsMissEXT(Id result_type, Id hit_object) { + code->Reserve(4); + return *code << OpId{spv::Op::OpHitObjectIsMissEXT, result_type} << hit_object << EndOp{}; +} + +Id Module::OpCooperativeMatrixLoadNV(Id result_type, Id pointer, Id stride, Id column_major, std::optional memory_access) { + code->Reserve(6 + (memory_access.has_value() ? 1 : 0)); + return *code << OpId{spv::Op::OpCooperativeMatrixLoadNV, result_type} << pointer << stride << column_major << memory_access << EndOp{}; +} + +void Module::OpCooperativeMatrixStoreNV(Id pointer, Id object, Id stride, Id column_major, std::optional memory_access) { + code->Reserve(5 + (memory_access.has_value() ? 1 : 0)); + *code << spv::Op::OpCooperativeMatrixStoreNV << pointer << object << stride << column_major << memory_access << EndOp{}; +} + +Id Module::OpCooperativeMatrixMulAddNV(Id result_type, Id a, Id b, Id c) { + code->Reserve(6); + return *code << OpId{spv::Op::OpCooperativeMatrixMulAddNV, result_type} << a << b << c << EndOp{}; +} + +Id Module::OpCooperativeMatrixLengthNV(Id result_type, Id type) { + code->Reserve(4); + return *code << OpId{spv::Op::OpCooperativeMatrixLengthNV, result_type} << type << EndOp{}; +} + +Id Module::OpCreateTensorLayoutNV(Id result_type) { + code->Reserve(3); + return *code << OpId{spv::Op::OpCreateTensorLayoutNV, result_type} << EndOp{}; +} + +Id Module::OpTensorLayoutSetDimensionNV(Id result_type, Id tensor_layout, std::span dim) { + code->Reserve(4 + dim.size()); + return *code << OpId{spv::Op::OpTensorLayoutSetDimensionNV, result_type} << tensor_layout << dim << EndOp{}; +} + +Id Module::OpTensorLayoutSetStrideNV(Id result_type, Id tensor_layout, std::span stride) { + code->Reserve(4 + stride.size()); + return *code << OpId{spv::Op::OpTensorLayoutSetStrideNV, result_type} << tensor_layout << stride << EndOp{}; +} + +Id Module::OpTensorLayoutSliceNV(Id result_type, Id tensor_layout, std::span operands) { + code->Reserve(4 + operands.size()); + return *code << OpId{spv::Op::OpTensorLayoutSliceNV, result_type} << tensor_layout << operands << EndOp{}; +} + +Id Module::OpTensorLayoutSetClampValueNV(Id result_type, Id tensor_layout, Id value) { + code->Reserve(5); + return *code << OpId{spv::Op::OpTensorLayoutSetClampValueNV, result_type} << tensor_layout << value << EndOp{}; +} + +Id Module::OpCreateTensorViewNV(Id result_type) { + code->Reserve(3); + return *code << OpId{spv::Op::OpCreateTensorViewNV, result_type} << EndOp{}; +} + +Id Module::OpTensorViewSetDimensionNV(Id result_type, Id tensor_view, std::span dim) { + code->Reserve(4 + dim.size()); + return *code << OpId{spv::Op::OpTensorViewSetDimensionNV, result_type} << tensor_view << dim << EndOp{}; +} + +Id Module::OpTensorViewSetStrideNV(Id result_type, Id tensor_view, std::span stride) { + code->Reserve(4 + stride.size()); + return *code << OpId{spv::Op::OpTensorViewSetStrideNV, result_type} << tensor_view << stride << EndOp{}; +} + +Id Module::OpTensorViewSetClipNV(Id result_type, Id tensor_view, Id clip_row_offset, Id clip_row_span, Id clip_col_offset, Id clip_col_span) { + code->Reserve(8); + return *code << OpId{spv::Op::OpTensorViewSetClipNV, result_type} << tensor_view << clip_row_offset << clip_row_span << clip_col_offset << clip_col_span << EndOp{}; +} + +Id Module::OpTensorLayoutSetBlockSizeNV(Id result_type, Id tensor_layout, std::span block_size) { + code->Reserve(4 + block_size.size()); + return *code << OpId{spv::Op::OpTensorLayoutSetBlockSizeNV, result_type} << tensor_layout << block_size << EndOp{}; +} + +Id Module::OpConvertUToImageNV(Id result_type, Id operand) { + code->Reserve(4); + return *code << OpId{spv::Op::OpConvertUToImageNV, result_type} << operand << EndOp{}; +} + +Id Module::OpConvertUToSamplerNV(Id result_type, Id operand) { + code->Reserve(4); + return *code << OpId{spv::Op::OpConvertUToSamplerNV, result_type} << operand << EndOp{}; +} + +Id Module::OpConvertImageToUNV(Id result_type, Id operand) { + code->Reserve(4); + return *code << OpId{spv::Op::OpConvertImageToUNV, result_type} << operand << EndOp{}; +} + +Id Module::OpConvertSamplerToUNV(Id result_type, Id operand) { + code->Reserve(4); + return *code << OpId{spv::Op::OpConvertSamplerToUNV, result_type} << operand << EndOp{}; +} + +Id Module::OpConvertUToSampledImageNV(Id result_type, Id operand) { + code->Reserve(4); + return *code << OpId{spv::Op::OpConvertUToSampledImageNV, result_type} << operand << EndOp{}; +} + +Id Module::OpConvertSampledImageToUNV(Id result_type, Id operand) { + code->Reserve(4); + return *code << OpId{spv::Op::OpConvertSampledImageToUNV, result_type} << operand << EndOp{}; +} + +void Module::OpSamplerImageAddressingModeNV(std::uint32_t bit_width) { + code->Reserve(2); + *code << spv::Op::OpSamplerImageAddressingModeNV << bit_width << EndOp{}; +} + +Id Module::OpRayQueryGetIntersectionSpherePositionNV(Id result_type, Id ray_query, Id intersection) { + code->Reserve(5); + return *code << OpId{spv::Op::OpRayQueryGetIntersectionSpherePositionNV, result_type} << ray_query << intersection << EndOp{}; +} + +Id Module::OpRayQueryGetIntersectionSphereRadiusNV(Id result_type, Id ray_query, Id intersection) { + code->Reserve(5); + return *code << OpId{spv::Op::OpRayQueryGetIntersectionSphereRadiusNV, result_type} << ray_query << intersection << EndOp{}; +} + +Id Module::OpRayQueryGetIntersectionLSSPositionsNV(Id result_type, Id ray_query, Id intersection) { + code->Reserve(5); + return *code << OpId{spv::Op::OpRayQueryGetIntersectionLSSPositionsNV, result_type} << ray_query << intersection << EndOp{}; +} + +Id Module::OpRayQueryGetIntersectionLSSRadiiNV(Id result_type, Id ray_query, Id intersection) { + code->Reserve(5); + return *code << OpId{spv::Op::OpRayQueryGetIntersectionLSSRadiiNV, result_type} << ray_query << intersection << EndOp{}; +} + +Id Module::OpRayQueryGetIntersectionLSSHitValueNV(Id result_type, Id ray_query, Id intersection) { + code->Reserve(5); + return *code << OpId{spv::Op::OpRayQueryGetIntersectionLSSHitValueNV, result_type} << ray_query << intersection << EndOp{}; +} + +Id Module::OpHitObjectGetSpherePositionNV(Id result_type, Id hit_object) { + code->Reserve(4); + return *code << OpId{spv::Op::OpHitObjectGetSpherePositionNV, result_type} << hit_object << EndOp{}; +} + +Id Module::OpHitObjectGetSphereRadiusNV(Id result_type, Id hit_object) { + code->Reserve(4); + return *code << OpId{spv::Op::OpHitObjectGetSphereRadiusNV, result_type} << hit_object << EndOp{}; +} + +Id Module::OpHitObjectGetLSSPositionsNV(Id result_type, Id hit_object) { + code->Reserve(4); + return *code << OpId{spv::Op::OpHitObjectGetLSSPositionsNV, result_type} << hit_object << EndOp{}; +} + +Id Module::OpHitObjectGetLSSRadiiNV(Id result_type, Id hit_object) { + code->Reserve(4); + return *code << OpId{spv::Op::OpHitObjectGetLSSRadiiNV, result_type} << hit_object << EndOp{}; +} + +Id Module::OpHitObjectIsSphereHitNV(Id result_type, Id hit_object) { + code->Reserve(4); + return *code << OpId{spv::Op::OpHitObjectIsSphereHitNV, result_type} << hit_object << EndOp{}; +} + +Id Module::OpHitObjectIsLSSHitNV(Id result_type, Id hit_object) { + code->Reserve(4); + return *code << OpId{spv::Op::OpHitObjectIsLSSHitNV, result_type} << hit_object << EndOp{}; +} + +Id Module::OpRayQueryIsSphereHitNV(Id result_type, Id ray_query, Id intersection) { + code->Reserve(5); + return *code << OpId{spv::Op::OpRayQueryIsSphereHitNV, result_type} << ray_query << intersection << EndOp{}; +} + +Id Module::OpRayQueryIsLSSHitNV(Id result_type, Id ray_query, Id intersection) { + code->Reserve(5); + return *code << OpId{spv::Op::OpRayQueryIsLSSHitNV, result_type} << ray_query << intersection << EndOp{}; +} + +Id Module::TypeCooperativeMatrixKHR(Id component_type, Id scope, Id rows, Id columns, Id use) { + declarations->Reserve(7); + return *declarations << OpId{spv::Op::OpTypeCooperativeMatrixKHR} << component_type << scope << rows << columns << use << EndOp{}; +} + +Id Module::TypeHitObjectNV() { + declarations->Reserve(2); + return *declarations << OpId{spv::Op::OpTypeHitObjectNV} << EndOp{}; +} + +Id Module::TypeHitObjectEXT() { + declarations->Reserve(2); + return *declarations << OpId{spv::Op::OpTypeHitObjectEXT} << EndOp{}; +} + +Id Module::TypeCooperativeMatrixNV(Id component_type, Id execution, Id rows, Id columns) { + declarations->Reserve(6); + return *declarations << OpId{spv::Op::OpTypeCooperativeMatrixNV} << component_type << execution << rows << columns << EndOp{}; +} + +Id Module::TypeTensorLayoutNV(Id dim, Id clamp_mode) { + declarations->Reserve(4); + return *declarations << OpId{spv::Op::OpTypeTensorLayoutNV} << dim << clamp_mode << EndOp{}; +} + +Id Module::TypeTensorViewNV(Id dim, Id has_dimensions, std::span p) { + declarations->Reserve(4 + p.size()); + return *declarations << OpId{spv::Op::OpTypeTensorViewNV} << dim << has_dimensions << p << EndOp{}; +} + +} // namespace Sirit diff --git a/src/instructions/annotation.cpp b/src/instructions/annotation.cpp deleted file mode 100644 index e3524bb..0000000 --- a/src/instructions/annotation.cpp +++ /dev/null @@ -1,27 +0,0 @@ -/* This file is part of the sirit project. - * Copyright (c) 2019 sirit - * This software may be used and distributed according to the terms of the - * 3-Clause BSD License - */ - -#include - -#include "sirit/sirit.h" - -#include "stream.h" - -namespace Sirit { - -Id Module::Decorate(Id target, spv::Decoration decoration, std::span literals) { - annotations->Reserve(3 + literals.size()); - return *annotations << spv::Op::OpDecorate << target << decoration << literals << EndOp{}; -} - -Id Module::MemberDecorate(Id structure_type, Literal member, spv::Decoration decoration, - std::span literals) { - annotations->Reserve(4 + literals.size()); - return *annotations << spv::Op::OpMemberDecorate << structure_type << member << decoration - << literals << EndOp{}; -} - -} // namespace Sirit diff --git a/src/instructions/arithmetic.cpp b/src/instructions/arithmetic.cpp deleted file mode 100644 index 4fa8057..0000000 --- a/src/instructions/arithmetic.cpp +++ /dev/null @@ -1,44 +0,0 @@ -/* This file is part of the sirit project. - * Copyright (c) 2019 sirit - * This software may be used and distributed according to the terms of the - * 3-Clause BSD License - */ - -#include "sirit/sirit.h" - -#include "stream.h" - -namespace Sirit { - -#define DEFINE_UNARY(funcname, opcode) \ - Id Module::funcname(Id result_type, Id operand) { \ - code->Reserve(4); \ - return *code << OpId{opcode, result_type} << operand << EndOp{}; \ - } - -#define DEFINE_BINARY(funcname, opcode) \ - Id Module::funcname(Id result_type, Id operand_1, Id operand_2) { \ - code->Reserve(5); \ - return *code << OpId{opcode, result_type} << operand_1 << operand_2 << EndOp{}; \ - } - -DEFINE_UNARY(OpSNegate, spv::Op::OpSNegate) -DEFINE_UNARY(OpFNegate, spv::Op::OpFNegate) - -DEFINE_BINARY(OpIAdd, spv::Op::OpIAdd) -DEFINE_BINARY(OpFAdd, spv::Op::OpFAdd) -DEFINE_BINARY(OpISub, spv::Op::OpISub) -DEFINE_BINARY(OpFSub, spv::Op::OpFSub) -DEFINE_BINARY(OpIMul, spv::Op::OpIMul) -DEFINE_BINARY(OpFMul, spv::Op::OpFMul) -DEFINE_BINARY(OpUDiv, spv::Op::OpUDiv) -DEFINE_BINARY(OpSDiv, spv::Op::OpSDiv) -DEFINE_BINARY(OpFDiv, spv::Op::OpFDiv) -DEFINE_BINARY(OpUMod, spv::Op::OpUMod) -DEFINE_BINARY(OpSMod, spv::Op::OpSMod) -DEFINE_BINARY(OpFMod, spv::Op::OpFMod) -DEFINE_BINARY(OpSRem, spv::Op::OpSRem) -DEFINE_BINARY(OpFRem, spv::Op::OpFRem) -DEFINE_BINARY(OpIAddCarry, spv::Op::OpIAddCarry) - -} // namespace Sirit diff --git a/src/instructions/atomic.cpp b/src/instructions/atomic.cpp deleted file mode 100644 index cee8849..0000000 --- a/src/instructions/atomic.cpp +++ /dev/null @@ -1,104 +0,0 @@ -/* This file is part of the sirit project. - * Copyright (c) 2019 sirit - * This software may be used and distributed according to the terms of the - * 3-Clause BSD License - */ - -#include "sirit/sirit.h" - -#include "stream.h" - -namespace Sirit { - -Id Module::OpAtomicLoad(Id result_type, Id pointer, Id memory, Id semantics) { - code->Reserve(6); - return *code << OpId{spv::Op::OpAtomicLoad, result_type} << pointer << memory << semantics - << EndOp{}; -} - -Id Module::OpAtomicStore(Id pointer, Id memory, Id semantics, Id value) { - code->Reserve(5); - return *code << OpId{spv::Op::OpAtomicStore} << pointer << memory << semantics << value - << EndOp{}; -} - -Id Module::OpAtomicExchange(Id result_type, Id pointer, Id memory, Id semantics, Id value) { - code->Reserve(7); - return *code << OpId{spv::Op::OpAtomicExchange, result_type} << pointer << memory << semantics - << value << EndOp{}; -} - -Id Module::OpAtomicCompareExchange(Id result_type, Id pointer, Id memory, Id equal, Id unequal, - Id value, Id comparator) { - code->Reserve(9); - return *code << OpId{spv::Op::OpAtomicCompareExchange, result_type} << pointer << memory - << equal << unequal << value << comparator << EndOp{}; -} - -Id Module::OpAtomicIIncrement(Id result_type, Id pointer, Id memory, Id semantics) { - code->Reserve(6); - return *code << OpId{spv::Op::OpAtomicIIncrement, result_type} << pointer << memory << semantics - << EndOp{}; -} - -Id Module::OpAtomicIDecrement(Id result_type, Id pointer, Id memory, Id semantics) { - code->Reserve(6); - return *code << OpId{spv::Op::OpAtomicIDecrement, result_type} << pointer << memory << semantics - << EndOp{}; -} - -Id Module::OpAtomicIAdd(Id result_type, Id pointer, Id memory, Id semantics, Id value) { - code->Reserve(7); - return *code << OpId{spv::Op::OpAtomicIAdd, result_type} << pointer << memory << semantics - << value << EndOp{}; -} - -Id Module::OpAtomicISub(Id result_type, Id pointer, Id memory, Id semantics, Id value) { - code->Reserve(7); - return *code << OpId{spv::Op::OpAtomicISub, result_type} << pointer << memory << semantics - << value << EndOp{}; -} - -Id Module::OpAtomicSMin(Id result_type, Id pointer, Id memory, Id semantics, Id value) { - code->Reserve(7); - return *code << OpId{spv::Op::OpAtomicSMin, result_type} << pointer << memory << semantics - << value << EndOp{}; -} - -Id Module::OpAtomicUMin(Id result_type, Id pointer, Id memory, Id semantics, Id value) { - code->Reserve(7); - return *code << OpId{spv::Op::OpAtomicUMin, result_type} << pointer << memory << semantics - << value << EndOp{}; -} - -Id Module::OpAtomicSMax(Id result_type, Id pointer, Id memory, Id semantics, Id value) { - code->Reserve(7); - return *code << OpId{spv::Op::OpAtomicSMax, result_type} << pointer << memory << semantics - << value << EndOp{}; -} - -Id Module::OpAtomicUMax(Id result_type, Id pointer, Id memory, Id semantics, Id value) { - code->Reserve(7); - return *code << OpId{spv::Op::OpAtomicUMax, result_type} << pointer << memory << semantics - << value << EndOp{}; -} - -Id Module::OpAtomicAnd(Id result_type, Id pointer, Id memory, Id semantics, Id value) { - code->Reserve(7); - return *code << OpId{spv::Op::OpAtomicAnd, result_type} << pointer << memory << semantics - << value << EndOp{}; -} - -Id Module::OpAtomicOr(Id result_type, Id pointer, Id memory, Id semantics, Id value) { - code->Reserve(7); - return *code << OpId{spv::Op::OpAtomicOr, result_type} << pointer << memory << semantics - << value << EndOp{}; -} - -Id Module::OpAtomicXor(Id result_type, Id pointer, Id memory, Id semantics, Id value) { - code->Reserve(7); - return *code << OpId{spv::Op::OpAtomicXor, result_type} << pointer << memory << semantics - << value << EndOp{}; -} - -} // namespace Sirit diff --git a/src/instructions/barrier.cpp b/src/instructions/barrier.cpp deleted file mode 100644 index 646b5cf..0000000 --- a/src/instructions/barrier.cpp +++ /dev/null @@ -1,23 +0,0 @@ -/* This file is part of the sirit project. - * Copyright (c) 2019 sirit - * This software may be used and distributed according to the terms of the - * 3-Clause BSD License - */ - -#include "sirit/sirit.h" - -#include "stream.h" - -namespace Sirit { - -Id Module::OpControlBarrier(Id execution, Id memory, Id semantics) { - code->Reserve(4); - return *code << spv::Op::OpControlBarrier << execution << memory << semantics << EndOp{}; -} - -Id Module::OpMemoryBarrier(Id scope, Id semantics) { - code->Reserve(3); - return *code << spv::Op::OpMemoryBarrier << scope << semantics << EndOp{}; -} - -} // namespace Sirit diff --git a/src/instructions/bit.cpp b/src/instructions/bit.cpp deleted file mode 100644 index c860613..0000000 --- a/src/instructions/bit.cpp +++ /dev/null @@ -1,76 +0,0 @@ -/* This file is part of the sirit project. - * Copyright (c) 2019 sirit - * This software may be used and distributed according to the terms of the - * 3-Clause BSD License - */ - -#include "sirit/sirit.h" - -#include "stream.h" - -namespace Sirit { - -Id Module::OpShiftRightLogical(Id result_type, Id base, Id shift) { - code->Reserve(5); - return *code << OpId{spv::Op::OpShiftRightLogical, result_type} << base << shift << EndOp{}; -} - -Id Module::OpShiftRightArithmetic(Id result_type, Id base, Id shift) { - code->Reserve(5); - return *code << OpId{spv::Op::OpShiftRightArithmetic, result_type} << base << shift << EndOp{}; -} - -Id Module::OpShiftLeftLogical(Id result_type, Id base, Id shift) { - code->Reserve(5); - return *code << OpId{spv::Op::OpShiftLeftLogical, result_type} << base << shift << EndOp{}; -} - -Id Module::OpBitwiseOr(Id result_type, Id operand_1, Id operand_2) { - code->Reserve(5); - return *code << OpId{spv::Op::OpBitwiseOr, result_type} << operand_1 << operand_2 << EndOp{}; -} - -Id Module::OpBitwiseXor(Id result_type, Id operand_1, Id operand_2) { - code->Reserve(5); - return *code << OpId{spv::Op::OpBitwiseXor, result_type} << operand_1 << operand_2 << EndOp{}; -} - -Id Module::OpBitwiseAnd(Id result_type, Id operand_1, Id operand_2) { - code->Reserve(5); - return *code << OpId{spv::Op::OpBitwiseAnd, result_type} << operand_1 << operand_2 << EndOp{}; -} - -Id Module::OpNot(Id result_type, Id operand) { - code->Reserve(4); - return *code << OpId{spv::Op::OpNot, result_type} << operand << EndOp{}; -} - -Id Module::OpBitFieldInsert(Id result_type, Id base, Id insert, Id offset, Id count) { - code->Reserve(7); - return *code << OpId{spv::Op::OpBitFieldInsert, result_type} << base << insert << offset - << count << EndOp{}; -} - -Id Module::OpBitFieldSExtract(Id result_type, Id base, Id offset, Id count) { - code->Reserve(6); - return *code << OpId{spv::Op::OpBitFieldSExtract, result_type} << base << offset << count - << EndOp{}; -} - -Id Module::OpBitFieldUExtract(Id result_type, Id base, Id offset, Id count) { - code->Reserve(6); - return *code << OpId{spv::Op::OpBitFieldUExtract, result_type} << base << offset << count - << EndOp{}; -} - -Id Module::OpBitReverse(Id result_type, Id base) { - code->Reserve(4); - return *code << OpId{spv::Op::OpBitReverse, result_type} << base << EndOp{}; -} - -Id Module::OpBitCount(Id result_type, Id base) { - code->Reserve(4); - return *code << OpId{spv::Op::OpBitCount, result_type} << base << EndOp{}; -} - -} // namespace Sirit diff --git a/src/instructions/constant.cpp b/src/instructions/constant.cpp deleted file mode 100644 index 612049c..0000000 --- a/src/instructions/constant.cpp +++ /dev/null @@ -1,48 +0,0 @@ -/* This file is part of the sirit project. - * Copyright (c) 2019 sirit - * This software may be used and distributed according to the terms of the - * 3-Clause BSD License - */ - -#include - -#include "sirit/sirit.h" - -#include "stream.h" - -namespace Sirit { - -Id Module::ConstantTrue(Id result_type) { - declarations->Reserve(3); - return *declarations << OpId{spv::Op::OpConstantTrue, result_type} << EndOp{}; -} - -Id Module::ConstantFalse(Id result_type) { - declarations->Reserve(3); - return *declarations << OpId{spv::Op::OpConstantFalse, result_type} << EndOp{}; -} - -Id Module::Constant(Id result_type, const Literal& literal) { - declarations->Reserve(3 + 2); - return *declarations << OpId{spv::Op::OpConstant, result_type} << literal << EndOp{}; -} - -Id Module::ConstantComposite(Id result_type, std::span constituents) { - declarations->Reserve(3 + constituents.size()); - return *declarations << OpId{spv::Op::OpConstantComposite, result_type} << constituents - << EndOp{}; -} - -Id Module::ConstantSampler(Id result_type, spv::SamplerAddressingMode addressing_mode, - bool normalized, spv::SamplerFilterMode filter_mode) { - declarations->Reserve(6); - return *declarations << OpId{spv::Op::OpConstantSampler, result_type} << addressing_mode - << normalized << filter_mode << EndOp{}; -} - -Id Module::ConstantNull(Id result_type) { - declarations->Reserve(3); - return *declarations << OpId{spv::Op::OpConstantNull, result_type} << EndOp{}; -} - -} // namespace Sirit diff --git a/src/instructions/conversion.cpp b/src/instructions/conversion.cpp deleted file mode 100644 index 0b4e2c8..0000000 --- a/src/instructions/conversion.cpp +++ /dev/null @@ -1,29 +0,0 @@ -/* This file is part of the sirit project. - * Copyright (c) 2019 sirit - * This software may be used and distributed according to the terms of the - * 3-Clause BSD License - */ - -#include "sirit/sirit.h" - -#include "stream.h" - -namespace Sirit { - -#define DEFINE_UNARY(opcode) \ - Id Module::opcode(Id result_type, Id operand) { \ - code->Reserve(4); \ - return *code << OpId{spv::Op::opcode, result_type} << operand << EndOp{}; \ - } - -DEFINE_UNARY(OpConvertFToU) -DEFINE_UNARY(OpConvertFToS) -DEFINE_UNARY(OpConvertSToF) -DEFINE_UNARY(OpConvertUToF) -DEFINE_UNARY(OpUConvert) -DEFINE_UNARY(OpSConvert) -DEFINE_UNARY(OpFConvert) -DEFINE_UNARY(OpQuantizeToF16) -DEFINE_UNARY(OpBitcast) - -} // namespace Sirit diff --git a/src/instructions/debug.cpp b/src/instructions/debug.cpp index 1ca3462..47b01d4 100644 --- a/src/instructions/debug.cpp +++ b/src/instructions/debug.cpp @@ -2,6 +2,11 @@ * Copyright (c) 2019 sirit * This software may be used and distributed according to the terms of the * 3-Clause BSD License + * + * Hand-written debug instructions whose convention is to return the input id + * (target / type) rather than the result of `<< EndOp{}`. The simple + * generator template can't express that. OpString and OpLine live in + * _generated.cpp. */ #include "sirit/sirit.h" @@ -23,14 +28,4 @@ Id Module::MemberName(Id type, u32 member, std::string_view name) { return type; } -Id Module::String(std::string_view string) { - debug->Reserve(3 + WordsInString(string)); - return *debug << OpId{spv::Op::OpString} << string << EndOp{}; -} - -Id Module::OpLine(Id file, Literal line, Literal column) { - debug->Reserve(4); - return *debug << spv::Op::OpLine << file << line << column << EndOp{}; -} - } // namespace Sirit diff --git a/src/instructions/derivatives.cpp b/src/instructions/derivatives.cpp deleted file mode 100644 index c944e2a..0000000 --- a/src/instructions/derivatives.cpp +++ /dev/null @@ -1,29 +0,0 @@ -/* This file is part of the sirit project. - * Copyright (c) 2021 sirit - * This software may be used and distributed according to the terms of the - * 3-Clause BSD License - */ - -#include "sirit/sirit.h" - -#include "stream.h" - -namespace Sirit { - -#define DEFINE_UNARY(funcname, opcode) \ - Id Module::funcname(Id result_type, Id operand) { \ - code->Reserve(4); \ - return *code << OpId{opcode, result_type} << operand << EndOp{}; \ - } - -DEFINE_UNARY(OpDPdx, spv::Op::OpDPdx) -DEFINE_UNARY(OpDPdy, spv::Op::OpDPdy) -DEFINE_UNARY(OpFwidth, spv::Op::OpFwidth) -DEFINE_UNARY(OpDPdxFine, spv::Op::OpDPdxFine) -DEFINE_UNARY(OpDPdyFine, spv::Op::OpDPdyFine) -DEFINE_UNARY(OpFwidthFine, spv::Op::OpFwidthFine) -DEFINE_UNARY(OpDPdxCoarse, spv::Op::OpDPdxCoarse) -DEFINE_UNARY(OpDPdyCoarse, spv::Op::OpDPdyCoarse) -DEFINE_UNARY(OpFwidthCoarse, spv::Op::OpFwidthCoarse) - -} // namespace Sirit diff --git a/src/instructions/extension.cpp b/src/instructions/extension.cpp index 9f7aa43..24fc0b2 100644 --- a/src/instructions/extension.cpp +++ b/src/instructions/extension.cpp @@ -2,9 +2,18 @@ * Copyright (c) 2019 sirit * This software may be used and distributed according to the terms of the * 3-Clause BSD License + * + * Hand-written extended-instruction helpers whose shape can't be expressed + * by the wrapper template in tools/generate_instructions.py. Specifically, + * OpDebugPrintf prepends `format` onto a runtime-built operands vector and + * routes through TypeVoid(). All other GLSL.std.450 / SPV_AMD_* / NonSemantic + * wrappers live in _generated.cpp. */ -#include +#include +#include + +#include #include "sirit/sirit.h" @@ -12,65 +21,13 @@ namespace Sirit { -Id Module::OpExtInst(Id result_type, Id set, u32 instruction, std::span operands) { - code->Reserve(5 + operands.size()); - return *code << OpId{spv::Op::OpExtInst, result_type} << set << instruction << operands - << EndOp{}; +Id Module::OpDebugPrintf(Id format, std::span fmt_args) { + std::vector operands; + operands.reserve(1 + fmt_args.size()); + operands.push_back(format); + std::copy(fmt_args.begin(), fmt_args.end(), std::back_inserter(operands)); + return OpExtInst(TypeVoid(), GetNonSemanticDebugPrintf(), NonSemanticDebugPrintfDebugPrintf, + operands); } -#define DEFINE_UNARY(funcname, opcode) \ - Id Module::funcname(Id result_type, Id operand) { \ - return OpExtInst(result_type, GetGLSLstd450(), opcode, operand); \ - } - -#define DEFINE_BINARY(funcname, opcode) \ - Id Module::funcname(Id result_type, Id operand_1, Id operand_2) { \ - return OpExtInst(result_type, GetGLSLstd450(), opcode, operand_1, operand_2); \ - } - -#define DEFINE_TRINARY(funcname, opcode) \ - Id Module::funcname(Id result_type, Id operand_1, Id operand_2, Id operand_3) { \ - return OpExtInst(result_type, GetGLSLstd450(), opcode, operand_1, operand_2, operand_3); \ - } - -DEFINE_UNARY(OpFAbs, GLSLstd450FAbs) -DEFINE_UNARY(OpSAbs, GLSLstd450SAbs) -DEFINE_UNARY(OpRound, GLSLstd450Round) -DEFINE_UNARY(OpRoundEven, GLSLstd450RoundEven) -DEFINE_UNARY(OpTrunc, GLSLstd450Trunc) -DEFINE_UNARY(OpFSign, GLSLstd450FSign) -DEFINE_UNARY(OpSSign, GLSLstd450SSign) -DEFINE_UNARY(OpFloor, GLSLstd450Floor) -DEFINE_UNARY(OpCeil, GLSLstd450Ceil) -DEFINE_UNARY(OpFract, GLSLstd450Fract) -DEFINE_UNARY(OpSin, GLSLstd450Sin) -DEFINE_UNARY(OpCos, GLSLstd450Cos) -DEFINE_UNARY(OpAsin, GLSLstd450Asin) -DEFINE_UNARY(OpAcos, GLSLstd450Acos) -DEFINE_BINARY(OpPow, GLSLstd450Pow) -DEFINE_UNARY(OpExp, GLSLstd450Exp) -DEFINE_UNARY(OpLog, GLSLstd450Log) -DEFINE_UNARY(OpExp2, GLSLstd450Exp2) -DEFINE_UNARY(OpLog2, GLSLstd450Log2) -DEFINE_UNARY(OpSqrt, GLSLstd450Sqrt) -DEFINE_UNARY(OpInverseSqrt, GLSLstd450InverseSqrt) -DEFINE_BINARY(OpFMin, GLSLstd450FMin) -DEFINE_BINARY(OpUMin, GLSLstd450UMin) -DEFINE_BINARY(OpSMin, GLSLstd450SMin) -DEFINE_BINARY(OpFMax, GLSLstd450FMax) -DEFINE_BINARY(OpUMax, GLSLstd450UMax) -DEFINE_BINARY(OpSMax, GLSLstd450SMax) -DEFINE_TRINARY(OpFClamp, GLSLstd450FClamp) -DEFINE_TRINARY(OpUClamp, GLSLstd450UClamp) -DEFINE_TRINARY(OpSClamp, GLSLstd450SClamp) -DEFINE_TRINARY(OpFma, GLSLstd450Fma) -DEFINE_UNARY(OpPackHalf2x16, GLSLstd450PackHalf2x16) -DEFINE_UNARY(OpUnpackHalf2x16, GLSLstd450UnpackHalf2x16) -DEFINE_UNARY(OpFindILsb, GLSLstd450FindILsb) -DEFINE_UNARY(OpFindSMsb, GLSLstd450FindSMsb) -DEFINE_UNARY(OpFindUMsb, GLSLstd450FindUMsb) -DEFINE_UNARY(OpInterpolateAtCentroid, GLSLstd450InterpolateAtCentroid) -DEFINE_BINARY(OpInterpolateAtSample, GLSLstd450InterpolateAtSample) -DEFINE_BINARY(OpInterpolateAtOffset, GLSLstd450InterpolateAtOffset) - } // namespace Sirit diff --git a/src/instructions/flow.cpp b/src/instructions/flow.cpp index 5f6b693..d5f176b 100644 --- a/src/instructions/flow.cpp +++ b/src/instructions/flow.cpp @@ -2,6 +2,11 @@ * Copyright (c) 2019 sirit * This software may be used and distributed according to the terms of the * 3-Clause BSD License + * + * Hand-written control-flow instructions whose emit shape can't be expressed + * by the simple `*stream << HEAD << op... << EndOp{}` template that + * tools/generate_instructions.py uses. Everything else in `Flow` lives in + * _generated.cpp. */ #include @@ -12,12 +17,6 @@ namespace Sirit { -Id Module::OpPhi(Id result_type, std::span operands) { - assert(operands.size() % 2 == 0); - code->Reserve(3 + operands.size()); - return *code << OpId{spv::Op::OpPhi, result_type} << operands << EndOp{}; -} - Id Module::DeferredOpPhi(Id result_type, std::span blocks) { deferred_phi_nodes.push_back(code->LocalAddress()); code->Reserve(3 + blocks.size() * 2); @@ -28,27 +27,10 @@ Id Module::DeferredOpPhi(Id result_type, std::span blocks) { return *code << EndOp{}; } -Id Module::OpLoopMerge(Id merge_block, Id continue_target, spv::LoopControlMask loop_control, - std::span literals) { - code->Reserve(4 + literals.size()); - return *code << spv::Op::OpLoopMerge << merge_block << continue_target << loop_control - << literals << EndOp{}; -} - -Id Module::OpSelectionMerge(Id merge_block, spv::SelectionControlMask selection_control) { - code->Reserve(3); - return *code << spv::Op::OpSelectionMerge << merge_block << selection_control << EndOp{}; -} - Id Module::OpLabel() { return Id{++bound}; } -Id Module::OpBranch(Id target_label) { - code->Reserve(2); - return *code << spv::Op::OpBranch << target_label << EndOp{}; -} - Id Module::OpBranchConditional(Id condition, Id true_label, Id false_label, u32 true_weight, u32 false_weight) { code->Reserve(6); @@ -72,38 +54,8 @@ Id Module::OpSwitch(Id selector, Id default_label, std::span lite return *code << EndOp{}; } -void Module::OpReturn() { - code->Reserve(1); - *code << spv::Op::OpReturn << EndOp{}; -} - -void Module::OpUnreachable() { - code->Reserve(1); - *code << spv::Op::OpUnreachable << EndOp{}; -} - -Id Module::OpReturnValue(Id value) { - code->Reserve(2); - return *code << spv::Op::OpReturnValue << value << EndOp{}; -} - -void Module::OpKill() { - code->Reserve(1); - *code << spv::Op::OpKill << EndOp{}; -} - -void Module::OpDemoteToHelperInvocation() { - code->Reserve(1); - *code << spv::Op::OpDemoteToHelperInvocation << EndOp{}; -} - void Module::OpDemoteToHelperInvocationEXT() { OpDemoteToHelperInvocation(); } -void Module::OpTerminateInvocation() { - code->Reserve(1); - *code << spv::Op::OpTerminateInvocation << EndOp{}; -} - } // namespace Sirit diff --git a/src/instructions/function.cpp b/src/instructions/function.cpp deleted file mode 100644 index f84cd7f..0000000 --- a/src/instructions/function.cpp +++ /dev/null @@ -1,34 +0,0 @@ -/* This file is part of the sirit project. - * Copyright (c) 2019 sirit - * This software may be used and distributed according to the terms of the - * 3-Clause BSD License - */ - -#include "sirit/sirit.h" - -#include "stream.h" - -namespace Sirit { - -Id Module::OpFunction(Id result_type, spv::FunctionControlMask function_control, Id function_type) { - code->Reserve(5); - return *code << OpId{spv::Op::OpFunction, result_type} << function_control << function_type - << EndOp{}; -} - -void Module::OpFunctionEnd() { - code->Reserve(1); - *code << spv::Op::OpFunctionEnd << EndOp{}; -} - -Id Module::OpFunctionCall(Id result_type, Id function, std::span arguments) { - code->Reserve(4 + arguments.size()); - return *code << OpId{spv::Op::OpFunctionCall, result_type} << function << arguments << EndOp{}; -} - -Id Module::OpFunctionParameter(Id result_type) { - code->Reserve(3); - return *code << OpId{spv::Op::OpFunctionParameter, result_type} << EndOp{}; -} - -} // namespace Sirit diff --git a/src/instructions/group.cpp b/src/instructions/group.cpp deleted file mode 100644 index 3b6f71a..0000000 --- a/src/instructions/group.cpp +++ /dev/null @@ -1,77 +0,0 @@ -/* This file is part of the sirit project. - * Copyright (c) 2019 sirit - * This software may be used and distributed according to the terms of the - * 3-Clause BSD License - */ - -#include "sirit/sirit.h" - -#include "stream.h" - -namespace Sirit { - -Id Module::OpSubgroupBallotKHR(Id result_type, Id predicate) { - code->Reserve(4); - return *code << OpId{spv::Op::OpSubgroupBallotKHR, result_type} << predicate << EndOp{}; -} - -Id Module::OpSubgroupReadInvocationKHR(Id result_type, Id value, Id index) { - code->Reserve(5); - return *code << OpId{spv::Op::OpSubgroupReadInvocationKHR, result_type} << value << index - << EndOp{}; -} - -Id Module::OpSubgroupAllKHR(Id result_type, Id predicate) { - code->Reserve(4); - return *code << OpId{spv::Op::OpSubgroupAllKHR, result_type} << predicate << EndOp{}; -} - -Id Module::OpSubgroupAnyKHR(Id result_type, Id predicate) { - code->Reserve(4); - return *code << OpId{spv::Op::OpSubgroupAnyKHR, result_type} << predicate << EndOp{}; -} - -Id Module::OpSubgroupAllEqualKHR(Id result_type, Id predicate) { - code->Reserve(4); - return *code << OpId{spv::Op::OpSubgroupAllEqualKHR, result_type} << predicate << EndOp{}; -} - -Id Module::OpGroupNonUniformBroadcast(Id result_type, Id scope, Id value, Id id) { - code->Reserve(6); - return *code << OpId{spv::Op::OpGroupNonUniformBroadcast, result_type} << scope << value - << id << EndOp{}; -} - -Id Module::OpGroupNonUniformShuffle(Id result_type, Id scope, Id value, Id id) { - code->Reserve(6); - return *code << OpId{spv::Op::OpGroupNonUniformShuffle, result_type} << scope << value << id - << EndOp{}; -} - -Id Module::OpGroupNonUniformShuffleXor(Id result_type, Id scope, Id value, Id mask) { - code->Reserve(6); - return *code << OpId{spv::Op::OpGroupNonUniformShuffleXor, result_type} << scope << value - << mask << EndOp{}; -} - -Id Module::OpGroupNonUniformAll(Id result_type, Id scope, Id predicate) { - code->Reserve(5); - return *code << OpId{spv::Op::OpGroupNonUniformAll, result_type} << scope << predicate << EndOp{}; -} - -Id Module::OpGroupNonUniformAny(Id result_type, Id scope, Id predicate) { - code->Reserve(5); - return *code << OpId{spv::Op::OpGroupNonUniformAny, result_type} << scope << predicate << EndOp{}; -} - -Id Module::OpGroupNonUniformAllEqual(Id result_type, Id scope, Id value) { - code->Reserve(5); - return *code << OpId{spv::Op::OpGroupNonUniformAllEqual, result_type} << scope << value << EndOp{}; -} - -Id Module::OpGroupNonUniformBallot(Id result_type, Id scope, Id predicate) { - code->Reserve(5); - return *code << OpId{spv::Op::OpGroupNonUniformBallot, result_type} << scope << predicate << EndOp{}; -} - -} // namespace Sirit diff --git a/src/instructions/image.cpp b/src/instructions/image.cpp deleted file mode 100644 index dfc19d4..0000000 --- a/src/instructions/image.cpp +++ /dev/null @@ -1,169 +0,0 @@ -/* This file is part of the sirit project. - * Copyright (c) 2019 sirit - * This software may be used and distributed according to the terms of the - * 3-Clause BSD License - */ - -#include - -#include "sirit/sirit.h" - -#include "stream.h" - -namespace Sirit { - -#define DEFINE_IMAGE_OP(opcode) \ - Id Module::opcode(Id result_type, Id sampled_image, Id coordinate, \ - std::optional image_operands, \ - std::span operands) { \ - code->Reserve(6 + operands.size()); \ - return *code << OpId{spv::Op::opcode, result_type} << sampled_image << coordinate \ - << image_operands << operands << EndOp{}; \ - } - -#define DEFINE_IMAGE_EXP_OP(opcode) \ - Id Module::opcode(Id result_type, Id sampled_image, Id coordinate, \ - spv::ImageOperandsMask image_operands, std::span operands) { \ - code->Reserve(6 + operands.size()); \ - return *code << OpId{spv::Op::opcode, result_type} << sampled_image << coordinate \ - << image_operands << operands << EndOp{}; \ - } - -#define DEFINE_IMAGE_EXTRA_OP(opcode) \ - Id Module::opcode(Id result_type, Id sampled_image, Id coordinate, Id extra, \ - std::optional image_operands, \ - std::span operands) { \ - code->Reserve(7 + operands.size()); \ - return *code << OpId{spv::Op::opcode, result_type} << sampled_image << coordinate << extra \ - << image_operands << operands << EndOp{}; \ - } - -#define DEFINE_IMAGE_EXTRA_EXP_OP(opcode) \ - Id Module::opcode(Id result_type, Id sampled_image, Id coordinate, Id extra, \ - spv::ImageOperandsMask image_operands, std::span operands) { \ - code->Reserve(8 + operands.size()); \ - return *code << OpId{spv::Op::opcode, result_type} << sampled_image << coordinate << extra \ - << image_operands << operands << EndOp{}; \ - } - -#define DEFINE_IMAGE_QUERY_OP(opcode) \ - Id Module::opcode(Id result_type, Id image) { \ - code->Reserve(5); \ - return *code << OpId{spv::Op::opcode, result_type} << image << EndOp{}; \ - } - -#define DEFINE_IMAGE_QUERY_BIN_OP(opcode) \ - Id Module::opcode(Id result_type, Id image, Id extra) { \ - code->Reserve(5); \ - return *code << OpId{spv::Op::opcode, result_type} << image << extra << EndOp{}; \ - } - -DEFINE_IMAGE_OP(OpImageSampleImplicitLod) -DEFINE_IMAGE_EXP_OP(OpImageSampleExplicitLod) -DEFINE_IMAGE_EXTRA_OP(OpImageSampleDrefImplicitLod) -DEFINE_IMAGE_EXTRA_EXP_OP(OpImageSampleDrefExplicitLod) -DEFINE_IMAGE_OP(OpImageSampleProjImplicitLod) -DEFINE_IMAGE_EXP_OP(OpImageSampleProjExplicitLod) -DEFINE_IMAGE_EXTRA_OP(OpImageSampleProjDrefImplicitLod) -DEFINE_IMAGE_EXTRA_EXP_OP(OpImageSampleProjDrefExplicitLod) -DEFINE_IMAGE_OP(OpImageFetch) -DEFINE_IMAGE_EXTRA_OP(OpImageGather) -DEFINE_IMAGE_EXTRA_OP(OpImageDrefGather) -DEFINE_IMAGE_OP(OpImageRead) -DEFINE_IMAGE_QUERY_BIN_OP(OpImageQuerySizeLod) -DEFINE_IMAGE_QUERY_OP(OpImageQuerySize) -DEFINE_IMAGE_QUERY_BIN_OP(OpImageQueryLod) -DEFINE_IMAGE_QUERY_OP(OpImageQueryLevels) -DEFINE_IMAGE_QUERY_OP(OpImageQuerySamples) - -Id Module::OpSampledImage(Id result_type, Id image, Id sampler) { - code->Reserve(5); - return *code << OpId{spv::Op::OpSampledImage, result_type} << image << sampler << EndOp{}; -} - -Id Module::OpImageWrite(Id image, Id coordinate, Id texel, - std::optional image_operands, - std::span operands) { - assert(image_operands.has_value() != operands.empty()); - code->Reserve(5 + operands.size()); - return *code << spv::Op::OpImageWrite << image << coordinate << texel << image_operands - << operands << EndOp{}; -} - -Id Module::OpImage(Id result_type, Id sampled_image) { - code->Reserve(4); - return *code << OpId{spv::Op::OpImage, result_type} << sampled_image << EndOp{}; -} - -Id Module::OpImageSparseSampleImplicitLod(Id result_type, Id sampled_image, Id coordinate, - std::optional image_operands, - std::span operands) { - code->Reserve(5 + (image_operands.has_value() ? 1 : 0) + operands.size()); - return *code << OpId{spv::Op::OpImageSparseSampleImplicitLod, result_type} << sampled_image - << coordinate << image_operands << operands << EndOp{}; -} - -Id Module::OpImageSparseSampleExplicitLod(Id result_type, Id sampled_image, Id coordinate, - spv::ImageOperandsMask image_operands, - std::span operands) { - code->Reserve(6 + operands.size()); - return *code << OpId{spv::Op::OpImageSparseSampleExplicitLod, result_type} << sampled_image - << coordinate << image_operands << operands << EndOp{}; -} - -Id Module::OpImageSparseSampleDrefImplicitLod(Id result_type, Id sampled_image, Id coordinate, - Id dref, - std::optional image_operands, - std::span operands) { - code->Reserve(6 + (image_operands.has_value() ? 1 : 0) + operands.size()); - return *code << OpId{spv::Op::OpImageSparseSampleDrefImplicitLod, result_type} << sampled_image - << coordinate << dref << image_operands << operands << EndOp{}; -} - -Id Module::OpImageSparseSampleDrefExplicitLod(Id result_type, Id sampled_image, Id coordinate, - Id dref, spv::ImageOperandsMask image_operands, - std::span operands) { - code->Reserve(7 + operands.size()); - return *code << OpId{spv::Op::OpImageSparseSampleDrefExplicitLod, result_type} << sampled_image - << coordinate << dref << image_operands << operands << EndOp{}; -} - -Id Module::OpImageSparseFetch(Id result_type, Id image, Id coordinate, - std::optional image_operands, - std::span operands) { - code->Reserve(5 + (image_operands.has_value() ? 1 : 0) + operands.size()); - return *code << OpId{spv::Op::OpImageSparseFetch, result_type} << image << coordinate - << image_operands << operands << EndOp{}; -} - -Id Module::OpImageSparseGather(Id result_type, Id sampled_image, Id coordinate, Id component, - std::optional image_operands, - std::span operands) { - code->Reserve(6 + operands.size()); - return *code << OpId{spv::Op::OpImageSparseGather, result_type} << sampled_image << coordinate - << component << image_operands << operands << EndOp{}; -} - -Id Module::OpImageSparseDrefGather(Id result_type, Id sampled_image, Id coordinate, Id dref, - std::optional image_operands, - std::span operands) { - code->Reserve(6 + operands.size()); - return *code << OpId{spv::Op::OpImageSparseDrefGather, result_type} << sampled_image - << coordinate << dref << image_operands << operands << EndOp{}; -} - -Id Module::OpImageSparseTexelsResident(Id result_type, Id resident_code) { - code->Reserve(4); - return *code << OpId{spv::Op::OpImageSparseTexelsResident, result_type} << resident_code - << EndOp{}; -} - -Id Module::OpImageSparseRead(Id result_type, Id image, Id coordinate, - std::optional image_operands, - std::span operands) { - code->Reserve(5 + (image_operands.has_value() ? 1 : 0) + operands.size()); - return *code << OpId{spv::Op::OpImageSparseRead, result_type} << image << coordinate - << image_operands << operands << EndOp{}; -} - -} // namespace Sirit diff --git a/src/instructions/logical.cpp b/src/instructions/logical.cpp deleted file mode 100644 index 4d7292b..0000000 --- a/src/instructions/logical.cpp +++ /dev/null @@ -1,65 +0,0 @@ -/* This file is part of the sirit project. - * Copyright (c) 2019 sirit - * This software may be used and distributed according to the terms of the - * 3-Clause BSD License - */ - -#include "sirit/sirit.h" - -#include "stream.h" - -namespace Sirit { - -#define DEFINE_UNARY(opcode) \ - Id Module::opcode(Id result_type, Id operand) { \ - code->Reserve(4); \ - return *code << OpId{spv::Op::opcode, result_type} << operand << EndOp{}; \ - } - -#define DEFINE_BINARY(opcode) \ - Id Module::opcode(Id result_type, Id operand_1, Id operand_2) { \ - code->Reserve(5); \ - return *code << OpId{spv::Op::opcode, result_type} << operand_1 << operand_2 << EndOp{}; \ - } - -#define DEFINE_TRINARY(opcode) \ - Id Module::opcode(Id result_type, Id operand_1, Id operand_2, Id operand_3) { \ - code->Reserve(6); \ - return *code << OpId{spv::Op::opcode, result_type} << operand_1 << operand_2 << operand_3 \ - << EndOp{}; \ - } - -DEFINE_UNARY(OpAny) -DEFINE_UNARY(OpAll) -DEFINE_UNARY(OpIsNan) -DEFINE_UNARY(OpIsInf) -DEFINE_BINARY(OpLogicalEqual) -DEFINE_BINARY(OpLogicalNotEqual) -DEFINE_BINARY(OpLogicalOr) -DEFINE_BINARY(OpLogicalAnd) -DEFINE_UNARY(OpLogicalNot) -DEFINE_TRINARY(OpSelect) -DEFINE_BINARY(OpIEqual) -DEFINE_BINARY(OpINotEqual) -DEFINE_BINARY(OpUGreaterThan) -DEFINE_BINARY(OpSGreaterThan) -DEFINE_BINARY(OpUGreaterThanEqual) -DEFINE_BINARY(OpSGreaterThanEqual) -DEFINE_BINARY(OpULessThan) -DEFINE_BINARY(OpSLessThan) -DEFINE_BINARY(OpULessThanEqual) -DEFINE_BINARY(OpSLessThanEqual) -DEFINE_BINARY(OpFOrdEqual) -DEFINE_BINARY(OpFUnordEqual) -DEFINE_BINARY(OpFOrdNotEqual) -DEFINE_BINARY(OpFUnordNotEqual) -DEFINE_BINARY(OpFOrdLessThan) -DEFINE_BINARY(OpFUnordLessThan) -DEFINE_BINARY(OpFOrdGreaterThan) -DEFINE_BINARY(OpFUnordGreaterThan) -DEFINE_BINARY(OpFOrdLessThanEqual) -DEFINE_BINARY(OpFUnordLessThanEqual) -DEFINE_BINARY(OpFOrdGreaterThanEqual) -DEFINE_BINARY(OpFUnordGreaterThanEqual) - -} // namespace Sirit diff --git a/src/instructions/memory.cpp b/src/instructions/memory.cpp deleted file mode 100644 index a542e9f..0000000 --- a/src/instructions/memory.cpp +++ /dev/null @@ -1,68 +0,0 @@ -/* This file is part of the sirit project. - * Copyright (c) 2019 sirit - * This software may be used and distributed according to the terms of the - * 3-Clause BSD License - */ - -#include - -#include "sirit/sirit.h" - -#include "stream.h" - -namespace Sirit { - -Id Module::OpImageTexelPointer(Id result_type, Id image, Id coordinate, Id sample) { - code->Reserve(6); - return *code << OpId{spv::Op::OpImageTexelPointer, result_type} << image << coordinate << sample - << EndOp{}; -} - -Id Module::OpLoad(Id result_type, Id pointer, std::optional memory_access) { - code->Reserve(5); - return *code << OpId{spv::Op::OpLoad, result_type} << pointer << memory_access << EndOp{}; -} - -Id Module::OpStore(Id pointer, Id object, std::optional memory_access) { - code->Reserve(4); - return *code << spv::Op::OpStore << pointer << object << memory_access << EndOp{}; -} - -Id Module::OpAccessChain(Id result_type, Id base, std::span indexes) { - assert(!indexes.empty()); - code->Reserve(4 + indexes.size()); - return *code << OpId{spv::Op::OpAccessChain, result_type} << base << indexes << EndOp{}; -} - -Id Module::OpVectorExtractDynamic(Id result_type, Id vector, Id index) { - code->Reserve(5); - return *code << OpId{spv::Op::OpVectorExtractDynamic, result_type} << vector << index - << EndOp{}; -} - -Id Module::OpVectorInsertDynamic(Id result_type, Id vector, Id component, Id index) { - code->Reserve(6); - return *code << OpId{spv::Op::OpVectorInsertDynamic, result_type} << vector << component - << index << EndOp{}; -} - -Id Module::OpCompositeInsert(Id result_type, Id object, Id composite, - std::span indexes) { - code->Reserve(5 + indexes.size()); - return *code << OpId{spv::Op::OpCompositeInsert, result_type} << object << composite << indexes - << EndOp{}; -} - -Id Module::OpCompositeExtract(Id result_type, Id composite, std::span indexes) { - code->Reserve(4 + indexes.size()); - return *code << OpId{spv::Op::OpCompositeExtract, result_type} << composite << indexes - << EndOp{}; -} - -Id Module::OpCompositeConstruct(Id result_type, std::span ids) { - assert(ids.size() >= 1); - code->Reserve(3 + ids.size()); - return *code << OpId{spv::Op::OpCompositeConstruct, result_type} << ids << EndOp{}; -} - -} // namespace Sirit diff --git a/src/instructions/misc.cpp b/src/instructions/misc.cpp deleted file mode 100644 index 6785346..0000000 --- a/src/instructions/misc.cpp +++ /dev/null @@ -1,38 +0,0 @@ -/* This file is part of the sirit project. - * Copyright (c) 2019 sirit - * This software may be used and distributed according to the terms of the - * 3-Clause BSD License - */ - -#include "sirit/sirit.h" - -#include "stream.h" - -namespace Sirit { - -Id Module::OpUndef(Id result_type) { - code->Reserve(3); - return *code << OpId{spv::Op::OpUndef, result_type} << EndOp{}; -} - -void Module::OpEmitVertex() { - code->Reserve(1); - *code << spv::Op::OpEmitVertex << EndOp{}; -} - -void Module::OpEndPrimitive() { - code->Reserve(1); - *code << spv::Op::OpEndPrimitive << EndOp{}; -} - -void Module::OpEmitStreamVertex(Id stream) { - code->Reserve(2); - *code << spv::Op::OpEmitStreamVertex << stream << EndOp{}; -} - -void Module::OpEndStreamPrimitive(Id stream) { - code->Reserve(2); - *code << spv::Op::OpEndStreamPrimitive << stream << EndOp{}; -} - -} // namespace Sirit diff --git a/src/instructions/type.cpp b/src/instructions/type.cpp deleted file mode 100644 index 3509d1f..0000000 --- a/src/instructions/type.cpp +++ /dev/null @@ -1,130 +0,0 @@ -/* This file is part of the sirit project. - * Copyright (c) 2019 sirit - * This software may be used and distributed according to the terms of the - * 3-Clause BSD License - */ - -#include -#include - -#include "sirit/sirit.h" - -#include "stream.h" - -namespace Sirit { - -Id Module::TypeVoid() { - declarations->Reserve(2); - return *declarations << OpId{spv::Op::OpTypeVoid} << EndOp{}; -} - -Id Module::TypeBool() { - declarations->Reserve(2); - return *declarations << OpId{spv::Op::OpTypeBool} << EndOp{}; -} - -Id Module::TypeInt(int width, bool is_signed) { - declarations->Reserve(4); - return *declarations << OpId{spv::Op::OpTypeInt} << width << is_signed << EndOp{}; -} - -Id Module::TypeSInt(int width) { - return TypeInt(width, true); -} - -Id Module::TypeUInt(int width) { - return TypeInt(width, false); -} - -Id Module::TypeFloat(int width) { - declarations->Reserve(3); - return *declarations << OpId{spv::Op::OpTypeFloat} << width << EndOp{}; -} - -Id Module::TypeVector(Id component_type, int component_count) { - assert(component_count >= 2); - declarations->Reserve(4); - return *declarations << OpId{spv::Op::OpTypeVector} << component_type << component_count - << EndOp{}; -} - -Id Module::TypeMatrix(Id column_type, int column_count) { - assert(column_count >= 2); - declarations->Reserve(4); - return *declarations << OpId{spv::Op::OpTypeMatrix} << column_type << column_count << EndOp{}; -} - -Id Module::TypeImage(Id sampled_type, spv::Dim dim, int depth, bool arrayed, bool ms, int sampled, - spv::ImageFormat image_format, - std::optional access_qualifier) { - declarations->Reserve(10); - return *declarations << OpId{spv::Op::OpTypeImage} << sampled_type << dim << depth << arrayed - << ms << sampled << image_format << access_qualifier << EndOp{}; -} - -Id Module::TypeSampler() { - declarations->Reserve(2); - return *declarations << OpId{spv::Op::OpTypeSampler} << EndOp{}; -} - -Id Module::TypeSampledImage(Id image_type) { - declarations->Reserve(3); - return *declarations << OpId{spv::Op::OpTypeSampledImage} << image_type << EndOp{}; -} - -Id Module::TypeArray(Id element_type, Id length) { - declarations->Reserve(4); - return *declarations << OpId{spv::Op::OpTypeArray} << element_type << length << EndOp{}; -} - -Id Module::TypeRuntimeArray(Id element_type) { - declarations->Reserve(3); - return *declarations << OpId{spv::Op::OpTypeRuntimeArray} << element_type << EndOp{}; -} - -Id Module::TypeStruct(std::span members) { - declarations->Reserve(2 + members.size()); - return *declarations << OpId{spv::Op::OpTypeStruct} << members << EndOp{}; -} - -Id Module::TypeOpaque(std::string_view name) { - declarations->Reserve(3 + WordsInString(name)); - return *declarations << OpId{spv::Op::OpTypeOpaque} << name << EndOp{}; -} - -Id Module::TypePointer(spv::StorageClass storage_class, Id type) { - declarations->Reserve(4); - return *declarations << OpId{spv::Op::OpTypePointer} << storage_class << type << EndOp{}; -} - -Id Module::TypeFunction(Id return_type, std::span arguments) { - declarations->Reserve(3 + arguments.size()); - return *declarations << OpId{spv::Op::OpTypeFunction} << return_type << arguments << EndOp{}; -} - -Id Module::TypeEvent() { - declarations->Reserve(2); - return *declarations << OpId{spv::Op::OpTypeEvent} << EndOp{}; -} - -Id Module::TypeDeviceEvent() { - declarations->Reserve(2); - return *declarations << OpId{spv::Op::OpTypeDeviceEvent} << EndOp{}; -} - -Id Module::TypeReserveId() { - declarations->Reserve(2); - return *declarations << OpId{spv::Op::OpTypeReserveId} << EndOp{}; -} - -Id Module::TypeQueue() { - declarations->Reserve(2); - return *declarations << OpId{spv::Op::OpTypeQueue} << EndOp{}; -} - -Id Module::TypePipe(spv::AccessQualifier access_qualifier) { - declarations->Reserve(2); - return *declarations << OpId{spv::Op::OpTypePipe} << access_qualifier << EndOp{}; -} - -} // namespace Sirit diff --git a/src/sirit.cpp b/src/sirit.cpp index 7075f23..2a687c0 100644 --- a/src/sirit.cpp +++ b/src/sirit.cpp @@ -132,11 +132,50 @@ Id Module::AddGlobalVariable(Id result_type, spv::StorageClass storage_class, Id Module::GetGLSLstd450() { if (!glsl_std_450) { - ext_inst_imports->Reserve(3 + 4); - glsl_std_450 = *ext_inst_imports << OpId{spv::Op::OpExtInstImport} << "GLSL.std.450" - << EndOp{}; + const std::string_view name = "GLSL.std.450"; + ext_inst_imports->Reserve(3 + WordsInString(name)); + glsl_std_450 = *ext_inst_imports << OpId{spv::Op::OpExtInstImport} << name << EndOp{}; } return *glsl_std_450; } +Id Module::GetNonSemanticDebugPrintf() { + if (!non_semantic_debug_printf) { + const std::string_view name = "NonSemantic.DebugPrintf"; + ext_inst_imports->Reserve(3 + WordsInString(name)); + non_semantic_debug_printf = + *ext_inst_imports << OpId{spv::Op::OpExtInstImport} << name << EndOp{}; + } + return *non_semantic_debug_printf; +} + +Id Module::GetAmdGcnShader() { + if (!amd_gcn_shader) { + const std::string_view name = "SPV_AMD_gcn_shader"; + ext_inst_imports->Reserve(3 + WordsInString(name)); + amd_gcn_shader = *ext_inst_imports << OpId{spv::Op::OpExtInstImport} << name << EndOp{}; + } + return *amd_gcn_shader; +} + +Id Module::GetAmdShaderTrinaryMinMax() { + if (!amd_shader_trinary_minmax) { + const std::string_view name = "SPV_AMD_shader_trinary_minmax"; + ext_inst_imports->Reserve(3 + WordsInString(name)); + amd_shader_trinary_minmax = + *ext_inst_imports << OpId{spv::Op::OpExtInstImport} << name << EndOp{}; + } + return *amd_shader_trinary_minmax; +} + +Id Module::GetAmdExplicitVertexParameter() { + if (!amd_explicit_vertex_parameter) { + const std::string_view name = "SPV_AMD_shader_explicit_vertex_parameter"; + ext_inst_imports->Reserve(3 + WordsInString(name)); + amd_explicit_vertex_parameter = + *ext_inst_imports << OpId{spv::Op::OpExtInstImport} << name << EndOp{}; + } + return *amd_explicit_vertex_parameter; +} + } // namespace Sirit diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index a3f9534..37b8daa 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,6 +1,79 @@ +# Tests pull in two extra dependencies via FetchContent so that consumers of +# the library never need them: SPIRV-Tools (for spirv-val + disassembly) and +# SPIRV-Cross (for round-trip cross-compilation back to GLSL/HLSL). The test +# target uses these to verify the SPIR-V we generate is valid for ray tracing +# and cooperative matrix workloads. + +include(FetchContent) + +# ---- SPIRV-Tools -------------------------------------------------------- +# Reuse the SPIRV-Headers we already have at externals/SPIRV-Headers, so +# SPIRV-Tools doesn't fetch a duplicate copy. +set(SPIRV_SKIP_EXECUTABLES ON CACHE BOOL "" FORCE) +set(SPIRV_SKIP_TESTS ON CACHE BOOL "" FORCE) +set(SPIRV_WERROR OFF CACHE BOOL "" FORCE) +set(SPIRV_BUILD_FUZZER OFF CACHE BOOL "" FORCE) +set(BUILD_TESTING OFF CACHE BOOL "" FORCE) + +# Pin to main; any SPIRV-Tools release older than the SPIRV-Headers we bundle +# will fail to compile because the auto-generated tables reference operand +# kinds (CooperativeVector, Tensor, MatMul) that older SPIRV-Tools doesn't +# declare. main tracks SPIRV-Headers closely. +FetchContent_Declare( + SPIRV-Tools + GIT_REPOSITORY https://github.com/KhronosGroup/SPIRV-Tools.git + GIT_TAG main + GIT_SHALLOW TRUE +) +FetchContent_MakeAvailable(SPIRV-Tools) + +# ---- SPIRV-Cross -------------------------------------------------------- +set(SPIRV_CROSS_CLI OFF CACHE BOOL "" FORCE) +set(SPIRV_CROSS_ENABLE_TESTS OFF CACHE BOOL "" FORCE) +set(SPIRV_CROSS_ENABLE_GLSL ON CACHE BOOL "" FORCE) +set(SPIRV_CROSS_ENABLE_HLSL ON CACHE BOOL "" FORCE) +set(SPIRV_CROSS_ENABLE_MSL OFF CACHE BOOL "" FORCE) +set(SPIRV_CROSS_ENABLE_CPP OFF CACHE BOOL "" FORCE) +set(SPIRV_CROSS_ENABLE_REFLECT OFF CACHE BOOL "" FORCE) +set(SPIRV_CROSS_ENABLE_C_API OFF CACHE BOOL "" FORCE) +set(SPIRV_CROSS_ENABLE_UTIL OFF CACHE BOOL "" FORCE) +set(SPIRV_CROSS_SKIP_INSTALL ON CACHE BOOL "" FORCE) + +FetchContent_Declare( + SPIRV-Cross + GIT_REPOSITORY https://github.com/KhronosGroup/SPIRV-Cross.git + GIT_TAG vulkan-sdk-1.3.290.0 + GIT_SHALLOW TRUE +) +FetchContent_MakeAvailable(SPIRV-Cross) + +# ---- sirit_tests -------------------------------------------------------- add_executable(sirit_tests - main.cpp) -target_link_libraries(sirit_tests PRIVATE sirit) + main.cpp + test_helpers.h + test_helpers.cpp + test_cross.h + test_cross.cpp + test_cooperative_matrix.cpp + test_raytracing.cpp + test_vertex_shader_replicated.cpp + test_fragment_shader_replicated.cpp + test_compute_shader_replicated.cpp +) +target_link_libraries(sirit_tests PRIVATE + sirit + SPIRV-Tools-static + spirv-cross-glsl + spirv-cross-hlsl + spirv-cross-core +) target_include_directories(sirit_tests PRIVATE . ../include) +# Ensure the tests find SPIRV-Tools headers (FetchContent_MakeAvailable +# doesn't propagate the include directory through the static target on all +# CMake versions). +target_include_directories(sirit_tests PRIVATE + ${spirv-tools_SOURCE_DIR}/include +) + add_test(sirit_tests sirit_tests) diff --git a/tests/main.cpp b/tests/main.cpp index e15eda4..018cb2f 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -12,29 +12,11 @@ #include -namespace { +#include "test_helpers.h" + +using namespace sirit_tests; -int g_total = 0; -int g_failures = 0; -const char* g_current_test = ""; - -#define CHECK(cond) \ - do { \ - ++g_total; \ - if (!(cond)) { \ - ++g_failures; \ - std::fprintf(stderr, " FAIL [%s] %s:%d: %s\n", g_current_test, __FILE__, __LINE__, \ - #cond); \ - } \ - } while (0) - -#define RUN_TEST(fn) \ - do { \ - g_current_test = #fn; \ - const int before = g_failures; \ - fn(); \ - std::fprintf(stderr, "%-32s %s\n", #fn, g_failures == before ? "ok" : "FAILED"); \ - } while (0) +namespace { struct Instruction { spv::Op opcode; @@ -325,7 +307,15 @@ void test_compute_shader_execution_mode() { CHECK(found_entry_point); } -} // namespace +} // namespace + +namespace sirit_tests { +void RegisterCooperativeMatrixTests(); +void RegisterRayTracingTests(); +void RegisterVertexShaderReplicationTests(); +void RegisterFragmentShaderReplicationTests(); +void RegisterComputeShaderReplicationTests(); +} // namespace sirit_tests int main() { RUN_TEST(test_vertex_shader_golden); @@ -337,6 +327,13 @@ int main() { RUN_TEST(test_constant_kinds); RUN_TEST(test_compute_shader_execution_mode); - std::fprintf(stderr, "\n%d/%d checks passed\n", g_total - g_failures, g_total); - return g_failures == 0 ? EXIT_SUCCESS : EXIT_FAILURE; + sirit_tests::RegisterCooperativeMatrixTests(); + sirit_tests::RegisterRayTracingTests(); + sirit_tests::RegisterVertexShaderReplicationTests(); + sirit_tests::RegisterFragmentShaderReplicationTests(); + sirit_tests::RegisterComputeShaderReplicationTests(); + + std::fprintf(stderr, "\n%d/%d checks passed\n", + sirit_tests::g_total - sirit_tests::g_failures, sirit_tests::g_total); + return sirit_tests::g_failures == 0 ? EXIT_SUCCESS : EXIT_FAILURE; } diff --git a/tests/test_compute_shader_replicated.cpp b/tests/test_compute_shader_replicated.cpp new file mode 100644 index 0000000..774a2f6 --- /dev/null +++ b/tests/test_compute_shader_replicated.cpp @@ -0,0 +1,144 @@ +/* Compute-shader replication test. + * + * Spec: a compute shader (LocalSize 64, 1, 1) that writes the constant 42 to + * SSBO[gl_GlobalInvocationID.x]. Demonstrates the StorageBuffer storage class + * (requires SPIR-V 1.3 baseline), a runtime-array-of-uint inside a struct + * decorated as Block, descriptor-set / binding decorations, and a chained + * OpAccessChain. + */ + +#include +#include +#include + +#include + +#include "test_helpers.h" + +using namespace sirit_tests; + +namespace { + +// SPIR-V 1.4 -- needed because OpEntryPoint's interface list contains the +// StorageBuffer SSBO variable. Earlier versions restrict the interface to +// Input/Output variables only. +constexpr std::uint32_t SpvVersion14 = 0x00010400; + +const char* kComputeSpvText = R"( + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %main "main" %gl_id %ssbo + OpExecutionMode %main LocalSize 64 1 1 + OpDecorate %gl_id BuiltIn GlobalInvocationId + OpDecorate %arr_uint ArrayStride 4 + OpDecorate %SSBO Block + OpMemberDecorate %SSBO 0 Offset 0 + OpDecorate %ssbo DescriptorSet 0 + OpDecorate %ssbo Binding 0 + %void = OpTypeVoid + %fn = OpTypeFunction %void + %uint = OpTypeInt 32 0 + %v3uint = OpTypeVector %uint 3 +%_ptr_Input_v3uint = OpTypePointer Input %v3uint + %gl_id = OpVariable %_ptr_Input_v3uint Input +%_ptr_Input_uint = OpTypePointer Input %uint + %u_0 = OpConstant %uint 0 + %u_42 = OpConstant %uint 42 + %arr_uint = OpTypeRuntimeArray %uint + %SSBO = OpTypeStruct %arr_uint +%_ptr_StorageBuffer_SSBO = OpTypePointer StorageBuffer %SSBO + %ssbo = OpVariable %_ptr_StorageBuffer_SSBO StorageBuffer +%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint + %main = OpFunction %void None %fn + %entry = OpLabel + %idx_ptr = OpAccessChain %_ptr_Input_uint %gl_id %u_0 + %idx = OpLoad %uint %idx_ptr + %out_ptr = OpAccessChain %_ptr_StorageBuffer_uint %ssbo %u_0 %idx + OpStore %out_ptr %u_42 + OpReturn + OpFunctionEnd +)"; + +std::vector BuildComputeShaderWithSirit() { + Sirit::Module m{SpvVersion14}; + + m.AddCapability(spv::Capability::Shader); + m.SetMemoryModel(spv::AddressingModel::Logical, spv::MemoryModel::GLSL450); + + const auto t_void = m.TypeVoid(); + const auto t_fn = m.TypeFunction(t_void); + const auto t_uint = m.TypeInt(32, false); + const auto t_v3uint = m.TypeVector(t_uint, 3); + const auto t_ptr_in_v3uint = m.TypePointer(spv::StorageClass::Input, t_v3uint); + const auto t_ptr_in_uint = m.TypePointer(spv::StorageClass::Input, t_uint); + const auto t_arr_uint = m.TypeRuntimeArray(t_uint); + const auto t_ssbo = m.TypeStruct(t_arr_uint); + const auto t_ptr_sb_ssbo = m.TypePointer(spv::StorageClass::StorageBuffer, t_ssbo); + const auto t_ptr_sb_uint = m.TypePointer(spv::StorageClass::StorageBuffer, t_uint); + + const auto gl_id = m.AddGlobalVariable(t_ptr_in_v3uint, spv::StorageClass::Input); + const auto ssbo = m.AddGlobalVariable(t_ptr_sb_ssbo, spv::StorageClass::StorageBuffer); + + m.Decorate(gl_id, spv::Decoration::BuiltIn, + static_cast(spv::BuiltIn::GlobalInvocationId)); + m.Decorate(t_arr_uint, spv::Decoration::ArrayStride, std::uint32_t{4}); + m.Decorate(t_ssbo, spv::Decoration::Block); + m.MemberDecorate(t_ssbo, 0, spv::Decoration::Offset, std::uint32_t{0}); + m.Decorate(ssbo, spv::Decoration::DescriptorSet, std::uint32_t{0}); + m.Decorate(ssbo, spv::Decoration::Binding, std::uint32_t{0}); + + const auto u_0 = m.Constant(t_uint, std::uint32_t{0}); + const auto u_42 = m.Constant(t_uint, std::uint32_t{42}); + + const auto main_fn = m.OpFunction(t_void, spv::FunctionControlMask::MaskNone, t_fn); + m.AddLabel(); + const auto idx_ptr = m.OpAccessChain(t_ptr_in_uint, gl_id, u_0); + const auto idx = m.OpLoad(t_uint, idx_ptr); + const auto out_ptr = m.OpAccessChain(t_ptr_sb_uint, ssbo, u_0, idx); + m.OpStore(out_ptr, u_42); + m.OpReturn(); + m.OpFunctionEnd(); + + m.AddEntryPoint(spv::ExecutionModel::GLCompute, main_fn, "main", gl_id, ssbo); + m.AddExecutionMode(main_fn, spv::ExecutionMode::LocalSize, std::uint32_t{64}, + std::uint32_t{1}, std::uint32_t{1}); + + return m.Assemble(); +} + +void test_compute_shader_replicates_reference() { + const auto reference = AssembleText(kComputeSpvText, SPV_ENV_VULKAN_1_2); + if (!reference.ok) { + std::fputs(reference.error.c_str(), stderr); + } + CHECK(reference.ok); + if (!reference.ok) return; + + const auto ours = BuildComputeShaderWithSirit(); + + const auto ref_val = Validate(reference.words, SPV_ENV_VULKAN_1_2); + if (!ref_val.ok) std::fputs(ref_val.messages.c_str(), stderr); + CHECK(ref_val.ok); + + const auto our_val = Validate(ours, SPV_ENV_VULKAN_1_2); + if (!our_val.ok) std::fputs(our_val.messages.c_str(), stderr); + CHECK(our_val.ok); + + const auto ref_sum = Summarize(reference.words); + const auto our_sum = Summarize(ours); + const auto diff = SummaryDiff(ref_sum, our_sum); + if (!diff.empty()) { + std::fputs(diff.c_str(), stderr); + } + CHECK(ref_sum == our_sum); +} + +} // namespace + +namespace sirit_tests { + +void RegisterComputeShaderReplicationTests() { + RUN_TEST(test_compute_shader_replicates_reference); +} + +} // namespace sirit_tests diff --git a/tests/test_cooperative_matrix.cpp b/tests/test_cooperative_matrix.cpp new file mode 100644 index 0000000..d61a643 --- /dev/null +++ b/tests/test_cooperative_matrix.cpp @@ -0,0 +1,119 @@ +/* Cooperative-matrix shader generation tests. + * + * Build a compute kernel that: + * 1. declares OpTypeCooperativeMatrixKHR (component=float32, scope=Subgroup, + * rows=cols=16) for MatrixA / MatrixB / MatrixAccumulator; + * 2. obtains zero-valued matrices via OpConstantNull; + * 3. multiplies via OpCooperativeMatrixMulAddKHR. + * + * The kernel does not touch global memory -- the goal is to verify sirit + * generates a structurally valid module that spirv-val accepts under the + * SPV_KHR_cooperative_matrix capability gates. We also disassemble and look + * for the expected opcodes / type id reuse. + */ + +#include +#include +#include +#include + +#include + +#include "test_helpers.h" + +using namespace sirit_tests; + +namespace { + +constexpr std::uint32_t SpvVersion16 = 0x00010600; // SPIR-V 1.6 (matches Vulkan 1.3) + +// Build the module described in this file's header. Returns the assembled +// SPIR-V words plus the result-type Id so the caller can spot-check it in the +// disassembly. +std::vector BuildCoopMatModule() { + Sirit::Module m{SpvVersion16}; + + m.AddCapability(spv::Capability::Shader); + m.AddCapability(spv::Capability::VulkanMemoryModel); + m.AddCapability(spv::Capability::CooperativeMatrixKHR); + m.AddExtension("SPV_KHR_cooperative_matrix"); + m.AddExtension("SPV_KHR_vulkan_memory_model"); + m.SetMemoryModel(spv::AddressingModel::Logical, spv::MemoryModel::Vulkan); + + const auto t_void = m.TypeVoid(); + const auto t_uint = m.TypeInt(32, false); + const auto t_float = m.TypeFloat(32); + + // Cooperative-matrix dimension / scope / use arguments must be specialization + // ids referring to OpConstants. Subgroup scope value = 3. + const auto c_scope_subgroup = m.Constant(t_uint, static_cast(spv::Scope::Subgroup)); + const auto c_dim_16 = m.Constant(t_uint, std::uint32_t{16}); + const auto c_use_a = m.Constant(t_uint, static_cast( + spv::CooperativeMatrixUse::MatrixAKHR)); + const auto c_use_b = m.Constant(t_uint, static_cast( + spv::CooperativeMatrixUse::MatrixBKHR)); + const auto c_use_acc = m.Constant(t_uint, static_cast( + spv::CooperativeMatrixUse::MatrixAccumulatorKHR)); + + const auto t_mat_a = m.TypeCooperativeMatrixKHR(t_float, c_scope_subgroup, c_dim_16, + c_dim_16, c_use_a); + const auto t_mat_b = m.TypeCooperativeMatrixKHR(t_float, c_scope_subgroup, c_dim_16, + c_dim_16, c_use_b); + const auto t_mat_c = m.TypeCooperativeMatrixKHR(t_float, c_scope_subgroup, c_dim_16, + c_dim_16, c_use_acc); + + // Type-dedup spot check before any function body lands. + const auto t_mat_a_again = m.TypeCooperativeMatrixKHR(t_float, c_scope_subgroup, c_dim_16, + c_dim_16, c_use_a); + CHECK(t_mat_a.value == t_mat_a_again.value); + + const auto t_func = m.TypeFunction(t_void); + const auto fn = m.OpFunction(t_void, spv::FunctionControlMask::MaskNone, t_func); + m.Name(fn, "main"); + m.AddLabel(); + + const auto null_a = m.ConstantNull(t_mat_a); + const auto null_b = m.ConstantNull(t_mat_b); + const auto null_c = m.ConstantNull(t_mat_c); + + const auto product = m.OpCooperativeMatrixMulAddKHR(t_mat_c, null_a, null_b, null_c, + std::nullopt); + (void)product; + + m.OpReturn(); + m.OpFunctionEnd(); + + m.AddEntryPoint(spv::ExecutionModel::GLCompute, fn, "main"); + m.AddExecutionMode(fn, spv::ExecutionMode::LocalSize, std::uint32_t{32}, std::uint32_t{1}, + std::uint32_t{1}); + return m.Assemble(); +} + +void test_coopmat_module_validates() { + const auto words = BuildCoopMatModule(); + const auto result = Validate(words, SPV_ENV_VULKAN_1_3); + if (!result.ok) { + std::fputs(result.messages.c_str(), stderr); + } + CHECK(result.ok); +} + +void test_coopmat_disassembly_contains_expected_ops() { + const auto words = BuildCoopMatModule(); + const std::string text = Disassemble(words, SPV_ENV_VULKAN_1_3); + CHECK(text.find("OpTypeCooperativeMatrixKHR") != std::string::npos); + CHECK(text.find("OpCooperativeMatrixMulAddKHR") != std::string::npos); + CHECK(text.find("CooperativeMatrixKHR") != std::string::npos); // capability + CHECK(text.find("\"SPV_KHR_cooperative_matrix\"") != std::string::npos); +} + +} // namespace + +namespace sirit_tests { + +void RegisterCooperativeMatrixTests() { + RUN_TEST(test_coopmat_module_validates); + RUN_TEST(test_coopmat_disassembly_contains_expected_ops); +} + +} // namespace sirit_tests diff --git a/tests/test_cross.cpp b/tests/test_cross.cpp new file mode 100644 index 0000000..d05e482 --- /dev/null +++ b/tests/test_cross.cpp @@ -0,0 +1,42 @@ +/* Implementation of CrossToGlsl. Isolated to its own TU so the + * unscoped-enum spirv.hpp pulled in by SPIRV-Cross doesn't conflict with + * sirit's spirv.hpp11 (both share the include guard `spirv_HPP`). This file + * must not include test_helpers.h or any sirit header. + */ + +#include "test_cross.h" + +#include +#include +#include + +#include +#include + +namespace sirit_tests { + +CrossCompileResult CrossToGlsl(std::span words, + std::uint32_t glsl_version, + bool vulkan_semantics) { + CrossCompileResult result; + try { + std::vector copy(words.begin(), words.end()); + spirv_cross::CompilerGLSL compiler(std::move(copy)); + spirv_cross::CompilerGLSL::Options opts; + opts.version = glsl_version; + opts.vulkan_semantics = vulkan_semantics; + opts.es = false; + compiler.set_common_options(opts); + result.source = compiler.compile(); + result.ok = true; + } catch (const std::exception& e) { + result.ok = false; + result.error = e.what(); + } catch (...) { + result.ok = false; + result.error = ""; + } + return result; +} + +} // namespace sirit_tests diff --git a/tests/test_cross.h b/tests/test_cross.h new file mode 100644 index 0000000..9bd54b2 --- /dev/null +++ b/tests/test_cross.h @@ -0,0 +1,31 @@ +/* SPIRV-Cross interface. Kept in its own header (separate from test_helpers.h) + * because (the unscoped enum form bundled with SPIRV-Cross) and + * (the scoped enum form sirit's API uses) share + * the same include guard `spirv_HPP`. Whichever one is #included first wins, + * leaving the other's symbols undefined and breaking the build. + * + * test_cross.cpp must NOT include test_helpers.h or any sirit header to keep + * spirv.hpp11 out of its translation unit. + */ + +#pragma once + +#include +#include +#include + +namespace sirit_tests { + +struct CrossCompileResult { + bool ok = false; + std::string source; // GLSL source on success + std::string error; // exception message on failure +}; + +/// Run SPIRV-Cross's GLSL backend against `words`. Used as a sanity check that +/// the produced module is structurally consumable by an external translator. +CrossCompileResult CrossToGlsl(std::span words, + std::uint32_t glsl_version = 460, + bool vulkan_semantics = true); + +} // namespace sirit_tests diff --git a/tests/test_fragment_shader_replicated.cpp b/tests/test_fragment_shader_replicated.cpp new file mode 100644 index 0000000..8f47396 --- /dev/null +++ b/tests/test_fragment_shader_replicated.cpp @@ -0,0 +1,110 @@ +/* Fragment-shader replication test. + * + * Spec: a fragment shader that writes a constant red color (1, 0, 0, 1) to + * output Location 0. Demonstrates the OriginUpperLeft execution mode, an + * Output v4float variable, and OpConstantComposite. + */ + +#include +#include +#include + +#include + +#include "test_helpers.h" + +using namespace sirit_tests; + +namespace { + +constexpr std::uint32_t SpvVersion10 = 0x00010000; + +const char* kFragmentSpvText = R"( + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %out_color + OpExecutionMode %main OriginUpperLeft + OpDecorate %out_color Location 0 + %void = OpTypeVoid + %fn = OpTypeFunction %void + %float = OpTypeFloat 32 + %v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float + %out_color = OpVariable %_ptr_Output_v4float Output + %f0_0 = OpConstant %float 0 + %f1_0 = OpConstant %float 1 + %color = OpConstantComposite %v4float %f1_0 %f0_0 %f0_0 %f1_0 + %main = OpFunction %void None %fn + %entry = OpLabel + OpStore %out_color %color + OpReturn + OpFunctionEnd +)"; + +std::vector BuildFragmentShaderWithSirit() { + Sirit::Module m{SpvVersion10}; + + m.AddCapability(spv::Capability::Shader); + m.SetMemoryModel(spv::AddressingModel::Logical, spv::MemoryModel::GLSL450); + + const auto t_void = m.TypeVoid(); + const auto t_fn = m.TypeFunction(t_void); + const auto t_float = m.TypeFloat(32); + const auto t_v4float = m.TypeVector(t_float, 4); + const auto t_ptr_out_v4 = m.TypePointer(spv::StorageClass::Output, t_v4float); + + const auto out_color = m.AddGlobalVariable(t_ptr_out_v4, spv::StorageClass::Output); + m.Decorate(out_color, spv::Decoration::Location, std::uint32_t{0}); + + const auto c_0 = m.Constant(t_float, 0.0f); + const auto c_1 = m.Constant(t_float, 1.0f); + const auto color = m.ConstantComposite(t_v4float, c_1, c_0, c_0, c_1); + + const auto main_fn = m.OpFunction(t_void, spv::FunctionControlMask::MaskNone, t_fn); + m.AddLabel(); + m.OpStore(out_color, color); + m.OpReturn(); + m.OpFunctionEnd(); + + m.AddEntryPoint(spv::ExecutionModel::Fragment, main_fn, "main", out_color); + m.AddExecutionMode(main_fn, spv::ExecutionMode::OriginUpperLeft); + + return m.Assemble(); +} + +void test_fragment_shader_replicates_reference() { + const auto reference = AssembleText(kFragmentSpvText, SPV_ENV_VULKAN_1_0); + if (!reference.ok) { + std::fputs(reference.error.c_str(), stderr); + } + CHECK(reference.ok); + if (!reference.ok) return; + + const auto ours = BuildFragmentShaderWithSirit(); + + const auto ref_val = Validate(reference.words, SPV_ENV_VULKAN_1_0); + if (!ref_val.ok) std::fputs(ref_val.messages.c_str(), stderr); + CHECK(ref_val.ok); + + const auto our_val = Validate(ours, SPV_ENV_VULKAN_1_0); + if (!our_val.ok) std::fputs(our_val.messages.c_str(), stderr); + CHECK(our_val.ok); + + const auto ref_sum = Summarize(reference.words); + const auto our_sum = Summarize(ours); + const auto diff = SummaryDiff(ref_sum, our_sum); + if (!diff.empty()) { + std::fputs(diff.c_str(), stderr); + } + CHECK(ref_sum == our_sum); +} + +} // namespace + +namespace sirit_tests { + +void RegisterFragmentShaderReplicationTests() { + RUN_TEST(test_fragment_shader_replicates_reference); +} + +} // namespace sirit_tests diff --git a/tests/test_helpers.cpp b/tests/test_helpers.cpp new file mode 100644 index 0000000..c42bc7e --- /dev/null +++ b/tests/test_helpers.cpp @@ -0,0 +1,269 @@ +/* Implementations of validation / disassembly / cross-compile helpers. + * + * SPIRV-Tools provides a stable C and C++ API; we use the C++ wrapper from + * . SPIRV-Cross is plain C++ headers. + */ + +#include "test_helpers.h" + +#include +#include +#include +#include +#include + +#include + +namespace sirit_tests { + +int g_total = 0; +int g_failures = 0; +const char* g_current_test = ""; + +namespace { + +const char* LevelName(spv_message_level_t level) { + switch (level) { + case SPV_MSG_FATAL: return "fatal"; + case SPV_MSG_INTERNAL_ERROR: return "internal"; + case SPV_MSG_ERROR: return "error"; + case SPV_MSG_WARNING: return "warning"; + case SPV_MSG_INFO: return "info"; + case SPV_MSG_DEBUG: return "debug"; + } + return "?"; +} + +} // namespace + +ValidationResult Validate(std::span words, spv_target_env env) { + ValidationResult result; + spvtools::SpirvTools tools(env); + std::ostringstream messages; + tools.SetMessageConsumer([&messages](spv_message_level_t level, const char* /*src*/, + const spv_position_t& pos, const char* msg) { + messages << " spirv-val [" << LevelName(level) << "] @" + << pos.line << ":" << pos.column << ": " << msg << '\n'; + }); + result.ok = tools.Validate(words.data(), words.size()); + if (!result.ok) { + // Append a disassembly so failures are debuggable without rerunning. + std::string dis; + tools.Disassemble(words.data(), words.size(), &dis, + SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES | + SPV_BINARY_TO_TEXT_OPTION_INDENT | + SPV_BINARY_TO_TEXT_OPTION_COMMENT); + messages << "\n--- module disassembly ---\n" << dis; + } + result.messages = messages.str(); + return result; +} + +std::string Disassemble(std::span words, spv_target_env env) { + spvtools::SpirvTools tools(env); + std::string out; + tools.Disassemble(words.data(), words.size(), &out, + SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES | + SPV_BINARY_TO_TEXT_OPTION_INDENT); + return out; +} + +AssembleResult AssembleText(const std::string& text, spv_target_env env) { + AssembleResult result; + spvtools::SpirvTools tools(env); + std::ostringstream messages; + tools.SetMessageConsumer([&messages](spv_message_level_t level, const char* /*src*/, + const spv_position_t& pos, const char* msg) { + messages << " spirv-as [" << LevelName(level) << "] @" + << pos.line << ":" << pos.column << ": " << msg << '\n'; + }); + result.ok = tools.Assemble(text, &result.words); + if (!result.ok) { + result.error = messages.str(); + } + return result; +} + + +// === Module structural summary ============================================= + +namespace { + +struct ParsedInst { + spv::Op opcode{}; + std::uint32_t word_count{}; + const std::uint32_t* words = nullptr; +}; + +std::vector ParseInstructionsLocal(std::span words) { + std::vector out; + if (words.size() < 5) { + return out; + } + std::size_t i = 5; + while (i < words.size()) { + const std::uint32_t header = words[i]; + const std::uint32_t op = header & 0xffffu; + const std::uint32_t wc = header >> 16; + if (wc == 0 || i + wc > words.size()) { + break; + } + out.push_back({static_cast(op), wc, &words[i]}); + i += wc; + } + return out; +} + +std::string ReadString(const std::uint32_t* words, std::size_t max_words) { + std::string out; + out.reserve(max_words * 4); + for (std::size_t i = 0; i < max_words; ++i) { + const std::uint32_t w = words[i]; + for (int b = 0; b < 4; ++b) { + const char c = static_cast((w >> (b * 8)) & 0xff); + if (c == '\0') { + return out; + } + out.push_back(c); + } + } + return out; +} + +std::size_t WordsForString(const std::uint32_t* words, std::size_t available) { + // Number of u32 words occupied by a packed null-terminated string, + // counting the word that contains the terminator. + for (std::size_t i = 0; i < available; ++i) { + const std::uint32_t w = words[i]; + for (int b = 0; b < 4; ++b) { + if (((w >> (b * 8)) & 0xff) == 0) { + return i + 1; + } + } + } + return available; +} + +} // namespace + +ModuleSummary Summarize(std::span words) { + ModuleSummary sum; + std::set caps; + std::set exts; + bool memory_model_seen = false; + + for (const auto& inst : ParseInstructionsLocal(words)) { + sum.opcode_counts[inst.opcode]++; + switch (inst.opcode) { + case spv::Op::OpCapability: + if (inst.word_count >= 2) { + caps.insert(static_cast(inst.words[1])); + } + break; + case spv::Op::OpExtension: + if (inst.word_count >= 2) { + exts.insert(ReadString(&inst.words[1], inst.word_count - 1)); + } + break; + case spv::Op::OpMemoryModel: + if (inst.word_count >= 3 && !memory_model_seen) { + sum.addressing_model = static_cast(inst.words[1]); + sum.memory_model = static_cast(inst.words[2]); + memory_model_seen = true; + } + break; + case spv::Op::OpEntryPoint: { + if (inst.word_count >= 4) { + EntryPointSummary ep; + ep.model = static_cast(inst.words[1]); + // words[2] = entry-point function id (renumberable; ignore) + // words[3..] = name (LiteralString) followed by interface ids + const std::uint32_t* name_words = &inst.words[3]; + const std::size_t avail = inst.word_count - 3; + const std::size_t name_word_count = WordsForString(name_words, avail); + ep.name = ReadString(name_words, name_word_count); + ep.interface_count = avail - name_word_count; + sum.entry_points.push_back(ep); + } + break; + } + default: + break; + } + } + + sum.capabilities.assign(caps.begin(), caps.end()); + sum.extensions.assign(exts.begin(), exts.end()); + std::sort(sum.entry_points.begin(), sum.entry_points.end(), + [](const EntryPointSummary& a, const EntryPointSummary& b) { + if (a.model != b.model) { + return static_cast(a.model) < + static_cast(b.model); + } + return a.name < b.name; + }); + return sum; +} + +std::string SummaryDiff(const ModuleSummary& expected, const ModuleSummary& actual) { + if (expected == actual) { + return {}; + } + std::ostringstream out; + out << "ModuleSummary mismatch:\n"; + + if (expected.capabilities != actual.capabilities) { + out << " capabilities differ: expected={"; + for (auto c : expected.capabilities) out << static_cast(c) << ","; + out << "} actual={"; + for (auto c : actual.capabilities) out << static_cast(c) << ","; + out << "}\n"; + } + if (expected.extensions != actual.extensions) { + out << " extensions differ: expected={"; + for (const auto& e : expected.extensions) out << e << ","; + out << "} actual={"; + for (const auto& e : actual.extensions) out << e << ","; + out << "}\n"; + } + if (expected.addressing_model != actual.addressing_model) { + out << " addressing_model: expected=" << static_cast(expected.addressing_model) + << " actual=" << static_cast(actual.addressing_model) << '\n'; + } + if (expected.memory_model != actual.memory_model) { + out << " memory_model: expected=" << static_cast(expected.memory_model) + << " actual=" << static_cast(actual.memory_model) << '\n'; + } + if (expected.entry_points != actual.entry_points) { + out << " entry points differ:\n"; + out << " expected: "; + for (const auto& ep : expected.entry_points) { + out << "(model=" << static_cast(ep.model) << ", name=\"" << ep.name + << "\", interfaces=" << ep.interface_count << ") "; + } + out << "\n actual: "; + for (const auto& ep : actual.entry_points) { + out << "(model=" << static_cast(ep.model) << ", name=\"" << ep.name + << "\", interfaces=" << ep.interface_count << ") "; + } + out << '\n'; + } + if (expected.opcode_counts != actual.opcode_counts) { + out << " opcode counts differ:\n"; + std::set all_ops; + for (const auto& [op, _] : expected.opcode_counts) all_ops.insert(op); + for (const auto& [op, _] : actual.opcode_counts) all_ops.insert(op); + for (auto op : all_ops) { + const auto e = expected.opcode_counts.contains(op) ? expected.opcode_counts.at(op) : 0; + const auto a = actual.opcode_counts.contains(op) ? actual.opcode_counts.at(op) : 0; + if (e != a) { + out << " op=" << static_cast(op) << ": expected=" << e + << " actual=" << a << '\n'; + } + } + } + return out.str(); +} + + +} // namespace sirit_tests diff --git a/tests/test_helpers.h b/tests/test_helpers.h new file mode 100644 index 0000000..96a120b --- /dev/null +++ b/tests/test_helpers.h @@ -0,0 +1,121 @@ +/* Shared infrastructure for sirit_tests. + * + * - Test-runner macros (CHECK / RUN_TEST) and the global counters they feed. + * - SPIR-V validator + disassembler wrappers (SPIRV-Tools). + * - SPIR-V -> GLSL round-trip helper (SPIRV-Cross). + * + * Every test source file includes this header. Globals are defined in + * test_helpers.cpp. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace sirit_tests { + +// === Test-runner globals (defined in test_helpers.cpp) ===================== + +extern int g_total; +extern int g_failures; +extern const char* g_current_test; + +#define CHECK(cond) \ + do { \ + ++::sirit_tests::g_total; \ + if (!(cond)) { \ + ++::sirit_tests::g_failures; \ + std::fprintf(stderr, " FAIL [%s] %s:%d: %s\n", ::sirit_tests::g_current_test, \ + __FILE__, __LINE__, #cond); \ + } \ + } while (0) + +#define RUN_TEST(fn) \ + do { \ + ::sirit_tests::g_current_test = #fn; \ + const int before = ::sirit_tests::g_failures; \ + fn(); \ + std::fprintf(stderr, "%-44s %s\n", #fn, \ + ::sirit_tests::g_failures == before ? "ok" : "FAILED"); \ + } while (0) + + +// === SPIR-V validation and disassembly (SPIRV-Tools) ======================= + +struct ValidationResult { + bool ok = false; + std::string messages; // diagnostic stream from spirv-val (empty on success) +}; + +/// Validate `words` for the given target environment. Returns ok=true if the +/// validator accepts the module. On failure `messages` carries spirv-val's +/// diagnostic text plus the disassembled module so a `CHECK(result.ok)` failure +/// is debuggable from the test log alone. +ValidationResult Validate(std::span words, + spv_target_env env = SPV_ENV_VULKAN_1_3); + +/// Disassemble `words` to human-readable SPIR-V text. Empty on parse failure. +std::string Disassemble(std::span words, + spv_target_env env = SPV_ENV_VULKAN_1_3); + + +// SPIRV-Cross interface (CrossToGlsl) lives in test_cross.h -- its bundled +// spirv.hpp shares an include guard with sirit's spirv.hpp11 and they cannot +// both appear in the same translation unit. + + +// === SPIR-V text -> binary (SPIRV-Tools assembler) ========================= + +struct AssembleResult { + bool ok = false; + std::vector words; + std::string error; +}; + +/// Run SPIRV-Tools' assembler on `text`. Used by the shader-replication tests +/// to obtain a canonical reference binary from a textual SPIR-V "spec". +AssembleResult AssembleText(const std::string& text, + spv_target_env env = SPV_ENV_VULKAN_1_3); + + +// === Structural module comparison ========================================== +// +// The replication tests don't aim for byte-equal binaries: spirv-as and sirit +// allocate result-ids in different orders. Instead they assemble both via +// each path and compare a coarse-grained structural summary so equivalent +// modules compare equal. + +struct EntryPointSummary { + spv::ExecutionModel model{}; + std::string name; + std::size_t interface_count{}; + + bool operator==(const EntryPointSummary&) const = default; +}; + +struct ModuleSummary { + std::vector capabilities; // sorted, deduped + std::vector extensions; // sorted, deduped + spv::AddressingModel addressing_model{}; + spv::MemoryModel memory_model{}; + std::vector entry_points; // sorted by (model, name) + std::map opcode_counts; // every emitted opcode + + bool operator==(const ModuleSummary&) const = default; +}; + +ModuleSummary Summarize(std::span words); + +/// Pretty-print the diff between two summaries (for use in test failure +/// messages). Returns an empty string when the two compare equal. +std::string SummaryDiff(const ModuleSummary& expected, const ModuleSummary& actual); + +} // namespace sirit_tests diff --git a/tests/test_raytracing.cpp b/tests/test_raytracing.cpp new file mode 100644 index 0000000..9b5dd5b --- /dev/null +++ b/tests/test_raytracing.cpp @@ -0,0 +1,117 @@ +/* Ray-tracing shader generation tests. + * + * Build a ray-generation shader that: + * 1. declares an OpTypeAccelerationStructureKHR + bound UniformConstant + * acceleration-structure variable; + * 2. declares a RayPayloadKHR vec4 variable; + * 3. issues an OpTraceRayKHR call against the loaded accel. + * + * Goal: the produced module passes spirv-val under SPV_ENV_VULKAN_1_3 and the + * disassembly contains the expected RT opcodes / capabilities / extensions. + */ + +#include +#include +#include + +#include + +#include "test_helpers.h" + +using namespace sirit_tests; + +namespace { + +constexpr std::uint32_t SpvVersion14 = 0x00010400; // SPIR-V 1.4 (Vulkan 1.2 baseline for KHR RT) + +std::vector BuildRayGenModule() { + Sirit::Module m{SpvVersion14}; + + m.AddCapability(spv::Capability::RayTracingKHR); + m.AddExtension("SPV_KHR_ray_tracing"); + m.SetMemoryModel(spv::AddressingModel::Logical, spv::MemoryModel::GLSL450); + + const auto t_void = m.TypeVoid(); + const auto t_uint = m.TypeInt(32, false); + const auto t_float = m.TypeFloat(32); + const auto t_v3float = m.TypeVector(t_float, 3); + const auto t_v4float = m.TypeVector(t_float, 4); + + const auto t_accel = m.TypeAccelerationStructureKHR(); + const auto t_ptr_uc_accel = m.TypePointer(spv::StorageClass::UniformConstant, t_accel); + const auto t_ptr_payload = m.TypePointer(spv::StorageClass::RayPayloadKHR, t_v4float); + + const auto accel_var = m.AddGlobalVariable(t_ptr_uc_accel, spv::StorageClass::UniformConstant); + m.Name(accel_var, "topLevelAS"); + m.Decorate(accel_var, spv::Decoration::DescriptorSet, std::uint32_t{0}); + m.Decorate(accel_var, spv::Decoration::Binding, std::uint32_t{0}); + + const auto payload_var = m.AddGlobalVariable(t_ptr_payload, spv::StorageClass::RayPayloadKHR); + m.Name(payload_var, "payload"); + m.Decorate(payload_var, spv::Decoration::Location, std::uint32_t{0}); + + const auto cf0 = m.Constant(t_float, 0.0f); + const auto cf1 = m.Constant(t_float, 1.0f); + const auto cf1000 = m.Constant(t_float, 1000.0f); + const auto cu0 = m.Constant(t_uint, std::uint32_t{0}); + const auto cu_ff = m.Constant(t_uint, std::uint32_t{0xff}); + + const auto origin = m.ConstantComposite(t_v3float, cf0, cf0, cf0); + const auto direction = m.ConstantComposite(t_v3float, cf0, cf0, cf1); + + const auto t_func = m.TypeFunction(t_void); + const auto fn = m.OpFunction(t_void, spv::FunctionControlMask::MaskNone, t_func); + m.Name(fn, "main"); + m.AddLabel(); + + const auto loaded_accel = m.OpLoad(t_accel, accel_var); + m.OpTraceRayKHR(loaded_accel, + /*ray_flags=*/cu0, + /*cull_mask=*/cu_ff, + /*sbt_offset=*/cu0, + /*sbt_stride=*/cu0, + /*miss_index=*/cu0, + /*ray_origin=*/origin, + /*ray_tmin=*/cf0, + /*ray_direction=*/direction, + /*ray_tmax=*/cf1000, + /*payload=*/payload_var); + + m.OpReturn(); + m.OpFunctionEnd(); + + m.AddEntryPoint(spv::ExecutionModel::RayGenerationKHR, fn, "main", accel_var, payload_var); + + return m.Assemble(); +} + +void test_raygen_module_validates() { + const auto words = BuildRayGenModule(); + const auto result = Validate(words, SPV_ENV_VULKAN_1_3); + if (!result.ok) { + std::fputs(result.messages.c_str(), stderr); + } + CHECK(result.ok); +} + +void test_raygen_disassembly_contains_expected_ops() { + const auto words = BuildRayGenModule(); + const std::string text = Disassemble(words, SPV_ENV_VULKAN_1_3); + CHECK(text.find("OpTypeAccelerationStructureKHR") != std::string::npos); + CHECK(text.find("OpTraceRayKHR") != std::string::npos); + CHECK(text.find("RayTracingKHR") != std::string::npos); // capability + CHECK(text.find("\"SPV_KHR_ray_tracing\"") != std::string::npos); // extension + CHECK(text.find("RayGenerationKHR") != std::string::npos); // execution model + CHECK(text.find("RayPayloadKHR") != std::string::npos); // storage class +} + +} // namespace + +namespace sirit_tests { + +void RegisterRayTracingTests() { + RUN_TEST(test_raygen_module_validates); + RUN_TEST(test_raygen_disassembly_contains_expected_ops); +} + +} // namespace sirit_tests diff --git a/tests/test_vertex_shader_replicated.cpp b/tests/test_vertex_shader_replicated.cpp new file mode 100644 index 0000000..49617ca --- /dev/null +++ b/tests/test_vertex_shader_replicated.cpp @@ -0,0 +1,129 @@ +/* Vertex-shader replication test. + * + * Spec: a pass-through vertex shader that loads a vec4 position from input + * Location 0 and stores it to gl_Position (gl_PerVertex.Position). + * + * Strategy: assemble the reference SPIR-V text via SPIRV-Tools, build the + * same shader with sirit, validate both, then compare ModuleSummary + * (capabilities / extensions / entry points / opcode multiset). + */ + +#include +#include +#include + +#include + +#include "test_helpers.h" + +using namespace sirit_tests; + +namespace { + +constexpr std::uint32_t SpvVersion10 = 0x00010000; + +const char* kVertexSpvText = R"( + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %main "main" %_ %in_pos + OpDecorate %per_vertex Block + OpMemberDecorate %per_vertex 0 BuiltIn Position + OpDecorate %in_pos Location 0 + %void = OpTypeVoid + %fn = OpTypeFunction %void + %float = OpTypeFloat 32 + %v4float = OpTypeVector %float 4 + %per_vertex = OpTypeStruct %v4float +%_ptr_Output_per_vertex = OpTypePointer Output %per_vertex + %_ = OpVariable %_ptr_Output_per_vertex Output +%_ptr_Input_v4float = OpTypePointer Input %v4float + %in_pos = OpVariable %_ptr_Input_v4float Input +%_ptr_Output_v4float = OpTypePointer Output %v4float + %uint = OpTypeInt 32 0 + %c0_uint = OpConstant %uint 0 + %main = OpFunction %void None %fn + %entry = OpLabel + %pos = OpLoad %v4float %in_pos + %gl_pos = OpAccessChain %_ptr_Output_v4float %_ %c0_uint + OpStore %gl_pos %pos + OpReturn + OpFunctionEnd +)"; + +std::vector BuildVertexShaderWithSirit() { + Sirit::Module m{SpvVersion10}; + + m.AddCapability(spv::Capability::Shader); + m.SetMemoryModel(spv::AddressingModel::Logical, spv::MemoryModel::GLSL450); + + const auto t_void = m.TypeVoid(); + const auto t_fn = m.TypeFunction(t_void); + const auto t_float = m.TypeFloat(32); + const auto t_v4float = m.TypeVector(t_float, 4); + const auto t_per_vertex = m.TypeStruct(t_v4float); + const auto t_ptr_out_pv = m.TypePointer(spv::StorageClass::Output, t_per_vertex); + const auto t_ptr_in_v4 = m.TypePointer(spv::StorageClass::Input, t_v4float); + const auto t_ptr_out_v4 = m.TypePointer(spv::StorageClass::Output, t_v4float); + const auto t_uint = m.TypeInt(32, false); + + const auto out_pv = m.AddGlobalVariable(t_ptr_out_pv, spv::StorageClass::Output); + const auto in_pos = m.AddGlobalVariable(t_ptr_in_v4, spv::StorageClass::Input); + + m.Decorate(t_per_vertex, spv::Decoration::Block); + m.MemberDecorate(t_per_vertex, 0, spv::Decoration::BuiltIn, + static_cast(spv::BuiltIn::Position)); + m.Decorate(in_pos, spv::Decoration::Location, std::uint32_t{0}); + + const auto c0 = m.Constant(t_uint, std::uint32_t{0}); + + const auto main_fn = m.OpFunction(t_void, spv::FunctionControlMask::MaskNone, t_fn); + m.AddLabel(); + const auto pos = m.OpLoad(t_v4float, in_pos); + const auto gl_pos = m.OpAccessChain(t_ptr_out_v4, out_pv, c0); + m.OpStore(gl_pos, pos); + m.OpReturn(); + m.OpFunctionEnd(); + + m.AddEntryPoint(spv::ExecutionModel::Vertex, main_fn, "main", out_pv, in_pos); + + return m.Assemble(); +} + +void test_vertex_shader_replicates_reference() { + const auto reference = AssembleText(kVertexSpvText, SPV_ENV_VULKAN_1_0); + if (!reference.ok) { + std::fputs(reference.error.c_str(), stderr); + } + CHECK(reference.ok); + if (!reference.ok) return; + + const auto ours = BuildVertexShaderWithSirit(); + + // Both validate. + const auto ref_val = Validate(reference.words, SPV_ENV_VULKAN_1_0); + if (!ref_val.ok) std::fputs(ref_val.messages.c_str(), stderr); + CHECK(ref_val.ok); + + const auto our_val = Validate(ours, SPV_ENV_VULKAN_1_0); + if (!our_val.ok) std::fputs(our_val.messages.c_str(), stderr); + CHECK(our_val.ok); + + // Structural equivalence. + const auto ref_sum = Summarize(reference.words); + const auto our_sum = Summarize(ours); + const auto diff = SummaryDiff(ref_sum, our_sum); + if (!diff.empty()) { + std::fputs(diff.c_str(), stderr); + } + CHECK(ref_sum == our_sum); +} + +} // namespace + +namespace sirit_tests { + +void RegisterVertexShaderReplicationTests() { + RUN_TEST(test_vertex_shader_replicates_reference); +} + +} // namespace sirit_tests diff --git a/tools/generate_instructions.py b/tools/generate_instructions.py new file mode 100644 index 0000000..47a661d --- /dev/null +++ b/tools/generate_instructions.py @@ -0,0 +1,633 @@ +"""Generate src/instructions/_generated.cpp from sirit.h + spirv.core.grammar.json. + +Cross-references each public `Module::*` method declaration in +include/sirit/sirit.h against the SPIR-V core grammar. For every method that +maps to a single direct opcode emit, this script emits the corresponding .cpp +body using the operand list from the grammar. + +The hand-written header remains the source of truth for the API surface +(method names, parameter names, default values, ergonomic template overloads). +The generator only fills in the mechanical bodies, where most past bugs lived +(wrong opcode constants, wrong OpId form, wrong operand order). + +What is skipped (intentionally): + - Template variadic overloads (defined inline in the header). + - Inline definitions in the header (have no .cpp counterpart). + - Multi-statement emits: DeferredOpPhi, OpBranchConditional, OpSwitch. + - Module-level state plumbing: AddCapability, AddExtension, SetMemoryModel, + AddEntryPoint, AddExecutionMode, AddLabel, AddLocalVariable, + AddGlobalVariable, Assemble, PatchDeferredPhi, GetGLSLstd450. + - Name aliases / wrappers: TypeSInt, TypeUInt, OpDemoteToHelperInvocationEXT. + - GLSL.std.450 extended-instruction wrappers (OpFAbs, OpSin, ...): they go + through OpExtInst and are produced by a different macro in extension.cpp. + - OpLabel() (returns an id without emitting). + +Output: src/instructions/_generated.cpp. Inspect, then decide whether to wire +in or replace existing files. +""" + +import json +import re +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +HEADER_PATH = ROOT / "include" / "sirit" / "sirit.h" +GRAMMAR_PATH = ( + ROOT / "externals" / "SPIRV-Headers" / "include" / "spirv" / "unified1" + / "spirv.core.grammar.json" +) +OUTPUT_PATH = ROOT / "src" / "instructions" / "_generated.cpp" + + +# --------------------------------------------------------------------------- +# Skip list and method-name -> grammar-opname mapping +# --------------------------------------------------------------------------- + +SKIP = { + # Module-level setup, multi-statement, or non-emitting helpers. + "AddCapability", "AddExtension", "SetMemoryModel", "AddEntryPoint", + "AddExecutionMode", "AddLabel", "AddLocalVariable", "AddGlobalVariable", + "Assemble", "PatchDeferredPhi", + "GetGLSLstd450", "GetNonSemanticDebugPrintf", + "GetAmdGcnShader", "GetAmdShaderTrinaryMinMax", "GetAmdExplicitVertexParameter", + # Multi-statement emits. + "DeferredOpPhi", "OpBranchConditional", "OpSwitch", + # Wrappers / aliases (no direct opcode mapping). + "TypeSInt", "TypeUInt", "OpDemoteToHelperInvocationEXT", + # Convention: these public methods return their input id (target / type) + # rather than the `<< EndOp{}` result, which the generic emit pattern + # cannot express. Keep hand-written. + "Name", "MemberName", + # OpLabel returns an id without emitting. + "OpLabel", + # OpDebugPrintf prepends `format` onto a hand-built operands vector and + # routes through TypeVoid(); the simple wrapper template can't express it. + "OpDebugPrintf", +} + + +# Method-name -> grammar-opname overrides for cases where the public API +# renames an opcode (e.g. drops the EXT suffix for ergonomics). +OPNAME_OVERRIDES = { + "OpAtomicFMax": "OpAtomicFMaxEXT", + "OpAtomicFMin": "OpAtomicFMinEXT", +} + + +# Extended-instruction sets known to the generator. Each entry maps +# grammar file (in the SPIR-V-Headers submodule) +# -> the C++ constant prefix used by the matching header +# -> the Module::Get() helper that returns the imported set Id. +EXTINST_SETS = [ + { + "file": "extinst.glsl.std.450.grammar.json", + "prefix": "GLSLstd450", + "getter": "GetGLSLstd450", + }, + { + "file": "extinst.spv-amd-gcn-shader.grammar.json", + "prefix": "AMD_gcn_shader", + "getter": "GetAmdGcnShader", + }, + { + "file": "extinst.spv-amd-shader-trinary-minmax.grammar.json", + "prefix": "AMD_shader_trinary_minmax", + "getter": "GetAmdShaderTrinaryMinMax", + }, + { + "file": "extinst.spv-amd-shader-explicit-vertex-parameter.grammar.json", + "prefix": "AMD_shader_explicit_vertex_parameter", + "getter": "GetAmdExplicitVertexParameter", + }, + { + "file": "extinst.nonsemantic.debugprintf.grammar.json", + "prefix": "NonSemanticDebugPrintf", + "getter": "GetNonSemanticDebugPrintf", + }, +] + + +def method_to_opname(name): + """Return the grammar opname for a Module method name, or None if it does + not correspond to a single SPIR-V opcode in the core grammar.""" + if name.startswith("Op"): + return name + # Type-Declaration / Constant-Creation / SpecConstant* drop the "Op" + # prefix in sirit's API to read like factories. + if name.startswith(("Type", "Constant", "SpecConstant")): + return "Op" + name + # Annotation ops drop "Op" too -- both the existing Decorate/MemberDecorate + # and the newer DecorationGroup / GroupDecorate / GroupMemberDecorate. + if name in ("Decorate", "MemberDecorate", "DecorationGroup", + "GroupDecorate", "GroupMemberDecorate"): + return "Op" + name + # Debug metadata ops (Name / MemberName / String / Source* / NoLine / + # ModuleProcessed) drop "Op". + if name in ("Name", "MemberName", "String", "Source", "SourceContinued", + "SourceExtension", "NoLine", "ModuleProcessed"): + return "Op" + name + return None + + +# Stream target by grammar `class` field. Anything not listed defaults to +# `code` (the catch-all instruction stream). +CLASS_TO_STREAM = { + "Type-Declaration": "declarations", + "Constant-Creation": "declarations", + "Annotation": "annotations", + "Debug": "debug", +} + + +def stream_for(opname, klass): + if opname == "OpEntryPoint": + return "entry_points" + if opname in ("OpExecutionMode", "OpExecutionModeId"): + return "execution_modes" + if opname == "OpExtInstImport": + return "ext_inst_imports" + return CLASS_TO_STREAM.get(klass, "code") + + +# --------------------------------------------------------------------------- +# Header parser +# --------------------------------------------------------------------------- + +def strip_block_comments(text): + return re.sub(r"/\*.*?\*/", "", text, flags=re.DOTALL) + + +def strip_line_comments(text): + # Keep line breaks so line numbers don't shift unnecessarily. + return re.sub(r"//[^\n]*", "", text) + + +def find_matching(text, start, open_ch, close_ch): + """Return index just past the matching close_ch, given an open_ch at + text[start]. Returns len(text) if unbalanced.""" + depth = 1 + i = start + 1 + while i < len(text) and depth > 0: + c = text[i] + if c == open_ch: + depth += 1 + elif c == close_ch: + depth -= 1 + i += 1 + return i + + +def strip_templates(text): + """Remove every `template <...> [requires(...)] ` block.""" + out = [] + i = 0 + template_re = re.compile(r"\btemplate\b\s*<") + while True: + m = template_re.search(text, i) + if not m: + out.append(text[i:]) + break + out.append(text[i:m.start()]) + # Skip past `template <...>`. + j = find_matching(text, m.end() - 1, "<", ">") + # Now scan forward to the end of the templated declaration / definition. + depth_paren = 0 + k = j + while k < len(text): + c = text[k] + if c == "(": + depth_paren += 1 + elif c == ")": + depth_paren -= 1 + elif c == ";" and depth_paren == 0: + k += 1 + break + elif c == "{" and depth_paren == 0: + k = find_matching(text, k, "{", "}") + break + k += 1 + i = k + return "".join(out) + + +def find_module_class_body(text): + """Return the substring inside `class Module { ... };`.""" + m = re.search(r"\bclass\s+Module\b\s*\{", text) + if not m: + raise RuntimeError("class Module not found") + body_end = find_matching(text, m.end() - 1, "{", "}") + return text[m.end():body_end - 1] + + +# Match a non-template declaration ending in `;`: +# (); +# We disallow `=` in params before the `(` and require a primitive-ish return +# type starting word (Id, void, std::vector<...>, spv::*). +DECL_RE = re.compile( + r""" + (? + (?: Id | void | bool + | std::vector<[\w\s,:<>]+> + ) + (?:\s*[\w<>:,\s\*&]*?)? # qualifiers/template args + ) + \s+(?P\w+)\s* # method name + \((?P[^)]*)\)\s* # (params) + ; + """, + re.VERBOSE, +) + + +def parse_param(p): + """Parse a single C++ parameter declaration. Returns (type, name). + Default values are stripped (we only need the name for the chain).""" + p = p.strip() + if not p: + return None + # Drop default value. + if "=" in p: + p = p[:p.index("=")].rstrip() + # The parameter name is the last identifier before any trailing array/ref. + # We split on the LAST whitespace that is not inside angle/round brackets. + depth = 0 + split_at = -1 + for i, ch in enumerate(p): + if ch in "<(": + depth += 1 + elif ch in ">)": + depth -= 1 + elif ch.isspace() and depth == 0: + split_at = i + if split_at == -1: + # Single token; treat as type with no name. + return (p, "") + type_part = p[:split_at].rstrip() + name_part = p[split_at + 1:].strip() + # Strip leading & or * if name accidentally captured them. + while name_part and name_part[0] in "&*": + type_part += name_part[0] + name_part = name_part[1:] + return (type_part, name_part) + + +def split_top_level(s, sep=","): + """Split `s` on `sep` at depth 0 (ignoring nesting in <...> and (...)).""" + out = [] + depth = 0 + last = 0 + for i, ch in enumerate(s): + if ch in "<(": + depth += 1 + elif ch in ">)": + depth -= 1 + elif ch == sep and depth == 0: + out.append(s[last:i]) + last = i + 1 + out.append(s[last:]) + return out + + +def parse_methods(header_text): + text = strip_block_comments(header_text) + text = strip_line_comments(text) + text = find_module_class_body(text) + text = strip_templates(text) + + methods = [] + for m in DECL_RE.finditer(text): + ret = m.group("ret").strip() + name = m.group("name") + params_raw = m.group("params").strip() + params = [] + if params_raw: + for chunk in split_top_level(params_raw): + parsed = parse_param(chunk) + if parsed is None: + continue + params.append(parsed) + methods.append({"ret": ret, "name": name, "params": params}) + return methods + + +# --------------------------------------------------------------------------- +# Reserve-size estimator +# --------------------------------------------------------------------------- + +# Word size for an operand of a given grammar `kind` -- as a Python expression +# template that may reference the C++ parameter name. +# +# For optional and span, the runtime cost is encoded against the +# parameter name (e.g. "indexes.size()", "memory_access.has_value() ? 1 : 0"). +def operand_size_term(kind, quantifier, param_name): + if quantifier == "*": + # Variadic: .size() words, one per element. + return f"{param_name}.size()" + if quantifier == "?": + # Optional: 1 word if present, 0 otherwise. + # We assume optional for a fixed-size T (the common case). + return f"({param_name}.has_value() ? 1 : 0)" + if kind == "LiteralString": + return f"WordsInString({param_name})" + # All other fixed-size kinds occupy 1 word. + # Note: LiteralContextDependentNumber may take 2 words for u64/double; the + # existing constant.cpp reserves a conservative 2 for the Constant case. + if kind == "LiteralContextDependentNumber": + return "2" + return "1" + + +def reserve_expr(operands): + """Build a Reserve(...) expression from grammar operand kinds + names.""" + base = 1 # the opcode word itself + base += sum(1 for op in operands if op.get("kind") in ("IdResultType", "IdResult")) + + extra_terms = [] + for op in operands: + kind = op.get("kind") + if kind in ("IdResultType", "IdResult"): + continue + param_name = op.get("_param_name") + if param_name is None: + # Optional grammar operand not exposed by the public API; the + # encoded instruction won't include it, so it costs zero words. + continue + term = operand_size_term(kind, op.get("quantifier", ""), param_name) + extra_terms.append(term) + + if not extra_terms: + return str(base) + # Sum constants where possible to keep the expression tidy. + const_total = base + runtime_terms = [] + for t in extra_terms: + if t == "1": + const_total += 1 + elif t == "2": + const_total += 2 + else: + runtime_terms.append(t) + parts = [str(const_total)] + runtime_terms + return " + ".join(parts) + + +# --------------------------------------------------------------------------- +# Body generator +# --------------------------------------------------------------------------- + +def head_form(operands): + has_rt = bool(operands) and operands[0].get("kind") == "IdResultType" + has_id = any(op.get("kind") == "IdResult" for op in operands) + if has_rt and has_id: + return "opid_rt" + if has_id: + return "opid_no_rt" + return "raw" + + +def assign_param_names(operands, c_params): + """Walk `operands` (skipping IdResultType/IdResult) and pair each remaining + operand with the next C++ parameter. Sets `op['_param_name']` in place. + + Returns (chain_param_names, error_message_or_None). + """ + # If operand list expects IdResultType, the first C++ param is `result_type`. + has_rt = bool(operands) and operands[0].get("kind") == "IdResultType" + if has_rt: + if not c_params or c_params[0][1] != "result_type": + return [], ( + f"first C++ parameter is not `result_type` " + f"(got {c_params[0][1] if c_params else 'no params'!r})" + ) + c_iter = iter(c_params[1:]) + else: + c_iter = iter(c_params) + + chain_names = [] + c_remaining = list(c_iter) + c_idx = 0 + for op in operands: + if op.get("kind") in ("IdResultType", "IdResult"): + continue + if c_idx >= len(c_remaining): + # No more C++ params. Allowed only for optional grammar operands + # that the public API has chosen not to expose. + if op.get("quantifier") == "?": + continue + return chain_names, ( + f"ran out of C++ params while binding required grammar " + f"operand {op.get('kind')}" + ) + _, pname = c_remaining[c_idx] + c_idx += 1 + op["_param_name"] = pname + chain_names.append(pname) + leftover = c_remaining[c_idx:] + + # Allow leftover C++ params only if the last grammar operand is a "trailing + # parameterized" kind (mask / value-enum-with-params), which the C++ side + # carries via an extra `<< span` token. + if leftover: + # Heuristic: if the last grammar operand kind is in PARAMETERIZED_KINDS, + # accept ONE leftover param. The C++ side passes a `std::span` + # carrying the per-mask-bit (or per-decoration) word group; treat it + # like a synthetic variadic operand so Reserve() accounts for its size. + if len(leftover) == 1: + leftover_name = leftover[0][1] + chain_names.append(leftover_name) + operands.append({ + "kind": "_TrailingSpan", + "quantifier": "*", + "_param_name": leftover_name, + }) + else: + return chain_names, ( + f"{len(leftover)} extra C++ params after binding all operands" + ) + return chain_names, None + + +def render_method(method, grammar, extinst_lookup): + name = method["name"] + if name in SKIP: + return None, "in skip-list" + + # Method-name override (e.g. OpAtomicFMax -> OpAtomicFMaxEXT) wins. + if name in OPNAME_OVERRIDES: + opname = OPNAME_OVERRIDES[name] + else: + opname = method_to_opname(name) + + if opname is None: + return None, f"no opname mapping for method '{name}'" + + # Extended-instruction wrapper path: method is an OpFoo whose `Foo` lives + # in one of our known extinst grammars but not in core. + if opname not in grammar and name.startswith("Op"): + ext_key = name[2:] + if ext_key in extinst_lookup: + return render_extinst_wrapper(method, extinst_lookup[ext_key]) + + if opname not in grammar: + return None, f"opname {opname} not in grammar" + + inst = grammar[opname] + klass = inst.get("class", "") + operands = [dict(op) for op in inst.get("operands", [])] # shallow copies + form = head_form(operands) + stream = stream_for(opname, klass) + + chain_names, err = assign_param_names(operands, method["params"]) + if err: + return None, f"signature/grammar mismatch: {err}" + + reserve = reserve_expr(operands) + chain = "".join(f" << {n}" for n in chain_names) + + if form == "opid_rt": + head = f"OpId{{spv::Op::{opname}, result_type}}" + elif form == "opid_no_rt": + head = f"OpId{{spv::Op::{opname}}}" + else: + head = f"spv::Op::{opname}" + + # Build the parameter list from the original method. + params_src = ", ".join(_format_param(p) for p in method["params"]) + ret = method["ret"] + + if ret == "void": + return_kw = "" + else: + return_kw = "return " + + body = ( + f"{ret} Module::{name}({params_src}) {{\n" + f" {stream}->Reserve({reserve});\n" + f" {return_kw}*{stream} << {head}{chain} << EndOp{{}};\n" + f"}}\n" + ) + return body, None + + +def _format_param(p): + type_part, name = p + # Re-emit as "Type name". Don't re-add default values: those are in the + # header declaration, not in the .cpp definition. + return f"{type_part} {name}".strip() + + +def render_extinst_wrapper(method, ext_entry): + """Render an `OpFoo` body that delegates to OpExtInst with the right + set + opcode constant. `ext_entry` carries the extinst metadata.""" + name = method["name"] + params = method["params"] + if not params or params[0][1] != "result_type": + return None, ( + f"extinst wrapper {name}: first parameter must be `result_type` " + f"(got {params[0][1] if params else 'none'!r})" + ) + arg_names = [p[1] for p in params[1:]] + args_src = (", " + ", ".join(arg_names)) if arg_names else "" + constant = ext_entry["prefix"] + ext_entry["opname"] + getter = ext_entry["getter"] + params_src = ", ".join(_format_param(p) for p in params) + body = ( + f"{method['ret']} Module::{name}({params_src}) {{\n" + f" return OpExtInst(result_type, {getter}(), {constant}{args_src});\n" + f"}}\n" + ) + return body, None + + +# --------------------------------------------------------------------------- +# Driver +# --------------------------------------------------------------------------- + +HEADER_BLURB = """\ +/* This file is auto-generated by tools/generate_instructions.py. + * Do not edit by hand. Regenerate after changes to the public header + * include/sirit/sirit.h or to the SPIR-V grammar files + * (externals/SPIRV-Headers/include/spirv/unified1/spirv.core.grammar.json + * and the bundled extinst.*.grammar.json files). + */ + +#include +#include +#include +#include +#include + +#include "sirit/sirit.h" + +#include "stream.h" + +namespace Sirit { + +""" + +FOOTER = "} // namespace Sirit\n" + + +def load_extinst_lookup(): + """Returns a dict mapping extinst opname (without "Op" prefix) to a record + {prefix, getter, opname} suitable for render_extinst_wrapper.""" + base_dir = ( + ROOT / "externals" / "SPIRV-Headers" / "include" / "spirv" / "unified1" + ) + lookup = {} + for entry in EXTINST_SETS: + path = base_dir / entry["file"] + if not path.exists(): + continue + data = json.loads(path.read_text(encoding="utf-8")) + for inst in data.get("instructions", []): + opname = inst["opname"] + lookup[opname] = { + "prefix": entry["prefix"], + "getter": entry["getter"], + "opname": opname, + } + return lookup + + +def main(): + grammar_data = json.loads(GRAMMAR_PATH.read_text(encoding="utf-8")) + grammar = {inst["opname"]: inst for inst in grammar_data["instructions"]} + extinst_lookup = load_extinst_lookup() + + header_text = HEADER_PATH.read_text(encoding="utf-8") + methods = parse_methods(header_text) + + bodies = [] + skipped = [] + errored = [] + + for m in methods: + body, err = render_method(m, grammar, extinst_lookup) + if body is not None: + bodies.append(body) + elif err and ("skip-list" in err or "no opname mapping" in err): + skipped.append((m["name"], err)) + else: + errored.append((m["name"], err)) + + OUTPUT_PATH.write_text( + HEADER_BLURB + "\n".join(bodies) + "\n" + FOOTER, encoding="utf-8" + ) + + print(f"Wrote {OUTPUT_PATH.relative_to(ROOT).as_posix()}") + print(f" generated: {len(bodies)} method(s)") + print(f" skipped: {len(skipped)} method(s)") + print(f" errors: {len(errored)} method(s)") + if errored: + print() + print("Errors (need manual review or skip-list entry):") + for name, err in errored: + print(f" {name}: {err}") + return 1 if errored else 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tools/validate_instructions.py b/tools/validate_instructions.py new file mode 100644 index 0000000..b8074d7 --- /dev/null +++ b/tools/validate_instructions.py @@ -0,0 +1,307 @@ +"""Validate src/instructions/*.cpp emitters against spirv.core.grammar.json. + +For each method body that emits a single chained SPIR-V instruction +(`*stream << HEAD << ... << EndOp{}`), this script verifies: + + 1. opcode-name match -- if the method name starts with `Op`, the emitted + opcode name must equal the method name. (Catches copy-paste bugs like + `OpImageSparseRead` accidentally emitting `OpImageSparseTexelsResident`.) + + 2. head-form match -- the OpId{} variant used must agree with the + grammar's IdResultType / IdResult presence: + * grammar has IdResultType + IdResult -> code must use OpId{op, rt} + * grammar has IdResult only -> code must use OpId{op} + (the OpId path always emits + a result-id; correct for + Type-Declaration ops) + * grammar has neither -> code must use raw + `spv::Op::Op` (the OpId + path would write a spurious + result-id; this catches the + OpAtomicStore bug type) + + 3. operand-count match -- the number of `<<`-separated operand expressions + between HEAD and EndOp must lie in [required, total + parameterized], + where: + required = count of grammar operands without `?` quantifier + (excluding IdResultType / IdResult) + total = count of all grammar operands + (excluding IdResultType / IdResult) + parameterized = 1 if the last grammar operand is a BitEnum mask or a + ValueEnum whose enumerants carry per-value parameters + (Decoration, ExecutionMode, ImageOperands, LoopControl, + MemoryAccess, ...). C++ producers add one extra `<<` token + to carry the per-value variadic word group. + +Methods declared inside `#define` macro bodies (lines ending with `\\`) are +skipped: the placeholder method names there don't correspond to grammar +opnames. Multi-statement emit patterns (OpBranchConditional, OpSwitch, +DeferredOpPhi etc.) are reported as skipped for manual review. + +Exit code: 0 if no issues, 1 otherwise. +""" + +import json +import re +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +GRAMMAR_PATH = ( + ROOT / "externals" / "SPIRV-Headers" / "include" / "spirv" / "unified1" + / "spirv.core.grammar.json" +) +INST_DIR = ROOT / "src" / "instructions" + + +def load_grammar(): + with GRAMMAR_PATH.open(encoding="utf-8") as f: + data = json.load(f) + by_opname = {inst["opname"]: inst for inst in data["instructions"]} + parameterized = compute_parameterized_kinds(data) + return by_opname, parameterized + + +def compute_parameterized_kinds(data): + """Operand kinds whose values may consume additional words from the wire, + which in this codebase translates to one extra `<<` token in the chain.""" + out = set() + for kind in data.get("operand_kinds", []): + category = kind.get("category") + name = kind.get("kind") + if category == "BitEnum": + out.add(name) + elif category == "ValueEnum": + for enumerant in kind.get("enumerants", []): + if enumerant.get("parameters"): + out.add(name) + break + return out + + +def expected_form(operands): + has_rt = bool(operands) and operands[0].get("kind") == "IdResultType" + has_id = any(op.get("kind") == "IdResult" for op in operands) + if has_rt and has_id: + return "opid_rt" + if has_id and not has_rt: + return "opid_no_rt" + if not has_id and not has_rt: + return "raw" + return "unknown" + + +def remaining_operands(operands): + return [ + op for op in operands + if op.get("kind") not in ("IdResultType", "IdResult") + ] + + +METHOD_RE = re.compile( + r""" + (?P(?:\w[\w<>:&\s,*]*?)) # return type + \s+Module::(?P\w+)\s* # Module::Name + \((?P[^)]*)\)\s*\{ # (params) { + (?P.*?) # body (non-greedy) + \n\} # closing } at column 0 + """, + re.DOTALL | re.VERBOSE, +) + +EMIT_RE = re.compile( + r""" + \*\s*\w+\s* # *target + <<\s* + (?POpId\s*\{[^}]+\}|spv::Op::Op\w+) + \s*(?P(?:<<\s*[^;]+?)?) # << op << op ... + <<\s*EndOp\s*\{\s*\} # << EndOp{} + """, + re.DOTALL | re.VERBOSE, +) + +OPID_RT_RE = re.compile( + r"OpId\s*\{\s*spv::Op::Op(?P\w+)\s*,\s*[\w_]+\s*\}" +) +OPID_NO_RT_RE = re.compile(r"OpId\s*\{\s*spv::Op::Op(?P\w+)\s*\}") +RAW_RE = re.compile(r"spv::Op::Op(?P\w+)") + + +def classify_head(head): + s = head.strip() + m = OPID_RT_RE.fullmatch(s) + if m: + return "opid_rt", m.group("op") + m = OPID_NO_RT_RE.fullmatch(s) + if m: + return "opid_no_rt", m.group("op") + m = RAW_RE.fullmatch(s) + if m: + return "raw", m.group("op") + return None, None + + +def split_operands(rest): + rest = rest.strip() + if not rest: + return [] + flat = re.sub(r"\s+", " ", rest) + parts = re.split(r"<<", flat) + return [p.strip() for p in parts if p.strip()] + + +def line_of_offset(text, offset): + return text.count("\n", 0, offset) + 1 + + +def is_in_macro(text, name_offset): + """Returns True if the line containing `name_offset` ends with a backslash + line-continuation (i.e. the method declaration is inside a #define).""" + line_start = text.rfind("\n", 0, name_offset) + 1 + line_end = text.find("\n", name_offset) + if line_end == -1: + line_end = len(text) + return text[line_start:line_end].rstrip().endswith("\\") + + +# Methods we expect not to emit (helpers / wrappers / no-ops). +NON_EMITTING = { + "OpLabel", # only allocates an id + "OpDemoteToHelperInvocationEXT", # alias-wrapper around OpDemoteToHelperInvocation +} + + +# Method name -> grammar opcode name overrides for cases where the public API +# intentionally drops the spec suffix (e.g. EXT) for ergonomics. The generator +# in generate_instructions.py uses the same map; keep them in sync. +OPNAME_OVERRIDES = { + "OpAtomicFMax": "OpAtomicFMaxEXT", + "OpAtomicFMin": "OpAtomicFMinEXT", +} + + +def validate_file(path, grammar, parameterized): + text = path.read_text(encoding="utf-8") + issues = [] + skipped = [] + + for mm in METHOD_RE.finditer(text): + method_name = mm.group("name") + body = mm.group("body") + body_offset = mm.start("body") + method_line = line_of_offset(text, mm.start("name")) + + if is_in_macro(text, mm.start("name")): + continue + + emits = list(EMIT_RE.finditer(body)) + + if len(emits) != 1: + if method_name in NON_EMITTING: + continue + if not re.search(r"spv::Op::Op\w+", body): + continue + reason = "no single-statement emit chain found" + if len(emits) > 1: + reason = f"{len(emits)} emit chains found" + skipped.append((method_line, method_name, reason)) + continue + + em = emits[0] + head = em.group("head") + rest = em.group("rest") or "" + chain_tokens = split_operands(rest) + emit_line = line_of_offset(text, body_offset + em.start()) + + form, op = classify_head(head) + if form is None: + skipped.append((emit_line, method_name, f"unparseable head: {head!r}")) + continue + + opname = "Op" + op + + # Check 1: method-name vs emitted-opcode mismatch. + # Allow the documented overrides (e.g. OpAtomicFMax -> OpAtomicFMaxEXT). + expected_opname = OPNAME_OVERRIDES.get(method_name, method_name) + if method_name.startswith("Op") and expected_opname != opname: + issues.append(( + emit_line, method_name, + f"emits {opname} but method is named {method_name}" + + (f" (expected {expected_opname})" if expected_opname != method_name else ""), + )) + continue + + if opname not in grammar: + issues.append(( + emit_line, method_name, + f"emits unknown opcode {opname} (not in grammar)", + )) + continue + + operands = grammar[opname].get("operands", []) + expected = expected_form(operands) + + # Check 2: head-form vs grammar + if form != expected: + kinds = [op_.get("kind") for op_ in operands] + issues.append(( + emit_line, method_name, + f"emits {opname} using head form '{form}' but grammar " + f"expects '{expected}' (operands: {kinds})", + )) + continue + + # Check 3: operand count + remaining = remaining_operands(operands) + required = sum(1 for op in remaining if op.get("quantifier") != "?") + total = len(remaining) + last_kind = remaining[-1].get("kind") if remaining else None + max_extra = 1 if last_kind in parameterized else 0 + max_count = total + max_extra + actual = len(chain_tokens) + if not (required <= actual <= max_count): + kinds_q = [ + f"{op_.get('kind')}{op_.get('quantifier','')}" for op_ in remaining + ] + issues.append(( + emit_line, method_name, + f"emits {opname} with {actual} operand expression(s); " + f"grammar expects {required}..{max_count} (operands: {kinds_q})", + )) + continue + + return issues, skipped + + +def main(): + grammar, parameterized = load_grammar() + all_issues = [] + all_skipped = [] + + for cpp in sorted(INST_DIR.glob("*.cpp")): + rel = cpp.relative_to(ROOT).as_posix() + issues, skipped = validate_file(cpp, grammar, parameterized) + for line, method, msg in issues: + all_issues.append(f"{rel}:{line} Module::{method}\n {msg}") + for line, method, reason in skipped: + all_skipped.append(f"{rel}:{line} Module::{method} ({reason})") + + if all_issues: + print("Issues:") + for issue in all_issues: + print() + print(issue) + print() + + if all_skipped: + print("Skipped (multi-statement emits or unparseable; manual review):") + for s in all_skipped: + print(f" {s}") + print() + + print(f"{len(all_issues)} issue(s); {len(all_skipped)} skipped.") + return 1 if all_issues else 0 + + +if __name__ == "__main__": + sys.exit(main())