Skip to content

clangsa: add thread-safety capability analysis of safepoint annotations#62288

Merged
vtjnash merged 4 commits into
masterfrom
jn/clang-safety-checks
Jul 15, 2026
Merged

clangsa: add thread-safety capability analysis of safepoint annotations#62288
vtjnash merged 4 commits into
masterfrom
jn/clang-safety-checks

Conversation

@vtjnash

@vtjnash vtjnash commented Jul 7, 2026

Copy link
Copy Markdown
Member

Add an Clang Thread Safety Analysis ("capability") model of Julia's safepoints, selected by compiling with make safesrc parallel to how -D__clang_gcanalyzer__ enables the GCChecker annotations. This turns the unreliable NOTSAFEPOINT thread-safety checks into mandatory-enforced capabilities CANSAFEPOINT. This is much faster, more reliable, and the error messages are more precise.

tl;dr; in the future, annotate functions with CANSAFEPOINT family macros if they can reach a safepoint (e.g. an allocation function). In a subsequent PR, I'll remove all NOTSAFEPOINT annotations.

Implementation: the safepoint annotations map onto a zero-sized jl_can_safepoint token capability: JL_CANSAFEPOINT requires it, JL_NOTSAFEPOINT_ENTER and JL_NOTSAFEPOINT_LEAVE release and acquire it, and JL_NOTSAFEPOINT holds nothing, so -Wthread-safety flags a JL_NOTSAFEPOINT function that reaches a safepoint. Because this checker has no implicit default, it requires JL_CANSAFEPOINT on every function that is not JL_NOTSAFEPOINT.

To add those annotations in bulk, there was a
julia-safepoint-annotations clang-tidy check auto fixit script used, which inserted JL_CANSAFEPOINT on the first declaration of any function that lacks a safepoint dispositiom. This then required many manual fixes afterwards, since there were many mistakes in the NOTSAFEPOINT annotations, a few places where the old default-safe analysis wasn't permitted under the new stricter analysis. There are still a few places where the analyzer is disabled due to existing bugs.

Also teach the GCChecker to treat any JL_CANSAFEPOINT function as a possible safepoint even when it is inlined and no safepoint is found in its body, so the annotation is honored as a promise rather than re-derived from the body. This is a prelude to erasing JL_NOTSAFPOINT entirely, since it is no longer necessary.

Lastly, we teach our clang-tidy first-decl pass to ensure this capability is tracked through function pointers also (upstream LLVM cannot do this). We'll unfortunately also eventually need to make some bugfixes to the clang-23 that is currently pre-release to handle function pointers correctly.

@vtjnash vtjnash force-pushed the jn/clang-safety-checks branch from e733f0a to 0090f6e Compare July 7, 2026 13:08

@fingolfin fingolfin left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for working on this! An obvious blocker is that doc/src/devdocs/gc-sa.md is not being updated to document the new annotation.

Do I understand right that the plan is that instead of annotating functions that do not contain a safepoint, we instead annotation function that might contain a safepoint? So we kinda flip what the "default" assumption is.

@vtjnash vtjnash force-pushed the jn/clang-safety-checks branch from 0090f6e to 5aea342 Compare July 7, 2026 15:34
@vtjnash

vtjnash commented Jul 7, 2026

Copy link
Copy Markdown
Member Author

That's right. We need about half as many annotations for CANSAFEPOINT than we had for NOTSAFEPOINT, and we're now able to correctly annotate more of codegen. It also means we can remove the list of all of the exceptions to that, and exceptions to the exceptions, and the exceptions to those, and so on (though CANSAFEPOINT does have some exceptions too). Because it can be checked with a total proof, it also means we validate it separately from gc-analyzer, making the heuristic gc algorithm more precise (by removing unnecessary states).

@vtjnash vtjnash force-pushed the jn/clang-safety-checks branch 4 times, most recently from f5faf0d to 112bcd3 Compare July 8, 2026 17:58
@vtjnash vtjnash requested a review from fingolfin July 8, 2026 17:58

@fingolfin fingolfin left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'll try to look through this, but it's a lot. For now a quick comment on the doc changes (thanks for those!)

Comment thread doc/src/devdocs/gc-sa.md Outdated
Comment thread doc/src/devdocs/gc-sa.md Outdated
@vtjnash vtjnash force-pushed the jn/clang-safety-checks branch from 112bcd3 to 408ef75 Compare July 13, 2026 17:06
@vtjnash vtjnash dismissed fingolfin’s stale review July 13, 2026 19:22

Documentation updated. Will address other feedback post-merge as well–there's many other places to fix, this is just trying to get in the framework so that we can enforce LLM stops introducing more mistakes.

@vtjnash vtjnash added DO NOT MERGE Do not merge this PR! and removed DO NOT MERGE Do not merge this PR! labels Jul 14, 2026
vtjnash and others added 4 commits July 15, 2026 14:26
Add an Clang Thread Safety Analysis ("capability") model of Julia's
safepoints, selected by compiling with `make safesrc` parallel to how
-D__clang_gcanalyzer__ enables the GCChecker annotations. This turns the
unreliable NOTSAFEPOINT thread-safety checks into mandatory-enforced
capabilities.

Implementation: the safepoint annotations map onto a zero-sized
jl_can_safepoint token capability: JL_CANSAFEPOINT requires it,
JL_NOTSAFEPOINT_ENTER and JL_NOTSAFEPOINT_LEAVE release and acquire it,
and JL_NOTSAFEPOINT holds nothing, so -Wthread-safety flags a
JL_NOTSAFEPOINT function that reaches a safepoint. Because this checker
has no implicit default, it requires JL_CANSAFEPOINT on every function
that is not JL_NOTSAFEPOINT.

To add those annotations in bulk, there was a
julia-safepoint-annotations clang-tidy check auto fixit script used,
which inserted JL_CANSAFEPOINT on the first declaration of any function
that lacks a safepoint dispositio. This then required many manual fixes
afterwards, since there were many mistakes in the NOTSAFEPOINT
annotations, a few places where the old default-safe analysis wasn't
permitted under the new stricter analysis. There are still a few places
where the analyzer is disabled due to existing bugs.

Also teach the GCChecker to treat any JL_CANSAFEPOINT function as a
possible safepoint even when it is inlined and no safepoint is found in
its body, so the annotation is honored as a promise rather than
re-derived from the body. This is a prelude to erasing JL_NOTSAFPOINT
entirely, since it is no longer necessary.

Lastly, we teach our clang-tidy first-decl pass to ensure this
capability is tracked through function pointers also (upstream LLVM
cannot do this).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nnotations

Simplifying the logical flow of jl_record_backtrace also revealed some
latent suboptimal behavior, which we can address, though is not
functionally relevant normally.
@vtjnash vtjnash force-pushed the jn/clang-safety-checks branch from 408ef75 to 58adf05 Compare July 15, 2026 14:30
@vtjnash vtjnash added the merge me PR is reviewed. Merge when all tests are passing label Jul 15, 2026
@vtjnash vtjnash merged commit 9821f8a into master Jul 15, 2026
6 of 10 checks passed
@vtjnash vtjnash deleted the jn/clang-safety-checks branch July 15, 2026 18:56
@adienes adienes removed the merge me PR is reviewed. Merge when all tests are passing label Jul 16, 2026
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.

3 participants