Llvm.dbg.value left out by IndVarSimplify

During sinkUnusedInvariants step in IndVarSimplify pass, it is possible that llvm.dbg.value is left out after loop invariant value is sunk into exit block. For example, if I debugify this test llvm-project/sink-from-preheader.ll at main · llvm/llvm-project · GitHub

opt -debugify -indvars -indvars-predicate-loops=0 -check-debugify -S llvm/test/Transforms/IndVarSimplify/sink-from-preheader.ll

It produces

; ModuleID = '../llvm-project/llvm/test/Transforms/IndVarSimplify/sink-from-preheader.ll'
source_filename = "../llvm-project/llvm/test/Transforms/IndVarSimplify/sink-from-preheader.ll"
target datalayout = "e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:32:64-f32:32:32-f64:32:64-v64:64:64-v128:128:128-a0:0:64-f80:128:128"
target triple = "i386-apple-darwin10.0"

define i32 @test(i32 %a, i32 %b, i32 %N) !dbg !5 {
entry:
  call void @llvm.dbg.value(metadata i32 %add, metadata !9, metadata !DIExpression()), !dbg !15
  br label %loop, !dbg !16

loop:                                             ; preds = %loop, %entry
  %iv = phi i32 [ 0, %entry ], [ %iv.next, %loop ], !dbg !17
  call void @llvm.dbg.value(metadata i32 %iv, metadata !11, metadata !DIExpression()), !dbg !17
  %iv.next = add nuw nsw i32 %iv, 1, !dbg !18
  call void @llvm.dbg.value(metadata i32 %iv.next, metadata !12, metadata !DIExpression()), !dbg !18
  %cmp = icmp slt i32 %iv.next, %N, !dbg !19
  call void @llvm.dbg.value(metadata i1 %cmp, metadata !13, metadata !DIExpression()), !dbg !19
  br i1 %cmp, label %loop, label %exit, !dbg !20

exit:                                             ; preds = %loop
  %add = add i32 %a, %b, !dbg !15
  ret i32 %add, !dbg !21
}

; Function Attrs: nocallback nofree nosync nounwind readnone speculatable willreturn
declare void @llvm.dbg.value(metadata, metadata, metadata) #0

attributes #0 = { nocallback nofree nosync nounwind readnone speculatable willreturn }

!llvm.dbg.cu = !{!0}
!llvm.debugify = !{!2, !3}
!llvm.module.flags = !{!4}

!0 = distinct !DICompileUnit(language: DW_LANG_C, file: !1, producer: "debugify", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug)
!1 = !DIFile(filename: "../llvm-project/llvm/test/Transforms/IndVarSimplify/sink-from-preheader.ll", directory: "/")
!2 = !{i32 7}
!3 = !{i32 4}
!4 = !{i32 2, !"Debug Info Version", i32 3}
!5 = distinct !DISubprogram(name: "test", linkageName: "test", scope: null, file: !1, line: 1, type: !6, scopeLine: 1, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !8)
!6 = !DISubroutineType(types: !7)
!7 = !{}
!8 = !{!9, !11, !12, !13}
!9 = !DILocalVariable(name: "1", scope: !5, file: !1, line: 1, type: !10)
!10 = !DIBasicType(name: "ty32", size: 32, encoding: DW_ATE_unsigned)
!11 = !DILocalVariable(name: "2", scope: !5, file: !1, line: 3, type: !10)
!12 = !DILocalVariable(name: "3", scope: !5, file: !1, line: 4, type: !10)
!13 = !DILocalVariable(name: "4", scope: !5, file: !1, line: 5, type: !14)
!14 = !DIBasicType(name: "ty8", size: 8, encoding: DW_ATE_unsigned)
!15 = !DILocation(line: 1, column: 1, scope: !5)
!16 = !DILocation(line: 2, column: 1, scope: !5)
!17 = !DILocation(line: 3, column: 1, scope: !5)
!18 = !DILocation(line: 4, column: 1, scope: !5)
!19 = !DILocation(line: 5, column: 1, scope: !5)
!20 = !DILocation(line: 6, column: 1, scope: !5)
!21 = !DILocation(line: 7, column: 1, scope: !5)

Even though -check-debugify is OK with the result, I find it strange that the llvm.dbg.value in entry block associates the source variable with a not-yet-created value. I think it should set to “undef” and another llvm.dbg.value should be created (a clone of the first one) after %add in exit block to reflect the new value.

More context:

I am facing a verifier failure in CoroSplit pass trigged by this.

Before IndVarSimplify:

preheader:
  %shr.i.i1076 = lshr i64 %316, 24, !dbg !84650
  %or.i.i1077 = or i64 %shr.i.i1076, 128, !dbg !84650
  call void @llvm.dbg.value(metadata i64 %or.i.i1077, metadata !84646, metadata !DIExpression()), !dbg !84647
  ... ...
  
loop:
 ... ...
 
exit:
 ... ...

After IndVarSimplify:

preheader:
  %shr.i.i1076 = lshr i64 %316, 24, !dbg !84650
  call void @llvm.dbg.value(metadata i64 %or.i.i1077, metadata !84646, metadata !DIExpression()), !dbg !84647
  ... ...
  
loop:
 ... ...
 
exit:
  %or.i.i1077 = or i64 %shr.i.i1076, 128, !dbg !84650
  ... ...

After coro::buildCoroutineFrame and spilling materializable instructions across suspension points

preheader:
... ...
  %or.i.i10772788 = or i64 %shr.i.i1076, 128, !dbg !88824
  %shr.i.i1076 = lshr i64 %272, 24, !dbg !88803
  call void @llvm.dbg.value(metadata i64 %or.i.i10772788, metadata !84646, metadata !DIExpression()), !dbg !84647
  ... ...
  
exit:
  %or.i.i1077 = or i64 %shr.i.i1076, 128, !dbg !84650
  ... ...

%or.i.i1077 is sunk into exit block but its llvm.dbg.value is left out. When building coro frame, materializable instructions are reconstructed. %or.i.i1077 is reconstructed at the beginning of the block containing its user llvm.dbg.value: llvm-project/CoroFrame.cpp at main · llvm/llvm-project · GitHub. This triggers an assert

Instruction does not dominate all uses!
  %shr.i.i1076 = lshr i64 %272, 24, !dbg !88803
  %or.i.i10772788 = or i64 %shr.i.i1076, 128, !dbg !88824

Even though I think the spilling code can be improved in this case, IndVarSimplify is where things go wrong.

I’d say this sounds like a bug you should file, but it looks like you already have with llvm.dbg.value left out by IndVarSimplify · Issue #55276 · llvm/llvm-project · GitHub

Sorry for double dipping. I figured this is likely a bug so posted in GitHub later. Somehow I can’t delete this post.