From: Monk Chiang Date: Wed, 7 Jan 2026 02:59:07 +0000 (-0800) Subject: RISC-V: Use long jump for crossing section boundaries X-Git-Url: http://git.ipfire.org/gitweb/?a=commitdiff_plain;h=8404a2538d64ea6c4abb59d9db59b19cb265af79;p=thirdparty%2Fgcc.git RISC-V: Use long jump for crossing section boundaries When -freorder-blocks-and-partition is used, GCC places cold code in .text.unlikely section. Jumps from hot code (.text) to cold code (.text.unlikely) may cross section boundaries. Since the linker may place these sections more than 1MB apart, the JAL instruction's ±1MB range can be exceeded, causing linker errors like: relocation truncated to fit: R_RISCV_JAL against `.text.unlikely' This patch fixes the issue by checking CROSSING_JUMP_P in the length attribute calculation for jump instructions. When a jump crosses section boundaries, the length is set to 8 bytes (AUIPC+JALR) instead of 4 bytes (JAL), ensuring the long form is used. This approach is consistent with other backends (NDS32, SH, ARC) that also use CROSSING_JUMP_P to handle cross-section jumps. gcc/ChangeLog: * config/riscv/riscv.md (length attribute): Check CROSSING_JUMP_P for jump instructions and use length 8 for crossing jumps. (jump): Update comment to explain when long form is used. gcc/testsuite/ChangeLog: * gcc.target/riscv/pr-crossing-jump-1.c: New test. * gcc.target/riscv/pr-crossing-jump-2.c: New test. * gcc.target/riscv/pr-crossing-jump-3.c: New test. --- diff --git a/gcc/config/riscv/riscv.md b/gcc/config/riscv/riscv.md index c350283fa34..3487ab1954e 100644 --- a/gcc/config/riscv/riscv.md +++ b/gcc/config/riscv/riscv.md @@ -626,13 +626,18 @@ (const_int 12))) ;; Jumps further than +/- 1 MiB require two instructions. + ;; Also, jumps that cross section boundaries (e.g., from hot to cold + ;; section when -freorder-blocks-and-partition is used) require two + ;; instructions because the linker may place the sections far apart. (eq_attr "type" "jump") - (if_then_else (and (le (minus (match_dup 0) (pc)) - (const_int 1048568)) - (le (minus (pc) (match_dup 0)) - (const_int 1048572))) - (const_int 4) - (const_int 8)) + (if_then_else (match_test "CROSSING_JUMP_P (insn)") + (const_int 8) + (if_then_else (and (le (minus (match_dup 0) (pc)) + (const_int 1048568)) + (le (minus (pc) (match_dup 0)) + (const_int 1048572))) + (const_int 4) + (const_int 8))) ;; Conservatively assume calls take two instructions (AUIPC + JALR). ;; The linker will opportunistically relax the sequence to JAL. @@ -3879,8 +3884,10 @@ [(set (pc) (label_ref (match_operand 0 "" "")))] "" { - /* Hopefully this does not happen often as this is going - to clobber $ra and muck up the return stack predictors. */ + /* Use the long form (AUIPC+JALR) if the jump distance exceeds 1 MiB, + or if the jump crosses section boundaries (e.g., from hot to cold + section when -freorder-blocks-and-partition is used). + Note: This clobbers $ra and mucks up the return stack predictors. */ if (get_attr_length (insn) == 8) return "jump\t%l0,ra"; diff --git a/gcc/testsuite/gcc.target/riscv/pr-crossing-jump-1.c b/gcc/testsuite/gcc.target/riscv/pr-crossing-jump-1.c new file mode 100644 index 00000000000..88def7ce545 --- /dev/null +++ b/gcc/testsuite/gcc.target/riscv/pr-crossing-jump-1.c @@ -0,0 +1,41 @@ +/* Test that jumps crossing section boundaries use the long form (AUIPC+JALR). + When -freorder-blocks-and-partition is used, cold code is placed in + .text.unlikely section. Jumps from hot code (.text) to cold code + (.text.unlikely) must use the long form because the linker may place + these sections more than 1MB apart, exceeding the JAL instruction's range. + + This test verifies that CROSSING_JUMP_P is handled correctly in the + RISC-V backend. */ + +/* { dg-do compile } */ +/* { dg-require-effective-target freorder } */ +/* { dg-options "-O2 -freorder-blocks-and-partition" } */ + +extern void abort (void); + +/* Force the error path to be cold. */ +static void __attribute__((cold, noinline)) +handle_error (void) +{ + abort (); +} + +/* Function with hot and cold paths. */ +int +check_positive (int x) +{ + if (__builtin_expect (x > 0, 1)) + { + /* Hot path - stays in .text */ + return x; + } + /* Cold path - moved to .text.unlikely */ + handle_error (); + return -1; +} + +/* The jump to the cold section should use "jump" (AUIPC+JALR) not "j" (JAL). + We check for "jump" instruction which is the long form. */ + +/* { dg-final { scan-assembler "jump\\t\\.L\[0-9\]+,ra" } } */ + diff --git a/gcc/testsuite/gcc.target/riscv/pr-crossing-jump-2.c b/gcc/testsuite/gcc.target/riscv/pr-crossing-jump-2.c new file mode 100644 index 00000000000..f2e7c55014d --- /dev/null +++ b/gcc/testsuite/gcc.target/riscv/pr-crossing-jump-2.c @@ -0,0 +1,45 @@ +/* Test that multiple crossing jumps all use the long form. + This is a more comprehensive test for CROSSING_JUMP_P handling. */ + +/* { dg-do compile } */ +/* { dg-require-effective-target freorder } */ +/* { dg-options "-O2 -freorder-blocks-and-partition" } */ + +extern void abort (void); + +static void __attribute__((cold, noinline)) +cold_path_1 (void) +{ + abort (); +} + +static void __attribute__((cold, noinline)) +cold_path_2 (void) +{ + abort (); +} + +/* Function with multiple cold paths. */ +int +validate (int a, int b) +{ + if (__builtin_expect (a > 0, 1)) + { + if (__builtin_expect (b > 0, 1)) + { + /* Hot path */ + return a + b; + } + /* Cold path 1 */ + cold_path_1 (); + } + /* Cold path 2 */ + cold_path_2 (); + return -1; +} + +/* All jumps to cold sections should use "jump" (AUIPC+JALR). + We expect at least 2 crossing jumps. */ + +/* { dg-final { scan-assembler-times "jump\\t\\.L\[0-9\]+,ra" 2 } } */ + diff --git a/gcc/testsuite/gcc.target/riscv/pr-crossing-jump-3.c b/gcc/testsuite/gcc.target/riscv/pr-crossing-jump-3.c new file mode 100644 index 00000000000..d594488e222 --- /dev/null +++ b/gcc/testsuite/gcc.target/riscv/pr-crossing-jump-3.c @@ -0,0 +1,33 @@ +/* Test that jumps within the same section still use the short form (JAL). + This ensures that the CROSSING_JUMP_P fix doesn't pessimize normal jumps. */ + +/* { dg-do compile } */ +/* { dg-options "-O2" } */ + +int global; + +/* Simple function with a loop - jumps stay within .text section. */ +int +sum_to_n (int n) +{ + int sum = 0; + for (int i = 1; i <= n; i++) + sum += i; + return sum; +} + +/* Function with conditional - jumps stay within .text section. */ +int +abs_value (int x) +{ + if (x < 0) + return -x; + return x; +} + +/* The backward jump in the loop should use "j" (JAL) not "jump" (AUIPC+JALR) + since it doesn't cross section boundaries. We verify that "jump" is NOT + used for intra-section jumps. */ + +/* { dg-final { scan-assembler-not "jump\\t\\.L\[0-9\]+,ra" } } */ +