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  <ja...@redhat.com>
> 
>       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 <rguent...@suse.de>
SUSE Software Solutions Germany GmbH,
Frankenstrasse 146, 90461 Nuernberg, Germany;
GF: Ivo Totev, Andrew McDonald, Werner Knoblich; (HRB 36809, AG Nuernberg)

Reply via email to