https://gcc.gnu.org/g:2f8a386c305711c987b82baeb9b8ba7fe281941b
commit 2f8a386c305711c987b82baeb9b8ba7fe281941b Author: Paul-Antoine Arras <[email protected]> Date: Fri Oct 16 11:42:56 2026 +0200 amdgcn: Use the real register budget for non-kernel functions The loop optimizers (ivopts, loop-invariant, predcom) estimate the number of available registers from target_avail_regs, which init_set_costs computes once from the non-fixed GENERAL_REGS, with no function in context. GCN only restricts the registers of non-kernel functions (to MAX_NORMAL_*) in gcn_conditional_register_usage once a function is current, so the count was always the one for kernels: 78 SGPRs, while IRA only gets 44 in a non-kernel function such as an outlined OpenMP body. Nothing recomputes the count afterwards, since reinit_regs does not touch the loop cost tables. Fix this by making GCN a SWITCHABLE_TARGET, as MIPS does for MIPS16. Kernels (and code with no current function) use default_target_globals; non-kernel functions use a second set, created lazily with save_target_globals_default_opts and selected from TARGET_SET_CURRENT_FUNCTION. The register-usage hook runs with the non-kernel function current when that set is created, so the restricted fixed_regs end up in it, and init_set_costs, run by initialize_rtl before the first GIMPLE pass of the first function expanded in that set, computes target_avail_regs from them. Switching between the two sets afterwards is a pointer swap. gcn_conditional_register_usage now decides kernel-ness from the declaration (new gcn_is_kernel) rather than from cfun->machine->normal_function, which is only set at expand time. The cached set mixes GC and non-GC data, so it is dropped before a PCH is written, as MIPS does. This also changes what loop-invariant's estimate_reg_pressure_cost and predcom's MAX_DISTANCE see for non-kernel functions, which is the intended behaviour. Known limitation: target_avail_regs only counts GENERAL_REGS, i.e. SGPRs on GCN; VGPRs are not accounted for. gcc/ChangeLog: * config/gcn/gcn.h (SWITCHABLE_TARGET): Define. * config/gcn/gcn.cc: Include target-globals.h. (normal_function_globals, old_is_kernel): New static variables. (gcn_is_kernel): New function. (gcn_set_current_function): New function. (gcn_prepare_pch_save): New function. (gcn_conditional_register_usage): Use gcn_is_kernel to detect non-kernel functions and do not require cfun->machine for them. (TARGET_SET_CURRENT_FUNCTION, TARGET_PREPARE_PCH_SAVE): Define. gcc/testsuite/ChangeLog: * gcc.target/gcn/ivopts-reg-budget-1.c: New test. (cherry picked from commit 6cdf9af5c4dae269be023e0493448bb00c98251a) Diff: --- gcc/config/gcn/gcn.cc | 73 +++++++++++++++++++++- gcc/config/gcn/gcn.h | 3 + gcc/testsuite/gcc.target/gcn/ivopts-reg-budget-1.c | 35 +++++++++++ 3 files changed, 109 insertions(+), 2 deletions(-) diff --git a/gcc/config/gcn/gcn.cc b/gcc/config/gcn/gcn.cc index 9c305398c782..bf25abed321d 100644 --- a/gcc/config/gcn/gcn.cc +++ b/gcc/config/gcn/gcn.cc @@ -55,6 +55,7 @@ #include "cgraph.h" #include "case-cfn-macros.h" #include "opts.h" +#include "target-globals.h" /* This file should be included last. */ #include "target-def.h" @@ -109,6 +110,13 @@ const struct gcn_device_def gcn_devices[] = { #include "gcn-devices.def" }; +/* Target state for non-kernel, normal functions. (Kernels use + default_target_globals.) */ +static GTY (()) struct target_globals *normal_function_globals; + +/* Whether the last function passed to gcn_set_current_function was a kernel. */ +static bool old_is_kernel = true; + /* }}} */ /* {{{ Initialization and options. */ @@ -1492,6 +1500,26 @@ gcn_stepped_zero_int_parallel_p (rtx op, int step) return true; } +/* Return true if FNDECL is a kernel function. A function is treated as a + kernel if it has the "amdgpu_hsa_kernel" attribute, or it is the main + function, or it has the "omp target entrypoint" attribute. */ + +static bool +gcn_is_kernel (tree fndecl) +{ + if (!fndecl) + return true; + + tree attr = lookup_attribute ("amdgpu_hsa_kernel", DECL_ATTRIBUTES (fndecl)); + if (!attr) + attr = lookup_attribute ("amdgpu_hsa_kernel", + TYPE_ATTRIBUTES (TREE_TYPE (fndecl))); + + return attr || (DECL_NAME (fndecl) && MAIN_NAME_P (DECL_NAME (fndecl))) + || lookup_attribute ("omp target entrypoint", DECL_ATTRIBUTES (fndecl)) + != NULL_TREE; +} + /* }}} */ /* {{{ Addresses, pointers and moves. */ @@ -2604,10 +2632,11 @@ gcn_conditional_register_usage (void) i++) fixed_regs[VGPR_REGNO (i)] = call_used_regs[VGPR_REGNO (i)] = 1; - if (!cfun || !cfun->machine) + if (!cfun) return; - if (cfun->machine->normal_function) + if (!gcn_is_kernel (cfun->decl) + || (cfun->machine && cfun->machine->normal_function)) { /* Restrict the set of SGPRs, VGPRs and AVGPRs used by non-kernel functions. */ @@ -2625,6 +2654,9 @@ gcn_conditional_register_usage (void) return; } + if (!cfun->machine) + return; + /* If the set of requested args is the default set, nothing more needs to be done. */ if (cfun->machine->args.requested == default_requested_args) @@ -4110,6 +4142,39 @@ gcn_expand_divmod_libfunc (rtx libfunc, machine_mode mode, rtx op0, rtx op1, RETURN_VALUE_REG + (wideinnermode == TImode ? 2 : 1)); } +/* Implement TARGET_SET_CURRENT_FUNCTION. Set up the target-dependent global + state depending on whether the current function is a kernel. */ + +static void +gcn_set_current_function (tree fndecl) +{ + bool is_kernel = gcn_is_kernel (fndecl); + if (is_kernel == old_is_kernel) + return; + + if (!is_kernel) + { + if (!normal_function_globals) + normal_function_globals = save_target_globals_default_opts (); + else + restore_target_globals (normal_function_globals); + } + else + restore_target_globals (&default_target_globals); + + old_is_kernel = is_kernel; +} + +/* Implement TARGET_PREPARE_PCH_SAVE. */ + +static void +gcn_prepare_pch_save (void) +{ + restore_target_globals (&default_target_globals); + normal_function_globals = NULL; + old_is_kernel = true; +} + /* }}} */ /* {{{ Miscellaneous. */ @@ -8738,6 +8803,8 @@ gcn_output_ensure_scalar (rtx *operands, machine_mode mode) #define TARGET_MODES_TIEABLE_P gcn_modes_tieable_p #undef TARGET_OPTION_OVERRIDE #define TARGET_OPTION_OVERRIDE gcn_option_override +#undef TARGET_PREPARE_PCH_SAVE +#define TARGET_PREPARE_PCH_SAVE gcn_prepare_pch_save #undef TARGET_PRETEND_OUTGOING_VARARGS_NAMED #define TARGET_PRETEND_OUTGOING_VARARGS_NAMED \ gcn_pretend_outgoing_varargs_named @@ -8755,6 +8822,8 @@ gcn_output_ensure_scalar (rtx *operands, machine_mode mode) #define TARGET_SECTION_TYPE_FLAGS gcn_section_type_flags #undef TARGET_SCALAR_MODE_SUPPORTED_P #define TARGET_SCALAR_MODE_SUPPORTED_P gcn_scalar_mode_supported_p +#undef TARGET_SET_CURRENT_FUNCTION +#define TARGET_SET_CURRENT_FUNCTION gcn_set_current_function #undef TARGET_SIMD_CLONE_ADJUST #define TARGET_SIMD_CLONE_ADJUST gcn_simd_clone_adjust #undef TARGET_SIMD_CLONE_COMPUTE_VECSIZE_AND_SIMDLEN diff --git a/gcc/config/gcn/gcn.h b/gcc/config/gcn/gcn.h index a758280a8403..fcc9728d3e26 100644 --- a/gcc/config/gcn/gcn.h +++ b/gcc/config/gcn/gcn.h @@ -919,3 +919,6 @@ enum gcn_builtin_codes || M == V2SFmode || M == V2DImode || M == V2DFmode) \ ? 2 \ : 1) + +/* For switching between kernel and regular function. */ +#define SWITCHABLE_TARGET 1 diff --git a/gcc/testsuite/gcc.target/gcn/ivopts-reg-budget-1.c b/gcc/testsuite/gcc.target/gcn/ivopts-reg-budget-1.c new file mode 100644 index 000000000000..378f93e1a069 --- /dev/null +++ b/gcc/testsuite/gcc.target/gcn/ivopts-reg-budget-1.c @@ -0,0 +1,35 @@ +/* Non-kernel functions may only use the first MAX_NORMAL_SGPR_COUNT SGPRs + (see gcn_conditional_register_usage), so the register budget seen by the + loop optimizers must be smaller for them than for kernels. */ + +/* { dg-do compile } */ +/* { dg-options "-O2 -fdump-tree-ivopts-details" } */ + +/* A normal function. It is expanded before main, i.e. it is the first + function for which RTL is initialized. */ + +__attribute__((noinline)) void +f (double *a, double *b, int n) +{ + for (int i = 0; i < n; i++) + a[i] = b[i] * 2.0; +} + +double *x, *y; +int n; + +/* main is compiled as a kernel. */ + +int +main () +{ + f (x, y, n); + for (int i = 0; i < n; i++) + x[i] += y[i]; + return 0; +} + +/* { dg-final { scan-assembler "\\.amdhsa_kernel\\s+main" } } */ +/* { dg-final { scan-assembler-not "\\.amdhsa_kernel\\s+f\\M" } } */ +/* { dg-final { scan-tree-dump-times "target_avail_regs 44" 1 "ivopts" } } */ +/* { dg-final { scan-tree-dump-times "target_avail_regs 78" 1 "ivopts" } } */
