Skip to content

Work around ORC emulated-TLS weak-discard crash on the in-process JIT#1045

Open
conrade-ctc wants to merge 1 commit into
compiler-research:mainfrom
conrade-ctc:orc-emutls-discard-workaround
Open

Work around ORC emulated-TLS weak-discard crash on the in-process JIT#1045
conrade-ctc wants to merge 1 commit into
compiler-research:mainfrom
conrade-ctc:orc-emutls-discard-workaround

Conversation

@conrade-ctc

Copy link
Copy Markdown
Contributor

Problem

On the in-process JIT, two incremental modules that each define the same
weak/linkonce_odr thread_local with a non-zero initializer crash the
interpreter. This happens whenever an inline function (or template static)
that odr-uses such a thread_local is pulled into two PartialTranslationUnits
— exactly what MakeFunctionCallable and repeated process() calls do.

Root cause is in LLVM ORC: IRMaterializationUnit's constructor registers
__emutls_t.<var> (emitted for non-zero-init emulated-TLS globals) in
SymbolFlags but not in SymbolToDefinition. When the second module
re-defines the weak symbol, JITDylib::defineImpl discards the duplicate via
IRMaterializationUnit::discard(), which looks the symbol up in
SymbolToDefinition, doesn't find it, and dereferences end() — an assertion
in +Asserts builds, heap corruption otherwise.

Fixed upstream in llvm/llvm-project#207161, but that only helps once the
LLVM baseline carries the fix (LLVM ≥ 23). This protects CppInterOp users on
current releases (21.x/22.x) in the meantime.

Fix

compat::dedupeWeakEmulatedTLS() (in Compatibility.h) runs on each module
before it reaches the JIT. For every weak/linkonce_odr non-zero-init
thread_local definition seen more than once, it demotes the duplicate to
available_externally. The IRMaterializationUnit constructor skips
available_externally globals, so the duplicate contributes no symbol, the
buggy discard() is never reached, and the first module remains the sole
definition that later modules resolve against within the JITDylib — i.e. the
dedup discard() was trying to do, done in IR before the crash.

It's wired into process() and the no-value ParseAndExecute(), covering both
user process() and wrapper compilation. Blast radius is limited to non-zero-init
weak thread_locals; every other weak/COMDAT symbol keeps the normal path,
where discard() already works.

Gated #if LLVM_VERSION_MAJOR < 23, so it compiles out once the fix is in the
baseline (mirroring the existing static_assert(LLVM_VERSION_MAJOR < 23, …)
deprecation pattern in Compatibility.h).

Test

FunctionReflection_DiscardDuplicateWeakEmulatedTLS reproduces the two-module
duplicate-weak-TLS scenario. Verified against a deliberately-unpatched
libLLVMOrcJIT.a: passes with the workaround, aborts without it (negative
control). Full suite stays green.

🤖 Done with the help of Claude Code (Claude Opus 4.8, human in the loop)

@conrade-ctc

Copy link
Copy Markdown
Contributor Author

@vgvassilev, here's a compatability patch/work-around for the bug that I just posted a PR to clang for.

@codecov

codecov Bot commented Jul 2, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 86.77%. Comparing base (e3fc693) to head (2ef5452).

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #1045      +/-   ##
==========================================
+ Coverage   86.73%   86.77%   +0.04%     
==========================================
  Files          23       23              
  Lines        5970     5990      +20     
==========================================
+ Hits         5178     5198      +20     
  Misses        792      792              
Files with missing lines Coverage Δ
lib/CppInterOp/Compatibility.h 82.81% <100.00%> (+0.62%) ⬆️
lib/CppInterOp/CppInterOpInterpreter.h 84.33% <100.00%> (+0.72%) ⬆️
Files with missing lines Coverage Δ
lib/CppInterOp/Compatibility.h 82.81% <100.00%> (+0.62%) ⬆️
lib/CppInterOp/CppInterOpInterpreter.h 84.33% <100.00%> (+0.72%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@github-actions github-actions Bot 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.

clang-tidy made some suggestions

Comment thread lib/CppInterOp/Compatibility.h
Comment thread lib/CppInterOp/Compatibility.h Outdated
Comment thread lib/CppInterOp/CppInterOpInterpreter.h
Comment thread unittests/CppInterOp/FunctionReflectionTest.cpp
@conrade-ctc conrade-ctc force-pushed the orc-emutls-discard-workaround branch from af53887 to 7dc5507 Compare July 2, 2026 13:00
Comment thread lib/CppInterOp/Compatibility.h Outdated
@conrade-ctc conrade-ctc force-pushed the orc-emutls-discard-workaround branch from 7dc5507 to 6f78b14 Compare July 3, 2026 08:12

@github-actions github-actions Bot 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.

clang-tidy made some suggestions

Comment thread lib/CppInterOp/Compatibility.h
Comment thread unittests/CppInterOp/FunctionReflectionTest.cpp
@conrade-ctc conrade-ctc force-pushed the orc-emutls-discard-workaround branch from 6f78b14 to cedb7ec Compare July 3, 2026 08:53

@github-actions github-actions Bot 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.

clang-tidy made some suggestions

Comment thread lib/CppInterOp/Compatibility.h
@conrade-ctc conrade-ctc force-pushed the orc-emutls-discard-workaround branch from cedb7ec to 8977039 Compare July 3, 2026 09:08
@github-actions

github-actions Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

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

@conrade-ctc conrade-ctc force-pushed the orc-emutls-discard-workaround branch from 8977039 to fa9c5cc Compare July 3, 2026 10:00
@github-actions

github-actions Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

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

@conrade-ctc conrade-ctc force-pushed the orc-emutls-discard-workaround branch from fa9c5cc to 835969d Compare July 3, 2026 10:14

@github-actions github-actions Bot 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.

clang-tidy made some suggestions

Comment thread lib/CppInterOp/CppInterOpInterpreter.h
@conrade-ctc conrade-ctc force-pushed the orc-emutls-discard-workaround branch from 835969d to 33714af Compare July 3, 2026 12:53
Demote duplicate weak thread_local definitions to available_externally before
they reach the JIT, sidestepping the buggy IRMaterializationUnit::discard
(fixed upstream by llvm/llvm-project#207161; compiles out on clang >= 23).

Cover every weak thread_local regardless of initializer: one with a non-trivial
ctor is zeroinitializer in IR (its real init runs in a __tls_init function) yet
still emits the emulated-TLS companion that trips discard. Adds regression tests
for both non-zero- and zero-init weak thread_locals.

Scoped to ELF: on COFF the demotion orphans comdat-associated members and on
Mach-O it breaks the paired _ZTH/_ZTW emission -- and on both, the JIT fails to
resolve __emutls_get_address before the discard bug is reachable, so the tests
skip there.

Co-developed-with-the-help-of: Claude Code (Claude Opus 4.8, human in the loop)
@conrade-ctc conrade-ctc force-pushed the orc-emutls-discard-workaround branch from 33714af to 2ef5452 Compare July 3, 2026 13:03
@github-actions

github-actions Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

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

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