From aef4142557eada49be700ed5ed9fc34fb4fe6fa9 Mon Sep 17 00:00:00 2001 From: Vladislav Dzhidzhoev Date: Tue, 26 May 2026 14:24:16 +0200 Subject: [PATCH] [DirectX][HLSL] Add /Qsource_in_debug_module to llc and Clang DXC driver dx.source metadata nodes contain shader source code information, such as source files content, macro definitions used to compile shader, and compiler arguments. PR for Clang to produce them is currently on review https://github.com/llvm/llvm-project/pull/199689. Original DXC has two modes to handle these nodes: 1. If /Qsource_in_debug_module is not specified, Source Info part (SRCI) is created inside shader's debug PDB file, and dx.source metadata nodes are replaced with empty nodes. 2. If /Qsource_in_debug_module is specified, dx.source nodes are preserved in debug bitcode module, for debug info consumers to use them directly. This patch reimplements the behavior described in item 2. /Qsource_in_debug_module is added to Clang DXC driver, and the corresponding flag is added to llc. In DXILWriterPass, this flag is used to determine how dx.source metadata nodes should be handled. --- clang/include/clang/Options/Options.td | 5 ++ clang/lib/Driver/ToolChains/Clang.cpp | 5 ++ .../Driver/dxc_source_in_debug_module.hlsl | 6 ++ .../Target/DirectX/DXILTranslateMetadata.cpp | 11 ++-- .../DirectX/DXILWriter/DXILWriterPass.cpp | 26 +++++++++ .../DirectX/ContainerData/SourceInfo-Strip.ll | 56 +++++++++++++++++++ 6 files changed, 105 insertions(+), 4 deletions(-) create mode 100644 clang/test/Driver/dxc_source_in_debug_module.hlsl create mode 100644 llvm/test/CodeGen/DirectX/ContainerData/SourceInfo-Strip.ll diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td index 025e8e7d7d76..c12aa193c345 100644 --- a/clang/include/clang/Options/Options.td +++ b/clang/include/clang/Options/Options.td @@ -9624,6 +9624,11 @@ def dxc_Fc : DXCJoinedOrSeparate<"Fc">, HelpText<"Output assembly listing file">; def dxc_Frs : DXCJoinedOrSeparate<"Frs">, HelpText<"Output additional root signature object file">; +def dxc_source_in_debug_module + : Option<["/", "-"], "Qsource_in_debug_module", KIND_FLAG>, + Group, + Visibility<[DXCOption]>, + HelpText<"Embed source code into debug module on DirectX target">; def dxil_validator_version : Option<["/", "-"], "validator-version", KIND_SEPARATE>, Group, Flags<[HelpHidden]>, Visibility<[DXCOption, ClangOption, CC1Option]>, diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp index 7657afb14f07..98b988008e10 100644 --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -3832,6 +3832,11 @@ static void RenderHLSLOptions(const ArgList &Args, ArgStringList &CmdArgs, if (!Args.hasArg(options::OPT_dxc_no_stdinc) && !Args.hasArg(options::OPT_nostdinc)) CmdArgs.push_back("-finclude-default-header"); + + if (Args.hasArg(options::OPT_dxc_source_in_debug_module)) { + CmdArgs.push_back("-mllvm"); + CmdArgs.push_back("--dx-source-in-debug-module"); + } } static void RenderOpenACCOptions(const Driver &D, const ArgList &Args, diff --git a/clang/test/Driver/dxc_source_in_debug_module.hlsl b/clang/test/Driver/dxc_source_in_debug_module.hlsl new file mode 100644 index 000000000000..cb09518c049c --- /dev/null +++ b/clang/test/Driver/dxc_source_in_debug_module.hlsl @@ -0,0 +1,6 @@ +// RUN: %clang_dxc -Tlib_6_7 -### /Zi /Qsource_in_debug_module %s 2>&1 | FileCheck %s +// RUN: %clang_dxc -Tlib_6_7 -### /Zi -Qsource_in_debug_module %s 2>&1 | FileCheck %s +// RUN: %clang_dxc -Tlib_6_7 -### /Zi %s 2>&1 | FileCheck %s --check-prefix=NOFLAG + +// CHECK: "-mllvm" "--dx-source-in-debug-module" +// NOFLAG-NOT: --dx-source-in-debug-module diff --git a/llvm/lib/Target/DirectX/DXILTranslateMetadata.cpp b/llvm/lib/Target/DirectX/DXILTranslateMetadata.cpp index 49a20e2f7517..240d5814d33d 100644 --- a/llvm/lib/Target/DirectX/DXILTranslateMetadata.cpp +++ b/llvm/lib/Target/DirectX/DXILTranslateMetadata.cpp @@ -514,15 +514,18 @@ static void cleanModuleFlags(Module &M) { M.addModuleFlag(Flag.Behavior, Flag.Key->getString(), Flag.Val); } -using GlobalMDList = std::array; +using GlobalMDList = std::array; // The following are compatible with DXIL but not emit with clang, they can // be added when applicable: // dx.typeAnnotations, dx.viewIDState, dx.dxrPayloadAnnotations static GlobalMDList CompatibleNamedModuleMDs = { - "llvm.ident", "llvm.module.flags", "dx.resources", "dx.valver", - "dx.shaderModel", "dx.version", "dx.entryPoints", -}; + "llvm.ident", "llvm.module.flags", + "dx.resources", "dx.valver", + "dx.shaderModel", "dx.version", + "dx.entryPoints", "dx.source.contents", + "dx.source.defines", "dx.source.mainFileName", + "dx.source.args"}; static void translateGlobalMetadata(Module &M, DXILResourceMap &DRM, DXILResourceTypeMap &DRTM, diff --git a/llvm/lib/Target/DirectX/DXILWriter/DXILWriterPass.cpp b/llvm/lib/Target/DirectX/DXILWriter/DXILWriterPass.cpp index f74accc3b861..f06777aa8f47 100644 --- a/llvm/lib/Target/DirectX/DXILWriter/DXILWriterPass.cpp +++ b/llvm/lib/Target/DirectX/DXILWriter/DXILWriterPass.cpp @@ -28,11 +28,17 @@ #include "llvm/InitializePasses.h" #include "llvm/Pass.h" #include "llvm/Support/Alignment.h" +#include "llvm/Support/CommandLine.h" #include "llvm/Transforms/Utils/ModuleUtils.h" using namespace llvm; using namespace llvm::dxil; +static cl::opt SourceInDebugModule( + "dx-source-in-debug-module", + cl::desc("Embed source code into debug module on DirectX target"), + cl::init(false)); + namespace { class WriteDXILPass : public llvm::ModulePass { raw_ostream &OS; // raw_ostream to print on @@ -138,6 +144,16 @@ static void removeLifetimeIntrinsics(Module &M) { } } +static void replaceNamedMetadataArray(Module &M, StringRef Name, + ArrayRef NewOps) { + NamedMDNode *NMD = M.getNamedMetadata(Name); + if (!NMD) + return; + NMD->eraseFromParent(); + M.getOrInsertNamedMetadata(Name)->addOperand( + MDTuple::get(M.getContext(), NewOps)); +} + class EmbedDXILPass : public llvm::ModulePass { public: static char ID; // Pass identification, replacement for typeid @@ -155,6 +171,16 @@ class EmbedDXILPass : public llvm::ModulePass { // fail the Module Verifier if performed in an earlier pass legalizeLifetimeIntrinsics(M); + // Replace dx.source metadata nodes with stubs. + if (!SourceInDebugModule) { + LLVMContext &Ctx = M.getContext(); + MDString *EmptyString = MDString::get(Ctx, ""); + replaceNamedMetadataArray(M, "dx.source.contents", + {EmptyString, EmptyString}); + replaceNamedMetadataArray(M, "dx.source.defines", {}); + replaceNamedMetadataArray(M, "dx.source.mainFileName", {EmptyString}); + replaceNamedMetadataArray(M, "dx.source.args", {}); + } const auto DIMap = DXILDebugInfoPass::run(M); WriteDXILToFile(M, OS, DIMap); diff --git a/llvm/test/CodeGen/DirectX/ContainerData/SourceInfo-Strip.ll b/llvm/test/CodeGen/DirectX/ContainerData/SourceInfo-Strip.ll new file mode 100644 index 000000000000..6feea2b79a2e --- /dev/null +++ b/llvm/test/CodeGen/DirectX/ContainerData/SourceInfo-Strip.ll @@ -0,0 +1,56 @@ +; Compare source info emission with and without --dx-source-in-debug-module flag. + +; RUN: llc %s --filetype=obj -o %t.dxbc +; RUN: llvm-objcopy --dump-section=DXIL=%t.dxil.bc %t.dxbc +; RUN: llvm-dis %t.dxil.bc -o - | FileCheck %s --check-prefix=ILDB-DIS + +; RUN: llc %s --filetype=obj -o %t.dxbc --dx-source-in-debug-module +; RUN: llvm-objcopy --dump-section=DXIL=%t.dxil.bc %t.dxbc +; RUN: llvm-dis %t.dxil.bc -o - | FileCheck %s --check-prefix=ILDB-SOURCE-DIS + +; Without the flag, dx.source should be replaced with dummy metadata. +; ILDB-DIS: !dx.source.contents = !{![[CONTENTS:[0-9]+]]} +; ILDB-DIS: !dx.source.defines = !{![[EMPTY_ARR:[0-9]+]]} +; ILDB-DIS: !dx.source.mainFileName = !{![[MAIN:[0-9]+]]} +; ILDB-DIS: !dx.source.args = !{![[EMPTY_ARR]]} +; ILDB-DIS: ![[CONTENTS]] = !{!"", !""} +; ILDB-DIS: ![[EMPTY_ARR]] = !{} +; ILDB-DIS: ![[MAIN]] = !{!""} + +; With the flag, dx.source should be be preserved. +; ILDB-SOURCE-DIS: !dx.source.args = !{![[ARGS:[0-9]+]]} +; ILDB-SOURCE-DIS: !dx.source.contents = !{![[FILE1:[0-9]+]], ![[FILE2:[0-9]+]], ![[FILE3:[0-9]+]]} +; ILDB-SOURCE-DIS: !dx.source.mainFileName = !{![[MAIN:[0-9]+]]} +; ILDB-SOURCE-DIS: !dx.source.defines = !{![[DEFINES:[0-9]+]]} +; ILDB-SOURCE-DIS: ![[FILE1]] = !{!"C:\\dx-source-metadata.hlsl", +; ILDB-SOURCE-DIS: ![[FILE2]] = !{!"C:\\a.hlsl" +; ILDB-SOURCE-DIS: ![[FILE3]] = !{!"C:\\b.hlsl" +; ILDB-SOURCE-DIS: ![[MAIN]] = !{!"C:\\dx-source-metadata.hlsl"} +; ILDB-SOURCE-DIS: ![[DEFINES]] = !{!"USER_DEF0=42", !"USER_DEF1=43"} + +target triple = "dxilv1.3-pc-shadermodel6.3-library" + +define float @_Z3fooff(float %a, float %b) { +entry: + %add = fadd float %a, %b + ret float %add +} + +!llvm.dbg.cu = !{!4} +!llvm.module.flags = !{!6, !7} + +!dx.source.args = !{!0} +!dx.source.contents = !{!1, !2, !3} +!dx.source.mainFileName = !{!8} +!dx.source.defines = !{!9} + +!0 = !{!"-g", !"-Tlib_6_3", !"-DUSER_DEF0=42", !"-DUSER_DEF1=43", !"C:\\\\dx-source-metadata.hlsl"} +!1 = !{!"C:\\dx-source-metadata.hlsl", !"#include \22a.hlsl\22\0A#include \22b.hlsl\22\0A\0Afloat foo(float a, float b) {\0A return a + b;\0A}\0A"} +!2 = !{!"C:\\a.hlsl", !"#include \22b.hlsl\22\0A"} +!3 = !{!"C:\\b.hlsl", !"#include \0A"} +!4 = distinct !DICompileUnit(language: DW_LANG_C99, file: !5, emissionKind: FullDebug) +!5 = !DIFile(filename: "dx-source-metadata.hlsl", directory: "C:\\") +!6 = !{i32 2, !"Dwarf Version", i32 4} +!7 = !{i32 2, !"Debug Info Version", i32 3} +!8 = !{!"C:\\dx-source-metadata.hlsl"} +!9 = !{!"USER_DEF0=42", !"USER_DEF1=43"}