llvm / llvm-project

The LLVM Project is a collection of modular and reusable compiler and toolchain technologies.
http://llvm.org
Other
28.79k stars 11.9k forks source link

[GVNSink] GVNSink fails to optimize LLVM IR produced by clang with -g flag #77415

Closed Apochens closed 9 months ago

Apochens commented 9 months ago

Hi, I am using opt to perform GVNSink optimization on my code. However, I found that the GVNSink pass failed to optimize my code when I gave it the LLVM IR with debug info. If I give it the LLVM IR without debug info, it performs well. I thought this may be a bug in GVNSink, so I file this issue. After trying to find why this happened, I found the root cause.

Here is the source code:

int fun(int a, int b) {
    int ret;
    if (b > 10) {
        int a1 = a + 1;
        int a2 = a1 ^ 1;
        ret = a2;
    } else {
        int b1 = b + 1;
        int b2 = b1 ^ 1;
        ret = b2;
    }
    return ret;
}

The clang and opt used are the latest version built from the source code:

hs@amdsu:~/llvm-test$ clang --version
clang version 18.0.0git (https://github.com/llvm/llvm-project.git 5dfcb3e5d1d16bb4f8fce52b3c089119ed977e7f)
Target: x86_64-unknown-linux-gnu
Thread model: posix
InstalledDir: /home/hs/llvm/usr/local/bin

hs@amdsu:~/llvm-test$ opt --version
LLVM (http://llvm.org/):
  LLVM version 18.0.0git
  Optimized build with assertions.
  Default target: x86_64-unknown-linux-gnu
  Host CPU: znver2

First, GVNSink is performed on LLVM IR without debug info

$ clang -S -emit-llvm -Xclang -disable-O0-optnone main.c -o main.ll
$ opt -S -passes=mem2reg,gvn-sink main.ll -o main-sink.ll

Then we have the following LLVM IR:

; ModuleID = 'main.ll'
source_filename = "main.c"
target datalayout = "e-m:e-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-linux-gnu"

; Function Attrs: noinline nounwind uwtable
define dso_local i32 @fun(i32 noundef %a, i32 noundef %b) #0 {
entry:
  %cmp = icmp sgt i32 %b, 10
  br i1 %cmp, label %if.then, label %if.else

if.then:                                          ; preds = %entry
  br label %if.end

if.else:                                          ; preds = %entry
  br label %if.end

if.end:                                           ; preds = %if.else, %if.then
  %a.sink = phi i32 [ %a, %if.then ], [ %b, %if.else ]
  %add = add nsw i32 %a.sink, 1
  %xor = xor i32 %add, 1
  ret i32 %xor
}

attributes #0 = { noinline nounwind uwtable "frame-pointer"="all" "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }

!llvm.module.flags = !{!0, !1, !2, !3, !4}
!llvm.ident = !{!5}

!0 = !{i32 1, !"wchar_size", i32 4}
!1 = !{i32 8, !"PIC Level", i32 2}
!2 = !{i32 7, !"PIE Level", i32 2}
!3 = !{i32 7, !"uwtable", i32 2}
!4 = !{i32 7, !"frame-pointer", i32 2}
!5 = !{!"clang version 18.0.0git (https://github.com/llvm/llvm-project.git 5dfcb3e5d1d16bb4f8fce52b3c089119ed977e7f)"}

Then, we perform GVNSink on LLVM IR with debug info:

$ clang -S -emit-llvm -g -Xclang -disable-O0-optnone main.c -o main-dbg.ll
$ opt -S -passes=mem2reg,gvn-sink main-dbg.ll -o main-dbg-sink.ll

We will get the following IR:

; ModuleID = 'main-dbg.ll'
source_filename = "main.c"
target datalayout = "e-m:e-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-linux-gnu"

; Function Attrs: noinline nounwind uwtable
define dso_local i32 @fun(i32 noundef %a, i32 noundef %b) #0 !dbg !10 {
entry:
  tail call void @llvm.dbg.value(metadata i32 %a, metadata !15, metadata !DIExpression()), !dbg !16
  tail call void @llvm.dbg.value(metadata i32 %b, metadata !17, metadata !DIExpression()), !dbg !16
  %cmp = icmp sgt i32 %b, 10, !dbg !18
  br i1 %cmp, label %if.then, label %if.else, !dbg !20

if.then:                                          ; preds = %entry
  %add = add nsw i32 %a, 1, !dbg !21
  tail call void @llvm.dbg.value(metadata i32 %add, metadata !23, metadata !DIExpression()), !dbg !24
  %xor = xor i32 %add, 1, !dbg !25
  tail call void @llvm.dbg.value(metadata i32 %xor, metadata !26, metadata !DIExpression()), !dbg !24
  tail call void @llvm.dbg.value(metadata i32 %xor, metadata !27, metadata !DIExpression()), !dbg !16
  br label %if.end, !dbg !28

if.else:                                          ; preds = %entry
  %add1 = add nsw i32 %b, 1, !dbg !29
  tail call void @llvm.dbg.value(metadata i32 %add1, metadata !31, metadata !DIExpression()), !dbg !32
  %xor2 = xor i32 %add1, 1, !dbg !33
  tail call void @llvm.dbg.value(metadata i32 %xor2, metadata !34, metadata !DIExpression()), !dbg !32
  tail call void @llvm.dbg.value(metadata i32 %xor2, metadata !27, metadata !DIExpression()), !dbg !16
  br label %if.end

if.end:                                           ; preds = %if.else, %if.then
  %ret.0 = phi i32 [ %xor, %if.then ], [ %xor2, %if.else ], !dbg !35
  tail call void @llvm.dbg.value(metadata i32 %ret.0, metadata !27, metadata !DIExpression()), !dbg !16
  ret i32 %ret.0, !dbg !36
}

; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none)
declare void @llvm.dbg.declare(metadata, metadata, metadata) #1

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

attributes #0 = { noinline nounwind uwtable "frame-pointer"="all" "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
attributes #1 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) }

!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 18.0.0git (https://github.com/llvm/llvm-project.git 5dfcb3e5d1d16bb4f8fce52b3c089119ed977e7f)", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None)
!1 = !DIFile(filename: "main.c", directory: "/home/hs/llvm-test", checksumkind: CSK_MD5, checksum: "68c28c3d0877bed08ff43db70c573802")
!2 = !{i32 7, !"Dwarf Version", i32 5}
!3 = !{i32 2, !"Debug Info Version", i32 3}
!4 = !{i32 1, !"wchar_size", i32 4}
!5 = !{i32 8, !"PIC Level", i32 2}
!6 = !{i32 7, !"PIE Level", i32 2}
!7 = !{i32 7, !"uwtable", i32 2}
!8 = !{i32 7, !"frame-pointer", i32 2}
!9 = !{!"clang version 18.0.0git (https://github.com/llvm/llvm-project.git 5dfcb3e5d1d16bb4f8fce52b3c089119ed977e7f)"}
!10 = distinct !DISubprogram(name: "fun", scope: !1, file: !1, line: 1, type: !11, scopeLine: 1, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !14)
!11 = !DISubroutineType(types: !12)
!12 = !{!13, !13, !13}
!13 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
!14 = !{}
!15 = !DILocalVariable(name: "a", arg: 1, scope: !10, file: !1, line: 1, type: !13)
!16 = !DILocation(line: 0, scope: !10)
!17 = !DILocalVariable(name: "b", arg: 2, scope: !10, file: !1, line: 1, type: !13)
!18 = !DILocation(line: 3, column: 11, scope: !19)
!19 = distinct !DILexicalBlock(scope: !10, file: !1, line: 3, column: 9)
!20 = !DILocation(line: 3, column: 9, scope: !10)
!21 = !DILocation(line: 4, column: 20, scope: !22)
!22 = distinct !DILexicalBlock(scope: !19, file: !1, line: 3, column: 17)
!23 = !DILocalVariable(name: "a1", scope: !22, file: !1, line: 4, type: !13)
!24 = !DILocation(line: 0, scope: !22)
!25 = !DILocation(line: 5, column: 21, scope: !22)
!26 = !DILocalVariable(name: "a2", scope: !22, file: !1, line: 5, type: !13)
!27 = !DILocalVariable(name: "ret", scope: !10, file: !1, line: 2, type: !13)
!28 = !DILocation(line: 7, column: 5, scope: !22)
!29 = !DILocation(line: 8, column: 20, scope: !30)
!30 = distinct !DILexicalBlock(scope: !19, file: !1, line: 7, column: 12)
!31 = !DILocalVariable(name: "b1", scope: !30, file: !1, line: 8, type: !13)
!32 = !DILocation(line: 0, scope: !30)
!33 = !DILocation(line: 9, column: 21, scope: !30)
!34 = !DILocalVariable(name: "b2", scope: !30, file: !1, line: 9, type: !13)
!35 = !DILocation(line: 0, scope: !19)
!36 = !DILocation(line: 12, column: 5, scope: !10)

Apparently, in the main-dbg-sink.ll (compiled with -g), the instructions (add and xor) in the if.then block and the if.else block are not sunk to the if.end block.

This is caused by the mishandle of LLVM Debug Intrinsics in GVNSink. When reversely traversing the current basic block's predecessors to find instructions to sink, GVNSink does not exclude LLVM Debug Intrinsics, which cannot be sunk, resulting in an empty candidate vector.

I have a patch for this issue. A pull request will be made later.

llvmbot commented 7 months ago

@llvm/issue-subscribers-debuginfo

Author: Shan Huang (Apochens)

Hi, I am using `opt` to perform GVNSink optimization on my code. However, I found that the GVNSink pass failed to optimize my code when I gave it the LLVM IR with debug info. If I give it the LLVM IR without debug info, it performs well. I thought this may be a bug in GVNSink, so I file this issue. After trying to find why this happened, I found the root cause. Here is the source code: ```c int fun(int a, int b) { int ret; if (b > 10) { int a1 = a + 1; int a2 = a1 ^ 1; ret = a2; } else { int b1 = b + 1; int b2 = b1 ^ 1; ret = b2; } return ret; } ``` The clang and opt used are the latest version built from the source code: ```bash hs@amdsu:~/llvm-test$ clang --version clang version 18.0.0git (https://github.com/llvm/llvm-project.git 5dfcb3e5d1d16bb4f8fce52b3c089119ed977e7f) Target: x86_64-unknown-linux-gnu Thread model: posix InstalledDir: /home/hs/llvm/usr/local/bin hs@amdsu:~/llvm-test$ opt --version LLVM (http://llvm.org/): LLVM version 18.0.0git Optimized build with assertions. Default target: x86_64-unknown-linux-gnu Host CPU: znver2 ``` First, GVNSink is performed on LLVM IR without debug info ```bash $ clang -S -emit-llvm -Xclang -disable-O0-optnone main.c -o main.ll $ opt -S -passes=mem2reg,gvn-sink main.ll -o main-sink.ll ``` Then we have the following LLVM IR: ```llvm ir ; ModuleID = 'main.ll' source_filename = "main.c" target datalayout = "e-m:e-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-linux-gnu" ; Function Attrs: noinline nounwind uwtable define dso_local i32 @fun(i32 noundef %a, i32 noundef %b) #0 { entry: %cmp = icmp sgt i32 %b, 10 br i1 %cmp, label %if.then, label %if.else if.then: ; preds = %entry br label %if.end if.else: ; preds = %entry br label %if.end if.end: ; preds = %if.else, %if.then %a.sink = phi i32 [ %a, %if.then ], [ %b, %if.else ] %add = add nsw i32 %a.sink, 1 %xor = xor i32 %add, 1 ret i32 %xor } attributes #0 = { noinline nounwind uwtable "frame-pointer"="all" "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } !llvm.module.flags = !{!0, !1, !2, !3, !4} !llvm.ident = !{!5} !0 = !{i32 1, !"wchar_size", i32 4} !1 = !{i32 8, !"PIC Level", i32 2} !2 = !{i32 7, !"PIE Level", i32 2} !3 = !{i32 7, !"uwtable", i32 2} !4 = !{i32 7, !"frame-pointer", i32 2} !5 = !{!"clang version 18.0.0git (https://github.com/llvm/llvm-project.git 5dfcb3e5d1d16bb4f8fce52b3c089119ed977e7f)"} ``` Then, we perform GVNSink on LLVM IR with debug info: ```bash $ clang -S -emit-llvm -g -Xclang -disable-O0-optnone main.c -o main-dbg.ll $ opt -S -passes=mem2reg,gvn-sink main-dbg.ll -o main-dbg-sink.ll ``` We will get the following IR: ```LLVM IR ; ModuleID = 'main-dbg.ll' source_filename = "main.c" target datalayout = "e-m:e-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-linux-gnu" ; Function Attrs: noinline nounwind uwtable define dso_local i32 @fun(i32 noundef %a, i32 noundef %b) #0 !dbg !10 { entry: tail call void @llvm.dbg.value(metadata i32 %a, metadata !15, metadata !DIExpression()), !dbg !16 tail call void @llvm.dbg.value(metadata i32 %b, metadata !17, metadata !DIExpression()), !dbg !16 %cmp = icmp sgt i32 %b, 10, !dbg !18 br i1 %cmp, label %if.then, label %if.else, !dbg !20 if.then: ; preds = %entry %add = add nsw i32 %a, 1, !dbg !21 tail call void @llvm.dbg.value(metadata i32 %add, metadata !23, metadata !DIExpression()), !dbg !24 %xor = xor i32 %add, 1, !dbg !25 tail call void @llvm.dbg.value(metadata i32 %xor, metadata !26, metadata !DIExpression()), !dbg !24 tail call void @llvm.dbg.value(metadata i32 %xor, metadata !27, metadata !DIExpression()), !dbg !16 br label %if.end, !dbg !28 if.else: ; preds = %entry %add1 = add nsw i32 %b, 1, !dbg !29 tail call void @llvm.dbg.value(metadata i32 %add1, metadata !31, metadata !DIExpression()), !dbg !32 %xor2 = xor i32 %add1, 1, !dbg !33 tail call void @llvm.dbg.value(metadata i32 %xor2, metadata !34, metadata !DIExpression()), !dbg !32 tail call void @llvm.dbg.value(metadata i32 %xor2, metadata !27, metadata !DIExpression()), !dbg !16 br label %if.end if.end: ; preds = %if.else, %if.then %ret.0 = phi i32 [ %xor, %if.then ], [ %xor2, %if.else ], !dbg !35 tail call void @llvm.dbg.value(metadata i32 %ret.0, metadata !27, metadata !DIExpression()), !dbg !16 ret i32 %ret.0, !dbg !36 } ; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none) declare void @llvm.dbg.declare(metadata, metadata, metadata) #1 ; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none) declare void @llvm.dbg.value(metadata, metadata, metadata) #1 attributes #0 = { noinline nounwind uwtable "frame-pointer"="all" "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } attributes #1 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) } !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 18.0.0git (https://github.com/llvm/llvm-project.git 5dfcb3e5d1d16bb4f8fce52b3c089119ed977e7f)", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None) !1 = !DIFile(filename: "main.c", directory: "/home/hs/llvm-test", checksumkind: CSK_MD5, checksum: "68c28c3d0877bed08ff43db70c573802") !2 = !{i32 7, !"Dwarf Version", i32 5} !3 = !{i32 2, !"Debug Info Version", i32 3} !4 = !{i32 1, !"wchar_size", i32 4} !5 = !{i32 8, !"PIC Level", i32 2} !6 = !{i32 7, !"PIE Level", i32 2} !7 = !{i32 7, !"uwtable", i32 2} !8 = !{i32 7, !"frame-pointer", i32 2} !9 = !{!"clang version 18.0.0git (https://github.com/llvm/llvm-project.git 5dfcb3e5d1d16bb4f8fce52b3c089119ed977e7f)"} !10 = distinct !DISubprogram(name: "fun", scope: !1, file: !1, line: 1, type: !11, scopeLine: 1, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !14) !11 = !DISubroutineType(types: !12) !12 = !{!13, !13, !13} !13 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) !14 = !{} !15 = !DILocalVariable(name: "a", arg: 1, scope: !10, file: !1, line: 1, type: !13) !16 = !DILocation(line: 0, scope: !10) !17 = !DILocalVariable(name: "b", arg: 2, scope: !10, file: !1, line: 1, type: !13) !18 = !DILocation(line: 3, column: 11, scope: !19) !19 = distinct !DILexicalBlock(scope: !10, file: !1, line: 3, column: 9) !20 = !DILocation(line: 3, column: 9, scope: !10) !21 = !DILocation(line: 4, column: 20, scope: !22) !22 = distinct !DILexicalBlock(scope: !19, file: !1, line: 3, column: 17) !23 = !DILocalVariable(name: "a1", scope: !22, file: !1, line: 4, type: !13) !24 = !DILocation(line: 0, scope: !22) !25 = !DILocation(line: 5, column: 21, scope: !22) !26 = !DILocalVariable(name: "a2", scope: !22, file: !1, line: 5, type: !13) !27 = !DILocalVariable(name: "ret", scope: !10, file: !1, line: 2, type: !13) !28 = !DILocation(line: 7, column: 5, scope: !22) !29 = !DILocation(line: 8, column: 20, scope: !30) !30 = distinct !DILexicalBlock(scope: !19, file: !1, line: 7, column: 12) !31 = !DILocalVariable(name: "b1", scope: !30, file: !1, line: 8, type: !13) !32 = !DILocation(line: 0, scope: !30) !33 = !DILocation(line: 9, column: 21, scope: !30) !34 = !DILocalVariable(name: "b2", scope: !30, file: !1, line: 9, type: !13) !35 = !DILocation(line: 0, scope: !19) !36 = !DILocation(line: 12, column: 5, scope: !10) ``` Apparently, in the main-dbg-sink.ll (compiled with `-g`), the instructions (`add` and `xor`) in the if.then block and the if.else block are not sunk to the if.end block. This is caused by the mishandle of LLVM Debug Intrinsics in GVNSink. When reversely traversing the current basic block's predecessors to find instructions to sink, GVNSink does not exclude LLVM Debug Intrinsics, which cannot be sunk, resulting in an empty candidate vector. I have a patch for this issue. A pull request will be made later.