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. Clang 13 never optimizes the call in any
case.
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.
#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
/* 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);