[PATCH 1/3] RISC-V: Add ZCMT table jump relaxation
Bigmagic
zhaofujin@nucleisys.com
Tue Jun 23 02:55:51 GMT 2026
Hi RISC-V maintainers and contributors,
When testing ZCMT functionality, I found that .option norelax does not actually prevent ZCMT instruction generation. Here's the test case I wrote:
Assembly
.section .text
.globl _start
.type _start, %function
_start:
.option push
.option norelax
.rept 70
call local_func
.endr
.option pop
.rept 70
call global_func
.endr
li a7, 93 # sys_exit
li a0, 0 # exit code
ecall
.type local_func, %function
local_func:
ret
.globl global_func
.type global_func, %function
global_func:
ret
With the following linker script:
Text
ENTRY(_start)
SECTIONS
{
.text 0x80000000 : {
*(.text)
}
.riscv.jvt : {
*(.riscv.jvt)
}
.data : {
*(.data)
}
}
Observation:
The .option push / .option norelax / .option pop block should prevent relaxation for the enclosed call instructions, but ZCMT transformations are still being applied.
Root Cause:
In _bfd_riscv_relax_section(), the ZCMT relaxation path does not check whether the relocation is paired with R_RISCV_RELAX. When .option norelax is used, the assembler does not emit R_RISCV_RELAX relocations alongside the call instructions, but the linker still applies ZCMT relaxation unconditionally.
Proposed Fix:
Skip ZCMT relaxation if the call relocation is not paired with R_RISCV_RELAX:
Diff
--- a/bfd/elfnn-riscv.c
+++ b/bfd/elfnn-riscv.c
@@ -6067,6 +6067,12 @@ _bfd_riscv_relax_section (bfd *abfd, asection *sec,
relax_func = _bfd_riscv_jvt_record;
else
continue;
+ /* Skip if not paired with R_RISCV_RELAX (i.e. .option norelax). */
+ if (i == sec->reloc_count - 1
+ || ELFNN_R_TYPE ((rel + 1)->r_info) != R_RISCV_RELAX
+ || rel->r_offset != (rel + 1)->r_offset)
+ continue;
+ i++;
*again = true;
}
else if (info->relax_trip == JVT_PROFILING_DETERMINE)
@@ -6076,6 +6082,12 @@ _bfd_riscv_relax_section (bfd *abfd, asection *sec,
relax_func = _bfd_riscv_jvt_mark;
else
continue;
+ /* Skip if not paired with R_RISCV_RELAX (i.e. .option norelax). */
+ if (i == sec->reloc_count - 1
+ || ELFNN_R_TYPE ((rel + 1)->r_info) != R_RISCV_RELAX
+ || rel->r_offset != (rel + 1)->r_offset)
+ continue;
+ i++;
}
}
else if (info->relax_pass == RELAX_PASS_SHORTEN_LUI_CALL_TRREL_PCREL)
Explanation of the check:
i == sec->reloc_count - 1 — ensure there's a next relocation to check
ELFNN_R_TYPE ((rel + 1)->r_info) != R_RISCV_RELAX — verify the next relocation is R_RISCV_RELAX
rel->r_offset != (rel + 1)->r_offset — ensure the relax relocation is at the same offset (paired)
Testing:
With this patch applied, the test case correctly preserves the call instructions inside the .option norelax block while still applying ZCMT relaxation to the unprotected calls outside the block.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://sourceware.org/pipermail/binutils/attachments/20260623/43661d8a/attachment.htm>
More information about the Binutils
mailing list