diff --git a/src/Changes b/src/Changes index 14d036ee11..85af9d94a8 100644 --- a/src/Changes +++ b/src/Changes @@ -1,5 +1,31 @@ Version 7.1 +10/7/26 [GH #176] +C-generating back end: always_inline attribute and inlined setjmp calls + +When lowering inserts a call of setjmp in a routine that has the GNU +always_inline attribute, the attribute is removed, because a back end GCC +compiler gives an error on an always_inline routine that calls setjmp (see +the entry for EDGcpfe/19110 of 1/10/18). However, the front end's inlining +could also introduce such a call, by expanding a call of an inline routine +whose lowered code calls setjmp inside the always_inline routine. The +generated C code was then rejected by GCC with an error indicating that the +always_inline routine can never be inlined. When GCC is the back end +compiler, such calls are now not inlined into a routine that has the +always_inline attribute. For example, with --g++: + + inline void *operator new(decltype(sizeof 0), void *p) noexcept { + return p; + } + inline void operator delete(void *, void *) noexcept {} + struct A { A(); }; + struct Alloc { void construct(A *p) { ::new ((void *)p) A(); } }; + [[gnu::always_inline]] static inline void tconstruct(Alloc &a, A *p) { + a.construct(p); // Previously, GCC gave an error on the generated code. + } + void f(Alloc &a, A *p) { tconstruct(a, p); } + + 10/5/26 [GH #195] C++-generating back end: dependent multi-dimensional subscripts diff --git a/src/inline.c b/src/inline.c index ffe28c61aa..7da03d15f8 100644 --- a/src/inline.c +++ b/src/inline.c @@ -820,6 +820,22 @@ expression can't be inlined. an_expr_node_ptr operand = expr->variant.operation.operands; an_expr_node_ptr operand2 = operand->next; a_constant_ptr con2 = NULL; +#if GNU_EXTENSIONS_ALLOWED && BACK_END_IS_C_GEN_BE + if (op == eok_call && + gcc_is_generated_code_target && + innermost_function_scope != NULL && + innermost_function_scope->variant.routine.ptr->always_inline && + is_eh_setjmp_routine(routine_from_function_expr(operand))) { + /* The code being inlined calls setjmp, and the routine it is being + inlined into has the GNU always_inline attribute. gcc gives an + error on an always_inline routine that calls setjmp + (because such a routine can never be inlined), so do not inline the + call. */ + *inlining_failed = TRUE; + } /* if */ +#endif /* GNU_EXTENSIONS_ALLOWED && BACK_END_IS_C_GEN_BE */ + /* Look for operations that now have constant operands because of + parameter variables remapped to constants. */ if (is_constant_node(operand) && (operand2 == NULL || is_constant_node(operand2))) { /* The operands are constant. */ diff --git a/src/lower_eh.c b/src/lower_eh.c index ed39d41e84..0b96251195 100644 --- a/src/lower_eh.c +++ b/src/lower_eh.c @@ -5380,6 +5380,17 @@ __suppress_optim_on_vars_in_try. NULL until created. STATIC_THREAD a_routine_ptr setjmp_routine, suppress_optim_on_vars_in_try_routine; + + +a_boolean is_eh_setjmp_routine(a_routine_ptr routine) +/* +Return TRUE if routine is the runtime routine setjmp called by the lowered +code for try blocks. +*/ +{ + return routine != NULL && routine == setjmp_routine; +} /* is_eh_setjmp_routine */ + #endif /* DO_FULL_PORTABLE_EH_LOWERING */ diff --git a/src/lower_eh.h b/src/lower_eh.h index 90d3e0a7cb..e736845ae0 100644 --- a/src/lower_eh.h +++ b/src/lower_eh.h @@ -111,6 +111,10 @@ extern void lower_try_block( extern an_expr_node_ptr make_internal_try_expr(an_expr_node_ptr try_expr, an_expr_node_ptr catch_expr); +#if DO_FULL_PORTABLE_EH_LOWERING +extern a_boolean is_eh_setjmp_routine(a_routine_ptr routine); +#endif /* DO_FULL_PORTABLE_EH_LOWERING */ + #if !DO_FULL_PORTABLE_EH_LOWERING extern an_expr_node_ptr make_thrown_object_address_node(void); #endif /* !DO_FULL_PORTABLE_EH_LOWERING */ diff --git a/tests/tests/regressions/.gh-176.rto/default.1.1.txt b/tests/tests/regressions/.gh-176.rto/default.1.1.txt new file mode 100644 index 0000000000..889d58e777 --- /dev/null +++ b/tests/tests/regressions/.gh-176.rto/default.1.1.txt @@ -0,0 +1 @@ +eccp -DTEST_NUMBER=1 --gnu_version=150000 --set_flag=no_checking_pragmas --g++ Test_name.c diff --git a/tests/tests/regressions/.gh-176.rto/default.1.2.txt b/tests/tests/regressions/.gh-176.rto/default.1.2.txt new file mode 100644 index 0000000000..ff2642c6d5 --- /dev/null +++ b/tests/tests/regressions/.gh-176.rto/default.1.2.txt @@ -0,0 +1 @@ +./a.out diff --git a/tests/tests/regressions/.gh-176.rto/default.2.1.txt b/tests/tests/regressions/.gh-176.rto/default.2.1.txt new file mode 100644 index 0000000000..4dba3d0278 --- /dev/null +++ b/tests/tests/regressions/.gh-176.rto/default.2.1.txt @@ -0,0 +1 @@ +eccp -DTEST_NUMBER=2 --gnu_version=150000 --set_flag=no_checking_pragmas --clang Test_name.c diff --git a/tests/tests/regressions/.gh-176.rto/default.2.2.txt b/tests/tests/regressions/.gh-176.rto/default.2.2.txt new file mode 100644 index 0000000000..ff2642c6d5 --- /dev/null +++ b/tests/tests/regressions/.gh-176.rto/default.2.2.txt @@ -0,0 +1 @@ +./a.out diff --git a/tests/tests/regressions/gh-176.sft.cpp b/tests/tests/regressions/gh-176.sft.cpp new file mode 100644 index 0000000000..d0ad4492f6 --- /dev/null +++ b/tests/tests/regressions/gh-176.sft.cpp @@ -0,0 +1,41 @@ +//type:rp +//options_all:--gnu_version=150000 --set_flag=no_checking_pragmas +//options:--g++:--clang +inline void *operator new(decltype(sizeof 0), void *p) noexcept { return p; } +int deletes; +inline void operator delete(void *, void *) noexcept { ++deletes; } + +struct A { A(); }; +// The code generated for Alloc::construct calls setjmp, to call the placement +// operator delete if the constructor throws. +struct Alloc { void construct(A *p) { ::new ((void *)p) A(); } }; +struct Alloc2 { Alloc a; void construct(A *p) { a.construct(p); } }; + +[[gnu::always_inline]] inline void tconstruct(Alloc &a, A *p) { + a.construct(p); +} + +[[gnu::always_inline]] static inline void sconstruct(Alloc &a, A *p) { + a.construct(p); // Previously gave a gcc error. +} + +[[gnu::always_inline]] static inline void s2construct(Alloc2 &a, A *p) { + a.construct(p); // Previously gave a gcc error. +} + +void f(Alloc &a, A *p) { tconstruct(a, p); } +void g(Alloc &a, A *p) { sconstruct(a, p); } +void h(Alloc2 &a, A *p) { s2construct(a, p); } + +A::A() { throw 1; } + +int main() { + alignas(A) unsigned char buf[sizeof(A)]; + A *p = (A *)buf; + Alloc a; + Alloc2 a2; + try { f(a, p); } catch (int) {} + try { g(a, p); } catch (int) {} + try { h(a2, p); } catch (int) {} + return deletes == 3 ? 0 : 1; +}