> On 5 Sep 2026, at 07:07, Andrea Pinski <[email protected]> wrote:
> 
> On Fri, Sep 4, 2026 at 4:57 AM <[email protected]> wrote:
>> 
>> From: Kyrylo Tkachov <[email protected]>
>> 
>> tree_could_trap_p describes evaluation as an rvalue.  However,
>> gimple_could_trap_p_1 uses it for an assignment LHS when store checks are
>> requested.  It also scans the LHS of other statements as an rvalue.  A load
>> can be safe while a store to the same address can trap.
>> 
>> Use gimple_store_p and lhs_could_trap_p for memory LHSs when store checks are
>> requested.  This keeps register definitions as non-stores and includes memory
>> results of GIMPLE_CALL.  Omit every statement LHS from the rvalue operand
>> scan.
>> 
>> A nothrow call statement can still trap when the caller stores its result.
>> The return-slot optimization flag permits direct construction in the
>> destination, but expansion can use a temporary and a caller-side copy.  Check
>> a memory call LHS when non-call exceptions are enabled.
>> 
>> Add self-tests for register definitions, assignment stores, and call result
>> stores.  Add a non-call exception test for nothrow call results.
>> 
>> Bootstrapped and tested on aarch64-unknown-linux-gnu.
>> 
>> gcc/ChangeLog:
>> 
>>        * gimple.cc (gimple_could_trap_p_1): Check a memory LHS with
>>        lhs_could_trap_p.  Do not inspect an LHS as an rvalue.
>>        (test_could_trap): New self-test.
>>        (gimple_cc_tests): Run it.
>>        * tree-eh.cc (tree_could_trap_1, tree_could_trap_p,
>>        lhs_could_trap_p): Clarify the function comments.
> Can you split the comment changes out?
> 
>>        (stmt_could_throw_p): Check a nothrow call's memory LHS.
> 
> This is PR 127184.

Thanks, I’ve split the comment changes in a separate patch and resent the ones 
that still need approval.
Kyrill

> 
>> 
>> gcc/testsuite/ChangeLog:
>> 
>>        * g++.dg/eh/noncall-store-call.C: New test.
>> 
>> Signed-off-by: Kyrylo Tkachov <[email protected]>
>> ---
>> gcc/gimple.cc                                | 74 +++++++++++++++++++-
>> gcc/testsuite/g++.dg/eh/noncall-store-call.C | 50 +++++++++++++
>> gcc/tree-eh.cc                               | 27 ++++---
>> 3 files changed, 138 insertions(+), 13 deletions(-)
>> create mode 100644 gcc/testsuite/g++.dg/eh/noncall-store-call.C
>> 
>> diff --git a/gcc/gimple.cc b/gcc/gimple.cc
>> index 3756a60a8e6..04ad17f5955 100644
>> --- a/gcc/gimple.cc
>> +++ b/gcc/gimple.cc
>> @@ -2382,8 +2382,8 @@ gimple_has_side_effects (const gimple *s)
>> 
>> /* Helper for gimple_could_trap_p and gimple_assign_rhs_could_trap_p.
>>    Return true if S can trap.  When INCLUDE_MEM is true, check whether
>> -   the memory operations could trap.  When INCLUDE_STORES is true and
>> -   S is a GIMPLE_ASSIGN, the LHS of the assignment is also checked.  */
>> +   the memory operations could trap.  When INCLUDE_STORES is true, also
>> +   check a memory LHS with store semantics.  */
>> 
>> bool
>> gimple_could_trap_p_1 (const gimple *s, bool include_mem, bool 
>> include_stores)
>> @@ -2393,7 +2393,17 @@ gimple_could_trap_p_1 (const gimple *s, bool 
>> include_mem, bool include_stores)
>> 
>>   if (include_mem)
>>     {
>> -      unsigned i, start = (is_gimple_assign (s) && !include_stores) ? 1 : 0;
>> +      unsigned i, start = 0;
>> +
>> +      /* Use store semantics for the LHS.  A call marked for return-slot
>> +        optimization can still use a temporary and a caller-side copy.  */
>> +      if (include_stores
>> +         && gimple_store_p (s)
>> +         && lhs_could_trap_p (gimple_get_lhs (s)))
>> +       return true;
>> +
>> +      if (gimple_has_lhs (s))
>> +       start = 1;
>> 
>>       for (i = start; i < gimple_num_ops (s); i++)
>>        if (tree_could_trap_p (gimple_op (s, i)))
>> @@ -3670,6 +3680,63 @@ test_assign_binop ()
>>   ASSERT_EQ (MULT_EXPR, gimple_assign_rhs_code (stmt));
>> }
>> 
>> +/* Verify that GIMPLE trap queries distinguish a register definition and a
>> +   trapping store from their nontrapping right-hand sides.  */
>> +
>> +static void
>> +test_could_trap ()
>> +{
>> +  tree reg_lhs = build_decl (UNKNOWN_LOCATION, VAR_DECL,
>> +                            get_identifier ("readonly_reg"),
>> +                            integer_type_node);
>> +  TREE_READONLY (reg_lhs) = 1;
>> +  gassign *stmt = gimple_build_assign (reg_lhs, integer_one_node);
>> +
>> +  ASSERT_FALSE (gimple_store_p (stmt));
>> +  ASSERT_FALSE (gimple_could_trap_p (stmt));
>> +  ASSERT_FALSE (gimple_assign_rhs_could_trap_p (stmt));
>> +
>> +  tree mem_lhs = build_decl (UNKNOWN_LOCATION, VAR_DECL,
>> +                            get_identifier ("readonly_mem"),
>> +                            integer_type_node);
>> +  TREE_READONLY (mem_lhs) = 1;
>> +  TREE_STATIC (mem_lhs) = 1;
>> +  TREE_ADDRESSABLE (mem_lhs) = 1;
>> +  stmt = gimple_build_assign (mem_lhs, integer_one_node);
>> +
>> +  ASSERT_TRUE (gimple_store_p (stmt));
>> +  ASSERT_TRUE (gimple_could_trap_p (stmt));
>> +  ASSERT_FALSE (gimple_assign_rhs_could_trap_p (stmt));
>> +
>> +  tree record_type = make_node (RECORD_TYPE);
>> +  tree field = build_decl (UNKNOWN_LOCATION, FIELD_DECL,
>> +                          get_identifier ("value"), integer_type_node);
>> +  DECL_CONTEXT (field) = record_type;
>> +  TYPE_FIELDS (record_type) = field;
>> +  layout_type (record_type);
>> +
>> +  tree call_lhs = build_decl (UNKNOWN_LOCATION, VAR_DECL,
>> +                             get_identifier ("readonly_call_mem"),
>> +                             record_type);
>> +  TREE_READONLY (call_lhs) = 1;
>> +  TREE_STATIC (call_lhs) = 1;
>> +  TREE_ADDRESSABLE (call_lhs) = 1;
>> +
>> +  tree fn_type = build_function_type_list (record_type, NULL_TREE);
>> +  tree fn = build_fn_decl ("nothrow_call", fn_type);
>> +  gcall *call = gimple_build_call (fn, 0);
>> +  gimple_call_set_lhs (call, call_lhs);
>> +  gimple_call_set_nothrow (call, true);
>> +
>> +  ASSERT_TRUE (gimple_store_p (call));
>> +  ASSERT_TRUE (gimple_could_trap_p (call));
>> +  ASSERT_FALSE (gimple_could_trap_p_1 (call, true, false));
>> +
>> +  gimple_call_set_return_slot_opt (call, true);
>> +  ASSERT_TRUE (gimple_could_trap_p (call));
>> +  ASSERT_FALSE (gimple_could_trap_p_1 (call, true, false));
>> +}
>> +
>> /* Build a GIMPLE_NOP and verify various properties of it.  */
>> 
>> static void
>> @@ -3723,6 +3790,7 @@ gimple_cc_tests ()
>> {
>>   test_assign_single ();
>>   test_assign_binop ();
>> +  test_could_trap ();
>>   test_nop_stmt ();
>>   test_return_stmt ();
>>   test_return_without_value ();
>> diff --git a/gcc/testsuite/g++.dg/eh/noncall-store-call.C 
>> b/gcc/testsuite/g++.dg/eh/noncall-store-call.C
>> new file mode 100644
>> index 00000000000..9027bb09cc1
>> --- /dev/null
>> +++ b/gcc/testsuite/g++.dg/eh/noncall-store-call.C
>> @@ -0,0 +1,50 @@
>> +// A nothrow call can still trap when the caller stores its result.  Return
>> +// slot optimization can fall back to a temporary and a caller-side copy.
>> +
>> +// { dg-do compile }
>> +// { dg-options "-O2 -fnon-call-exceptions -fdump-tree-eh 
>> -fdump-tree-optimized-eh" }
>> +
>> +struct one
>> +{
>> +  __UINTPTR_TYPE__ value;
>> +};
>> +
>> +struct large
>> +{
>> +  __UINTPTR_TYPE__ value[8];
>> +  ~large ();
>> +};
>> +
>> +extern one make_one () throw ();
>> +extern large make_large () throw ();
>> +
>> +void
>> +store_one (one &dest)
>> +{
>> +  try
>> +    {
>> +      dest = make_one ();
>> +    }
>> +  catch (...)
>> +    {
>> +      __builtin_trap ();
>> +    }
>> +}
>> +
>> +void *operator new (__SIZE_TYPE__, void *) throw ();
>> +
>> +void
>> +construct_large (large &dest)
>> +{
>> +  try
>> +    {
>> +      ::new (&dest) large (make_large ());
>> +    }
>> +  catch (...)
>> +    {
>> +      __builtin_trap ();
>> +    }
>> +}
>> +
>> +// { dg-final { scan-tree-dump-times "__cxa_begin_catch" 2 "optimized" } }
>> +// { dg-final { scan-tree-dump-times "return slot optimization" 1 "eh" } }
>> diff --git a/gcc/tree-eh.cc b/gcc/tree-eh.cc
>> index d2c2f3b3157..e48d9502fb0 100644
>> --- a/gcc/tree-eh.cc
>> +++ b/gcc/tree-eh.cc
>> @@ -2753,9 +2753,9 @@ ref_outside_object_p (tree size, poly_offset_int off, 
>> tree refsz)
>> }
>> 
>> /* Return true if EXPR can trap, as in dereferencing an invalid pointer
>> -   location or floating point arithmetic.  C.f. the rtl version, may_trap_p.
>> -   This routine expects only GIMPLE lhs or rhs input.
>> -   LHS is true when LHS is a lhs.  */
>> +   location or evaluating floating-point arithmetic.  See may_trap_p for the
>> +   RTL counterpart.  This routine expects only GIMPLE lhs or rhs input.  LHS
>> +   is true when EXPR is the lhs of a store.  */
>> 
>> static bool
>> tree_could_trap_1 (tree expr, bool lhs)
>> @@ -2935,11 +2935,8 @@ tree_could_trap_1 (tree expr, bool lhs)
>>     }
>> }
>> 
>> -
>> -
>> -/* Return true if EXPR can trap, as in dereferencing an invalid pointer
>> -   location or floating point arithmetic.  C.f. the rtl version, may_trap_p.
>> -   This routine expects only GIMPLE lhs or rhs input.  */
>> +/* Return true if EXPR can trap when evaluated as an rvalue.  See may_trap_p
>> +   for the RTL counterpart.  */
>> 
>> bool
>> tree_could_trap_p (tree expr)
>> @@ -2947,7 +2944,7 @@ tree_could_trap_p (tree expr)
>>   return tree_could_trap_1 (expr, false);
>> }
>> 
>> -/* Returns true if LHS is known not to trap as a store.  */
>> +/* Return true if LHS can trap as a store.  */
>> bool
>> lhs_could_trap_p (tree lhs)
>> {
>> @@ -3094,7 +3091,17 @@ stmt_could_throw_p (function *fun, gimple *stmt)
>>       return true;
>> 
>>     case GIMPLE_CALL:
>> -      return !gimple_call_nothrow_p (as_a <gcall *> (stmt));
>> +      {
>> +       gcall *call = as_a <gcall *> (stmt);
>> +       if (!gimple_call_nothrow_p (call))
>> +         return true;
>> +       /* Return slot optimization can fall back to a temporary and a
>> +          caller-side copy.  */
>> +       if ((fun && !fun->can_throw_non_call_exceptions)
>> +           || !gimple_store_p (call))
>> +         return false;
>> +       return lhs_could_trap_p (gimple_call_lhs (call));
>> +      }
>> 
>>     case GIMPLE_COND:
>>       {
>> --
>> 2.50.1 (Apple Git-155)
>> 

Reply via email to