#3231·tilelang

[BUG] Reusing an alloc_var name as a loop target causes an out-of-scope error

Author: sepcntCreated Sep 15, 2026Updated Sep 15, 2026
Labelsbug

Required prerequisites

What version of TileLang are you using?

0.1.14 and 0.1.14+cuda.gitd178b4c9

System information

Reproduced in:

  • Linux, Python 3.13, TileLang 0.1.14, PyTorch 2.7.1+cu128, RTX 5090.
  • Windows, Python 3.13, TileLang source build at d178b4c9, PyTorch 2.11.0+cu130, RTX 5060 Laptop.

The failure occurs during frontend IR construction, before CUDA compilation.

Problem description

Reusing a previous T.alloc_var name as the induction variable of a subsequent loop raises an out-of-scope error.

The second loop introduces a new induction variable, but the frontend treats its binding as an assignment to the previous mutable scalar. Renaming only the second loop target avoids the error.

Reproducible example code

python
import tilelang.language as T
@T.prim_func
def kernel(A: T.Tensor((2,), "int32")):
    with T.Kernel(1, threads=1):
        for j in T.serial(2):
            d = T.alloc_var("int32")
            d = j
            A[j] = d
        for d in T.Parallel(2):
            A[d] = d
Control case: replace the second loop with:
for k in T.Parallel(2):
    A[k] = k

Traceback

pytb
Relevant excerpt:
File ".../repro_loop_rebind.py", line 17, in kernel
    A[d] = d
File ".../tilelang/language/eager/builder.py", in rval
    raise RuntimeError(...)
RuntimeError: Immutable variable `d` is used outside its defining region!
The error dump shows the second loop incorrectly storing its induction variable into the old buffer:
d[0] = v

Expected behavior

The second loop should bind d to its own induction variable, without writing to the previous alloc_var. The kernel should produce [0, 1]. Actual reads of expired bindings should remain rejected.

Additional context

In Builder.bind(), an existing local.var binding takes the mutable-assignment path when the incoming value is a PrimExpr. Loop targets currently use this same path. Distinguishing loop-target binding from ordinary assignment avoids the unintended buffer store while preserving mutable-scalar updates and scope validation.