https://gcc.gnu.org/g:ce66e61c785a024d217cd71c95fd1eb9aa5f3ea8
commit r17-4220-gce66e61c785a024d217cd71c95fd1eb9aa5f3ea8 Author: Kyrylo Tkachov <[email protected]> Date: Fri Aug 7 10:28:19 2026 +0200 genextract: drive insn_extract from the operand path table genextract already knows each operand's location as a path string, built by walk_rtx while it descends the pattern: a digit selects an XEXP and a letter an XVECEXP. print_path then expanded those strings back into C, so insn_extract became one switch with an arm per insn code. Patterns that extract alike share an arm, but the labels still cost a line each: on aarch64, 16068 case labels selecting 649 distinct bodies, 782KB of labels against 252KB of bodies, all inside a single 1MB function. On riscv64 it is 45386 labels. Emit the path strings as data and walk them instead. Three tables replace the switch. extract_method_of_code[] maps an insn code to an extraction method. Method 0 means the code has no pattern to extract from, which is the error case the old default arm handled, and method 1 means an old-style define_peephole, whose operand count is only known at run time: static const unsigned short extract_method_of_code[] = { 0, 650, 650, 649, 649, 648, 647, 646, 646, 646, ... extract_methods[] gives each method its operand and dup counts and its offsets into the path and dup-number pools: static const struct extract_method_d extract_methods[] = { ... { 51, 0, 4, 0 }, /* 4 operands, no dups, paths at offset 51 */ and extract_paths[] holds the paths themselves, NUL separated, operands first and dups second. "1c" is XVECEXP (XEXP (pat, 1), 0, 2), because '1' selects XEXP 1 and 'c' selects XVECEXP element 2: static const char extract_paths[] = "1c0\0001c1\0001d\0001e\0001f\0001c\0001d\0001e\000... So the arm that used to read case 16029: /* aarch64_sme_fmopsvnx8hivnx16qi */ case 16028: /* aarch64_sme_fmopavnx8hivnx16qi */ ... /* 34 more labels */ case 15740: /* aarch64_sme_fmopavnx4sivnx16qi */ ro[0] = *(ro_loc[0] = &XVECEXP (XEXP (pat, 1), 0, 2)); ro[1] = *(ro_loc[1] = &XVECEXP (XEXP (pat, 1), 0, 3)); ro[2] = *(ro_loc[2] = &XVECEXP (XEXP (pat, 1), 0, 4)); ro[3] = *(ro_loc[3] = &XVECEXP (XEXP (pat, 1), 0, 5)); ro[4] = *(ro_loc[4] = &XVECEXP (XEXP (pat, 1), 0, 6)); break; is now one shared entry "1c\0001d\0001e\0001f\0001g" in extract_paths[], one row in extract_methods[], and 36 entries in extract_method_of_code[] that name it. insn_extract itself becomes a loop over the method's operands, calling follow_extract_path once each. The terminator is spelled \000 rather than \0 because the next character is often a digit, which a shorter octal escape would absorb. insn-extract.cc drops from 1.04MB/21139 lines to 124KB/1823 lines on aarch64, 2.25MB to 246KB on riscv64, 750KB to 100KB on x86_64 and 170KB to 26KB on avr. Compile time falls by 92% and peak memory from 332MB to 166MB. insn_extract is hot, so the I measured that there's no non-noise impact on compile time over a bunch of input files. A differential harness compares the operand and dup locations the old switch and the new tables produce, for every insn code: 16116 on aarch64, 45386 on riscv64, 11335 on x86_64, 10608 on i686 and 3562 on avr. 87007 insn codes, no differences. avr covers the old-style define_peephole path, which the other four do not have. Bootstrapped on aarch64-none-linux-gnu. gcc/ChangeLog: * genextract.cc (MISSING_OPERAND_CHAR): New macro. (pathpool, dupnums): New variables. (add_path, print_string_literal, print_extractions): New functions. (print_path): Remove. (print_header): Only emit the prologue. (main): Call print_extractions. Signed-off-by: Kyrylo Tkachov <[email protected]> Diff: --- gcc/genextract.cc | 316 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 199 insertions(+), 117 deletions(-) diff --git a/gcc/genextract.cc b/gcc/genextract.cc index 44094fdc281f..62babeb3982b 100644 --- a/gcc/genextract.cc +++ b/gcc/genextract.cc @@ -304,52 +304,31 @@ walk_rtx (md_rtx_info *info, rtx x, class accum_extract *acc) } } -/* Given a PATH, representing a path down the instruction's - pattern from the root to a certain point, output code to - evaluate to the rtx at that point. */ +/* The character that marks an operand number that a pattern does not use. + It cannot clash with a path step, which is always a digit or a letter. */ +#define MISSING_OPERAND_CHAR '!' -static void -print_path (const char *path) -{ - int len = strlen (path); - int i; - - if (len == 0) - { - /* Don't emit "pat", since we may try to take the address of it, - which isn't what is intended. */ - fputs ("PATTERN (insn)", stdout); - return; - } - - /* We first write out the operations (XEXP or XVECEXP) in reverse - order, then write "pat", then the indices in forward order. */ +/* The paths of all extraction methods, concatenated. Each path is NUL + terminated; the paths of one method are adjacent, operands first and + dups second. */ +static struct obstack pathpool; - for (i = len - 1; i >= 0 ; i--) - { - if (ISLOWER (path[i]) || ISUPPER (path[i])) - fputs ("XVECEXP (", stdout); - else if (ISDIGIT (path[i])) - fputs ("XEXP (", stdout); - else - gcc_unreachable (); - } +/* The dup numbers of all extraction methods, concatenated. */ +static vec<int> dupnums; - fputs ("pat", stdout); +/* Add PATH, which is null for an operand number that the pattern skips, + to the path pool. */ - for (i = 0; i < len; i++) - { - if (ISUPPER (path[i])) - printf (", 0, %d)", path[i] - UPPER_OFFSET); - else if (ISLOWER (path[i])) - printf (", 0, %d)", path[i] - 'a'); - else if (ISDIGIT (path[i])) - printf (", %d)", path[i] - '0'); - else - gcc_unreachable (); - } +static void +add_path (const char *path) +{ + if (path) + obstack_grow (&pathpool, path, strlen (path)); + else + obstack_1grow (&pathpool, MISSING_OPERAND_CHAR); + obstack_1grow (&pathpool, '\0'); } - + static void print_header (void) { @@ -374,43 +353,203 @@ print_header (void) /* This variable is used as the \"location\" of any missing operand\n\ whose numbers are skipped by a given pattern. */\n\ static rtx junk ATTRIBUTE_UNUSED;\n"); +} + +/* Print STR as a C string literal, broken into chunks so that no output + line gets excessively long. */ + +static void +print_string_literal (const char *str, unsigned int len) +{ + printf (" \""); + for (unsigned int i = 0; i < len; i++) + { + if (str[i] == '\0') + /* Spell the terminator with all three octal digits: the next + character may be a digit, which a shorter escape would absorb. */ + printf ("\\000"); + else + putchar (str[i]); + if ((i % 60) == 59 && i + 1 < len) + printf ("\"\n \""); + } + printf ("\""); +} + +/* Print the tables that drive insn_extract, and insn_extract itself. */ + +static void +print_extractions (void) +{ + struct extraction *p; + struct code_ptr *link; + unsigned int i; + + /* Number the methods and record, for every insn code, the method that + extracts its operands. Method 0 means "not an extractable insn" and + method 1 means "an old-style define_peephole", whose operand count is + only known at run time. */ + auto_vec<unsigned int> method_of_code; + auto_vec<struct extraction *> methods; + auto_vec<unsigned int> path_start; + auto_vec<unsigned int> dup_start; + + for (link = peepholes; link; link = link->next) + { + while (method_of_code.length () <= (unsigned int) link->insn_code) + method_of_code.safe_push (0); + method_of_code[link->insn_code] = 1; + } + + obstack_init (&pathpool); + for (p = extractions; p; p = p->next) + { + unsigned int method = methods.length () + 2; + gcc_assert (method <= USHRT_MAX); + gcc_assert (p->op_count <= UCHAR_MAX && p->dup_count <= UCHAR_MAX); + path_start.safe_push (obstack_object_size (&pathpool)); + dup_start.safe_push (dupnums.length ()); + methods.safe_push (p); + for (i = 0; i < p->op_count; i++) + add_path (p->oplocs[i]); + for (i = 0; i < p->dup_count; i++) + { + add_path (p->duplocs[i]); + gcc_assert (IN_RANGE (p->dupnums[i], 0, UCHAR_MAX)); + dupnums.safe_push (p->dupnums[i]); + } + for (link = p->insns; link; link = link->next) + { + while (method_of_code.length () <= (unsigned int) link->insn_code) + method_of_code.safe_push (0); + method_of_code[link->insn_code] = method; + } + } + path_start.safe_push (obstack_object_size (&pathpool)); + + unsigned int pool_len = obstack_object_size (&pathpool); + const char *pool = XOBFINISH (&pathpool, const char *); + + printf ("/* The paths that locate the operands and the dups of each\n" + " extraction method. A path is a sequence of steps down the\n" + " pattern: a digit D selects XEXP (x, D - '0'), a lower-case\n" + " letter L selects XVECEXP (x, 0, L - 'a') and an upper-case\n" + " letter U selects XVECEXP (x, 0, U - %d). An empty path denotes\n" + " the pattern itself and '%c' an operand number that the pattern\n" + " does not use. */\n", UPPER_OFFSET, MISSING_OPERAND_CHAR); + printf ("#define UPPER_OFFSET %d\n", UPPER_OFFSET); + printf ("#define MISSING_OPERAND_CHAR '%c'\n\n", MISSING_OPERAND_CHAR); + printf ("static const char extract_paths[] =\n"); + print_string_literal (pool, pool_len); + printf (";\n\n"); + + printf ("static const unsigned char extract_dup_num[] = {"); + for (i = 0; i < dupnums.length (); i++) + printf ("%s%d,", (i % 20) == 0 ? "\n " : " ", dupnums[i]); + printf ("%s0\n};\n\n", dupnums.length () ? "\n " : ""); + + printf ("struct extract_method_d {\n" + " unsigned int paths;\n" + " unsigned int dups;\n" + " unsigned char n_operands;\n" + " unsigned char n_dups;\n" + "};\n\n"); + + printf ("static const struct extract_method_d extract_methods[] = {\n" + " { 0, 0, 0, 0 },\n" + " { 0, 0, 0, 0 },\n"); + for (i = 0; i < methods.length (); i++) + printf (" { %u, %u, %u, %u },\n", path_start[i], dup_start[i], + methods[i]->op_count, methods[i]->dup_count); + printf ("};\n\n"); + + printf ("static const unsigned short extract_method_of_code[] = {"); + for (i = 0; i < method_of_code.length (); i++) + printf ("%s%u,", (i % 20) == 0 ? "\n " : " ", method_of_code[i]); + printf ("\n};\n\n"); + + puts ("\ +/* Follow one NUL-terminated path in extract_paths from *PP, starting at\n\ + the pattern *ROOT, and return the location it selects. *PP is left\n\ + just after the path's terminator. */\n\ +\n\ +static inline rtx *\n\ +follow_extract_path (const char **pp, rtx *root)\n{\n\ + const char *p = *pp;\n\ + rtx *loc = root;\n\ + for (; *p; p++)\n\ + if (ISDIGIT (*p))\n\ + loc = &XEXP (*loc, *p - '0');\n\ + else if (ISLOWER (*p))\n\ + loc = &XVECEXP (*loc, 0, *p - 'a');\n\ + else\n\ + loc = &XVECEXP (*loc, 0, *p - UPPER_OFFSET);\n\ + *pp = p + 1;\n\ + return loc;\n\ +}\n"); puts ("\ void\n\ insn_extract (rtx_insn *insn)\n{\n\ rtx *ro = recog_data.operand;\n\ rtx **ro_loc = recog_data.operand_loc;\n\ - rtx pat = PATTERN (insn);\n\ - int i ATTRIBUTE_UNUSED; /* only for peepholes */\n\ + int icode = INSN_CODE (insn);\n\ \n\ if (flag_checking)\n\ {\n\ memset (ro, 0xab, sizeof (*ro) * MAX_RECOG_OPERANDS);\n\ memset (ro_loc, 0xab, sizeof (*ro_loc) * MAX_RECOG_OPERANDS);\n\ - }\n"); - - puts ("\ - switch (INSN_CODE (insn))\n\ + }\n\ +\n\ + unsigned int method = (icode >= 0\n\ + && icode < (int) ARRAY_SIZE (extract_method_of_code)\n\ + ? extract_method_of_code[icode] : 0);\n\ + if (method == 0)\n\ {\n\ - default:\n\ /* Control reaches here if insn_extract has been called with an\n\ - unrecognizable insn (code -1), or an insn whose INSN_CODE\n\ - corresponds to a DEFINE_EXPAND in the machine description;\n\ - either way, a bug. */\n\ - if (INSN_CODE (insn) < 0)\n\ - fatal_insn (\"unrecognizable insn:\", insn);\n\ + unrecognizable insn (code -1), or an insn whose INSN_CODE\n\ + corresponds to a DEFINE_EXPAND in the machine description;\n\ + either way, a bug. */\n\ + if (icode < 0)\n\ + fatal_insn (\"unrecognizable insn:\", insn);\n\ else\n\ - fatal_insn (\"insn with invalid code number:\", insn);\n"); + fatal_insn (\"insn with invalid code number:\", insn);\n\ + }\n\ +\n\ + if (method == 1)\n\ + {\n\ + /* An old-style define_peephole. The vector in the insn was created\n\ + just for this function and contains nothing but operands. */\n\ + for (int i = XVECLEN (PATTERN (insn), 0) - 1; i >= 0; i--)\n\ + ro[i] = *(ro_loc[i] = &XVECEXP (PATTERN (insn), 0, i));\n\ + return;\n\ + }\n\ +\n\ + const struct extract_method_d *m = &extract_methods[method];\n\ + const char *p = extract_paths + m->paths;\n\ + for (unsigned int i = 0; i < m->n_operands; i++)\n\ + if (*p == MISSING_OPERAND_CHAR)\n\ + {\n\ + ro[i] = const0_rtx;\n\ + ro_loc[i] = &junk;\n\ + p += 2;\n\ + }\n\ + else\n\ + {\n\ + rtx *loc = follow_extract_path (&p, &PATTERN (insn));\n\ + ro_loc[i] = loc;\n\ + ro[i] = *loc;\n\ + }\n\ + for (unsigned int i = 0; i < m->n_dups; i++)\n\ + {\n\ + recog_data.dup_loc[i] = follow_extract_path (&p, &PATTERN (insn));\n\ + recog_data.dup_num[i] = extract_dup_num[m->dups + i];\n\ + }\n}"); } int main (int argc, const char **argv) { - unsigned int i; - struct extraction *p; - struct code_ptr *link; - const char *name; - progname = "genextract"; if (!init_rtx_reader_args (argc, argv)) @@ -444,64 +583,7 @@ main (int argc, const char **argv) return FATAL_EXIT_CODE; print_header (); - - /* Write out code to handle peepholes and the insn_codes that it should - be called for. */ - if (peepholes) - { - for (link = peepholes; link; link = link->next) - printf (" case %d:\n", link->insn_code); - - /* The vector in the insn says how many operands it has. - And all it contains are operands. In fact, the vector was - created just for the sake of this function. We need to set the - location of the operands for sake of simplifications after - extraction, like eliminating subregs. */ - puts (" for (i = XVECLEN (pat, 0) - 1; i >= 0; i--)\n" - " ro[i] = *(ro_loc[i] = &XVECEXP (pat, 0, i));\n" - " break;\n"); - } - - /* Write out all the ways to extract insn operands. */ - for (p = extractions; p; p = p->next) - { - for (link = p->insns; link; link = link->next) - { - i = link->insn_code; - name = get_insn_name (i); - if (name) - printf (" case %d: /* %s */\n", i, name); - else - printf (" case %d:\n", i); - } - - for (i = 0; i < p->op_count; i++) - { - if (p->oplocs[i] == 0) - { - printf (" ro[%d] = const0_rtx;\n", i); - printf (" ro_loc[%d] = &junk;\n", i); - } - else - { - printf (" ro[%d] = *(ro_loc[%d] = &", i, i); - print_path (p->oplocs[i]); - puts (");"); - } - } - - for (i = 0; i < p->dup_count; i++) - { - printf (" recog_data.dup_loc[%d] = &", i); - print_path (p->duplocs[i]); - puts (";"); - printf (" recog_data.dup_num[%d] = %d;\n", i, p->dupnums[i]); - } - - puts (" break;\n"); - } - - puts (" }\n}"); + print_extractions (); fflush (stdout); return (ferror (stdout) != 0 ? FATAL_EXIT_CODE : SUCCESS_EXIT_CODE); }
