Skip to content

Conversation

@cjacek
Copy link
Contributor

@cjacek cjacek commented Jun 5, 2025

MSVC always emits minimal CodeView metadata with compiler information, even when debug info is otherwise disabled. Other tools may rely on this metadata being present. For example, linkers use it to determine whether hotpatching is enabled for the object file.

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:codegen IR generation bugs: mangling, exceptions, etc. llvm:codegen debuginfo labels Jun 5, 2025
@cjacek
Copy link
Contributor Author

cjacek commented Jun 5, 2025

Depends on #142969.

@llvmbot
Copy link
Member

llvmbot commented Jun 5, 2025

@llvm/pr-subscribers-debuginfo

@llvm/pr-subscribers-clang-codegen

Author: Jacek Caban (cjacek)

Changes

MSVC always emits minimal CodeView metadata containing compiler information, even when debug info is otherwise disabled. While this data is typically not meaningful on its own, the linker may use it to detect whether the object file was built with hotpatch support. To match this behavior, emit compiler info whenever the hotpatch option is enabled.


Full diff: https://github.com/llvm/llvm-project/pull/142970.diff

10 Files Affected:

  • (modified) clang/lib/CodeGen/BackendUtil.cpp (-1)
  • (modified) clang/lib/CodeGen/CodeGenModule.cpp (+7-3)
  • (modified) clang/test/CodeGen/patchable-function-entry.c (+2)
  • (modified) llvm/include/llvm/Target/TargetOptions.h (+1-4)
  • (modified) llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp (+6-2)
  • (modified) llvm/lib/CodeGen/AsmPrinter/CodeViewDebug.cpp (+28-5)
  • (modified) llvm/lib/CodeGen/AsmPrinter/CodeViewDebug.h (+3)
  • (added) llvm/test/DebugInfo/PDB/hotpatch-dwarf.c (+59)
  • (added) llvm/test/DebugInfo/PDB/hotpatch-nodebug.c (+57)
  • (added) llvm/test/DebugInfo/PDB/hotpatch.test (+57)
diff --git a/clang/lib/CodeGen/BackendUtil.cpp b/clang/lib/CodeGen/BackendUtil.cpp
index cd5fc48c4a22b..2365f675b2dc3 100644
--- a/clang/lib/CodeGen/BackendUtil.cpp
+++ b/clang/lib/CodeGen/BackendUtil.cpp
@@ -473,7 +473,6 @@ static bool initTargetOptions(const CompilerInstance &CI,
   Options.LoopAlignment = CodeGenOpts.LoopAlignment;
   Options.DebugStrictDwarf = CodeGenOpts.DebugStrictDwarf;
   Options.ObjectFilenameForDebug = CodeGenOpts.ObjectFilenameForDebug;
-  Options.Hotpatch = CodeGenOpts.HotPatch;
   Options.JMCInstrument = CodeGenOpts.JMCInstrument;
   Options.XCOFFReadOnlyPointers = CodeGenOpts.XCOFFReadOnlyPointers;
 
diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp
index 468fc6e0e5c56..ddad9a66766fd 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -408,11 +408,11 @@ CodeGenModule::CodeGenModule(ASTContext &C,
     TBAA.reset(new CodeGenTBAA(Context, getTypes(), TheModule, CodeGenOpts,
                                getLangOpts()));
 
-  // If debug info or coverage generation is enabled, create the CGDebugInfo
-  // object.
+  // If debug info, coverage generation or hotpatch is enabled, create the
+  // CGDebugInfo object.
   if (CodeGenOpts.getDebugInfo() != llvm::codegenoptions::NoDebugInfo ||
       CodeGenOpts.CoverageNotesFile.size() ||
-      CodeGenOpts.CoverageDataFile.size())
+      CodeGenOpts.CoverageDataFile.size() || CodeGenOpts.HotPatch)
     DebugInfo.reset(new CGDebugInfo(*this));
 
   Block.GlobalUniqueCount = 0;
@@ -1031,6 +1031,10 @@ void CodeGenModule::Release() {
     // Function ID tables for EH Continuation Guard.
     getModule().addModuleFlag(llvm::Module::Warning, "ehcontguard", 1);
   }
+  if (CodeGenOpts.HotPatch) {
+    // Note if we are compiling with /hotpatch.
+    getModule().addModuleFlag(llvm::Module::Warning, "ms-hotpatch", 1);
+  }
   if (Context.getLangOpts().Kernel) {
     // Note if we are compiling with /kernel.
     getModule().addModuleFlag(llvm::Module::Warning, "ms-kernel", 1);
diff --git a/clang/test/CodeGen/patchable-function-entry.c b/clang/test/CodeGen/patchable-function-entry.c
index 2acd748758490..748d5c8bc5ae3 100644
--- a/clang/test/CodeGen/patchable-function-entry.c
+++ b/clang/test/CodeGen/patchable-function-entry.c
@@ -39,3 +39,5 @@ void f(void) {}
 // HOTPATCH: attributes #1 = { {{.*}} "patchable-function"="prologue-short-redirect"
 // HOTPATCH: attributes #2 = { {{.*}} "patchable-function"="prologue-short-redirect"
 // HOTPATCH: attributes #3 = { {{.*}} "patchable-function"="prologue-short-redirect"
+// HOTPATCH: !0 = distinct !DICompileUnit(language: DW_LANG_C11, file: !1, producer: "clang version 21.0.0git", isOptimized: false, runtimeVersion: 0, emissionKind: NoDebug, splitDebugInlining: false, nameTableKind: None)
+// HOTPATCH: !{{.}} = !{i32 2, !"ms-hotpatch", i32 1}
diff --git a/llvm/include/llvm/Target/TargetOptions.h b/llvm/include/llvm/Target/TargetOptions.h
index fd8dad4f6f791..25bbb73e9dedc 100644
--- a/llvm/include/llvm/Target/TargetOptions.h
+++ b/llvm/include/llvm/Target/TargetOptions.h
@@ -150,7 +150,7 @@ namespace llvm {
           EmitAddrsig(false), BBAddrMap(false), EmitCallSiteInfo(false),
           SupportsDebugEntryValues(false), EnableDebugEntryValues(false),
           ValueTrackingVariableLocations(false), ForceDwarfFrameSection(false),
-          XRayFunctionIndex(true), DebugStrictDwarf(false), Hotpatch(false),
+          XRayFunctionIndex(true), DebugStrictDwarf(false),
           PPCGenScalarMASSEntries(false), JMCInstrument(false),
           EnableCFIFixup(false), MisExpect(false), XCOFFReadOnlyPointers(false),
           VerifyArgABICompliance(true),
@@ -363,9 +363,6 @@ namespace llvm {
     /// By default, it is set to false.
     unsigned DebugStrictDwarf : 1;
 
-    /// Emit the hotpatch flag in CodeView debug.
-    unsigned Hotpatch : 1;
-
     /// Enables scalar MASS conversions
     unsigned PPCGenScalarMASSEntries : 1;
 
diff --git a/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp b/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp
index bcfc64c6f36bb..d58e89dffc5c3 100644
--- a/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp
+++ b/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp
@@ -561,8 +561,12 @@ bool AsmPrinter::doInitialization(Module &M) {
 
   if (MAI->doesSupportDebugInformation()) {
     bool EmitCodeView = M.getCodeViewFlag();
-    if (EmitCodeView &&
-        (TM.getTargetTriple().isOSWindows() || TM.getTargetTriple().isUEFI()))
+    // MSVC always emits minimal CodeView information with compiler metadata,
+    // even when debug info is otherwise disabled. While it's usually not
+    // meaningful, the linker may use it to detect whether the object file has
+    // hotpatching enabled. Emit compiler info in such cases.
+    if ((TM.getTargetTriple().isOSWindows() || TM.getTargetTriple().isUEFI()) &&
+        (EmitCodeView || M.getModuleFlag("ms-hotpatch")))
       Handlers.push_back(std::make_unique<CodeViewDebug>(this));
     if (!EmitCodeView || M.getDwarfVersion()) {
       if (hasDebugInfo()) {
diff --git a/llvm/lib/CodeGen/AsmPrinter/CodeViewDebug.cpp b/llvm/lib/CodeGen/AsmPrinter/CodeViewDebug.cpp
index fc43bc6f7776d..a0013fb30aa80 100644
--- a/llvm/lib/CodeGen/AsmPrinter/CodeViewDebug.cpp
+++ b/llvm/lib/CodeGen/AsmPrinter/CodeViewDebug.cpp
@@ -613,7 +613,8 @@ static SourceLanguage MapDWLangToCVLang(unsigned DWLang) {
 void CodeViewDebug::beginModule(Module *M) {
   // If module doesn't have named metadata anchors or COFF debug section
   // is not available, skip any debug info related stuff.
-  if (!Asm->hasDebugInfo() ||
+  if (!(Asm->hasDebugInfo() ||
+        MMI->getModule()->getModuleFlag("ms-hotpatch")) ||
       !Asm->getObjFileLowering().getCOFFDebugSymbolsSection()) {
     Asm = nullptr;
     return;
@@ -622,10 +623,21 @@ void CodeViewDebug::beginModule(Module *M) {
   TheCPU = mapArchToCVCPUType(M->getTargetTriple().getArch());
 
   // Get the current source language.
-  const MDNode *Node = *M->debug_compile_units_begin();
+  const MDNode *Node;
+  if (Asm->hasDebugInfo()) {
+    Node = *M->debug_compile_units_begin();
+  } else {
+    // When emitting only compiler information, we may have only NoDebug CUs,
+    // which would be skipped by debug_compile_units_begin.
+    NamedMDNode *CUs = MMI->getModule()->getNamedMetadata("llvm.dbg.cu");
+    Node = *CUs->operands().begin();
+  }
   const auto *CU = cast<DICompileUnit>(Node);
 
   CurrentSourceLanguage = MapDWLangToCVLang(CU->getSourceLanguage());
+  NoDebug = !M->getCodeViewFlag();
+  if (NoDebug)
+    return;
 
   collectGlobalVariableInfo();
 
@@ -636,7 +648,7 @@ void CodeViewDebug::beginModule(Module *M) {
 }
 
 void CodeViewDebug::endModule() {
-  if (!Asm || !Asm->hasDebugInfo())
+  if (!Asm)
     return;
 
   // The COFF .debug$S section consists of several subsections, each starting
@@ -653,6 +665,9 @@ void CodeViewDebug::endModule() {
   emitCompilerInformation();
   endCVSubsection(CompilerInfo);
 
+  if (NoDebug)
+    return;
+
   emitInlineeLinesSubsection();
 
   // Emit per-function debug information.
@@ -846,8 +861,8 @@ void CodeViewDebug::emitCompilerInformation() {
   }
   using ArchType = llvm::Triple::ArchType;
   ArchType Arch = MMI->getModule()->getTargetTriple().getArch();
-  if (Asm->TM.Options.Hotpatch || Arch == ArchType::thumb ||
-      Arch == ArchType::aarch64) {
+  if (Arch == ArchType::thumb || Arch == ArchType::aarch64 ||
+      MMI->getModule()->getModuleFlag("ms-hotpatch")) {
     Flags |= static_cast<uint32_t>(CompileSym3Flags::HotPatch);
   }
 
@@ -1440,6 +1455,9 @@ void CodeViewDebug::collectVariableInfo(const DISubprogram *SP) {
 }
 
 void CodeViewDebug::beginFunctionImpl(const MachineFunction *MF) {
+  if (NoDebug)
+    return;
+
   const TargetSubtargetInfo &TSI = MF->getSubtarget();
   const TargetRegisterInfo *TRI = TSI.getRegisterInfo();
   const MachineFrameInfo &MFI = MF->getFrameInfo();
@@ -3031,6 +3049,9 @@ void CodeViewDebug::collectLexicalBlockInfo(
 }
 
 void CodeViewDebug::endFunctionImpl(const MachineFunction *MF) {
+  if (NoDebug)
+    return;
+
   const Function &GV = MF->getFunction();
   assert(FnDebugInfo.count(&GV));
   assert(CurFn == FnDebugInfo[&GV].get());
@@ -3089,6 +3110,8 @@ static bool isUsableDebugLoc(DebugLoc DL) {
 
 void CodeViewDebug::beginInstruction(const MachineInstr *MI) {
   DebugHandlerBase::beginInstruction(MI);
+  if (NoDebug)
+    return;
 
   // Ignore DBG_VALUE and DBG_LABEL locations and function prologue.
   if (!Asm || !CurFn || MI->isDebugInstr() ||
diff --git a/llvm/lib/CodeGen/AsmPrinter/CodeViewDebug.h b/llvm/lib/CodeGen/AsmPrinter/CodeViewDebug.h
index d13b315135ad9..3bf3e6db768d7 100644
--- a/llvm/lib/CodeGen/AsmPrinter/CodeViewDebug.h
+++ b/llvm/lib/CodeGen/AsmPrinter/CodeViewDebug.h
@@ -98,6 +98,9 @@ class LLVM_LIBRARY_VISIBILITY CodeViewDebug : public DebugHandlerBase {
   /// The codeview CPU type used by the translation unit.
   codeview::CPUType TheCPU;
 
+  /// Whether to emit compiler information only.
+  bool NoDebug = false;
+
   static LocalVarDef createDefRangeMem(uint16_t CVRegister, int Offset);
 
   /// Similar to DbgVariable in DwarfDebug, but not dwarf-specific.
diff --git a/llvm/test/DebugInfo/PDB/hotpatch-dwarf.c b/llvm/test/DebugInfo/PDB/hotpatch-dwarf.c
new file mode 100644
index 0000000000000..5b1682ee20efd
--- /dev/null
+++ b/llvm/test/DebugInfo/PDB/hotpatch-dwarf.c
@@ -0,0 +1,59 @@
+; RUN: llc -filetype=obj -o - %s | llvm-readobj --codeview - | FileCheck %s
+
+; ModuleID = 'a.c'
+source_filename = "a.c"
+target datalayout = "e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-unknown-windows-msvc19.33.0"
+
+; Function Attrs: noinline nounwind optnone uwtable
+define dso_local void @test() #0 !dbg !10 {
+entry:
+  ret void, !dbg !13
+}
+
+attributes #0 = { noinline nounwind optnone uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "patchable-function"="prologue-short-redirect" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!2, !3, !4, !5, !6, !7, !8}
+!llvm.ident = !{!9}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C11, file: !1, producer: "clang version 21.0.0git", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None)
+!1 = !DIFile(filename: "a.c", directory: "/tmp")
+!2 = !{i32 7, !"Dwarf Version", i32 4}
+!3 = !{i32 2, !"ms-hotpatch", i32 1}
+!4 = !{i32 2, !"Debug Info Version", i32 3}
+!5 = !{i32 1, !"wchar_size", i32 2}
+!6 = !{i32 8, !"PIC Level", i32 2}
+!7 = !{i32 7, !"uwtable", i32 2}
+!8 = !{i32 1, !"MaxTLSAlign", i32 65536}
+!9 = !{!"clang version 21.0.0git"}
+!10 = distinct !DISubprogram(name: "test", scope: !1, file: !1, line: 1, type: !11, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !0)
+!11 = !DISubroutineType(types: !12)
+!12 = !{null}
+!13 = !DILocation(line: 1, column: 14, scope: !10)
+
+
+; CHECK:      CodeViewDebugInfo [
+; CHECK-NEXT:   Section: .debug$S (4)
+; CHECK-NEXT:   Magic: 0x4
+; CHECK-NEXT:   Subsection [
+; CHECK-NEXT:     SubSectionType: Symbols (0xF1)
+; CHECK-NEXT:     SubSectionSize: 0x40
+; CHECK-NEXT:     ObjNameSym {
+; CHECK-NEXT:       Kind: S_OBJNAME (0x1101)
+; CHECK-NEXT:       Signature: 0x0
+; CHECK-NEXT:       ObjectName:
+; CHECK-NEXT:     }
+; CHECK-NEXT:     Compile3Sym {
+; CHECK-NEXT:       Kind: S_COMPILE3 (0x113C)
+; CHECK-NEXT:       Language: C (0x0)
+; CHECK-NEXT:       Flags [ (0x4000)
+; CHECK-NEXT:         HotPatch (0x4000)
+; CHECK-NEXT:       ]
+; CHECK-NEXT:       Machine: X64 (0xD0)
+; CHECK-NEXT:       FrontendVersion: 21.0.0.0
+; CHECK-NEXT:       BackendVersion: 21000.0.0.0
+; CHECK-NEXT:       VersionName: clang version 21.0.0git
+; CHECK-NEXT:     }
+; CHECK-NEXT:   ]
+; CHECK-NEXT: ]
diff --git a/llvm/test/DebugInfo/PDB/hotpatch-nodebug.c b/llvm/test/DebugInfo/PDB/hotpatch-nodebug.c
new file mode 100644
index 0000000000000..abce081746188
--- /dev/null
+++ b/llvm/test/DebugInfo/PDB/hotpatch-nodebug.c
@@ -0,0 +1,57 @@
+; RUN: llc -filetype=obj -o - %s | llvm-readobj --codeview - | FileCheck %s
+
+; ModuleID = 'a.c'
+source_filename = "a.c"
+target datalayout = "e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-unknown-windows-msvc19.33.0"
+
+; Function Attrs: noinline nounwind optnone uwtable
+define dso_local void @test() #0 !dbg !9 {
+entry:
+  ret void, !dbg !12
+}
+
+attributes #0 = { noinline nounwind optnone uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "patchable-function"="prologue-short-redirect" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!2, !3, !4, !5, !6, !7}
+!llvm.ident = !{!8}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C11, file: !1, producer: "clang version 21.0.0git", isOptimized: false, runtimeVersion: 0, emissionKind: NoDebug, splitDebugInlining: false, nameTableKind: None)
+!1 = !DIFile(filename: "a.c", directory: "/tmp")
+!2 = !{i32 2, !"ms-hotpatch", i32 1}
+!3 = !{i32 2, !"Debug Info Version", i32 3}
+!4 = !{i32 1, !"wchar_size", i32 2}
+!5 = !{i32 8, !"PIC Level", i32 2}
+!6 = !{i32 7, !"uwtable", i32 2}
+!7 = !{i32 1, !"MaxTLSAlign", i32 65536}
+!8 = !{!"clang version 21.0.0git"}
+!9 = distinct !DISubprogram(name: "test", scope: !1, file: !1, line: 1, type: !10, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !0)
+!10 = !DISubroutineType(types: !11)
+!11 = !{}
+!12 = !DILocation(line: 1, column: 14, scope: !9)
+
+; CHECK:      CodeViewDebugInfo [
+; CHECK-NEXT:   Section: .debug$S (4)
+; CHECK-NEXT:   Magic: 0x4
+; CHECK-NEXT:   Subsection [
+; CHECK-NEXT:     SubSectionType: Symbols (0xF1)
+; CHECK-NEXT:     SubSectionSize: 0x40
+; CHECK-NEXT:     ObjNameSym {
+; CHECK-NEXT:       Kind: S_OBJNAME (0x1101)
+; CHECK-NEXT:       Signature: 0x0
+; CHECK-NEXT:       ObjectName:
+; CHECK-NEXT:     }
+; CHECK-NEXT:     Compile3Sym {
+; CHECK-NEXT:       Kind: S_COMPILE3 (0x113C)
+; CHECK-NEXT:       Language: C (0x0)
+; CHECK-NEXT:       Flags [ (0x4000)
+; CHECK-NEXT:         HotPatch (0x4000)
+; CHECK-NEXT:       ]
+; CHECK-NEXT:       Machine: X64 (0xD0)
+; CHECK-NEXT:       FrontendVersion: 21.0.0.0
+; CHECK-NEXT:       BackendVersion: 21000.0.0.0
+; CHECK-NEXT:       VersionName: clang version 21.0.0git
+; CHECK-NEXT:     }
+; CHECK-NEXT:   ]
+; CHECK-NEXT: ]
diff --git a/llvm/test/DebugInfo/PDB/hotpatch.test b/llvm/test/DebugInfo/PDB/hotpatch.test
new file mode 100644
index 0000000000000..7ff15fa3c69a4
--- /dev/null
+++ b/llvm/test/DebugInfo/PDB/hotpatch.test
@@ -0,0 +1,57 @@
+; RUN: llc -filetype=obj -o - %s | llvm-readobj --codeview - | FileCheck %s
+
+; ModuleID = 'a.c'
+source_filename = "a.c"
+target datalayout = "e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-unknown-windows-msvc19.33.0"
+
+; Function Attrs: noinline nounwind optnone uwtable
+define dso_local void @test() #0 !dbg !10 {
+entry:
+  ret void, !dbg !13
+}
+
+attributes #0 = { noinline nounwind optnone uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "patchable-function"="prologue-short-redirect" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!2, !3, !4, !5, !6, !7, !8}
+!llvm.ident = !{!9}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C11, file: !1, producer: "clang version 21.0.0git", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None)
+!1 = !DIFile(filename: "a.c", directory: "/tmp", checksumkind: CSK_MD5, checksum: "c59e1f6192de3124537b024248301dd1")
+!2 = !{i32 2, !"CodeView", i32 1}
+!3 = !{i32 2, !"ms-hotpatch", i32 1}
+!4 = !{i32 2, !"Debug Info Version", i32 3}
+!5 = !{i32 1, !"wchar_size", i32 2}
+!6 = !{i32 8, !"PIC Level", i32 2}
+!7 = !{i32 7, !"uwtable", i32 2}
+!8 = !{i32 1, !"MaxTLSAlign", i32 65536}
+!9 = !{!"clang version 21.0.0git"}
+!10 = distinct !DISubprogram(name: "test", scope: !1, file: !1, line: 1, type: !11, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !0)
+!11 = !DISubroutineType(types: !12)
+!12 = !{null}
+!13 = !DILocation(line: 1, scope: !10)
+
+; CHECK:      CodeViewDebugInfo [
+; CHECK-NEXT:   Section: .debug$S (4)
+; CHECK-NEXT:   Magic: 0x4
+; CHECK-NEXT:   Subsection [
+; CHECK-NEXT:     SubSectionType: Symbols (0xF1)
+; CHECK-NEXT:     SubSectionSize: 0x40
+; CHECK-NEXT:     ObjNameSym {
+; CHECK-NEXT:       Kind: S_OBJNAME (0x1101)
+; CHECK-NEXT:       Signature: 0x0
+; CHECK-NEXT:       ObjectName:
+; CHECK-NEXT:     }
+; CHECK-NEXT:     Compile3Sym {
+; CHECK-NEXT:       Kind: S_COMPILE3 (0x113C)
+; CHECK-NEXT:       Language: C (0x0)
+; CHECK-NEXT:       Flags [ (0x4000)
+; CHECK-NEXT:         HotPatch (0x4000)
+; CHECK-NEXT:       ]
+; CHECK-NEXT:       Machine: X64 (0xD0)
+; CHECK-NEXT:       FrontendVersion: 21.0.0.0
+; CHECK-NEXT:       BackendVersion: 21000.0.0.0
+; CHECK-NEXT:       VersionName: clang version 21.0.0git
+; CHECK-NEXT:     }
+; CHECK-NEXT:   ]

@llvmbot
Copy link
Member

llvmbot commented Jun 5, 2025

@llvm/pr-subscribers-clang

Author: Jacek Caban (cjacek)

Changes

MSVC always emits minimal CodeView metadata containing compiler information, even when debug info is otherwise disabled. While this data is typically not meaningful on its own, the linker may use it to detect whether the object file was built with hotpatch support. To match this behavior, emit compiler info whenever the hotpatch option is enabled.


Full diff: https://github.com/llvm/llvm-project/pull/142970.diff

10 Files Affected:

  • (modified) clang/lib/CodeGen/BackendUtil.cpp (-1)
  • (modified) clang/lib/CodeGen/CodeGenModule.cpp (+7-3)
  • (modified) clang/test/CodeGen/patchable-function-entry.c (+2)
  • (modified) llvm/include/llvm/Target/TargetOptions.h (+1-4)
  • (modified) llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp (+6-2)
  • (modified) llvm/lib/CodeGen/AsmPrinter/CodeViewDebug.cpp (+28-5)
  • (modified) llvm/lib/CodeGen/AsmPrinter/CodeViewDebug.h (+3)
  • (added) llvm/test/DebugInfo/PDB/hotpatch-dwarf.c (+59)
  • (added) llvm/test/DebugInfo/PDB/hotpatch-nodebug.c (+57)
  • (added) llvm/test/DebugInfo/PDB/hotpatch.test (+57)
diff --git a/clang/lib/CodeGen/BackendUtil.cpp b/clang/lib/CodeGen/BackendUtil.cpp
index cd5fc48c4a22b..2365f675b2dc3 100644
--- a/clang/lib/CodeGen/BackendUtil.cpp
+++ b/clang/lib/CodeGen/BackendUtil.cpp
@@ -473,7 +473,6 @@ static bool initTargetOptions(const CompilerInstance &CI,
   Options.LoopAlignment = CodeGenOpts.LoopAlignment;
   Options.DebugStrictDwarf = CodeGenOpts.DebugStrictDwarf;
   Options.ObjectFilenameForDebug = CodeGenOpts.ObjectFilenameForDebug;
-  Options.Hotpatch = CodeGenOpts.HotPatch;
   Options.JMCInstrument = CodeGenOpts.JMCInstrument;
   Options.XCOFFReadOnlyPointers = CodeGenOpts.XCOFFReadOnlyPointers;
 
diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp
index 468fc6e0e5c56..ddad9a66766fd 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -408,11 +408,11 @@ CodeGenModule::CodeGenModule(ASTContext &C,
     TBAA.reset(new CodeGenTBAA(Context, getTypes(), TheModule, CodeGenOpts,
                                getLangOpts()));
 
-  // If debug info or coverage generation is enabled, create the CGDebugInfo
-  // object.
+  // If debug info, coverage generation or hotpatch is enabled, create the
+  // CGDebugInfo object.
   if (CodeGenOpts.getDebugInfo() != llvm::codegenoptions::NoDebugInfo ||
       CodeGenOpts.CoverageNotesFile.size() ||
-      CodeGenOpts.CoverageDataFile.size())
+      CodeGenOpts.CoverageDataFile.size() || CodeGenOpts.HotPatch)
     DebugInfo.reset(new CGDebugInfo(*this));
 
   Block.GlobalUniqueCount = 0;
@@ -1031,6 +1031,10 @@ void CodeGenModule::Release() {
     // Function ID tables for EH Continuation Guard.
     getModule().addModuleFlag(llvm::Module::Warning, "ehcontguard", 1);
   }
+  if (CodeGenOpts.HotPatch) {
+    // Note if we are compiling with /hotpatch.
+    getModule().addModuleFlag(llvm::Module::Warning, "ms-hotpatch", 1);
+  }
   if (Context.getLangOpts().Kernel) {
     // Note if we are compiling with /kernel.
     getModule().addModuleFlag(llvm::Module::Warning, "ms-kernel", 1);
diff --git a/clang/test/CodeGen/patchable-function-entry.c b/clang/test/CodeGen/patchable-function-entry.c
index 2acd748758490..748d5c8bc5ae3 100644
--- a/clang/test/CodeGen/patchable-function-entry.c
+++ b/clang/test/CodeGen/patchable-function-entry.c
@@ -39,3 +39,5 @@ void f(void) {}
 // HOTPATCH: attributes #1 = { {{.*}} "patchable-function"="prologue-short-redirect"
 // HOTPATCH: attributes #2 = { {{.*}} "patchable-function"="prologue-short-redirect"
 // HOTPATCH: attributes #3 = { {{.*}} "patchable-function"="prologue-short-redirect"
+// HOTPATCH: !0 = distinct !DICompileUnit(language: DW_LANG_C11, file: !1, producer: "clang version 21.0.0git", isOptimized: false, runtimeVersion: 0, emissionKind: NoDebug, splitDebugInlining: false, nameTableKind: None)
+// HOTPATCH: !{{.}} = !{i32 2, !"ms-hotpatch", i32 1}
diff --git a/llvm/include/llvm/Target/TargetOptions.h b/llvm/include/llvm/Target/TargetOptions.h
index fd8dad4f6f791..25bbb73e9dedc 100644
--- a/llvm/include/llvm/Target/TargetOptions.h
+++ b/llvm/include/llvm/Target/TargetOptions.h
@@ -150,7 +150,7 @@ namespace llvm {
           EmitAddrsig(false), BBAddrMap(false), EmitCallSiteInfo(false),
           SupportsDebugEntryValues(false), EnableDebugEntryValues(false),
           ValueTrackingVariableLocations(false), ForceDwarfFrameSection(false),
-          XRayFunctionIndex(true), DebugStrictDwarf(false), Hotpatch(false),
+          XRayFunctionIndex(true), DebugStrictDwarf(false),
           PPCGenScalarMASSEntries(false), JMCInstrument(false),
           EnableCFIFixup(false), MisExpect(false), XCOFFReadOnlyPointers(false),
           VerifyArgABICompliance(true),
@@ -363,9 +363,6 @@ namespace llvm {
     /// By default, it is set to false.
     unsigned DebugStrictDwarf : 1;
 
-    /// Emit the hotpatch flag in CodeView debug.
-    unsigned Hotpatch : 1;
-
     /// Enables scalar MASS conversions
     unsigned PPCGenScalarMASSEntries : 1;
 
diff --git a/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp b/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp
index bcfc64c6f36bb..d58e89dffc5c3 100644
--- a/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp
+++ b/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp
@@ -561,8 +561,12 @@ bool AsmPrinter::doInitialization(Module &M) {
 
   if (MAI->doesSupportDebugInformation()) {
     bool EmitCodeView = M.getCodeViewFlag();
-    if (EmitCodeView &&
-        (TM.getTargetTriple().isOSWindows() || TM.getTargetTriple().isUEFI()))
+    // MSVC always emits minimal CodeView information with compiler metadata,
+    // even when debug info is otherwise disabled. While it's usually not
+    // meaningful, the linker may use it to detect whether the object file has
+    // hotpatching enabled. Emit compiler info in such cases.
+    if ((TM.getTargetTriple().isOSWindows() || TM.getTargetTriple().isUEFI()) &&
+        (EmitCodeView || M.getModuleFlag("ms-hotpatch")))
       Handlers.push_back(std::make_unique<CodeViewDebug>(this));
     if (!EmitCodeView || M.getDwarfVersion()) {
       if (hasDebugInfo()) {
diff --git a/llvm/lib/CodeGen/AsmPrinter/CodeViewDebug.cpp b/llvm/lib/CodeGen/AsmPrinter/CodeViewDebug.cpp
index fc43bc6f7776d..a0013fb30aa80 100644
--- a/llvm/lib/CodeGen/AsmPrinter/CodeViewDebug.cpp
+++ b/llvm/lib/CodeGen/AsmPrinter/CodeViewDebug.cpp
@@ -613,7 +613,8 @@ static SourceLanguage MapDWLangToCVLang(unsigned DWLang) {
 void CodeViewDebug::beginModule(Module *M) {
   // If module doesn't have named metadata anchors or COFF debug section
   // is not available, skip any debug info related stuff.
-  if (!Asm->hasDebugInfo() ||
+  if (!(Asm->hasDebugInfo() ||
+        MMI->getModule()->getModuleFlag("ms-hotpatch")) ||
       !Asm->getObjFileLowering().getCOFFDebugSymbolsSection()) {
     Asm = nullptr;
     return;
@@ -622,10 +623,21 @@ void CodeViewDebug::beginModule(Module *M) {
   TheCPU = mapArchToCVCPUType(M->getTargetTriple().getArch());
 
   // Get the current source language.
-  const MDNode *Node = *M->debug_compile_units_begin();
+  const MDNode *Node;
+  if (Asm->hasDebugInfo()) {
+    Node = *M->debug_compile_units_begin();
+  } else {
+    // When emitting only compiler information, we may have only NoDebug CUs,
+    // which would be skipped by debug_compile_units_begin.
+    NamedMDNode *CUs = MMI->getModule()->getNamedMetadata("llvm.dbg.cu");
+    Node = *CUs->operands().begin();
+  }
   const auto *CU = cast<DICompileUnit>(Node);
 
   CurrentSourceLanguage = MapDWLangToCVLang(CU->getSourceLanguage());
+  NoDebug = !M->getCodeViewFlag();
+  if (NoDebug)
+    return;
 
   collectGlobalVariableInfo();
 
@@ -636,7 +648,7 @@ void CodeViewDebug::beginModule(Module *M) {
 }
 
 void CodeViewDebug::endModule() {
-  if (!Asm || !Asm->hasDebugInfo())
+  if (!Asm)
     return;
 
   // The COFF .debug$S section consists of several subsections, each starting
@@ -653,6 +665,9 @@ void CodeViewDebug::endModule() {
   emitCompilerInformation();
   endCVSubsection(CompilerInfo);
 
+  if (NoDebug)
+    return;
+
   emitInlineeLinesSubsection();
 
   // Emit per-function debug information.
@@ -846,8 +861,8 @@ void CodeViewDebug::emitCompilerInformation() {
   }
   using ArchType = llvm::Triple::ArchType;
   ArchType Arch = MMI->getModule()->getTargetTriple().getArch();
-  if (Asm->TM.Options.Hotpatch || Arch == ArchType::thumb ||
-      Arch == ArchType::aarch64) {
+  if (Arch == ArchType::thumb || Arch == ArchType::aarch64 ||
+      MMI->getModule()->getModuleFlag("ms-hotpatch")) {
     Flags |= static_cast<uint32_t>(CompileSym3Flags::HotPatch);
   }
 
@@ -1440,6 +1455,9 @@ void CodeViewDebug::collectVariableInfo(const DISubprogram *SP) {
 }
 
 void CodeViewDebug::beginFunctionImpl(const MachineFunction *MF) {
+  if (NoDebug)
+    return;
+
   const TargetSubtargetInfo &TSI = MF->getSubtarget();
   const TargetRegisterInfo *TRI = TSI.getRegisterInfo();
   const MachineFrameInfo &MFI = MF->getFrameInfo();
@@ -3031,6 +3049,9 @@ void CodeViewDebug::collectLexicalBlockInfo(
 }
 
 void CodeViewDebug::endFunctionImpl(const MachineFunction *MF) {
+  if (NoDebug)
+    return;
+
   const Function &GV = MF->getFunction();
   assert(FnDebugInfo.count(&GV));
   assert(CurFn == FnDebugInfo[&GV].get());
@@ -3089,6 +3110,8 @@ static bool isUsableDebugLoc(DebugLoc DL) {
 
 void CodeViewDebug::beginInstruction(const MachineInstr *MI) {
   DebugHandlerBase::beginInstruction(MI);
+  if (NoDebug)
+    return;
 
   // Ignore DBG_VALUE and DBG_LABEL locations and function prologue.
   if (!Asm || !CurFn || MI->isDebugInstr() ||
diff --git a/llvm/lib/CodeGen/AsmPrinter/CodeViewDebug.h b/llvm/lib/CodeGen/AsmPrinter/CodeViewDebug.h
index d13b315135ad9..3bf3e6db768d7 100644
--- a/llvm/lib/CodeGen/AsmPrinter/CodeViewDebug.h
+++ b/llvm/lib/CodeGen/AsmPrinter/CodeViewDebug.h
@@ -98,6 +98,9 @@ class LLVM_LIBRARY_VISIBILITY CodeViewDebug : public DebugHandlerBase {
   /// The codeview CPU type used by the translation unit.
   codeview::CPUType TheCPU;
 
+  /// Whether to emit compiler information only.
+  bool NoDebug = false;
+
   static LocalVarDef createDefRangeMem(uint16_t CVRegister, int Offset);
 
   /// Similar to DbgVariable in DwarfDebug, but not dwarf-specific.
diff --git a/llvm/test/DebugInfo/PDB/hotpatch-dwarf.c b/llvm/test/DebugInfo/PDB/hotpatch-dwarf.c
new file mode 100644
index 0000000000000..5b1682ee20efd
--- /dev/null
+++ b/llvm/test/DebugInfo/PDB/hotpatch-dwarf.c
@@ -0,0 +1,59 @@
+; RUN: llc -filetype=obj -o - %s | llvm-readobj --codeview - | FileCheck %s
+
+; ModuleID = 'a.c'
+source_filename = "a.c"
+target datalayout = "e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-unknown-windows-msvc19.33.0"
+
+; Function Attrs: noinline nounwind optnone uwtable
+define dso_local void @test() #0 !dbg !10 {
+entry:
+  ret void, !dbg !13
+}
+
+attributes #0 = { noinline nounwind optnone uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "patchable-function"="prologue-short-redirect" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!2, !3, !4, !5, !6, !7, !8}
+!llvm.ident = !{!9}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C11, file: !1, producer: "clang version 21.0.0git", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None)
+!1 = !DIFile(filename: "a.c", directory: "/tmp")
+!2 = !{i32 7, !"Dwarf Version", i32 4}
+!3 = !{i32 2, !"ms-hotpatch", i32 1}
+!4 = !{i32 2, !"Debug Info Version", i32 3}
+!5 = !{i32 1, !"wchar_size", i32 2}
+!6 = !{i32 8, !"PIC Level", i32 2}
+!7 = !{i32 7, !"uwtable", i32 2}
+!8 = !{i32 1, !"MaxTLSAlign", i32 65536}
+!9 = !{!"clang version 21.0.0git"}
+!10 = distinct !DISubprogram(name: "test", scope: !1, file: !1, line: 1, type: !11, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !0)
+!11 = !DISubroutineType(types: !12)
+!12 = !{null}
+!13 = !DILocation(line: 1, column: 14, scope: !10)
+
+
+; CHECK:      CodeViewDebugInfo [
+; CHECK-NEXT:   Section: .debug$S (4)
+; CHECK-NEXT:   Magic: 0x4
+; CHECK-NEXT:   Subsection [
+; CHECK-NEXT:     SubSectionType: Symbols (0xF1)
+; CHECK-NEXT:     SubSectionSize: 0x40
+; CHECK-NEXT:     ObjNameSym {
+; CHECK-NEXT:       Kind: S_OBJNAME (0x1101)
+; CHECK-NEXT:       Signature: 0x0
+; CHECK-NEXT:       ObjectName:
+; CHECK-NEXT:     }
+; CHECK-NEXT:     Compile3Sym {
+; CHECK-NEXT:       Kind: S_COMPILE3 (0x113C)
+; CHECK-NEXT:       Language: C (0x0)
+; CHECK-NEXT:       Flags [ (0x4000)
+; CHECK-NEXT:         HotPatch (0x4000)
+; CHECK-NEXT:       ]
+; CHECK-NEXT:       Machine: X64 (0xD0)
+; CHECK-NEXT:       FrontendVersion: 21.0.0.0
+; CHECK-NEXT:       BackendVersion: 21000.0.0.0
+; CHECK-NEXT:       VersionName: clang version 21.0.0git
+; CHECK-NEXT:     }
+; CHECK-NEXT:   ]
+; CHECK-NEXT: ]
diff --git a/llvm/test/DebugInfo/PDB/hotpatch-nodebug.c b/llvm/test/DebugInfo/PDB/hotpatch-nodebug.c
new file mode 100644
index 0000000000000..abce081746188
--- /dev/null
+++ b/llvm/test/DebugInfo/PDB/hotpatch-nodebug.c
@@ -0,0 +1,57 @@
+; RUN: llc -filetype=obj -o - %s | llvm-readobj --codeview - | FileCheck %s
+
+; ModuleID = 'a.c'
+source_filename = "a.c"
+target datalayout = "e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-unknown-windows-msvc19.33.0"
+
+; Function Attrs: noinline nounwind optnone uwtable
+define dso_local void @test() #0 !dbg !9 {
+entry:
+  ret void, !dbg !12
+}
+
+attributes #0 = { noinline nounwind optnone uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "patchable-function"="prologue-short-redirect" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!2, !3, !4, !5, !6, !7}
+!llvm.ident = !{!8}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C11, file: !1, producer: "clang version 21.0.0git", isOptimized: false, runtimeVersion: 0, emissionKind: NoDebug, splitDebugInlining: false, nameTableKind: None)
+!1 = !DIFile(filename: "a.c", directory: "/tmp")
+!2 = !{i32 2, !"ms-hotpatch", i32 1}
+!3 = !{i32 2, !"Debug Info Version", i32 3}
+!4 = !{i32 1, !"wchar_size", i32 2}
+!5 = !{i32 8, !"PIC Level", i32 2}
+!6 = !{i32 7, !"uwtable", i32 2}
+!7 = !{i32 1, !"MaxTLSAlign", i32 65536}
+!8 = !{!"clang version 21.0.0git"}
+!9 = distinct !DISubprogram(name: "test", scope: !1, file: !1, line: 1, type: !10, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !0)
+!10 = !DISubroutineType(types: !11)
+!11 = !{}
+!12 = !DILocation(line: 1, column: 14, scope: !9)
+
+; CHECK:      CodeViewDebugInfo [
+; CHECK-NEXT:   Section: .debug$S (4)
+; CHECK-NEXT:   Magic: 0x4
+; CHECK-NEXT:   Subsection [
+; CHECK-NEXT:     SubSectionType: Symbols (0xF1)
+; CHECK-NEXT:     SubSectionSize: 0x40
+; CHECK-NEXT:     ObjNameSym {
+; CHECK-NEXT:       Kind: S_OBJNAME (0x1101)
+; CHECK-NEXT:       Signature: 0x0
+; CHECK-NEXT:       ObjectName:
+; CHECK-NEXT:     }
+; CHECK-NEXT:     Compile3Sym {
+; CHECK-NEXT:       Kind: S_COMPILE3 (0x113C)
+; CHECK-NEXT:       Language: C (0x0)
+; CHECK-NEXT:       Flags [ (0x4000)
+; CHECK-NEXT:         HotPatch (0x4000)
+; CHECK-NEXT:       ]
+; CHECK-NEXT:       Machine: X64 (0xD0)
+; CHECK-NEXT:       FrontendVersion: 21.0.0.0
+; CHECK-NEXT:       BackendVersion: 21000.0.0.0
+; CHECK-NEXT:       VersionName: clang version 21.0.0git
+; CHECK-NEXT:     }
+; CHECK-NEXT:   ]
+; CHECK-NEXT: ]
diff --git a/llvm/test/DebugInfo/PDB/hotpatch.test b/llvm/test/DebugInfo/PDB/hotpatch.test
new file mode 100644
index 0000000000000..7ff15fa3c69a4
--- /dev/null
+++ b/llvm/test/DebugInfo/PDB/hotpatch.test
@@ -0,0 +1,57 @@
+; RUN: llc -filetype=obj -o - %s | llvm-readobj --codeview - | FileCheck %s
+
+; ModuleID = 'a.c'
+source_filename = "a.c"
+target datalayout = "e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-unknown-windows-msvc19.33.0"
+
+; Function Attrs: noinline nounwind optnone uwtable
+define dso_local void @test() #0 !dbg !10 {
+entry:
+  ret void, !dbg !13
+}
+
+attributes #0 = { noinline nounwind optnone uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "patchable-function"="prologue-short-redirect" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!2, !3, !4, !5, !6, !7, !8}
+!llvm.ident = !{!9}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C11, file: !1, producer: "clang version 21.0.0git", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None)
+!1 = !DIFile(filename: "a.c", directory: "/tmp", checksumkind: CSK_MD5, checksum: "c59e1f6192de3124537b024248301dd1")
+!2 = !{i32 2, !"CodeView", i32 1}
+!3 = !{i32 2, !"ms-hotpatch", i32 1}
+!4 = !{i32 2, !"Debug Info Version", i32 3}
+!5 = !{i32 1, !"wchar_size", i32 2}
+!6 = !{i32 8, !"PIC Level", i32 2}
+!7 = !{i32 7, !"uwtable", i32 2}
+!8 = !{i32 1, !"MaxTLSAlign", i32 65536}
+!9 = !{!"clang version 21.0.0git"}
+!10 = distinct !DISubprogram(name: "test", scope: !1, file: !1, line: 1, type: !11, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !0)
+!11 = !DISubroutineType(types: !12)
+!12 = !{null}
+!13 = !DILocation(line: 1, scope: !10)
+
+; CHECK:      CodeViewDebugInfo [
+; CHECK-NEXT:   Section: .debug$S (4)
+; CHECK-NEXT:   Magic: 0x4
+; CHECK-NEXT:   Subsection [
+; CHECK-NEXT:     SubSectionType: Symbols (0xF1)
+; CHECK-NEXT:     SubSectionSize: 0x40
+; CHECK-NEXT:     ObjNameSym {
+; CHECK-NEXT:       Kind: S_OBJNAME (0x1101)
+; CHECK-NEXT:       Signature: 0x0
+; CHECK-NEXT:       ObjectName:
+; CHECK-NEXT:     }
+; CHECK-NEXT:     Compile3Sym {
+; CHECK-NEXT:       Kind: S_COMPILE3 (0x113C)
+; CHECK-NEXT:       Language: C (0x0)
+; CHECK-NEXT:       Flags [ (0x4000)
+; CHECK-NEXT:         HotPatch (0x4000)
+; CHECK-NEXT:       ]
+; CHECK-NEXT:       Machine: X64 (0xD0)
+; CHECK-NEXT:       FrontendVersion: 21.0.0.0
+; CHECK-NEXT:       BackendVersion: 21000.0.0.0
+; CHECK-NEXT:       VersionName: clang version 21.0.0git
+; CHECK-NEXT:     }
+; CHECK-NEXT:   ]

@aganea
Copy link
Member

aganea commented Jun 5, 2025

MSVC emits this debug section at all times (S_OBJNAME and S_COMPILE3). Can't we do the same without checking for the getModuleFlag("ms-hotpatch") flag?

@aganea
Copy link
Member

aganea commented Jun 5, 2025

The other a bit related point to this, is perhaps we should re-evaluate enabling /HOTPATCH at all times on x64 targets, like MSVC does.

@cjacek
Copy link
Contributor Author

cjacek commented Jun 5, 2025

I think we could emit this unconditionally, but I wasn’t entirely sure, so I went with a less invasive change for now. I'll prepare a new version.

In this version, Clang emits a full debug IR, but if we're going to do this by default, it probably makes more sense to emit just a minimal llvm.dbg.cu when debug info isn’t otherwise enabled. I’ve already experimented with that approach.

Also, enabling /HOTPATCH by default on x64 is an interesting idea. If we decide to go that route, then always emitting the compiler info (without conditioning) makes even more sense.

@aganea
Copy link
Member

aganea commented Jun 5, 2025

In this version, Clang emits a full debug IR, but if we're going to do this by default, it probably makes more sense to emit just a minimal llvm.dbg.cu when debug info isn’t otherwise enabled. I’ve already experimented with that approach.

Yes, I think one of my worry regarding that is increase in build times, since now we would be spawing a CGDebugInfo every time for non-debuginfo builds.

Also, enabling /HOTPATCH by default on x64 is an interesting idea. If we decide to go that route, then always emitting the compiler info (without conditioning) makes even more sense.

When I added that flag, I noted different preludes being generated with /HOTPATCH. Functions have to start with a 2-byte instruction if we enabled this by default which is not always the case in Clang/LLVM. There could be edge cases where the code is shifted in memory and accross the cache lines, and that could cause performance regressions. I think that should be minimal, but it'd be nice to run some standard performance tests if we did that change.

@dpaoliello
Copy link
Contributor

MSVC emits this debug section at all times (S_OBJNAME and S_COMPILE3). Can't we do the same without checking for the getModuleFlag("ms-hotpatch") flag?

I would much rather do this: we have other internal tooling withing Microsoft that relies on being able to check what compiler built a module (although we always enable debug info - but it would good to have this in case a team forgot to enable it).

@cjacek cjacek force-pushed the hotpatch-codeview branch from 3534285 to 4b7ecac Compare June 6, 2025 14:42
@cjacek cjacek changed the title [CodeGen][COFF] Always emit CodeView compiler info when hotpatch option is enabled [CodeGen][COFF] Always emit CodeView compiler info on Windows targets Jun 6, 2025
@cjacek
Copy link
Contributor Author

cjacek commented Jun 6, 2025

The new version always emits S_OBJNAME and S_COMPILE3 on Windows targets, regardless of debug info settings. It no longer depends on #142969. On the Clang side, it emits only minimal compiler info in IR when full debug info is not enabled.

I wasn't sure whether to enable this for UEFI targets as well, so I didn't. I enabled it for MinGW targets too, since I saw no reason to treat them differently.

@mizvekov
Copy link
Contributor

mizvekov commented Jun 6, 2025

I wasn't sure whether to enable this for UEFI targets as well, so I didn't. I enabled it for MinGW targets too, since I saw no reason to treat them differently.

AFAIK only recent GCC toolchain supports codeview. I am not sure GDB even does, so this could be a problem.

codeview::CPUType TheCPU;

/// Whether to emit compiler information only.
bool NoDebug = false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be clearer reading the code above if we named this OnlyCompilerInfo.

}

void CodeViewDebug::beginModule(Module *M) {
// If module doesn't have named metadata anchors or COFF debug section
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adjust comment.

M.getNamedMetadata("llvm.dbg.cu"))
Handlers.push_back(std::make_unique<CodeViewDebug>(this));
if (!EmitCodeView || M.getDwarfVersion()) {
if (M.getDwarfVersion() || !M.getCodeViewFlag()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just being picky here, but can you please leave the condition in the same order to ease downstream integrations?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. I initially reordered the other check as well but reverted it to keep the more expensive check later. Thanks!

@mstorsjo
Copy link
Member

mstorsjo commented Jun 6, 2025

I wasn't sure whether to enable this for UEFI targets as well, so I didn't. I enabled it for MinGW targets too, since I saw no reason to treat them differently.

Hmm, so a "regular" mingw object file would end up having both DWARF (if building with -g) and codeview (for the compiler info)? Does this cause confusion when linking (probably not, as it would either retain the DWARF or the codeview parts, depending on linker arguments I guess)? If GNU ld bails out on this we probably shouldn't, but at least recent versions should cope with it I think. It may be good to verify that it doesn't end up triggering GNU ld to produce only PDB output instead of the expected embedded DWARF, or something like that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you understand what is happening here? I don't understand why this would change....

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test emits DWARF debug info, which results in two debug handlers, both emitting labels and causing duplication. I think we can avoid this; I'll take a closer look.

@cjacek
Copy link
Contributor Author

cjacek commented Jun 6, 2025

Hmm, so a "regular" mingw object file would end up having both DWARF (if building with -g) and codeview (for the compiler info)?

Yes.

Does this cause confusion when linking (probably not, as it would either retain the DWARF or the codeview parts, depending on linker arguments I guess)?

I don’t think it should cause any confusion, and my testing confirms that. LLD stores debug chunks separately when reading COFF files in ObjFile::readSection, and only uses them when generating a PDB file, based on the command-line arguments. I’m less familiar with binutils, but it also appears to condition PDB generation on command-line options and discards CodeView sections based on their names.

AFAIK only recent GCC toolchain supports codeview. I am not sure GDB even does, so this could be a problem.

The discard I mentioned above appears to predate CodeView support, git blame shows it was part of the initial import to CVS in 1999. Note that with this change, we’d still use DWARF by default for actual debug info on MinGW; the CodeView compiler info is just an addition.

MSVC always emits minimal CodeView metadata with compiler information, even when
debug info is otherwise disabled. Other tools may rely on this metadata being
present. For example, linkers use it to determine whether hotpatching is enabled
for the object file.
@cjacek cjacek force-pushed the hotpatch-codeview branch from fe97c5c to 0e2aaa5 Compare June 8, 2025 21:25
@cjacek
Copy link
Contributor Author

cjacek commented Jun 8, 2025

The new version avoids creating unnecessary labels (and most of DebugHandlerBase) by setting Asm to null. I also added handling for unknown architecture types in mapArchToCVCPUType, which is triggered when running some of the Generic tests on Windows. Additionally, I tweaked a few tests that compare output assembly or IR, as they were failing due to the file name now being encoded.

@cjacek cjacek merged commit be5c96b into llvm:main Jun 13, 2025
7 checks passed
@cjacek cjacek deleted the hotpatch-codeview branch June 13, 2025 20:48
@cjacek
Copy link
Contributor Author

cjacek commented Jun 13, 2025

Merged, thanks!

cjacek added a commit that referenced this pull request Jun 13, 2025
…t (NFC)

Fixes test from #142970 on Fuchsia CI, which uses "Fuchsia clang version" prefix.
tomtor pushed a commit to tomtor/llvm-project that referenced this pull request Jun 14, 2025
…llvm#142970)

MSVC always emits minimal CodeView metadata with compiler information,
even when debug info is otherwise disabled. Other tools may rely on this
metadata being present. For example, linkers use it to determine whether
hotpatching is enabled for the object file.
tomtor pushed a commit to tomtor/llvm-project that referenced this pull request Jun 14, 2025
…t (NFC)

Fixes test from llvm#142970 on Fuchsia CI, which uses "Fuchsia clang version" prefix.
akuhlens pushed a commit to akuhlens/llvm-project that referenced this pull request Jun 24, 2025
…llvm#142970)

MSVC always emits minimal CodeView metadata with compiler information,
even when debug info is otherwise disabled. Other tools may rely on this
metadata being present. For example, linkers use it to determine whether
hotpatching is enabled for the object file.
akuhlens pushed a commit to akuhlens/llvm-project that referenced this pull request Jun 24, 2025
…t (NFC)

Fixes test from llvm#142970 on Fuchsia CI, which uses "Fuchsia clang version" prefix.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clang:codegen IR generation bugs: mangling, exceptions, etc. clang Clang issues not falling into any other category debuginfo llvm:codegen

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants