> 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"
0001-genopinit-group-optab-patterns-by-their-availability.patch
Description: 0001-genopinit-group-optab-patterns-by-their-availability.patch
