Skip to content

fix(test): stop -O3 tail-calling away the return-address trampolines - #819

Merged
rkennke merged 2 commits into
mainfrom
roman/fix-ra-attribution-tailcall
Sep 25, 2026
Merged

rkennke merged 2 commits into
mainfrom
roman/fix-ra-attribution-tailcall

Conversation

@rkennke

@rkennke rkennke commented Sep 24, 2026

Copy link
Copy Markdown
Contributor

What

ReturnAddressAttributionTest.Test3_UnwindRowSelectedAtReturnAddress fails in the release configuration on x86_64 (returnAddressAttribution_ut.cpp:929).

prof_ra_cfi_trampoline exists so the caller of the boundary frame is a specific, symbolizable function rather than the gtest body. At -O3 the compiler turns its call into a tail jump:

prof_ra_cfi_trampoline:
  movl $0x1,-0x4(%rsp)
  mov  -0x4(%rsp),%eax
  jmp  prof_ra_cfi_caller      <-- tail call, no frame

With the jmp the trampoline never establishes a frame, so at runtime the real caller of prof_ra_cfi_caller is TestBody. The walker reports that correctly — this is a fixture bug, not a walker bug. chain[kBoundaryIndex] still resolves to prof_ra_cfi_caller, so the boundary traversal the test was written to cover is unaffected; only the frame-identity assertion at [2] encoded a call-shape assumption that -O3 invalidates.

Why the existing guard didn't hold

The fixture carried noinline plus a volatile int guard, commented "volatile to defeat tail-call/inlining folding this frame away." Neither does: noinline stops the trampoline being inlined into its caller, and the volatile store completed before the call.

Moving the volatile store after the call makes the call non-tail by construction. That is compiler-agnostic, unlike __attribute__((disable_tail_calls)), which is clang-only while ConfigurationPresets still carries a gcc path.

prof_ra_plt_trampoline had the identical defect; its test (Test4) is currently gated off on x86_64 by the alignment check, so it never surfaced. Both are fixed.

Verification

Release codegen after the change, for both trampolines:

push %rbp / mov %rsp,%rbp / ... / call <callee> / movl $0x2 / ret

Run on linux-x64 in all four configurations — release, debug, asan, tsan — each 15 passed, 3 arch-gated skips, 0 failures. spotlessCheck green.

Not verified from this box: aarch64 release, which is presumably the same failure (Test3 runs there and -O3 tail-calls there too). test:release is applied to this PR so the release matrix actually exercises it.

Why CI stayed green on #786

The failure dates to #786, which added the fixture — no later commit is involved; it reproduces at 0d0653365 itself.

PR CI builds its config matrix from PR labels (ci.yml, compute-configurations): debug always, release only behind a test:release label. #786 carried only sphinx:critical, so every cell in its run read (<jdk>, debug, regular) — no release cell existed. gtests did run there, in debug, and passed legitimately. The asan and tsan presets both pass -fno-optimize-sibling-calls and no -O, so they cannot exhibit it either. -O3 is the only configuration that produces the tail call, and it is opt-in.

Not quarantine-related: no matching entry in ddprof-test/quarantine.txt.

Worth deciding separately (not changed here): release gtests being label-gated means release-only breakage stays invisible on a normal PR. Also nightly.yml:18's comment that gtests "run on every PR via native-sanitizer-tests in ci.yml" is stale — ci.yml has no sanitizer job.

🤖 Generated with Claude Code

Test3_UnwindRowSelectedAtReturnAddress fails in the release configuration on
x86_64. prof_ra_cfi_trampoline exists so that the caller of the boundary frame
is a specific, symbolizable function rather than the gtest body, but at -O3 the
compiler turns its call into a tail jump:

    prof_ra_cfi_trampoline:
      movl $0x1,-0x4(%rsp)
      mov  -0x4(%rsp),%eax
      jmp  prof_ra_cfi_caller

With the jmp the trampoline never establishes a frame, so at runtime the real
caller of prof_ra_cfi_caller is TestBody. The walker reports that correctly and
the fixture's expectation is what is wrong -- chain[kBoundaryIndex] still
resolves to prof_ra_cfi_caller, so the boundary traversal the test was written
to cover is unaffected.

The existing guard did not prevent this. noinline stops the trampoline being
inlined into its caller, and the volatile store completed before the call, so
neither blocked the tail jump. Moving the volatile store after the call makes
the call non-tail by construction, which is compiler-agnostic -- unlike
__attribute__((disable_tail_calls)), which is clang-only while
ConfigurationPresets still carries a gcc path.

prof_ra_plt_trampoline had the same defect; its test is currently gated off on
x86_64 by the alignment check, so it never surfaced. Both are fixed.

Release codegen after the change, for both trampolines:

      push %rbp / mov %rsp,%rbp / ... / call <callee> / movl $0x2 / ret

Verified on linux-x64 in all four configurations -- release, debug, asan and
tsan -- each 15 passed, 3 arch-gated skips, 0 failures.

The failure dates to #786, which added the fixture; no later commit is
involved. It stayed invisible because PR CI builds its matrix from labels
(ci.yml, compute-configurations): debug always, release only behind a
test:release label. #786 carried only sphinx:critical, so every cell in its
run was debug, where the tail call does not occur. The asan and tsan presets
both pass -fno-optimize-sibling-calls, so they cannot exhibit it either.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
@rkennke
rkennke requested a review from a team as a code owner September 24, 2026 17:40
@rkennke rkennke added the test:release Run CI tests with release configuration label Sep 24, 2026

@datadog-datadog-us1-prod datadog-datadog-us1-prod Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bits Code Review: PASS

More details

The post-call volatile stores prevent tail-call optimization and keep both trampoline frames present.

Was this helpful? React 👍 or 👎

Open Bits AI session

🤖 Bits Code Review · Commit 269ba26 · @DataDog review to ask questions

@dd-octo-sts

dd-octo-sts Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

✅ All 40 integration tests passed

📊 Dashboard · 👷 Pipeline · 📦 8b500d4f

@dd-octo-sts

dd-octo-sts Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

CI Test Results

Run: #36051969614 | Commit: aed2258 | Duration: 16m 15s (longest job)

❌ 1 of 64 test jobs failed

Status Overview

JDK glibc-aarch64/debug glibc-aarch64/release glibc-amd64/debug glibc-amd64/release musl-aarch64/debug musl-aarch64/release musl-amd64/debug musl-amd64/release
8 - - ✅ ✅ - - - -
8-ibm - - ✅ ✅ - - - -
8-j9 ✅ ✅ ✅ ✅ - - - -
8-librca - - - - ✅ ✅ ✅ ✅
8-orcl - - ✅ ✅ - - - -
11 - - ✅ ✅ - - - -
11-j9 ✅ ✅ ✅ ✅ - - - -
11-librca - - - - ✅ ✅ ✅ ✅
17 ✅ ✅ ✅ ✅ - - - -
17-graal ✅ ✅ ❌ ✅ - - - -
17-j9 ✅ ✅ ✅ ✅ - - - -
17-librca - - - - ✅ ✅ ✅ ✅
21 ✅ ✅ ✅ ✅ - - - -
21-graal ✅ ✅ ✅ ✅ - - - -
21-librca - - - - ✅ ✅ ✅ ✅
25 ✅ ✅ ✅ ✅ - - - -
25-graal ✅ ✅ ✅ ✅ - - - -
25-librca - - - - ✅ ✅ ✅ ✅

Legend: ✅ passed | ❌ failed | ⚪ skipped | 🚫 cancelled

Failed Jobs

Summary: Total: 64 | Passed: 63 | Failed: 1


Updated: 2026-09-24 20:20:13 UTC

The volatile store before the call never contributed anything. Both compilers
emit it and then tear the frame down anyway -- that is the original defect, not
a weaker version of the fix. Only the store after the call leaves work to do on
return, which is what stops the call being turned into a tail jump.

Keeping both halves left the ineffective one sitting directly under a comment
claiming it worked, which is how the first version came to be believed.

Codegen checked at -O3 with clang and gcc on x86_64 and with clang on aarch64:
a store only before the call tail-jumps, a store only after does not, and the
two together are no better than after alone.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
@rkennke
rkennke merged commit 4065563 into main Sep 25, 2026
141 of 143 checks passed
@rkennke
rkennke deleted the roman/fix-ra-attribution-tailcall branch September 25, 2026 08:18
@github-actions github-actions Bot added this to the 1.51.0 milestone Sep 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

test:release Run CI tests with release configuration

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant