Skip to content

[Bug][Relax] FoldConstant leaks bindings emitted by a legalizer it only tries speculatively: dynamic_strided_slice leaves a call_tir to an undefined shape_func ("There is no definition of Module.shape_func") #20808

Open

Description

@YuanchengJiang

The following code:

import tvm
from tvm import relax
from tvm.script import ir as I
from tvm.script import relax as R


@I.ir_module
class Module:
    @R.function
    def main(
        x: R.Tensor((8, 8), "float32"),
        begin: R.Tensor((2,), "int64"),
        end: R.Tensor((2,), "int64"),
        strides: R.Tensor((2,), "int64"),
    ):
        with R.dataflow():
            gv = R.dynamic_strided_slice(x, begin, end, strides)
            R.output(gv)
        return gv


tvm.compile(Module, target="llvm")  # OK

mod = relax.transform.FoldConstant()(Module)
mod.show()  # leftover call_tir(Module.shape_func, ...) + match_cast bindings; no shape_func in the module
print([gv.name_hint for gv in mod.functions.keys()])  # ['main']
# relax.analysis.well_formed(mod) -> ValueError: GlobalVar Module.shape_func is not defined.
tvm.compile(mod, target="llvm")
# tvm.error.InternalError: Check failed: (it != functions.end()) is false: There is no definition of Module.shape_func

After FoldConstant:

with R.dataflow():
    lv = R.call_tir(Module.shape_func, (x, begin, end, strides), ty_args=[R.Tensor((2,), dtype="int64")])
    lv1: R.Shape(ndim=2) = R.tensor_to_shape(lv)
    lv2: R.Shape([s, s_1]) = R.match_cast(lv1, R.Shape([s, s_1]))
    gv: R.Tensor(dtype="float32", ndim=2) = R.dynamic_strided_slice(x, begin, end, strides)
    R.output(gv)

Resulted in this output:

  File "src/relax/ir/transform.cc", line 140, in tvm::relax::transform::FunctionPassObj::operator()(tvm::IRModule, tvm::transform::PassContext const&) const
  File "src/relax/transform/rewrite_dataflow_reshape.cc", line 166, in operator()
  File "src/relax/transform/rewrite_dataflow_reshape.cc", line 159, in tvm::relax::RewriteDataflowReshape(tvm::relax::Function const&, tvm::IRModule const&)
  File "src/relax/ir/expr_functor.cc", line 786, in tvm::relax::ExprMutator::VisitExpr_(tvm::relax::FunctionNode const*)
  File "src/relax/ir/expr_functor.cc", line 828, in tvm::relax::ExprMutator::VisitExpr_(tvm::relax::SeqExprNode const*)
  File "src/relax/transform/rewrite_dataflow_reshape.cc", line 67, in tvm::relax::DataflowReshapeRewriter::VisitBindingBlock(tvm::relax::BindingBlock const&)
  File "src/relax/ir/expr_functor.cc", line 936, in tvm::relax::ExprMutator::VisitBindingBlock_(tvm::relax::DataflowBlockNode const*)
  File "src/relax/ir/expr_functor.cc", line 851, in tvm::relax::ExprMutator::VisitBinding_(tvm::relax::VarBindingNode const*, tvm::GenericConstNode const*)
  File "src/relax/transform/rewrite_dataflow_reshape.cc", line 94, in tvm::relax::DataflowReshapeRewriter::VisitExpr_(tvm::CallNode const*)
  File "src/ir/module.cc", line 206, in tvm::BaseFunc tvm::IRModuleNode::Lookup(const tvm::GlobalVar&) const
tvm.error.InternalError: Check failed: (it != functions.end()) is false: There is no definition of Module.shape_func

There are two possible fixes. One is to run the speculative legalization in a scratch scope (a fresh BlockBuilder or a discarded binding block) and keep its bindings and PrimFuncs only when the call actually folds. The other is to skip the legalize attempt unless every tensor argument is a constant. Any legalizer that emits bindings (not only dynamic_strided_slice) can leak the same way. Related open FoldConstant/legalizer issues: #20192, #20193 (different symptom: reshape legalizer assertion on a runtime shape).

No GPU is needed (the original failure was on nvptx, but llvm fails the same way). Reproduces 3/3.

To reproduce:

python3 min.py

Commit:

apache/tvm 46a45c5f574360e69e312236cafda7e7eb48b440 (2026-10-11)

Build configuration:

config.cmake + USE_LLVM "llvm-config-18 --link-static", USE_LIBBACKTRACE ON, USE_SORT ON, USE_RANDOM ON,
USE_VULKAN ON, USE_KHRONOS_SPIRV /usr; cmake -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo (gcc 11, lld)

Operating System:

Ubuntu 22.04.5 LTS (Docker image fusion-fuzz-tvm:latest), no GPU

Activity

  1. added
    needs-triagePRs or issues that need to be investigated by maintainers to find the right assignees to address it
    on Oct 22, 2026
  2. xgbtxy commented on Oct 22, 2026

    @xgbtxy

    I reproduced this with your module on Windows x64, Python 3.12 and the prebuilt apache-tvm==0.27.0.post1 wheel (generated version metadata identifies commit 7b5acb32b; LLVM is enabled).

    The original module passes well_formed and compiles with tvm.compile(..., target="llvm"). After FoldConstant, only main remains, and both well_formed and compilation fail because shape_func is undefined. This is validation of that wheel build; I have not built current main or executed the compiled VM.

    I also noticed #20488 already changes the speculative FLegalize probe and explicitly discusses dynamic_strided_slice in its data-dependent handling. Its gate may leave this binding-isolation issue separate, but I have not tested that PR. Would maintainers prefer coordinating this regression there, or a separate binding-isolation follow-up after that change? I have not started an overlapping implementation.

    This reproduction and source comparison were assisted by Codex.

  3. Yuhx141 commented on Oct 25, 2026

    @Yuhx141

    Independent CPU confirmation on TVM 0.28.dev0, revision 27576ce2d1b2cf5212ad46d5710e594bdcbb5df0, with matching FFI df463f9bd269867fc7f4d578d62d80cc2a11d004 (Ubuntu 24.04.4 x86_64, Python 3.12.3, LLVM 18.1.3).

    A one-dimensional positive-size variant below passes source well-formedness and actually executes on LLVM CPU, returning [1, 2]. After FoldConstant, check_well_formed is false and well_formed raises ValueError: GlobalVar Module.shape_func is not defined. The same mechanism reproduced on three fresh source modules.

    """Standalone LLVM CPU reproducer; no ONNX or GraphOpt dependency."""
    import numpy as np
    import tvm
    from tvm import relax
    from tvm.script import ir as I
    from tvm.script import relax as R
    @I.ir_module
    class Module:
        @R.function
        def main(x: R.Tensor((4,), "float32"), end: R.Tensor((1,), "int64")) -> R.Tensor(dtype="float32", ndim=1):
            with R.dataflow():
                out = R.dynamic_strided_slice(x, R.const([0], "int64"), end, R.const([1], "int64"))
                R.output(out)
            return out
    
    
    assert relax.analysis.check_well_formed(Module, check_ty=True)
    before = Module.script(show_meta=True)
    executable = relax.build(Module, target=tvm.target.Target("llvm", host="llvm"), relax_pipeline="default")
    vm = relax.VirtualMachine(executable, tvm.cpu())
    inputs = [np.array([1, 2, 3, 4], dtype="float32"), np.array([2], dtype="int64")]
    expected = np.array([1, 2], dtype="float32")
    actual = vm["main"](*[tvm.runtime.tensor(x) for x in inputs]).numpy()
    np.testing.assert_array_equal(actual, expected)
    assert Module.script(show_meta=True) == before
    print("TVM:", tvm.__version__, flush=True)
    print("Source LLVM output:", actual.tolist(), flush=True)
    optimized = relax.transform.FoldConstant()(Module)
    print("Target well formed:", bool(relax.analysis.check_well_formed(optimized, check_ty=True)), flush=True)
    relax.analysis.well_formed(optimized, check_ty=True)

    This confirms the same binding/helper leakage reported here; I am not opening a duplicate issue or proposing an overlapping fix. I have not built current main or tested #20488. The source VM execution above also rules out an ONNX importer or snapshot-instrumentation dependency for this variant.

    Report preparation used Codex; this exact script was executed in the stated environment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs-triagePRs or issues that need to be investigated by maintainers to find the right assignees to address ittype: bug

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions