Skip to content
This repository was archived by the owner on Aug 5, 2026. It is now read-only.

Suppress debug info on the AppendTypesSlow trampoline variable - #218

Merged
aaronj0 merged 1 commit into
compiler-research:masterfrom
conrade-ctc:nodebug-appendtypesslow-trampoline
Jul 23, 2026
Merged

Suppress debug info on the AppendTypesSlow trampoline variable#218
aaronj0 merged 1 commit into
compiler-research:masterfrom
conrade-ctc:nodebug-appendtypesslow-trampoline

Conversation

@conrade-ctc

Copy link
Copy Markdown

What

Marks the __Cppyy_AppendTypesSlow trampoline variable __attribute__((nodebug)).

Why

Since #213 ("Resolve templated type names as a whole in GetScope"), GetScope's
fallback for templated names routes the whole type expression through a
__Cppyy_AppendTypesSlow<expr> var; declaration. When the interpreter runs with
debug info enabled (-g in CPPINTEROP_EXTRA_INTERPRETER_ARGS), each such
declaration emits the complete debug-info DIE tree of every template argument —
for a heavyweight class that is every member declaration, each becoming a uniqued
DISubprogram probed against the LLVMContext's ever-growing uniquing set. The
variable name is fresh on every call, so nothing is cached and the cost is paid
per failed templated-name lookup.

On a large real-world workload (market-data replay JIT-compiling large headers,
-ggdb3), this regressed wall clock ~8.5x (162s → 1378s); a profile showed >90%
of CPU in DenseMap<DISubprogram*, ...> probe/insert inside the JIT. The previous
split-and-InstantiateTemplate path was Sema-only and never paid codegen or debug
info. With the trampoline variable marked nodebug, the workload returns to 137s —
faster than before the GetScope rework — while user code keeps full debug info and
the whole-expression resolution semantics are unchanged.

The trampoline exists only to be reflected on via GetVariableType, so its debug
info carries no value.

test_templates.py / test_stltypes.py / test_datatypes.py / test_regression.py
pass with the change (they exercise AppendTypesSlow through templated lookups).

🤖 Done with the help of Claude Code (Fable 5, human in the loop)

With -g in the interpreter args, each __Cppyy_AppendTypesSlow<...> declare
emits the full debug-info DIE tree of every template argument (all member
declarations included). Since 'Resolve templated type names as a whole in
GetScope' routes whole type expressions through the trampoline, that cost
is paid per failed templated-name lookup, is uncacheable (fresh variable
each call), and grows with the context's uniqued-DISubprogram set -- an
8.5x wall-clock regression on debug-info-enabled JIT workloads with large
headers. The trampoline exists only to be reflected on, so mark the
variable nodebug.

Co-developed-with-the-help-of: Claude Code (Fable 5, human in the loop)
@conrade-ctc

conrade-ctc commented Jul 16, 2026

Copy link
Copy Markdown
Author

@vgvassilev, @guitargeek, this is another one we started hitting after a rebase that included the above ref'd stuff, but note that it was an unexpected interaction with -g debug info. easy surgical fix.

@aaronj0 aaronj0 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGMT!

// nodebug: with -g the variable's debug info would carry the full DIE
// tree of every template argument (all member declarations included) --
// a large, uncacheable per-lookup cost on heavyweight types.
if (!Cpp::Declare(("__Cppyy_AppendTypesSlow<" + candidate + "> __attribute__((nodebug)) " + var + ";\n").c_str(), /*silent=*/true)) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

That causes the wrappers to be bigger and thus memory hoarding of already a heavy operation. Is that a huge problem for the debug setup?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

It's a large, measured problem: with -g, each probe variable's debug info pulls the complete DIE tree of its template argument (all member declarations, transitively) into the PTU — on our production replay (large-header trading workload, heavy AppendTypesSlow traffic) that was a 10x wall-clock regression, 1378s → 137s after suppressing, plus the DWARF itself hoarded per lookup. Against that, the attribute adds 26 bytes to a per-probe source buffer that already exists — paid on non-debug setups too, but it's noise next to the buffer itself. If even that bothers you I can gate the attribute text on whether the interpreter session has debug info enabled, at the cost of a slightly less uniform probe string.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

In that case, we can cheat on the JitCall side where just after we parse the prototype, we can check if we passed -g to the interpreter, and then call setAttr(createAttr(nodebug)) on wrapper declaration. That would save bytes I believe.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

No wait, on a second thought that might be suboptimal. Let's move forward with the current approach -- it would give us one more incentive to get rid of this string manipulations..

@vgvassilev vgvassilev left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM!

@aaronj0
aaronj0 merged commit 5805043 into compiler-research:master Jul 23, 2026
23 of 25 checks passed
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants