Skip to content

[BugFix] Fix two-instance modeling in ThreadSync cross-thread race checks - #2805

Merged
SiriusNEO merged 4 commits into
tile-ai:mainfrom
LJC00118:qwq31
Jul 30, 2026
Merged

[BugFix] Fix two-instance modeling in ThreadSync cross-thread race checks#2805
SiriusNEO merged 4 commits into
tile-ai:mainfrom
LJC00118:qwq31

Conversation

@LJC00118

@LJC00118 LJC00118 commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Fixed ThreadSync cross-thread race modeling by introducing/using FreshenMutableReads, improving constraint renaming/substitution/merging semantics, and refining is_assume/assumption handling to avoid incorrect “two-instance” cross-thread equivalence checks.
  • Strengthened barrier hoisting decisions under divergent/non-uniform control flow by adding a proof-based “block-uniform condition” check for if hoist candidates and refining condition/thread-variable property tracking.
  • Improved cross-thread conflict detection by adjusting constraint preparation/instance substitution for disjoint-pointer and cross-thread hazard proofs, and refining same-index conflict short-circuiting.
  • Reworked parallel-loop race verification to model “other iteration” constraints via renamed constraint-set merging and updated equality proof logic accordingly.
  • Expanded ThreadSync regression coverage for cases where thread-private accesses must not sync (including modulo-if, non-uniform if/else branches, loop cases), plus new assertions around barrier insertion and unorderable-hazard reporting with premise/assume/bind interactions.
  • Minor doc maintenance: added disjointness to the spelling wordlist.

C++ style / lint notes

  • This PR changes C++ implementation/declarations (not documented C++ style rules) and does not modify docs/developer_guide/cpp_style.md.
  • The CI step “C++ API Style Audit (warning only)” may emit advisory findings (e.g., TLCPP003/TLCPP004) due to header surface changes (adding tvm::tl::FreshenMutableReads and Constr::FreshenReads, and related constraint visitor/set behavior). These are warning-only and should be treated separately from correctness/build/test outcomes.

@github-actions

Copy link
Copy Markdown

👋 Hi! Thank you for contributing to the TileLang project.

Please remember to run pre-commit run --all-files in the root directory of the project to ensure your changes are properly linted and formatted. This will help ensure your contribution passes the format check.

We appreciate you taking this step! Our team will review your contribution, and we look forward to your awesome work! 🚀

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The change adds fresh mutable-read handling and richer constraint-set operations, applies proof-based uniformity to synchronization hoisting, revises cross-thread conflict and parallel-loop race analysis, and expands regression tests for divergent and thread-private access patterns.

Changes

Thread synchronization analysis

Layer / File(s) Summary
Constraint freshness and guard modeling
src/transform/common/constr_visitor.h, docs/spelling_wordlist.txt
Adds mutable-read freshening, preserves constraint kinds and assumptions during substitution and population, refines constraint-set merging and renaming, and applies pure assertion guards within sequences.
Uniformity proving and barrier hoisting
src/transform/thread_storage_sync.cc
Adds constraint-backed block-uniformity proofs and updates synchronization hoisting decisions for conditional control flow.
Cross-thread conflict and race proofs
src/transform/thread_storage_sync.cc, src/transform/verify_parallel_loop.cc
Uses renamed, predicate-lowered constraint sets for pointer disjointness, conflict detection, and parallel-loop race checks.
Barrier behavior regression coverage
testing/python/transform/test_tilelang_transform_thread_sync.py
Adds tests for thread-private accesses, divergent guards, flat binds, premise handling, barrier insertion, and hazard reporting.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant TileLangThreadSyncPlanner
  participant IsBlockUniformCondition
  participant ConstrSet
  participant Analyzer
  TileLangThreadSyncPlanner->>IsBlockUniformCondition: prove if-condition uniformity
  IsBlockUniformCondition->>ConstrSet: rename thread instances and merge constraints
  ConstrSet->>Analyzer: populate predicate constraints
  Analyzer-->>IsBlockUniformCondition: return agreement proof
  IsBlockUniformCondition-->>TileLangThreadSyncPlanner: return uniformity result
  TileLangThreadSyncPlanner->>TileLangThreadSyncPlanner: apply barrier hoisting decision
Loading

Possibly related PRs

  • tile-ai/tilelang#2806: Modifies the parallel-loop race-checking path and aggregates race diagnostics with span information.

Suggested reviewers: jjppp

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 51.02% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: fixing two-instance modeling in ThreadSync cross-thread race checks.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🧹 Nitpick comments (7)
src/transform/thread_storage_sync.cc (2)

1533-1540: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

threads.size() + idx - 3 underflows when fewer than three thread dims are bound.

lhs.threads / rhs.threads are indexed as size() + idx - 3; with fewer than three entries this is unsigned wraparound and an out-of-range Array access. TileLang normally binds tx/ty/tz, so this mirrors the existing assumption in FindConflict, but an ICHECK_GE(lhs.threads.size(), 3U) here would turn a potential OOB into a clear diagnostic.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/transform/thread_storage_sync.cc` around lines 1533 - 1540, Add an
ICHECK_GE(lhs.threads.size(), 3U) precondition before the indexing loop in the
thread-variable synchronization logic, ensuring fewer than three bound thread
dimensions produce a clear diagnostic instead of unsigned underflow. Keep the
existing indexing and substitution behavior unchanged for valid inputs.

1014-1028: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Second warning over-claims in the requires_hoist case.

The unconditional "the race remains" message assumes both ends of the conflict sit inside the branch. That holds for the depends_on_runtime && !is_block_uniform path, but the requires_hoist && !proven_uniform path hoists because ThreadPartialSyncRewriter cannot lower a partial barrier — there the hoist can be the correct fix, and telling users the race remains is misleading. Consider gating the second warning on the first predicate only.

♻️ Suggested split
-      if ((condition_prop.depends_on_runtime && !is_block_uniform) ||
-          (condition_prop.requires_hoist && !proven_uniform)) {
+      bool divergent_runtime =
+          condition_prop.depends_on_runtime && !is_block_uniform;
+      bool unlowerable_partial =
+          condition_prop.requires_hoist && !proven_uniform;
+      if (divergent_runtime || unlowerable_partial) {
         LOG(WARNING)
             << "[ThreadSync] Hoisting sync from inside if to before if. "
             << "Condition is not safe for in-if sync: " << op->condition;
-        LOG(WARNING) << "[ThreadSync] The hoisted barrier no longer separates "
+        if (divergent_runtime) {
+          LOG(WARNING) << "[ThreadSync] The hoisted barrier no longer separates "
                         "the accesses it was inserted for, as both ends of the "
                         "conflict are inside the branch, so the race remains. "
                         "Constrain the condition -- a T.assume on the "
                         "parameters it reads -- to keep the barrier in place.";
+        }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/transform/thread_storage_sync.cc` around lines 1014 - 1028, Gate the
second warning about the hoisted barrier no longer separating accesses and the
race remaining on the `condition_prop.depends_on_runtime && !is_block_uniform`
predicate only. Keep the initial hoisting warning for both predicates, but do
not emit the race-warning text when hoisting is caused solely by
`condition_prop.requires_hoist && !proven_uniform`.
src/transform/common/constr_visitor.h (2)

373-383: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Asserts outside a SeqStmt now contribute no constraint.

With the standalone VisitStmt_(AssertStmtNode) removed, a tirx::AssertStmt that is the sole statement of a body (not wrapped in a SeqStmt) is silently ignored, so its premise is unavailable to downstream proofs. The direction is safe (fewer facts ⇒ more barriers), but it is an easy-to-miss hole; a fallback override that pushes the pure condition for the remainder of the enclosing scope, or a brief comment recording the limitation, would help.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/transform/common/constr_visitor.h` around lines 373 - 383, Handle pure
tirx::AssertStmt nodes that occur outside SeqStmt traversal so their conditions
remain available to downstream proofs; update the AssertStmtNode handling in the
statement visitor to push the condition for the enclosing scope, or document the
intentional limitation if it cannot be supported. Preserve the existing
side-effect purity check and mutable-state semantics.

261-267: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider rate-limiting this warning.

Merge is reached from FindConflict/PointerAccessIsDisjoint, which run once per access pair per statement, so a single mis-renamed bind would emit this warning O(n²) times per kernel. LOG_EVERY_N(WARNING, ...) (or DLOG) keeps the diagnostic without flooding the build log.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/transform/common/constr_visitor.h` around lines 261 - 267, Rate-limit the
conflicting-bind warning in ConstrSet::Merge, which can be reached repeatedly
through FindConflict and PointerAccessIsDisjoint. Replace the unconditional
LOG(WARNING) emission with the project’s supported rate-limited warning
mechanism, such as LOG_EVERY_N, while preserving the existing diagnostic message
and values.
testing/python/transform/test_tilelang_transform_thread_sync.py (3)

1284-1285: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the placement this test is named for.

test_sync_may_stay_inside_block_uniform_guard only checks that a barrier exists, so it would still pass if the barrier were hoisted out of the uniform guard — exactly the regression it is meant to pin.

♻️ Add the position check
     s = run_passes_script(func)
     assert 'T.tvm_storage_sync("shared")' in s, f"Expected a barrier for a cross-thread hazard:\n{s}"
+    sync_pos = s.index('T.tvm_storage_sync("shared")')
+    if_pos = s.index("if flag < 0")
+    assert sync_pos > if_pos, f"Barrier should stay inside the block-uniform guard:\n{s}"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@testing/python/transform/test_tilelang_transform_thread_sync.py` around lines
1284 - 1285, Update test_sync_may_stay_inside_block_uniform_guard to assert that
T.tvm_storage_sync("shared") remains inside the block’s uniform guard, not
merely that it exists in the generated script. Inspect the generated string from
run_passes_script(func) and verify the barrier’s position relative to the guard
markers so hoisting it outside the guard fails the test.

590-593: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Three negative tests run ThreadSync outside the shared harness. Each builds tvm.IRModule({"main": func}) and calls the pass directly, skipping the cuda target attribute, AnnotateDeviceRegions, and SplitHostDevice that run_passes applies — so these "no sync expected" assertions validate a different pass configuration than the rest of the file.

  • testing/python/transform/test_tilelang_transform_thread_sync.py#L590-L593: replace the manual module build and pass invocation with s = run_passes_script(func).
  • testing/python/transform/test_tilelang_transform_thread_sync.py#L711-L714: same replacement with run_passes_script(func).
  • testing/python/transform/test_tilelang_transform_thread_sync.py#L769-L772: same replacement with run_passes_script(func).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@testing/python/transform/test_tilelang_transform_thread_sync.py` around lines
590 - 593, Update the three negative tests at
testing/python/transform/test_tilelang_transform_thread_sync.py lines 590-593,
711-714, and 769-772 to replace manual IRModule construction and direct
ThreadSync invocation with s = run_passes_script(func), preserving each existing
no-sync assertion.

1116-1122: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Extend the test harness for non-standard pass orders.

InjectAssumes is exposed as tilelang.transform.InjectAssumes, but this test bypasses run_passes for a specific pass ordering. If this ordering should be reusable, add support for an optional extra pass/list instead of duplicating the helper body.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@testing/python/transform/test_tilelang_transform_thread_sync.py` around lines
1116 - 1122, The test’s manual pass sequence should be reusable through the
existing run_passes helper. Extend run_passes to accept an optional extra pass
or pass list, then invoke it for this non-standard ordering so InjectAssumes is
applied without duplicating the helper’s setup logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@testing/python/transform/test_tilelang_transform_thread_sync.py`:
- Around line 917-921: Update the placement assertion around run_passes_script
and the shared tvm_storage_sync marker to locate the specific divergent guard
text, matching the precise guard anchor used by the other placement tests,
instead of using the first generic "if " occurrence in the script.

---

Nitpick comments:
In `@src/transform/common/constr_visitor.h`:
- Around line 373-383: Handle pure tirx::AssertStmt nodes that occur outside
SeqStmt traversal so their conditions remain available to downstream proofs;
update the AssertStmtNode handling in the statement visitor to push the
condition for the enclosing scope, or document the intentional limitation if it
cannot be supported. Preserve the existing side-effect purity check and
mutable-state semantics.
- Around line 261-267: Rate-limit the conflicting-bind warning in
ConstrSet::Merge, which can be reached repeatedly through FindConflict and
PointerAccessIsDisjoint. Replace the unconditional LOG(WARNING) emission with
the project’s supported rate-limited warning mechanism, such as LOG_EVERY_N,
while preserving the existing diagnostic message and values.

In `@src/transform/thread_storage_sync.cc`:
- Around line 1533-1540: Add an ICHECK_GE(lhs.threads.size(), 3U) precondition
before the indexing loop in the thread-variable synchronization logic, ensuring
fewer than three bound thread dimensions produce a clear diagnostic instead of
unsigned underflow. Keep the existing indexing and substitution behavior
unchanged for valid inputs.
- Around line 1014-1028: Gate the second warning about the hoisted barrier no
longer separating accesses and the race remaining on the
`condition_prop.depends_on_runtime && !is_block_uniform` predicate only. Keep
the initial hoisting warning for both predicates, but do not emit the
race-warning text when hoisting is caused solely by
`condition_prop.requires_hoist && !proven_uniform`.

In `@testing/python/transform/test_tilelang_transform_thread_sync.py`:
- Around line 1284-1285: Update test_sync_may_stay_inside_block_uniform_guard to
assert that T.tvm_storage_sync("shared") remains inside the block’s uniform
guard, not merely that it exists in the generated script. Inspect the generated
string from run_passes_script(func) and verify the barrier’s position relative
to the guard markers so hoisting it outside the guard fails the test.
- Around line 590-593: Update the three negative tests at
testing/python/transform/test_tilelang_transform_thread_sync.py lines 590-593,
711-714, and 769-772 to replace manual IRModule construction and direct
ThreadSync invocation with s = run_passes_script(func), preserving each existing
no-sync assertion.
- Around line 1116-1122: The test’s manual pass sequence should be reusable
through the existing run_passes helper. Extend run_passes to accept an optional
extra pass or pass list, then invoke it for this non-standard ordering so
InjectAssumes is applied without duplicating the helper’s setup logic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 0df4447a-223f-4608-b447-9e34c3efa7a0

📥 Commits

Reviewing files that changed from the base of the PR and between e41fadb and 9986b9b.

📒 Files selected for processing (4)
  • src/transform/common/constr_visitor.h
  • src/transform/thread_storage_sync.cc
  • src/transform/verify_parallel_loop.cc
  • testing/python/transform/test_tilelang_transform_thread_sync.py

Comment on lines +917 to +921
s = run_passes_script(func)
if 'T.tvm_storage_sync("shared")' in s:
sync_pos = s.index('T.tvm_storage_sync("shared")')
if_pos = s.index("if ")
assert sync_pos < if_pos, f"Barrier must be hoisted out of the divergent guard:\n{s}"

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

s.index("if ") finds the first if in the whole script, not this guard.

If the printed module contains any earlier if (e.g. an assertion or a nested construct emitted by the harness), the position comparison checks the wrong anchor. The other placement tests already use the precise form; do the same here.

♻️ Anchor on the guard text
-    s = run_passes_script(func)
-    if 'T.tvm_storage_sync("shared")' in s:
-        sync_pos = s.index('T.tvm_storage_sync("shared")')
-        if_pos = s.index("if ")
-        assert sync_pos < if_pos, f"Barrier must be hoisted out of the divergent guard:\n{s}"
+    s = run_passes_script(func)
+    if 'T.tvm_storage_sync("shared")' in s:
+        sync_pos = s.index('T.tvm_storage_sync("shared")')
+        if_pos = s.index("if bx * 4 + tx // 32 < n")
+        assert sync_pos < if_pos, f"Barrier must be hoisted out of the divergent guard:\n{s}"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
s = run_passes_script(func)
if 'T.tvm_storage_sync("shared")' in s:
sync_pos = s.index('T.tvm_storage_sync("shared")')
if_pos = s.index("if ")
assert sync_pos < if_pos, f"Barrier must be hoisted out of the divergent guard:\n{s}"
s = run_passes_script(func)
if 'T.tvm_storage_sync("shared")' in s:
sync_pos = s.index('T.tvm_storage_sync("shared")')
if_pos = s.index("if bx * 4 + tx // 32 < n")
assert sync_pos < if_pos, f"Barrier must be hoisted out of the divergent guard:\n{s}"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@testing/python/transform/test_tilelang_transform_thread_sync.py` around lines
917 - 921, Update the placement assertion around run_passes_script and the
shared tvm_storage_sync marker to locate the specific divergent guard text,
matching the precise guard anchor used by the other placement tests, instead of
using the first generic "if " occurrence in the script.

Comment thread src/transform/thread_storage_sync.cc Outdated
Comment thread src/transform/common/constr_visitor.h

@coderabbitai coderabbitai 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/transform/thread_storage_sync.cc (1)

1503-1546: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Guard the 3-thread indexing before renaming conflicts.

PointerAccessIsDisjoint and FindConflict read access.threads[threads.size() + idx - 3] for idx = 0..2, but new access entries are populated directly from env_threads_. If fewer than three rank-1 thread dims are active / reachable, this can trigger an OOB access. Scope the loops to the actual tail entries present (std::min({lhs.threads.unsigned_size(), rhs.threads.unsigned_size(), 3U})) or otherwise guarantee exactly three rank-1 thread vars before this logic runs.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/transform/thread_storage_sync.cc` around lines 1503 - 1546, Guard the
tail-thread indexing in PointerAccessIsDisjoint and FindConflict before
accessing entries at threads.size() + idx - 3. Limit iteration to the available
tail entries using the minimum of both thread-list sizes and three, or establish
an equivalent precondition guaranteeing three rank-1 thread variables, so fewer
active dimensions cannot cause out-of-bounds access.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/transform/thread_storage_sync.cc`:
- Around line 1503-1546: Guard the tail-thread indexing in
PointerAccessIsDisjoint and FindConflict before accessing entries at
threads.size() + idx - 3. Limit iteration to the available tail entries using
the minimum of both thread-list sizes and three, or establish an equivalent
precondition guaranteeing three rank-1 thread variables, so fewer active
dimensions cannot cause out-of-bounds access.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 4a36ef08-557d-4968-a925-eab3a1dcbf28

📥 Commits

Reviewing files that changed from the base of the PR and between 9d4113c and 33176bc.

📒 Files selected for processing (2)
  • src/transform/common/constr_visitor.h
  • src/transform/thread_storage_sync.cc

@SiriusNEO
SiriusNEO merged commit 2bb0def into tile-ai:main Jul 30, 2026
9 of 10 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