glslang icon indicating copy to clipboard operation
glslang copied to clipboard

Add Declaration Nodes to AST for Better Debug Info

Open qingyuanzNV opened this issue 9 months ago • 2 comments

Glslang is having trouble to generate good debug information for variables because we don't have AST representation for variable definitions. The variables are generated at the site of its first use, where some of the original declaration information is already lost.

In https://github.com/KhronosGroup/glslang/pull/3401, I attempted to correct the global variable line information by adding a TSourceLoc to track the original definition location in TIntermSource. But this approach doesn't fix a broader issue that some variables may be stripped away because of constant folding or unused symbol.

We could probably do better with an optional toggle to allow glslang to build AST nodes for variable declaration. A proof-of-concept implementation is at here. This should be optional to 1) don't slow down standard library compilation 2) don't break existing programs that work on glslang AST

Given an example shader program:

out float outz;

and

#version 450
#extension GL_GOOGLE_include_directive : require
#include "test.h"

// CHECK: COMMENT ONE!
const float one = 1;
in float inx, iny;
in float inz;

float bar(int x) {
    return x + one;
}

float foo(float x, const float y) {
    return x + y;
}

void main() {
    const int localOne = 1;
    outz = foo(inx, iny) + bar(localOne);
}

We'll create an AST

Shader version: 450
Requested GL_GOOGLE_cpp_style_line_directive
Requested GL_GOOGLE_include_directive
gl_FragCoord origin is upper left
0:? Sequence
0:2  Sequence
0:?     Decl: outz
0:6  Sequence
0:?     Decl: one
0:7  Sequence
0:?     Decl: inx
0:?     Decl: iny
0:8  Sequence
0:?     Decl: inz
0:10  Function Definition: bar(i1; ( global highp float)
0:10    Function Parameters:
0:10      'x' ( in highp int)
0:11    Sequence
0:11      Branch: Return with expression
0:11        add ( temp highp float)
0:11          Convert int to float ( temp highp float)
0:11            'x' ( in highp int)
0:11          Constant:
0:11            1.000000
0:14  Function Definition: foo(f1;f1; ( global highp float)
0:14    Function Parameters:
0:14      'x' ( in highp float)
0:14      'y' ( const (read only) highp float)
0:15    Sequence
0:15      Branch: Return with expression
0:15        add ( temp highp float)
0:15          'x' ( in highp float)
0:15          'y' ( const (read only) highp float)
0:18  Function Definition: main( ( global void)
0:18    Function Parameters:
0:19    Sequence
0:19      Sequence
0:?         Decl: localOne
0:20      move second child to first child ( temp highp float)
0:20        'outz' ( out highp float)
0:20        add ( temp highp float)
0:20          Function Call: foo(f1;f1; ( global highp float)
0:20            'inx' ( smooth in highp float)
0:20            'iny' ( smooth in highp float)
0:20          Function Call: bar(i1; ( global highp float)
0:20            Constant:
0:20              1 (const int)
0:?   Linker Objects
0:?     'outz' ( out highp float)
0:?     'one' ( const highp float)
0:?       1.000000
0:?     'inx' ( smooth in highp float)
0:?     'iny' ( smooth in highp float)
0:?     'inz' ( smooth in highp float)

resulting in SPIR-V (with non-semantic debug):

; SPIR-V
; Version: 1.0
; Generator: Khronos Glslang Reference Front End; 11
; Bound: 124
; Schema: 0
               OpCapability Shader
               OpExtension "SPV_KHR_non_semantic_info"
          %3 = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
          %4 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %main "main" %outz %inx %iny %inz
               OpExecutionMode %main OriginUpperLeft
          %1 = OpString "test.frag"
          %2 = OpString "./test.h"
          %9 = OpString "uint"
         %18 = OpString "int"
         %23 = OpString "float"
         %30 = OpString "bar"
         %33 = OpString "// OpModuleProcessed auto-map-bindings
// OpModuleProcessed auto-map-locations
// OpModuleProcessed client vulkan100
// OpModuleProcessed target-env vulkan1.0
// OpModuleProcessed entry-point main
#line 1
#version 450
#extension GL_GOOGLE_include_directive : require
#include \"test.h\"

// CHECK: COMMENT ONE!
const float one = 1;
in float inx, iny;
in float inz;

float bar(int x) {
    return x + one;
}

float foo(float x, const float y) {
    return x + y;
}

void main() {
    const int localOne = 1;
    outz = foo(inx, iny) + bar(localOne);
}"
         %41 = OpString "x"
         %51 = OpString "foo"
         %59 = OpString "y"
         %61 = OpString "main"
         %67 = OpString "

out float outz;"
         %71 = OpString "outz"
         %76 = OpString "one"
         %82 = OpString "inx"
         %85 = OpString "iny"
         %89 = OpString "inz"
        %113 = OpString "localOne"
               OpSourceExtension "GL_GOOGLE_cpp_style_line_directive"
               OpSourceExtension "GL_GOOGLE_include_directive"
               OpName %main "main"
               OpName %bar_i1_ "bar(i1;"
               OpName %x "x"
               OpName %foo_f1_f1_ "foo(f1;f1;"
               OpName %x_0 "x"
               OpName %y "y"
               OpName %outz "outz"
               OpName %inx "inx"
               OpName %iny "iny"
               OpName %inz "inz"
               OpName %param "param"
               OpName %param_0 "param"
       %void = OpTypeVoid
          %6 = OpTypeFunction %void
       %uint = OpTypeInt 32 0
    %uint_32 = OpConstant %uint 32
     %uint_6 = OpConstant %uint 6
     %uint_0 = OpConstant %uint 0
         %10 = OpExtInst %void %3 DebugTypeBasic %9 %uint_32 %uint_6 %uint_0
     %uint_3 = OpConstant %uint 3
          %7 = OpExtInst %void %3 DebugTypeFunction %uint_3 %void
        %int = OpTypeInt 32 1
     %uint_4 = OpConstant %uint 4
         %19 = OpExtInst %void %3 DebugTypeBasic %18 %uint_32 %uint_4 %uint_0
%_ptr_Function_int = OpTypePointer Function %int
      %float = OpTypeFloat 32
         %24 = OpExtInst %void %3 DebugTypeBasic %23 %uint_32 %uint_3 %uint_0
         %25 = OpTypeFunction %float %_ptr_Function_int
         %26 = OpExtInst %void %3 DebugTypeFunction %uint_3 %24 %19
         %32 = OpExtInst %void %3 DebugSource %1 %33
    %uint_10 = OpConstant %uint 10
     %uint_1 = OpConstant %uint 1
     %uint_2 = OpConstant %uint 2
         %35 = OpExtInst %void %3 DebugCompilationUnit %uint_1 %uint_4 %32 %uint_2
         %31 = OpExtInst %void %3 DebugFunction %30 %26 %32 %uint_10 %uint_0 %35 %30 %uint_3 %uint_10
         %40 = OpExtInst %void %3 DebugLocalVariable %41 %19 %32 %uint_10 %uint_0 %31 %uint_4 %uint_1
         %43 = OpExtInst %void %3 DebugExpression
%_ptr_Function_float = OpTypePointer Function %float
         %45 = OpTypeFunction %float %_ptr_Function_float %float
         %46 = OpExtInst %void %3 DebugTypeFunction %uint_3 %24 %24 %24
    %uint_14 = OpConstant %uint 14
         %52 = OpExtInst %void %3 DebugFunction %51 %46 %32 %uint_14 %uint_0 %35 %51 %uint_3 %uint_14
         %56 = OpExtInst %void %3 DebugLocalVariable %41 %24 %32 %uint_14 %uint_0 %52 %uint_4 %uint_1
         %58 = OpExtInst %void %3 DebugLocalVariable %59 %24 %32 %uint_14 %uint_0 %52 %uint_4 %uint_2
    %uint_18 = OpConstant %uint 18
         %62 = OpExtInst %void %3 DebugFunction %61 %7 %32 %uint_18 %uint_0 %35 %61 %uint_3 %uint_18
         %66 = OpExtInst %void %3 DebugSource %2 %67
%_ptr_Output_float = OpTypePointer Output %float
       %outz = OpVariable %_ptr_Output_float Output
     %uint_8 = OpConstant %uint 8
         %70 = OpExtInst %void %3 DebugGlobalVariable %71 %24 %66 %uint_2 %uint_0 %35 %71 %outz %uint_8
    %float_1 = OpConstant %float 1
         %75 = OpExtInst %void %3 DebugGlobalVariable %76 %24 %32 %uint_6 %uint_0 %35 %76 %float_1 %uint_8
     %uint_7 = OpConstant %uint 7
%_ptr_Input_float = OpTypePointer Input %float
        %inx = OpVariable %_ptr_Input_float Input
         %81 = OpExtInst %void %3 DebugGlobalVariable %82 %24 %32 %uint_7 %uint_0 %35 %82 %inx %uint_8
        %iny = OpVariable %_ptr_Input_float Input
         %84 = OpExtInst %void %3 DebugGlobalVariable %85 %24 %32 %uint_7 %uint_0 %35 %85 %iny %uint_8
        %inz = OpVariable %_ptr_Input_float Input
         %88 = OpExtInst %void %3 DebugGlobalVariable %89 %24 %32 %uint_8 %uint_0 %35 %89 %inz %uint_8
    %uint_11 = OpConstant %uint 11
    %uint_15 = OpConstant %uint 15
    %uint_19 = OpConstant %uint 19
      %int_1 = OpConstant %int 1
        %112 = OpExtInst %void %3 DebugLocalVariable %113 %19 %32 %uint_19 %uint_0 %62 %uint_4
    %uint_20 = OpConstant %uint 20
               OpLine %1 18 11
       %main = OpFunction %void None %6
         %16 = OpLabel
      %param = OpVariable %_ptr_Function_float Function
    %param_0 = OpVariable %_ptr_Function_int Function
         %64 = OpExtInst %void %3 DebugScope %35
         %65 = OpExtInst %void %3 DebugLine %66 %uint_2 %uint_2 %uint_0 %uint_0
         %73 = OpExtInst %void %3 DebugLine %32 %uint_6 %uint_6 %uint_0 %uint_0
         %77 = OpExtInst %void %3 DebugLine %32 %uint_7 %uint_7 %uint_0 %uint_0
         %86 = OpExtInst %void %3 DebugLine %32 %uint_8 %uint_8 %uint_0 %uint_0
        %107 = OpExtInst %void %3 DebugFunctionDefinition %62 %main
        %108 = OpExtInst %void %3 DebugScope %62
        %109 = OpExtInst %void %3 DebugLine %32 %uint_19 %uint_19 %uint_0 %uint_0
        %114 = OpExtInst %void %3 DebugValue %112 %int_1 %43
        %115 = OpExtInst %void %3 DebugLine %32 %uint_20 %uint_20 %uint_0 %uint_0
        %117 = OpLoad %float %iny
        %119 = OpLoad %float %inx
               OpStore %param %119
        %120 = OpFunctionCall %float %foo_f1_f1_ %param %117
               OpStore %param_0 %int_1
        %122 = OpFunctionCall %float %bar_i1_ %param_0
        %123 = OpFAdd %float %120 %122
               OpStore %outz %123
               OpReturn
               OpFunctionEnd
               OpLine %1 10 16
    %bar_i1_ = OpFunction %float None %25
          %x = OpFunctionParameter %_ptr_Function_int
         %29 = OpLabel
         %38 = OpExtInst %void %3 DebugScope %31
         %39 = OpExtInst %void %3 DebugLine %32 %uint_10 %uint_10 %uint_0 %uint_0
         %42 = OpExtInst %void %3 DebugDeclare %40 %x %43
         %90 = OpExtInst %void %3 DebugFunctionDefinition %31 %bar_i1_
         %91 = OpExtInst %void %3 DebugScope %31
         %92 = OpExtInst %void %3 DebugLine %32 %uint_11 %uint_11 %uint_0 %uint_0
         %94 = OpLoad %int %x
         %95 = OpConvertSToF %float %94
         %96 = OpFAdd %float %95 %float_1
               OpReturnValue %96
               OpFunctionEnd
               OpLine %1 14 33
 %foo_f1_f1_ = OpFunction %float None %45
        %x_0 = OpFunctionParameter %_ptr_Function_float
          %y = OpFunctionParameter %float
         %50 = OpLabel
         %54 = OpExtInst %void %3 DebugScope %52
         %55 = OpExtInst %void %3 DebugLine %32 %uint_14 %uint_14 %uint_0 %uint_0
         %57 = OpExtInst %void %3 DebugDeclare %56 %x_0 %43
         %60 = OpExtInst %void %3 DebugValue %58 %y %43
         %99 = OpExtInst %void %3 DebugFunctionDefinition %52 %foo_f1_f1_
        %100 = OpExtInst %void %3 DebugScope %52
        %101 = OpExtInst %void %3 DebugLine %32 %uint_15 %uint_15 %uint_0 %uint_0
        %103 = OpLoad %float %x_0
        %104 = OpFAdd %float %103 %y
               OpReturnValue %104
               OpFunctionEnd

The prototype needs some refinement, but we could clearly see we are now correctly generating debug information for global variables and global/local constants.

qingyuanzNV avatar Nov 18 '23 00:11 qingyuanzNV

I think this is a good idea and will also help with various other DebugInfo issues we have had. As you say, it makes sense to limit this to variables explicitly declared by the user, given that this will mostly be used for DebugInfo. Indeed, maybe it makes sense to only have those nodes generated when the user requests generation of nonsemantic debuginfo.

arcady-lunarg avatar Nov 18 '23 00:11 arcady-lunarg

Would like to collect opinions about how the declaration should be represented in the AST. The current implementation looks like:

class TIntermDecl : public TIntermNode {
    TIntermSymbol* declSymbol; // a dummy symbol access
    TIntermNode* initNode; // initializer expression
};

I'm a little concerned about whether we could reuse the TIntermSymbol here. It could significantly simplify the implementation, but I'm not sure if this would break some traversal implementation.

qingyuanzNV avatar Nov 18 '23 00:11 qingyuanzNV