#20340·tvm

[Bug] Fixed-point multiply legalization rejects runtime operands, zero-exponent scaling, and integer flags

Author: sepcntCreated Sep 15, 2026Updated Sep 15, 2026
Labelstype: bugneeds-triage

Expected behavior

The default legalization of tirx.q_multiply_shift and tirx.q_multiply_shift_per_axis should:

  • Accept runtime multipliers and shifts, using the general lowering when the constant-only optimization is inapplicable.
  • Return x for q_multiply_shift(x, 1 << 30, 31, 1).
  • Accept the documented integer 0/1 flags in the per-axis operation.

Actual behavior

Three related defects occur in src/target/intrin_rule.cc:

  1. Runtime operands: get_int_value asserts that its argument is an IntImm or Broadcast(IntImm). Runtime y, or runtime s with y == 1 << 30, triggers an internal error instead of falling back to QMultiplyShift.
  2. Zero exponent: With y == 1 << 30 and s == 1, the fast path constructs a rounding factor using 1 << -1, causing an internal shift-range error.
  3. Integer flags: The per-axis legalizer forwards is_lshift_required directly to Select, whose condition must be boolean. Passing integer 0 or 1 triggers an internal type check. The fast path also assumes q == 31 without checking it. These defects were reported independently by downstream TileLang fuzzing:

Environment

  • Apache TVM checkout: cc0f9f07c17c8118a781fdca55f7fe45f7de916a
  • Windows, Python 3.12.13
  • Built from source with clang-cl 22.1.8
  • CUDA and LLVM disabled
  • Pinned tvm-ffi built from source

Steps to reproduce

python
import tvm
from tvm import tirx
x = tirx.Var("x", "int32")
y = tirx.Var("y", "int32")
s = tirx.Var("s", "int32")
cases = [
    ("runtime multiplier", "tirx.q_multiply_shift", [x, y, 31, 1]),
    ("runtime shift", "tirx.q_multiply_shift", [x, 1 << 30, 31, s]),
    ("zero exponent", "tirx.q_multiply_shift", [x, 1 << 30, 31, 1]),
    (
        "integer flag",
        "tirx.q_multiply_shift_per_axis",
        [x, y, 0, 1, 31, 0, 1],
    ),
]
for name, op_name, args in cases:
    call = tirx.call_intrin("int32", op_name, *args)
    legalize = tvm.ir.Op.get(op_name).get_attr("default.FLegalize")
    try:
        print(name, legalize(call))
    except tvm.error.InternalError as err:
        print(name, err)

A localized fix can make constant extraction non-asserting, guard the fast path appropriately, handle the zero exponent explicitly, and normalize per-axis flags to boolean conditions.

Triage

  • needs-triage
  • type: bug