adf8d6a463
## Rationale
TIRx variables use inherited `ExprNode::ty` as their single semantic
type. Retaining a primitive handle surrogate erases the distinction
between scalar values, typed pointers, and true opaque pointers, then
forces later passes and code generators to reconstruct information that
the IR already owns.
## Changes
- Remove the duplicate reflected `Var::type_annotation` state and
preserve exact `PrimType` or `PointerType` through construction,
visitors, transforms, specialization, builders, printers, and code
generation.
- Keep scalar-only boundaries explicit through `PrimExpr`, `PrimVar`,
and `PrimType`; pointer-capable values remain general `Expr` or `Var`.
- Keep helper boundaries no broader than their contracts: TE tensor
variable indices use `PrimVar`, while expression deep equality recurses
through general `Expr` only where pointer-bearing `Call` arguments
require it and does not generalize private arithmetic subclasses.
- Keep core statement reflection typed as `Expr`, name general
reinterpret targets as `target_ty`, and preserve exact pointer calls in
the general vectorization path with explicit scalarization behavior.
- Delete `PrimType::Handle()` and `PrimType::IsHandle()`. True opaque
pointers use `PointerType::VoidPointerTy()`; TVMScript renders the
canonical global type as `T.handle`, standalone values as `T.handle()`,
and scoped void pointers with a keyword-only storage scope.
- Make `CodeGenSourceBase::SSAGetID` a single `Type` boundary across
source backends, without a separate primitive-type or runtime-dtype
variant.
- Keep WebGPU semantic argument classification type-aware: storage
buffers are identified from `PointerType`, POD arguments from
`PrimType`, and only the final `FunctionInfo` launch ABI is serialized
to `DLDataType`.
- Preserve exact pointer semantics at runtime boundaries, including
access pointers, packed calls and returns, external calls, storage
rewrites, and target-specific lowering.
## Migration guide
- **Variable types:** In C++, replace `var->type_annotation` with
`var->ty`; in Python, replace `var.type_annotation` with `var.ty`. The
result is the exact `Type`: scalar variables carry `PrimType`, while
pointer variables carry `PointerType`.
- **Scalar boundaries:** Use `PrimVar` and `PrimExpr` for variables and
expressions that are semantically scalar. When starting from a general
view, narrow explicitly with `var.as_or_throw<PrimVar>()` or
`expr.as_or_throw<PrimExpr>()`. Keep pointer-capable fields and call
arguments as `Var` or `Expr`. A default-constructed `PrimVar` is
nullable, so construct local scalar variables explicitly, for example
`PrimVar i("i")`.
- **Opaque pointers:** Replace `PrimType::Handle()` with
`PointerType::VoidPointerTy()`. Replace `IsHandle()` tests with explicit
`PointerType` inspection; use `PointerType(element_type, storage_scope)`
when the pointee type is known instead of erasing it to a runtime handle
dtype.
- **TVMScript handles:** Use `arg: T.handle` for a global void-pointer
annotation and `arg = T.handle()` for a standalone value. Use
`T.handle(storage_scope="shared")` for a scoped void pointer. Typed
pointers use forms such as `T.handle("float32")`, `T.handle("float32",
"global")`, or `T.handle("float32", "shared")`. Legacy
`T.handle("void")` input remains parse-compatible, but the printer
canonicalizes it to `T.handle` (or the keyword-only scoped form).
- The separate `tirx.type_annotation` intrinsic used by access-pointer
APIs is unchanged; this migration removes only the duplicate variable
field.
## Validation
- Complete native C++ test executable: 122/122 passed, including
`IRF.CountVar`.
- Relax binding-rewrite suite: 12/12 passed, including transferred-user
bookkeeping.
- Canonical typed/void/scoped TVMScript handle printer and round-trip
checks: 5/5 passed.
171 lines
6.7 KiB
C++
171 lines
6.7 KiB
C++
/*
|
|
* Licensed to the Apache Software Foundation (ASF) under one
|
|
* or more contributor license agreements. See the NOTICE file
|
|
* distributed with this work for additional information
|
|
* regarding copyright ownership. The ASF licenses this file
|
|
* to you under the Apache License, Version 2.0 (the
|
|
* "License"); you may not use this file except in compliance
|
|
* with the License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing,
|
|
* software distributed under the License is distributed on an
|
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
* KIND, either express or implied. See the License for the
|
|
* specific language governing permissions and limitations
|
|
* under the License.
|
|
*/
|
|
|
|
#include "../src/arith/pattern_match.h"
|
|
|
|
#include <gtest/gtest.h>
|
|
#include <tvm/tirx/analysis.h>
|
|
|
|
TEST(Pattern, Basic) {
|
|
using namespace tvm;
|
|
using namespace tvm::tirx;
|
|
using namespace tvm::arith;
|
|
tvm::tirx::PrimVar x("x"), y("y"), z("z");
|
|
PrimExpr scalable_lanes = Mul(Call(PrimType::Int(32), builtin::vscale(), {}), 4);
|
|
arith::PVar<PrimExpr> px, py, pz;
|
|
arith::PVar<DLDataType> pt;
|
|
arith::PVar<PrimExpr> planes;
|
|
arith::PCallExpr<PVscaleOp> vscale;
|
|
|
|
// arithmetics
|
|
auto r = 1 + (y + 1);
|
|
TVM_FFI_ICHECK(!(px + (px + px)).Match(r));
|
|
TVM_FFI_ICHECK(!(px + (py + py)).Match(r));
|
|
TVM_FFI_ICHECK((px + (py + pz)).Match(r));
|
|
auto pattern = px + (py + pz);
|
|
TVM_FFI_ICHECK(pattern.Match(r));
|
|
{
|
|
TVM_FFI_ICHECK((px + (py + px)).Match(r));
|
|
auto rr = (px + py).Eval();
|
|
|
|
TVM_FFI_ICHECK(tirx::ExprDeepEqual()(rr, 1 + y));
|
|
TVM_FFI_ICHECK(tirx::ExprDeepEqual()(px.Eval() + py.Eval(), 1 + y));
|
|
}
|
|
{
|
|
TVM_FFI_ICHECK((px + max(py, px)).Match((x + 1) + max(y, (x + 1))));
|
|
TVM_FFI_ICHECK(tirx::ExprDeepEqual()(px.Eval(), x + 1));
|
|
}
|
|
TVM_FFI_ICHECK(!(px + min(py, px)).Match((x + 1) + max(y, (x + 1))));
|
|
|
|
TVM_FFI_ICHECK((px + min(py, px)).Match(z + min(y, z)));
|
|
TVM_FFI_ICHECK((px + truncdiv(py, px * py)).Match(x + truncdiv(2, x * 2)));
|
|
TVM_FFI_ICHECK((px - truncmod(py, px * pz)).Match(x - truncmod(2, x * 2)));
|
|
TVM_FFI_ICHECK((px - floormod(py, px * PConst<PrimExpr>(2))).Match(x - floormod(2, x * 2)));
|
|
|
|
// logicals
|
|
TVM_FFI_ICHECK((px == pz).Match(x == 1));
|
|
TVM_FFI_ICHECK((px != pz).Match(x != 1));
|
|
TVM_FFI_ICHECK((px > py).Match(x > y));
|
|
TVM_FFI_ICHECK((px < py).Match(x < y));
|
|
TVM_FFI_ICHECK((px <= py).Match(x <= y));
|
|
TVM_FFI_ICHECK((px >= py).Match(x >= y));
|
|
TVM_FFI_ICHECK((px >= py && px < pz).Match(x >= y && x < z));
|
|
TVM_FFI_ICHECK((!(px > py || px != py)).Match(!(x > y || x != y)));
|
|
{
|
|
TVM_FFI_ICHECK(select(px >= pz, py, py + pz).Match(tirx::Select((x + 1) >= 1, y, y + 1)));
|
|
TVM_FFI_ICHECK(tirx::ExprDeepEqual()(px.Eval(), x + 1));
|
|
}
|
|
// bit intrinsics
|
|
{
|
|
TVM_FFI_ICHECK((px >> pz).Match(x >> 1));
|
|
TVM_FFI_ICHECK(is_const_int(pz.Eval(), 1));
|
|
}
|
|
TVM_FFI_ICHECK(!(px >> pz).Match(x << 1));
|
|
TVM_FFI_ICHECK((px << pz).Match(x << 1));
|
|
TVM_FFI_ICHECK((px & pz).Match(x & 1));
|
|
TVM_FFI_ICHECK((px | pz).Match(x | 1));
|
|
TVM_FFI_ICHECK((px ^ pz).Match(x ^ 1));
|
|
TVM_FFI_ICHECK((px - (~(py | (px * pz)))).Match(x - (~(2 | (x * 2)))));
|
|
// select
|
|
{
|
|
TVM_FFI_ICHECK(select(px > pz, py, py + pz).Match(tirx::Select(x > 1, y, y + 1)));
|
|
TVM_FFI_ICHECK(is_const_int(pz.Eval(), 1));
|
|
}
|
|
TVM_FFI_ICHECK(!select(px > pz, py, py + pz).Match(tirx::Select(x > 2, y, y + 1)));
|
|
TVM_FFI_ICHECK(!select(px > pz, py, py).Match(tirx::Select(x > 2, y, y + 1)));
|
|
{
|
|
TVM_FFI_ICHECK(select(px, py, pz).Match(tirx::Select(x > 2, y, y + 1)));
|
|
TVM_FFI_ICHECK(tirx::ExprDeepEqual()(pz.Eval(), y + 1));
|
|
}
|
|
// if_then_else
|
|
{
|
|
TVM_FFI_ICHECK(if_then_else(px > pz, py, py + pz).Match(if_then_else(x > 1, y, y + 1)));
|
|
TVM_FFI_ICHECK(is_const_int(pz.Eval(), 1));
|
|
}
|
|
// cast pattern
|
|
{
|
|
TVM_FFI_ICHECK(!cast(PConst<DLDataType>(DLDataType{kDLInt, 32, 1}), px)
|
|
.Match(tirx::Cast(PrimType::Float(64), x)));
|
|
TVM_FFI_ICHECK(cast(pt, px).Match(tirx::Cast(PrimType::Float(64), x)));
|
|
TVM_FFI_ICHECK((pt.Eval() == DLDataType{kDLFloat, 64, 1}));
|
|
auto zz = cast(pt, px).Eval();
|
|
TVM_FFI_ICHECK(
|
|
(cast(pt, px) - cast(pt, py))
|
|
.Match(tirx::Cast(PrimType::Float(64), x) - tirx::Cast(PrimType::Int(64), x)));
|
|
auto expr = tirx::Cast(PrimType::Int(32), tirx::Cast(PrimType::Float(64), x));
|
|
TVM_FFI_ICHECK(!(cast(pt, cast(pt, px))).Match(expr));
|
|
}
|
|
// ramp pattern
|
|
{
|
|
TVM_FFI_ICHECK(ramp(px, PConst<PrimExpr>(1), planes).Match(tirx::Ramp(x, 1, 10)));
|
|
TVM_FFI_ICHECK(planes.Eval().as<IntImmNode>()->value == 10);
|
|
TVM_FFI_ICHECK(ramp(px, PConst<PrimExpr>(1), planes).Match(tirx::Ramp(x, 1, scalable_lanes)));
|
|
TVM_FFI_ICHECK((vscale * PConst<PrimExpr>(4)).Match(planes.Eval()));
|
|
TVM_FFI_ICHECK(!ramp(px, PConst<PrimExpr>(1), planes).Match(tirx::Ramp(x, 2, 10)));
|
|
}
|
|
// broadcast pattern
|
|
{
|
|
TVM_FFI_ICHECK(broadcast(px, planes).Match(tirx::Broadcast(x, 10)));
|
|
TVM_FFI_ICHECK(planes.Eval().as<IntImmNode>()->value == 10);
|
|
TVM_FFI_ICHECK(broadcast(px * py, planes).Match(tirx::Broadcast(x * 10, 10)));
|
|
TVM_FFI_ICHECK(broadcast(px, planes).Match(tirx::Broadcast(x, scalable_lanes)));
|
|
TVM_FFI_ICHECK((vscale * PConst<PrimExpr>(4)).Match(planes.Eval()));
|
|
}
|
|
}
|
|
|
|
TEST(Pattern, IntImm) {
|
|
using namespace tvm;
|
|
tirx::PrimVar tx("tx"), ty("ty");
|
|
arith::PVar<IntImm> c;
|
|
arith::PVar<tirx::Var> v;
|
|
{
|
|
// We can match integer and Var, both of which are
|
|
// special case container of Expr
|
|
TVM_FFI_ICHECK((v * c).Match(tx * 3));
|
|
TVM_FFI_ICHECK_EQ(c.Eval()->value, 3);
|
|
TVM_FFI_ICHECK((v * 3).Match(tx * 3));
|
|
}
|
|
// cannot match c to ty
|
|
TVM_FFI_ICHECK(!(v * c).Match(tx * ty));
|
|
// cannot match tx + 1 to v
|
|
TVM_FFI_ICHECK(!(v * c).Match((tx + 1) * 3));
|
|
}
|
|
|
|
TEST(Pattern, MatchWithType) {
|
|
using namespace tvm;
|
|
// match expr with specified dtype
|
|
arith::PVarWithDataType<PrimExpr, arith::PConst<DLDataType>> pat(DLDataType{kDLFloat, 32, 1});
|
|
tirx::PrimVar x("x", PrimType::Float(32));
|
|
tirx::PrimVar y("y", PrimType::Float(32));
|
|
tirx::PrimVar x_int("x", PrimType::Int(32));
|
|
tirx::PrimVar y_int("y", PrimType::Int(32));
|
|
TVM_FFI_ICHECK(pat.Match(x + y * 2.0f));
|
|
TVM_FFI_ICHECK(!pat.Match(x_int + y_int * 2));
|
|
|
|
// match vectorized expr with specified element dtype
|
|
arith::PVecDataType vec_ty(DLDataType{kDLFloat, 32, 1});
|
|
arith::PVarWithDataType<PrimExpr, arith::PVecDataType> vpat(vec_ty);
|
|
tirx::PrimVar vx("x", PrimType::Float(32, 8));
|
|
tirx::PrimVar vy("y", PrimType::Float(32, 8));
|
|
tirx::PrimVar vx_int("x", PrimType::Int(32, 8));
|
|
tirx::PrimVar vy_int("y", PrimType::Int(32, 8));
|
|
TVM_FFI_ICHECK(vpat.Match(vx + vy * tirx::Broadcast(2.0f, 8)));
|
|
TVM_FFI_ICHECK(!vpat.Match(vx_int + vy_int * tirx::Broadcast(2, 8)));
|
|
}
|