Re: [PATCH v2 1/3] match: allow c variable for the code/operation of the result [PR126912]
Andrea Pinski <[email protected]>
| Newsgroups | gmane.comp.gcc.patches |
|---|---|
| Message-ID | <CALvbMcBc1tHiRerNa+ZO55tZZbkoFteqyf2SiMHMCtmMM=_4Lw@mail.gmail.com> |
On Wed, Aug 19, 2026 at 1:56 AM Richard Biener <[email protected]> wrote: > > On Wed, Aug 19, 2026 at 2:34 AM Andrea Pinski > <[email protected]> wrote: > > > > 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. > > > > Currently this only allows if the variable contains `code` in it. > > If the identifier has cmp in it; genmatch assumes it will be a > > tcc_comparison for type resultion. > > > > I can add support for combined_fn and internal_fn later if needed. > > From looking into the match patterns right now; there is a few but none > > use a for loop for cases but rather just a few (3/4) if statements. So it > > was not high on my list of things to support currently. > > I do not like the automagic ID recognition too much. Esp. automatically > treating anything with 'cmp' as tcc_comparison might lead to wrong code. > If we stick with that can you please code-generate runtime checking at least? > There might be more problematic cases for type-inference(?) and most > definitely for "escape" handling - capture_info::walk_result checks > for COND_EXPR and TRUTH_{AND,OR}IF_EXPR but you do not touch > this. Runtime checking could be done for those as well. > > But I wonder whether sth like > > (simplify (...) > (with { tree_code code = ...; } > (code<tcc_comparison> ...))) > > would be better overall, that is, require the set of possible operators to > be specified as declared operator list. So it would be a user-id > declaration at result parsing time. > > I thought of > > (tcc_comparsion code @0 @1) > > but this doesn't seem lispy syntax. Alternatively do "toplevel" > > (simplify (...) > (with { tree_code = ...; } > (with_code code (tcc_comparison) > (code @0 @1))) > > so similar to (for ..) syntax, have a declare syntax. Allow > > (with_code code (tcc_comparison) > code2 (plus minus) > > as well. I think I prefer (with_code ...)? What about this: (simplify (bit_xor (cmp @@0 @@1) integer_truep) (with_code rescode tcc_comparison resbincode 2 resunarycode 1 restrianycode 3 (with { rescode = invert_tree_comparison (cmp, HONOR_NANS (@0)); } (if (TREE_CODE_CLASS (rescode) == tcc_comparison) (rescode @0 @1))))) with_code can only contain a `(with` expression too. 3 can't be COND_EXPR/VEC_COND_EXPR. (I can't see where that would happen anyways) 2 can't be either TRUTH_ANDIF_EXPR/TRUTH_ORIF_EXPR either. 1 can be VCE/REAL_PART_EXPR/IMAG_PART_EXPR; otherwise it is tcc_unary. 2 can't be tcc_comparison. Note I can't see any use right now outside of comparison or internal/builtins. The MIN/MAX results from minmax_from_comparison is the only other place which we could use it though currently an if happens so it could simplify the generation code slightly. Anyways I will try to come up with a patch later this week. > > Richard. > > > Bootstrapped and tested on x86_64-linux-gnu. > > > > PR middle-end/126912 > > gcc/ChangeLog: > > > > * doc/match-and-simplify.texi: Document new syntax. > > * genmatch.cc (class c_code_id): New class. > > (c_code_id::get_c_code_id): New method. > > (c_ids): New hashtable. > > (test): Handle C_CODE_ID. > > (lower_for): Handle c_code_id. > > (get_operand_type): Check cmp of c_code_id. > > (expr::gen_transform): Likewise. > > (dt_simplify::gen_1): Handle C_CODE_ID like CODE. > > (parser::parse_operation): Add bool res argument. For unknown id > > see if this can be a c code id for res. > > (parser::parse_expr): Add bool res argument. Pass down to parse_op > > and parse_operation. > > (parser::parse_op): Add bool res argument. > > Pass down to parse_expr and parse_op. > > (parser::parse_result): Pass true to parse_op and parse_expr. > > (parser::parse_simplify): Pass false to parse_op. > > (main): Allocate and free c_ids. > > * 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]> > > --- > > gcc/doc/match-and-simplify.texi | 13 ++++ > > gcc/genmatch.cc | 114 ++++++++++++++++++++++++-------- > > gcc/match.pd | 77 +++++++++------------ > > 3 files changed, 129 insertions(+), 75 deletions(-) > > > > diff --git a/gcc/doc/match-and-simplify.texi b/gcc/doc/match-and-simplify.texi > > index b187dd27561..4f171a36f69 100644 > > --- a/gcc/doc/match-and-simplify.texi > > +++ b/gcc/doc/match-and-simplify.texi > > @@ -361,6 +361,19 @@ 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 the result of a simplify, a variable defined in @code{with} can be > > +use as the resulting tree code if it contains @code{code} in the name. > > +Comparisons results need to have cmp in its name to get the correct type for it. > > + > > +@smallexample > > + (simplify > > + (bit_xor (cmp @@0 @@1) integer_truep) > > + (with @{ enum tree_code rescmpcode = invert_tree_comparison > > + (cmp, HONOR_NANS (@@0)); @} > > + (if (TREE_CODE_CLASS (rescmpcode) == tcc_comparison) > > + (rescmpcode @@0 @@1)))) > > +@end smallexample > > + > > 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 920cf27877d..44da9fa3970 100644 > > --- a/gcc/genmatch.cc > > +++ b/gcc/genmatch.cc > > @@ -1121,7 +1121,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 +1199,37 @@ public: > > vec<simplify *> matchers; > > }; > > > > +// Identifier that maps to a operator defined by a 'C' identifier. > > +// Contains code in the id, also if contains cmp then it is a compare. > > + > > +class c_code_id : public id_base > > +{ > > +public: > > + c_code_id (const char *id_) > > + : id_base (id_base::C_CODE_ID, xstrdup (id_)), > > + cmp (strstr (id_, "cmp")) {} > > + static id_base *get_c_code_id (const char *); > > + ~c_code_id() { delete const_cast<char*>(id); } > > + bool cmp; > > +}; > > + > > +static hash_table<id_base> *c_ids; > > + > > +id_base * > > +c_code_id::get_c_code_id (const char *id) > > +{ > > + if (!strstr (id, "code")) > > + return nullptr; > > + id_base tem (id_base::C_CODE_ID, id); > > + > > + id_base **op = c_ids->find_slot_with_hash (&tem, tem.hashval, INSERT); > > + if (!*op) > > + *op = new c_code_id (id); > > + > > + return *op; > > +} > > + > > + > > /* Identifier that maps to a operator defined by a 'for' directive. */ > > > > class user_id : public id_base > > @@ -1244,6 +1275,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. */ > > > > @@ -2301,6 +2340,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 +3263,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)->cmp)) > > return other_oprnd_type; > > else if (*op == COND_EXPR > > && pos == 0) > > @@ -3285,8 +3328,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)->cmp)) > > { > > /* comparisons use boolean_type_node (or what gets in), but > > their operands need to figure out the types themselves. */ > > @@ -3451,7 +3496,7 @@ 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 || opr->kind == id_base::C_CODE_ID) > > fprintf_indent (f, indent, "_r%d = fold_build%d_loc (loc, %s, %s", > > depth, ops.length(), opr_name, type); > > else > > @@ -3460,7 +3505,7 @@ 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 && opr->kind != id_base::C_CODE_ID) > > { > > fprintf_indent (f, indent, "if (!_r%d)\n", depth); > > fprintf_indent (f, indent, " goto %s;\n", fail_label); > > @@ -4630,7 +4675,7 @@ 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)) > > + if (is_a <operator_id *> (opr) || is_a <c_code_id *> (opr)) > > fprintf_indent (f, indent, > > "_r = fold_build%d_loc (loc, %s, type", > > e->ops.length (), > > @@ -4644,7 +4689,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); > > @@ -5146,11 +5191,11 @@ private: > > > > unsigned get_internal_capture_id (); > > > > - id_base *parse_operation (unsigned char &); > > + id_base *parse_operation (unsigned char &, bool); > > operand *parse_capture (operand *, bool); > > - operand *parse_expr (); > > + operand *parse_expr (bool = false); > > c_expr *parse_c_expr (cpp_ttype); > > - operand *parse_op (); > > + operand *parse_op (bool); > > > > void record_operlist (location_t, user_id *); > > > > @@ -5338,7 +5383,7 @@ parser::record_operlist (location_t loc, user_id *p) > > convert2? */ > > > > id_base * > > -parser::parse_operation (unsigned char &opt_grp) > > +parser::parse_operation (unsigned char &opt_grp, bool res) > > { > > const cpp_token *id_tok = peek (); > > char *alt_id = NULL; > > @@ -5365,7 +5410,18 @@ parser::parse_operation (unsigned char &opt_grp) > > } > > id_base *op = get_operator (alt_id ? alt_id : id); > > if (!op) > > - fatal_at (id_tok, "unknown operator %s", alt_id ? alt_id : id); > > + { > > + // An unknown id that contains code in it is considered a C code identifier > > + if (res) > > + { > > + if (id_base *op = c_code_id::get_c_code_id (id)) > > + { > > + free (alt_id); > > + return op; > > + } > > + } > > + fatal_at (id_tok, "unknown operator %s", alt_id ? alt_id : id); > > + } > > if (alt_id) > > free (alt_id); > > user_id *p = dyn_cast<user_id *> (op); > > @@ -5420,11 +5476,11 @@ parser::parse_capture (operand *op, bool require_existing) > > expr = '(' <operation>[capture][flag][type] <operand>... ')' */ > > > > class operand * > > -parser::parse_expr () > > +parser::parse_expr (bool res) > > { > > const cpp_token *token = peek (); > > unsigned char opt_grp; > > - expr *e = new expr (parse_operation (opt_grp), token->src_loc); > > + expr *e = new expr (parse_operation (opt_grp, res), token->src_loc); > > token = peek (); > > operand *op; > > bool is_commutative = false; > > @@ -5537,7 +5593,7 @@ parser::parse_expr () > > else if (!(token->flags & PREV_WHITE)) > > fatal_at (token, "expected expression operand"); > > > > - e->append_op (parse_op ()); > > + e->append_op (parse_op (res)); > > } > > while (1); > > } > > @@ -5602,14 +5658,14 @@ parser::parse_c_expr (cpp_ttype start) > > op = predicate | expr | c_expr | capture */ > > > > class operand * > > -parser::parse_op () > > +parser::parse_op (bool res) > > { > > const cpp_token *token = peek (); > > class operand *op = NULL; > > if (token->type == CPP_OPEN_PAREN) > > { > > eat_token (CPP_OPEN_PAREN); > > - op = parse_expr (); > > + op = parse_expr (res); > > eat_token (CPP_CLOSE_PAREN); > > } > > else if (token->type == CPP_OPEN_BRACE) > > @@ -5695,7 +5751,7 @@ parser::parse_result (operand *result, predicate_id *matcher) > > { > > const cpp_token *token = peek (); > > if (token->type != CPP_OPEN_PAREN) > > - return parse_op (); > > + return parse_op (true); > > > > eat_token (CPP_OPEN_PAREN); > > if (peek_ident ("if")) > > @@ -5709,15 +5765,15 @@ parser::parse_result (operand *result, predicate_id *matcher) > > if (peek ()->type == CPP_OPEN_PAREN) > > ife->falseexpr = parse_result (result, matcher); > > else if (peek ()->type != CPP_CLOSE_PAREN) > > - ife->falseexpr = parse_op (); > > + ife->falseexpr = parse_op (true); > > } > > else if (peek ()->type != CPP_CLOSE_PAREN) > > { > > - ife->trueexpr = parse_op (); > > + ife->trueexpr = parse_op (true); > > if (peek ()->type == CPP_OPEN_PAREN) > > ife->falseexpr = parse_result (result, matcher); > > else if (peek ()->type != CPP_CLOSE_PAREN) > > - ife->falseexpr = parse_op (); > > + ife->falseexpr = parse_op (true); > > } > > /* If this if is immediately closed then it contains a > > manual matcher or is part of a predicate definition. */ > > @@ -5752,7 +5808,7 @@ parser::parse_result (operand *result, predicate_id *matcher) > > if (peek ()->type == CPP_OPEN_PAREN) > > ife->trueexpr = parse_result (result, matcher); > > else > > - ife->trueexpr = parse_op (); > > + ife->trueexpr = parse_op (true); > > eat_token (CPP_CLOSE_PAREN); > > if (peek ()->type != CPP_OPEN_PAREN > > || !peek_ident ("if", 2)) > > @@ -5771,7 +5827,7 @@ parser::parse_result (operand *result, predicate_id *matcher) > > if (peek ()->type == CPP_OPEN_PAREN) > > ife->trueexpr = parse_result (result, matcher); > > else > > - ife->trueexpr = parse_op (); > > + ife->trueexpr = parse_op (true); > > if (peek ()->type == CPP_OPEN_PAREN) > > fatal_at (peek(), "if inside switch cannot have an else"); > > eat_token (CPP_CLOSE_PAREN); > > @@ -5787,7 +5843,7 @@ parser::parse_result (operand *result, predicate_id *matcher) > > else > > { > > /* switch default clause */ > > - ife->falseexpr = parse_op (); > > + ife->falseexpr = parse_op (true); > > eat_token (CPP_CLOSE_PAREN); > > return res; > > } > > @@ -5799,7 +5855,7 @@ parser::parse_result (operand *result, predicate_id *matcher) > > { > > operand *op = result; > > if (!matcher) > > - op = parse_expr (); > > + op = parse_expr (true); > > eat_token (CPP_CLOSE_PAREN); > > return op; > > } > > @@ -5826,7 +5882,7 @@ parser::parse_simplify (simplify::simplify_kind kind, > > > > const cpp_token *loc = peek (); > > parsing_match_operand = true; > > - class operand *match = parse_op (); > > + class operand *match = parse_op (false); > > finish_match_operand (match); > > parsing_match_operand = false; > > if (match->type == operand::OP_CAPTURE && !matcher) > > @@ -6366,6 +6422,7 @@ main (int argc, char **argv) > > > > /* Pre-seed operators. */ > > operators = new hash_table<id_base> (1024); > > + c_ids = new hash_table<id_base> (4); > > #define DEFTREECODE(SYM, STRING, TYPE, NARGS) \ > > add_operator (SYM, # SYM, # TYPE, NARGS); > > #define END_OF_BASE_TREE_CODES > > @@ -6471,6 +6528,7 @@ main (int argc, char **argv) > > cpp_destroy (r); > > > > delete operators; > > + delete c_ids; > > > > return 0; > > } > > diff --git a/gcc/match.pd b/gcc/match.pd > > index eae8717bcfe..d255795c8dd 100644 > > --- a/gcc/match.pd > > +++ b/gcc/match.pd > > @@ -4006,20 +4006,19 @@ 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); > > - } > > - (if (rescode == INTEGER_CST) > > - { res; } > > - (if (rescode == rcmp) > > - (rcmp @0 @1))))))))) > > + (simplify > > + (bitop (cmp1 @0 @1) (cmp2 @0 @1)) > > + (with { > > + 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)))))))) > > > > /* (type)([0,1]@a != 0) -> (type)a > > (type)([0,1]@a == 1) -> (type)a > > @@ -7781,8 +7780,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)) > > @@ -7793,39 +7790,29 @@ 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 > > + invert_tree_comparison will tell us. */ > > + (with { enum tree_code rescmpcode = invert_tree_comparison > > (cmp, HONOR_NANS (@0)); } > > - (if (ic == icmp) > > - (icmp @0 @1) > > - (if (ic == ncmp) > > - (ncmp @0 @1)))))) > > + (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 > > + (with { enum tree_code rescmpcode = invert_tree_comparison > > (cmp, HONOR_NANS (@0)); } > > - (if (ic == icmp) > > - (icmp @0 @1) > > - (if (ic == ncmp) > > - (ncmp @0 @1))))) > > + (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 > > + invert_tree_comparison will tell us. */ > > + (with { enum tree_code rescmpcode = 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)))))))) > > + (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) > > @@ -7838,21 +7825,17 @@ 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 > > + (with { enum tree_code rescmpcode = invert_tree_comparison > > (cmp, HONOR_NANS (@0)); } > > - (if (ic == icmp) > > - (icmp @0 @1) > > - (if (ic == ncmp) > > - (ncmp @0 @1)))))) > > + (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 > > + (with { enum tree_code rescmpcode = invert_tree_comparison > > (cmp, HONOR_NANS (@0)); } > > - (if (ic == icmp) > > - (icmp @0 @1) > > - (if (ic == ncmp) > > - (ncmp @0 @1))))))) > > + (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 > > -- > > 2.43.0 > >