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);
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 <jakub@redhat.com>
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.