Skip to content

Do not instantiate non-template static data members in GetVariableOffset#1067

Merged
vgvassilev merged 1 commit into
compiler-research:mainfrom
conrade-ctc:fix-getvariableoffset-nontemplate-static
Jul 16, 2026
Merged

Do not instantiate non-template static data members in GetVariableOffset#1067
vgvassilev merged 1 commit into
compiler-research:mainfrom
conrade-ctc:fix-getvariableoffset-nontemplate-static

Conversation

@conrade-ctc

@conrade-ctc conrade-ctc commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Cpp::GetVariableOffset calls Sema::InstantiateVariableDefinition on any static data member whose address is not found and whose declaration has no initializer. That Sema entry point requires a templated variable: for a non-template class's static member, getTemplateInstantiationPattern() returns null, the assert is compiled out in release builds, and clang dereferences null (this == nullptr inside VarDecl::getDefinition).

The libstdc++ <compare> ordering types hit the exact triple condition — declared in-class without an initializer, defined out-of-line as inline constexpr (so no exported symbol to find), not yet JIT-emitted. Minimal repro on a release build:

Cpp::Declare("#include <compare>");
Cpp::GetVariableOffset(Cpp::GetNamed("less", Cpp::GetScope("std::partial_ordering")));  // SIGSEGV

Per review (keeping the constexpr fast path cheap): an already-parsed out-of-line definition is now resolved first — it may carry the initializer the constexpr evaluation consumes, with no Sema work at all — and the pattern-guarded InstantiateVariableDefinition is only the fallback for genuinely templated statics. getDefinition() is null-checked in both arms; the existing fallthrough then does the right thing for these variables (discardable-ODR linkage → codegen emission → real address). The regression test crashes at exactly this stack without the fix and passes with it.

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

@codecov

codecov Bot commented Jul 15, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 87.07%. Comparing base (3103be9) to head (cf7c88a).

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #1067      +/-   ##
==========================================
+ Coverage   87.01%   87.07%   +0.05%     
==========================================
  Files          23       23              
  Lines        6069     6073       +4     
==========================================
+ Hits         5281     5288       +7     
+ Misses        788      785       -3     
Files with missing lines Coverage Δ
lib/CppInterOp/CppInterOp.cpp 89.61% <100.00%> (+0.10%) ⬆️
Files with missing lines Coverage Δ
lib/CppInterOp/CppInterOp.cpp 89.61% <100.00%> (+0.10%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@conrade-ctc

Copy link
Copy Markdown
Contributor Author

cc @vgvassilev @rathorey-ctc

@github-actions

Copy link
Copy Markdown
Contributor

clang-tidy review says "All clean, LGTM! 👍"

@conrade-ctc
conrade-ctc force-pushed the fix-getvariableoffset-nontemplate-static branch from 378ef0c to 20d4b64 Compare July 15, 2026 19:32
@github-actions

Copy link
Copy Markdown
Contributor

clang-tidy review says "All clean, LGTM! 👍"

VD = Def;
}
if (VD->hasInit() &&
(VD->isConstexpr() || VD->getType().isConstQualified())) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The constexpr branch is critical for performance. Can the new code come after it?

@conrade-ctc conrade-ctc Jul 16, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

updated: the constexpr branch hasn't moved, and its hot path runs no new code: the guarded block sits inside if (!VD->hasInit()), and when there's no initializer the constexpr branch has nothing to evaluate yet — the instantiation exists to materialize the initializer that branch consumes, so it can't come after it. But the performance concern did point at a real win: upstream ran InstantiateVariableDefinition unconditionally (transaction push + Sema instantiation machinery). Reworked so an already-parsed out-of-line definition is preferred first — std::partial_ordering::less now reaches the constexpr evaluation with no Sema work at all — and the pattern-guarded instantiation is only the fallback for genuinely templated statics.

@conrade-ctc

Copy link
Copy Markdown
Contributor Author

CI triage for the two red jobs — neither traces to this diff:

  • ubu24-x86-gcc14-cling-llvm20-root: ROOT configure fails with ninja: build.ninja: etc/cppinterop/CppInterOp/Box.h is defined as an output multiple times — same failure on other open PRs (e.g. Rebase computation under RASTV and support for recursion #1064's last run).
  • ubu24-x86-gcc14-cling-llvm20-asan: LSAN exit-leak report, byte-identical to a failure on unchanged main (nightly run https://github.com/compiler-research/CppInterOp/actions/runs/29124927678): 74184/57 + 29144/1 + 25758/58 … = 269,234 bytes in 231 allocations, all indirect, allocated under an unrelated test's CreateInterpreter. The new tests may perturb how often the pre-existing leak manifests, but the leaked object predates this change.

All cling/clang-repl functional suites pass.

Cpp::GetVariableOffset called Sema::InstantiateVariableDefinition on any
static data member with no initializer and no resolved address. That Sema
entry point requires a variable instantiated from a template: for a
non-template class's static member (std::partial_ordering::less is the
in-the-wild shape), getTemplateInstantiationPattern() is null, the assert
is compiled out in release builds, and clang dereferences null.

Resolve an already-parsed out-of-line definition first - it may carry the
initializer the constexpr fast path evaluates, with no Sema work at all -
and only fall back to InstantiateVariableDefinition when an instantiation
pattern actually exists.

Co-developed-with-the-help-of: Claude Code (Fable 5, human in the loop)
@conrade-ctc
conrade-ctc force-pushed the fix-getvariableoffset-nontemplate-static branch from 20d4b64 to cf7c88a Compare July 16, 2026 17:59
@github-actions

Copy link
Copy Markdown
Contributor

clang-tidy review says "All clean, LGTM! 👍"

@vgvassilev vgvassilev left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM!

@vgvassilev
vgvassilev merged commit 321657d into compiler-research:main Jul 16, 2026
28 of 30 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants