https://gcc.gnu.org/g:d3481fc3f8746f823d9d4bc1fd0e5d4384d0fc37
commit r17-3999-gd3481fc3f8746f823d9d4bc1fd0e5d4384d0fc37 Author: Andrea Pinski <[email protected]> Date: Sun Aug 16 22:48:14 2026 -0700 match: allow c variable for the code/operation of the result [PR126912] This allows a C variable for the code of the result to reduce how many for loops are needed and reduces the code size of the generated files by a factor of 6 (in some cases). This should allow for arm to build again at -O0. The way this is done is adding an optional part to the with expression which defines an C identifier (but not explictly in the C code). An example of this is: ``` (for bitop (bit_and bit_ior bit_xor simple_comparison) (for cmp1 (tcc_comparison) (for cmp2 (tcc_comparison) (simplify (bitop (cmp1 @0 @1) (cmp2 @0 @1)) (with rescmpcode (tcc_comparison) { tree_code rescmpcode; tree res; bool honor_nans = HONOR_NANS (@0); rescmpcode = combine_comparisons (bitop, cmp1, cmp2, type, honor_nans, &res); } (if (rescmpcode == INTEGER_CST) { res; } (if (TREE_CODE_CLASS (rescmpcode) == tcc_comparison) (rescmpcode @0 @1)))))))) ``` For comparisons with combine_comparisons/invert_comparisons we would need to have one more for loop and that would cause an explosion of code. Changes since v2: * v3: changed syntax to be explict define the operator rather than implicit. Bootstrapped and tested on x86_64-linux-gnu. PR middle-end/126912 gcc/ChangeLog: * doc/match-and-simplify.texi: Document new with optional operands. * genmatch.cc (fprint_indent): New function. (class c_code_id): New class. (c_code_id::gen_checking): New mthod. (get_operator): For c_code_id, mark it as used too. (lower_for): Treat c_code_id like a function. (get_operand_type): Handle c_code_id with cmps set as a comparison. (expr::gen_transform): Likewise. Call gen_checking for c_code_id and handle c_code_id for generic for function and tree code case. (dt_simplify::gen_1): Handle c_code_id. (parser::parse_c_with_ids): New method. (parser::parse_result): Handle optional part of with. * match.pd (`(a CMP1 b) bitop (a CMP2 b)`): Remove rcmp loop and update for the new syntax. (other comparison loops): Update for the new syntax. Signed-off-by: Andrea Pinski <[email protected]> Diff: --- gcc/doc/match-and-simplify.texi | 21 ++++ gcc/genmatch.cc | 265 ++++++++++++++++++++++++++++++++++++++-- gcc/match.pd | 103 ++++++++-------- 3 files changed, 327 insertions(+), 62 deletions(-) diff --git a/gcc/doc/match-and-simplify.texi b/gcc/doc/match-and-simplify.texi index b187dd275611..98885747e3f3 100644 --- a/gcc/doc/match-and-simplify.texi +++ b/gcc/doc/match-and-simplify.texi @@ -361,6 +361,27 @@ Usually the types of the generated result expressions are determined from the context, but sometimes like in the above case it is required that you specify them explicitly. +In addition, with can simplify a resulting operation +and its possible operations. Note currently functions and tree +codes cannot be intermixed as possible resulting operations. +The C expression of the @code{with} needs to fully declare +the variable too. + +@smallexample + (simplify + (bit_xor (cmp @@0 @@1) integer_truep) + (with rescmpcode (tcc_comparison) + @{ + enum tree_code rescmpcode; + rescmpcode = invert_tree_comparison (cmp, HONOR_NANS (@@0)); + @} + (if (TREE_CODE_CLASS (rescmpcode) == tcc_comparison) + (rescmpcode @@0 @@1)))) +@end smallexample + +Here rescmpcode is declared as a possible resulting operation where +the tree codes could be tcc_comparison. + Another modifier for generated expressions is @code{^} which tells the machinery to try more matches for some special cases. For example, normally the @code{cond} only allows the gimple diff --git a/gcc/genmatch.cc b/gcc/genmatch.cc index 1bc5875d7dd6..71a47f95996a 100644 --- a/gcc/genmatch.cc +++ b/gcc/genmatch.cc @@ -841,6 +841,15 @@ fprintf_indent (FILE *f, unsigned int indent, const char *format, ...) va_end (ap); } +// Prints INDENT spaces to F. +static void +fprint_indent (FILE *f, unsigned int indent) +{ + for (; indent >= 8; indent -= 8) + fputc ('\t', f); + fprintf (f, "%*s", indent, ""); +} + /* Secondary stream for fp_decl. */ static FILE *header_file; @@ -1121,7 +1130,7 @@ comparison_code_p (enum tree_code code) class id_base : public nofree_ptr_hash<id_base> { public: - enum id_kind { CODE, FN, PREDICATE, USER, NULL_ID } kind; + enum id_kind { CODE, FN, PREDICATE, USER, C_CODE_ID, NULL_ID } kind; id_base (id_kind, const char *, int = -1); @@ -1199,6 +1208,102 @@ public: vec<simplify *> matchers; }; +// Identifier that maps to a operator defined by a 'C' identifier. +// Contains code in the id +// Defined in the `with` expressions. + +class c_code_id : public id_base +{ +public: + c_code_id (const char *id_, location_t loc) + : id_base (id_base::C_CODE_ID, xstrdup (id_)), m_loc (loc) {} + static id_base *get_c_code_id (const char *); + ~c_code_id() { delete const_cast<char*>(id); } + vec<id_base*> possible_values{vNULL}; + // These are cached values based on possible_values. + // This contains just coparisons. + bool m_cmp = false; + // Possible values are tree code only. + bool m_code_only = false; + // Possible values are (combined) functions only. + bool m_fn_only = false; + + // Marking so we can detect if the declaration was done in C code. + bool used = false; + location_t m_loc; + void gen_checking (FILE *, int); +}; + + +/* Generates the checking code to make sure that the value of the identifier + is only one of the possible values. + F is where the printed to. INDENT is the initial indentation of the + code. */ + +void +c_code_id::gen_checking (FILE *f, int indent) +{ + // This checking is only done when checking is enabled. + fprintf_indent (f, indent, "if (flag_checking)\n"); + indent+=2; + + fprintf_indent (f, indent, "{\n"); + indent+=2; + + // For cases were there are possible codes, emit + // an assert that the identifier is not an ERROR_MARK. + if (!m_fn_only) + fprintf_indent (f, indent, "gcc_assert (%s != ERROR_MARK);\n", id); + + // For comparisons, emit an quick assert first. + if (m_cmp) + fprintf_indent (f, indent, "gcc_assert (TREE_CODE_CLASS (%s) == tcc_comparison);\n", id); + + // Currently only support the case where there is only either functions + // or tree codes; not both. + + if (m_code_only) + fprintf_indent (f, indent, "switch ((tree_code)%s)\n", id); + else + fprintf_indent (f, indent, "switch ((combined_fn)%s)\n", id); + + indent+=2; + fprintf_indent (f, indent, "{\n"); + int t = 0; + + // Emit the possible values as a case. + for (id_base *i : possible_values) + { + if (t == 0) + fprint_indent (f, indent); + if (*id == CONVERT_EXPR || *id == NOP_EXPR) + fprintf (f, "CASE_CONVERT:"); + else + fprintf (f, "case %s:", i->id); + t++; + // Putting 4 on each line for more compact source. + if (t < 4) + fprintf (f, " "); + else + { + fprintf (f, "\n"); + t = 0; + } + } + if (t != 0) + fprintf (f, "\n"); + fprintf_indent (f, indent, " break;\n"); + fprintf_indent (f, indent, "default:\n"); + fprintf_indent (f, indent, " gcc_unreachable ();\n"); + fprintf_indent (f, indent, "}\n"); + indent-=2; + + indent-=2; + fprintf_indent (f, indent, "}\n"); + indent-=2; +} + + /* Identifier that maps to a operator defined by a 'for' directive. */ class user_id : public id_base @@ -1244,6 +1349,14 @@ is_a_helper <user_id *>::test (id_base *id) return id->kind == id_base::USER; } +template<> +template<> +inline bool +is_a_helper <c_code_id *>::test (id_base *id) +{ + return id->kind == id_base::C_CODE_ID; +} + /* If ID has a pair of consecutive, commutative operands, return the index of the first, otherwise return -1. */ @@ -1399,6 +1512,11 @@ get_operator (const char *id, bool allow_null = false) /* If this is a user-defined identifier track whether it was used. */ if (user_id *uid = dyn_cast<user_id *> (op)) uid->used = true; + + // If this was an C code id, we track to make sure it was declared + // in the C part of the expression. + if (c_code_id *uid = dyn_cast<c_code_id *> (op)) + uid->used = true; return op; } @@ -1617,8 +1735,9 @@ class with_expr : public operand public: with_expr (location_t loc) : operand (OP_WITH, loc), with (NULL), subexpr (NULL) {} - c_expr *with; + c_expr *with{NULL}; operand *subexpr; + vec<c_code_id*> m_ids{}; }; template<> @@ -2301,6 +2420,8 @@ lower_for (simplify *sin, vec<simplify *>& simplifiers) } else if (is_a <fn_id *> (ids[i]->substitutes[j])) ; + else if (is_a <c_code_id *> (ids[i]->substitutes[j])) + ; else can_delay_subst = false; } @@ -3222,8 +3343,10 @@ get_operand_type (id_base *op, unsigned pos, else if (*op == REALPART_EXPR || *op == IMAGPART_EXPR) return other_oprnd_type; - else if (is_a <operator_id *> (op) - && strcmp (as_a <operator_id *> (op)->tcc, "tcc_comparison") == 0) + else if ((is_a <operator_id *> (op) + && strcmp (as_a <operator_id *> (op)->tcc, "tcc_comparison") == 0) + || (is_a <c_code_id *> (op) + && as_a <c_code_id *>(op)->m_cmp)) return other_oprnd_type; else if (*op == COND_EXPR && pos == 0) @@ -3285,8 +3408,10 @@ expr::gen_transform (FILE *f, int indent, const char *dest, bool gimple, depth); type = optype; } - else if (is_a <operator_id *> (opr) - && !strcmp (as_a <operator_id *> (opr)->tcc, "tcc_comparison")) + else if ((is_a <operator_id *> (opr) + && !strcmp (as_a <operator_id *> (opr)->tcc, "tcc_comparison")) + || (is_a <c_code_id *> (opr) + && as_a <c_code_id *> (opr)->m_cmp)) { /* comparisons use boolean_type_node (or what gets in), but their operands need to figure out the types themselves. */ @@ -3354,6 +3479,10 @@ expr::gen_transform (FILE *f, int indent, const char *dest, bool gimple, else opr_name = operation->id; + // For C code ID, emit the checking for possible values. + if (c_code_id *op_c = dyn_cast<c_code_id*> (operation)) + op_c->gen_checking (f, indent); + if (gimple) { if (child_in_place) @@ -3444,6 +3573,7 @@ expr::gen_transform (FILE *f, int indent, const char *dest, bool gimple, } else { + c_code_id *cid = dyn_cast <c_code_id *> (opr); if (possible_noop_convert (opr)) { fprintf_indent (f, indent, "if (TREE_TYPE (_o%d[0]) != %s)\n", @@ -3451,7 +3581,8 @@ expr::gen_transform (FILE *f, int indent, const char *dest, bool gimple, fprintf_indent (f, indent + 2, "{\n"); indent += 4; } - if (opr->kind == id_base::CODE) + if (opr->kind == id_base::CODE + || (cid && cid->m_code_only)) fprintf_indent (f, indent, "_r%d = fold_build%d_loc (loc, %s, %s", depth, ops.length(), opr_name, type); else @@ -3460,7 +3591,8 @@ expr::gen_transform (FILE *f, int indent, const char *dest, bool gimple, for (unsigned i = 0; i < ops.length (); ++i) fprintf (f, ", _o%d[%u]", depth, i); fprintf (f, ");\n"); - if (opr->kind != id_base::CODE) + if (opr->kind != id_base::CODE + && !(cid && cid->m_code_only)) { fprintf_indent (f, indent, "if (!_r%d)\n", depth); fprintf_indent (f, indent, " goto %s;\n", fail_label); @@ -4466,6 +4598,8 @@ dt_simplify::gen_1 (FILE *f, int indent, bool gimple, operand *result) opr = uid->substitutes[0]; else if (is_a <predicate_id *> (opr)) is_predicate = true; + if (c_code_id *cid = dyn_cast <c_code_id *> (opr)) + cid->gen_checking (f, indent); if (!is_predicate) fprintf_indent (f, indent, "res_op->set_op (%s, type, %d);\n", *e->operation == CONVERT_EXPR @@ -4633,7 +4767,10 @@ dt_simplify::gen_1 (FILE *f, int indent, bool gimple, operand *result) "_r = non_lvalue_loc (loc, res_op0);\n"); else { - if (is_a <operator_id *> (opr)) + c_code_id *cid = dyn_cast <c_code_id *> (opr); + if (cid) + cid->gen_checking (f, indent); + if (is_a <operator_id *> (opr) || (cid && cid->m_code_only)) fprintf_indent (f, indent, "_r = fold_build%d_loc (loc, %s, type", e->ops.length (), @@ -4647,7 +4784,7 @@ dt_simplify::gen_1 (FILE *f, int indent, bool gimple, operand *result) for (unsigned j = 0; j < e->ops.length (); ++j) fprintf (f, ", res_op%d", j); fprintf (f, ");\n"); - if (!is_a <operator_id *> (opr)) + if (!is_a <operator_id *> (opr) && !is_a<c_code_id *> (opr)) { fprintf_indent (f, indent, "if (!_r)\n"); fprintf_indent (f, indent, " goto %s;\n", fail_label); @@ -5153,6 +5290,7 @@ private: operand *parse_capture (operand *, bool); operand *parse_expr (); c_expr *parse_c_expr (cpp_ttype); + void parse_c_with_ids (vec<c_code_id*> *); operand *parse_op (); void record_operlist (location_t, user_id *); @@ -5545,6 +5683,100 @@ parser::parse_expr () while (1); } +// Parse the C identifiers part of the `with` expressions. +// `id (id+)` + +void +parser::parse_c_with_ids (vec<c_code_id*> *ids) +{ + const cpp_token *token; + while (1) + { + token = peek (); + if (token->type != CPP_NAME) + break; + + // Insert the C defined operators into the operator hash. + const char *id = get_ident (); + if (get_operator (id, true) != NULL) + fatal_at (token, "operator already defined"); + c_code_id *op = new c_code_id (id, token->src_loc); + id_base **slot = operators->find_slot_with_hash (op, op->hashval, INSERT); + *slot = op; + ids->safe_push (op); + + eat_token (CPP_OPEN_PAREN); + while ((token = peek_ident ()) != 0) + { + const char *oper = get_ident (); + id_base *idb = get_operator (oper, true); + if (idb == NULL) + fatal_at (token, "no such operator '%s'", oper); + if (idb == null_id) + fatal_at (token, "null operator not allowed in possible opperators"); + if (is_a<predicate_id*> (idb)) + fatal_at (token, "predicate oeprator not allowed in possible opperators"); + if (is_a<c_code_id*> (idb)) + fatal_at (token, "A value operator is not in possible opperators"); + if (user_id *p = dyn_cast<user_id*> (idb)) + { + if (p->is_oper_list) + op->possible_values.safe_splice (p->substitutes); + else + fatal_at (token, "iterators cannot be used as possible opperators"); + } + else + op->possible_values.safe_push (idb); + } + token = expect (CPP_CLOSE_PAREN); + if (!op->possible_values.length ()) + fatal_at (token, "possible operator-list should be non empty"); + bool cmps = true; + bool code_only = true; + bool fn_only = true; + // Remove duplicates. + // Add a warning? + for (size_t i = 0; i < op->possible_values.length (); i++) + { + for (size_t j = i + 1; j < op->possible_values.length (); j++) + { + if (op->possible_values[i] == op->possible_values[j] + || (*op->possible_values[i] == CONVERT_EXPR + && *op->possible_values[j] == NOP_EXPR) + || (*op->possible_values[j] == CONVERT_EXPR + && *op->possible_values[i] == NOP_EXPR)) + { + op->possible_values.unordered_remove (j); + j--; + } + } + } + + // Cache some information about the possible operators: + // compares, functions only, tree code only. + for (id_base *i : op->possible_values) + { + if (operator_id *ii = dyn_cast <operator_id *> (i)) + { + fn_only = false; + if (strcmp (ii->tcc, "tcc_comparison") != 0) + cmps = false; + } + else if (is_a <fn_id*>(i)) + cmps = code_only = false; + else + gcc_unreachable (); + } + op->m_cmp = cmps; + op->m_code_only = code_only; + op->m_fn_only = fn_only; + // FIXME: Add support for a mixed case, + // generic handling is more complex + if (!code_only && !fn_only) + fatal_at (token, "Cannot support a mix of functions and tree codes at this point."); + } +} + /* Lex native C code delimited by START recording the preprocessing tokens for later processing. c_expr = ('{'|'(') <pp token>... ('}'|')') */ @@ -5737,11 +5969,24 @@ parser::parse_result (operand *result, predicate_id *matcher) { eat_ident ("with"); with_expr *withe = new with_expr (token->src_loc); + parse_c_with_ids (&withe->m_ids); /* Parse (with c-expr expr) as (if-with (true) expr). */ withe->with = parse_c_expr (CPP_OPEN_BRACE); + // Check to see if the id was declared in the with statement. + // This is a basic check; there can be false positives + for (c_code_id *id : withe->m_ids) + { + if (!id->used) + { + fatal_at (id->m_loc, "operator %s declared inside 'with' but not in C expression", id->id); + } + } withe->with->nr_stmts = 0; withe->subexpr = parse_result (result, matcher); eat_token (CPP_CLOSE_PAREN); + // Remove operators from the hash as they are now out of scope. + for (c_code_id *id : withe->m_ids) + operators->remove_elt (id); return withe; } else if (peek_ident ("switch")) diff --git a/gcc/match.pd b/gcc/match.pd index 54cad1cd2bc4..9f834115a52d 100644 --- a/gcc/match.pd +++ b/gcc/match.pd @@ -4003,20 +4003,20 @@ DEFINE_INT_AND_FLOAT_ROUND_FN (RINT) (for bitop (bit_and bit_ior) (for cmp1 (tcc_comparison) (for cmp2 (tcc_comparison) - (for rcmp (tcc_comparison) - (simplify - (bitop (cmp1 @0 @1) (cmp2 @0 @1)) - (with { - tree_code rescode; - tree res; - bool honor_nans = HONOR_NANS (@0); - rescode = combine_comparisons (bitop, cmp1, cmp2, - type, honor_nans, &res); + (simplify + (bitop (cmp1 @0 @1) (cmp2 @0 @1)) + (with rescmpcode (tcc_comparison) + { + tree_code rescmpcode; + tree res; + bool honor_nans = HONOR_NANS (@0); + rescmpcode = combine_comparisons (bitop, cmp1, cmp2, + type, honor_nans, &res); } - (if (rescode == INTEGER_CST) - { res; } - (if (rescode == rcmp) - (rcmp @0 @1))))))))) + (if (rescmpcode == INTEGER_CST) + { res; } + (if (TREE_CODE_CLASS (rescmpcode) == tcc_comparison) + (rescmpcode @0 @1)))))))) /* (type)([0,1]@a != 0) -> (type)a (type)([0,1]@a == 1) -> (type)a @@ -7827,8 +7827,6 @@ DEFINE_INT_AND_FLOAT_ROUND_FN (RINT) operator using invert_tree_comparison we have to simulate that with expression code iteration. */ (for cmp (tcc_comparison) - icmp (inverted_tcc_comparison) - ncmp (inverted_tcc_comparison_with_nans) /* Ideally we'd like to combine the following two patterns and handle some more cases by using (logical_inverted_value (cmp @0 @1)) @@ -7839,39 +7837,38 @@ DEFINE_INT_AND_FLOAT_ROUND_FN (RINT) (if (VECTOR_TYPE_P (type) || (INTEGRAL_TYPE_P (type) && TYPE_PRECISION (type) == 1)) /* Comparison inversion may be impossible for trapping math, - invert_tree_comparison will tell us. But we can't use - a computed operator in the replacement tree thus we have - to play the trick below. */ - (with { enum tree_code ic = invert_tree_comparison - (cmp, HONOR_NANS (@0)); } - (if (ic == icmp) - (icmp @0 @1) - (if (ic == ncmp) - (ncmp @0 @1)))))) + invert_tree_comparison will tell us. */ + (with rescmpcode (tcc_comparison) + { + enum tree_code rescmpcode; + rescmpcode = invert_tree_comparison (cmp, HONOR_NANS (@0)); + } + (if (TREE_CODE_CLASS (rescmpcode) == tcc_comparison) + (rescmpcode @0 @1))))) (simplify (bit_xor (cmp @0 @1) integer_truep) - (with { enum tree_code ic = invert_tree_comparison - (cmp, HONOR_NANS (@0)); } - (if (ic == icmp) - (icmp @0 @1) - (if (ic == ncmp) - (ncmp @0 @1))))) + (with rescmpcode (tcc_comparison) + { + enum tree_code rescmpcode; + rescmpcode = invert_tree_comparison (cmp, HONOR_NANS (@0)); + } + (if (TREE_CODE_CLASS (rescmpcode) == tcc_comparison) + (rescmpcode @0 @1)))) /* ((cast)cmp) - 1 -> -(cast)icmp . */ (simplify (plus (convert? (cmp@2 @0 @1)) integer_minus_onep) (if (TYPE_PRECISION (type) > 1 && INTEGRAL_TYPE_P (TREE_TYPE (@2)) && TYPE_PRECISION (TREE_TYPE (@2)) == 1) /* Comparison inversion may be impossible for trapping math, - invert_tree_comparison will tell us. But we can't use - a computed operator in the replacement tree thus we have - to play the trick below. */ - (with { enum tree_code ic = invert_tree_comparison - (cmp, HONOR_NANS (@0)); - tree cmptype = TREE_TYPE (@2); } - (if (ic == icmp) - (negate (convert (icmp:cmptype @0 @1))) - (if (ic == ncmp) - (negate (convert (ncmp:cmptype @0 @1)))))))) + invert_tree_comparison will tell us. */ + (with rescmpcode (tcc_comparison) + { + enum tree_code rescmpcode; + rescmpcode = invert_tree_comparison (cmp, HONOR_NANS (@0)); + tree cmptype = TREE_TYPE (@2); + } + (if (TREE_CODE_CLASS (rescmpcode) == tcc_comparison) + (negate (convert (rescmpcode:cmptype @0 @1))))))) /* The following bits are handled by fold_binary_op_with_conditional_arg. */ (simplify (ne (cmp@2 @0 @1) integer_zerop) @@ -7884,21 +7881,23 @@ DEFINE_INT_AND_FLOAT_ROUND_FN (RINT) (simplify (ne (cmp@2 @0 @1) integer_truep) (if (types_match (type, TREE_TYPE (@2))) - (with { enum tree_code ic = invert_tree_comparison - (cmp, HONOR_NANS (@0)); } - (if (ic == icmp) - (icmp @0 @1) - (if (ic == ncmp) - (ncmp @0 @1)))))) + (with rescmpcode (tcc_comparison) + { + enum tree_code rescmpcode; + rescmpcode = invert_tree_comparison (cmp, HONOR_NANS (@0)); + } + (if (TREE_CODE_CLASS (rescmpcode) == tcc_comparison) + (rescmpcode @0 @1))))) (simplify (eq (cmp@2 @0 @1) integer_zerop) (if (types_match (type, TREE_TYPE (@2))) - (with { enum tree_code ic = invert_tree_comparison - (cmp, HONOR_NANS (@0)); } - (if (ic == icmp) - (icmp @0 @1) - (if (ic == ncmp) - (ncmp @0 @1))))))) + (with rescmpcode (tcc_comparison) + { + enum tree_code rescmpcode; + rescmpcode = invert_tree_comparison (cmp, HONOR_NANS (@0)); + } + (if (TREE_CODE_CLASS (rescmpcode) == tcc_comparison) + (rescmpcode @0 @1)))))) /* Transform comparisons of the form X - Y CMP 0 to X CMP Y. ??? The transformation is valid for the other operators if overflow
