Skip to content

Commit 70c1d6a

Browse files
JDevliegherellvmbot
authored andcommitted
[DebugInfo][WebAssembly] Anchor stack locals to the frame base (llvm#211826)
WebAssembly's stack pointer is not a register that can be described in DWARF. When a function has no virtual frame base, because its stack pointer is never explicitly referenced (e.g. a function whose only local is dead), the frame base falls back to the __stack_pointer global and the frame register is the physical SP. addMachineReg failed for that register, so addMachineRegExpression dropped the base and the caller emitted the frame offset with nothing under it: a bare DW_OP_plus_uconst that underflows the DWARF stack when the location is evaluated. ``` DW_AT_frame_base (DW_OP_WASM_location 0x3 0x0, DW_OP_stack_value) DW_AT_location (DW_OP_plus_uconst 0xc) ;; before DW_AT_location (DW_OP_fbreg +12) ;; after ``` A frame-relative location does not need the frame register to be describable on its own, since it is anchored through DW_AT_frame_base and DW_OP_fbreg. Take the same path already used for a virtual frame register so a physical one without a DWARF number is handled too. Assisted-by: Claude PS: I also updated the frame-base fallback comment in WebAssemblyFrameLowering: reads now work at the innermost frame, and the remaining limitation is that the __stack_pointer global does not describe outer frames. (cherry picked from commit d3f58f9)
1 parent bb9934d commit 70c1d6a

3 files changed

Lines changed: 49 additions & 2 deletions

File tree

llvm/lib/CodeGen/AsmPrinter/DwarfExpression.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,13 @@ bool DwarfExpression::addMachineReg(const TargetRegisterInfo &TRI,
122122
return true;
123123
}
124124

125+
// The frame register is referenced through DW_OP_fbreg relative to
126+
// DW_AT_frame_base, so it needs no DWARF register number of its own.
127+
if (isFrameRegister(TRI, MachineReg)) {
128+
DwarfRegs.push_back(Register::createRegister(-1, nullptr));
129+
return true;
130+
}
131+
125132
// Walk up the super-register chain until we find a valid number.
126133
// For example, EAX on x86_64 is a 32-bit fragment of RAX with offset 0.
127134
for (MCPhysReg SR : TRI.superregs(MachineReg)) {

llvm/lib/Target/WebAssembly/WebAssemblyFrameLowering.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -413,8 +413,9 @@ WebAssemblyFrameLowering::getDwarfFrameBase(const MachineFunction &MF) const {
413413
// that this code is not reached in that case, but assert here to be sure.
414414
assert(!MF.getSubtarget<WebAssemblySubtarget>().hasLibcallThreadContext());
415415

416-
// TODO: This should work on a breakpoint at a function with no frame,
417-
// but probably won't work for traversing up the stack.
416+
// The __stack_pointer global holds only the innermost frame's stack
417+
// pointer, so this frame base is correct only for the innermost frame.
418+
// TODO: Describe outer frames, which need a per-frame stack pointer value.
418419
Loc.Location.WasmLoc = {WebAssembly::TI_GLOBAL_RELOC, 0};
419420
}
420421
return Loc;
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
;; A function whose stack pointer is never referenced has no virtual frame base
2+
;; local, so its frame base is the __stack_pointer global. A stack local must
3+
;; still be described relative to that frame base with DW_OP_fbreg: the stack
4+
;; pointer is not a DWARF register, but a frame-relative location needs no
5+
;; register of its own. Otherwise the base is lost and the offset underflows
6+
;; the DWARF stack when the location is evaluated.
7+
8+
; RUN: llc < %s -filetype=obj | llvm-dwarfdump - | FileCheck %s
9+
10+
; CHECK: DW_TAG_subprogram
11+
; CHECK: DW_AT_frame_base ({{.*}}0x3 0x0, DW_OP_stack_value)
12+
; CHECK: DW_TAG_variable
13+
; CHECK-NEXT: DW_AT_location (DW_OP_fbreg +12)
14+
; CHECK-NEXT: DW_AT_name ("t1")
15+
16+
target triple = "wasm32-unknown-unknown"
17+
18+
define hidden i32 @main() #0 !dbg !6 {
19+
%1 = alloca i32, align 4
20+
#dbg_declare(ptr %1, !11, !DIExpression(), !13)
21+
ret i32 0, !dbg !13
22+
}
23+
24+
attributes #0 = { noinline nounwind optnone }
25+
26+
!llvm.dbg.cu = !{!0}
27+
!llvm.module.flags = !{!2, !3}
28+
29+
!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "clang", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
30+
!1 = !DIFile(filename: "t.c", directory: "/")
31+
!2 = !{i32 7, !"Dwarf Version", i32 4}
32+
!3 = !{i32 2, !"Debug Info Version", i32 3}
33+
!6 = distinct !DISubprogram(name: "main", scope: !1, file: !1, line: 1, type: !7, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !10)
34+
!7 = !DISubroutineType(types: !8)
35+
!8 = !{!9}
36+
!9 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
37+
!10 = !{!11}
38+
!11 = !DILocalVariable(name: "t1", scope: !6, file: !1, line: 1, type: !9)
39+
!13 = !DILocation(line: 1, column: 1, scope: !6)

0 commit comments

Comments
 (0)