https://gcc.gnu.org/g:7ae96151ed91609c331d7199e71cf14072c120fd
commit r17-4242-g7ae96151ed91609c331d7199e71cf14072c120fd Author: Kyrylo Tkachov <[email protected]> Date: Thu Sep 3 18:27:04 2026 +0200 middle-end: Check GIMPLE memory LHSs as stores [PR127184] 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. Tested on x86_64-pc-linux-gnu. PR middle-end/127184 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 (stmt_could_throw_p): Check a nothrow call's memory LHS. gcc/testsuite/ChangeLog: * g++.dg/eh/noncall-store-call.C: New test. Signed-off-by: Kyrylo Tkachov <[email protected]> Diff: --- gcc/gimple.cc | 74 ++++++++++++++++++++++++++-- gcc/testsuite/g++.dg/eh/noncall-store-call.C | 49 ++++++++++++++++++ gcc/tree-eh.cc | 12 ++++- 3 files changed, 131 insertions(+), 4 deletions(-) diff --git a/gcc/gimple.cc b/gcc/gimple.cc index 3756a60a8e6e..04ad17f59556 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 000000000000..0be0151450b7 --- /dev/null +++ b/gcc/testsuite/g++.dg/eh/noncall-store-call.C @@ -0,0 +1,49 @@ +// 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" } } diff --git a/gcc/tree-eh.cc b/gcc/tree-eh.cc index 1a781f7a0907..e3b8db0b6d01 100644 --- a/gcc/tree-eh.cc +++ b/gcc/tree-eh.cc @@ -3132,7 +3132,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: {
