Skip to content
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

[coroutines] Use DILocation from new storage for hoisted dbg.declare #75104

Merged
merged 1 commit into from
Dec 12, 2023

Conversation

apolloww
Copy link
Contributor

@apolloww apolloww commented Dec 11, 2023

Make the hoisted dbg.declare inherent the DILocation scope from the new storage.

After hoisting, the dbg.declare is moved into the block that defines the new storage. This could create an inconsistency in the debug location scope hierarchy where the scope of hoisted dbg.declare (i.e. DILexicalBlock) is enclosed with the scope of the block (i.e. DISubprogram). This confuses LiveDebugValues pass to think that the hoisted dbg.declare is killed in that block and does not generate DBG_VALUE in other blocks. Debugger won't be able to track its value anymore.

@llvmbot
Copy link
Collaborator

llvmbot commented Dec 11, 2023

@llvm/pr-subscribers-llvm-transforms

@llvm/pr-subscribers-coroutines

Author: Wei Wang (apolloww)

Changes

After hoisting, the dbg.declare is moved into the block that defines the new storage. This could create an inconsistency in the debug location scope hierarchy where the scope of hoisted dbg.declare (i.e. DILexicalBlock) is enclosed with the scope of the block (i.e. DISubprogram). This confuses LiveDebugValues pass to think that the hoisted dbg.declare is killed in that block and does not generate DBG_VALUE.


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

2 Files Affected:

  • (modified) llvm/lib/Transforms/Coroutines/CoroFrame.cpp (+4-2)
  • (modified) llvm/test/Transforms/Coroutines/coro-debug-frame-variable.ll (+16-13)
diff --git a/llvm/lib/Transforms/Coroutines/CoroFrame.cpp b/llvm/lib/Transforms/Coroutines/CoroFrame.cpp
index 1134b20880f183..2c43f49e712f20 100644
--- a/llvm/lib/Transforms/Coroutines/CoroFrame.cpp
+++ b/llvm/lib/Transforms/Coroutines/CoroFrame.cpp
@@ -2887,9 +2887,11 @@ void coro::salvageDebugInfo(
   // dbg.declare does.
   if (isa<DbgDeclareInst>(DVI)) {
     std::optional<BasicBlock::iterator> InsertPt;
-    if (auto *I = dyn_cast<Instruction>(Storage))
+    if (auto *I = dyn_cast<Instruction>(Storage)) {
       InsertPt = I->getInsertionPointAfterDef();
-    else if (isa<Argument>(Storage))
+      if (I->getDebugLoc())
+        DVI->setDebugLoc(I->getDebugLoc());
+    } else if (isa<Argument>(Storage))
       InsertPt = F->getEntryBlock().begin();
     if (InsertPt)
       DVI->moveBefore(*(*InsertPt)->getParent(), *InsertPt);
diff --git a/llvm/test/Transforms/Coroutines/coro-debug-frame-variable.ll b/llvm/test/Transforms/Coroutines/coro-debug-frame-variable.ll
index 37b4126ce37304..53995fb3498e59 100644
--- a/llvm/test/Transforms/Coroutines/coro-debug-frame-variable.ll
+++ b/llvm/test/Transforms/Coroutines/coro-debug-frame-variable.ll
@@ -31,8 +31,8 @@
 ; CHECK:       entry:
 ; CHECK:         %j = alloca i32, align 4
 ; CHECK:         call void @llvm.dbg.declare(metadata ptr %j, metadata ![[JVAR:[0-9]+]], metadata !DIExpression()), !dbg ![[JDBGLOC:[0-9]+]]
-; CHECK:         %[[MEMORY:.*]] = call ptr @new
-; CHECK:         call void @llvm.dbg.declare(metadata ptr %[[MEMORY]], metadata ![[XVAR:[0-9]+]], metadata !DIExpression(DW_OP_plus_uconst, 32)), !dbg ![[IDBGLOC:[0-9]+]]
+; CHECK:         %[[MEMORY:.*]] = call ptr @new({{.+}}), !dbg ![[IDBGLOC:[0-9]+]]
+; CHECK:         call void @llvm.dbg.declare(metadata ptr %[[MEMORY]], metadata ![[XVAR:[0-9]+]], metadata !DIExpression(DW_OP_plus_uconst, 32)), !dbg ![[IDBGLOC]]
 ; CHECK:         call void @llvm.dbg.declare(metadata ptr %[[MEMORY]], metadata ![[IVAR:[0-9]+]], metadata !DIExpression(DW_OP_plus_uconst, 20)), !dbg ![[IDBGLOC]]
 ; CHECK:       await.ready:
 ;
@@ -48,18 +48,20 @@
 ; CHECK:       await.ready:
 ;
 ; CHECK-DAG: ![[IVAR]] = !DILocalVariable(name: "i"
-; CHECK-DAG: ![[SCOPE:[0-9]+]] = distinct !DILexicalBlock(scope: !6, file: !1, line: 23, column: 12)
-; CHECK-DAG: ![[IDBGLOC]] = !DILocation(line: 24, column: 7, scope: ![[SCOPE]])
+; CHECK-DAG: ![[PROG_SCOPE:[0-9]+]] = distinct !DISubprogram(name: "foo", linkageName: "_Z3foov"
+; CHECK-DAG: ![[BLK_SCOPE:[0-9]+]] = distinct !DILexicalBlock(scope: ![[PROG_SCOPE]], file: !1, line: 23, column: 12)
+; CHECK-DAG: ![[IDBGLOC]] = !DILocation(line: 23, column: 6, scope: ![[PROG_SCOPE]])
 ; CHECK-DAG: ![[XVAR]] = !DILocalVariable(name: "x"
 ; CHECK-DAG: ![[JVAR]] = !DILocalVariable(name: "j"
-; CHECK-DAG: ![[JDBGLOC]] = !DILocation(line: 32, column: 7, scope: ![[SCOPE]])
+; CHECK-DAG: ![[JDBGLOC]] = !DILocation(line: 32, column: 7, scope: ![[BLK_SCOPE]])
 
 ; CHECK-DAG: ![[XVAR_RESUME]] = !DILocalVariable(name: "x"
-; CHECK-DAG: ![[IDBGLOC_RESUME]] = !DILocation(line: 24, column: 7, scope: ![[RESUME_SCOPE:[0-9]+]])
-; CHECK-DAG: ![[RESUME_SCOPE]] = distinct !DILexicalBlock(scope: !22, file: !1, line: 23, column: 12)
+; CHECK-DAG: ![[RESUME_PROG_SCOPE:[0-9]+]] = distinct !DISubprogram(name: "foo", linkageName: "_Z3foov"
+; CHECK-DAG: ![[IDBGLOC_RESUME]] = !DILocation(line: 24, column: 7, scope: ![[RESUME_BLK_SCOPE:[0-9]+]])
+; CHECK-DAG: ![[RESUME_BLK_SCOPE]] = distinct !DILexicalBlock(scope: ![[RESUME_PROG_SCOPE]], file: !1, line: 23, column: 12)
 ; CHECK-DAG: ![[IVAR_RESUME]] = !DILocalVariable(name: "i"
 ; CHECK-DAG: ![[JVAR_RESUME]] = !DILocalVariable(name: "j"
-; CHECK-DAG: ![[JDBGLOC_RESUME]] = !DILocation(line: 32, column: 7, scope: ![[RESUME_SCOPE]])
+; CHECK-DAG: ![[JDBGLOC_RESUME]] = !DILocation(line: 32, column: 7, scope: ![[RESUME_BLK_SCOPE]])
 define void @f() presplitcoroutine !dbg !8 {
 entry:
   %__promise = alloca i8, align 8
@@ -71,13 +73,13 @@ entry:
   br i1 %alloc, label %coro.alloc, label %coro.init
 
 coro.alloc:                                       ; preds = %entry
-  %size = call i64 @llvm.coro.size.i64()
-  %memory = call ptr @new(i64 %size)
-  br label %coro.init
+  %size = call i64 @llvm.coro.size.i64(), !dbg !23
+  %memory = call ptr @new(i64 %size), !dbg !23
+  br label %coro.init, !dbg !23
 
 coro.init:                                        ; preds = %coro.alloc, %entry
-  %phi.entry.alloc = phi ptr [ null, %entry ], [ %memory, %coro.alloc ]
-  %begin = call ptr @llvm.coro.begin(token %id, ptr %phi.entry.alloc)
+  %phi.entry.alloc = phi ptr [ null, %entry ], [ %memory, %coro.alloc ], !dbg !23
+  %begin = call ptr @llvm.coro.begin(token %id, ptr %phi.entry.alloc), !dbg !23
   %ready = call i1 @await_ready()
   br i1 %ready, label %init.ready, label %init.suspend
 
@@ -239,3 +241,4 @@ declare void @llvm.memset.p0.i64(ptr nocapture writeonly, i8, i64, i1 immarg)
 !20 = !DILocation(line: 43, column: 3, scope: !7)
 !21 = !DILocation(line: 43, column: 8, scope: !7)
 !22 = distinct !DILexicalBlock(scope: !8, file: !1, line: 23, column: 12)
+!23 = !DILocation(line: 23, column: 6, scope: !8)

@apolloww
Copy link
Contributor Author

apolloww commented Dec 11, 2023

This is to fix an issue we saw internally where lldb can't see local variable (lives on frame) in the ramp function. It is because after hoisting, we have something like

define dso_local ptr @_Z3fooi(i32 noundef %arg) !dbg !1455 {
... ...
  coro.init:                                        ; preds = %coro.init.from.entry, %coro.init.from.coro.alloc
    %0 = phi ptr [ %.coro.init, %coro.init.from.entry ], [ %call.coro.init, %coro.init.from.coro.alloc ], !dbg !1489
    call void @llvm.dbg.declare(metadata ptr %0, metadata !1491, metadata !DIExpression(DW_OP_plus_uconst, 24)), !dbg !1492
    call void @llvm.dbg.declare(metadata ptr %0, metadata !1493, metadata !DIExpression(DW_OP_plus_uconst, 16)), !dbg !1494
    call void @llvm.dbg.declare(metadata ptr %0, metadata !1484, metadata !DIExpression(DW_OP_plus_uconst, 20)), !dbg !1494
    call void @llvm.dbg.declare(metadata ptr %0, metadata !1459, metadata !DIExpression()), !dbg !1494
... ...

!1455 = distinct !DISubprogram(name: "foo", linkageName: "_Z3fooi", scope: !19, file: !19, line: 20, type: !1456, scopeLine: 20, flags: DIFlagPrototyped,    spFlags: DISPFlagDefinition, unit: !18, retainedNodes: !1458)
!1489 = !DILocation(line: 20, column: 19, scope: !1455)
!1487 = distinct !DILexicalBlock(scope: !1455, file: !19, line: 20, column: 19)
!1492 = !DILocation(line: 21, column: 7, scope: !1487)
!1494 = !DILocation(line: 0, scope: !1455)

Because !1492 is a smaller scope than !1489, LiveDebugValues kills the dbg.declare at the end of coro.init block. This change makes dbg.declare take !1489.

The same issue also happens to .resume, .destory|cleanup functions:

define internal fastcc void @_Z3fooi.resume(ptr noundef nonnull align 8 dereferenceable(32) %0)  !dbg !1779 {
  entry.resume:
    %.debug = alloca ptr, align 8
    call void @llvm.dbg.declare(metadata ptr %.debug, metadata !1782, metadata !DIExpression(DW_OP_deref, DW_OP_plus_uconst, 24)), !dbg !1784
    call void @llvm.dbg.declare(metadata ptr %.debug, metadata !1785, metadata !DIExpression(DW_OP_deref, DW_OP_plus_uconst, 16)), !dbg !1786
    call void @llvm.dbg.declare(metadata ptr %.debug, metadata !1787, metadata !DIExpression(DW_OP_deref, DW_OP_plus_uconst, 20)), !dbg !1786
    call void @llvm.dbg.declare(metadata ptr %.debug, metadata !1781, metadata !DIExpression(DW_OP_deref)), !dbg !1786
.... ....
!1779 = distinct !DISubprogram(name: "foo", linkageName: "_Z3fooi", scope: !19, file: !19, line: 20, type: !1456, scopeLine: 20, flags: DIFlagPrototyped,    spFlags: DISPFlagDefinition, unit: !18, retainedNodes: !1780)
!1783 = distinct !DILexicalBlock(scope: !1779, file: !19, line: 20, column: 19)
!1784 = !DILocation(line: 21, column: 7, scope: !1783)
!1786 = !DILocation(line: 0, scope: !1779)

but we've got lucky because LiveDebugValues assumes everything in the entry block always live. Would probably fix this as well later.

Copy link
Member

@ChuanqiXu9 ChuanqiXu9 left a comment

Choose a reason for hiding this comment

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

LGTM. Thanks.

@apolloww apolloww merged commit 31cf6df into llvm:main Dec 12, 2023
5 of 6 checks passed
@mizvekov
Copy link
Contributor

This caused a regression building one of my projects, clang now generates invalid IR:

mismatched subprogram between llvm.dbg.declare variable and !dbg attachment
  call void @llvm.dbg.declare(metadata ptr %4, metadata !34468, metadata !DIExpression(DW_OP_plus_uconst, 176)), !dbg !34467

I can confirm reverting this fixes the issue.

I am going to revert this on main now, and will work to provide a reduced reproducer shortly.

mizvekov added a commit that referenced this pull request Dec 13, 2023
mizvekov added a commit that referenced this pull request Dec 13, 2023
…declare" (#75282)

Reverts #75104

Original commit causes clang to generate invalid IR:
```
mismatched subprogram between llvm.dbg.declare variable and !dbg attachment
  call void @llvm.dbg.declare(metadata ptr %4, metadata !34468, metadata !DIExpression(DW_OP_plus_uconst, 176)), !dbg !34467
```
@apolloww
Copy link
Contributor Author

Sorry for the breakage. I'll look into this once the repro is available.

@mizvekov
Copy link
Contributor

Reproducer:

clang -cc1 -triple x86_64-pc-windows-msvc -emit-llvm -flto=full -gcodeview -debug-info-kind=constructor -O1 -std=c++20 -Wno-coroutine-missing-unhandled-exception -x c++ test.cpp
namespace std {
template <class = void> struct coroutine_handle {
  static coroutine_handle from_address(void *);
  operator coroutine_handle<>();
  operator bool();
  void *address();
};
template <class _Ret, class...> struct coroutine_traits : _Ret {};
}

struct A {
  bool await_ready() noexcept;
  template <class PromiseType>
  auto await_suspend(std::coroutine_handle<PromiseType>) -> std::coroutine_handle<>;
  void await_resume() noexcept;
};
std::coroutine_handle a;
struct B {
  struct promise_type {
    B get_return_object();
    void return_void();
    A initial_suspend();
    A final_suspend() noexcept;
  };
  ~B() {
    if (auto b = a)
      ;
  }
};
B f() {
  for (;;) {
    A d;
    []() -> B { co_return; }();
    co_await d;
  }
}

Produces:

mismatched subprogram between llvm.dbg.declare variable and !dbg attachment
  call void @llvm.dbg.declare(metadata ptr %3, metadata !88, metadata !DIExpression(DW_OP_plus_uconst, 18)), !dbg !86
label %coro.init
ptr @"?f@@YA?AUB@@XZ"
!88 = !DILocalVariable(name: "b", scope: !89, file: !5, line: 26, type: !6)
!91 = distinct !DISubprogram(name: "~B", linkageName: "??1B@@QEAA@XZ", scope: !36, file: !5, line: 25, type: !61, scopeLine: 25, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !2, declaration: !60, retainedNodes: !92)
!86 = !DILocation(line: 30, column: 7, scope: !33)
!33 = distinct !DISubprogram(name: "f", linkageName: "?f@@YA?AUB@@XZ", scope: !5, file: !5, line: 30, type: !34, scopeLine: 30, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !2, retainedNodes: !64)
fatal error: error in backend: Broken function

zyx-billy added a commit that referenced this pull request May 23, 2024
…level for hoisted DbgDeclare Loc (#92978)

Minor patch following up on
#75402.

The more generalized version of [this
error](#75104 (comment))
is whenever we have a debug variable created in one subprogram scope
inlined into another subprogram scope. So instead of checking
optimization level, it is safer to just check whether the subprogram
scope of the variable matches the subprogram scope of the hoisted
position.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants