On Fri, 31 Jan 2025, Jakub Jelinek wrote:
> Hi!
>
> compare_operand uses operand_equal_p under the hood, which e.g. for
> INTEGER_CSTs will just match the values rather regardless of their types.
> Now, in many comparing the type is redundant, if we have
> x_2 = y_3 + 1;
> we've already compared the type for the lhs and also for rhs1, there won't
> be any surprises on rhs2.
> As noted in the PR, there are cases where the type of the operand is the
> sole place of information and we don't want to ICF merge functions if the
> types differ.
> One case is stdarg functions, arguments passed to ..., it is different
> if we pass 1, 1L, 1LL.
> Another case are the K&R unprototyped functions (sure, gone in C23).
> And yet another case are inline asm operands, "r" (1) is different from "r"
> (1L) from "r" (1LL).
>
> So, the following patch determines based on lack of fntype (e.g. for
> internal functions), or on !prototype_p, or on stdarg_p (in that case
> using number of named arguments) which arguments need to have type checked
> and does that, plus compares types on inline asm operands (maybe it would be
> enough to do that just for input operands but we have just a routine to
> handle both and I didn't feel we need to differentiate).
>
> Fuirthermore, I've noticed fntype{1,2} isn't actually compared if it is a
> direct call (gimple_call_fndecl is non-NULL). That is wrong too, we could
> have
> void (*fn) (int, long long) = (void (*) (int, long long)) foo;
> fn (1, 1LL);
> in one case and
> void (*fn) (long long, int) = (void (*) (long long, int)) foo;
> fn (1LL, 1);
> in another, both folded into a direct call of foo with different
> gimple_call_fntype. Sure, one of them would be UB at runtime (or both), but
> what if we ICF merge it into something that into the one UB at runtime
> and the program actually calls the correct one only?
>
> Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk?
>
> 2025-01-31 Jakub Jelinek <[email protected]>
>
> PR ipa/117432
> * ipa-icf-gimple.cc (func_checker::compare_asm_inputs_outputs):
> Also return_false if operands have incompatible types.
> (func_checker::compare_gimple_call): Also check fntype1 vs. fntype2
> compatibility if at least one of the calls has different
> gimple_call_fntype from the FUNCTION_TYPE of the called decl. For
> calls to non-prototyped calls or for stdarg_p functions after the
> last named argument (if any) check type compatibility of call
> arguments.
>
> * gcc.c-torture/execute/pr117432.c: New test.
> * gcc.target/i386/pr117432.c: New test.
>
> --- gcc/ipa-icf-gimple.cc.jj 2025-01-02 11:23:16.334519404 +0100
> +++ gcc/ipa-icf-gimple.cc 2025-01-30 16:21:05.782127011 +0100
> @@ -459,7 +459,9 @@ func_checker::compare_asm_inputs_outputs
> return false;
>
> if (!compare_operand (TREE_VALUE (t1), TREE_VALUE (t2),
> - get_operand_access_type (map, t1)))
> + get_operand_access_type (map, t1))
> + || !types_compatible_p (TREE_TYPE (TREE_VALUE (t1)),
> + TREE_TYPE (TREE_VALUE (t2))))
> return return_false ();
>
> tree p1 = TREE_PURPOSE (t1);
> @@ -718,8 +720,11 @@ func_checker::compare_gimple_call (gcall
>
> /* For direct calls we verify that types are compatible so if we matched
> callees, callers must match, too. For indirect calls however verify
> - function type. */
> - if (!gimple_call_fndecl (s1))
> + function type. And also verify it for direct calls with some different
> + fntype. */
> + if (!gimple_call_fndecl (s1)
> + || TREE_TYPE (TREE_TYPE (t1)) != fntype1
> + || TREE_TYPE (TREE_TYPE (t2)) != fntype2)
I think we want to always compare the ABI relevant fntypes. It seems
we can arrive here with internal function calls where t1/t2 are
"somthing" (NULL?). I guess doing this as else {} of the
if (gimple_call_internal_p (s1) (with gimple_call_internal_fn compare
in a conditiona if) would be a lot clearer?
> {
> if ((fntype1 && !fntype2)
> || (!fntype1 && fntype2)
> @@ -738,6 +743,24 @@ func_checker::compare_gimple_call (gcall
> get_operand_access_type (&map, chain1)))
> return return_false_with_msg ("static call chains are different");
>
> + unsigned check_arg_types_from = gimple_call_num_args (s1);
> + if (!fntype1
> + || !fntype2
> + || !prototype_p (fntype1)
> + || !prototype_p (fntype2))
> + check_arg_types_from = 0;
> + else if (stdarg_p (fntype1))
> + {
> + check_arg_types_from = list_length (TYPE_ARG_TYPES (fntype1));
> + if (stdarg_p (fntype2))
> + {
> + unsigned n = list_length (TYPE_ARG_TYPES (fntype2));
> + check_arg_types_from = MIN (check_arg_types_from, n);
> + }
> + }
> + else if (stdarg_p (fntype2))
> + check_arg_types_from = list_length (TYPE_ARG_TYPES (fntype2));
> +
> /* Checking of argument. */
> for (i = 0; i < gimple_call_num_args (s1); ++i)
> {
> @@ -746,6 +769,10 @@ func_checker::compare_gimple_call (gcall
>
> if (!compare_operand (t1, t2, get_operand_access_type (&map, t1)))
> return return_false_with_msg ("GIMPLE call operands are different");
> + if (i >= check_arg_types_from
> + && !types_compatible_p (TREE_TYPE (t1), TREE_TYPE (t2)))
> + return return_false_with_msg ("GIMPLE call operand types are "
> + "different");
> }
The rest looks OK to me.
Richard.
> /* Return value checking. */
> --- gcc/testsuite/gcc.c-torture/execute/pr117432.c.jj 2025-01-30
> 17:34:51.745191701 +0100
> +++ gcc/testsuite/gcc.c-torture/execute/pr117432.c 2025-01-30
> 16:48:45.050300148 +0100
> @@ -0,0 +1,71 @@
> +/* PR ipa/117432 */
> +
> +#include <stdarg.h>
> +
> +long long r;
> +
> +__attribute__((noipa)) void
> +baz (int tag, ...)
> +{
> + va_list ap;
> + va_start (ap, tag);
> + if (!r)
> + r = va_arg (ap, long long);
> + else
> + r = va_arg (ap, int);
> + va_end (ap);
> +}
> +
> +void
> +foo (void)
> +{
> + baz (1, -1, 0);
> +}
> +
> +void
> +bar (void)
> +{
> + baz (1, -1LL, 0);
> +}
> +
> +__attribute__((noipa)) void
> +qux (...)
> +{
> + va_list ap;
> + va_start (ap);
> + if (!r)
> + r = va_arg (ap, long long);
> + else
> + r = va_arg (ap, int);
> + va_end (ap);
> +}
> +
> +void
> +corge (void)
> +{
> + qux (-2, 0);
> +}
> +
> +void
> +fred (void)
> +{
> + qux (-2LL, 0);
> +}
> +
> +int
> +main ()
> +{
> + bar ();
> + if (r != -1LL)
> + __builtin_abort ();
> + foo ();
> + if (r != -1)
> + __builtin_abort ();
> + r = 0;
> + fred ();
> + if (r != -2LL)
> + __builtin_abort ();
> + corge ();
> + if (r != -2)
> + __builtin_abort ();
> +}
> --- gcc/testsuite/gcc.target/i386/pr117432.c.jj 2025-01-30
> 17:34:32.427457117 +0100
> +++ gcc/testsuite/gcc.target/i386/pr117432.c 2025-01-30 17:33:38.937192052
> +0100
> @@ -0,0 +1,17 @@
> +/* PR ipa/117432 */
> +/* { dg-do compile { target lp64 } } */
> +/* { dg-options "-O2" } */
> +/* { dg-final { scan-assembler "# myinsn %rax" } } */
> +/* { dg-final { scan-assembler "# myinsn %eax" } } */
> +
> +void
> +foo (void)
> +{
> + asm volatile ("# myinsn %0" : : "r" (-42L));
> +}
> +
> +void
> +bar (void)
> +{
> + asm volatile ("# myinsn %0" : : "r" (-42));
> +}
>
> Jakub
>
>
--
Richard Biener <[email protected]>
SUSE Software Solutions Germany GmbH,
Frankenstrasse 146, 90461 Nuernberg, Germany;
GF: Ivo Totev, Andrew McDonald, Werner Knoblich; (HRB 36809, AG Nuernberg)