[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