From: Kyrylo Tkachov <[email protected]>
genmatch writes one function for each outer operation. Output partitioning
places these functions whole, so one operation matcher can dominate an output
part. On current trunk, gimple_simplify_COND_EXPR has 58,429 lines and
1.9 MB of generated source. Compiling it with profile generation for Armv8-A
Thumb produces 1,152 "conditional branch out of range" assembler errors.
This causes arm-linux-gnueabihf lto profiledbootstrap to fail after my
recent "genmatch: Handle overlapping delayed operator sets" commit.
Split large per-operation matchers between top-level alternatives. End each
chunk with a call in tail position to the next chunk, place the next chunk in
the shortest output part, and forward the matcher parameters.
Keep each overlap-connected dispatch group and each DT_MATCH subtree in one
chunk. This keeps the pattern order unchanged. Use one tenth of the average
number of decision-tree nodes per output part as the target chunk size. A
complete group can exceed this target. Keep single-file output unchanged
because it has no shared declaration header.
On aarch64-none-linux-gnu with the default ten parts, the largest-to-smallest
source size ratio changes from 2.43 to 1.10 for GIMPLE and from 1.60 to 1.09
for GENERIC. This allows for the gimle-match stuff to compile faster
with parallel cores.
The split Arm profile build compiles all ten GIMPLE parts. Its largest
COND_EXPR function is about 112 kB. A check of emitted pattern markers found
the same order in all 1,902 logical matcher functions with 2, 10, 40, and 64
parts.
Bootstrapped and regression tested on aarch64-none-linux-gnu.
The Armv8-A Thumb profile build of all ten GIMPLE matcher parts also passed.
Ok for trunk?
Thanks,
Kyrill
gcc/ChangeLog:
* genmatch.cc (MATCHER_FN_FILE_FRACTION): New constant.
(class fn_splitter): New class.
(fn_splitter::print_params, fn_splitter::print_args)
(fn_splitter::cut, fn_splitter::add_subtree)
(fn_splitter::emit_slice, fn_splitter::emit_alternatives): New
functions.
(collect_alt_units): New function.
(dt_node::gen_kids): Add an optional fn_splitter argument and use it
at valid function-level boundaries.
(decision_tree::gen): Set the matcher function budget and use
fn_splitter for large per-operation matchers.
Signed-off-by: Kyrylo Tkachov <[email protected]>
---
gcc/genmatch.cc | 327 ++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 319 insertions(+), 8 deletions(-)
diff --git a/gcc/genmatch.cc b/gcc/genmatch.cc
index 42a04436c57..155c7e13ac6 100644
--- a/gcc/genmatch.cc
+++ b/gcc/genmatch.cc
@@ -2516,6 +2516,7 @@ lower (vec<simplify *>& simplifiers, bool gimple)
simplifies and has those as its leafs. */
class dt_simplify;
+class fn_splitter;
/* A hash-map collecting semantically equivalent leafs in the decision
tree for splitting out to separate functions. */
@@ -2571,7 +2572,7 @@ public:
virtual void gen (FILE *, int, bool, int) {}
- void gen_kids (FILE *, int, bool, int, bool);
+ void gen_kids (FILE *, int, bool, int, bool, fn_splitter * = NULL);
void gen_kids_1 (FILE *, int, bool, int,
const vec<dt_operand *> &, const vec<dt_operand *> &,
const vec<dt_operand *> &, const vec<dt_operand *> &,
@@ -4259,11 +4260,277 @@ emit_fn_group (FILE *f, int indent, bool gimple, int
depth,
fprintf_indent (f, indent, " break;\n");
}
+/* Target matcher functions at this fraction of the decision-tree nodes that
+ one output file receives on average. */
+
+static const unsigned int MATCHER_FN_FILE_FRACTION = 10;
+
+/* Split a generated matcher across functions. Each new function continues
+ matching where the previous function stopped. */
+
+class fn_splitter
+{
+public:
+ /* These are the alternative vectors accepted by gen_kids_1, in their
+ emission order. ALT_OTHERS identifies the separate dt_node vector. */
+ enum alt_cat
+ {
+ ALT_GIMPLE_EXPRS,
+ ALT_FNS,
+ ALT_GENERIC_EXPRS,
+ ALT_GENERIC_FNS,
+ ALT_PREDS,
+ ALT_OTHERS
+ };
+
+ /* One complete group from category CAT at INDEX. NODES estimates the
+ amount of generated code for the group. */
+ struct alt_unit
+ {
+ alt_cat cat;
+ unsigned index;
+ unsigned nodes;
+ };
+
+ fn_splitter (vec<FILE *> &files_, FILE *f_, const char *fname_,
+ bool gimple_, unsigned nops_, unsigned budget_)
+ : f (f_), files (files_), fname (fname_), gimple (gimple_),
+ nops (nops_), budget (budget_), nchunks (0), nodes (0)
+ {}
+
+ FILE *add_subtree (dt_node *);
+ void emit_alternatives (dt_node *, int, int,
+ const vec<dt_operand *> &,
+ const vec<dt_operand *> &,
+ const vec<dt_operand *> &,
+ const vec<dt_operand *> &,
+ const vec<dt_operand *> &,
+ const vec<dt_node *> &);
+
+ /* The output file for the current function. */
+ FILE *f;
+
+private:
+ /* Return true if adding MORE nodes to HAVE nodes requires a new function.
+ Always let an empty function accept one complete alternative. A zero
+ budget disables splitting. */
+ bool full (unsigned have, unsigned more) const
+ {
+ return budget && have && have + more > budget;
+ }
+
+ void print_params (FILE *);
+ void print_args (FILE *);
+ void cut ();
+ void emit_slice (dt_node *, int, int, const vec<alt_unit> &, unsigned,
+ unsigned, const vec<dt_operand_group *> *const *,
+ const vec<dt_operand *> &, const vec<dt_node *> &);
+
+ /* All output files used for the generated matcher. */
+ vec<FILE *> &files;
+ /* The base name for new functions. */
+ const char *fname;
+ /* True for GIMPLE matching and false for GENERIC matching. */
+ bool gimple;
+ /* The number of operands passed to each function. */
+ unsigned nops;
+ /* The target maximum number of nodes accumulated in one function. */
+ unsigned budget;
+ /* The number of new functions. */
+ unsigned nchunks;
+ /* The estimated nodes in the current function. */
+ unsigned nodes;
+};
+
+/* Print the parameter list of a new function to TO. Also print its
+ declaration to the generated header. */
+
+void
+fn_splitter::print_params (FILE *to)
+{
+ if (gimple)
+ fp_decl (to, " (gimple_match_op *ARG_UNUSED (res_op),"
+ " gimple_seq *ARG_UNUSED (seq),\n"
+ "\t\t tree (*valueize)(tree) ATTRIBUTE_UNUSED,\n"
+ "\t\t code_helper ARG_UNUSED (code),"
+ " tree ARG_UNUSED (type)");
+ else
+ fp_decl (to, " (location_t ARG_UNUSED (loc),"
+ " enum tree_code ARG_UNUSED (code),\n"
+ "\t\t const tree ARG_UNUSED (type)");
+ for (unsigned i = 0; i < nops; ++i)
+ fp_decl (to, ", tree ARG_UNUSED (_p%d)", i);
+ fp_decl (to, ")");
+}
+
+/* Print to TO the arguments that forward the current matcher state to a new
+ function. */
+
+void
+fn_splitter::print_args (FILE *to)
+{
+ fputs (gimple ? " (res_op, seq, valueize, code, type"
+ : " (loc, code, type", to);
+ for (unsigned i = 0; i < nops; ++i)
+ fprintf (to, ", _p%d", i);
+ fputs (")", to);
+}
+
+/* End the current function with a call to a new function. Start the new
+ function in the shortest output file. */
+
+void
+fn_splitter::cut ()
+{
+ /* Multiple output files require a header, which also carries the
+ declarations for functions emitted in a different file. */
+ gcc_assert (header_file);
+
+ const char *ret_type = gimple ? "bool" : "tree";
+ ++nchunks;
+ fprintf (f, " return %s_chunk_%u", fname, nchunks);
+ print_args (f);
+ fputs (";\n}\n", f);
+
+ f = choose_output (files);
+ fp_decl (f, "\n%s\n%s_chunk_%u", ret_type, fname, nchunks);
+ print_params (f);
+ fp_decl_done (f, "");
+ fputs ("{\n", f);
+ fprintf_indent (f, 2, "const bool debug_dump = "
+ "dump_file && (dump_flags & TDF_FOLDING);\n");
+ nodes = 0;
+}
+
+/* Add NODE as a complete subtree. Start a new function before NODE if the
+ current one is full. Return the output file for NODE. */
+
+FILE *
+fn_splitter::add_subtree (dt_node *node)
+{
+ if (full (nodes, node->total_size))
+ cut ();
+ nodes += node->total_size;
+ return f;
+}
+
+/* Emit alternatives UNITS[FIRST, LAST) for NODE. GROUPS holds the grouped
+ expression and function alternatives. PREDS and OTHERS hold the remaining
+ operands and decision-tree nodes. */
+
+void
+fn_splitter::emit_slice (dt_node *node, int indent, int depth,
+ const vec<alt_unit> &units, unsigned first,
+ unsigned last,
+ const vec<dt_operand_group *> *const *groups,
+ const vec<dt_operand *> &preds,
+ const vec<dt_node *> &others)
+{
+ auto_vec<dt_operand *> sub[ALT_OTHERS];
+ auto_vec<dt_node *> sub_others;
+
+ for (unsigned i = first; i < last; ++i)
+ if (units[i].cat == ALT_OTHERS)
+ sub_others.safe_push (others[units[i].index]);
+ else if (units[i].cat == ALT_PREDS)
+ sub[ALT_PREDS].safe_push (preds[units[i].index]);
+ else
+ for (auto op : (*groups[units[i].cat])[units[i].index]->operands)
+ sub[units[i].cat].safe_push (op);
+
+ node->gen_kids_1 (f, indent, gimple, depth, sub[ALT_GIMPLE_EXPRS],
+ sub[ALT_GENERIC_EXPRS], sub[ALT_FNS],
+ sub[ALT_GENERIC_FNS], sub[ALT_PREDS], sub_others, false);
+}
+
+/* Append the complete operand GROUPS to UNITS as category CAT. */
+
+static void
+collect_alt_units (vec<fn_splitter::alt_unit> &units,
+ fn_splitter::alt_cat cat,
+ const vec<dt_operand_group *> &groups)
+{
+ for (unsigned i = 0; i < groups.length (); ++i)
+ {
+ /* Only alternatives with a level-one parent inspect a parameter of the
+ matcher entry function. */
+ unsigned nodes = 0;
+ for (auto op : groups[i]->operands)
+ {
+ gcc_assert (op->parent && op->parent->level == 1);
+ nodes += op->total_size;
+ }
+ units.safe_push (fn_splitter::alt_unit { cat, i, nodes });
+ }
+}
+
+/* Emit the alternatives for NODE in their normal category order. Start a
+ new function before an alternative that exceeds the remaining budget. */
+
+void
+fn_splitter::emit_alternatives (dt_node *node, int indent, int depth,
+ const vec<dt_operand *> &gimple_exprs,
+ const vec<dt_operand *> &generic_exprs,
+ const vec<dt_operand *> &fns,
+ const vec<dt_operand *> &generic_fns,
+ const vec<dt_operand *> &preds,
+ const vec<dt_node *> &others)
+{
+ auto_delete_vec<dt_operand_group> group_storage[ALT_PREDS];
+ collect_operand_groups (gimple_exprs, group_storage[ALT_GIMPLE_EXPRS]);
+ collect_operand_groups (fns, group_storage[ALT_FNS]);
+ collect_operand_groups (generic_exprs,
+ group_storage[ALT_GENERIC_EXPRS]);
+ collect_operand_groups (generic_fns, group_storage[ALT_GENERIC_FNS]);
+
+ const vec<dt_operand_group *> *groups[ALT_PREDS];
+ for (unsigned cat = ALT_GIMPLE_EXPRS; cat < ALT_PREDS; ++cat)
+ groups[cat] = &group_storage[cat];
+
+ auto_vec<alt_unit> units;
+ for (unsigned cat = ALT_GIMPLE_EXPRS; cat < ALT_PREDS; ++cat)
+ collect_alt_units (units, (alt_cat) cat, *groups[cat]);
+ for (unsigned i = 0; i < preds.length (); ++i)
+ {
+ gcc_assert (preds[i]->parent && preds[i]->parent->level == 1);
+ units.safe_push (alt_unit { ALT_PREDS, i, preds[i]->total_size });
+ }
+ for (unsigned i = 0; i < others.length (); ++i)
+ units.safe_push (alt_unit { ALT_OTHERS, i, others[i]->total_size });
+
+ if (units.is_empty ())
+ return;
+
+ unsigned first = 0;
+ unsigned pending = 0;
+ for (unsigned i = 0; i < units.length (); ++i)
+ {
+ if (full (nodes + pending, units[i].nodes))
+ {
+ if (first != i)
+ emit_slice (node, indent, depth, units, first, i, groups, preds,
+ others);
+ cut ();
+ first = i;
+ pending = 0;
+ }
+ pending += units[i].nodes;
+ }
+ emit_slice (node, indent, depth, units, first, units.length (), groups,
+ preds, others);
+ nodes += pending;
+}
+
/* Generate matching code for the children of the decision tree node. */
void
-dt_node::gen_kids (FILE *f, int indent, bool gimple, int depth, bool in_gcond)
+dt_node::gen_kids (FILE *f, int indent, bool gimple, int depth, bool in_gcond,
+ fn_splitter *sp)
{
+ /* A gcond matcher starts inside an open scope and is not a valid function
+ boundary. */
+ gcc_assert (!sp || !in_gcond);
+
auto_vec<dt_operand *> gimple_exprs;
auto_vec<dt_operand *> generic_exprs;
auto_vec<dt_operand *> fns;
@@ -4332,10 +4599,31 @@ dt_node::gen_kids (FILE *f, int indent, bool gimple,
int depth, bool in_gcond)
Like DT_TRUE, DT_MATCH serves as a barrier as it can cause
dependent matches to get out-of-order. Generate code now
for what we have collected sofar. */
- gen_kids_1 (f, indent, gimple, depth, gimple_exprs, generic_exprs,
- fns, generic_fns, preds, others, in_gcond);
+ if (sp)
+ {
+ sp->emit_alternatives (this, indent, depth, gimple_exprs,
+ generic_exprs, fns, generic_fns, preds,
+ others);
+ f = sp->f;
+ }
+ else
+ gen_kids_1 (f, indent, gimple, depth, gimple_exprs,
+ generic_exprs, fns, generic_fns, preds, others,
+ in_gcond);
/* And output the true operand itself. */
- kids[i]->gen (f, indent, gimple, depth);
+ if (sp && kids[i]->type == dt_node::DT_TRUE)
+ {
+ /* A DT_TRUE node emits no code and opens no scope. Its children
+ remain valid function-level split points. */
+ kids[i]->gen_kids (f, indent, gimple, depth, false, sp);
+ f = sp->f;
+ }
+ else
+ {
+ if (sp)
+ f = sp->add_subtree (kids[i]);
+ kids[i]->gen (f, indent, gimple, depth);
+ }
gimple_exprs.truncate (0);
generic_exprs.truncate (0);
fns.truncate (0);
@@ -4348,8 +4636,12 @@ dt_node::gen_kids (FILE *f, int indent, bool gimple, int
depth, bool in_gcond)
}
/* Generate code for the remains. */
- gen_kids_1 (f, indent, gimple, depth, gimple_exprs, generic_exprs,
- fns, generic_fns, preds, others, in_gcond);
+ if (sp)
+ sp->emit_alternatives (this, indent, depth, gimple_exprs, generic_exprs,
+ fns, generic_fns, preds, others);
+ else
+ gen_kids_1 (f, indent, gimple, depth, gimple_exprs, generic_exprs,
+ fns, generic_fns, preds, others, in_gcond);
}
/* Generate matching code for the children of the decision tree node. */
@@ -5324,6 +5616,14 @@ decision_tree::gen (vec <FILE *> &files, bool gimple)
}
fprintf (stderr, "removed %u duplicate tails\n", rcnt);
+ /* A file receives about ROOT->TOTAL_SIZE / NFILES decision-tree nodes.
+ Keep matcher functions small enough to spread them across the files.
+ Keep single-file output unchanged. */
+ unsigned nfiles = files.length ();
+ unsigned budget = (nfiles > 1
+ ? root->total_size / nfiles / MATCHER_FN_FILE_FRACTION
+ : 0);
+
for (unsigned n = 1; n <= 7; ++n)
{
bool has_kids_p = false;
@@ -5366,7 +5666,18 @@ decision_tree::gen (vec <FILE *> &files, bool gimple)
fprintf (f, "{\n");
fprintf_indent (f, 2, "const bool debug_dump = "
"dump_file && (dump_flags & TDF_FOLDING);\n");
- dop->gen_kids (f, 2, gimple, 0, false);
+ if (budget && dop->total_size > budget)
+ {
+ char *fname = xasprintf ("genmatch_%s_simplify_%s",
+ gimple ? "gimple" : "generic",
+ e->operation->id);
+ fn_splitter sp (files, f, fname, gimple, n, budget);
+ dop->gen_kids (f, 2, gimple, 0, false, &sp);
+ f = sp.f;
+ free (fname);
+ }
+ else
+ dop->gen_kids (f, 2, gimple, 0, false);
if (gimple)
fprintf (f, " return false;\n");
else
--
2.50.1 (Apple Git-155)