Skip to content

[AArch64] "Support" debug info for SVE types on Windows. #147865

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jul 13, 2025

Conversation

efriedma-quic
Copy link
Collaborator

There isn't any way to encode a variable in an SVE register, and there isn't any way to encode a scalable offset, and as far as I know that's unlikely to change in the near future. So suppress any debug info which would require those encodings.

This isn't ideal, but we need to ship something which doesn't crash.

Alternatively, for Z registers, we could emit debug info assuming the vector length is 128 bits, but that seems like it would lead to unintuitive results.

The change to AArch64FrameLowering is needed to avoid a crash. But we can't actually test that the returned offset is correct: LiveDebugValues performs the query, then discards the result.

There isn't any way to encode a variable in an SVE register, and there
isn't any way to encode a scalable offset, and as far as I know that's
unlikely to change in the near future.  So suppress any debug info which
would require those encodings.

This isn't ideal, but we need to ship something which doesn't crash.

Alternatively, for Z registers, we could emit debug info assuming the
vector length is 128 bits, but that seems like it would lead to
unintuitive results.

The change to AArch64FrameLowering is needed to avoid a crash.  But we
can't actually test that the returned offset is correct: LiveDebugValues
performs the query, then discards the result.
@llvmbot
Copy link
Member

llvmbot commented Jul 10, 2025

@llvm/pr-subscribers-debuginfo

Author: Eli Friedman (efriedma-quic)

Changes

There isn't any way to encode a variable in an SVE register, and there isn't any way to encode a scalable offset, and as far as I know that's unlikely to change in the near future. So suppress any debug info which would require those encodings.

This isn't ideal, but we need to ship something which doesn't crash.

Alternatively, for Z registers, we could emit debug info assuming the vector length is 128 bits, but that seems like it would lead to unintuitive results.

The change to AArch64FrameLowering is needed to avoid a crash. But we can't actually test that the returned offset is correct: LiveDebugValues performs the query, then discards the result.


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

5 Files Affected:

  • (modified) llvm/include/llvm/MC/MCRegisterInfo.h (+8)
  • (modified) llvm/lib/CodeGen/AsmPrinter/CodeViewDebug.cpp (+9-2)
  • (modified) llvm/lib/Target/AArch64/AArch64FrameLowering.cpp (+4-2)
  • (modified) llvm/lib/Target/AArch64/MCTargetDesc/AArch64MCTargetDesc.cpp (+14)
  • (added) llvm/test/DebugInfo/COFF/AArch64/codeview-sve.ll (+161)
diff --git a/llvm/include/llvm/MC/MCRegisterInfo.h b/llvm/include/llvm/MC/MCRegisterInfo.h
index aad3792f88784..afb55278d7ca8 100644
--- a/llvm/include/llvm/MC/MCRegisterInfo.h
+++ b/llvm/include/llvm/MC/MCRegisterInfo.h
@@ -16,6 +16,7 @@
 #define LLVM_MC_MCREGISTERINFO_H
 
 #include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/DenseSet.h"
 #include "llvm/ADT/iterator.h"
 #include "llvm/ADT/iterator_range.h"
 #include "llvm/MC/LaneBitmask.h"
@@ -190,6 +191,7 @@ class LLVM_ABI MCRegisterInfo {
   const DwarfLLVMRegPair *EHDwarf2LRegs;      // Dwarf to LLVM regs mapping EH
   DenseMap<MCRegister, int> L2SEHRegs;        // LLVM to SEH regs mapping
   DenseMap<MCRegister, int> L2CVRegs;         // LLVM to CV regs mapping
+  DenseSet<MCRegister> IgnoredCVRegs;
 
   mutable std::vector<std::vector<MCPhysReg>> RegAliasesCache;
   ArrayRef<MCPhysReg> getCachedAliasesOf(MCRegister R) const;
@@ -353,6 +355,12 @@ class LLVM_ABI MCRegisterInfo {
     L2CVRegs[LLVMReg] = CVReg;
   }
 
+  void ignoreCVReg(MCRegister LLVMReg) { IgnoredCVRegs.insert(LLVMReg); }
+
+  bool isIgnoredCVReg(MCRegister LLVMReg) const {
+    return IgnoredCVRegs.contains(LLVMReg);
+  }
+
   /// This method should return the register where the return
   /// address can be found.
   MCRegister getRARegister() const {
diff --git a/llvm/lib/CodeGen/AsmPrinter/CodeViewDebug.cpp b/llvm/lib/CodeGen/AsmPrinter/CodeViewDebug.cpp
index bc74daf983e40..8abeb56adeac9 100644
--- a/llvm/lib/CodeGen/AsmPrinter/CodeViewDebug.cpp
+++ b/llvm/lib/CodeGen/AsmPrinter/CodeViewDebug.cpp
@@ -1318,8 +1318,10 @@ void CodeViewDebug::collectVariableInfoFromMFTable(
         TFI->getFrameIndexReference(*Asm->MF, VI.getStackSlot(), FrameReg);
     uint16_t CVReg = TRI->getCodeViewRegNum(FrameReg);
 
-    assert(!FrameOffset.getScalable() &&
-           "Frame offsets with a scalable component are not supported");
+    if (FrameOffset.getScalable()) {
+      // No encoding currently exists for scalable offsets; bail out.
+      continue;
+    }
 
     // Calculate the label ranges.
     LocalVarDef DefRange =
@@ -1410,6 +1412,11 @@ void CodeViewDebug::calculateRanges(
       if (Location->FragmentInfo->OffsetInBits % 8)
         continue;
 
+    if (TRI->isIgnoredCVReg(Location->Register)) {
+      // No encoding currently exists for this register; bail out.
+      continue;
+    }
+
     LocalVarDef DR;
     DR.CVRegister = TRI->getCodeViewRegNum(Location->Register);
     DR.InMemory = !Location->LoadChain.empty();
diff --git a/llvm/lib/Target/AArch64/AArch64FrameLowering.cpp b/llvm/lib/Target/AArch64/AArch64FrameLowering.cpp
index b91e5c55e520d..bcdbebe0d11c2 100644
--- a/llvm/lib/Target/AArch64/AArch64FrameLowering.cpp
+++ b/llvm/lib/Target/AArch64/AArch64FrameLowering.cpp
@@ -2958,9 +2958,11 @@ StackOffset AArch64FrameLowering::resolveFrameOffsetReference(
         StackOffset::get(MFI.getStackSize() - AFI->getCalleeSavedStackSize(),
                          ObjectOffset);
     if (FPAfterSVECalleeSaves) {
-      assert(-ObjectOffset > (int64_t)AFI->getSVECalleeSavedStackSize() &&
-             "Math isn't correct for CSRs with FPAfterSVECalleeSaves");
       FPOffset += StackOffset::getScalable(AFI->getSVECalleeSavedStackSize());
+      if (-ObjectOffset <= (int64_t)AFI->getSVECalleeSavedStackSize()) {
+        FPOffset += StackOffset::getFixed(AFI->getCalleeSavedStackSize());
+        SPOffset += StackOffset::getFixed(AFI->getCalleeSavedStackSize());
+      }
     }
     // Always use the FP for SVE spills if available and beneficial.
     if (hasFP(MF) && (SPOffset.getFixed() ||
diff --git a/llvm/lib/Target/AArch64/MCTargetDesc/AArch64MCTargetDesc.cpp b/llvm/lib/Target/AArch64/MCTargetDesc/AArch64MCTargetDesc.cpp
index efc13589bab63..ecec6cd84492e 100644
--- a/llvm/lib/Target/AArch64/MCTargetDesc/AArch64MCTargetDesc.cpp
+++ b/llvm/lib/Target/AArch64/MCTargetDesc/AArch64MCTargetDesc.cpp
@@ -302,6 +302,20 @@ void AArch64_MC::initLLVMToCVRegMapping(MCRegisterInfo *MRI) {
   };
   for (const auto &I : RegMap)
     MRI->mapLLVMRegToCVReg(I.Reg, static_cast<int>(I.CVReg));
+  static constexpr MCRegister IgnoredRegs[] = {
+      AArch64::Z0,  AArch64::Z1,  AArch64::Z2,  AArch64::Z3,  AArch64::Z4,
+      AArch64::Z5,  AArch64::Z6,  AArch64::Z7,  AArch64::Z8,  AArch64::Z9,
+      AArch64::Z10, AArch64::Z11, AArch64::Z12, AArch64::Z13, AArch64::Z14,
+      AArch64::Z15, AArch64::Z16, AArch64::Z17, AArch64::Z18, AArch64::Z19,
+      AArch64::Z20, AArch64::Z21, AArch64::Z22, AArch64::Z23, AArch64::Z24,
+      AArch64::Z25, AArch64::Z26, AArch64::Z27, AArch64::Z28, AArch64::Z29,
+      AArch64::Z30, AArch64::Z31, AArch64::P0,  AArch64::P1,  AArch64::P2,
+      AArch64::P3,  AArch64::P4,  AArch64::P5,  AArch64::P6,  AArch64::P7,
+      AArch64::P8,  AArch64::P9,  AArch64::P10, AArch64::P11, AArch64::P12,
+      AArch64::P13, AArch64::P14, AArch64::P15,
+  };
+  for (const auto &R : IgnoredRegs)
+    MRI->ignoreCVReg(R);
 }
 
 bool AArch64_MC::isHForm(const MCInst &MI, const MCInstrInfo *MCII) {
diff --git a/llvm/test/DebugInfo/COFF/AArch64/codeview-sve.ll b/llvm/test/DebugInfo/COFF/AArch64/codeview-sve.ll
new file mode 100644
index 0000000000000..446a84dc0294c
--- /dev/null
+++ b/llvm/test/DebugInfo/COFF/AArch64/codeview-sve.ll
@@ -0,0 +1,161 @@
+; RUN: llc < %s -filetype=obj | llvm-readobj --codeview - | FileCheck %s
+
+; The point of this is mostly just to avoid crashing... there isn't any way
+; to encode most of the information we want to encode.  But we try to do what
+; we can.
+;
+; Generated from:
+;
+; #include <arm_sve.h>
+; void g();
+; svint32_t f(svint32_t aaa, svint32_t bbb, svint32_t *ccc) {
+;   asm("":::"z0", "z1", "z2", "z3", "z4", "z5", "z6", "z7", "z8", "z9",
+;            "z10", "z11", "z12", "z13", "z14", "z15", "z16", "z17", "z18",
+;            "z19", "z20", "z21", "z22", "z23", "z24", "z25", "z26", "z27",
+;            "z28", "z29", "z30", "z31");
+;  return aaa**ccc+bbb;
+;}
+
+; Emit the SVE type.  We represent this as an array with unknown bound.
+
+; CHECK:      Array (0x1000) {
+; CHECK-NEXT:     TypeLeafKind: LF_ARRAY (0x1503)
+; CHECK-NEXT:     ElementType: int (0x74)
+; CHECK-NEXT:     IndexType: unsigned __int64 (0x23)
+; CHECK-NEXT:     SizeOf: 0
+; CHECK-NEXT:     Name:
+; CHECK-NEXT:   }
+
+; Emit frame information.  This is missing the size of the SVE
+; variables, but we can't really do anything about that.
+
+; CHECK:         FrameProcSym {
+; CHECK-NEXT:      Kind: S_FRAMEPROC (0x1012)
+; CHECK-NEXT:      TotalFrameBytes: 0x10
+; CHECK-NEXT:      PaddingFrameBytes: 0x0
+; CHECK-NEXT:      OffsetToPadding: 0x0
+; CHECK-NEXT:      BytesOfCalleeSavedRegisters: 0x0
+; CHECK-NEXT:      OffsetOfExceptionHandler: 0x0
+; CHECK-NEXT:      SectionIdOfExceptionHandler: 0x0
+; CHECK-NEXT:      Flags [ (0x116008)
+; CHECK-NEXT:        HasInlineAssembly (0x8)
+; CHECK-NEXT:        OptimizedForSpeed (0x100000)
+; CHECK-NEXT:        SafeBuffers (0x2000)
+; CHECK-NEXT:      ]
+; CHECK-NEXT:      LocalFramePtrReg: ARM64_NOREG (0x0)
+; CHECK-NEXT:      ParamFramePtrReg: ARM64_NOREG (0x0)
+; CHECK-NEXT:    }
+
+; Emit the symbols for the local variables.
+;
+; ccc is a normal pointer.
+;
+; We can't represent bbb anywhere in its range; there's no way to name Z
+; registers, and no way to express its location on the stack relative
+; to the stack pointer when it's spilled.
+;
+; In the middle of the range, aaa happens to have a scalable offset of zero,
+; so we can represent it while it's on the stack.
+
+; CHECK-NEXT:    LocalSym {
+; CHECK-NEXT:      Kind: S_LOCAL (0x113E)
+; CHECK-NEXT:      Type: 0x1000
+; CHECK-NEXT:      Flags [ (0x1)
+; CHECK-NEXT:        IsParameter (0x1)
+; CHECK-NEXT:      ]
+; CHECK-NEXT:      VarName: aaa
+; CHECK-NEXT:    }
+; CHECK-NEXT:    DefRangeRegisterRelSym {
+; CHECK-NEXT:      Kind: S_DEFRANGE_REGISTER_REL (0x1145)
+; CHECK-NEXT:      BaseRegister: ARM64_SP (0x51)
+; CHECK-NEXT:      HasSpilledUDTMember: No
+; CHECK-NEXT:      OffsetInParent: 0
+; CHECK-NEXT:      BasePointerOffset: 0
+; CHECK-NEXT:      LocalVariableAddrRange {
+; CHECK-NEXT:        OffsetStart: .text+0x58
+; CHECK-NEXT:        ISectStart: 0x0
+; CHECK-NEXT:        Range: 0xC
+; CHECK-NEXT:      }
+; CHECK-NEXT:    }
+; CHECK-NEXT:    LocalSym {
+; CHECK-NEXT:      Kind: S_LOCAL (0x113E)
+; CHECK-NEXT:      Type: 0x1000
+; CHECK-NEXT:      Flags [ (0x101)
+; CHECK-NEXT:        IsOptimizedOut (0x100)
+; CHECK-NEXT:        IsParameter (0x1)
+; CHECK-NEXT:      ]
+; CHECK-NEXT:      VarName: bbb
+; CHECK-NEXT:    }
+; CHECK-NEXT:    LocalSym {
+; CHECK-NEXT:      Kind: S_LOCAL (0x113E)
+; CHECK-NEXT:      Type: * (0x1001)
+; CHECK-NEXT:      Flags [ (0x1)
+; CHECK-NEXT:        IsParameter (0x1)
+; CHECK-NEXT:      ]
+; CHECK-NEXT:      VarName: ccc
+; CHECK-NEXT:    }
+; CHECK-NEXT:    DefRangeRegisterSym {
+; CHECK-NEXT:      Kind: S_DEFRANGE_REGISTER (0x1141)
+; CHECK-NEXT:      Register: ARM64_X0 (0x32)
+; CHECK-NEXT:      MayHaveNoName: 0
+; CHECK-NEXT:      LocalVariableAddrRange {
+; CHECK-NEXT:        OffsetStart: .text+0x0
+; CHECK-NEXT:        ISectStart: 0x0
+; CHECK-NEXT:        Range: 0xBC
+; CHECK-NEXT:      }
+; CHECK-NEXT:    }
+; CHECK-NEXT:    ProcEnd {
+; CHECK-NEXT:      Kind: S_PROC_ID_END (0x114F)
+; CHECK-NEXT:    }
+
+target triple = "aarch64-unknown-windows-msvc19.33.0"
+
+; Function Attrs: mustprogress nounwind uwtable vscale_range(1,16)
+define dso_local <vscale x 4 x i32> @"?f@@YAU__SVInt32_t@__clang@@U12@0PEAU12@@Z"(<vscale x 4 x i32> %aaa, <vscale x 4 x i32> %bbb, ptr noundef readonly captures(none) %ccc) local_unnamed_addr #0 !dbg !10 {
+entry:
+    #dbg_value(ptr %ccc, !23, !DIExpression(), !26)
+    #dbg_value(<vscale x 4 x i32> %bbb, !24, !DIExpression(), !26)
+    #dbg_value(<vscale x 4 x i32> %aaa, !25, !DIExpression(), !26)
+  tail call void asm sideeffect "", "~{z0},~{z1},~{z2},~{z3},~{z4},~{z5},~{z6},~{z7},~{z8},~{z9},~{z10},~{z11},~{z12},~{z13},~{z14},~{z15},~{z16},~{z17},~{z18},~{z19},~{z20},~{z21},~{z22},~{z23},~{z24},~{z25},~{z26},~{z27},~{z28},~{z29},~{z30},~{z31}"() #1, !dbg !27, !srcloc !28
+  %0 = load <vscale x 4 x i32>, ptr %ccc, align 16, !dbg !27
+  %mul = mul <vscale x 4 x i32> %0, %aaa, !dbg !27
+  %add = add <vscale x 4 x i32> %mul, %bbb, !dbg !27
+  ret <vscale x 4 x i32> %add, !dbg !27
+}
+
+attributes #0 = { mustprogress nounwind uwtable vscale_range(1,16) "frame-pointer"="non-leaf" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="generic" "target-features"="+fp-armv8,+fullfp16,+neon,+sve,+v8a,-fmv" }
+attributes #1 = { nounwind }
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!2, !3, !4, !5, !6, !7, !8}
+!llvm.ident = !{!9}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, producer: "clang version 21.0.0git", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None)
+!1 = !DIFile(filename: "-", directory: "", checksumkind: CSK_MD5, checksum: "e54fc2ba768e4a43f64b8a9d03a374d6")
+!2 = !{i32 2, !"CodeView", 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 7, !"frame-pointer", i32 1}
+!8 = !{i32 7, !"debug-info-assignment-tracking", i1 true}
+!9 = !{!"clang version 21.0.0git"}
+!10 = distinct !DISubprogram(name: "f", linkageName: "?f@@YAU__SVInt32_t@__clang@@U12@0PEAU12@@Z", scope: !11, file: !11, line: 2, type: !12, scopeLine: 2, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !22)
+!11 = !DIFile(filename: "<stdin>", directory: "", checksumkind: CSK_MD5, checksum: "e54fc2ba768e4a43f64b8a9d03a374d6")
+!12 = !DISubroutineType(types: !13)
+!13 = !{!14, !14, !14, !21}
+!14 = !DIDerivedType(tag: DW_TAG_typedef, name: "svint32_t", file: !15, line: 30, baseType: !16)
+!15 = !DIFile(filename: "arm_sve.h", directory: "", checksumkind: CSK_MD5, checksum: "34027e9d24f4b03c6e5370869d5cc907")
+!16 = !DIDerivedType(tag: DW_TAG_typedef, name: "__SVInt32_t", file: !1, baseType: !17)
+!17 = !DICompositeType(tag: DW_TAG_array_type, baseType: !18, flags: DIFlagVector, elements: !19)
+!18 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+!19 = !{!20}
+!20 = !DISubrange(lowerBound: 0, upperBound: !DIExpression(DW_OP_constu, 2, DW_OP_bregx, 46, 0, DW_OP_mul, DW_OP_constu, 1, DW_OP_minus))
+!21 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !14, size: 64)
+!22 = !{!23, !24, !25}
+!23 = !DILocalVariable(name: "ccc", arg: 3, scope: !10, file: !11, line: 2, type: !21)
+!24 = !DILocalVariable(name: "bbb", arg: 2, scope: !10, file: !11, line: 2, type: !14)
+!25 = !DILocalVariable(name: "aaa", arg: 1, scope: !10, file: !11, line: 2, type: !14)
+!26 = !DILocation(line: 0, scope: !10)
+!27 = !DILocation(line: 2, scope: !10)
+!28 = !{i64 98}

@llvmbot
Copy link
Member

llvmbot commented Jul 10, 2025

@llvm/pr-subscribers-backend-aarch64

Author: Eli Friedman (efriedma-quic)

Changes

There isn't any way to encode a variable in an SVE register, and there isn't any way to encode a scalable offset, and as far as I know that's unlikely to change in the near future. So suppress any debug info which would require those encodings.

This isn't ideal, but we need to ship something which doesn't crash.

Alternatively, for Z registers, we could emit debug info assuming the vector length is 128 bits, but that seems like it would lead to unintuitive results.

The change to AArch64FrameLowering is needed to avoid a crash. But we can't actually test that the returned offset is correct: LiveDebugValues performs the query, then discards the result.


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

5 Files Affected:

  • (modified) llvm/include/llvm/MC/MCRegisterInfo.h (+8)
  • (modified) llvm/lib/CodeGen/AsmPrinter/CodeViewDebug.cpp (+9-2)
  • (modified) llvm/lib/Target/AArch64/AArch64FrameLowering.cpp (+4-2)
  • (modified) llvm/lib/Target/AArch64/MCTargetDesc/AArch64MCTargetDesc.cpp (+14)
  • (added) llvm/test/DebugInfo/COFF/AArch64/codeview-sve.ll (+161)
diff --git a/llvm/include/llvm/MC/MCRegisterInfo.h b/llvm/include/llvm/MC/MCRegisterInfo.h
index aad3792f88784..afb55278d7ca8 100644
--- a/llvm/include/llvm/MC/MCRegisterInfo.h
+++ b/llvm/include/llvm/MC/MCRegisterInfo.h
@@ -16,6 +16,7 @@
 #define LLVM_MC_MCREGISTERINFO_H
 
 #include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/DenseSet.h"
 #include "llvm/ADT/iterator.h"
 #include "llvm/ADT/iterator_range.h"
 #include "llvm/MC/LaneBitmask.h"
@@ -190,6 +191,7 @@ class LLVM_ABI MCRegisterInfo {
   const DwarfLLVMRegPair *EHDwarf2LRegs;      // Dwarf to LLVM regs mapping EH
   DenseMap<MCRegister, int> L2SEHRegs;        // LLVM to SEH regs mapping
   DenseMap<MCRegister, int> L2CVRegs;         // LLVM to CV regs mapping
+  DenseSet<MCRegister> IgnoredCVRegs;
 
   mutable std::vector<std::vector<MCPhysReg>> RegAliasesCache;
   ArrayRef<MCPhysReg> getCachedAliasesOf(MCRegister R) const;
@@ -353,6 +355,12 @@ class LLVM_ABI MCRegisterInfo {
     L2CVRegs[LLVMReg] = CVReg;
   }
 
+  void ignoreCVReg(MCRegister LLVMReg) { IgnoredCVRegs.insert(LLVMReg); }
+
+  bool isIgnoredCVReg(MCRegister LLVMReg) const {
+    return IgnoredCVRegs.contains(LLVMReg);
+  }
+
   /// This method should return the register where the return
   /// address can be found.
   MCRegister getRARegister() const {
diff --git a/llvm/lib/CodeGen/AsmPrinter/CodeViewDebug.cpp b/llvm/lib/CodeGen/AsmPrinter/CodeViewDebug.cpp
index bc74daf983e40..8abeb56adeac9 100644
--- a/llvm/lib/CodeGen/AsmPrinter/CodeViewDebug.cpp
+++ b/llvm/lib/CodeGen/AsmPrinter/CodeViewDebug.cpp
@@ -1318,8 +1318,10 @@ void CodeViewDebug::collectVariableInfoFromMFTable(
         TFI->getFrameIndexReference(*Asm->MF, VI.getStackSlot(), FrameReg);
     uint16_t CVReg = TRI->getCodeViewRegNum(FrameReg);
 
-    assert(!FrameOffset.getScalable() &&
-           "Frame offsets with a scalable component are not supported");
+    if (FrameOffset.getScalable()) {
+      // No encoding currently exists for scalable offsets; bail out.
+      continue;
+    }
 
     // Calculate the label ranges.
     LocalVarDef DefRange =
@@ -1410,6 +1412,11 @@ void CodeViewDebug::calculateRanges(
       if (Location->FragmentInfo->OffsetInBits % 8)
         continue;
 
+    if (TRI->isIgnoredCVReg(Location->Register)) {
+      // No encoding currently exists for this register; bail out.
+      continue;
+    }
+
     LocalVarDef DR;
     DR.CVRegister = TRI->getCodeViewRegNum(Location->Register);
     DR.InMemory = !Location->LoadChain.empty();
diff --git a/llvm/lib/Target/AArch64/AArch64FrameLowering.cpp b/llvm/lib/Target/AArch64/AArch64FrameLowering.cpp
index b91e5c55e520d..bcdbebe0d11c2 100644
--- a/llvm/lib/Target/AArch64/AArch64FrameLowering.cpp
+++ b/llvm/lib/Target/AArch64/AArch64FrameLowering.cpp
@@ -2958,9 +2958,11 @@ StackOffset AArch64FrameLowering::resolveFrameOffsetReference(
         StackOffset::get(MFI.getStackSize() - AFI->getCalleeSavedStackSize(),
                          ObjectOffset);
     if (FPAfterSVECalleeSaves) {
-      assert(-ObjectOffset > (int64_t)AFI->getSVECalleeSavedStackSize() &&
-             "Math isn't correct for CSRs with FPAfterSVECalleeSaves");
       FPOffset += StackOffset::getScalable(AFI->getSVECalleeSavedStackSize());
+      if (-ObjectOffset <= (int64_t)AFI->getSVECalleeSavedStackSize()) {
+        FPOffset += StackOffset::getFixed(AFI->getCalleeSavedStackSize());
+        SPOffset += StackOffset::getFixed(AFI->getCalleeSavedStackSize());
+      }
     }
     // Always use the FP for SVE spills if available and beneficial.
     if (hasFP(MF) && (SPOffset.getFixed() ||
diff --git a/llvm/lib/Target/AArch64/MCTargetDesc/AArch64MCTargetDesc.cpp b/llvm/lib/Target/AArch64/MCTargetDesc/AArch64MCTargetDesc.cpp
index efc13589bab63..ecec6cd84492e 100644
--- a/llvm/lib/Target/AArch64/MCTargetDesc/AArch64MCTargetDesc.cpp
+++ b/llvm/lib/Target/AArch64/MCTargetDesc/AArch64MCTargetDesc.cpp
@@ -302,6 +302,20 @@ void AArch64_MC::initLLVMToCVRegMapping(MCRegisterInfo *MRI) {
   };
   for (const auto &I : RegMap)
     MRI->mapLLVMRegToCVReg(I.Reg, static_cast<int>(I.CVReg));
+  static constexpr MCRegister IgnoredRegs[] = {
+      AArch64::Z0,  AArch64::Z1,  AArch64::Z2,  AArch64::Z3,  AArch64::Z4,
+      AArch64::Z5,  AArch64::Z6,  AArch64::Z7,  AArch64::Z8,  AArch64::Z9,
+      AArch64::Z10, AArch64::Z11, AArch64::Z12, AArch64::Z13, AArch64::Z14,
+      AArch64::Z15, AArch64::Z16, AArch64::Z17, AArch64::Z18, AArch64::Z19,
+      AArch64::Z20, AArch64::Z21, AArch64::Z22, AArch64::Z23, AArch64::Z24,
+      AArch64::Z25, AArch64::Z26, AArch64::Z27, AArch64::Z28, AArch64::Z29,
+      AArch64::Z30, AArch64::Z31, AArch64::P0,  AArch64::P1,  AArch64::P2,
+      AArch64::P3,  AArch64::P4,  AArch64::P5,  AArch64::P6,  AArch64::P7,
+      AArch64::P8,  AArch64::P9,  AArch64::P10, AArch64::P11, AArch64::P12,
+      AArch64::P13, AArch64::P14, AArch64::P15,
+  };
+  for (const auto &R : IgnoredRegs)
+    MRI->ignoreCVReg(R);
 }
 
 bool AArch64_MC::isHForm(const MCInst &MI, const MCInstrInfo *MCII) {
diff --git a/llvm/test/DebugInfo/COFF/AArch64/codeview-sve.ll b/llvm/test/DebugInfo/COFF/AArch64/codeview-sve.ll
new file mode 100644
index 0000000000000..446a84dc0294c
--- /dev/null
+++ b/llvm/test/DebugInfo/COFF/AArch64/codeview-sve.ll
@@ -0,0 +1,161 @@
+; RUN: llc < %s -filetype=obj | llvm-readobj --codeview - | FileCheck %s
+
+; The point of this is mostly just to avoid crashing... there isn't any way
+; to encode most of the information we want to encode.  But we try to do what
+; we can.
+;
+; Generated from:
+;
+; #include <arm_sve.h>
+; void g();
+; svint32_t f(svint32_t aaa, svint32_t bbb, svint32_t *ccc) {
+;   asm("":::"z0", "z1", "z2", "z3", "z4", "z5", "z6", "z7", "z8", "z9",
+;            "z10", "z11", "z12", "z13", "z14", "z15", "z16", "z17", "z18",
+;            "z19", "z20", "z21", "z22", "z23", "z24", "z25", "z26", "z27",
+;            "z28", "z29", "z30", "z31");
+;  return aaa**ccc+bbb;
+;}
+
+; Emit the SVE type.  We represent this as an array with unknown bound.
+
+; CHECK:      Array (0x1000) {
+; CHECK-NEXT:     TypeLeafKind: LF_ARRAY (0x1503)
+; CHECK-NEXT:     ElementType: int (0x74)
+; CHECK-NEXT:     IndexType: unsigned __int64 (0x23)
+; CHECK-NEXT:     SizeOf: 0
+; CHECK-NEXT:     Name:
+; CHECK-NEXT:   }
+
+; Emit frame information.  This is missing the size of the SVE
+; variables, but we can't really do anything about that.
+
+; CHECK:         FrameProcSym {
+; CHECK-NEXT:      Kind: S_FRAMEPROC (0x1012)
+; CHECK-NEXT:      TotalFrameBytes: 0x10
+; CHECK-NEXT:      PaddingFrameBytes: 0x0
+; CHECK-NEXT:      OffsetToPadding: 0x0
+; CHECK-NEXT:      BytesOfCalleeSavedRegisters: 0x0
+; CHECK-NEXT:      OffsetOfExceptionHandler: 0x0
+; CHECK-NEXT:      SectionIdOfExceptionHandler: 0x0
+; CHECK-NEXT:      Flags [ (0x116008)
+; CHECK-NEXT:        HasInlineAssembly (0x8)
+; CHECK-NEXT:        OptimizedForSpeed (0x100000)
+; CHECK-NEXT:        SafeBuffers (0x2000)
+; CHECK-NEXT:      ]
+; CHECK-NEXT:      LocalFramePtrReg: ARM64_NOREG (0x0)
+; CHECK-NEXT:      ParamFramePtrReg: ARM64_NOREG (0x0)
+; CHECK-NEXT:    }
+
+; Emit the symbols for the local variables.
+;
+; ccc is a normal pointer.
+;
+; We can't represent bbb anywhere in its range; there's no way to name Z
+; registers, and no way to express its location on the stack relative
+; to the stack pointer when it's spilled.
+;
+; In the middle of the range, aaa happens to have a scalable offset of zero,
+; so we can represent it while it's on the stack.
+
+; CHECK-NEXT:    LocalSym {
+; CHECK-NEXT:      Kind: S_LOCAL (0x113E)
+; CHECK-NEXT:      Type: 0x1000
+; CHECK-NEXT:      Flags [ (0x1)
+; CHECK-NEXT:        IsParameter (0x1)
+; CHECK-NEXT:      ]
+; CHECK-NEXT:      VarName: aaa
+; CHECK-NEXT:    }
+; CHECK-NEXT:    DefRangeRegisterRelSym {
+; CHECK-NEXT:      Kind: S_DEFRANGE_REGISTER_REL (0x1145)
+; CHECK-NEXT:      BaseRegister: ARM64_SP (0x51)
+; CHECK-NEXT:      HasSpilledUDTMember: No
+; CHECK-NEXT:      OffsetInParent: 0
+; CHECK-NEXT:      BasePointerOffset: 0
+; CHECK-NEXT:      LocalVariableAddrRange {
+; CHECK-NEXT:        OffsetStart: .text+0x58
+; CHECK-NEXT:        ISectStart: 0x0
+; CHECK-NEXT:        Range: 0xC
+; CHECK-NEXT:      }
+; CHECK-NEXT:    }
+; CHECK-NEXT:    LocalSym {
+; CHECK-NEXT:      Kind: S_LOCAL (0x113E)
+; CHECK-NEXT:      Type: 0x1000
+; CHECK-NEXT:      Flags [ (0x101)
+; CHECK-NEXT:        IsOptimizedOut (0x100)
+; CHECK-NEXT:        IsParameter (0x1)
+; CHECK-NEXT:      ]
+; CHECK-NEXT:      VarName: bbb
+; CHECK-NEXT:    }
+; CHECK-NEXT:    LocalSym {
+; CHECK-NEXT:      Kind: S_LOCAL (0x113E)
+; CHECK-NEXT:      Type: * (0x1001)
+; CHECK-NEXT:      Flags [ (0x1)
+; CHECK-NEXT:        IsParameter (0x1)
+; CHECK-NEXT:      ]
+; CHECK-NEXT:      VarName: ccc
+; CHECK-NEXT:    }
+; CHECK-NEXT:    DefRangeRegisterSym {
+; CHECK-NEXT:      Kind: S_DEFRANGE_REGISTER (0x1141)
+; CHECK-NEXT:      Register: ARM64_X0 (0x32)
+; CHECK-NEXT:      MayHaveNoName: 0
+; CHECK-NEXT:      LocalVariableAddrRange {
+; CHECK-NEXT:        OffsetStart: .text+0x0
+; CHECK-NEXT:        ISectStart: 0x0
+; CHECK-NEXT:        Range: 0xBC
+; CHECK-NEXT:      }
+; CHECK-NEXT:    }
+; CHECK-NEXT:    ProcEnd {
+; CHECK-NEXT:      Kind: S_PROC_ID_END (0x114F)
+; CHECK-NEXT:    }
+
+target triple = "aarch64-unknown-windows-msvc19.33.0"
+
+; Function Attrs: mustprogress nounwind uwtable vscale_range(1,16)
+define dso_local <vscale x 4 x i32> @"?f@@YAU__SVInt32_t@__clang@@U12@0PEAU12@@Z"(<vscale x 4 x i32> %aaa, <vscale x 4 x i32> %bbb, ptr noundef readonly captures(none) %ccc) local_unnamed_addr #0 !dbg !10 {
+entry:
+    #dbg_value(ptr %ccc, !23, !DIExpression(), !26)
+    #dbg_value(<vscale x 4 x i32> %bbb, !24, !DIExpression(), !26)
+    #dbg_value(<vscale x 4 x i32> %aaa, !25, !DIExpression(), !26)
+  tail call void asm sideeffect "", "~{z0},~{z1},~{z2},~{z3},~{z4},~{z5},~{z6},~{z7},~{z8},~{z9},~{z10},~{z11},~{z12},~{z13},~{z14},~{z15},~{z16},~{z17},~{z18},~{z19},~{z20},~{z21},~{z22},~{z23},~{z24},~{z25},~{z26},~{z27},~{z28},~{z29},~{z30},~{z31}"() #1, !dbg !27, !srcloc !28
+  %0 = load <vscale x 4 x i32>, ptr %ccc, align 16, !dbg !27
+  %mul = mul <vscale x 4 x i32> %0, %aaa, !dbg !27
+  %add = add <vscale x 4 x i32> %mul, %bbb, !dbg !27
+  ret <vscale x 4 x i32> %add, !dbg !27
+}
+
+attributes #0 = { mustprogress nounwind uwtable vscale_range(1,16) "frame-pointer"="non-leaf" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="generic" "target-features"="+fp-armv8,+fullfp16,+neon,+sve,+v8a,-fmv" }
+attributes #1 = { nounwind }
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!2, !3, !4, !5, !6, !7, !8}
+!llvm.ident = !{!9}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, producer: "clang version 21.0.0git", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None)
+!1 = !DIFile(filename: "-", directory: "", checksumkind: CSK_MD5, checksum: "e54fc2ba768e4a43f64b8a9d03a374d6")
+!2 = !{i32 2, !"CodeView", 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 7, !"frame-pointer", i32 1}
+!8 = !{i32 7, !"debug-info-assignment-tracking", i1 true}
+!9 = !{!"clang version 21.0.0git"}
+!10 = distinct !DISubprogram(name: "f", linkageName: "?f@@YAU__SVInt32_t@__clang@@U12@0PEAU12@@Z", scope: !11, file: !11, line: 2, type: !12, scopeLine: 2, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !22)
+!11 = !DIFile(filename: "<stdin>", directory: "", checksumkind: CSK_MD5, checksum: "e54fc2ba768e4a43f64b8a9d03a374d6")
+!12 = !DISubroutineType(types: !13)
+!13 = !{!14, !14, !14, !21}
+!14 = !DIDerivedType(tag: DW_TAG_typedef, name: "svint32_t", file: !15, line: 30, baseType: !16)
+!15 = !DIFile(filename: "arm_sve.h", directory: "", checksumkind: CSK_MD5, checksum: "34027e9d24f4b03c6e5370869d5cc907")
+!16 = !DIDerivedType(tag: DW_TAG_typedef, name: "__SVInt32_t", file: !1, baseType: !17)
+!17 = !DICompositeType(tag: DW_TAG_array_type, baseType: !18, flags: DIFlagVector, elements: !19)
+!18 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+!19 = !{!20}
+!20 = !DISubrange(lowerBound: 0, upperBound: !DIExpression(DW_OP_constu, 2, DW_OP_bregx, 46, 0, DW_OP_mul, DW_OP_constu, 1, DW_OP_minus))
+!21 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !14, size: 64)
+!22 = !{!23, !24, !25}
+!23 = !DILocalVariable(name: "ccc", arg: 3, scope: !10, file: !11, line: 2, type: !21)
+!24 = !DILocalVariable(name: "bbb", arg: 2, scope: !10, file: !11, line: 2, type: !14)
+!25 = !DILocalVariable(name: "aaa", arg: 1, scope: !10, file: !11, line: 2, type: !14)
+!26 = !DILocation(line: 0, scope: !10)
+!27 = !DILocation(line: 2, scope: !10)
+!28 = !{i64 98}

Comment on lines 360 to 362
bool isIgnoredCVReg(MCRegister LLVMReg) const {
return IgnoredCVRegs.contains(LLVMReg);
}
Copy link
Member

@MacDue MacDue Jul 10, 2025

Choose a reason for hiding this comment

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

I think you could skip the DenseSet and just do:

Suggested change
bool isIgnoredCVReg(MCRegister LLVMReg) const {
return IgnoredCVRegs.contains(LLVMReg);
}
bool isIgnoredCVReg(MCRegister LLVMReg) const {
return (LLVMReg >= AArch64::Z0 && LLVMReg <= AArch64::Z31)
|| (LLVMReg >= AArch64::P0 && LLVMReg <= AArch64::P15);
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Pretty sure that breaks layering; the AArch64 register names are only defined in the AArch64 backend. I could make it a virtual method, I guess?

Copy link
Member

Choose a reason for hiding this comment

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

Ah, I didn't spot this was outside the AArch64 backend, but yes, you could make it virtual or allow registering a std::function/llvm::function_ref callback to filter registers.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Changed to virtual method.

@dpaoliello
Copy link
Contributor

@pmsjt thoughts on how to handle this?

Copy link
Contributor

@dpaoliello dpaoliello left a comment

Choose a reason for hiding this comment

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

Seems reasonable to me.

Copy link
Member

@MacDue MacDue left a comment

Choose a reason for hiding this comment

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

Don't know much about Windows CV, but the changes seem reasonable 👍

@efriedma-quic efriedma-quic merged commit 4f5d67b into llvm:main Jul 13, 2025
9 checks passed
@llvm-ci
Copy link
Collaborator

llvm-ci commented Jul 13, 2025

LLVM Buildbot has detected a new failure on builder lldb-arm-ubuntu running on linaro-lldb-arm-ubuntu while building llvm at step 6 "test".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/18/builds/18925

Here is the relevant piece of the build log for the reference
Step 6 (test) failure: build (failure)
...
UNSUPPORTED: lldb-api :: functionalities/data-formatter/data-formatter-objc/TestDataFormatterObjCNSError.py (373 of 3332)
UNSUPPORTED: lldb-api :: functionalities/data-formatter/data-formatter-objc/TestDataFormatterObjCNSNumber.py (374 of 3332)
UNSUPPORTED: lldb-api :: functionalities/data-formatter/data-formatter-objc/TestDataFormatterObjCNSURL.py (375 of 3332)
UNSUPPORTED: lldb-api :: functionalities/data-formatter/data-formatter-objc/TestDataFormatterObjCPlain.py (376 of 3332)
UNSUPPORTED: lldb-api :: functionalities/data-formatter/data-formatter-objc/TestDataFormatterObjNSException.py (377 of 3332)
UNSUPPORTED: lldb-api :: functionalities/data-formatter/data-formatter-objc/cmtime/TestDataFormatterCMTime.py (378 of 3332)
UNSUPPORTED: lldb-api :: functionalities/data-formatter/data-formatter-objc/nsindexpath/TestDataFormatterNSIndexPath.py (379 of 3332)
UNSUPPORTED: lldb-api :: functionalities/data-formatter/data-formatter-objc/nsstring/TestDataFormatterNSString.py (380 of 3332)
UNSUPPORTED: lldb-api :: functionalities/data-formatter/data-formatter-proper-plurals/TestFormattersOneIsSingular.py (381 of 3332)
UNRESOLVED: lldb-api :: driver/terminal/TestTerminalDimensions.py (382 of 3332)
******************** TEST 'lldb-api :: driver/terminal/TestTerminalDimensions.py' FAILED ********************
Script:
--
/usr/bin/python3.10 /home/tcwg-buildbot/worker/lldb-arm-ubuntu/llvm-project/lldb/test/API/dotest.py -u CXXFLAGS -u CFLAGS --env LLVM_LIBS_DIR=/home/tcwg-buildbot/worker/lldb-arm-ubuntu/build/./lib --env LLVM_INCLUDE_DIR=/home/tcwg-buildbot/worker/lldb-arm-ubuntu/build/include --env LLVM_TOOLS_DIR=/home/tcwg-buildbot/worker/lldb-arm-ubuntu/build/./bin --arch armv8l --build-dir /home/tcwg-buildbot/worker/lldb-arm-ubuntu/build/lldb-test-build.noindex --lldb-module-cache-dir /home/tcwg-buildbot/worker/lldb-arm-ubuntu/build/lldb-test-build.noindex/module-cache-lldb/lldb-api --clang-module-cache-dir /home/tcwg-buildbot/worker/lldb-arm-ubuntu/build/lldb-test-build.noindex/module-cache-clang/lldb-api --executable /home/tcwg-buildbot/worker/lldb-arm-ubuntu/build/./bin/lldb --compiler /home/tcwg-buildbot/worker/lldb-arm-ubuntu/build/./bin/clang --dsymutil /home/tcwg-buildbot/worker/lldb-arm-ubuntu/build/./bin/dsymutil --make /usr/bin/gmake --llvm-tools-dir /home/tcwg-buildbot/worker/lldb-arm-ubuntu/build/./bin --lldb-obj-root /home/tcwg-buildbot/worker/lldb-arm-ubuntu/build/tools/lldb --lldb-libs-dir /home/tcwg-buildbot/worker/lldb-arm-ubuntu/build/./lib --cmake-build-type Release /home/tcwg-buildbot/worker/lldb-arm-ubuntu/llvm-project/lldb/test/API/driver/terminal -p TestTerminalDimensions.py
--
Exit Code: 1

Command Output (stdout):
--
lldb version 21.0.0git (https://github.com/llvm/llvm-project.git revision 4f5d67b3e414cf5f2c9f7440e97837b718bda4cc)
  clang revision 4f5d67b3e414cf5f2c9f7440e97837b718bda4cc
  llvm revision 4f5d67b3e414cf5f2c9f7440e97837b718bda4cc
Skipping the following test categories: ['libc++', 'msvcstl', 'dsym', 'gmodules', 'debugserver', 'objc']

--
Command Output (stderr):
--
FAIL: LLDB (/home/tcwg-buildbot/worker/lldb-arm-ubuntu/build/bin/clang-arm) :: test (TestTerminalDimensions.TerminalDimensionsTest)
======================================================================
ERROR: test (TestTerminalDimensions.TerminalDimensionsTest)
   Test that the lldb driver correctly reports the (PExpect) terminal dimension.
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/tcwg-buildbot/worker/lldb-arm-ubuntu/llvm-project/lldb/packages/Python/lldbsuite/test/decorators.py", line 149, in wrapper
    return func(*args, **kwargs)
  File "/home/tcwg-buildbot/worker/lldb-arm-ubuntu/llvm-project/lldb/test/API/driver/terminal/TestTerminalDimensions.py", line 21, in test
    self.expect("settings show term-height", ["term-height (unsigned) = 20"])
  File "/home/tcwg-buildbot/worker/lldb-arm-ubuntu/llvm-project/lldb/packages/Python/lldbsuite/test/lldbpexpect.py", line 94, in expect
    self.child.expect_exact(s)
  File "/usr/local/lib/python3.10/dist-packages/pexpect/spawnbase.py", line 432, in expect_exact
    return exp.expect_loop(timeout)
  File "/usr/local/lib/python3.10/dist-packages/pexpect/expect.py", line 181, in expect_loop
    return self.timeout(e)
  File "/usr/local/lib/python3.10/dist-packages/pexpect/expect.py", line 144, in timeout
    raise exc
pexpect.exceptions.TIMEOUT: Timeout exceeded.
<pexpect.pty_spawn.spawn object at 0xe9b60358>
command: /home/tcwg-buildbot/worker/lldb-arm-ubuntu/build/bin/lldb
args: ['/home/tcwg-buildbot/worker/lldb-arm-ubuntu/build/bin/lldb', '--no-lldbinit', '--no-use-colors', '-O', 'settings clear --all', '-O', 'settings set symbols.enable-external-lookup false', '-O', 'settings set target.inherit-tcc true', '-O', 'settings set target.disable-aslr false', '-O', 'settings set target.detach-on-error false', '-O', 'settings set target.auto-apply-fixits false', '-O', 'settings set plugin.process.gdb-remote.packet-timeout 60', '-O', 'settings set symbols.clang-modules-cache-path "/home/tcwg-buildbot/worker/lldb-arm-ubuntu/build/lldb-test-build.noindex/module-cache-lldb/lldb-api"', '-O', 'settings set use-color false', '-O', 'settings set show-statusline false']

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants