[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