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, &reg_obstack);
    +   bitmap_initialize (&scratch_operand_bitmap, &reg_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

Reply via email to