TileLang Semantic Validation
Core principle
Reject a program only when the violated semantic invariant is established.
Keep these outcomes distinct:
| Finding |
Treatment |
| Semantically invalid for every supported lowering |
Default error |
| Valid in principle but unsupported by the current lowering |
Precise unsupported-case diagnostic or fallback |
| Valid but an optimization does not apply |
Warning or silent fallback, never a semantic error |
| Potentially invalid but not provable |
Warning, opt-in verifier, or no report |
Do not turn an optimizer limitation into a language rule. For example,
T.vectorized containing a loop-invariant T.serial is semantically valid even
though the current vectorization planner may scalarize it.
Workflow
1. Reconstruct the actual contract
Before proposing or changing a rule:
Locate the source API under tilelang/language/.
Determine its source TIR representation. Do not infer semantics from the
Python spelling alone: T.Pipelined is a serial For with annotations, and
T.Persistent expands early into binds plus a serial loop.
Trace the construct through the target pipelines and identify the first pass
that assumes the proposed invariant.
Search tests and examples for valid counterexamples.
Load the relevant domain reference completely:
- loop nesting and loop/storage ownership:
references/loop-rules.md;
- bounds, initialization, scopes, races, synchronization, barriers, or
async operations:
references/memory-concurrency.md;
- TileOps, software pipelines, reducers, layouts, kernel launch, or dtype
contracts:
references/operations-layout.md.
For loop-nesting work, also run:
python .agents/skills/tilelang-semantic/scripts/scan_loop_nesting.py
Absence from the repository is not proof that a construct is invalid. Confirm
the downstream assumption in code or with a minimal lowering test.
Treat the scanner as an inventory aid, not a proof: inspect non-literal
annotation dictionaries and generated TIR manually.
2. Specify the rule before implementing it
Write down all of the following:
- Scope: the exact node, storage scope, or lexical region covered.
- Invariant: what every valid program must satisfy.
- Proof criterion: what the checker must establish before reporting.
- Exemptions: atomics, reducers, generated IR, target-specific paths, etc.
- Near-neighbor valid case: the smallest similar program that must remain
accepted.
- Diagnostic: name the offending construct and give an actionable rewrite.
- Enforcement stage: frontend, pre-lower, early native verification,
post-inference, or backend-specific lowering.
For nesting rules, define a path as one lexical ancestor chain from the
function body to a leaf statement. Sequential sibling loops are different
paths.
3. Choose the earliest reliable enforcement stage
| Stage |
Use it for |
| Frontend API |
Invalid argument combinations or types known while constructing the loop/op |
PreLowerSemanticCheck |
Backend-independent source-TIR structure with user-facing syntax still recognizable |
Early native Verify* pass |
Analyzer-, effect-, region-, or dataflow-dependent checks shared by backends |
After LayoutInference |
Contracts involving inferred layout_map or parallel_loop_layout |
| Backend pipeline |
Rules that genuinely depend on target instructions or execution geometry |
Do not place a rule after a transform that erases the evidence needed to report
it. If an early expansion erases identity, preserve an explicit annotation
instead of pattern-matching incidental lowered shapes.
4. Implement a validator, not a transform
- Keep validation side-effect free.
- Maintain lexical state with a stack and restore it on every exit path.
- Use
For, Buffer, Var, and other ObjectRef handles for retained identity;
follow $tilelang-tvm-ir for C++ TIR code.
- Share traversal/variable-use utilities when rules need the same facts, but
keep diagnostics and rule ownership independent.
- Include source spans when the IR carries them.
- Use a stable prefix such as
[TileLang Semantic Check] and describe a legal
replacement.
- Honor the existing global pre-lower opt-out for compatibility. Do not add a
new per-rule opt-out for a proven-invalid rule unless compatibility requires
one; reserve fine-grained opt-outs for inconclusive analyses.
5. Test the semantic boundary
Add, at minimum:
- one failing test for each violation shape;
- one near-neighbor valid test;
- a proof-boundary case that remains accepted when the analysis is
inconclusive;
- alternate control-flow paths for state/lifecycle rules;
- target-specific accepted and unsupported cases when capability matters;
- a diagnostic assertion that checks the useful part of the message.
Also run affected backend codegen tests when a rule migrates existing kernels.
Use $tilelang-build for repository test commands. For a Python pre-lower
checker, start with:
python -m pytest testing/python/analysis -q
python -m ruff check <changed-python-files>
git diff --check
Validation ownership map
tilelang/engine/semantic_check.py: shared pre-lowering entry point.
tilelang/analysis/*checker.py: source-TIR structural rules and actionable
frontend diagnostics.
src/transform/verify_*.cc: native effect/dataflow verification such as
reducer lifecycle, buffer initialization, and parallel races.
src/transform/legalize_safe_memory_access.cc: bounds proof, global guards,
and optional local/shared warnings.
tilelang/language/*_op.py and tilelang/language/builtin.py: source API
operand and annotation validation.
src/transform/{pipeline_planning,inject_pipeline}.cc: pipeline dependency,
ordering, replayability, and multi-versioning contracts.
src/transform/layout_inference/parallel_loop_layout_validator.h:
post-inference parallel-layout annotation contract.
tilelang/{cuda,rocm,cpu,metal,webgpu}/pipeline.py: target pipeline order.
tilelang/transform/pass_config.py: opt-outs and strictness controls.
Read $tilelang-layout before changing rules whose truth depends on fragment
ownership, replication, loop partitioning, or inferred loop layouts.
Avoid
- Do not equate
T.Parallel with T.vectorized; they represent different
execution layers and use different lowering paths.
- Do not reject every construct that falls back to serial execution.
- Do not promote “cannot prove safe” to “proven unsafe.”
- Do not use Python
assert for new user-facing legality checks; assertions
disappear under optimized Python and usually produce poor diagnostics.
- Do not infer source intent from
ForKind alone when annotations define the
construct.
- Do not treat nested pipeline requests as backend-independent invalidity;
hierarchical-pipeline support is a backend capability.
- Do not require parallel extent to equal thread count or explicit layout
shape; partitioning, replication, and guarded tails intentionally permit
differences.
- Do not reject barriers merely because they occur inside
T.Parallel;
validate participant sets, path uniformity, and execution counts.
- Do not report a missing target feature or missed optimization as universal
semantic invalidity.
Resources
references/loop-rules.md: established loop contracts, representation
details, non-rules, and backend-specific nested-pipeline guidance.
references/memory-concurrency.md: buffer bounds/initialization/ownership,
aliasing, races, collective synchronization, barriers, and async lifetimes.
references/operations-layout.md: TileOp, pipeline, reducer, layout,
kernel/target, and dtype contracts plus the implementation roadmap.
scripts/scan_loop_nesting.py: inventory lexical loop pairs and detect
nested software-pipeline requests in Python sources without judging backend
support.
1---2name: tilelang-semantic3description: Design, implement, debug, or review TileLang semantic rules and validation, including loop nesting, buffer bounds/initialization/ownership, data races, synchronization and async lifecycles, TileOp shape/dtype/scope contracts, software pipelines, reducers, layouts, kernel launch constraints, and actionable diagnostics. Use when changing tilelang/analysis checkers, early Verify* passes, language legality rules, or regression tests for invalid TileLang programs.4---56# TileLang Semantic Validation78## Core principle910Reject a program only when the violated semantic invariant is established.11Keep these outcomes distinct:1213| Finding | Treatment |14|---|---|15| Semantically invalid for every supported lowering | Default error |16| Valid in principle but unsupported by the current lowering | Precise unsupported-case diagnostic or fallback |17| Valid but an optimization does not apply | Warning or silent fallback, never a semantic error |18| Potentially invalid but not provable | Warning, opt-in verifier, or no report |1920Do not turn an optimizer limitation into a language rule. For example,21`T.vectorized` containing a loop-invariant `T.serial` is semantically valid even22though the current vectorization planner may scalarize it.2324## Workflow2526### 1. Reconstruct the actual contract2728Before proposing or changing a rule:29301. Locate the source API under `tilelang/language/`.312. Determine its source TIR representation. Do not infer semantics from the32 Python spelling alone: `T.Pipelined` is a serial `For` with annotations, and33 `T.Persistent` expands early into binds plus a serial loop.343. Trace the construct through the target pipelines and identify the first pass35 that assumes the proposed invariant.364. Search tests and examples for valid counterexamples.375. Load the relevant domain reference completely:3839 - loop nesting and loop/storage ownership:40 `references/loop-rules.md`;41 - bounds, initialization, scopes, races, synchronization, barriers, or42 async operations: `references/memory-concurrency.md`;43 - TileOps, software pipelines, reducers, layouts, kernel launch, or dtype44 contracts: `references/operations-layout.md`.4546For loop-nesting work, also run:4748```bash49python .agents/skills/tilelang-semantic/scripts/scan_loop_nesting.py50```5152Absence from the repository is not proof that a construct is invalid. Confirm53the downstream assumption in code or with a minimal lowering test.54Treat the scanner as an inventory aid, not a proof: inspect non-literal55annotation dictionaries and generated TIR manually.5657### 2. Specify the rule before implementing it5859Write down all of the following:6061- **Scope:** the exact node, storage scope, or lexical region covered.62- **Invariant:** what every valid program must satisfy.63- **Proof criterion:** what the checker must establish before reporting.64- **Exemptions:** atomics, reducers, generated IR, target-specific paths, etc.65- **Near-neighbor valid case:** the smallest similar program that must remain66 accepted.67- **Diagnostic:** name the offending construct and give an actionable rewrite.68- **Enforcement stage:** frontend, pre-lower, early native verification,69 post-inference, or backend-specific lowering.7071For nesting rules, define a *path* as one lexical ancestor chain from the72function body to a leaf statement. Sequential sibling loops are different73paths.7475### 3. Choose the earliest reliable enforcement stage7677| Stage | Use it for |78|---|---|79| Frontend API | Invalid argument combinations or types known while constructing the loop/op |80| `PreLowerSemanticCheck` | Backend-independent source-TIR structure with user-facing syntax still recognizable |81| Early native `Verify*` pass | Analyzer-, effect-, region-, or dataflow-dependent checks shared by backends |82| After `LayoutInference` | Contracts involving inferred `layout_map` or `parallel_loop_layout` |83| Backend pipeline | Rules that genuinely depend on target instructions or execution geometry |8485Do not place a rule after a transform that erases the evidence needed to report86it. If an early expansion erases identity, preserve an explicit annotation87instead of pattern-matching incidental lowered shapes.8889### 4. Implement a validator, not a transform9091- Keep validation side-effect free.92- Maintain lexical state with a stack and restore it on every exit path.93- Use `For`, `Buffer`, `Var`, and other ObjectRef handles for retained identity;94 follow `$tilelang-tvm-ir` for C++ TIR code.95- Share traversal/variable-use utilities when rules need the same facts, but96 keep diagnostics and rule ownership independent.97- Include source spans when the IR carries them.98- Use a stable prefix such as `[TileLang Semantic Check]` and describe a legal99 replacement.100- Honor the existing global pre-lower opt-out for compatibility. Do not add a101 new per-rule opt-out for a proven-invalid rule unless compatibility requires102 one; reserve fine-grained opt-outs for inconclusive analyses.103104### 5. Test the semantic boundary105106Add, at minimum:1071081. one failing test for each violation shape;1092. one near-neighbor valid test;1103. a proof-boundary case that remains accepted when the analysis is111 inconclusive;1124. alternate control-flow paths for state/lifecycle rules;1135. target-specific accepted and unsupported cases when capability matters;1146. a diagnostic assertion that checks the useful part of the message.115116Also run affected backend codegen tests when a rule migrates existing kernels.117Use `$tilelang-build` for repository test commands. For a Python pre-lower118checker, start with:119120```bash121python -m pytest testing/python/analysis -q122python -m ruff check <changed-python-files>123git diff --check124```125126## Validation ownership map127128- `tilelang/engine/semantic_check.py`: shared pre-lowering entry point.129- `tilelang/analysis/*checker.py`: source-TIR structural rules and actionable130 frontend diagnostics.131- `src/transform/verify_*.cc`: native effect/dataflow verification such as132 reducer lifecycle, buffer initialization, and parallel races.133- `src/transform/legalize_safe_memory_access.cc`: bounds proof, global guards,134 and optional local/shared warnings.135- `tilelang/language/*_op.py` and `tilelang/language/builtin.py`: source API136 operand and annotation validation.137- `src/transform/{pipeline_planning,inject_pipeline}.cc`: pipeline dependency,138 ordering, replayability, and multi-versioning contracts.139- `src/transform/layout_inference/parallel_loop_layout_validator.h`:140 post-inference parallel-layout annotation contract.141- `tilelang/{cuda,rocm,cpu,metal,webgpu}/pipeline.py`: target pipeline order.142- `tilelang/transform/pass_config.py`: opt-outs and strictness controls.143144Read `$tilelang-layout` before changing rules whose truth depends on fragment145ownership, replication, loop partitioning, or inferred loop layouts.146147## Avoid148149- Do not equate `T.Parallel` with `T.vectorized`; they represent different150 execution layers and use different lowering paths.151- Do not reject every construct that falls back to serial execution.152- Do not promote “cannot prove safe” to “proven unsafe.”153- Do not use Python `assert` for new user-facing legality checks; assertions154 disappear under optimized Python and usually produce poor diagnostics.155- Do not infer source intent from `ForKind` alone when annotations define the156 construct.157- Do not treat nested pipeline requests as backend-independent invalidity;158 hierarchical-pipeline support is a backend capability.159- Do not require parallel extent to equal thread count or explicit layout160 shape; partitioning, replication, and guarded tails intentionally permit161 differences.162- Do not reject barriers merely because they occur inside `T.Parallel`;163 validate participant sets, path uniformity, and execution counts.164- Do not report a missing target feature or missed optimization as universal165 semantic invalidity.166167## Resources168169- `references/loop-rules.md`: established loop contracts, representation170 details, non-rules, and backend-specific nested-pipeline guidance.171- `references/memory-concurrency.md`: buffer bounds/initialization/ownership,172 aliasing, races, collective synchronization, barriers, and async lifetimes.173- `references/operations-layout.md`: TileOp, pipeline, reducer, layout,174 kernel/target, and dtype contracts plus the implementation roadmap.175- `scripts/scan_loop_nesting.py`: inventory lexical loop pairs and detect176 nested software-pipeline requests in Python sources without judging backend177 support.