> On 9 Sep 2026, at 13:43, Richard Sandiford <[email protected]> wrote:
> 
> <[email protected]> writes:
>> From: Kyrylo Tkachov <[email protected]>
>> 
>> init_all_optabs evaluated one HAVE_* macro per optab pattern.  That is 3328
>> assignments on aarch64 and 14830 on riscv64.  genopinit already worked around
>> the cost of compiling them by splitting them into functions of 1000
>> statements each, with the comment "In order to prevent pathological
>> situations in dataflow analysis".
>> 
>> genflags.cc derives HAVE_<name> from the pattern's C test, and defines it as
>> 1 when that test is statically true, so patterns whose tests agree after the
>> same normalisation also agree on the definition of HAVE_<name>.  Targets have
>> far fewer distinct tests than patterns.  Number the distinct tests, emit a
>> pattern to test map, and evaluate each test once.
>> 
>>  static void
>>  init_optabs_00 (struct target_optabs *optabs)
>>  {
>>    bool *ena = optabs->pat_enable;
>>    ena[0] = HAVE_extendqihi2;
>>    ...                                 /* 1000 of these, four times */
>>  }
>> 
>> becomes
>> 
>>  static const unsigned char pat_cond[NUM_OPTAB_PATTERNS] = { 0, 0, ... };
>> 
>>  void
>>  init_all_optabs (struct target_optabs *optabs)
>>  {
>>    const bool cond[] = {
>>      !!HAVE_extendqihi2,
>>      ...                               /* one per distinct condition */
>>    };
>>    bool *ena = optabs->pat_enable;
>>    for (unsigned int i = 0; i < NUM_OPTAB_PATTERNS; ++i)
>>      ena[i] = cond[pat_cond[i]];
>>  }
>> 
>> The conditions stay local to init_all_optabs, so every call evaluates them
>> for the current target options, and each pat_enable entry stays independent
>> for swap_optab_enable.
>> 
>> Distinct conditions per target: aarch64 136 of 3328 patterns, riscv64 986 of
>> 14830, x86_64 346 of 2975, i686 288 of 2614, avr 30 of 926.
>> 
>> On aarch64 insn-opinit.cc shrinks from 279KB to 174KB.  Compiling that file
>> on its own with the host g++ 11.4.0 at -g -O2 takes 93% less wall time
>> and 65% less peak memory, 609MB down to 216MB.
>> init_all_optabs also gets cheaper to run: it executes once per target_optabs
>> initialisation, so each #pragma GCC target and each target attribute now 
>> costs
>> 136 condition evaluations instead of 3328.
>> 
>> Checked on the five targets above that every pattern's HAVE_ definition text
>> matches that of its group representative, and that no group holds two
>> different texts.
>> 
>> Bootstrapped on aarch64-none-linux-gnu.
>> Ok for trunk?
>> 
>> gcc/ChangeLog:
>> 
>> * genopinit.cc (struct pattern_info): New.
>> (patterns): Use it.
>> (gen_insn): Record the pattern's C test.
>> (pattern_cmp): Use pattern_info.
>> (narrowest_uint_type): New function.
>> (main): Emit a condition-number table and a loop instead of one
>> assignment per pattern, and drop the split into 1000-pattern
>> functions.
>> 
>> Suggested-by: Tamar Christina <[email protected]>
>> Signed-off-by: Kyrylo Tkachov <[email protected]>
>> ---
>> gcc/genopinit.cc | 112 ++++++++++++++++++++++++++++++-----------------
>> 1 file changed, 73 insertions(+), 39 deletions(-)
>> 
>> diff --git a/gcc/genopinit.cc b/gcc/genopinit.cc
>> index 62eaf5bd378..4a8cfed7c27 100644
>> --- a/gcc/genopinit.cc
>> +++ b/gcc/genopinit.cc
>> @@ -35,22 +35,37 @@ static const char * const rtx_upname[] = {
>> 
>> #undef DEF_RTL_EXPR
>> 
>> +/* An optab pattern and the condition under which it is available.  */
>> +
>> +struct pattern_info : optab_pattern
>> +{
>> +  /* The pattern's C test, or "" if the pattern is always available.
>> +     genflags.cc defines HAVE_<name> as 1 for a statically true test and
>> +     as the test itself otherwise, so two patterns that agree on COND
>> +     also agree on the definition of HAVE_<name>.  */
>> +  const char *cond;
>> +};
>> +
>> /* Vector in which to collect insns that match.  */
>> -static vec<optab_pattern> patterns;
>> +static vec<pattern_info> patterns;
>> 
>> static void
>> gen_insn (md_rtx_info *info)
>> {
>> -  optab_pattern p;
>> +  pattern_info p;
>>   if (find_optab (&p, XSTR (info->def, 0)))
>> -    patterns.safe_push (p);
>> +    {
>> +      const char *cond = get_c_test (info->def);
>> +      p.cond = maybe_eval_c_test (cond) == 1 ? "" : cond;
>> +      patterns.safe_push (p);
>> +    }
>> }
>> 
>> static int
>> pattern_cmp (const void *va, const void *vb)
>> {
>> -  const optab_pattern *a = (const optab_pattern *)va;
>> -  const optab_pattern *b = (const optab_pattern *)vb;
>> +  const pattern_info *a = (const pattern_info *)va;
>> +  const pattern_info *b = (const pattern_info *)vb;
>>   return a->sort_num - b->sort_num;
>> }
>> 
>> @@ -173,12 +188,29 @@ handle_overloaded_gen (FILE *file, overloaded_name 
>> *oname)
>>     }
>> }
>> 
>> +/* Return the name of the narrowest unsigned integer type that is guaranteed
>> +   to hold every value below LIMIT.  The generated file is compiled for the
>> +   host, which need not be the machine that runs this generator, so use the
>> +   ranges that ISO C requires rather than the ones this machine happens to
>> +   provide.  GCC hosts have at least 32-bit ints, which cover every vec
>> +   index.  */
>> +
>> +static const char *
>> +narrowest_uint_type (unsigned int limit)
>> +{
>> +  if (limit <= 256)
>> +    return "unsigned char";
>> +  if (limit <= 65536)
>> +    return "unsigned short";
>> +  return "unsigned int";
>> +}
>> +
>> int
>> main (int argc, const char **argv)
>> {
>>   FILE *h_file, *s_file;
>>   unsigned int i, j, n, last_kind[5];
>> -  optab_pattern *p;
>> +  pattern_info *p;
>> 
>>   progname = "genopinit";
>> 
>> @@ -367,45 +399,47 @@ main (int argc, const char **argv)
>>     fprintf (s_file, "  { %#08x, CODE_FOR_%s },\n", p->sort_num, p->name);
>>   fprintf (s_file, "};\n\n");
>> 
>> -  /* Some targets like riscv have a large number of patterns.  In order to
>> -     prevent pathological situations in dataflow analysis split the init
>> -     function into separate ones that initialize 1000 patterns each.  */
>> +  /* Targets have far fewer distinct conditions than optab patterns.  Number
>> +     the distinct conditions and record the number that each pattern uses, 
>> so
>> +     that init_all_optabs can evaluate every condition once and then fill
>> +     pat_enable from a table.  That keeps init_all_optabs a fixed size no
>> +     matter how many patterns a target has, and makes it cheaper to run.  */
>> 
>> -  const int patterns_per_function = 1000;
>> -
>> -  if (patterns.length () > patterns_per_function)
>> +  auto_vec<unsigned int> pat_cond (patterns.length ());
>> +  auto_vec<unsigned int> cond_rep;
>> +  hash_map<nofree_string_hash, unsigned int> cond_ids;
>> +  for (i = 0; patterns.iterate (i, &p); ++i)
>>     {
>> -      unsigned num_init_functions
>> - = patterns.length () / patterns_per_function + 1;
>> -      for (i = 0; i < num_init_functions; i++)
>> +      bool existed;
>> +      unsigned int &id = cond_ids.get_or_insert (p->cond, &existed);
>> +      if (!existed)
>> {
>> -   fprintf (s_file, "static void\ninit_optabs_%02d "
>> -    "(struct target_optabs *optabs)\n{\n", i);
>> -   fprintf (s_file, "  bool *ena = optabs->pat_enable;\n");
>> -   unsigned start = i * patterns_per_function;
>> -   unsigned end = MIN (patterns.length (),
>> -       (i + 1) * patterns_per_function);
>> -   for (j = start; j < end; ++j)
>> -     fprintf (s_file, "  ena[%u] = HAVE_%s;\n", j, patterns[j].name);
>> -   fprintf (s_file, "}\n\n");
>> +   id = cond_rep.length ();
>> +   cond_rep.safe_push (i);
>> }
>> -
>> -      fprintf (s_file, "void\ninit_all_optabs "
>> -        "(struct target_optabs *optabs)\n{\n");
>> -      for (i = 0; i < num_init_functions; ++i)
>> - fprintf (s_file, "  init_optabs_%02d (optabs);\n", i);
>> -      fprintf (s_file, "}\n\n");
>> -    }
>> -  else
>> -    {
>> -      fprintf (s_file, "void\ninit_all_optabs "
>> -        "(struct target_optabs *optabs)\n{\n");
>> -      fprintf (s_file, "  bool *ena = optabs->pat_enable;\n");
>> -      for (i = 0; patterns.iterate (i, &p); ++i)
>> - fprintf (s_file, "  ena[%u] = HAVE_%s;\n", i, p->name);
>> -      fprintf (s_file, "}\n\n");
>> +      pat_cond.quick_push (id);
>>     }
>> 
>> +  fprintf (s_file, "static const %s pat_cond[NUM_OPTAB_PATTERNS] = {\n ",
>> +    narrowest_uint_type (cond_rep.length ()));
>> +  for (i = 0; i < pat_cond.length (); ++i)
>> +    fprintf (s_file, " %u,%s", pat_cond[i], (i % 20) == 19 ? "\n " : "");
>> +  fprintf (s_file, "\n};\n\n");
>> +
>> +  /* The conditions are local to init_all_optabs, so that each call 
>> evaluates
>> +     them for the current target options.  Force each one to bool, since a
>> +     HAVE_* macro can expand to an int-valued expression and narrowing in a
>> +     braced initializer is ill-formed.  */
>> +  fprintf (s_file, "void\ninit_all_optabs (struct target_optabs 
>> *optabs)\n{\n");
>> +  fprintf (s_file, "  const bool cond[] = {\n");
>> +  for (i = 0; i < cond_rep.length (); ++i)
>> +    fprintf (s_file, "    !!HAVE_%s,\n", patterns[cond_rep[i]].name);
> 
> Using:
> 
>  bool (HAVE_%s)
> 
> seems more direct.  OK with that change, thanks (independently of the rest,
> which I hope to get to "soon").
> 

Thanks very much for the reviews!
Here’s what I’m pushing.
Kyrill

> Richard
> 
> 
>> +  fprintf (s_file, "  };\n");
>> +  fprintf (s_file, "  bool *ena = optabs->pat_enable;\n");
>> +  fprintf (s_file, "  for (unsigned int i = 0; i < NUM_OPTAB_PATTERNS; 
>> ++i)\n");
>> +  fprintf (s_file, "    ena[i] = cond[pat_cond[i]];\n");
>> +  fprintf (s_file, "}\n\n");
>> +
>>   fprintf (s_file,
>>    "/* Returns TRUE if the target supports any of the partial vector\n"
>>    "   optabs: while_ult_optab, len_load_optab, len_store_optab,\n"


Attachment: 0001-genopinit-group-optab-patterns-by-their-availability.patch
Description: 0001-genopinit-group-optab-patterns-by-their-availability.patch

Reply via email to