yum-mirror/glslang

git clone https://git.yummers.dev/yum-mirror/glslang

Qingyuan ZhengImplement DebugEntryPoint instruction in NSDI 1024ede04ac

main
26.7 KiB625 linesraw
1// Copyright (C) 2026 NVIDIA Corporation.
2//
3// All rights reserved.
4//
5// Redistribution and use in source and binary forms, with or without
6// modification, are permitted provided that the following conditions
7// are met:
8//
9//    Redistributions of source code must retain the above copyright
10//    notice, this list of conditions and the following disclaimer.
11//
12//    Redistributions in binary form must reproduce the above
13//    copyright notice, this list of conditions and the following
14//    disclaimer in the documentation and/or other materials provided
15//    with the distribution.
16//
17//    Neither the name of 3Dlabs Inc. Ltd. nor the names of its
18//    contributors may be used to endorse or promote products derived
19//    from this software without specific prior written permission.
20//
21// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25// COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32// POSSIBILITY OF SUCH DAMAGE.
33
34// Tests for NonSemantic.Shader.DebugInfo (NSDI) features.
35
36#include "TestFixture.h"
37#include "SPIRV/NonSemanticShaderDebugInfo.h"
38#include "glslang/Public/ResourceLimits.h"
39#include <gtest/gtest.h>
40#include <cstdint>
41#include <sstream>
42#include <string>
43#include <vector>
44
45namespace glslangtest {
46namespace {
47
48std::string decodeLiteralString(const uint32_t* words, size_t wordCount)
49{
50    std::string result;
51    for (size_t wordIndex = 0; wordIndex < wordCount; ++wordIndex) {
52        for (unsigned int byteIndex = 0; byteIndex < 4; ++byteIndex) {
53            const char character = static_cast<char>((words[wordIndex] >> (8 * byteIndex)) & 0xff);
54            if (character == '\0')
55                return result;
56            result.push_back(character);
57        }
58    }
59    return result;
60}
61
62bool containsDebugTypeBasic(const std::vector<uint32_t>& spirv, const char* expectedName,
63                            uint32_t expectedWidth, spv::FPEncoding expectedFPEncoding)
64{
65    if (spirv.size() < 5 || spirv[0] != spv::MagicNumber)
66        return false;
67
68    const uint32_t idBound = spirv[3];
69    std::vector<std::string> strings(idBound);
70    std::vector<uint32_t> constants(idBound);
71    std::vector<bool> isConstant(idBound);
72    uint32_t debugInfoImportId = 0;
73
74    size_t end = spirv.size();
75
76    // Resolve the IDs referenced by DebugTypeBasic before inspecting the instruction itself.
77    // OpExtInstImport gives the result ID used to select the .101 debug-info instruction set,
78    // OpString gives the source-level type name, and OpConstant gives the values of the width,
79    // base encoding, flags, and optional FPEncoding operands.
80    //
81    // For example, the relevant SPIR-V for floate2m1_t has this shape:
82    //   %debug = OpExtInstImport "NonSemantic.Shader.DebugInfo.101"
83    //   %name = OpString "floate2m1_t"
84    //   %width = OpConstant %uint 4
85    //   %encoding = OpConstant %uint 3       ; NonSemanticShaderDebugInfoFloat
86    //   %flags = OpConstant %uint 0          ; NonSemanticShaderDebugInfoNone
87    //   %fpEncoding = OpConstant %uint 4225  ; Float4E2M1EXT
88    //   %type = OpExtInst %void %debug DebugTypeBasic
89    //               %name %width %encoding %flags %fpEncoding
90    for (size_t offset = 5; offset < spirv.size();) {
91        const uint32_t wordCount = spirv[offset] >> spv::WordCountShift;
92        const spv::Op opcode = static_cast<spv::Op>(spirv[offset] & spv::OpCodeMask);
93        if (wordCount == 0 || offset + wordCount > spirv.size())
94            return false;
95
96        if (opcode == spv::Op::OpFunction) {
97            // SPIR-V's logical layout places imports, debug strings, constants, and global
98            // debug-type instructions before the first OpFunction. Record that boundary while
99            // resolving IDs so this pass can exit early and the subsequent DebugTypeBasic scan
100            // can stop there as well.
101            end = offset;
102            break;
103        }
104
105        if (opcode == spv::Op::OpExtInstImport && wordCount >= 3) {
106            const uint32_t resultId = spirv[offset + 1];
107            if (resultId < idBound &&
108                decodeLiteralString(&spirv[offset + 2], wordCount - 2) ==
109                    "NonSemantic.Shader.DebugInfo.101") {
110                debugInfoImportId = resultId;
111            }
112        } else if (opcode == spv::Op::OpString && wordCount >= 3) {
113            const uint32_t resultId = spirv[offset + 1];
114            if (resultId < idBound)
115                strings[resultId] = decodeLiteralString(&spirv[offset + 2], wordCount - 2);
116        } else if (opcode == spv::Op::OpConstant && wordCount == 4) {
117            const uint32_t resultId = spirv[offset + 2];
118            if (resultId < idBound) {
119                constants[resultId] = spirv[offset + 3];
120                isConstant[resultId] = true;
121            }
122        }
123
124        offset += wordCount;
125    }
126
127    if (debugInfoImportId == 0)
128        return false;
129
130    const auto hasConstantValue = [&](uint32_t id, uint32_t value) {
131        return id < idBound && isConstant[id] && constants[id] == value;
132    };
133
134    // A DebugTypeBasic with an FPEncoding has five operands after its instruction number:
135    // Name, Size, Encoding, Flags, and FPEncoding. Including the five-word OpExtInst prefix,
136    // that gives a word count of 10. Match the imported .101 instruction set and resolve each
137    // operand ID through the OpString and OpConstant tables populated above.
138    for (size_t offset = 5; offset < end;) {
139        const uint32_t wordCount = spirv[offset] >> spv::WordCountShift;
140        const spv::Op opcode = static_cast<spv::Op>(spirv[offset] & spv::OpCodeMask);
141        if (wordCount == 0 || offset + wordCount > spirv.size())
142            return false;
143
144        if (opcode == spv::Op::OpExtInst && wordCount == 10 &&
145            spirv[offset + 3] == debugInfoImportId &&
146            spirv[offset + 4] == NonSemanticShaderDebugInfoDebugTypeBasic) {
147            const uint32_t nameId = spirv[offset + 5];
148            const uint32_t widthId = spirv[offset + 6];
149            const uint32_t encodingId = spirv[offset + 7];
150            const uint32_t flagsId = spirv[offset + 8];
151            const uint32_t fpEncodingId = spirv[offset + 9];
152            if (nameId < idBound && strings[nameId] == expectedName &&
153                hasConstantValue(widthId, expectedWidth) &&
154                hasConstantValue(encodingId, NonSemanticShaderDebugInfoFloat) &&
155                hasConstantValue(flagsId, NonSemanticShaderDebugInfoNone) &&
156                hasConstantValue(fpEncodingId, static_cast<uint32_t>(expectedFPEncoding))) {
157                return true;
158            }
159        }
160
161        offset += wordCount;
162    }
163
164    return false;
165}
166
167bool containsDebugEntryPoint(const std::vector<uint32_t>& spirv, const char* expectedCompilerSignature,
168                             const char* expectedCommandLineArguments, const char* expectedCurrentWorkingDirectory)
169{
170    if (spirv.size() < 5 || spirv[0] != spv::MagicNumber)
171        return false;
172
173    const uint32_t idBound = spirv[3];
174    std::vector<std::string> strings(idBound);
175    uint32_t debugInfoImportId = 0;
176    uint32_t compilerSignatureId = 0;
177    uint32_t commandLineArgumentsId = 0;
178    uint32_t currentWorkingDirectoryId = 0;
179
180    for (size_t offset = 5; offset < spirv.size();) {
181        const uint32_t wordCount = spirv[offset] >> spv::WordCountShift;
182        const spv::Op opcode = static_cast<spv::Op>(spirv[offset] & spv::OpCodeMask);
183        if (wordCount == 0 || offset + wordCount > spirv.size())
184            return false;
185
186        if (opcode == spv::Op::OpExtInstImport && wordCount >= 3) {
187            if (decodeLiteralString(&spirv[offset + 2], wordCount - 2) ==
188                "NonSemantic.Shader.DebugInfo.102") {
189                debugInfoImportId = spirv[offset + 1];
190            }
191        } else if (opcode == spv::Op::OpString && wordCount >= 3) {
192            const uint32_t resultId = spirv[offset + 1];
193            if (resultId < idBound)
194                strings[resultId] = decodeLiteralString(&spirv[offset + 2], wordCount - 2);
195        } else if (opcode == spv::Op::OpExtInst && wordCount == 10 &&
196                   spirv[offset + 3] == debugInfoImportId &&
197                   spirv[offset + 4] == NonSemanticShaderDebugInfoDebugEntryPoint) {
198            compilerSignatureId = spirv[offset + 7];
199            commandLineArgumentsId = spirv[offset + 8];
200            currentWorkingDirectoryId = spirv[offset + 9];
201        }
202
203        offset += wordCount;
204    }
205
206    return debugInfoImportId != 0 && compilerSignatureId < idBound &&
207           strings[compilerSignatureId] == expectedCompilerSignature && commandLineArgumentsId < idBound &&
208           strings[commandLineArgumentsId] == expectedCommandLineArguments && currentWorkingDirectoryId < idBound &&
209           strings[currentWorkingDirectoryId] == expectedCurrentWorkingDirectory;
210}
211
212class SpvDebugInfoTest : public ::testing::Test {
213protected:
214    bool compileToSpirvWithDebugInfo(const std::string& source, std::vector<uint32_t>& spirv, std::string& error,
215                                     const char* compilerSignature = nullptr,
216                                     const char* commandLineArguments = nullptr,
217                                     const char* currentWorkingDirectory = nullptr)
218    {
219        spirv.clear();
220        error.clear();
221        glslang::TShader shader(EShLangCompute);
222        const char* str = source.c_str();
223        shader.setStrings(&str, 1);
224        shader.setEnvInput(glslang::EShSourceGlsl, EShLangCompute,
225                           glslang::EShClientVulkan, 110);
226        shader.setEnvClient(glslang::EShClientVulkan, glslang::EShTargetVulkan_1_1);
227        shader.setEnvTarget(glslang::EShTargetSpv, glslang::EShTargetSpv_1_3);
228
229        EShMessages messages = static_cast<EShMessages>(EShMsgDefault | EShMsgDebugInfo);
230        if (!shader.parse(GetDefaultResources(), 450, false, messages)) {
231            error = "COMPILATION_FAILED: " + std::string(shader.getInfoLog());
232            return false;
233        }
234
235        glslang::TProgram program;
236        program.addShader(&shader);
237        if (!program.link(messages)) {
238            error = "LINKING_FAILED: " + std::string(program.getInfoLog());
239            return false;
240        }
241
242        glslang::SpvOptions opts;
243        opts.generateDebugInfo = true;
244        opts.emitNonSemanticShaderDebugInfo = true;
245        opts.disableOptimizer = true;
246        opts.compilerSignature = compilerSignature;
247        opts.commandLineArguments = commandLineArguments;
248        opts.currentWorkingDirectory = currentWorkingDirectory;
249
250        glslang::GlslangToSpv(*program.getIntermediate(EShLangCompute), spirv, &opts);
251        return true;
252    }
253
254    // Compile a GLSL compute shader with NonSemantic debug info enabled, targeting
255    // Vulkan 1.1 / SPIR-V 1.3, and return the disassembly.
256    std::string compileWithDebugInfo(const std::string& source)
257    {
258        std::vector<uint32_t> spirv;
259        std::string error;
260        if (!compileToSpirvWithDebugInfo(source, spirv, error))
261            return error;
262
263        std::ostringstream out;
264        spv::Disassemble(out, spirv);
265        return out.str();
266    }
267};
268
269TEST_F(SpvDebugInfoTest, DebugEntryPointUsesSuppliedCompilationMetadata)
270{
271    const std::string source = R"(
272#version 450 core
273layout(local_size_x = 1) in;
274void main() {}
275)";
276    constexpr char compilerSignature[] = "test compiler signature";
277    constexpr char commandLineArguments[] = "-V -gVS shader.comp";
278    constexpr char currentWorkingDirectory[] = "test/current/working/directory";
279    std::vector<uint32_t> spirv;
280    std::string error;
281    ASSERT_TRUE(compileToSpirvWithDebugInfo(source, spirv, error, compilerSignature, commandLineArguments,
282                                           currentWorkingDirectory))
283        << error;
284
285    EXPECT_TRUE(containsDebugEntryPoint(spirv, compilerSignature, commandLineArguments, currentWorkingDirectory));
286}
287
288// DebugTypeVectorIdEXT (opcode 109) must be emitted for OpTypeCooperativeVectorNV
289// when NonSemantic debug info is enabled.
290TEST_F(SpvDebugInfoTest, CooperativeVectorNVEmitsDebugTypeVectorIdEXT)
291{
292    const std::string source = R"(
293#version 450 core
294#extension GL_KHR_memory_scope_semantics : enable
295#extension GL_NV_cooperative_vector : enable
296#extension GL_EXT_shader_explicit_arithmetic_types : enable
297layout(local_size_x = 64) in;
298layout(set = 0, binding = 0) buffer B { float data[]; } buf;
299void main() {
300    coopvecNV<float, 8> v = coopvecNV<float, 8>(0.0);
301    v = v + v;
302    buf.data[gl_GlobalInvocationID.x] = v[0];
303}
304)";
305    std::string spirv = compileWithDebugInfo(source);
306    EXPECT_NE(spirv.find("DebugTypeVectorIdEXT"), std::string::npos)
307        << "Expected DebugTypeVectorIdEXT in disassembly.\nSPIR-V:\n" << spirv;
308    EXPECT_NE(spirv.find("TypeCooperativeVectorNV"), std::string::npos)
309        << "Expected TypeCooperativeVectorNV in disassembly.\nSPIR-V:\n" << spirv;
310}
311
312// DebugTypeVectorIdEXT must also be emitted when the component count is a
313// specialization constant (OpSpecConstant), since the spec allows any constant
314// instruction for the ComponentCount operand.
315TEST_F(SpvDebugInfoTest, CooperativeVectorNVSpecConstCountEmitsDebugTypeVectorIdEXT)
316{
317    const std::string source = R"(
318#version 450 core
319#extension GL_KHR_memory_scope_semantics : enable
320#extension GL_NV_cooperative_vector : enable
321#extension GL_EXT_shader_explicit_arithmetic_types : enable
322layout(local_size_x = 64) in;
323layout(constant_id = 0) const uint N = 16;
324layout(set = 0, binding = 0) buffer B { float data[]; } buf;
325void main() {
326    coopvecNV<float, N> v = coopvecNV<float, N>(1.0);
327    buf.data[gl_GlobalInvocationID.x] = v[0];
328}
329)";
330    std::string spirv = compileWithDebugInfo(source);
331    EXPECT_NE(spirv.find("DebugTypeVectorIdEXT"), std::string::npos)
332        << "Expected DebugTypeVectorIdEXT with spec-constant count.\nSPIR-V:\n" << spirv;
333}
334
335// DebugTypeCooperativeMatrixKHR (opcode 110) must replace the former
336// opaque-composite workaround for OpTypeCooperativeMatrixKHR.
337TEST_F(SpvDebugInfoTest, CooperativeMatrixKHREmitsDebugTypeCooperativeMatrixKHR)
338{
339    const std::string source = R"(
340#version 450 core
341#pragma use_vulkan_memory_model
342#extension GL_KHR_memory_scope_semantics : enable
343#extension GL_KHR_cooperative_matrix : enable
344#extension GL_EXT_shader_explicit_arithmetic_types_float16 : enable
345layout(local_size_x = 32) in;
346layout(set = 0, binding = 0) buffer B { float16_t data[]; } buf;
347void main() {
348    coopmat<float16_t, gl_ScopeSubgroup, 16, 16, gl_MatrixUseAccumulator> m =
349        coopmat<float16_t, gl_ScopeSubgroup, 16, 16, gl_MatrixUseAccumulator>(float16_t(0.0));
350    coopMatStore(m, buf.data, 0, 0, gl_CooperativeMatrixLayoutRowMajor);
351}
352)";
353    std::string spirv = compileWithDebugInfo(source);
354    EXPECT_NE(spirv.find("DebugTypeCooperativeMatrixKHR"), std::string::npos)
355        << "Expected DebugTypeCooperativeMatrixKHR in disassembly.\nSPIR-V:\n" << spirv;
356    // The old opaque-composite workaround must not appear.
357    EXPECT_EQ(spirv.find("coopmat<"), std::string::npos)
358        << "Opaque-composite workaround should not appear in disassembly.\nSPIR-V:\n" << spirv;
359}
360
361// DebugTypeCooperativeMatrixKHR must be emitted when Rows and Columns are
362// specialization constants (OpSpecConstant), since the spec allows any constant
363// instruction for those operands.
364TEST_F(SpvDebugInfoTest, CooperativeMatrixKHRSpecConstDimsEmitsDebugType)
365{
366    const std::string source = R"(
367#version 450 core
368#pragma use_vulkan_memory_model
369#extension GL_KHR_memory_scope_semantics : enable
370#extension GL_KHR_cooperative_matrix : enable
371#extension GL_EXT_shader_explicit_arithmetic_types_float16 : enable
372layout(local_size_x = 32) in;
373layout(constant_id = 0) const uint M = 16;
374layout(constant_id = 1) const uint N = 16;
375layout(set = 0, binding = 0) buffer B { float16_t data[]; } buf;
376void main() {
377    coopmat<float16_t, gl_ScopeSubgroup, M, N, gl_MatrixUseAccumulator> m =
378        coopmat<float16_t, gl_ScopeSubgroup, M, N, gl_MatrixUseAccumulator>(float16_t(0.0));
379    coopMatStore(m, buf.data, 0, 0, gl_CooperativeMatrixLayoutRowMajor);
380}
381)";
382    std::string spirv = compileWithDebugInfo(source);
383    EXPECT_NE(spirv.find("DebugTypeCooperativeMatrixKHR"), std::string::npos)
384        << "Expected DebugTypeCooperativeMatrixKHR with spec-constant dims.\nSPIR-V:\n" << spirv;
385}
386
387// DebugTypeCooperativeMatrixKHR must be emitted for each distinct (component,
388// scope, rows, cols, use) tuple -- MatrixUseA, MatrixUseB, and MatrixUseAccumulator
389// with different component types all produce separate debug type instructions.
390TEST_F(SpvDebugInfoTest, CooperativeMatrixKHRMultipleUsesEmitDistinctDebugTypes)
391{
392    const std::string source = R"(
393#version 450 core
394#pragma use_vulkan_memory_model
395#extension GL_KHR_memory_scope_semantics : enable
396#extension GL_KHR_cooperative_matrix : enable
397#extension GL_EXT_shader_explicit_arithmetic_types_float16 : enable
398layout(local_size_x = 32) in;
399layout(set = 0, binding = 0) buffer B { float16_t data[]; } buf;
400void main() {
401    coopmat<float16_t, gl_ScopeSubgroup, 16, 16, gl_MatrixUseA>           matA;
402    coopmat<float16_t, gl_ScopeSubgroup, 16, 16, gl_MatrixUseB>           matB;
403    coopmat<float,     gl_ScopeSubgroup, 16, 16, gl_MatrixUseAccumulator>  matC =
404        coopmat<float, gl_ScopeSubgroup, 16, 16, gl_MatrixUseAccumulator>(0.0);
405    matC = coopMatMulAdd(matA, matB, matC);
406    coopMatStore(matC, buf.data, 0, 0, gl_CooperativeMatrixLayoutRowMajor);
407}
408)";
409    std::string spirv = compileWithDebugInfo(source);
410
411    // Count occurrences of DebugTypeCooperativeMatrixKHR: one for each distinct
412    // (component, scope, rows, cols, use) combination used above.
413    size_t count = 0;
414    size_t pos = 0;
415    while ((pos = spirv.find("DebugTypeCooperativeMatrixKHR", pos)) != std::string::npos) {
416        ++count;
417        pos += 1;
418    }
419    EXPECT_GE(count, 3u)
420        << "Expected at least 3 DebugTypeCooperativeMatrixKHR instructions.\nSPIR-V:\n" << spirv;
421}
422
423// The removed golden-file test spv.debuginfo.coopmatKHR.comp combined spec-constant
424// dimensions with all three matrix use types. Verify that distinct
425// DebugTypeCooperativeMatrixKHR instructions are emitted for MatrixUseA (lM x lK),
426// MatrixUseB (lK x lN), and MatrixUseAccumulator (lM x lN) when all dimensions are
427// specialization constants.
428TEST_F(SpvDebugInfoTest, CooperativeMatrixKHRSpecConstDimsMultipleUsesEmitDistinctDebugTypes)
429{
430    const std::string source = R"(
431#version 450 core
432#pragma use_vulkan_memory_model
433#extension GL_KHR_memory_scope_semantics : enable
434#extension GL_KHR_cooperative_matrix : enable
435#extension GL_EXT_shader_explicit_arithmetic_types_float16 : enable
436layout(local_size_x = 32) in;
437layout(constant_id = 0) const uint lM = 16;
438layout(constant_id = 1) const uint lN = 16;
439layout(constant_id = 2) const uint lK = 16;
440layout(set = 0, binding = 0) buffer B { float data[]; } buf;
441void main() {
442    coopmat<float16_t, gl_ScopeSubgroup, lM, lK, gl_MatrixUseA>           matA;
443    coopmat<float16_t, gl_ScopeSubgroup, lK, lN, gl_MatrixUseB>           matB;
444    coopmat<float,     gl_ScopeSubgroup, lM, lN, gl_MatrixUseAccumulator>  matC =
445        coopmat<float, gl_ScopeSubgroup, lM, lN, gl_MatrixUseAccumulator>(0.0);
446    matC = coopMatMulAdd(matA, matB, matC);
447    coopMatStore(matC, buf.data, 0, 0, gl_CooperativeMatrixLayoutRowMajor);
448}
449)";
450    std::string spirv = compileWithDebugInfo(source);
451
452    size_t count = 0;
453    size_t pos = 0;
454    while ((pos = spirv.find("DebugTypeCooperativeMatrixKHR", pos)) != std::string::npos) {
455        ++count;
456        pos += 1;
457    }
458    EXPECT_GE(count, 3u)
459        << "Expected at least 3 DebugTypeCooperativeMatrixKHR instructions "
460           "(one per use type with spec-constant dimensions).\nSPIR-V:\n" << spirv;
461}
462
463// DebugTypeBasic for bfloat16_t must carry the debug type name "bfloat16_t",
464// which is emitted only when the FPEncoding operand (BFloat16KHR = 0) is present.
465TEST_F(SpvDebugInfoTest, BFloat16EmitsDebugTypeBasicWithFPEncoding)
466{
467    const std::string source = R"(
468#version 450 core
469#extension GL_EXT_bfloat16 : require
470#extension GL_EXT_shader_explicit_arithmetic_types : enable
471layout(local_size_x = 64) in;
472layout(set = 0, binding = 0) buffer B { float data[]; } buf;
473void main() {
474    bfloat16_t bf = bfloat16_t(1.0);
475    buf.data[gl_GlobalInvocationID.x] = float(bf);
476}
477)";
478    std::string spirv = compileWithDebugInfo(source);
479    // The DebugTypeBasic for bfloat16_t should contain the name "bfloat16_t".
480    EXPECT_NE(spirv.find("\"bfloat16_t\""), std::string::npos)
481        << "Expected \"bfloat16_t\" string in debug type.\nSPIR-V:\n" << spirv;
482}
483
484// DebugTypeBasic for floate4m3_t must carry the debug type name "floate4m3_t",
485// which is emitted only when the FPEncoding operand (Float8E4M3EXT = 4214) is present.
486TEST_F(SpvDebugInfoTest, Float8E4M3EmitsDebugTypeBasicWithFPEncoding)
487{
488    const std::string source = R"(
489#version 450 core
490#extension GL_EXT_bfloat16 : require
491#extension GL_EXT_float_e4m3 : require
492#extension GL_EXT_shader_explicit_arithmetic_types : enable
493layout(local_size_x = 64) in;
494layout(set = 0, binding = 0) buffer B { float data[]; } buf;
495void main() {
496    floate4m3_t f = floate4m3_t(2.0);
497    buf.data[gl_GlobalInvocationID.x] = float(f);
498}
499)";
500    std::string spirv = compileWithDebugInfo(source);
501    EXPECT_NE(spirv.find("\"floate4m3_t\""), std::string::npos)
502        << "Expected \"floate4m3_t\" string in debug type.\nSPIR-V:\n" << spirv;
503}
504
505// DebugTypeBasic for floate5m2_t must carry the debug type name "floate5m2_t",
506// which is emitted only when the FPEncoding operand (Float8E5M2EXT = 4215) is present.
507TEST_F(SpvDebugInfoTest, Float8E5M2EmitsDebugTypeBasicWithFPEncoding)
508{
509    const std::string source = R"(
510#version 450 core
511#extension GL_EXT_bfloat16 : require
512#extension GL_EXT_float_e5m2 : require
513#extension GL_EXT_shader_explicit_arithmetic_types : enable
514layout(local_size_x = 64) in;
515layout(set = 0, binding = 0) buffer B { float data[]; } buf;
516void main() {
517    floate5m2_t f = floate5m2_t(3.0);
518    buf.data[gl_GlobalInvocationID.x] = float(f);
519}
520)";
521    std::string spirv = compileWithDebugInfo(source);
522    EXPECT_NE(spirv.find("\"floate5m2_t\""), std::string::npos)
523        << "Expected \"floate5m2_t\" string in debug type.\nSPIR-V:\n" << spirv;
524}
525
526// DebugTypeBasic for OCP microscaling types must carry their source-level type
527// names, which are emitted only when the FPEncoding operand is present.
528TEST_F(SpvDebugInfoTest, OcpMicroscalingTypesEmitDebugTypeBasicWithFPEncoding)
529{
530    const std::string source = R"(
531#version 450 core
532#extension GL_EXT_float_e2m1 : require
533#extension GL_EXT_float_e3m2 : require
534#extension GL_EXT_float_e2m3 : require
535#extension GL_EXT_float_ue8m0 : require
536#extension GL_EXT_float_mxint8 : require
537#extension GL_EXT_shader_explicit_arithmetic_types : enable
538layout(local_size_x = 64) in;
539layout(set = 0, binding = 0) buffer B { float data[]; } buf;
540void main() {
541    floate2m1_t e2m1 = floate2m1_t(1.0fe2m1);
542    floate3m2_t e3m2 = floate3m2_t(1.0fe3m2);
543    floate2m3_t e2m3 = floate2m3_t(1.0fe2m3);
544    floatue8m0_t ue8m0 = floatue8m0_t(1.0fue8m0);
545    floatmxint8_t mxint8 = floatmxint8_t(1.0fmxint8);
546    buf.data[gl_GlobalInvocationID.x] =
547        float(e2m1) + float(e3m2) + float(e2m3) + float(ue8m0) + float(mxint8);
548}
549)";
550    std::vector<uint32_t> spirv;
551    std::string error;
552    ASSERT_TRUE(compileToSpirvWithDebugInfo(source, spirv, error)) << error;
553
554    struct ExpectedDebugType {
555        const char* name;
556        uint32_t width;
557        spv::FPEncoding fpEncoding;
558    };
559    const ExpectedDebugType expectedTypes[] = {
560        {"floate2m1_t", 4, spv::FPEncoding::Float4E2M1EXT},
561        {"floate3m2_t", 6, spv::FPEncoding::Float6E3M2EXT},
562        {"floate2m3_t", 6, spv::FPEncoding::Float6E2M3EXT},
563        {"floatue8m0_t", 8, spv::FPEncoding::Float8UnsignedE8M0EXT},
564        {"floatmxint8_t", 8, spv::FPEncoding::MXInt8EXT},
565    };
566
567    std::ostringstream disassembly;
568    spv::Disassemble(disassembly, spirv);
569    for (const auto& expected : expectedTypes) {
570        EXPECT_TRUE(containsDebugTypeBasic(spirv, expected.name, expected.width, expected.fpEncoding))
571            << "Expected DebugTypeBasic for \"" << expected.name << "\" with width " << expected.width
572            << " and FPEncoding " << static_cast<uint32_t>(expected.fpEncoding)
573            << ".\nSPIR-V:\n" << disassembly.str();
574    }
575}
576
577// When a version-101 opcode is emitted the import string must be promoted to
578// NonSemantic.Shader.DebugInfo.101.
579TEST_F(SpvDebugInfoTest, Version101OpcodePromotesImportStringTo101)
580{
581    // DebugTypeCooperativeMatrixKHR is a version-101 opcode; using it must
582    // cause the import string to read ".101", not ".100".
583    const std::string source = R"(
584#version 450 core
585#pragma use_vulkan_memory_model
586#extension GL_KHR_memory_scope_semantics : enable
587#extension GL_KHR_cooperative_matrix : enable
588#extension GL_EXT_shader_explicit_arithmetic_types_float16 : enable
589layout(local_size_x = 32) in;
590layout(set = 0, binding = 0) buffer B { float16_t data[]; } buf;
591void main() {
592    coopmat<float16_t, gl_ScopeSubgroup, 16, 16, gl_MatrixUseAccumulator> m =
593        coopmat<float16_t, gl_ScopeSubgroup, 16, 16, gl_MatrixUseAccumulator>(float16_t(0.0));
594    coopMatStore(m, buf.data, 0, 0, gl_CooperativeMatrixLayoutRowMajor);
595}
596)";
597    std::string spirv = compileWithDebugInfo(source);
598    EXPECT_NE(spirv.find("\"NonSemantic.Shader.DebugInfo.101\""), std::string::npos)
599        << "Expected import string NonSemantic.Shader.DebugInfo.101.\nSPIR-V:\n" << spirv;
600    EXPECT_EQ(spirv.find("\"NonSemantic.Shader.DebugInfo.100\""), std::string::npos)
601        << "Import string must not be .100 when version-101 opcodes are used.\nSPIR-V:\n" << spirv;
602}
603
604// When only version-100 opcodes are emitted the import string must stay at
605// NonSemantic.Shader.DebugInfo.100 and must not be promoted to .101.
606TEST_F(SpvDebugInfoTest, Version100OnlyKeepsImportStringAt100)
607{
608    const std::string source = R"(
609#version 450 core
610layout(local_size_x = 64) in;
611layout(set = 0, binding = 0) buffer B { float data[]; } buf;
612void main() {
613    float x = buf.data[gl_GlobalInvocationID.x];
614    buf.data[gl_GlobalInvocationID.x] = x * 2.0;
615}
616)";
617    std::string spirv = compileWithDebugInfo(source);
618    EXPECT_NE(spirv.find("\"NonSemantic.Shader.DebugInfo.100\""), std::string::npos)
619        << "Expected import string NonSemantic.Shader.DebugInfo.100.\nSPIR-V:\n" << spirv;
620    EXPECT_EQ(spirv.find("\"NonSemantic.Shader.DebugInfo.101\""), std::string::npos)
621        << "Import string must not be .101 when only version-100 opcodes are used.\nSPIR-V:\n" << spirv;
622}
623
624} // namespace
625} // namespace glslangtest