https://gcc.gnu.org/bugzilla/show_bug.cgi?id=123978
--- Comment #7 from GCC Commits <cvs-commit at gcc dot gnu.org> --- The master branch has been updated by Jakub Jelinek <[email protected]>: https://gcc.gnu.org/g:a985742fc083a2be20519d902bf41dd5ee2585ca commit r16-7322-ga985742fc083a2be20519d902bf41dd5ee2585ca Author: Jakub Jelinek <[email protected]> Date: Thu Feb 5 13:39:42 2026 +0100 ranger: Fix up WIDEN_MULT_EXPR handling in the ranger [PR123978] In r13-6617 WIDEN_MULT_EXPR support has been added to the ranger, though I guess until we started to use ranger during expansion in r16-1398 it wasn't really used much because vrp2 happens before widen_mul. WIDEN_MULT_EXPR is documented to be /* Widening multiplication. The two arguments are of type t1 and t2, both integral types that have the same precision, but possibly different signedness. The result is of integral type t3, such that t3 is at least twice the size of t1/t2. WIDEN_MULT_EXPR is equivalent to first widening (promoting) the arguments from type t1 to type t3, and from t2 to type t3 and then multiplying them. */ and IMHO ranger should follow that description, so not relying on the precisions to be exactly 2x but >= 2x. More importantly, I don't see convert_mult_to_widen actually ever testing TYPE_UNSIGNED on the result, why would it when the actual RTL optabs don't care about that, in RTL the signs are relevant just whether it is smul_widen, umul_widen or usmul_widen. Though on GIMPLE whether the result is signed or unsigned is important, for value rangers it is essential (in addition to whether the result type is wrapping or undefined overflow). Unfortunately the ranger doesn't tell wi_fold about the signs of the operands and wide_int can be both signed and unsigned, all it knows is the precision of the operands, so r13-6617 handled it by introducing two variants (alternate codes for WIDEN_MULT_EXPR). One was assuming first operand is signed, the other the first operand is unsigned and both were assuming that the second operand has the same sign as the result and that result has exactly 2x precision of the arguments. That is clearly wrong, on the following testcase we have u w* u -> s stmt and ranger incorrectly concluded that the result has [0, 0] range because the operands were [0, 0xffffffff] and [0, -1] (both had actually [0, 0xffffffff] range, but as it used sign extension rather than zero extension for the latter given the signed result, it got it wrong). And when we see [0, 0] range for memset length argument, we just optimize it away completely at expansion time, which is wrong for the testcase where it can be arbitrary long long int [0, 0xffffffff] * long long int [0, 0xffffffff], so because of signed overflow I believe the right range is long long int [0, 0x7fffffffffffffff], as going above that would be UB and both operands are non-negative. The following patch fixes it by not having 2 magic ops for WIDEN_MULT_EXPR, but 3, one roughly corresponding to smul_widen, one to umul_widen and one to usmul_widen (though confusingly with sumul order of operands). The first one handles s w* s -> {u,s}, the second one u w* u -> {u,s} and the last one s w* u -> {u,s} with u w* s -> {u,s} handled by swapping the operands as before. Also, in all cases it uses TYPE_PRECISION (type) as the precision to extend to, because that is the precision in which the actual multiplication is performed, the operation as described is (type) op1 * (type) op2. Note, r13-6617 also added OP_WIDEN_PLUS_{SIGNED,UNSIGNED} and handlers for that, but it doesn't seem to be wired up in any way, so I think it is dead code: |git grep OP_WIDEN_PLUS_ . |ChangeLog-2023: (OP_WIDEN_PLUS_SIGNED): New. |ChangeLog-2023: (OP_WIDEN_PLUS_UNSIGNED): New. |range-op.cc: set (OP_WIDEN_PLUS_SIGNED, op_widen_plus_signed); |range-op.cc: set (OP_WIDEN_PLUS_UNSIGNED, op_widen_plus_unsigned); |range-op.h:#define OP_WIDEN_PLUS_SIGNED ((unsigned) MAX_TREE_CODES + 2) |range-op.h:#define OP_WIDEN_PLUS_UNSIGNED ((unsigned) MAX_TREE_CODES + 3) My understanding is that it is misnamed attempt to implement WIDEN_SUM_EXPR handling but one that wasn't hooked up in maybe_non_standard. I wonder if we shouldn't keep it as is for GCC 16, rename to OP_WIDEN_SUM_* in stage1, hook it up in maybe_non_standard (in this case 2 ops are sufficient, the description is /* Widening summation. The first argument is of type t1. The second argument is of type t2, such that t2 is at least twice the size of t1. The type of the entire expression is also t2. WIDEN_SUM_EXPR is equivalent to first widening (promoting) the first argument from type t1 to type t2, and then summing it with the second argument. */ and so we know second argument has the same type as the result, so all we need to encode is the sign of the first argument. And the ops should be both renamed and fixed, instead of wi::overflow_type ov_lb, ov_ub; signop s = TYPE_SIGN (type); wide_int lh_wlb = wide_int::from (lh_lb, wi::get_precision (lh_lb) * 2, SIGNED); wide_int lh_wub = wide_int::from (lh_ub, wi::get_precision (lh_ub) * 2, SIGNED); wide_int rh_wlb = wide_int::from (rh_lb, wi::get_precision (rh_lb) * 2, s); wide_int rh_wub = wide_int::from (rh_ub, wi::get_precision (rh_ub) * 2, s); wide_int new_lb = wi::add (lh_wlb, rh_wlb, s, &ov_lb); wide_int new_ub = wi::add (lh_wub, rh_wub, s, &ov_ub); r = int_range<2> (type, new_lb, new_ub); I'd go for wide_int lh_wlb = wide_int::from (lh_lb, TYPE_PRECISION (type), SIGNED); wide_int lh_wub = wide_int::from (lh_ub, TYPE_PRECISION (type), SIGNED); return op_plus.wi_fold (r, type, lh_wlb, lh_wub, rh_lb, rh_ub); (and similarly for the unsigned case with s/SIGNED/UNSIGNED/g). Reasons: the precision again needs to be widen to type's precision, there is no point to widen the second operand as it is already supposed to have the right precision and operator_plus actually ends with value_range_with_overflow (r, type, new_lb, new_ub, ov_lb, ov_ub); to handle the overflows etc., r = int_range<2> (type, new_lb, new_ub); won't do it. 2026-02-04 Jakub Jelinek <[email protected]> PR middle-end/123978 * range-op.h (OP_WIDEN_MULT_SIGNED_UNSIGNED): Define. (OP_WIDEN_PLUS_SIGNED, OP_WIDEN_PLUS_UNSIGNED, RANGE_OP_TABLE_SIZE): Renumber. * gimple-range-op.cc (imple_range_op_handler::maybe_non_standard): Use 3 different classes instead of 2 for WIDEN_MULT_EXPR, one for both operands signed, one for both operands unsigned and one for operands with different signedness. In the last case canonicalize to first operand signed second unsigned. * range-op.cc (operator_widen_mult_signed::wi_fold): Use TYPE_PRECISION (type) rather than wi::get_precision (whatever) * 2, use SIGNED for all wide_int::from calls. (operator_widen_mult_unsigned::wi_fold): Similarly but use UNSIGNED for all wide_int::from calls. (class operator_widen_mult_signed_unsigned): New type. (operator_widen_mult_signed_unsigned::wi_fold): Define. * gcc.c-torture/execute/pr123978.c: New test.
