[llvm] [BOLT][RISCV] Preserve debug metadata when rewriting call pairs (PR #221116)
Alexander Wilson via llvm-commits
llvm-commits at lists.llvm.org
Wed Sep 9 18:02:43 PDT 2026
https://github.com/rdtscp updated https://github.com/llvm/llvm-project/pull/221116
>From 6010395f3e261f2f5f4abfdd0eab829440a6bb07 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/6] [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 27351a5945c52..e4f6095d2a571 100644
--- a/bolt/lib/Passes/FixRISCVCallsPass.cpp
+++ b/bolt/lib/Passes/FixRISCVCallsPass.cpp
@@ -51,10 +51,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);
// Mark the replacement NOP for removal by the later RemoveNops pass.
MIB->addAnnotation(*II, "NOP", static_cast<uint32_t>(1));
diff --git a/bolt/lib/Target/RISCV/RISCVMCPlusBuilder.cpp b/bolt/lib/Target/RISCV/RISCVMCPlusBuilder.cpp
index a9cd6d98c4a03..05e8ccd40a772 100644
--- a/bolt/lib/Target/RISCV/RISCVMCPlusBuilder.cpp
+++ b/bolt/lib/Target/RISCV/RISCVMCPlusBuilder.cpp
@@ -735,11 +735,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 c2efc348a2090c80ca9173f0c571ab6690516262 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/6] [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
>From f4411c62c2de98c44c30415afa6c2083901a5b21 Mon Sep 17 00:00:00 2001
From: Alexander Wilson <rdtscp at meta.com>
Date: Tue, 8 Sep 2026 05:19:07 -0700
Subject: [PATCH 3/6] [BOLT][RISCV] Preserve call-pair offsets without NOPs
Preserve the removed AUIPC source location on the reconstructed call and record its input offset as an alias of the call's existing JALR offset. Emit both mappings at the generated call address so DWARF scope boundaries remain exact after redundant NOP removal and call relaxation.
Update the DWARF regression test to cover a shrinking near call followed by an out-of-range call, and verify both scope and line-table addresses.
Assisted-by: Codex
---
bolt/include/bolt/Core/MCPlus.h | 1 +
bolt/include/bolt/Core/MCPlusBuilder.h | 6 +++
bolt/lib/Core/BinaryEmitter.cpp | 11 +++--
bolt/lib/Core/MCPlusBuilder.cpp | 14 ++++++
bolt/lib/Passes/FixRISCVCallsPass.cpp | 8 +++-
bolt/lib/Target/RISCV/RISCVMCPlusBuilder.cpp | 8 ++--
bolt/test/RISCV/dwarf-scope-call-pair.s | 48 +++++++++++---------
7 files changed, 65 insertions(+), 31 deletions(-)
diff --git a/bolt/include/bolt/Core/MCPlus.h b/bolt/include/bolt/Core/MCPlus.h
index e97c31049f48d..3f2fcba6d853e 100644
--- a/bolt/include/bolt/Core/MCPlus.h
+++ b/bolt/include/bolt/Core/MCPlus.h
@@ -69,6 +69,7 @@ class MCAnnotation {
kTailCall, /// Tail call.
kConditionalTailCall, /// CTC.
kOffset, /// Offset in the function.
+ kOffsetAlias, /// Additional offset mapped to this instruction.
kLabel, /// MCSymbol pointing to this instruction.
kSize, /// Size of the instruction.
kDynamicBranch, /// Jit instruction patched at runtime.
diff --git a/bolt/include/bolt/Core/MCPlusBuilder.h b/bolt/include/bolt/Core/MCPlusBuilder.h
index e55db33834ee8..73034cae77dbc 100644
--- a/bolt/include/bolt/Core/MCPlusBuilder.h
+++ b/bolt/include/bolt/Core/MCPlusBuilder.h
@@ -1460,6 +1460,12 @@ class MCPlusBuilder {
/// Set offset of \p Inst in the original function.
bool setOffset(MCInst &Inst, uint32_t Offset) const;
+ /// Return an additional original function offset associated with \p Inst.
+ std::optional<uint32_t> getOffsetAlias(const MCInst &Inst) const;
+
+ /// Associate an additional original function offset with \p Inst.
+ bool setOffsetAlias(MCInst &Inst, uint32_t Offset) const;
+
/// Remove offset annotation.
bool clearOffset(MCInst &Inst) const;
diff --git a/bolt/lib/Core/BinaryEmitter.cpp b/bolt/lib/Core/BinaryEmitter.cpp
index 29dbcab7945ec..d373aea6f1432 100644
--- a/bolt/lib/Core/BinaryEmitter.cpp
+++ b/bolt/lib/Core/BinaryEmitter.cpp
@@ -484,11 +484,16 @@ void BinaryEmitter::emitFunctionBody(BinaryFunction &BF, FunctionFragment &FF,
// an instruction's output address to augment the IO address map (BAT,
// SDT/probe address translation, or --update-debug-sections DWARF range
// updates).
- if (BF.requiresPreciseAddressMap() && BC.MIB->getOffset(Instr)) {
- const uint32_t Offset = *BC.MIB->getOffset(Instr);
+ const std::optional<uint32_t> Offset = BC.MIB->getOffset(Instr);
+ const std::optional<uint32_t> OffsetAlias =
+ BC.MIB->getOffsetAlias(Instr);
+ if (BF.requiresPreciseAddressMap() && (Offset || OffsetAlias)) {
if (!InstrLabel)
InstrLabel = BC.Ctx->createTempSymbol();
- BB->getLocSyms().emplace_back(Offset, InstrLabel);
+ if (Offset)
+ BB->getLocSyms().emplace_back(*Offset, InstrLabel);
+ if (OffsetAlias && OffsetAlias != Offset)
+ BB->getLocSyms().emplace_back(*OffsetAlias, InstrLabel);
}
if (InstrLabel)
diff --git a/bolt/lib/Core/MCPlusBuilder.cpp b/bolt/lib/Core/MCPlusBuilder.cpp
index 1bcae94214909..c036ea303f449 100644
--- a/bolt/lib/Core/MCPlusBuilder.cpp
+++ b/bolt/lib/Core/MCPlusBuilder.cpp
@@ -329,6 +329,20 @@ bool MCPlusBuilder::setOffset(MCInst &Inst, uint32_t Offset) const {
return true;
}
+std::optional<uint32_t>
+MCPlusBuilder::getOffsetAlias(const MCInst &Inst) const {
+ std::optional<int64_t> Value =
+ getAnnotationOpValue(Inst, MCAnnotation::kOffsetAlias);
+ if (!Value)
+ return std::nullopt;
+ return static_cast<uint32_t>(*Value);
+}
+
+bool MCPlusBuilder::setOffsetAlias(MCInst &Inst, uint32_t Offset) const {
+ setAnnotationOpValue(Inst, MCAnnotation::kOffsetAlias, Offset);
+ return true;
+}
+
bool MCPlusBuilder::clearOffset(MCInst &Inst) const {
if (!hasAnnotation(Inst, MCAnnotation::kOffset))
return false;
diff --git a/bolt/lib/Passes/FixRISCVCallsPass.cpp b/bolt/lib/Passes/FixRISCVCallsPass.cpp
index e4f6095d2a571..08710b86524a1 100644
--- a/bolt/lib/Passes/FixRISCVCallsPass.cpp
+++ b/bolt/lib/Passes/FixRISCVCallsPass.cpp
@@ -51,12 +51,12 @@ void FixRISCVCallsPass::runOnFunction(BinaryFunction &BF) {
auto *Target = MIB->getTargetSymbol(*II);
assert(Target && "Cannot find call target");
- MCInst OldAUIPC = *II;
+ const SMLoc AUIPCLoc = II->getLoc();
+ const std::optional<uint32_t> AUIPCOffset = MIB->getOffset(*II);
MCInst OldCall = *NextII;
auto L = BC.scopeLock();
MIB->createNoop(*II);
- MIB->moveAnnotations(std::move(OldAUIPC), *II);
// Mark the replacement NOP for removal by the later RemoveNops pass.
MIB->addAnnotation(*II, "NOP", static_cast<uint32_t>(1));
@@ -68,6 +68,10 @@ void FixRISCVCallsPass::runOnFunction(BinaryFunction &BF) {
// Discard annotations added by the builder before moving the originals.
MIB->stripAnnotations(*NextII);
MIB->moveAnnotations(std::move(OldCall), *NextII);
+ if (AUIPCLoc.isValid())
+ NextII->setLoc(AUIPCLoc);
+ if (AUIPCOffset)
+ MIB->setOffsetAlias(*NextII, *AUIPCOffset);
II = std::next(NextII);
continue;
diff --git a/bolt/lib/Target/RISCV/RISCVMCPlusBuilder.cpp b/bolt/lib/Target/RISCV/RISCVMCPlusBuilder.cpp
index 05e8ccd40a772..a9cd6d98c4a03 100644
--- a/bolt/lib/Target/RISCV/RISCVMCPlusBuilder.cpp
+++ b/bolt/lib/Target/RISCV/RISCVMCPlusBuilder.cpp
@@ -735,11 +735,11 @@ class RISCVMCPlusBuilder : public MCPlusBuilder {
}
void createNoop(MCInst &Inst) const override {
- Inst.setOpcode(RISCV::ADDI);
Inst.clear();
- Inst.addOperand(MCOperand::createReg(RISCV::X0));
- Inst.addOperand(MCOperand::createReg(RISCV::X0));
- Inst.addOperand(MCOperand::createImm(0));
+ Inst = MCInstBuilder(RISCV::ADDI)
+ .addReg(RISCV::X0)
+ .addReg(RISCV::X0)
+ .addImm(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
index a1ab8e2ef40d5..00a2a0c504419 100644
--- a/bolt/test/RISCV/dwarf-scope-call-pair.s
+++ b/bolt/test/RISCV/dwarf-scope-call-pair.s
@@ -1,30 +1,28 @@
-## 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. Also check that the replacement NOP retains the
-## source line associated with the original AUIPC.
+## Check that removing the redundant AUIPC from a rewritten call pair preserves
+## its input offset and source location on the reconstructed call. The first
+## call shrinks from an AUIPC/JALR pair to a JAL. The parent scope ends at the
+## AUIPC of the second, out-of-range call, while its child ends at the
+## intervening compressed NOP. Losing the second AUIPC offset maps the parent's
+## high_pc inside that call instead of at its beginning.
# REQUIRES: system-linux
# RUN: llvm-mc -triple riscv64 -mattr=+c -filetype obj -o %t.o %s
# 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: --section-start=.far=0x400000 -e foo -o %t %t.o
+# RUN: llvm-bolt --update-debug-sections --skip-funcs=far_callee \
+# RUN: -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 --debug-line %t.bolt >> %t.out
# RUN: FileCheck %s < %t.out
# CHECK-LABEL: <foo>:
-# CHECK: nop
-# CHECK-NEXT: auipc
-# CHECK-NEXT: jalr
+# CHECK-NEXT: {{.*}}jal {{.*}} <near_callee>
# CHECK-NEXT: nop
-# CHECK-NEXT: [[PARENT_END:[0-9a-f]+]]:{{.*}}nop
-# CHECK-NEXT: auipc
+# CHECK-NEXT: [[PARENT_END:[0-9a-f]+]]:{{.*}}auipc
# CHECK-NEXT: jalr
+# CHECK-NEXT: ret
# CHECK: DW_TAG_lexical_block
# CHECK: DW_AT_low_pc
# CHECK-NEXT: DW_AT_high_pc {{.*}}0x{{0*}}[[PARENT_END]])
@@ -38,26 +36,32 @@
.type foo, at function
foo:
.Lfoo_begin:
- call callee
+ call near_callee
.option rvc
.Lchild_end:
c.nop
.option norvc
.Lparent_end:
- call callee
+ call far_callee
.Lret:
ret
.Lfoo_end:
.size foo, .-foo
- .globl callee
- .section .callee,"ax", at progbits
+ .globl near_callee
+ .p2align 2
+ .type near_callee, at function
+near_callee:
+ ret
+ .size near_callee, .-near_callee
+
+ .globl far_callee
+ .section .far,"ax", at progbits
.p2align 2
- .type callee, at function
-callee:
+ .type far_callee, at function
+far_callee:
ret
-.Lcallee_end:
- .size callee, .-callee
+ .size far_callee, .-far_callee
.section .debug_abbrev,"", at progbits
.byte 1 # Abbrev code
>From dfb0149128799b61934fa8708ee7f712e8edf6e8 Mon Sep 17 00:00:00 2001
From: Alexander Wilson <rdtscp at meta.com>
Date: Wed, 9 Sep 2026 07:20:14 -0700
Subject: [PATCH 4/6] [BOLT][RISCV] Reproduce AUIPC scope errors and preserve
BAT mappings
Exercise parent/child range containment at AUIPC and JALR boundaries after an earlier call shrinks. Check exact endpoints and source lines with default NOP removal, retained NOPs, and non-shrinking calls.
Emit input-offset aliases before the primary mapping so BAT reverse lookups continue to select the original JALR. Keep alias lookups inside the precise-address-map guard.
Test plan: the unfixed control reports two containment errors; the new regression and 33 selected x86 range/address-map tests pass. All five failures in the available RISC-V suite also reproduce on the unfixed control. Changed-line clang-format and git diff --check pass.
Assisted-by: OpenAI Codex
---
bolt/lib/Core/BinaryEmitter.cpp | 22 +-
bolt/test/RISCV/dwarf-scope-call-pair.s | 333 +++++++++++++-----------
2 files changed, 191 insertions(+), 164 deletions(-)
diff --git a/bolt/lib/Core/BinaryEmitter.cpp b/bolt/lib/Core/BinaryEmitter.cpp
index d373aea6f1432..08d44e3d4c566 100644
--- a/bolt/lib/Core/BinaryEmitter.cpp
+++ b/bolt/lib/Core/BinaryEmitter.cpp
@@ -484,16 +484,18 @@ void BinaryEmitter::emitFunctionBody(BinaryFunction &BF, FunctionFragment &FF,
// an instruction's output address to augment the IO address map (BAT,
// SDT/probe address translation, or --update-debug-sections DWARF range
// updates).
- const std::optional<uint32_t> Offset = BC.MIB->getOffset(Instr);
- const std::optional<uint32_t> OffsetAlias =
- BC.MIB->getOffsetAlias(Instr);
- if (BF.requiresPreciseAddressMap() && (Offset || OffsetAlias)) {
- if (!InstrLabel)
- InstrLabel = BC.Ctx->createTempSymbol();
- if (Offset)
- BB->getLocSyms().emplace_back(*Offset, InstrLabel);
- if (OffsetAlias && OffsetAlias != Offset)
- BB->getLocSyms().emplace_back(*OffsetAlias, InstrLabel);
+ if (BF.requiresPreciseAddressMap()) {
+ const std::optional<uint32_t> Offset = BC.MIB->getOffset(Instr);
+ const std::optional<uint32_t> OffsetAlias =
+ BC.MIB->getOffsetAlias(Instr);
+ if (Offset || OffsetAlias) {
+ if (!InstrLabel)
+ InstrLabel = BC.Ctx->createTempSymbol();
+ if (OffsetAlias && OffsetAlias != Offset)
+ BB->getLocSyms().emplace_back(*OffsetAlias, InstrLabel);
+ if (Offset)
+ BB->getLocSyms().emplace_back(*Offset, InstrLabel);
+ }
}
if (InstrLabel)
diff --git a/bolt/test/RISCV/dwarf-scope-call-pair.s b/bolt/test/RISCV/dwarf-scope-call-pair.s
index 00a2a0c504419..e33bfb6f85724 100644
--- a/bolt/test/RISCV/dwarf-scope-call-pair.s
+++ b/bolt/test/RISCV/dwarf-scope-call-pair.s
@@ -1,187 +1,212 @@
-## Check that removing the redundant AUIPC from a rewritten call pair preserves
-## its input offset and source location on the reconstructed call. The first
-## call shrinks from an AUIPC/JALR pair to a JAL. The parent scope ends at the
-## AUIPC of the second, out-of-range call, while its child ends at the
-## intervening compressed NOP. Losing the second AUIPC offset maps the parent's
-## high_pc inside that call instead of at its beginning.
+## A shrinking call changes the offsets of later scope boundaries within the
+## same basic block. Both the AUIPC and JALR of a rewritten pair must map to the
+## surviving call. Otherwise the low_pc of the first parent follows its child's
+## low_pc, and the high_pc of the second child exceeds its parent's high_pc.
+## Check exact endpoints too: verification alone misses misplaced boundaries
+## when replacement NOPs are retained or the first call does not shrink.
# REQUIRES: system-linux
-# RUN: llvm-mc -triple riscv64 -mattr=+c -filetype obj -o %t.o %s
+# RUN: llvm-mc -triple riscv64 -dwarf-version=4 -filetype=obj %s -o %t.o
# RUN: ld.lld --no-relax --emit-relocs --section-start=.text=0x10000 \
-# RUN: --section-start=.far=0x400000 -e foo -o %t %t.o
-# RUN: llvm-bolt --update-debug-sections --skip-funcs=far_callee \
-# RUN: -o %t.bolt %t
+# RUN: --section-start=.far=0x400000 -e foo %t.o -o %t
+# RUN: llvm-dwarfdump --verify %t
+# RUN: llvm-bolt %t --update-debug-sections --skip-funcs=far_callee -o %t.bolt
# RUN: llvm-dwarfdump --verify %t.bolt
# RUN: llvm-objdump -d --no-show-raw-insn %t.bolt > %t.out
# RUN: llvm-dwarfdump --debug-info --debug-line %t.bolt >> %t.out
-# RUN: FileCheck %s < %t.out
+# RUN: FileCheck %s --check-prefixes=CHECK,SHORT < %t.out
+
+## BAT reverse lookups use the last entry at a shared output address. Keep the
+## original JALR mapping after the AUIPC alias so branch profiles are unchanged.
+# RUN: llvm-bolt %t --update-debug-sections --skip-funcs=far_callee \
+# RUN: --enable-bat -o %t.bat
+# RUN: llvm-bat-dump %t.bat --dump-all | FileCheck %s --check-prefix=BAT
+
+# BAT: BB mappings:
+# BAT-NEXT: 0x0 -> 0x0 hash:
+# BAT-NEXT: 0x4 -> 0x8 (branch)
+# BAT-NEXT: 0x4 -> 0xc (branch)
+# BAT-NEXT: 0xc -> 0x10 (branch)
+# BAT-NEXT: 0x10 -> 0x14 (branch)
+# BAT-NEXT: 0x10 -> 0x18 (branch)
+# BAT-NEXT: 0x18 -> 0x1c (branch)
+# BAT-NEXT: NumBlocks: 1
+
+# RUN: llvm-bolt %t --update-debug-sections --skip-funcs=far_callee \
+# RUN: --keep-nops -o %t.keep
+# RUN: llvm-dwarfdump --verify %t.keep
+# RUN: llvm-objdump -d --no-show-raw-insn %t.keep > %t.keep.out
+# RUN: llvm-dwarfdump --debug-info --debug-line %t.keep >> %t.keep.out
+# RUN: FileCheck %s --check-prefixes=CHECK,SHORT,KEEP < %t.keep.out
+
+# RUN: llvm-mc -triple riscv64 -dwarf-version=4 -filetype=obj \
+# RUN: --defsym FIRST_FAR=1 %s -o %t.far.o
+# RUN: ld.lld --no-relax --emit-relocs --section-start=.text=0x10000 \
+# RUN: --section-start=.far=0x400000 -e foo %t.far.o -o %t.far
+# RUN: llvm-bolt %t.far --update-debug-sections --skip-funcs=far_callee \
+# RUN: -o %t.far.bolt
+# RUN: llvm-dwarfdump --verify %t.far.bolt
+# RUN: llvm-objdump -d --no-show-raw-insn %t.far.bolt > %t.far.out
+# RUN: llvm-dwarfdump --debug-info --debug-line %t.far.bolt >> %t.far.out
+# RUN: FileCheck %s --check-prefixes=CHECK,LONG < %t.far.out
+
+## An internal call makes BOLT preserve NOPs for this function even without
+## --keep-nops. The AUIPC must not acquire two different output mappings.
+# RUN: llvm-mc -triple riscv64 -dwarf-version=4 -filetype=obj \
+# RUN: --defsym INTERNAL=1 %s -o %t.internal.o
+# RUN: ld.lld --no-relax --emit-relocs --section-start=.text=0x10000 \
+# RUN: --section-start=.far=0x400000 -e foo %t.internal.o -o %t.internal
+# RUN: llvm-bolt %t.internal --update-debug-sections --skip-funcs=far_callee \
+# RUN: -o %t.internal.bolt
+# RUN: llvm-dwarfdump --verify %t.internal.bolt
+# RUN: llvm-objdump -d --no-show-raw-insn %t.internal.bolt > %t.internal.out
+# RUN: llvm-dwarfdump --debug-info --debug-line %t.internal.bolt >> %t.internal.out
+# RUN: FileCheck %s --check-prefixes=CHECK,SHORT,KEEP,INTERNAL < %t.internal.out
# CHECK-LABEL: <foo>:
-# CHECK-NEXT: {{.*}}jal {{.*}} <near_callee>
-# CHECK-NEXT: nop
-# CHECK-NEXT: [[PARENT_END:[0-9a-f]+]]:{{.*}}auipc
+# INTERNAL-NEXT: jal
+# KEEP-NEXT: nop
+# SHORT-NEXT: [[FIRST:[0-9a-f]+]]:{{.*}}jal {{.*}} <near_callee>
+# LONG-NEXT: [[FIRST:[0-9a-f]+]]:{{.*}}auipc
+# LONG-NEXT: jalr
+# KEEP-NEXT: nop
+# CHECK-NOT: nop
+# CHECK: [[BEGIN:[0-9a-f]+]]:{{.*}}auipc
+# CHECK-NEXT: jalr
+# CHECK-NEXT: [[END:[0-9a-f]+]]:{{.*}}addi
+# KEEP-NEXT: nop
+# CHECK-NEXT: [[BOUNDARY:[0-9a-f]+]]:{{.*}}auipc
# CHECK-NEXT: jalr
# CHECK-NEXT: ret
-# 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
+# CHECK: DW_AT_name ("parent_high")
+# CHECK-NEXT: DW_AT_low_pc
+# CHECK-NEXT: DW_AT_high_pc (0x{{0*}}[[BOUNDARY]])
+# CHECK: DW_AT_name ("child_high")
+# CHECK-NEXT: DW_AT_low_pc
+# CHECK-NEXT: DW_AT_high_pc (0x{{0*}}[[BOUNDARY]])
+# CHECK: DW_AT_name ("parent_low")
+# CHECK-NEXT: DW_AT_low_pc (0x{{0*}}[[BEGIN]])
+# CHECK-NEXT: DW_AT_high_pc (0x{{0*}}[[END]])
+# CHECK: DW_AT_name ("child_low")
+# CHECK-NEXT: DW_AT_low_pc (0x{{0*}}[[BEGIN]])
+# CHECK-NEXT: DW_AT_high_pc (0x{{0*}}[[END]])
+# CHECK: 0x{{0*}}[[FIRST]] 10
+# CHECK: 0x{{0*}}[[BEGIN]] 20
+# CHECK: 0x{{0*}}[[END]] 30
+# CHECK: 0x{{0*}}[[BOUNDARY]] 40
.text
.option norvc
.option norelax
- .globl foo
- .p2align 2
- .type foo, at function
+ .file 1 "dwarf-scope-call-pair.s"
+ .globl foo
+ .type foo, at function
foo:
-.Lfoo_begin:
- call near_callee
- .option rvc
-.Lchild_end:
- c.nop
- .option norvc
+ .ifdef INTERNAL
+ jal ra, .Lfirst_call
+ .endif
+.Lfirst_call:
+ .loc 1 10
+ .ifdef FIRST_FAR
+ call far_callee
+ .else
+ call near_callee
+ .endif
+.Lparent_begin:
+ .loc 1 20
+ .reloc ., R_RISCV_CALL_PLT, far_callee
+ auipc ra, 0
+.Lchild_begin:
+ .loc 1 21
+ jalr ra
.Lparent_end:
- call far_callee
-.Lret:
+ .loc 1 30
+ addi a0, a0, 1
+.Lboundary_auipc:
+ .loc 1 40
+ .reloc ., R_RISCV_CALL_PLT, far_callee
+ auipc ra, 0
+.Lboundary_jalr:
+ .loc 1 41
+ jalr ra
+ .loc 1 50
ret
.Lfoo_end:
- .size foo, .-foo
+ .size foo, .-foo
- .globl near_callee
- .p2align 2
- .type near_callee, at function
+ .globl near_callee
+ .type near_callee, at function
near_callee:
ret
- .size near_callee, .-near_callee
+ .size near_callee, .-near_callee
- .globl far_callee
.section .far,"ax", at progbits
- .p2align 2
- .type far_callee, at function
+ .globl far_callee
+ .type far_callee, at function
far_callee:
ret
- .size far_callee, .-far_callee
+ .size far_callee, .-far_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
+ .byte 1, 0x11, 1
+ .byte 0x03, 0x08
+ .byte 0x11, 0x01
+ .byte 0x12, 0x06
+ .byte 0x10, 0x17
+ .byte 0, 0
+
+ .byte 2, 0x2e, 1
+ .byte 0x03, 0x08
+ .byte 0x11, 0x01
+ .byte 0x12, 0x06
+ .byte 0, 0
+
+ .byte 3, 0x0b, 1
+ .byte 0x03, 0x08
+ .byte 0x11, 0x01
+ .byte 0x12, 0x06
+ .byte 0, 0
+
+ .byte 4, 0x0b, 0
+ .byte 0x03, 0x08
+ .byte 0x11, 0x01
+ .byte 0x12, 0x06
+ .byte 0, 0
+ .byte 0
.section .debug_info,"", at progbits
-.Lcu_begin:
- .long .Lcu_end-.Lcu_version
+ .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 .Lfoo_end-.Lfoo_begin
- .asciz "dwarf-scope-call-pair.s"
- .long .Lline_table_start # DW_AT_stmt_list
- .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
+ .short 4
+ .long .debug_abbrev
+ .byte 8
+ .byte 1
+ .asciz "dwarf-scope-call-pair.s"
+ .quad foo
+ .long .Lfoo_end-foo
+ .long 0
+ .byte 2
+ .asciz "foo"
+ .quad foo
+ .long .Lfoo_end-foo
+ .byte 3
+ .asciz "parent_high"
+ .quad foo
+ .long .Lboundary_jalr-foo
+ .byte 3
+ .asciz "child_high"
+ .quad foo
+ .long .Lboundary_auipc-foo
+ .byte 3
+ .asciz "parent_low"
+ .quad .Lparent_begin
+ .long .Lparent_end-.Lparent_begin
+ .byte 4
+ .asciz "child_low"
+ .quad .Lchild_begin
+ .long .Lparent_end-.Lchild_begin
+ .byte 0, 0, 0, 0, 0
.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:
- .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
+ .section .note.GNU-stack,"", at progbits
>From 3b348c17808fe712e3f64e7912ada9294c64c009 Mon Sep 17 00:00:00 2001
From: Alexander Wilson <rdtscp at meta.com>
Date: Wed, 9 Sep 2026 09:01:35 -0700
Subject: [PATCH 5/6] [BOLT][RISCV] Use a generic input-offset alias annotation
Store the original AUIPC offset in a single uint32_t InputOffsetAlias annotation using the existing generic annotation API. Remove the reserved annotation kind and dedicated MCPlusBuilder accessors.
Keep the original JALR offset, duplicate-offset suppression, and alias-before-primary ordering for BAT unchanged.
Test plan: all 54 RISC-V tests and 33 selected x86 range/address-map tests pass. Changed-line clang-format and git diff --check pass. A controlled real-binary comparison has identical DWARF and allocated sections; only .note.bolt_info differs.
Assisted-by: OpenAI Codex
---
bolt/include/bolt/Core/MCPlus.h | 1 -
bolt/include/bolt/Core/MCPlusBuilder.h | 6 ------
bolt/lib/Core/BinaryEmitter.cpp | 6 +++---
bolt/lib/Core/MCPlusBuilder.cpp | 14 --------------
bolt/lib/Passes/FixRISCVCallsPass.cpp | 2 +-
5 files changed, 4 insertions(+), 25 deletions(-)
diff --git a/bolt/include/bolt/Core/MCPlus.h b/bolt/include/bolt/Core/MCPlus.h
index 3f2fcba6d853e..e97c31049f48d 100644
--- a/bolt/include/bolt/Core/MCPlus.h
+++ b/bolt/include/bolt/Core/MCPlus.h
@@ -69,7 +69,6 @@ class MCAnnotation {
kTailCall, /// Tail call.
kConditionalTailCall, /// CTC.
kOffset, /// Offset in the function.
- kOffsetAlias, /// Additional offset mapped to this instruction.
kLabel, /// MCSymbol pointing to this instruction.
kSize, /// Size of the instruction.
kDynamicBranch, /// Jit instruction patched at runtime.
diff --git a/bolt/include/bolt/Core/MCPlusBuilder.h b/bolt/include/bolt/Core/MCPlusBuilder.h
index 73034cae77dbc..e55db33834ee8 100644
--- a/bolt/include/bolt/Core/MCPlusBuilder.h
+++ b/bolt/include/bolt/Core/MCPlusBuilder.h
@@ -1460,12 +1460,6 @@ class MCPlusBuilder {
/// Set offset of \p Inst in the original function.
bool setOffset(MCInst &Inst, uint32_t Offset) const;
- /// Return an additional original function offset associated with \p Inst.
- std::optional<uint32_t> getOffsetAlias(const MCInst &Inst) const;
-
- /// Associate an additional original function offset with \p Inst.
- bool setOffsetAlias(MCInst &Inst, uint32_t Offset) const;
-
/// Remove offset annotation.
bool clearOffset(MCInst &Inst) const;
diff --git a/bolt/lib/Core/BinaryEmitter.cpp b/bolt/lib/Core/BinaryEmitter.cpp
index 08d44e3d4c566..e521af9f54bda 100644
--- a/bolt/lib/Core/BinaryEmitter.cpp
+++ b/bolt/lib/Core/BinaryEmitter.cpp
@@ -486,12 +486,12 @@ void BinaryEmitter::emitFunctionBody(BinaryFunction &BF, FunctionFragment &FF,
// updates).
if (BF.requiresPreciseAddressMap()) {
const std::optional<uint32_t> Offset = BC.MIB->getOffset(Instr);
- const std::optional<uint32_t> OffsetAlias =
- BC.MIB->getOffsetAlias(Instr);
+ const auto OffsetAlias =
+ BC.MIB->tryGetAnnotationAs<uint32_t>(Instr, "InputOffsetAlias");
if (Offset || OffsetAlias) {
if (!InstrLabel)
InstrLabel = BC.Ctx->createTempSymbol();
- if (OffsetAlias && OffsetAlias != Offset)
+ if (OffsetAlias && (!Offset || *OffsetAlias != *Offset))
BB->getLocSyms().emplace_back(*OffsetAlias, InstrLabel);
if (Offset)
BB->getLocSyms().emplace_back(*Offset, InstrLabel);
diff --git a/bolt/lib/Core/MCPlusBuilder.cpp b/bolt/lib/Core/MCPlusBuilder.cpp
index c036ea303f449..1bcae94214909 100644
--- a/bolt/lib/Core/MCPlusBuilder.cpp
+++ b/bolt/lib/Core/MCPlusBuilder.cpp
@@ -329,20 +329,6 @@ bool MCPlusBuilder::setOffset(MCInst &Inst, uint32_t Offset) const {
return true;
}
-std::optional<uint32_t>
-MCPlusBuilder::getOffsetAlias(const MCInst &Inst) const {
- std::optional<int64_t> Value =
- getAnnotationOpValue(Inst, MCAnnotation::kOffsetAlias);
- if (!Value)
- return std::nullopt;
- return static_cast<uint32_t>(*Value);
-}
-
-bool MCPlusBuilder::setOffsetAlias(MCInst &Inst, uint32_t Offset) const {
- setAnnotationOpValue(Inst, MCAnnotation::kOffsetAlias, Offset);
- return true;
-}
-
bool MCPlusBuilder::clearOffset(MCInst &Inst) const {
if (!hasAnnotation(Inst, MCAnnotation::kOffset))
return false;
diff --git a/bolt/lib/Passes/FixRISCVCallsPass.cpp b/bolt/lib/Passes/FixRISCVCallsPass.cpp
index 08710b86524a1..38a0f130fdba1 100644
--- a/bolt/lib/Passes/FixRISCVCallsPass.cpp
+++ b/bolt/lib/Passes/FixRISCVCallsPass.cpp
@@ -71,7 +71,7 @@ void FixRISCVCallsPass::runOnFunction(BinaryFunction &BF) {
if (AUIPCLoc.isValid())
NextII->setLoc(AUIPCLoc);
if (AUIPCOffset)
- MIB->setOffsetAlias(*NextII, *AUIPCOffset);
+ MIB->addAnnotation(*NextII, "InputOffsetAlias", *AUIPCOffset);
II = std::next(NextII);
continue;
>From 46904746b5ac17e250d0ad9fdc8cbedcccfb8358 Mon Sep 17 00:00:00 2001
From: Alexander Wilson <rdtscp at meta.com>
Date: Wed, 9 Sep 2026 17:58:52 -0700
Subject: [PATCH 6/6] [BOLT][RISCV] Preserve existing call source locations
Use the AUIPC source location only when the surviving call has none. Explain why the AUIPC input-offset alias must survive NOP removal and name the distinct-alias condition in BinaryEmitter.
Update the regression to require the original JALR source lines. It fails before this change and passes afterward. All 54 RISC-V tests and 33 selected x86 range/address-map tests pass; formatting and whitespace checks pass.
Assisted-by: OpenAI Codex
---
bolt/lib/Core/BinaryEmitter.cpp | 4 +++-
bolt/lib/Passes/FixRISCVCallsPass.cpp | 5 ++++-
bolt/test/RISCV/dwarf-scope-call-pair.s | 4 ++--
3 files changed, 9 insertions(+), 4 deletions(-)
diff --git a/bolt/lib/Core/BinaryEmitter.cpp b/bolt/lib/Core/BinaryEmitter.cpp
index e521af9f54bda..da8110f87e9ea 100644
--- a/bolt/lib/Core/BinaryEmitter.cpp
+++ b/bolt/lib/Core/BinaryEmitter.cpp
@@ -488,10 +488,12 @@ void BinaryEmitter::emitFunctionBody(BinaryFunction &BF, FunctionFragment &FF,
const std::optional<uint32_t> Offset = BC.MIB->getOffset(Instr);
const auto OffsetAlias =
BC.MIB->tryGetAnnotationAs<uint32_t>(Instr, "InputOffsetAlias");
+ const bool HasDistinctOffsetAlias =
+ OffsetAlias && (!Offset || *OffsetAlias != *Offset);
if (Offset || OffsetAlias) {
if (!InstrLabel)
InstrLabel = BC.Ctx->createTempSymbol();
- if (OffsetAlias && (!Offset || *OffsetAlias != *Offset))
+ if (HasDistinctOffsetAlias)
BB->getLocSyms().emplace_back(*OffsetAlias, InstrLabel);
if (Offset)
BB->getLocSyms().emplace_back(*Offset, InstrLabel);
diff --git a/bolt/lib/Passes/FixRISCVCallsPass.cpp b/bolt/lib/Passes/FixRISCVCallsPass.cpp
index 38a0f130fdba1..154609a3c04c5 100644
--- a/bolt/lib/Passes/FixRISCVCallsPass.cpp
+++ b/bolt/lib/Passes/FixRISCVCallsPass.cpp
@@ -68,8 +68,11 @@ void FixRISCVCallsPass::runOnFunction(BinaryFunction &BF) {
// Discard annotations added by the builder before moving the originals.
MIB->stripAnnotations(*NextII);
MIB->moveAnnotations(std::move(OldCall), *NextII);
- if (AUIPCLoc.isValid())
+ if (!NextII->getLoc().isValid() && AUIPCLoc.isValid())
NextII->setLoc(AUIPCLoc);
+ // The AUIPC's input offset may mark a DWARF scope boundary. Keep it on
+ // the surviving call alongside the JALR's offset so both input
+ // addresses map to the call even after the replacement NOP is removed.
if (AUIPCOffset)
MIB->addAnnotation(*NextII, "InputOffsetAlias", *AUIPCOffset);
diff --git a/bolt/test/RISCV/dwarf-scope-call-pair.s b/bolt/test/RISCV/dwarf-scope-call-pair.s
index e33bfb6f85724..bf1be9b2a113b 100644
--- a/bolt/test/RISCV/dwarf-scope-call-pair.s
+++ b/bolt/test/RISCV/dwarf-scope-call-pair.s
@@ -92,9 +92,9 @@
# CHECK-NEXT: DW_AT_low_pc (0x{{0*}}[[BEGIN]])
# CHECK-NEXT: DW_AT_high_pc (0x{{0*}}[[END]])
# CHECK: 0x{{0*}}[[FIRST]] 10
-# CHECK: 0x{{0*}}[[BEGIN]] 20
+# CHECK: 0x{{0*}}[[BEGIN]] 21
# CHECK: 0x{{0*}}[[END]] 30
-# CHECK: 0x{{0*}}[[BOUNDARY]] 40
+# CHECK: 0x{{0*}}[[BOUNDARY]] 41
.text
.option norvc
More information about the llvm-commits
mailing list