Commit 9752f802b3869c96446f7e84e43d89360695c0f8 introduced
eliminate_tail_calls to turn the final call of a function into a jump.
Commit 431b6b1cd92a54f4a53b66a5ff27a64381ff9a23 then applied it to
complain() and complain_with_dbg() so that these wrappers would not appear
in diagnostic backtraces.
The GCC expansion enables sibling-call optimization. The Clang expansion
currently uses disable_tail_calls, which has the opposite meaning: Clang
documents it as preventing tail-call optimization inside the attributed
function.
This is visible through HAProxy's own "debug dev check" command. GCC 11
omits complain_with_dbg() from the resulting backtrace, while Clang 14 and
Clang 18 retain one complain_with_dbg() frame. The builds otherwise succeed,
so build success alone does not expose the difference.
Making eliminate_tail_calls empty for Clang removes the inverse attribute,
but is not sufficient by itself: both tested Clang versions then inline
ha_backtrace_to_stderr() into the wrapper and still retain its frame. Also
mark the callee noinline so that Clang emits the intended final jump.
The resulting source was tested on x86-64 at master revision
3081fdb5846b1b7ab8f93d1f28b31af13502f5db. Normal GCC 11, Clang 14, and
Clang 18 builds passed, and "debug dev check" omitted the wrapper frame in
all three repaired builds. Object-code inspection confirmed a final jump.
CI-aligned no-features builds with GCC 13 and Clang 18 also passed, including
143 default, bug, and devel regression tests for each compiler. A broader
Clang 14 run passed 167 regression tests. The enabled unit tests passed for
the repaired GCC and Clang builds.
The user-visible effect is limited to developer backtrace quality. This is
not a security or performance fix. This is submitted as an RFC because a
maintainer may prefer a Clang-specific spelling or a reusable noinline
abstraction. This is the approach I had in mind, and any suggestions would
be appreciated.
No backport is needed because both introducing commits are only in the 3.5
development branch.
---
include/haproxy/compiler.h | 14 ++++++--------
src/debug.c | 2 +-
2 files changed, 7 insertions(+), 9 deletions(-)
diff --git a/include/haproxy/compiler.h b/include/haproxy/compiler.h
index 72792476c..697eb2787 100644
--- a/include/haproxy/compiler.h
+++ b/include/haproxy/compiler.h
@@ -124,14 +124,12 @@
#endif
/* tail-call elimination (turn the final call of a function into a jump). This
- * applies to the calling function. Clang doesn't support -f type optimizations
- * but has a dedicated attribute that we can use. Modern versions have a
- * "musttail" attribute at the statement level (per-return), but that's harder
- * to turn into something portable.
- */
-#if defined(__clang__) && __has_attribute(disable_tail_calls)
-# define eliminate_tail_calls __attribute__((disable_tail_calls))
-#elif !defined(__clang__) && __has_attribute(optimize)
+ * applies to the calling function. Clang has no function attribute to enable
+ * this optimization. Modern versions have a "musttail" attribute at the
+ * statement level (per-return), but that's harder to turn into something
+ * portable.
+ */
+#if !defined(__clang__) && __has_attribute(optimize)
# define eliminate_tail_calls
__attribute__((optimize("optimize-sibling-calls")))
#else
# define eliminate_tail_calls
diff --git a/src/debug.c b/src/debug.c
index 20b15a761..b3cb4e03a 100644
--- a/src/debug.c
+++ b/src/debug.c
@@ -293,7 +293,7 @@ void ha_dump_backtrace(struct buffer *buf, const char
*prefix, int dump)
/* dump a backtrace of current thread's stack to stderr. Displays the hint
about
* the core if hint & 1.
*/
-void ha_backtrace_to_stderr(int hint)
+__attribute__((noinline)) void ha_backtrace_to_stderr(int hint)
{
char area[8192];
struct buffer b = b_make(area, sizeof(area), 0, 0);
--
2.50.1 (Apple Git-155)