File: spirv_logging.cpp

package info (click to toggle)
vulkan-validationlayers 1.4.321.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 47,412 kB
  • sloc: cpp: 594,175; python: 11,321; sh: 24; makefile: 20; xml: 14
file content (475 lines) | stat: -rw-r--r-- 20,501 bytes parent folder | download | duplicates (6)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
/* Copyright (c) 2024-2025 The Khronos Group Inc.
 * Copyright (c) 2024-2025 Valve Corporation
 * Copyright (c) 2024-2025 LunarG, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "spirv_logging.h"
#include <string>
#include <cstring>

// Fix GCC 13 issues with regex
#if defined(__GNUC__) && (__GNUC__ > 12)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
#endif
#include <regex>
#if defined(__GNUC__) && (__GNUC__ > 12)
#pragma GCC diagnostic pop
#endif

#include "state_tracker/shader_instruction.h"
#include <spirv/unified1/NonSemanticShaderDebugInfo100.h>
#include <spirv/unified1/spirv.hpp>
#include "generated/spirv_grammar_helper.h"

namespace spirv {

static const int kModuleStartingOffset = 5;  // first 5 words of module are the headers
static inline uint32_t Opcode(uint32_t instruction) { return instruction & 0x0ffffu; }
static inline uint32_t Length(uint32_t instruction) { return instruction >> 16; }

struct SpirvLoggingInfo {
    uint32_t file_string_id = 0;  // OpString with filename
    uint32_t line_number_start = 0;
    uint32_t line_number_end = 0;
    // sometimes compiler will just give zero here, so will need to ignore then
    uint32_t column_number = 0;
    bool using_shader_debug_info = false;  // NonSemantic.Shader.DebugInfo.100
    std::string reported_filename;
};

// Read the contents of the SPIR-V OpSource instruction and any following continuation instructions.
// Split the single string into a vector of strings, one for each line, for easier processing.
static void ReadOpSource(const std::vector<uint32_t> &instructions, const uint32_t reported_file_id,
                         std::vector<std::string> &out_source_lines) {
    uint32_t offset = kModuleStartingOffset;
    while (offset < instructions.size()) {
        const uint32_t instruction = instructions[offset];
        const uint32_t length = Length(instruction);
        const uint32_t opcode = Opcode(instruction);
        if (opcode != spv::OpSource || length < 5 || instructions[offset + 3] != reported_file_id) {
            offset += length;
            continue;
        }

        // OpSource has been found
        std::istringstream in_stream;
        std::string current_line;
        const char *str = reinterpret_cast<const char *>(&instructions[offset + 4]);
        in_stream.str(str);
        while (std::getline(in_stream, current_line)) {
            out_source_lines.emplace_back(current_line);
        }

        offset += length;  // point to next instruction after OpSource
        break;
    }

    // Look for OpSourceContinued, it must be right after
    while (offset < instructions.size()) {
        const uint32_t continue_insn = instructions[offset];
        const uint32_t length = Length(continue_insn);
        const uint32_t opcode = Opcode(continue_insn);
        if (opcode != spv::OpSourceContinued) {
            break;
        }

        std::istringstream in_stream;
        std::string current_line;
        const char *str = reinterpret_cast<const char *>(&instructions[offset + 1]);
        in_stream.str(str);
        while (std::getline(in_stream, current_line)) {
            out_source_lines.emplace_back(current_line);
        }
        offset += length;
    }
}

static void ReadDebugSource(const std::vector<uint32_t> &instructions, const uint32_t debug_source_id, uint32_t &out_file_string_id,
                            std::vector<std::string> &out_source_lines) {
    uint32_t offset = kModuleStartingOffset;
    while (offset < instructions.size()) {
        const uint32_t instruction = instructions[offset];
        const uint32_t length = Length(instruction);
        const uint32_t opcode = Opcode(instruction);
        if (opcode != spv::OpExtInst || instructions[offset + 2] != debug_source_id ||
            instructions[offset + 4] != NonSemanticShaderDebugInfo100DebugSource) {
            offset += length;
            continue;
        }

        // We have now found the proper OpExtInst DebugSource
        out_file_string_id = instructions[offset + 5];

        // Optional source Text not provided so nothing left to do
        if (length < 7) {
            return;
        }

        const uint32_t string_id = instructions[offset + 6];
        const char *source_text = spirv::GetOpString(instructions, string_id);
        if (!source_text) {
            return;  // error should be caught in spirv-val, but don't crash here
        }

        std::istringstream in_stream;
        std::string current_line;
        in_stream.str(source_text);
        while (std::getline(in_stream, current_line)) {
            out_source_lines.emplace_back(current_line);
        }

        offset += length;  // point to next instruction after OpSource
        break;
    }

    // Look for DebugSourceContinued, it must be right after
    while (offset < instructions.size()) {
        const uint32_t continue_insn = instructions[offset];
        const uint32_t length = Length(continue_insn);
        const uint32_t opcode = Opcode(continue_insn);

        if (opcode != spv::OpExtInst || instructions[offset + 4] != NonSemanticShaderDebugInfo100DebugSourceContinued) {
            break;
        }

        const uint32_t string_id = instructions[offset + 5];
        const char *continue_text = spirv::GetOpString(instructions, string_id);
        if (!continue_text) {
            return;  // error should be caught in spirv-val, but don't crash here
        }

        std::istringstream in_stream;
        std::string current_line;
        in_stream.str(continue_text);
        while (std::getline(in_stream, current_line)) {
            out_source_lines.emplace_back(current_line);
        }

        offset += length;
    }
}

// The task here is to search the OpSource content to find the #line directive with the
// line number that is closest to, but still prior to the reported error line number and
// still within the reported filename.
// From this known position in the OpSource content we can add the difference between
// the #line line number and the reported error line number to determine the location
// in the OpSource content of the reported error line.
//
// Considerations:
// - Look only at #line directives that specify the reported_filename since
//   the reported error line number refers to its location in the reported filename.
// - If a #line directive does not have a filename, the file is the reported filename, or
//   the filename found in a prior #line directive.  (This is C-preprocessor behavior)
// - It is possible (e.g., inlining) for blocks of code to get shuffled out of their
//   original order and the #line directives are used to keep the numbering correct.  This
//   is why we need to examine the entire contents of the source, instead of leaving early
//   when finding a #line line number larger than the reported error line number.
//
static bool GetLineFromDirective(const std::string &string, uint32_t *linenumber, std::string &filename) {
    static const std::regex line_regex(  // matches #line directives
        "^"                              // beginning of line
        "\\s*"                           // optional whitespace
        "#"                              // required text
        "\\s*"                           // optional whitespace
        "line"                           // required text
        "\\s+"                           // required whitespace
        "([0-9]+)"                       // required first capture - line number
        "(\\s+)?"                        // optional second capture - whitespace
        "(\".+\")?"                      // optional third capture - quoted filename with at least one char inside
        ".*");                           // rest of line (needed when using std::regex_match since the entire line is tested)

    std::smatch captures;

    const bool found_line = std::regex_match(string, captures, line_regex);
    if (!found_line) return false;

    // filename is optional and considered found only if the whitespace and the filename are captured
    if (captures[2].matched && captures[3].matched) {
        // Remove enclosing double quotes.  The regex guarantees the quotes and at least one char.
        filename = captures[3].str().substr(1, captures[3].str().size() - 2);
    }
    *linenumber = (uint32_t)std::stoul(captures[1]);
    return true;
}

// Return false if any error arise
static bool GetLineAndFilename(std::ostringstream &ss, const std::vector<uint32_t> &instructions, SpirvLoggingInfo &logging_info) {
    const std::string debug_info_type = (logging_info.using_shader_debug_info) ? "DebugSource" : "OpLine";
    if (logging_info.file_string_id == 0) {
        // This error should be caught in spirv-val
        ss << "(Unable to find file string from SPIR-V " << debug_info_type << ")\n";
        return false;
    }

    const char *file_string_insn = spirv::GetOpString(instructions, logging_info.file_string_id);
    if (!file_string_insn) {
        // This error should be caught in spirv-val
        ss << "(Unable to find SPIR-V OpString " << logging_info.file_string_id << " from " << debug_info_type << " instruction)\n";
        return false;
    }

    // We print out lines the same way gcc/clang does
    //    somefile.cpp:33:22
    // Where 33 is the line and 22 is the column
    // Currently we don't have a real good use to try and use the end line/columns (we are not selecting)
    logging_info.reported_filename = std::string(file_string_insn);
    if (!logging_info.reported_filename.empty()) {
        ss << logging_info.reported_filename << ':';
    } else {
        ss << "<source>:";
    }
    ss << logging_info.line_number_start;
    if (logging_info.column_number != 0) {
        ss << ':' << logging_info.column_number;
    }

    ss << '\n';
    return true;
}

static void GetSourceLines(std::ostringstream &ss, const std::vector<std::string> &source_lines,
                           const SpirvLoggingInfo &logging_info) {
    if (source_lines.empty()) {
        if (logging_info.using_shader_debug_info) {
            ss << "No Text operand found in DebugSource\n";
        } else {
            ss << "Unable to find SPIR-V OpSource\n";
        }
        return;
    }

    // Find the line in the OpSource content that corresponds to the reported error file and line.
    uint32_t saved_line_number = 0;
    std::string current_filename = logging_info.reported_filename;  // current "preprocessor" filename state.
    std::vector<std::string>::size_type saved_opsource_offset = 0;

    // This was designed to fine the best line if using #line in GLSL
    bool found_best_line = false;
    if (!logging_info.using_shader_debug_info) {
        for (auto it = source_lines.begin(); it != source_lines.end(); ++it) {
            uint32_t parsed_line_number;
            std::string parsed_filename;
            const bool found_line = GetLineFromDirective(*it, &parsed_line_number, parsed_filename);
            if (!found_line) continue;

            const bool found_filename = parsed_filename.size() > 0;
            if (found_filename) {
                current_filename = parsed_filename;
            }
            if ((!found_filename) || (current_filename == logging_info.reported_filename)) {
                // Update the candidate best line directive, if the current one is prior and closer to the reported line
                if (logging_info.line_number_start >= parsed_line_number) {
                    if (!found_best_line || (logging_info.line_number_start - parsed_line_number <=
                                             logging_info.line_number_start - saved_line_number)) {
                        saved_line_number = parsed_line_number;
                        saved_opsource_offset = std::distance(source_lines.begin(), it);
                        found_best_line = true;
                    }
                }
            }
        }
    }

    if (logging_info.using_shader_debug_info) {
        // For Shader Debug Info, we should have all the information we need
        ss << '\n';
        for (uint32_t line_index = logging_info.line_number_start; line_index <= logging_info.line_number_end; line_index++) {
            if (line_index > source_lines.size()) {
                ss << line_index << ": [No line found in source]";
                break;
            }
            ss << line_index << ": " << source_lines[line_index - 1] << '\n';
        }
        // Only show column if since a single line is displayed
        if (logging_info.column_number > 0 && logging_info.line_number_start == logging_info.line_number_end) {
            // If normally it would be '  someCode' it will look like '23:   someCode'
            // We need to add columns for the line number prefix we are adding prior
            const size_t line_index_spaces = std::to_string(logging_info.line_number_start).length();
            const size_t column_count = logging_info.column_number + line_index_spaces + 1;
            std::string spaces(column_count, ' ');
            ss << spaces << '^';
        }

    } else if (found_best_line) {
        assert(logging_info.line_number_start >= saved_line_number);
        const size_t opsource_index = (logging_info.line_number_start - saved_line_number) + 1 + saved_opsource_offset;
        if (opsource_index < source_lines.size()) {
            ss << '\n' << logging_info.line_number_start << ": " << source_lines[opsource_index] << '\n';
        } else {
            ss << "Internal error: calculated source line of " << opsource_index << " for source size of " << source_lines.size()
               << " lines\n";
        }
    } else if (logging_info.line_number_start < source_lines.size() && logging_info.line_number_start != 0) {
        // file lines normally start at 1 index
        ss << '\n' << source_lines[logging_info.line_number_start - 1] << '\n';
        if (logging_info.column_number > 0) {
            std::string spaces(logging_info.column_number - 1, ' ');
            ss << spaces << '^';
        }
    } else {
        ss << "Unable to find a suitable line in SPIR-V OpSource\n";
    }
}

void GetShaderSourceInfo(std::ostringstream &ss, const std::vector<uint32_t> &instructions, const Instruction &last_line_insn) {
    // Read the source code and split it up into separate lines.
    //
    // 1. OpLine will point to a OpSource/OpSourceContinued which have the string built-in
    // 2. DebugLine will point to a DebugSource/DebugSourceContinued that each point to a OpString
    //
    // For the second one, we need to build the source lines up sooner
    std::vector<std::string> source_lines;

    SpirvLoggingInfo logging_info = {};
    if (last_line_insn.Opcode() == spv::OpLine) {
        logging_info.using_shader_debug_info = false;
        logging_info.file_string_id = last_line_insn.Word(1);
        logging_info.line_number_start = last_line_insn.Word(2);
        logging_info.line_number_end = logging_info.line_number_start;  // OpLine only give a single line granularity
        logging_info.column_number = last_line_insn.Word(3);
    } else {
        // NonSemanticShaderDebugInfo100DebugLine
        logging_info.using_shader_debug_info = true;
        logging_info.line_number_start = GetConstantValue(instructions, last_line_insn.Word(6));
        logging_info.line_number_end = GetConstantValue(instructions, last_line_insn.Word(7));
        logging_info.column_number = GetConstantValue(instructions, last_line_insn.Word(8));
        const uint32_t debug_source_id = last_line_insn.Word(5);
        ReadDebugSource(instructions, debug_source_id, logging_info.file_string_id, source_lines);
    }

    if (!GetLineAndFilename(ss, instructions, logging_info)) {
        return;
    }

    // Defer finding source from OpLine until we know we have a valid file string to tie it too
    if (!logging_info.using_shader_debug_info) {
        ReadOpSource(instructions, logging_info.file_string_id, source_lines);
    }

    GetSourceLines(ss, source_lines, logging_info);
}

const char *GetOpString(const std::vector<uint32_t> &instructions, uint32_t string_id) {
    uint32_t offset = kModuleStartingOffset;
    while (offset < instructions.size()) {
        const uint32_t instruction = instructions[offset];
        const uint32_t length = Length(instruction);
        const uint32_t opcode = Opcode(instruction);

        // if here, seen all OpString and can return early
        if (opcode == spv::OpFunction) break;

        if (opcode == spv::OpString) {
            const uint32_t result_id = instructions[offset + 1];
            if (result_id == string_id) {
                return reinterpret_cast<const char *>(&instructions[offset + 2]);
            }
        }
        offset += length;
    }
    return nullptr;
}

uint32_t GetConstantValue(const std::vector<uint32_t> &instructions, uint32_t constant_id) {
    uint32_t offset = kModuleStartingOffset;
    while (offset < instructions.size()) {
        const uint32_t instruction = instructions[offset];
        const uint32_t length = Length(instruction);
        const uint32_t opcode = Opcode(instruction);

        // if here, seen all OpConstant and can return early
        if (opcode == spv::OpFunction) break;

        if (opcode == spv::OpConstant) {
            const uint32_t result_id = instructions[offset + 2];
            if (result_id == constant_id) {
                return instructions[offset + 3];
            }
        }
        offset += length;
    }
    assert(false);
    return 0;
}

void GetExecutionModelNames(const std::vector<uint32_t> &instructions, std::ostringstream &ss) {
    bool first_stage = true;

    uint32_t offset = kModuleStartingOffset;
    while (offset < instructions.size()) {
        const uint32_t instruction = instructions[offset];
        const uint32_t length = Length(instruction);
        const uint32_t opcode = Opcode(instruction);

        // if here, seen all OpEntryPoint and can return early
        if (opcode == spv::OpFunction) break;

        if (opcode == spv::OpEntryPoint) {
            if (first_stage) {
                first_stage = false;
            } else {
                ss << ", ";
            }
            const uint32_t entry_point_id = instructions[offset + 1];
            ss << string_SpvExecutionModel(entry_point_id);
        }

        offset += length;
    }
}

// Find the OpLine/DebugLine just before the failing instruction indicated by the debug info.
// Return the offset into the instructions array
uint32_t GetDebugLineOffset(const std::vector<uint32_t> &instructions, uint32_t instruction_position) {
    uint32_t index = 0;
    uint32_t shader_debug_info_set_id = 0;
    uint32_t last_line_inst_offset = 0;

    uint32_t offset = kModuleStartingOffset;
    while (offset < instructions.size()) {
        const uint32_t instruction = instructions[offset];
        const uint32_t length = Length(instruction);
        const uint32_t opcode = Opcode(instruction);

        if (opcode == spv::OpExtInstImport) {
            const char *str = reinterpret_cast<const char *>(&instructions[offset + 2]);
            if (strcmp(str, "NonSemantic.Shader.DebugInfo.100") == 0) {
                shader_debug_info_set_id = instructions[offset + 1];
            }
        }

        if (opcode == spv::OpExtInst && instructions[offset + 3] == shader_debug_info_set_id &&
            instructions[offset + 4] == NonSemanticShaderDebugInfo100DebugLine) {
            last_line_inst_offset = offset;
        } else if (opcode == spv::OpLine) {
            last_line_inst_offset = offset;
        } else if (opcode == spv::OpFunctionEnd) {
            last_line_inst_offset = 0;  // debug lines can't cross functions boundaries
        }

        if (index == instruction_position) {
            break;
        }
        index++;

        offset += length;
    }

    return last_line_inst_offset;
}

}  // namespace spirv