[llvm] [BOLT][RISCV] Preserve debug metadata when rewriting call pairs (PR #221116)

Alexander Wilson via llvm-commits llvm-commits at lists.llvm.org
Mon Sep 7 06:01:51 PDT 2026


https://github.com/rdtscp updated https://github.com/llvm/llvm-project/pull/221116

>From b374b3bd6fd360b2e33d2aeb1ca2af7fa12eafa0 Mon Sep 17 00:00:00 2001
From: Alexander Wilson <rdtscp at meta.com>
Date: Thu, 3 Sep 2026 16:12:29 -0700
Subject: [PATCH 1/2] [BOLT][RISCV] Preserve debug metadata when rewriting call
 pairs

FixRISCVCalls rewrites an AUIPC/JALR call pair by replacing the AUIPC with a NOP and reconstructing the call. This discarded the AUIPC source location and BOLT offset annotations used to translate exact DWARF lexical-scope boundaries. A boundary at the pair could consequently move inside a preceding expanded call.

Build the NOP in place to retain its source location and transfer the original AUIPC annotations to the replacement instruction.

Assisted-by: OpenAI Codex
---
 bolt/lib/Passes/FixRISCVCallsPass.cpp        |   2 +
 bolt/lib/Target/RISCV/RISCVMCPlusBuilder.cpp |   8 +-
 bolt/test/RISCV/dwarf-scope-call-pair.s      | 157 +++++++++++++++++++
 3 files changed, 163 insertions(+), 4 deletions(-)
 create mode 100644 bolt/test/RISCV/dwarf-scope-call-pair.s

diff --git a/bolt/lib/Passes/FixRISCVCallsPass.cpp b/bolt/lib/Passes/FixRISCVCallsPass.cpp
index 6b73bd6854c9d..8fb31aef7a6a6 100644
--- a/bolt/lib/Passes/FixRISCVCallsPass.cpp
+++ b/bolt/lib/Passes/FixRISCVCallsPass.cpp
@@ -49,10 +49,12 @@ void FixRISCVCallsPass::runOnFunction(BinaryFunction &BF) {
         auto *Target = MIB->getTargetSymbol(*II);
         assert(Target && "Cannot find call target");
 
+        MCInst OldAUIPC = *II;
         MCInst OldCall = *NextII;
         auto L = BC.scopeLock();
 
         MIB->createNoop(*II);
+        MIB->moveAnnotations(std::move(OldAUIPC), *II);
 
         if (MIB->isTailCall(*NextII))
           MIB->createTailCall(*NextII, Target, Ctx);
diff --git a/bolt/lib/Target/RISCV/RISCVMCPlusBuilder.cpp b/bolt/lib/Target/RISCV/RISCVMCPlusBuilder.cpp
index 349f38cb11b26..b1ce2f0cc2f50 100644
--- a/bolt/lib/Target/RISCV/RISCVMCPlusBuilder.cpp
+++ b/bolt/lib/Target/RISCV/RISCVMCPlusBuilder.cpp
@@ -676,11 +676,11 @@ class RISCVMCPlusBuilder : public MCPlusBuilder {
   }
 
   void createNoop(MCInst &Inst) const override {
+    Inst.setOpcode(RISCV::ADDI);
     Inst.clear();
-    Inst = MCInstBuilder(RISCV::ADDI)
-               .addReg(RISCV::X0)
-               .addReg(RISCV::X0)
-               .addImm(0);
+    Inst.addOperand(MCOperand::createReg(RISCV::X0));
+    Inst.addOperand(MCOperand::createReg(RISCV::X0));
+    Inst.addOperand(MCOperand::createImm(0));
   }
 
   void createShortJmp(InstructionListType &Seq, const MCSymbol *Target,
diff --git a/bolt/test/RISCV/dwarf-scope-call-pair.s b/bolt/test/RISCV/dwarf-scope-call-pair.s
new file mode 100644
index 0000000000000..7bb95c4ad2d2f
--- /dev/null
+++ b/bolt/test/RISCV/dwarf-scope-call-pair.s
@@ -0,0 +1,157 @@
+## Check that rewriting an AUIPC/JALR call pair preserves an input offset used
+## as a DWARF lexical-scope boundary. The first call grows from eight to twelve
+## bytes. The parent scope ends at the AUIPC of the second call, while its child
+## ends at the intervening compressed NOP. Losing the AUIPC offset maps the
+## parent's high_pc inside the preceding rewritten call and makes the child
+## extend beyond its parent.
+
+# REQUIRES: system-linux
+
+# RUN: llvm-mc -triple riscv64 -mattr=+c -filetype obj -o %t.o %s
+# RUN: ld.lld --no-relax --emit-relocs -e foo -o %t %t.o
+# RUN: llvm-bolt --update-debug-sections -o %t.bolt %t
+# RUN: llvm-dwarfdump --verify %t.bolt
+# RUN: llvm-objdump -d --no-show-raw-insn %t.bolt > %t.out
+# RUN: llvm-dwarfdump --debug-info %t.bolt >> %t.out
+# RUN: FileCheck %s < %t.out
+
+# CHECK-LABEL: <foo>:
+# CHECK:      nop
+# CHECK-NEXT: auipc
+# CHECK-NEXT: jalr
+# CHECK-NEXT: nop
+# CHECK-NEXT: [[PARENT_END:[0-9a-f]+]]:{{.*}}nop
+# CHECK-NEXT: auipc
+# CHECK-NEXT: jalr
+# CHECK:      DW_TAG_lexical_block
+# CHECK:      DW_AT_low_pc
+# CHECK-NEXT: DW_AT_high_pc {{.*}}0x{{0*}}[[PARENT_END]])
+
+        .text
+        .option norvc
+        .option norelax
+        .globl  foo
+        .p2align 2
+        .type   foo, at function
+foo:
+.Lfoo_begin:
+        call    callee
+        .option rvc
+.Lchild_end:
+        c.nop
+        .option norvc
+.Lparent_end:
+        call    callee
+        ret
+.Lfoo_end:
+        .size   foo, .-foo
+
+        .skip   (1 << 21)
+
+        .globl  callee
+        .p2align 2
+        .type   callee, at function
+callee:
+        ret
+.Lcallee_end:
+        .size   callee, .-callee
+
+        .section .debug_abbrev,"", at progbits
+        .byte   1                       # Abbrev code
+        .byte   17                      # DW_TAG_compile_unit
+        .byte   1                       # DW_CHILDREN_yes
+        .byte   37                      # DW_AT_producer
+        .byte   8                       # DW_FORM_string
+        .byte   17                      # DW_AT_low_pc
+        .byte   1                       # DW_FORM_addr
+        .byte   18                      # DW_AT_high_pc
+        .byte   6                       # DW_FORM_data4
+        .byte   3                       # DW_AT_name
+        .byte   8                       # DW_FORM_string
+        .byte   16                      # DW_AT_stmt_list
+        .byte   23                      # DW_FORM_sec_offset
+        .byte   0
+        .byte   0
+        .byte   2                       # Abbrev code
+        .byte   46                      # DW_TAG_subprogram
+        .byte   1                       # DW_CHILDREN_yes
+        .byte   3                       # DW_AT_name
+        .byte   8                       # DW_FORM_string
+        .byte   17                      # DW_AT_low_pc
+        .byte   1                       # DW_FORM_addr
+        .byte   18                      # DW_AT_high_pc
+        .byte   6                       # DW_FORM_data4
+        .byte   0
+        .byte   0
+        .byte   3                       # Abbrev code
+        .byte   11                      # DW_TAG_lexical_block
+        .byte   1                       # DW_CHILDREN_yes
+        .byte   17                      # DW_AT_low_pc
+        .byte   1                       # DW_FORM_addr
+        .byte   18                      # DW_AT_high_pc
+        .byte   6                       # DW_FORM_data4
+        .byte   0
+        .byte   0
+        .byte   4                       # Abbrev code
+        .byte   11                      # DW_TAG_lexical_block
+        .byte   0                       # DW_CHILDREN_no
+        .byte   17                      # DW_AT_low_pc
+        .byte   1                       # DW_FORM_addr
+        .byte   18                      # DW_AT_high_pc
+        .byte   6                       # DW_FORM_data4
+        .byte   0
+        .byte   0
+        .byte   0
+
+        .section .debug_info,"", at progbits
+.Lcu_begin:
+        .long   .Lcu_end-.Lcu_version
+.Lcu_version:
+        .short  4                       # DWARF version
+        .long   .debug_abbrev
+        .byte   8                       # Address size
+        .byte   1                       # DW_TAG_compile_unit
+        .asciz  "test producer"
+        .quad   .Lfoo_begin
+        .long   .Lcallee_end-.Lfoo_begin
+        .asciz  "dwarf-scope-call-pair.s"
+        .long   .Lline_table_start
+        .byte   2                       # DW_TAG_subprogram
+        .asciz  "foo"
+        .quad   .Lfoo_begin
+        .long   .Lfoo_end-.Lfoo_begin
+        .byte   3                       # Parent lexical block
+        .quad   .Lfoo_begin
+        .long   .Lparent_end-.Lfoo_begin
+        .byte   4                       # Child lexical block
+        .quad   .Lfoo_begin
+        .long   .Lchild_end-.Lfoo_begin
+        .byte   0                       # End parent children
+        .byte   0                       # End subprogram children
+        .byte   0                       # End CU children
+.Lcu_end:
+
+        .section .debug_line,"", at progbits
+.Lline_table_start:
+        .long   .Lline_table_end-.Lline_version
+.Lline_version:
+        .short  4                       # DWARF version
+        .long   .Lline_prologue_end-.Lline_prologue_start
+.Lline_prologue_start:
+        .byte   1                       # Minimum instruction length
+        .byte   1                       # Maximum operations per instruction
+        .byte   1                       # Default is_stmt
+        .byte   -5                      # Line base
+        .byte   14                      # Line range
+        .byte   13                      # Opcode base
+        .byte   0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1
+        .byte   0                       # Include directory terminator
+        .asciz  "dwarf-scope-call-pair.s"
+        .uleb128 0                      # Directory index
+        .uleb128 0                      # Modification time
+        .uleb128 0                      # File size
+        .byte   0                       # File table terminator
+.Lline_prologue_end:
+.Lline_table_end:
+
+        .section ".note.GNU-stack","", at progbits

>From 187658549201893cf7220a7f953d95d2fbd29258 Mon Sep 17 00:00:00 2001
From: Alexander Wilson <rdtscp at meta.com>
Date: Mon, 7 Sep 2026 06:01:16 -0700
Subject: [PATCH 2/2] [BOLT][RISCV] Exercise line info in call-pair test

Add a DWARF line-number program to dwarf-scope-call-pair.s and verify that the replacement NOP retains the source line associated with the original AUIPC. Keep the callee in a fixed, skipped section so that the calls remain outside JAL range while producing a valid rewritten line table.

Assisted-by: OpenAI Codex
---
 bolt/test/RISCV/dwarf-scope-call-pair.s | 42 ++++++++++++++++++++-----
 1 file changed, 34 insertions(+), 8 deletions(-)

diff --git a/bolt/test/RISCV/dwarf-scope-call-pair.s b/bolt/test/RISCV/dwarf-scope-call-pair.s
index 7bb95c4ad2d2f..a1ab8e2ef40d5 100644
--- a/bolt/test/RISCV/dwarf-scope-call-pair.s
+++ b/bolt/test/RISCV/dwarf-scope-call-pair.s
@@ -3,16 +3,18 @@
 ## bytes. The parent scope ends at the AUIPC of the second call, while its child
 ## ends at the intervening compressed NOP. Losing the AUIPC offset maps the
 ## parent's high_pc inside the preceding rewritten call and makes the child
-## extend beyond its parent.
+## extend beyond its parent. Also check that the replacement NOP retains the
+## source line associated with the original AUIPC.
 
 # REQUIRES: system-linux
 
 # RUN: llvm-mc -triple riscv64 -mattr=+c -filetype obj -o %t.o %s
-# RUN: ld.lld --no-relax --emit-relocs -e foo -o %t %t.o
-# RUN: llvm-bolt --update-debug-sections -o %t.bolt %t
+# RUN: ld.lld --no-relax --emit-relocs --section-start=.text=0x10000 \
+# RUN:   --section-start=.callee=0x400000 -e foo -o %t %t.o
+# RUN: llvm-bolt --update-debug-sections --skip-funcs=callee -o %t.bolt %t
 # RUN: llvm-dwarfdump --verify %t.bolt
 # RUN: llvm-objdump -d --no-show-raw-insn %t.bolt > %t.out
-# RUN: llvm-dwarfdump --debug-info %t.bolt >> %t.out
+# RUN: llvm-dwarfdump --debug-info --debug-line %t.bolt >> %t.out
 # RUN: FileCheck %s < %t.out
 
 # CHECK-LABEL: <foo>:
@@ -26,6 +28,7 @@
 # CHECK:      DW_TAG_lexical_block
 # CHECK:      DW_AT_low_pc
 # CHECK-NEXT: DW_AT_high_pc {{.*}}0x{{0*}}[[PARENT_END]])
+# CHECK:      0x{{0*}}[[PARENT_END]] 30
 
         .text
         .option norvc
@@ -42,13 +45,13 @@ foo:
         .option norvc
 .Lparent_end:
         call    callee
+.Lret:
         ret
 .Lfoo_end:
         .size   foo, .-foo
 
-        .skip   (1 << 21)
-
         .globl  callee
+        .section .callee,"ax", at progbits
         .p2align 2
         .type   callee, at function
 callee:
@@ -113,9 +116,9 @@ callee:
         .byte   1                       # DW_TAG_compile_unit
         .asciz  "test producer"
         .quad   .Lfoo_begin
-        .long   .Lcallee_end-.Lfoo_begin
+        .long   .Lfoo_end-.Lfoo_begin
         .asciz  "dwarf-scope-call-pair.s"
-        .long   .Lline_table_start
+        .long   .Lline_table_start      # DW_AT_stmt_list
         .byte   2                       # DW_TAG_subprogram
         .asciz  "foo"
         .quad   .Lfoo_begin
@@ -152,6 +155,29 @@ callee:
         .uleb128 0                      # File size
         .byte   0                       # File table terminator
 .Lline_prologue_end:
+        .byte   0, 9, 2                 # DW_LNE_set_address
+        .quad   .Lfoo_begin
+        .byte   3                       # DW_LNS_advance_line
+        .sleb128 9                      # Line 10
+        .byte   1                       # DW_LNS_copy
+        .byte   2                       # DW_LNS_advance_pc
+        .uleb128 .Lchild_end-.Lfoo_begin
+        .byte   3                       # DW_LNS_advance_line
+        .sleb128 10                     # Line 20
+        .byte   1                       # DW_LNS_copy
+        .byte   2                       # DW_LNS_advance_pc
+        .uleb128 .Lparent_end-.Lchild_end
+        .byte   3                       # DW_LNS_advance_line
+        .sleb128 10                     # Line 30
+        .byte   1                       # DW_LNS_copy
+        .byte   2                       # DW_LNS_advance_pc
+        .uleb128 .Lret-.Lparent_end
+        .byte   3                       # DW_LNS_advance_line
+        .sleb128 10                     # Line 40
+        .byte   1                       # DW_LNS_copy
+        .byte   2                       # DW_LNS_advance_pc
+        .uleb128 .Lfoo_end-.Lret
+        .byte   0, 1, 1                 # DW_LNE_end_sequence
 .Lline_table_end:
 
         .section ".note.GNU-stack","", at progbits



More information about the llvm-commits mailing list