Changes since v1: - Formatting changes in `vec.h`. - Supplemental table handling fixes in `ira.cc`. - Increase in reload limit in `lra-constraints.cc`.
This patch series adjusts the RTL infrastructure so that it can recognize and handle an arbitrary amount of instruction operands. This is mostly done by replacing the usages of C-style arrays with a fixed bound in favor of SOO-vectors and `array_view`s. `MAX_RECOG_OPERANDS` is retained for compatibility, but now it mostly serves as a hint for inline buffer size for SOO-vectors. The only observable effect for most targets is the ability to express asm blocks with more than 30 operands without issues, but this PR is also a prerequisite for register allocation for targets with instructions that have an arbitrary operand count. The patchset is bootstrapped and tested on x86_64. I've done some benchmarking, and the patch seems to cause a ~4% slowdown across the testsuite. --- This is a forge pull request published on the gcc-patches mailing list mailing list as requested by feedable via Sourceware Forge <[email protected]>. Forge discussion: https://forge.sourceware.org/gcc/gcc/pulls/223 Get it locally using: ``` git fetch forge-upstream "+refs/versioned_pull/223/*:refs/versioned_pull/223/*" git switch -c "pr-223-v2" "refs/versioned_pull/223/2/head" ``` Or, download the patch at: https://forge.sourceware.org/gcc/gcc/pulls/223.diff Created on: 2026-08-20 20:36:45+00:00 Latest update: 2026-08-24 23:24:44+00:00 Changes: 33 changed files, 894 additions, 531 deletions Head revision: feedable/gcc-TEST ref operand-cnt-test commit a6ca7e094b4d57ac4c8661911c328b5d6823ea5f Base revision: gcc/gcc ref trunk commit 844c665f1701e889b4883df65004dfc310445422 r17-3346-g844c665f1701e8 Merge base: 844c665f1701e889b4883df65004dfc310445422 Requested Reviewers: Changed files: - M: gcc/cfgexpand.cc - M: gcc/config/i386/i386-expand.cc - M: gcc/config/i386/i386.md - M: gcc/cse.cc - M: gcc/emit-rtl.cc - M: gcc/final.cc - M: gcc/genattrtab.cc - M: gcc/genconfig.cc - M: gcc/genemit.cc - M: gcc/genextract.cc - M: gcc/genrecog.cc - M: gcc/haifa-sched.cc - M: gcc/ira-conflicts.cc - M: gcc/ira-costs.cc - M: gcc/ira-int.h - M: gcc/ira.cc - M: gcc/ira.h - M: gcc/iterator-utils.h - M: gcc/loop-invariant.cc - M: gcc/lra-constraints.cc - M: gcc/lra-eliminations.cc - M: gcc/lra-int.h - M: gcc/lra.cc - M: gcc/postreload.cc - M: gcc/recog.cc - M: gcc/recog.h - M: gcc/regrename.cc - M: gcc/reload.cc - M: gcc/rtl.h - M: gcc/testsuite/gcc.dg/pr61692.c - M: gcc/varasm.cc - M: gcc/vec.cc - M: gcc/vec.h feedable (7): iterator-utils.h: Fixup const_derived_container constructor vec.h: Add a convenient way to construct an invalid array_slice vec.h: Add utility functions to array_slice vec.h: Implement copy&move construction&assignment for auto_vec i386: Don't use operands array as storage rtl: Make operand count dynamic rtl: Adjust generation machinery to work with arbitrary amount of operands gcc/cfgexpand.cc | 21 +-- gcc/config/i386/i386-expand.cc | 26 +-- gcc/config/i386/i386.md | 113 ++++++------ gcc/cse.cc | 44 ++--- gcc/emit-rtl.cc | 28 ++- gcc/final.cc | 74 ++++---- gcc/genattrtab.cc | 2 +- gcc/genconfig.cc | 5 +- gcc/genemit.cc | 17 +- gcc/genextract.cc | 23 ++- gcc/genrecog.cc | 24 +-- gcc/haifa-sched.cc | 18 +- gcc/ira-conflicts.cc | 4 +- gcc/ira-costs.cc | 77 +++++--- gcc/ira-int.h | 6 +- gcc/ira.cc | 39 +++- gcc/ira.h | 5 +- gcc/iterator-utils.h | 2 +- gcc/loop-invariant.cc | 13 +- gcc/lra-constraints.cc | 315 ++++++++++++++++----------------- gcc/lra-eliminations.cc | 4 +- gcc/lra-int.h | 5 +- gcc/lra.cc | 46 ++--- gcc/postreload.cc | 10 +- gcc/recog.cc | 86 ++++----- gcc/recog.h | 54 +++++- gcc/regrename.cc | 30 ++-- gcc/reload.cc | 4 +- gcc/rtl.h | 7 +- gcc/testsuite/gcc.dg/pr61692.c | 10 +- gcc/varasm.cc | 8 +- gcc/vec.cc | 143 +++++++++++++++ gcc/vec.h | 162 +++++++++++++++++ 33 files changed, 894 insertions(+), 531 deletions(-) Range-diff against v1: 1: ad88464fe42ab = 1: ad88464fe42ab iterator-utils.h: Fixup const_derived_container constructor 2: 94ccda57ada63 = 2: 94ccda57ada63 vec.h: Add a convenient way to construct an invalid array_slice 3: 5d2323cff34aa ! 3: 469ce43cb8d99 vec.h: Add utility functions to array_slice @@ gcc/vec.h: array_slice<T>::operator[] (unsigned int i) const +bool +array_slice<T>::contains (const U &v) +{ -+ for (const auto &x: *this) -+ if (v == x) return true; ++ for (const auto &x : *this) ++ if (v == x) ++ return true; + return false; +} + @@ gcc/vec.h: array_slice<T>::operator[] (unsigned int i) const +bool +array_slice<T>::find (const F &pred) +{ -+ for (const auto &x: *this) -+ if (pred (x)) return true; ++ for (const auto &x : *this) ++ if (pred (x)) ++ return true; + return false; +} + 4: b11e19daac158 ! 4: 348c0e4c9fc19 vec.h: Implement copy&move construction&assignment for auto_vec @@ Commit message gcc/ChangeLog: - * vec.cc (struct count_cdrors): Test utility type. + * vec.cc (struct count_cdtors): Test utility type. (test_auto_vec_copy_move_construct): New test. (test_auto_vec_move_assign): Likewise. (test_auto_vec_copy_assign): Likewise. @@ gcc/vec.cc: test_auto_delete_vec () ASSERT_EQ (dtor_count, 2); } -+struct count_cdrors{ ++struct count_cdtors{ + static int copy_cnt, move_cnt, ctor_cnt, dtor_cnt; + static void reset () + { @@ gcc/vec.cc: test_auto_delete_vec () + ASSERT_EQ (dtor_cnt, dtor); + reset (); + } -+ count_cdrors () { ++ctor_cnt; } -+ ~count_cdrors () { ++dtor_cnt; } -+ count_cdrors (count_cdrors &&) { ++move_cnt; } -+ count_cdrors (const count_cdrors &) { ++copy_cnt; } -+ count_cdrors &operator= (count_cdrors &&) { return ++move_cnt, *this; } -+ count_cdrors &operator= (const count_cdrors &) { return ++copy_cnt, *this; } ++ count_cdtors () { ++ctor_cnt; } ++ ~count_cdtors () { ++dtor_cnt; } ++ count_cdtors (count_cdtors &&) { ++move_cnt; } ++ count_cdtors (const count_cdtors &) { ++copy_cnt; } ++ count_cdtors &operator= (count_cdtors &&) { return ++move_cnt, *this; } ++ count_cdtors &operator= (const count_cdtors &) { return ++copy_cnt, *this; } +}; -+int count_cdrors::copy_cnt = 0, -+ count_cdrors::move_cnt = 0, -+ count_cdrors::ctor_cnt = 0, -+ count_cdrors::dtor_cnt = 0; ++int count_cdtors::copy_cnt = 0, ++ count_cdtors::move_cnt = 0, ++ count_cdtors::ctor_cnt = 0, ++ count_cdtors::dtor_cnt = 0; + +static void +test_auto_vec_copy_move_construct () +{ -+ count_cdrors::reset (); ++ count_cdtors::reset (); + { -+ count_cdrors::validate (0, 0, 0, 0); -+ auto_vec<count_cdrors, 1> test (2); -+ count_cdrors::validate (0, 0, 0, 0); ++ count_cdtors::validate (0, 0, 0, 0); ++ auto_vec<count_cdtors, 1> test (2); ++ count_cdtors::validate (0, 0, 0, 0); + test.quick_grow_cleared (2); -+ count_cdrors::validate (0, 0, 2, 0); ++ count_cdtors::validate (0, 0, 2, 0); + auto test2 = test; -+ count_cdrors::validate (2, 0, 0, 0); ++ count_cdtors::validate (2, 0, 0, 0); + auto test3 = std::move (test); -+ count_cdrors::validate (0, 0, 0, 0); ++ count_cdtors::validate (0, 0, 0, 0); + } -+ count_cdrors::validate (0, 0, 0, 4); ++ count_cdtors::validate (0, 0, 0, 4); + { -+ auto_vec<count_cdrors, 2> test (2); -+ count_cdrors::validate (0, 0, 0, 0); ++ auto_vec<count_cdtors, 2> test (2); ++ count_cdtors::validate (0, 0, 0, 0); + test.quick_grow_cleared (2); -+ count_cdrors::validate (0, 0, 2, 0); ++ count_cdtors::validate (0, 0, 2, 0); + auto test2 = test; -+ count_cdrors::validate (2, 0, 0, 0); ++ count_cdtors::validate (2, 0, 0, 0); + auto test3 = std::move (test); -+ count_cdrors::validate (0, 2, 0, 0); ++ count_cdtors::validate (0, 2, 0, 0); + } -+ count_cdrors::validate (0, 0, 0, 6); ++ count_cdtors::validate (0, 0, 0, 6); +} + +static void +test_auto_vec_move_assign () +{ + { -+ auto_vec<count_cdrors, 1> from (1), to (1); ++ auto_vec<count_cdtors, 1> from (1), to (1); + from.quick_grow_cleared (1); + to.quick_grow_cleared (1); -+ count_cdrors::validate (0, 0, 2, 0); ++ count_cdtors::validate (0, 0, 2, 0); + to = std::move (from); -+ count_cdrors::validate (0, 1, 0, 0); ++ count_cdtors::validate (0, 1, 0, 0); + } -+ count_cdrors::validate (0, 0, 0, 2); ++ count_cdtors::validate (0, 0, 0, 2); + { -+ auto_vec<count_cdrors, 1> from (1), to (2); ++ auto_vec<count_cdtors, 1> from (1), to (2); + from.quick_grow_cleared (1); + to.quick_grow_cleared (2); -+ count_cdrors::validate (0, 0, 3, 0); ++ count_cdtors::validate (0, 0, 3, 0); + to = std::move (from); -+ count_cdrors::validate (0, 1, 0, 2); ++ count_cdtors::validate (0, 1, 0, 2); + } -+ count_cdrors::validate (0, 0, 0, 2); ++ count_cdtors::validate (0, 0, 0, 2); + { -+ auto_vec<count_cdrors, 1> from (2), to (1); ++ auto_vec<count_cdtors, 1> from (2), to (1); + from.quick_grow_cleared (2); + to.quick_grow_cleared (1); -+ count_cdrors::validate (0, 0, 3, 0); ++ count_cdtors::validate (0, 0, 3, 0); + to = std::move (from); -+ count_cdrors::validate (0, 0, 0, 1); ++ count_cdtors::validate (0, 0, 0, 1); + } -+ count_cdrors::validate (0, 0, 0, 2); ++ count_cdtors::validate (0, 0, 0, 2); + { -+ auto_vec<count_cdrors, 1> from (2), to (2); ++ auto_vec<count_cdtors, 1> from (2), to (2); + from.quick_grow_cleared (2); + to.quick_grow_cleared (2); -+ count_cdrors::validate (0, 0, 4, 0); ++ count_cdtors::validate (0, 0, 4, 0); + to = std::move (from); -+ count_cdrors::validate (0, 0, 0, 2); ++ count_cdtors::validate (0, 0, 0, 2); + } -+ count_cdrors::validate (0, 0, 0, 2); ++ count_cdtors::validate (0, 0, 0, 2); +} + +static void +test_auto_vec_copy_assign () +{ + { -+ auto_vec<count_cdrors, 1> from (1), to (1); ++ auto_vec<count_cdtors, 1> from (1), to (1); + from.quick_grow_cleared (1); + to.quick_grow_cleared (1); -+ count_cdrors::validate (0, 0, 2, 0); ++ count_cdtors::validate (0, 0, 2, 0); + to = from; -+ count_cdrors::validate (1, 0, 0, 0); ++ count_cdtors::validate (1, 0, 0, 0); + } -+ count_cdrors::validate (0, 0, 0, 2); ++ count_cdtors::validate (0, 0, 0, 2); + { -+ auto_vec<count_cdrors, 1> from (1), to (2); ++ auto_vec<count_cdtors, 1> from (1), to (2); + from.quick_grow_cleared (1); + to.quick_grow_cleared (2); -+ count_cdrors::validate (0, 0, 3, 0); ++ count_cdtors::validate (0, 0, 3, 0); + to = from; -+ count_cdrors::validate (1, 0, 0, 1); ++ count_cdtors::validate (1, 0, 0, 1); + } -+ count_cdrors::validate (0, 0, 0, 2); ++ count_cdtors::validate (0, 0, 0, 2); + { -+ auto_vec<count_cdrors, 1> from (2), to (1); ++ auto_vec<count_cdtors, 1> from (2), to (1); + from.quick_grow_cleared (2); + to.quick_grow_cleared (1); -+ count_cdrors::validate (0, 0, 3, 0); ++ count_cdtors::validate (0, 0, 3, 0); + to = from; -+ count_cdrors::validate (2, 0, 0, 1); ++ count_cdtors::validate (2, 0, 0, 1); + } -+ count_cdrors::validate (0, 0, 0, 4); ++ count_cdtors::validate (0, 0, 0, 4); + { -+ auto_vec<count_cdrors, 1> from (2), to (2); ++ auto_vec<count_cdtors, 1> from (2), to (2); + from.quick_grow_cleared (2); + to.quick_grow_cleared (2); -+ count_cdrors::validate (0, 0, 4, 0); ++ count_cdtors::validate (0, 0, 4, 0); + to = from; -+ count_cdrors::validate (2, 0, 0, 0); ++ count_cdtors::validate (2, 0, 0, 0); + } -+ count_cdrors::validate (0, 0, 0, 4); ++ count_cdtors::validate (0, 0, 0, 4); +} + /* Verify accesses to vector elements are done indirectly. */ 5: aec89fa152446 = 5: c049d0fc9cdce i386: Don't use operands array as storage 6: bda6f75a1d0f6 ! 6: 1b4d679adfe4d rtl: Make operand count dynamic @@ Commit message * ira.cc (ira_former_scratch_operand_p): Introduce storage for additional operands. (ira_register_new_scratch_op): Likewise. + (remove_scratches): Likewise. + (ira_restore_scratches): Likewise. * ira.h (ira_former_scratch_operand_p): Use unsigned for opno. (ira_register_new_scratch_op): Likewise. * loop-invariant.cc (regs_set[): Turn into a vector. @@ Commit message (check_and_process_move): Likewise. (process_alt_operands): Likewise. (curr_insn_transform): Likewsie. - (MAX_RELOAD_INSNS_NUMBER): Adjust to be per-operand. + (MAX_RELOAD_INSNS_PER_OPERAND_NUMBER): Add per-operand limit. (combine_reload_insn): Use vectors. (lra_constraints): Compute max reload count exactly. (add_to_inherit): Handle vector. (inherit_in_ebb): Likewise. * lra-eliminations.cc (eliminate_regs_in_insn): Use vectors. - * lra-int.h (LRA_MAX_INSN_RELOADS): Adjust to be per-operand. - (lra_update_dups): Use array_slice. + * lra-int.h (lra_update_dups): Use array_slice. * lra.cc (lra_update_dups): Likewise. (lra_set_insn_recog_data): Likewise. (lra_update_insn_recog_data): Likewise. @@ gcc/ira.cc: ira_former_scratch_p (int regno) { - return bitmap_bit_p (&scratch_operand_bitmap, - INSN_UID (insn) * MAX_RECOG_OPERANDS + nop) != 0; -+ if (nop <= MAX_RECOG_OPERANDS) ++ if (nop < MAX_RECOG_OPERANDS) + return bitmap_bit_p (&scratch_operand_bitmap, + INSN_UID (insn) * MAX_RECOG_OPERANDS + nop) != 0; + nop -= MAX_RECOG_OPERANDS; @@ gcc/ira.cc: ira_former_scratch_p (int regno) + .get_or_insert (INSN_UID (insn), &old); + unsigned nop_high = nop / HOST_BITS_PER_WIDE_INT; + unsigned nop_low = nop % HOST_BITS_PER_WIDE_INT; -+ if (ops.length () < nop_high) ++ if (ops.length () <= nop_high) + return false; -+ return ops[nop_high] & (1 << nop_low); ++ return ops[nop_high] & (HOST_WIDE_INT_1U << nop_low); } /* Register operand NOP in INSN as a former scratch. It will be @@ gcc/ira.cc: ira_register_new_scratch_op (rtx_insn *insn, int nop, int icode) - bitmap_set_bit (&scratch_operand_bitmap, - INSN_UID (insn) * MAX_RECOG_OPERANDS + nop); add_reg_note (insn, REG_UNUSED, op); -+ if (nop <= MAX_RECOG_OPERANDS) ++ if (nop < MAX_RECOG_OPERANDS) + { + bitmap_set_bit (&scratch_operand_bitmap, + INSN_UID (insn) * MAX_RECOG_OPERANDS + nop); @@ gcc/ira.cc: ira_register_new_scratch_op (rtx_insn *insn, int nop, int icode) + .get_or_insert (INSN_UID (insn), &old); + unsigned nop_high = nop / HOST_BITS_PER_WIDE_INT; + unsigned nop_low = nop % HOST_BITS_PER_WIDE_INT; -+ if (ops.length () < nop_high + 1) ++ if (ops.length () <= nop_high) + ops.safe_grow_cleared (nop_high + 1); + ops[nop_high] |= 1 << nop_low; } /* Return true if string STR contains constraint 'X'. */ +@@ gcc/ira.cc: remove_scratches (void) + scratches.create (get_max_uid ()); + bitmap_initialize (&scratch_bitmap, ®_obstack); + bitmap_initialize (&scratch_operand_bitmap, ®_obstack); ++ additional_scratch_operand_bitmap.empty (); + FOR_EACH_BB_FN (bb, cfun) + FOR_BB_INSNS (bb, insn) + if (INSN_P (insn) +@@ gcc/ira.cc: ira_restore_scratches (FILE *dump_file) + scratches.release (); + bitmap_clear (&scratch_bitmap); + bitmap_clear (&scratch_operand_bitmap); ++ additional_scratch_operand_bitmap.empty (); + } + + ## gcc/ira.h ## @@ gcc/ira.h: extern bool ira_bad_reload_regno (int, rtx, rtx); @@ gcc/lra-constraints.cc: curr_insn_transform (bool check_only_p) &goal_alt_exclude_start_hard_regs[i], &before, &after, false); @@ gcc/lra-constraints.cc: loc_equivalence_callback (rtx loc, const_rtx, void *data) - return NULL_RTX; - } - --/* Maximum number of generated reload insns per an insn. It is for -+/* Maximum number of generated reload insns per an insn operand. It is for preventing this pass cycling in a bug case. */ --#define MAX_RELOAD_INSNS_NUMBER LRA_MAX_INSN_RELOADS -+#define MAX_RELOAD_INSNS_NUMBER 3 + #define MAX_RELOAD_INSNS_NUMBER LRA_MAX_INSN_RELOADS ++/* Same as above but per an insn operand. */ ++#define MAX_RELOAD_INSNS_PER_OPERAND_NUMBER 3 ++ /* The current iteration number of this LRA pass. */ int lra_constraint_iter; + @@ gcc/lra-constraints.cc: combine_reload_insn (rtx_insn *from, rtx_insn *to) rtx_insn *saved_insn; rtx set, from_reg, to_reg, op; @@ gcc/lra-constraints.cc: lra_constraints (bool first_p) original_insn = curr_insn; + reload_limit + = lra_get_insn_recog_data (curr_insn)->insn_static_data->n_operands; -+ reload_limit *= MAX_RELOAD_INSNS_NUMBER; ++ reload_limit *= MAX_RELOAD_INSNS_PER_OPERAND_NUMBER; ++ /* Allow at least as many reloads as were allowed historically to ++ prevent regressions. */ ++ if (reload_limit < MAX_RELOAD_INSNS_NUMBER) ++ reload_limit = MAX_RELOAD_INSNS_NUMBER; } else if (combine_reload_insn (curr_insn, original_insn)) { 7: c0214189f7e69 ! 7: a6ca7e094b4d5 rtl: Adjust generation machinery to work with arbitrary amount of operands @@ gcc/genrecog.cc: print_subroutine (FILE *f, output_state *os, state *s, int proc if (proc_id == 0) { fprintf (f, " recog_data.insn = NULL;\n"); -+ fprintf (f, " recog_data.init (MAX_RECOG_OPERANDS, 0);\n"); ++ fprintf (f, " recog_data.init_recog ();\n"); } print_state (f, os, s, 2, true); fprintf (f, "}\n"); @@ gcc/recog.h: struct recog_data_d + if (index >= operand.length ()) + operand.safe_grow_cleared (index + 1); + return operand[index]; ++ } ++ void init_recog () ++ { ++ /* This is not supposed to be required, but many backends store ++ values that are not reflected in the pattern into the operands ++ array, so we can't really know how many operands are required ++ to be present. */ ++ operand.safe_grow_cleared (MAX_RECOG_OPERANDS); + } /* It is very tempting to make the 5 operand related arrays into a structure and index on that. However, to be source compatible -- 2.54.0
