This is an automated email from the ASF dual-hosted git repository.
tqchen pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tvm.git
The following commit(s) were added to refs/heads/main by this push:
new 81bb3dcfe6 [REFACTOR][TIR] Split masked buffer access into special
calls (#20244)
81bb3dcfe6 is described below
commit 81bb3dcfe6bcacee62a9639558fea5bd6eb4cc90
Author: Tianqi Chen <[email protected]>
AuthorDate: Mon Aug 31 10:06:24 2026 -0400
[REFACTOR][TIR] Split masked buffer access into special calls (#20244)
Move lane-mask semantics out of ordinary BufferLoad and BufferStore
nodes into dedicated tirx.masked_load and tirx.masked_store calls.
Construct the intrinsic Calls directly, update the semantic consumers
that require masked-memory knowledge, use BufferType-based reasoning
elsewhere, and keep LLVM lowering in dedicated masked-load/store helpers
while rejecting unsupported source backends.
Validation:
- LLVM-enabled build
- 756 focused and broad masked/script/LLVM/legalization tests passed, 10
skipped
- Clang-format, Ruff, and git diff --check passed
---
include/tvm/tirx/buffer.h | 10 +-
include/tvm/tirx/builtin.h | 16 +++
include/tvm/tirx/expr.h | 8 +-
include/tvm/tirx/script/builder/ir.h | 5 +-
include/tvm/tirx/stmt.h | 6 +-
python/tvm/tirx/buffer.py | 17 +--
python/tvm/tirx/expr.py | 5 -
python/tvm/tirx/expr_functor.py | 2 +-
python/tvm/tirx/op.py | 46 +++++++
python/tvm/tirx/script/builder/ir.py | 11 +-
python/tvm/tirx/script/parser/parser.py | 2 +-
python/tvm/tirx/stmt.py | 8 --
python/tvm/tirx/stmt_functor.py | 8 +-
src/backend/vulkan/codegen/codegen_spirv.cc | 6 +-
src/backend/webgpu/codegen/codegen_webgpu.cc | 6 +-
src/relax/ir/expr_functor.cc | 13 +-
.../analysis/sblock_access_region_detector.cc | 28 +++++
src/s_tir/transform/inject_virtual_thread.cc | 37 +++++-
src/s_tir/transform/lower_match_buffer.cc | 14 ++-
.../manifest_shared_memory_local_stage.cc | 3 -
src/s_tir/transform/storage_access.cc | 24 +++-
src/target/llvm/codegen_llvm.cc | 69 ++++++++++-
src/target/llvm/codegen_llvm.h | 2 +
src/target/source/codegen_c.cc | 6 +-
src/te/operation/create_primfunc.cc | 6 +-
src/tirx/analysis/deep_equal.cc | 3 +-
src/tirx/analysis/verify_memory.cc | 8 ++
src/tirx/ir/buffer.cc | 15 +--
src/tirx/ir/expr.cc | 33 +----
src/tirx/ir/expr_functor.cc | 2 +-
src/tirx/ir/stmt.cc | 28 +----
src/tirx/op/builtin.cc | 9 ++
src/tirx/script/builder/ir.cc | 5 +-
src/tirx/script/printer/buffer.cc | 21 +---
src/tirx/script/printer/utils.h | 9 +-
src/tirx/transform/flatten_buffer.cc | 15 +++
src/tirx/transform/remove_no_op.cc | 3 +-
src/tirx/transform/storage_rewrite.cc | 122 +++++++++++++-----
src/tirx/transform/unsupported_dtype_legalize.cc | 97 +++++++++++----
src/tirx/transform/vectorize_loop.cc | 68 ++++++++---
tests/python/codegen/test_target_codegen.py | 38 +++++-
tests/python/codegen/test_target_codegen_llvm.py | 29 ++++-
.../s_tir/analysis/test_sblock_access_region.py | 22 ++++
.../test_s_tir_transform_inject_virtual_thread.py | 35 ++++++
.../test_s_tir_transform_lower_match_buffer.py | 16 +++
tests/python/tirx-base/test_tir_nodes.py | 71 +----------
.../test_tir_transform_bf16_legalize.py | 80 ++++++++++++
.../tirx-transform/test_tir_transform_vectorize.py | 136 ++++++++++++++++-----
tests/python/tirx/transform/test_stmt_functor.py | 2 +-
.../tvmscript/test_tvmscript_ir_builder_tir.py | 14 ---
.../python/tvmscript/test_tvmscript_printer_tir.py | 81 +++++++++---
tests/python/tvmscript/test_tvmscript_roundtrip.py | 19 ++-
52 files changed, 930 insertions(+), 409 deletions(-)
diff --git a/include/tvm/tirx/buffer.h b/include/tvm/tirx/buffer.h
index 24cc3b9f1a..c54102276c 100644
--- a/include/tvm/tirx/buffer.h
+++ b/include/tvm/tirx/buffer.h
@@ -232,20 +232,14 @@ class BufferVar : public Var {
* \brief Create an Expr that does a vector load at begin index.
* \param begin The beginning index
* \param dtype The data type to be loaded.
- * \param predicate A vector mask of boolean values indicating which lanes
of a vector are to be
- * loaded. The number lanes of the mask must be equal to the number of lanes
in being loaded.
*/
- TVM_DLL PrimExpr vload(ffi::Array<PrimExpr> begin, PrimType dtype,
- ffi::Optional<PrimExpr> predicate = std::nullopt)
const;
+ TVM_DLL PrimExpr vload(ffi::Array<PrimExpr> begin, PrimType dtype) const;
/*!
* \brief Create a Stmt that does a vector store at begin index.
* \param begin The beginning index
* \param value The value to be stored.
- * \param predicate A vector mask of boolean values indicating which lanes
of a vector are to be
- * stored. The number lanes of the mask must be equal to the number of lanes
in value.
*/
- TVM_DLL Stmt vstore(ffi::Array<PrimExpr> begin, PrimExpr value,
- ffi::Optional<PrimExpr> predicate = std::nullopt) const;
+ TVM_DLL Stmt vstore(ffi::Array<PrimExpr> begin, PrimExpr value) const;
/*!
* \brief Get a flattened version of the buffer.
diff --git a/include/tvm/tirx/builtin.h b/include/tvm/tirx/builtin.h
index 3026894016..f209b381f7 100644
--- a/include/tvm/tirx/builtin.h
+++ b/include/tvm/tirx/builtin.h
@@ -771,6 +771,22 @@ TVM_DLL const Op& vscale();
*/
TVM_DLL const Op& get_active_lane_mask();
+/*!
+ * \brief Masked buffer load.
+ *
+ * Arguments are the buffer variable, one or more indices, and a trailing
boolean lane mask.
+ * The result type is the vector type loaded from the selected lanes.
+ */
+TVM_DLL const Op& masked_load();
+
+/*!
+ * \brief Masked buffer store.
+ *
+ * Arguments are the buffer variable, value, one or more indices, and a
trailing boolean lane
+ * mask. The result type is void.
+ */
+TVM_DLL const Op& masked_store();
+
/*! \brief Annotate a predicate not be considered as target condition of loop
partition. */
TVM_DLL const Op& ignore_loop_partition();
/*!
diff --git a/include/tvm/tirx/expr.h b/include/tvm/tirx/expr.h
index 4a4d4b7ce8..6696533ccf 100644
--- a/include/tvm/tirx/expr.h
+++ b/include/tvm/tirx/expr.h
@@ -548,14 +548,11 @@ class BufferLoadNode : public ExprNode {
BufferVar buffer;
/*! \brief The indices location to be loaded. */
ffi::Array<PrimExpr> indices;
- /*! \brief The predicate mask for loading values. */
- ffi::Optional<PrimExpr> predicate;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<BufferLoadNode>()
.def_ro("buffer", &BufferLoadNode::buffer,
refl::AttachFieldFlag::SEqHashDefRecursive())
- .def_ro("indices", &BufferLoadNode::indices)
- .def_ro("predicate", &BufferLoadNode::predicate);
+ .def_ro("indices", &BufferLoadNode::indices);
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("tirx.BufferLoad", BufferLoadNode,
ExprNode);
@@ -582,8 +579,7 @@ class BufferLoadNode : public ExprNode {
*/
class BufferLoad : public PrimExpr {
public:
- TVM_DLL explicit BufferLoad(BufferVar buffer, ffi::Array<PrimExpr> indices,
- ffi::Optional<PrimExpr> predicate =
std::nullopt, Span span = Span());
+ TVM_DLL explicit BufferLoad(BufferVar buffer, ffi::Array<PrimExpr> indices,
Span span = Span());
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(BufferLoad, PrimExpr,
BufferLoadNode);
static constexpr bool _type_container_is_exact = true;
TVM_DEFINE_OBJECT_REF_COW_METHOD(BufferLoadNode);
diff --git a/include/tvm/tirx/script/builder/ir.h
b/include/tvm/tirx/script/builder/ir.h
index fcb6fb4b10..affdbfa690 100644
--- a/include/tvm/tirx/script/builder/ir.h
+++ b/include/tvm/tirx/script/builder/ir.h
@@ -498,11 +498,8 @@ Var EnvThread(ffi::String thread_tag, PrimType dtype =
PrimType::Int(32));
* \param buffer The buffer.
* \param value The value to be stored.
* \param indices The indices location to be stored.
- * \param predicate A vector mask of boolean values indicating which lanes of
a vector are to be
- * stored. The number lanes of the mask must be equal to the number of lanes
in value.
*/
-void BufferStore(BufferVar buffer, PrimExpr value, ffi::Array<PrimExpr>
indices,
- ffi::Optional<PrimExpr> predicate);
+void BufferStore(BufferVar buffer, PrimExpr value, ffi::Array<PrimExpr>
indices);
/*!
* \brief Evaluate the input expression.
diff --git a/include/tvm/tirx/stmt.h b/include/tvm/tirx/stmt.h
index d95c1af501..85ae71d9fe 100644
--- a/include/tvm/tirx/stmt.h
+++ b/include/tvm/tirx/stmt.h
@@ -207,16 +207,13 @@ class BufferStoreNode : public StmtNode {
PrimExpr value;
/*! \brief The indices location to be stored. */
ffi::Array<PrimExpr> indices;
- /*! \brief The predicate mask for storing values. */
- ffi::Optional<PrimExpr> predicate;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<BufferStoreNode>()
.def_ro("buffer", &BufferStoreNode::buffer,
refl::AttachFieldFlag::SEqHashDefRecursive())
.def_ro("value", &BufferStoreNode::value)
- .def_ro("indices", &BufferStoreNode::indices)
- .def_ro("predicate", &BufferStoreNode::predicate);
+ .def_ro("indices", &BufferStoreNode::indices);
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("tirx.BufferStore", BufferStoreNode,
StmtNode);
};
@@ -228,7 +225,6 @@ class BufferStoreNode : public StmtNode {
class BufferStore : public Stmt {
public:
TVM_DLL explicit BufferStore(BufferVar buffer, PrimExpr value,
ffi::Array<PrimExpr> indices,
- ffi::Optional<PrimExpr> predicate =
std::nullopt,
Span span = Span());
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(BufferStore, Stmt,
BufferStoreNode);
diff --git a/python/tvm/tirx/buffer.py b/python/tvm/tirx/buffer.py
index 376707a97f..d832aa7602 100644
--- a/python/tvm/tirx/buffer.py
+++ b/python/tvm/tirx/buffer.py
@@ -148,7 +148,7 @@ class _BufferMethods:
extent, # type: ignore
)
- def vload(self, begin, dtype=None, predicate=None):
+ def vload(self, begin, dtype=None):
"""Generate an Expr that loads dtype from begin index.
Parameters
@@ -160,10 +160,6 @@ class _BufferMethods:
The data type to be loaded,
can be vector type which have lanes that is multiple of
Buffer.dtype
- predicate : Optional[Expr]
- A vector mask of boolean values indicating which lanes of a vector
are to be
- loaded. The number lanes of the mask must be equal to the number
of lanes being loaded.
-
Returns
-------
load : Expr
@@ -171,9 +167,9 @@ class _BufferMethods:
"""
begin = (begin,) if isinstance(begin, int) or
tvm.ir.is_prim_expr(begin) else begin
dtype = dtype if dtype else self.ty.dtype
- return _ffi_api.BufferVLoad(self, begin, dtype, predicate) # type:
ignore
+ return _ffi_api.BufferVLoad(self, begin, dtype) # type: ignore
- def vstore(self, begin, value, predicate=None):
+ def vstore(self, begin, value):
"""Generate a Stmt that store value into begin index.
Parameters
@@ -184,18 +180,13 @@ class _BufferMethods:
value : Expr
The value to be stored.
- predicate : Optional[Expr]
- A vector mask of boolean values indicating which lanes of a vector
are to be
- stored. The number lanes of the mask must be equal to the number
of lanes in
- value.
-
Returns
-------
store : Stmt
The corresponding store stmt.
"""
begin = (begin,) if isinstance(begin, int) or
tvm.ir.is_prim_expr(begin) else begin
- return _ffi_api.BufferVStore(self, begin, value, predicate) # type:
ignore
+ return _ffi_api.BufferVStore(self, begin, value) # type: ignore
def scope(self):
"""Return the storage scope associated with this buffer.
diff --git a/python/tvm/tirx/expr.py b/python/tvm/tirx/expr.py
index 6e29d94444..978f1acb09 100644
--- a/python/tvm/tirx/expr.py
+++ b/python/tvm/tirx/expr.py
@@ -1186,9 +1186,6 @@ class BufferLoad(ExprWithOp):
span : Optional[Span]
The location of this expression in the source code.
- predicate : Optional[Expr]
- A vector mask of boolean values indicating which lanes of a vector are
to be
- loaded. The number lanes of the mask must be equal to the number of
lanes being loaded.
"""
buffer: Buffer
@@ -1198,14 +1195,12 @@ class BufferLoad(ExprWithOp):
self,
buffer: Buffer,
indices: list[Expr],
- predicate: Expr | None = None,
span: Span | None = None,
) -> None:
self.__init_handle_by_constructor__(
_ffi_api.BufferLoad,
buffer,
indices,
- predicate,
span, # type: ignore
)
diff --git a/python/tvm/tirx/expr_functor.py b/python/tvm/tirx/expr_functor.py
index def3b18bda..4fb502cb8b 100644
--- a/python/tvm/tirx/expr_functor.py
+++ b/python/tvm/tirx/expr_functor.py
@@ -472,7 +472,7 @@ class ExprMutator(ExprFunctor):
if all(old_index is new_index for old_index, new_index in
zip(op.indices, indices)):
return op
else:
- return tvm.tirx.BufferLoad(op.buffer, indices, op.predicate)
+ return tvm.tirx.BufferLoad(op.buffer, indices)
def visit_opaque_expr_(self, op):
"""Mutator implementation for an opaque construction-time
expression."""
diff --git a/python/tvm/tirx/op.py b/python/tvm/tirx/op.py
index 39939803a6..a4963f54e1 100644
--- a/python/tvm/tirx/op.py
+++ b/python/tvm/tirx/op.py
@@ -3003,6 +3003,52 @@ def get_active_lane_mask(dtype, base, limit):
return call_intrin(dtype, "tirx.get_active_lane_mask", base, limit)
+def masked_load(dtype, buffer, *indices_and_mask):
+ """Load vector lanes selected by a predicate mask.
+
+ Parameters
+ ----------
+ dtype : str
+ The vector data type to load.
+
+ buffer : Buffer
+ The buffer to load.
+
+ indices_and_mask : Expr
+ The buffer indices followed by a boolean lane mask. The mask must
match the
+ lane count and scalability of the loaded vector.
+
+ Returns
+ -------
+ call : Expr
+ A ``tirx.masked_load`` call with result type ``dtype``.
+ """
+ return call_intrin(dtype, "tirx.masked_load", buffer, *indices_and_mask)
+
+
+def masked_store(buffer, value, *indices_and_mask):
+ """Store vector lanes selected by a predicate mask.
+
+ Parameters
+ ----------
+ buffer : Buffer
+ The buffer to update.
+
+ value : Expr
+ The vector value to store.
+
+ indices_and_mask : Expr
+ The buffer indices followed by a boolean lane mask. The mask must
match the
+ lane count and scalability of ``value``.
+
+ Returns
+ -------
+ call : Expr
+ A void-typed ``tirx.masked_store`` call.
+ """
+ return call_intrin("void", "tirx.masked_store", buffer, value,
*indices_and_mask)
+
+
def get_vscale_expr(dtype: str | tvm_ffi.dtype, min_size: int = 128) -> Expr:
"""
Create a datatype dependent scalable expression.
diff --git a/python/tvm/tirx/script/builder/ir.py
b/python/tvm/tirx/script/builder/ir.py
index d7897647fd..e2ed32a925 100644
--- a/python/tvm/tirx/script/builder/ir.py
+++ b/python/tvm/tirx/script/builder/ir.py
@@ -2336,7 +2336,6 @@ def buffer_store(
buffer: Buffer, # pylint: disable=redefined-outer-name
value: Expr,
indices: list[Expr | slice],
- predicate: Expr | None = None,
) -> None:
"""Buffer store node.
@@ -2351,10 +2350,6 @@ def buffer_store(
indices : List[Union[Expr, slice]]
The indices location to be stored.
- predicate : Optional[Expr]
- A vector mask of boolean values indicating which lanes of a vector are
to be
- stored. The number lanes of the mask must be equal to the number of
lanes in
- value.
"""
from tvm.arith import Analyzer # pylint: disable=import-outside-toplevel
@@ -2377,7 +2372,7 @@ def buffer_store(
if isinstance(value, bool) and buffer.ty.dtype == "bool":
value = IntImm("bool", value)
return _ffi_api.BufferStore( # type: ignore[attr-defined] # pylint:
disable=no-member
- buffer, value, expr_indices, predicate
+ buffer, value, expr_indices
)
@@ -3223,6 +3218,8 @@ vectorlow = _dtype_forward(_tir_op.vectorlow)
vectorhigh = _dtype_forward(_tir_op.vectorhigh)
vectorcombine = _dtype_forward(_tir_op.vectorcombine)
get_active_lane_mask = _dtype_forward(_tir_op.get_active_lane_mask)
+masked_load = _dtype_forward(_tir_op.masked_load)
+masked_store = _op_wrapper(_tir_op.masked_store)
dp4a = _dtype_forward(_tir_op.dp4a)
@@ -3589,6 +3586,8 @@ __all__ = [
"Range",
"vscale",
"get_active_lane_mask",
+ "masked_load",
+ "masked_store",
"call_kernel",
"ignore_loop_partition",
]
diff --git a/python/tvm/tirx/script/parser/parser.py
b/python/tvm/tirx/script/parser/parser.py
index 2efef79d07..4e17808970 100644
--- a/python/tvm/tirx/script/parser/parser.py
+++ b/python/tvm/tirx/script/parser/parser.py
@@ -1044,7 +1044,7 @@ def visit_expr_stmt(self: Parser, node: doc.Expr) -> None:
# Ignore docstrings
pass
elif isinstance(res, tvm.tirx.stmt.BufferStore):
- T.buffer_store(res.buffer, res.value, res.indices, res.predicate)
+ T.buffer_store(res.buffer, res.value, res.indices)
elif is_buffer_var(res):
# ``T.match_buffer(...)`` used as a bare statement (no LHS) — the
# buffer object is discarded; the underlying side effect (the
diff --git a/python/tvm/tirx/stmt.py b/python/tvm/tirx/stmt.py
index fe4c98a402..fdccf5ee14 100644
--- a/python/tvm/tirx/stmt.py
+++ b/python/tvm/tirx/stmt.py
@@ -288,11 +288,6 @@ class BufferStore(Stmt):
indices : List[Expr]
The indices location to be stored.
- predicate : Optional[Expr]
- A vector mask of boolean values indicating which lanes of a vector are
to be
- stored. The number lanes of the mask must be equal to the number of
lanes in
- value.
-
span : Optional[Span]
The location of the stmt in the source code.
"""
@@ -300,7 +295,6 @@ class BufferStore(Stmt):
buffer: Buffer
value: Expr
indices: list[Expr]
- predicate: Expr | None
span: Span | None
def __init__(
@@ -308,7 +302,6 @@ class BufferStore(Stmt):
buffer: Buffer,
value: Expr,
indices: list[Expr],
- predicate: Expr | None = None,
span: Span | None = None,
) -> None:
self.__init_handle_by_constructor__(
@@ -316,7 +309,6 @@ class BufferStore(Stmt):
buffer,
value,
indices,
- predicate,
span, # type: ignore
)
diff --git a/python/tvm/tirx/stmt_functor.py b/python/tvm/tirx/stmt_functor.py
index 1f9755b22a..a0a7c4a553 100644
--- a/python/tvm/tirx/stmt_functor.py
+++ b/python/tvm/tirx/stmt_functor.py
@@ -292,8 +292,6 @@ class StmtVisitor(StmtFunctor):
"""Visitor implementation for BufferStore."""
self.visit_expr(op.value)
_visit_array(op.indices, lambda x: self.visit_expr(x))
- if op.predicate is not None:
- self.visit_expr(op.predicate)
def visit_assert_(self, op):
"""Visitor implementation for AssertStmt."""
@@ -546,14 +544,12 @@ class StmtMutator(StmtFunctor):
"""Mutator implementation for BufferStore."""
value = self.visit_expr(op.value)
indices = [self.visit_expr(idx) for idx in op.indices]
- predicate = self.visit_expr(op.predicate) if op.predicate is not None
else None
-
indices_changed = any(old is not new for old, new in zip(op.indices,
indices))
- if value is op.value and not indices_changed and predicate is
op.predicate:
+ if value is op.value and not indices_changed:
return op
- return tvm.tirx.BufferStore(op.buffer, value, indices, predicate,
op.span)
+ return tvm.tirx.BufferStore(op.buffer, value, indices, op.span)
def visit_buffer_realize_(self, op):
"""Mutator implementation for BufferRealize."""
diff --git a/src/backend/vulkan/codegen/codegen_spirv.cc
b/src/backend/vulkan/codegen/codegen_spirv.cc
index 69958d7d1b..af8ade1743 100644
--- a/src/backend/vulkan/codegen/codegen_spirv.cc
+++ b/src/backend/vulkan/codegen/codegen_spirv.cc
@@ -331,6 +331,10 @@ spirv::Value CodeGenSPIRV::VisitExpr_(const LetNode* op) {
}
spirv::Value CodeGenSPIRV::VisitExpr_(const CallNode* op) {
+ TVM_FFI_ICHECK(!op->op.same_as(builtin::masked_load()))
+ << "Predicated buffer load is not supported.";
+ TVM_FFI_ICHECK(!op->op.same_as(builtin::masked_store()))
+ << "Predicated buffer store is not supported.";
if (op->op.same_as(builtin::buffer_data())) {
TVM_FFI_ICHECK_EQ(op->args.size(), 1U);
return MakeValue(op->args[0]);
@@ -594,7 +598,6 @@ spirv::Value CodeGenSPIRV::VisitExpr_(const BroadcastNode*
op) {
spirv::Value CodeGenSPIRV::VisitExpr_(const BufferLoadNode* op) {
TVM_FFI_ICHECK_EQ(op->indices.size(), 1) << "SPIR-V codegen expects flat
memory buffers";
- TVM_FFI_ICHECK(!op->predicate.has_value()) << "Predicated buffer load is not
supported.";
Var buffer_var = op->buffer.var();
PrimExpr prim_index = op->indices[0];
@@ -681,7 +684,6 @@ spirv::Value CodeGenSPIRV::VisitExpr_(const ShuffleNode*
op) {
void CodeGenSPIRV::VisitStmt_(const BufferStoreNode* op) {
TVM_FFI_ICHECK_EQ(op->indices.size(), 1) << "SPIR-V codegen expects flat
memory buffers";
- TVM_FFI_ICHECK(!op->predicate.has_value()) << "Predicated buffer store is
not supported.";
Var buffer_var = op->buffer.var();
PrimExpr prim_index = op->indices[0];
diff --git a/src/backend/webgpu/codegen/codegen_webgpu.cc
b/src/backend/webgpu/codegen/codegen_webgpu.cc
index a82f9f96f1..5d20f26b2d 100644
--- a/src/backend/webgpu/codegen/codegen_webgpu.cc
+++ b/src/backend/webgpu/codegen/codegen_webgpu.cc
@@ -453,6 +453,10 @@ PrimExpr CodeGenWebGPU::EnforceU32(PrimExpr value) {
}
void CodeGenWebGPU::VisitExpr_(const CallNode* op, std::ostream& os) { //
NOLINT(*)
+ TVM_FFI_ICHECK(!op->op.same_as(builtin::masked_load()))
+ << "Predicated buffer load is not supported.";
+ TVM_FFI_ICHECK(!op->op.same_as(builtin::masked_store()))
+ << "Predicated buffer store is not supported.";
if (op->op.same_as(builtin::reinterpret())) {
// generate bitcast<TYPE>(ARG)
os << "bitcast<";
@@ -583,7 +587,6 @@ void CodeGenWebGPU::VisitExpr_(const BufferLoadNode* op,
std::ostream& os) { //
// to ensure correctness in the case of nested-expression
// do not try to lift common printings from each case
TVM_FFI_ICHECK_EQ(op->indices.size(), 1) << "Load from non-flat memory not
supported.";
- TVM_FFI_ICHECK(!op->predicate.has_value()) << "Predicated buffer load is not
supported.";
PrimType value_ty = op->ty.as_or_throw<PrimType>();
PrimExpr index = op->indices[0];
@@ -655,7 +658,6 @@ void CodeGenWebGPU::VisitStmt_(const BindNode* op) {
void CodeGenWebGPU::VisitStmt_(const BufferStoreNode* op) {
TVM_FFI_ICHECK_EQ(op->indices.size(), 1) << "Store to non-flat memory not
supported.";
- TVM_FFI_ICHECK(!op->predicate.has_value()) << "Predicated buffer store is
not supported.";
PrimType value_ty = op->value.ty();
const PrimType& element_ty = op->buffer->dtype;
diff --git a/src/relax/ir/expr_functor.cc b/src/relax/ir/expr_functor.cc
index 76c84cdb4d..bdb16ea1ba 100644
--- a/src/relax/ir/expr_functor.cc
+++ b/src/relax/ir/expr_functor.cc
@@ -180,9 +180,6 @@ void ExprVisitor::VisitExpr_(const tirx::BufferLoadNode*
op) {
for (const PrimExpr& index : op->indices) {
this->VisitExpr(index);
}
- if (op->predicate.has_value()) {
- this->VisitExpr(op->predicate.value());
- }
VisitExprDepTypeFieldIfNeeded(this, op->ty);
}
@@ -536,16 +533,10 @@ Expr ExprMutatorBase::VisitExpr_(const CallNode*
call_node) {
Expr ExprMutatorBase::VisitExpr_(const tirx::BufferLoadNode* op) {
ffi::Array<PrimExpr> indices = op->indices.Map(
[this](const PrimExpr& e) { return
this->VisitExpr(e).as_or_throw<PrimExpr>(); });
- ffi::Optional<PrimExpr> predicate = op->predicate;
- if (predicate.has_value()) {
- predicate = this->VisitExpr(predicate.value()).as_or_throw<PrimExpr>();
- }
- bool predicate_unchanged =
- !op->predicate.has_value() ||
predicate.value().same_as(op->predicate.value());
- if (indices.same_as(op->indices) && predicate_unchanged) {
+ if (indices.same_as(op->indices)) {
return ffi::GetRef<Expr>(op);
}
- return tirx::BufferLoad(op->buffer, indices, predicate, op->span);
+ return tirx::BufferLoad(op->buffer, indices, op->span);
}
#define RELAX_MUTATE_TIRX_BINOP(OP) \
diff --git a/src/s_tir/analysis/sblock_access_region_detector.cc
b/src/s_tir/analysis/sblock_access_region_detector.cc
index 3feb2c42ea..8256a19247 100644
--- a/src/s_tir/analysis/sblock_access_region_detector.cc
+++ b/src/s_tir/analysis/sblock_access_region_detector.cc
@@ -210,6 +210,34 @@ void BlockReadWriteDetector::VisitStmt_(const BindNode*
op) {
}
void BlockReadWriteDetector::VisitExpr_(const CallNode* op) {
+ auto update_masked_access = [this](const BufferVar& buffer, const
ffi::Array<PrimExpr>& indices,
+ std::vector<BufferVar>* buffers,
+ std::vector<std::vector<arith::IntSet>>*
regions) {
+ std::vector<arith::IntSet> relaxed_region;
+ for (PrimExpr index : indices) {
+ PrimExpr remapped_index = Substitute(index, let_bindings_);
+ while (!remapped_index.same_as(index)) {
+ index = remapped_index;
+ remapped_index = Substitute(index, let_bindings_);
+ }
+
relaxed_region.push_back(arith::EvalSet(arith::IntSet::Vector(remapped_index),
dom_map_));
+ }
+ Update(buffers, regions, buffer, relaxed_region);
+ };
+ if (op->op.same_as(builtin::masked_load()) ||
op->op.same_as(builtin::masked_store())) {
+ bool is_load = op->op.same_as(builtin::masked_load());
+ BufferVar buffer(op->args[0].as_or_throw<Var>());
+ ffi::Array<PrimExpr> indices;
+ for (size_t i = is_load ? 1 : 2; i + 1 < op->args.size(); ++i) {
+ indices.push_back(op->args[i].as_or_throw<PrimExpr>());
+ }
+ update_masked_access(buffer, indices, is_load ? &read_buffers_ :
&writes_buffers_,
+ is_load ? &read_regions_ : &write_regions_);
+ for (size_t i = 1; i < op->args.size(); ++i) {
+ VisitExpr(op->args[i]);
+ }
+ return;
+ }
if (op->op.same_as(builtin::tvm_access_ptr())) {
const VarNode* buffer_var = op->args[1].as<VarNode>();
if (const auto* data = op->args[1].as<CallNode>();
diff --git a/src/s_tir/transform/inject_virtual_thread.cc
b/src/s_tir/transform/inject_virtual_thread.cc
index e6b0396190..b6c8fe7162 100644
--- a/src/s_tir/transform/inject_virtual_thread.cc
+++ b/src/s_tir/transform/inject_virtual_thread.cc
@@ -75,7 +75,18 @@ class ExprTouched final : public StmtExprVisitor {
}
void VisitExpr_(const VarNode* op) final { HandleUseVar(op); }
void VisitExpr_(const CallNode* op) final {
- if (op->op.same_as(builtin::tvm_access_ptr())) {
+ if (op->op.same_as(builtin::masked_load()) ||
op->op.same_as(builtin::masked_store())) {
+ bool is_load = op->op.same_as(builtin::masked_load());
+ const VarNode* buffer = op->args[0].as_or_throw<Var>().get();
+ if (is_load) {
+ HandleUseVar(buffer);
+ } else {
+ HandleWriteVar(buffer);
+ }
+ for (size_t i = 1; i < op->args.size(); ++i) {
+ this->VisitExpr(op->args[i]);
+ }
+ } else if (op->op.same_as(builtin::tvm_access_ptr())) {
const auto* rw_mask = op->args[4].as<IntImmNode>();
auto buffer = GetBufferDataVar(op->args[1]);
if (!buffer.has_value()) {
@@ -240,7 +251,29 @@ class VTInjector : public arith::IRMutatorWithAnalyzer {
}
// Expression.
Expr VisitExpr_(const CallNode* op) final {
- if (op->op.same_as(builtin::buffer_data())) {
+ if (op->op.same_as(builtin::masked_load()) ||
op->op.same_as(builtin::masked_store())) {
+ bool is_load = op->op.same_as(builtin::masked_load());
+ BufferVar buffer(op->args[0].as_or_throw<Var>());
+ PrimExpr value;
+ if (!is_load) value =
this->VisitPrimExpr(op->args[1].as_or_throw<PrimExpr>());
+ ffi::Array<PrimExpr> indices;
+ for (size_t i = is_load ? 1 : 2; i + 1 < op->args.size(); ++i) {
+
indices.push_back(this->VisitPrimExpr(op->args[i].as_or_throw<PrimExpr>()));
+ }
+ PrimExpr predicate =
this->VisitPrimExpr(op->args.back().as_or_throw<PrimExpr>());
+ if (is_load) {
+ BufferLoad access = VisitBufferAccess(BufferLoad(buffer, indices,
op->span));
+ ffi::Array<Expr> args{access->buffer.var()};
+ for (const PrimExpr& index : access->indices) args.push_back(index);
+ args.push_back(predicate);
+ return Call(op->ty, op->op, args, op->attrs, op->ty_args, op->span);
+ }
+ BufferStore access = VisitBufferAccess(BufferStore(buffer, value,
indices, op->span));
+ ffi::Array<Expr> args{access->buffer.var(), access->value};
+ for (const PrimExpr& index : access->indices) args.push_back(index);
+ args.push_back(predicate);
+ return Call(op->ty, op->op, args, op->attrs, op->ty_args, op->span);
+ } else if (op->op.same_as(builtin::buffer_data())) {
auto buffer = GetBufferDataVar(ffi::GetRef<Call>(op)).value();
auto it = alloc_remap_.find(buffer.get());
if (it == alloc_remap_.end()) {
diff --git a/src/s_tir/transform/lower_match_buffer.cc
b/src/s_tir/transform/lower_match_buffer.cc
index 98343099ea..9fbcfc25c3 100644
--- a/src/s_tir/transform/lower_match_buffer.cc
+++ b/src/s_tir/transform/lower_match_buffer.cc
@@ -63,7 +63,7 @@ class MatchBufferLower : public StmtExprMutator {
for (const auto& kv : match_buffers_) {
orig_buffers.push_back(kv.first);
}
- Stmt stmt = StmtExprMutator ::VisitStmt_(op);
+ Stmt stmt = StmtExprMutator::VisitStmt_(op);
// Add remapped buffer keys to match_buffers_
for (const BufferVar& orig_buf : orig_buffers) {
if (auto remap_it = buffer_remap_.find(orig_buf); remap_it !=
buffer_remap_.end()) {
@@ -107,6 +107,14 @@ class MatchBufferLower : public StmtExprMutator {
}
Expr VisitExpr_(const CallNode* op) final {
+ if ((op->op.same_as(builtin::masked_load()) ||
op->op.same_as(builtin::masked_store())) &&
+ !op->args.empty()) {
+ if (auto var = op->args[0].as<Var>(); var &&
var.value()->ty.as<BufferTypeNode>()) {
+ BufferVar buffer(var.value());
+ TVM_FFI_ICHECK(!match_buffers_.count(buffer))
+ << "Predicated buffer access is not currently supported in lower
match buffer pass.";
+ }
+ }
if (op->op.same_as(builtin::buffer_data()) && op->args.size() == 1) {
if (auto var = op->args[0].as<Var>();
var.has_value() && var.value()->ty.as<BufferTypeNode>()) {
@@ -137,8 +145,6 @@ class MatchBufferLower : public StmtExprMutator {
auto n = CopyOnWrite(op);
n->indices = ConvertIndices(MatchBufferRegion(buffer, source),
op->indices);
n->buffer = source->buffer;
- TVM_FFI_ICHECK(!op->predicate.has_value())
- << "Predicated buffer store is not currently supported in lower
match buffer pass.";
return Stmt(n);
}
}
@@ -157,8 +163,6 @@ class MatchBufferLower : public StmtExprMutator {
const BufferVar& buffer = (*it).first;
const BufferRegion& source = (*it).second;
ffi::Array<PrimExpr> indices = ConvertIndices(MatchBufferRegion(buffer,
source), op->indices);
- TVM_FFI_ICHECK(!op->predicate.has_value())
- << "Predicated buffer load is not currently supported in lower match
buffer pass.";
return BufferLoad(source->buffer, indices);
}
}
diff --git a/src/s_tir/transform/manifest_shared_memory_local_stage.cc
b/src/s_tir/transform/manifest_shared_memory_local_stage.cc
index 5dd428dece..4a3be8ee71 100644
--- a/src/s_tir/transform/manifest_shared_memory_local_stage.cc
+++ b/src/s_tir/transform/manifest_shared_memory_local_stage.cc
@@ -72,9 +72,6 @@ class IntermediateStageRewriter {
Stmt local_stage = MakeLocalStage(block, new_buffer, buffer_indices,
relaxed_loops, store);
// Step 3: Create BufferLoad from the intermediate buffer
- TVM_FFI_ICHECK(!store->predicate.has_value())
- << "Predicated buffer store is not currently supported in "
- "manifest shared memory local stage pass.";
BufferLoad new_buffer_load = BufferLoad(new_buffer, buffer_indices);
BufferStore new_buffer_store = block->body.as_or_throw<BufferStore>();
new_buffer_store.CopyOnWrite()->value = new_buffer_load;
diff --git a/src/s_tir/transform/storage_access.cc
b/src/s_tir/transform/storage_access.cc
index 897ccfa57d..2ad37af585 100644
--- a/src/s_tir/transform/storage_access.cc
+++ b/src/s_tir/transform/storage_access.cc
@@ -260,7 +260,29 @@ void StorageAccessVisitor::VisitStmt_(const WhileNode* op)
{
}
void StorageAccessVisitor::VisitExpr_(const CallNode* op) {
- if (op->op.same_as(builtin::address_of())) {
+ Call call = ffi::GetRef<Call>(op);
+ if (op->op.same_as(builtin::masked_load()) ||
op->op.same_as(builtin::masked_store())) {
+ bool is_load = op->op.same_as(builtin::masked_load());
+ BufferVar buffer(op->args[0].as_or_throw<Var>());
+ PrimType value_dtype =
+ is_load ? op->ty.as_or_throw<PrimType>() :
op->args[1].as_or_throw<PrimExpr>().ty();
+ Var buf = ResolveBuffer(buffer.var());
+ StorageScope scope = StorageScope::Create(buffer.scope());
+ if (Enabled(buf.get(), scope)) {
+ TVM_FFI_ICHECK(allow_append_) << call << " " << scope.to_string();
+ AccessEntry e;
+ e.threads = env_threads();
+ e.buffer = buf;
+ e.dtype = value_dtype.WithLanes(1);
+ for (size_t i = is_load ? 1 : 2; i + 1 < op->args.size(); ++i) {
+
e.touched.push_back(arith::IntSet::Vector(op->args[i].as_or_throw<PrimExpr>()));
+ }
+ e.type = is_load ? kRead : kWrite;
+ e.scope = scope;
+ curr_stmt_.access.emplace_back(std::move(e));
+ }
+ StmtExprVisitor::VisitExpr_(op);
+ } else if (op->op.same_as(builtin::address_of())) {
if (const auto* load = op->args[0].as<BufferLoadNode>()) {
// Taking an address does not read the buffer value. Visit only the
// load's children so index expressions still contribute accesses.
diff --git a/src/target/llvm/codegen_llvm.cc b/src/target/llvm/codegen_llvm.cc
index 24da88e24e..74f5bac291 100644
--- a/src/target/llvm/codegen_llvm.cc
+++ b/src/target/llvm/codegen_llvm.cc
@@ -1871,7 +1871,7 @@ llvm::Value* CodeGenLLVM::VisitExpr_(const
BufferLoadNode* op) {
// Pass all indices into BufferAccessHelper. In CodeGenLLVM,
// non-flat indices will result in an error in CreateBufferPtr, but
// a subclass may override CreateBufferPtr.
- BufferAccessHelper(op->buffer, op->indices, op->predicate, access_dtype,
make_load);
+ BufferAccessHelper(op->buffer, op->indices, std::nullopt, access_dtype,
make_load);
llvm::Value* ret;
if (loads.size() == 1) {
@@ -1888,8 +1888,73 @@ llvm::Value* CodeGenLLVM::VisitExpr_(const
BufferLoadNode* op) {
return ret;
}
+llvm::Value* CodeGenLLVM::CreateMaskedLoad(const CallNode* op) {
+ TVM_FFI_ICHECK_GE(op->args.size(), 3U);
+ BufferVar buffer(op->args[0].as_or_throw<Var>());
+ ffi::Array<PrimExpr> indices;
+ for (size_t i = 1; i + 1 < op->args.size(); ++i) {
+ indices.push_back(op->args[i].as_or_throw<PrimExpr>());
+ }
+ PrimExpr predicate = op->args.back().as_or_throw<PrimExpr>();
+ PrimType value_dtype = op->ty.as_or_throw<PrimType>();
+ PrimType access_dtype = BufferAccessType(value_dtype);
+ std::vector<llvm::Value*> loads;
+ auto make_load =
+ [this, &loads](TypedPointer buffer_ptr, int /*subelement_i*/,
llvm::Value* predicate,
+ int alignment, bool is_volatile) {
+ TVM_FFI_ICHECK(!is_volatile)
+ << "The masked load intrinsic does not support declaring load as
volatile.";
+ llvm::Instruction* value = builder_->CreateMaskedLoad(buffer_ptr.type,
buffer_ptr.addr,
+
llvm::Align(alignment), predicate);
+ loads.push_back(value);
+ return value;
+ };
+ BufferAccessHelper(buffer, indices, predicate, access_dtype, make_load);
+ llvm::Value* ret =
+ loads.size() == 1 ? loads[0] :
llvm::UndefValue::get(DTypeToLLVMType(access_dtype));
+ for (size_t i = 0; loads.size() > 1 && i < loads.size(); ++i) {
+ ret = builder_->CreateInsertElement(ret, loads[i], ConstInt32(i));
+ }
+ return access_dtype.same_as(value_dtype) ? ret : CreateCast(access_dtype,
value_dtype, ret);
+}
+
+llvm::Value* CodeGenLLVM::CreateMaskedStore(const CallNode* op) {
+ TVM_FFI_ICHECK_GE(op->args.size(), 4U);
+ BufferVar buffer(op->args[0].as_or_throw<Var>());
+ PrimExpr value_expr = op->args[1].as_or_throw<PrimExpr>();
+ ffi::Array<PrimExpr> indices;
+ for (size_t i = 2; i + 1 < op->args.size(); ++i) {
+ indices.push_back(op->args[i].as_or_throw<PrimExpr>());
+ }
+ PrimExpr predicate = op->args.back().as_or_throw<PrimExpr>();
+ PrimType value_dtype = value_expr.ty();
+ llvm::Value* value = MakeValue(value_expr);
+ PrimType access_dtype = BufferAccessType(value_dtype);
+ if (!access_dtype.same_as(value_dtype)) {
+ value = CreateCast(value_dtype, access_dtype, value);
+ value_dtype = access_dtype;
+ }
+ llvm::Instruction* last_store = nullptr;
+ auto make_store =
+ [this, value, &last_store](TypedPointer buffer_ptr, int subelement_i,
llvm::Value* predicate,
+ int alignment, bool is_volatile) {
+ TVM_FFI_ICHECK(!is_volatile)
+ << "The masked store intrinsic does not support declaring store as
volatile.";
+ llvm::Value* to_store =
+ subelement_i == -1 ? value : builder_->CreateExtractElement(value,
subelement_i);
+ last_store = builder_->CreateMaskedStore(to_store, buffer_ptr.addr,
llvm::Align(alignment),
+ predicate);
+ return last_store;
+ };
+ BufferAccessHelper(buffer, indices, predicate, value_dtype, make_store);
+ TVM_FFI_ICHECK(last_store != nullptr);
+ return last_store;
+}
+
llvm::Value* CodeGenLLVM::VisitExpr_(const CallNode* op) {
const ffi::Array<Expr>& args = op->args;
+ if (op->op.same_as(builtin::masked_load())) return CreateMaskedLoad(op);
+ if (op->op.same_as(builtin::masked_store())) return CreateMaskedStore(op);
if (op->op.same_as(builtin::buffer_data())) {
TVM_FFI_ICHECK_EQ(args.size(), 1U);
return MakeValue(args[0]);
@@ -2037,7 +2102,7 @@ void CodeGenLLVM::VisitStmt_(const BufferStoreNode* op) {
// Pass all indices into BufferAccessHelper. In CodeGenLLVM,
// non-flat indices will result in an error in CreateBufferPtr, but
// a subclass may override CreateBufferPtr.
- BufferAccessHelper(op->buffer, op->indices, op->predicate, value_dtype,
make_store);
+ BufferAccessHelper(op->buffer, op->indices, std::nullopt, value_dtype,
make_store);
}
void CodeGenLLVM::VisitStmt_(const ForNode* op) {
diff --git a/src/target/llvm/codegen_llvm.h b/src/target/llvm/codegen_llvm.h
index 8a7e0bd1b1..ea9763448f 100644
--- a/src/target/llvm/codegen_llvm.h
+++ b/src/target/llvm/codegen_llvm.h
@@ -363,6 +363,8 @@ class CodeGenLLVM : public ExprFunctor<llvm::Value*(const
Expr&)>,
std::function<llvm::Instruction*(TypedPointer buffer_ptr, int
subelement_i,
llvm::Value* predicate, int alignment,
bool is_volatile)>
make_instruction);
+ llvm::Value* CreateMaskedLoad(const CallNode* op);
+ llvm::Value* CreateMaskedStore(const CallNode* op);
const VarNode* GetBufferPhysicalRoot(const VarNode* buffer) const;
// Initialize target
virtual void InitTarget();
diff --git a/src/target/source/codegen_c.cc b/src/target/source/codegen_c.cc
index da8a6cb849..2b62d55dc2 100644
--- a/src/target/source/codegen_c.cc
+++ b/src/target/source/codegen_c.cc
@@ -671,6 +671,10 @@ void CodeGenC::PrintCallExtern(Type ret_type, ffi::String
global_symbol,
}
void CodeGenC::VisitExpr_(const CallNode* op, std::ostream& os) { // NOLINT(*)
+ TVM_FFI_ICHECK(!op->op.same_as(builtin::masked_load()))
+ << "Predicated buffer load is not supported.";
+ TVM_FFI_ICHECK(!op->op.same_as(builtin::masked_store()))
+ << "Predicated buffer store is not supported.";
if (auto opt_call_op = op->op.as<Op>()) {
auto call_op = opt_call_op.value();
@@ -951,7 +955,6 @@ void CodeGenC::VisitStmt_(const DeclBufferNode* op) {
void CodeGenC::VisitExpr_(const BufferLoadNode* op, std::ostream& os) { //
NOLINT(*)
TVM_FFI_ICHECK_EQ(op->indices.size(), 1) << "Load from non-flat memory not
supported.";
- TVM_FFI_ICHECK(!op->predicate.has_value()) << "Predicated buffer load is not
supported.";
PrimType value_ty = op->ty.as_or_throw<PrimType>();
PrimExpr index = op->indices[0];
@@ -1026,7 +1029,6 @@ void CodeGenC::VisitExpr_(const BufferLoadNode* op,
std::ostream& os) { // NOLI
void CodeGenC::VisitStmt_(const BufferStoreNode* op) {
TVM_FFI_ICHECK_EQ(op->indices.size(), 1) << "Store to non-flat memory not
supported.";
- TVM_FFI_ICHECK(!op->predicate.has_value()) << "Predicated buffer store is
not supported.";
PrimType value_ty = op->value.ty();
const PrimType& element_ty = op->buffer->dtype;
diff --git a/src/te/operation/create_primfunc.cc
b/src/te/operation/create_primfunc.cc
index 2332276f10..70ab9e0d9f 100644
--- a/src/te/operation/create_primfunc.cc
+++ b/src/te/operation/create_primfunc.cc
@@ -84,7 +84,7 @@ class TensorLoadToBufferTransformer : public StmtExprMutator {
auto it = tensor2buffers_.find(tensor);
TVM_FFI_ICHECK(it != tensor2buffers_.end()) << "IndexError: Cannot find
the tensor " << tensor;
const BufferVar& buffer = it->second;
- return BufferLoad(buffer, te::GetTensorLoadIndices(call), std::nullopt,
call->span);
+ return BufferLoad(buffer, te::GetTensorLoadIndices(call), call->span);
}
private:
@@ -111,7 +111,7 @@ class BufferSubstituter : public StmtExprMutator {
auto load = StmtExprMutator::VisitExpr_(op).as_or_throw<BufferLoad>();
auto it = buffer_map_.find(load->buffer.get());
if (it != buffer_map_.end()) {
- return BufferLoad(it->second, load->indices, load->predicate,
load->span);
+ return BufferLoad(it->second, load->indices, load->span);
}
return load;
}
@@ -120,7 +120,7 @@ class BufferSubstituter : public StmtExprMutator {
auto store = StmtExprMutator::VisitStmt_(op).as_or_throw<BufferStore>();
auto it = buffer_map_.find(store->buffer.get());
if (it != buffer_map_.end()) {
- return BufferStore(it->second, store->value, store->indices,
store->predicate, store->span);
+ return BufferStore(it->second, store->value, store->indices,
store->span);
}
return store;
}
diff --git a/src/tirx/analysis/deep_equal.cc b/src/tirx/analysis/deep_equal.cc
index e915aad1b8..aecf22e0ab 100644
--- a/src/tirx/analysis/deep_equal.cc
+++ b/src/tirx/analysis/deep_equal.cc
@@ -142,8 +142,7 @@ class ExprDeepEqualChecker : private ExprFunctor<bool(const
Expr&, const PrimExp
const auto* prhs = rhs.as<BufferLoadNode>();
// we run pointer comparison of the buffer
return plhs->ty.as_or_throw<PrimType>() ==
prhs->ty.as_or_throw<PrimType>() &&
- plhs->buffer.same_as(prhs->buffer) && ArrayDeepEqual(plhs->indices,
prhs->indices) &&
- OptionalDeepEqual(plhs->predicate, prhs->predicate);
+ plhs->buffer.same_as(prhs->buffer) && ArrayDeepEqual(plhs->indices,
prhs->indices);
}
bool VisitExpr_(const LetNode* plhs, const PrimExpr& rhs) final {
diff --git a/src/tirx/analysis/verify_memory.cc
b/src/tirx/analysis/verify_memory.cc
index 5694284614..3f79148985 100644
--- a/src/tirx/analysis/verify_memory.cc
+++ b/src/tirx/analysis/verify_memory.cc
@@ -98,6 +98,14 @@ class MemoryAccessVerifier final : protected StmtExprVisitor
{
HandleLoadStoreToVariable(op->buffer.var());
return StmtExprVisitor::VisitStmt_(op);
}
+
+ void VisitExpr_(const CallNode* op) final {
+ if ((op->op.same_as(builtin::masked_load()) ||
op->op.same_as(builtin::masked_store())) &&
+ !op->args.empty()) {
+ HandleLoadStoreToVariable(op->args[0].as_or_throw<Var>());
+ }
+ StmtExprVisitor::VisitExpr_(op);
+ }
//@}
/// Check if the value of a Variable comes from function argument.
diff --git a/src/tirx/ir/buffer.cc b/src/tirx/ir/buffer.cc
index c0a29ff8ba..14e0358001 100644
--- a/src/tirx/ir/buffer.cc
+++ b/src/tirx/ir/buffer.cc
@@ -375,8 +375,7 @@ BufferVar BufferVar::GetFlattenedBuffer() const {
}
}
-PrimExpr BufferVar::vload(ffi::Array<PrimExpr> begin, PrimType value_dtype,
- ffi::Optional<PrimExpr> predicate) const {
+PrimExpr BufferVar::vload(ffi::Array<PrimExpr> begin, PrimType value_dtype)
const {
const BufferTypeNode* n = operator->();
TVM_FFI_ICHECK(n != nullptr);
PrimType buffer_dtype(n->dtype);
@@ -397,11 +396,10 @@ PrimExpr BufferVar::vload(ffi::Array<PrimExpr> begin,
PrimType value_dtype,
indices.Set(indices.size() - 1, Ramp(base, 1, factor));
}
}
- return BufferLoad(*this, indices, predicate);
+ return BufferLoad(*this, indices);
}
-Stmt BufferVar::vstore(ffi::Array<PrimExpr> begin, PrimExpr value,
- ffi::Optional<PrimExpr> predicate) const {
+Stmt BufferVar::vstore(ffi::Array<PrimExpr> begin, PrimExpr value) const {
const BufferTypeNode* n = operator->();
TVM_FFI_ICHECK(n != nullptr);
PrimType value_dtype = value.ty();
@@ -423,7 +421,7 @@ Stmt BufferVar::vstore(ffi::Array<PrimExpr> begin, PrimExpr
value,
indices.Set(indices.size() - 1, Ramp(base, 1, factor));
}
}
- return BufferStore(*this, value, indices, predicate);
+ return BufferStore(*this, value, indices);
}
ffi::String BufferVar::scope() const { return (*this)->storage_scope; }
@@ -581,10 +579,7 @@ TVM_FFI_STATIC_INIT_BLOCK() {
.def_method("tirx.BufferGetFlattenedBuffer",
&BufferVar::GetFlattenedBuffer)
.def_method("tirx.BufferOffsetOf", &BufferVar::OffsetOf)
.def_method("tirx.BufferOffsetOfp", &BufferVar::OffsetOf_p)
- .def_method(
- "tirx.BufferVLoad",
- static_cast<PrimExpr (BufferVar::*)(ffi::Array<PrimExpr>, PrimType,
- ffi::Optional<PrimExpr>)
const>(&BufferVar::vload))
+ .def_method("tirx.BufferVLoad", &BufferVar::vload)
.def_method("tirx.BufferVStore", &BufferVar::vstore)
.def_method("tirx.BufferStorageScope", &BufferVar::scope)
.def_method("tirx.BufferWithAllocatedAddr",
&BufferVar::with_allocated_addr)
diff --git a/src/tirx/ir/expr.cc b/src/tirx/ir/expr.cc
index 860a13f7eb..1c7f946771 100644
--- a/src/tirx/ir/expr.cc
+++ b/src/tirx/ir/expr.cc
@@ -748,38 +748,15 @@ void BufferLoadNode::LegalizeDType() {
}
}
-BufferLoad::BufferLoad(BufferVar buffer, ffi::Array<PrimExpr> indices,
- ffi::Optional<PrimExpr> predicate, Span span) {
+BufferLoad::BufferLoad(BufferVar buffer, ffi::Array<PrimExpr> indices, Span
span) {
TVM_FFI_ICHECK_EQ(buffer->shape.size(), indices.size())
<< "BufferVar " << buffer.name() << " is " << buffer->shape.size()
<< "-dimensional, cannot be indexed with the " << indices.size()
<< "-dimensional indices provided.";
- if (predicate.has_value()) {
- PrimType predicate_ty = predicate.value().ty();
- bool is_index_scalable = indices.empty() ? false :
indices.back().ty().IsScalableVector();
- bool is_predicate_scalable = predicate_ty.IsScalableVector();
- TVM_FFI_ICHECK_EQ(is_index_scalable, is_predicate_scalable)
- << "Predicate mask dtype and load indices must both be scalable.";
-
- int16_t buffer_encoded_lanes =
static_cast<int16_t>(buffer->dtype->dtype.lanes);
- int buffer_lanes = buffer_encoded_lanes < -1 ? -buffer_encoded_lanes :
buffer_encoded_lanes;
- int index_lanes = indices.empty() ? 1 :
GetLanesOrVScaleFactor(indices.back().ty());
- int predicate_lanes = GetLanesOrVScaleFactor(predicate_ty);
- TVM_FFI_ICHECK_EQ(index_lanes * buffer_lanes, predicate_lanes)
- << "Got a predicate mask with " << predicate_lanes
- << " lanes, but trying to load a vector with " << index_lanes
- << " lanes. The number of lanes must match.";
-
- TVM_FFI_ICHECK(predicate_ty.MatchesCode(DLDataTypeCode::kDLBool) ||
- predicate_ty.MatchesElementType(DLDataTypeCode::kDLUInt, 1))
- << "Predicate mask elements must be boolean values, but got " <<
predicate_ty->dtype << ".";
- }
-
ffi::ObjectPtr<BufferLoadNode> node = ffi::make_object<BufferLoadNode>();
node->buffer = std::move(buffer);
node->indices = std::move(indices);
- node->predicate = std::move(predicate);
node->span = std::move(span);
node->LegalizeDType();
data_ = std::move(node);
@@ -787,10 +764,10 @@ BufferLoad::BufferLoad(BufferVar buffer,
ffi::Array<PrimExpr> indices,
TVM_FFI_STATIC_INIT_BLOCK() {
namespace refl = tvm::ffi::reflection;
- refl::GlobalDef().def("tirx.BufferLoad", [](BufferVar buffer,
ffi::Array<PrimExpr> indices,
- ffi::Optional<PrimExpr>
predicate, Span span) {
- return BufferLoad(buffer, indices, predicate, span);
- });
+ refl::GlobalDef().def("tirx.BufferLoad",
+ [](BufferVar buffer, ffi::Array<PrimExpr> indices,
Span span) {
+ return BufferLoad(buffer, indices, span);
+ });
}
} // namespace tirx
diff --git a/src/tirx/ir/expr_functor.cc b/src/tirx/ir/expr_functor.cc
index 9a73caf2c8..f739891473 100644
--- a/src/tirx/ir/expr_functor.cc
+++ b/src/tirx/ir/expr_functor.cc
@@ -124,7 +124,7 @@ Expr ExprMutator::VisitExpr_(const BufferLoadNode* op) {
if (indices.same_as(op->indices)) {
return ffi::GetRef<PrimExpr>(op);
} else {
- return BufferLoad(op->buffer, indices, op->predicate);
+ return BufferLoad(op->buffer, indices);
}
}
diff --git a/src/tirx/ir/stmt.cc b/src/tirx/ir/stmt.cc
index a35791c60a..84d70b8fc1 100644
--- a/src/tirx/ir/stmt.cc
+++ b/src/tirx/ir/stmt.cc
@@ -422,7 +422,7 @@ TVM_FFI_INLINE int GetLanesOrVScaleFactor(const PrimType&
ty) {
}
BufferStore::BufferStore(BufferVar buffer, PrimExpr value,
ffi::Array<PrimExpr> indices,
- ffi::Optional<PrimExpr> predicate, Span span) {
+ Span span) {
TVM_FFI_ICHECK_EQ(buffer->shape.size(), indices.size())
<< "BufferVar " << buffer.name() << " is " << buffer->shape.size()
<< "-dimensional, cannot be indexed with the " << indices.size()
@@ -442,12 +442,6 @@ BufferStore::BufferStore(BufferVar buffer, PrimExpr value,
ffi::Array<PrimExpr>
TVM_FFI_ICHECK(!(is_index_scalable && is_buffer_dtype_scalable))
<< "Index dtype and buffer dtype can't both be scalable.";
- if (predicate.has_value()) {
- bool is_predicate_dtype_scalable =
predicate.value().ty().IsScalableVector();
- TVM_FFI_ICHECK_EQ(is_value_dtype_scalable, is_predicate_dtype_scalable)
- << "Predicate mask dtype and value dtype must both be scalable.";
- }
-
if (is_index_scalable || is_buffer_dtype_scalable) {
TVM_FFI_ICHECK(is_value_dtype_scalable) << "Can't store non-scalable data
into scalable buffer";
}
@@ -461,21 +455,6 @@ BufferStore::BufferStore(BufferVar buffer, PrimExpr value,
ffi::Array<PrimExpr>
<< index_lanes * buffer_lanes << " (" << index_lanes << " index lanes *
" << buffer_lanes
<< " buffer element lanes)";
- if (predicate.has_value()) {
- PrimType predicate_ty = predicate.value().ty();
- int predicate_dtype_lanes = GetLanesOrVScaleFactor(predicate_ty);
- TVM_FFI_ICHECK_EQ(value_dtype_lanes, predicate_dtype_lanes)
- << "Got a predicate mask with " << predicate_dtype_lanes
- << " lanes, but trying to store a value with " << value_dtype_lanes
- << " lanes. The number of lanes must match.";
-
- PrimType predicate_element_ty = predicate_ty.WithLanes(1);
- TVM_FFI_ICHECK(predicate_element_ty.MatchesCode(DLDataTypeCode::kDLBool) ||
-
predicate_element_ty.MatchesElementType(DLDataTypeCode::kDLUInt, 1))
- << "Predicate mask elements must be boolean values, but got "
- << ffi::DLDataTypeToString(predicate_element_ty->dtype) << ".";
- }
-
PrimType buffer_dtype = PrimType::Void();
if (is_index_scalable || is_buffer_dtype_scalable) {
buffer_dtype = PrimType::ScalableVector(buffer->dtype.code(),
buffer->dtype.bits(),
@@ -495,7 +474,6 @@ BufferStore::BufferStore(BufferVar buffer, PrimExpr value,
ffi::Array<PrimExpr>
node->buffer = std::move(buffer);
node->value = std::move(value);
node->indices = std::move(indices);
- node->predicate = std::move(predicate);
node->span = std::move(span);
data_ = std::move(node);
}
@@ -504,9 +482,7 @@ TVM_FFI_STATIC_INIT_BLOCK() {
namespace refl = tvm::ffi::reflection;
refl::GlobalDef().def("tirx.BufferStore",
[](BufferVar buffer, PrimExpr value,
ffi::Array<PrimExpr> indices,
- ffi::Optional<PrimExpr> predicate, Span span) {
- return BufferStore(buffer, value, indices,
predicate, span);
- });
+ Span span) { return BufferStore(buffer, value,
indices, span); });
}
// BufferRegion
diff --git a/src/tirx/op/builtin.cc b/src/tirx/op/builtin.cc
index 737d857e09..df9fe03382 100644
--- a/src/tirx/op/builtin.cc
+++ b/src/tirx/op/builtin.cc
@@ -379,6 +379,15 @@ TIR_DEFINE_BUILTIN_FUNC(get_active_lane_mask)
.set_attr<TScriptDtypePrintLocation>("TScriptDtypePrintLocation",
static_cast<int64_t>(ScriptDtypePrintLocation::kFirst));
+TIR_DEFINE_BUILTIN_FUNC(masked_load)
+ .set_attr<TCallEffectKind>("TCallEffectKind",
static_cast<int64_t>(CallEffectKind::kReadState))
+ .set_attr<TScriptDtypePrintLocation>("TScriptDtypePrintLocation",
+
static_cast<int64_t>(ScriptDtypePrintLocation::kFirst));
+
+TIR_DEFINE_BUILTIN_FUNC(masked_store)
+ .set_attr<TCallEffectKind>("TCallEffectKind",
+
static_cast<int64_t>(CallEffectKind::kUpdateState));
+
TIR_DEFINE_BUILTIN_FUNC(ignore_loop_partition)
.set_num_inputs(1)
.set_attr<TCallEffectKind>("TCallEffectKind",
static_cast<int64_t>(CallEffectKind::kPure))
diff --git a/src/tirx/script/builder/ir.cc b/src/tirx/script/builder/ir.cc
index f4a6a81f84..a94b97fb79 100644
--- a/src/tirx/script/builder/ir.cc
+++ b/src/tirx/script/builder/ir.cc
@@ -784,8 +784,7 @@ Var EnvThread(ffi::String thread_tag, PrimType dtype) {
return var;
}
-void BufferStore(BufferVar buffer, PrimExpr value, ffi::Array<PrimExpr>
indices,
- ffi::Optional<PrimExpr> predicate = std::nullopt) {
+void BufferStore(BufferVar buffer, PrimExpr value, ffi::Array<PrimExpr>
indices) {
PrimType buffer_dtype = buffer->dtype;
PrimType index_ty = indices.empty() ? PrimType::Int(32) :
indices.back().ty();
bool is_index_scalable = !indices.empty() && index_ty.IsScalableVector();
@@ -833,7 +832,7 @@ void BufferStore(BufferVar buffer, PrimExpr value,
ffi::Array<PrimExpr> indices,
}
value = tvm::cast(lhs_dtype, value);
}
- tvm::tirx::BufferStore store(buffer, value, indices, predicate);
+ tvm::tirx::Stmt store = tvm::tirx::BufferStore(buffer, value, indices);
if (lhs_dtype != rhs_dtype) {
if (lhs_dtype.code() != rhs_dtype.code()) {
if ((lhs_dtype.MatchesCode(DLDataTypeCode::kDLInt,
DLDataTypeCode::kDLUInt)) &&
diff --git a/src/tirx/script/printer/buffer.cc
b/src/tirx/script/printer/buffer.cc
index c13a06aa0d..34750447d3 100644
--- a/src/tirx/script/printer/buffer.cc
+++ b/src/tirx/script/printer/buffer.cc
@@ -413,8 +413,7 @@ TVM_STATIC_IR_FUNCTOR(IRDocsifier, vtable)
ExprDoc value = d->AsDoc<ExprDoc>(store->value, p->Attr("value"));
// special case for scalar buffers
- if ((store->buffer.IsScalar(true) || store->buffer.IsScalar(false))
&&
- !store->predicate.has_value()) {
+ if (store->buffer.IsScalar(true) || store->buffer.IsScalar(false)) {
// TVM_FFI_ICHECK(store->indices.size() == 1 &&
tirx::is_zero(store->indices[0]))
// << "1-dim buffer with shape (1,) store with indices other
than [0] is not "
// "supported";
@@ -424,14 +423,6 @@ TVM_STATIC_IR_FUNCTOR(IRDocsifier, vtable)
return AssignDoc(doc.value(), value, std::nullopt);
}
- // Use .vstore(...) syntax when there is a predicate
- if (store->predicate.has_value()) {
- ExprDoc indices = d->AsDoc<ExprDoc>(store->indices,
p->Attr("indices"));
- ExprDoc predicate = d->AsDoc<ExprDoc>(store->predicate,
p->Attr("predicate"));
- return ExprStmtDoc(
- buffer->Attr("vstore")->Call({indices, value}, {"predicate"},
{predicate}));
- }
-
return AssignDoc(
/*lhs=*/buffer[BufferIndices(store->indices, p->Attr("indices"),
d)],
/*rhs=*/value, std::nullopt);
@@ -443,8 +434,7 @@ TVM_STATIC_IR_FUNCTOR(IRDocsifier, vtable)
ExprDoc buffer = d->AsDoc<ExprDoc>(load->buffer, p->Attr("buffer"));
// special case for scalar
- if ((load->buffer.IsScalar(true) || load->buffer.IsScalar(false)) &&
- !load->predicate.has_value()) {
+ if (load->buffer.IsScalar(true) || load->buffer.IsScalar(false)) {
// TVM_FFI_ICHECK(load->indices.size() == 1 &&
tirx::is_zero(load->indices[0]))
// << "Scalar buffer load with indices other than [0] is not
supported";
ffi::Optional<ExprDoc> doc = d->GetVarDoc(load->buffer);
@@ -453,13 +443,6 @@ TVM_STATIC_IR_FUNCTOR(IRDocsifier, vtable)
return doc.value();
}
- // Use .vload(...) syntax when there is a predicate
- if (load->predicate.has_value()) {
- ExprDoc indices = d->AsDoc<ExprDoc>(load->indices,
p->Attr("indices"));
- ExprDoc predicate = d->AsDoc<ExprDoc>(load->predicate,
p->Attr("predicate"));
- return buffer->Attr("vload")->Call({indices}, {"predicate"},
{predicate});
- }
-
return buffer[BufferIndices(load->indices, p->Attr("indices"), d)];
});
diff --git a/src/tirx/script/printer/utils.h b/src/tirx/script/printer/utils.h
index b2246a667f..e98e09c1ff 100644
--- a/src/tirx/script/printer/utils.h
+++ b/src/tirx/script/printer/utils.h
@@ -123,6 +123,12 @@ inline void AsDocBody(const tirx::Stmt& stmt, AccessPath
p, TIRFrameNode* f, con
if (load->buffer.same_as(buffer)) {
found = true;
}
+ } else if (const auto* call = node.as<CallNode>()) {
+ if (call->op.same_as(tirx::builtin::masked_load()) &&
!call->args.empty()) {
+ if (auto var = call->args[0].as<Var>(); var &&
var.value().same_as(buffer.var())) {
+ found = true;
+ }
+ }
}
});
return found;
@@ -137,8 +143,7 @@ inline void AsDocBody(const tirx::Stmt& stmt, AccessPath p,
TIRFrameNode* f, con
if (d->cfg->syntax_sugar && alloc != nullptr &&
alloc->buffer.IsScalar(true) && i + 1 < n) {
const auto* store = body[i + 1].as<tirx::BufferStoreNode>();
bool can_merge_init = store != nullptr &&
store->buffer.same_as(alloc->buffer) &&
- !store->predicate.has_value() &&
store->indices.size() == 1 &&
- tirx::is_zero(store->indices[0]) &&
+ store->indices.size() == 1 &&
tirx::is_zero(store->indices[0]) &&
!value_refs_buffer(store->value, alloc->buffer);
if (can_merge_init) {
Doc alloc_doc = d->AsDoc(body[i], item_p);
diff --git a/src/tirx/transform/flatten_buffer.cc
b/src/tirx/transform/flatten_buffer.cc
index d901d91434..978ecec1f9 100644
--- a/src/tirx/transform/flatten_buffer.cc
+++ b/src/tirx/transform/flatten_buffer.cc
@@ -248,6 +248,21 @@ class BufferFlattener : public
arith::IRMutatorWithAnalyzer {
}
Expr VisitExpr_(const CallNode* op) final {
+ if (op->op.same_as(builtin::masked_load()) ||
op->op.same_as(builtin::masked_store())) {
+ bool is_load = op->op.same_as(builtin::masked_load());
+ BufferVar original(op->args[0].as_or_throw<Var>());
+ ffi::Array<PrimExpr> indices;
+ for (size_t i = is_load ? 1 : 2; i + 1 < op->args.size(); ++i) {
+
indices.push_back(this->VisitPrimExpr(op->args[i].as_or_throw<PrimExpr>()));
+ }
+ buffers_used_.insert(original);
+ const FlatInfo& info = Lookup(original);
+ ffi::Array<Expr> args{info.flattened.var()};
+ if (!is_load) args.push_back(this->VisitExpr(op->args[1]));
+ for (const PrimExpr& index : FoldIndices(info, indices))
args.push_back(index);
+ args.push_back(this->VisitExpr(op->args.back()));
+ return Call(op->ty, op->op, args, op->attrs, op->ty_args, op->span);
+ }
if (op->op.same_as(builtin::buffer_data()) && op->args.size() == 1) {
if (auto var = op->args[0].as<Var>()) {
if (var.value()->ty.as<BufferTypeNode>()) {
diff --git a/src/tirx/transform/remove_no_op.cc
b/src/tirx/transform/remove_no_op.cc
index 9311a4e1ea..57dec79eae 100644
--- a/src/tirx/transform/remove_no_op.cc
+++ b/src/tirx/transform/remove_no_op.cc
@@ -193,8 +193,7 @@ class NoOpRemover : public arith::IRMutatorWithAnalyzer {
// A write whose destination is known to already contain the
// values to be written is a no-op.
- PrimExpr stores_existing_value =
- store->value - BufferLoad(store->buffer, store->indices,
store->predicate) == 0;
+ PrimExpr stores_existing_value = store->value - BufferLoad(store->buffer,
store->indices) == 0;
stores_existing_value = analyzer_->Simplify(stores_existing_value);
if (is_one(stores_existing_value)) {
return only_side_effects();
diff --git a/src/tirx/transform/storage_rewrite.cc
b/src/tirx/transform/storage_rewrite.cc
index 00116b968f..5d5c0bfe9d 100644
--- a/src/tirx/transform/storage_rewrite.cc
+++ b/src/tirx/transform/storage_rewrite.cc
@@ -145,20 +145,7 @@ class LinearAccessPatternFinder final : public
StmtExprVisitor {
scope_.push_back(StmtEntry());
// visit subexpr
StmtExprVisitor::VisitStmt_(op);
- // Add write access.
- const VarNode* buffer_var =
- buffer_aliases_.Get(op->buffer.var()).value_or(op->buffer.var()).get();
- auto it = alloc_info_.find(buffer_var);
- if (it != alloc_info_.end() && it->second.alloc) {
- TVM_FFI_ICHECK_LT(it->second.level, scope_.size());
- scope_[it->second.level].touched.push_back(buffer_var);
-
- TVM_FFI_ICHECK_EQ(1, it->second.num_physical_dimensions)
- << "BufferVar " << op->buffer.name() << " is allocated with "
- << it->second.num_physical_dimensions
- << " physical dimensions, but is accessed as having "
- << "1 physical dimension" << std::endl;
- }
+ RecordAccess(op->buffer);
StmtEntry e = scope_.back();
scope_.pop_back();
if (e.touched.size() != 0) {
@@ -168,23 +155,8 @@ class LinearAccessPatternFinder final : public
StmtExprVisitor {
}
void VisitExpr_(const BufferLoadNode* op) final {
- // Add write access.
StmtExprVisitor::VisitExpr_(op);
-
- const VarNode* buffer_var =
- buffer_aliases_.Get(op->buffer.var()).value_or(op->buffer.var()).get();
- auto it = alloc_info_.find(buffer_var);
- if (it != alloc_info_.end() && it->second.alloc) {
- TVM_FFI_ICHECK_LT(it->second.level, scope_.size())
- << "Load memory in places other than store.";
- scope_[it->second.level].touched.push_back(buffer_var);
-
- TVM_FFI_ICHECK_EQ(1, it->second.num_physical_dimensions)
- << "BufferVar " << op->buffer.name() << " is allocated with "
- << it->second.num_physical_dimensions
- << " physical dimensions, but is accessed as having "
- << "1 physical dimension" << std::endl;
- }
+ RecordAccess(op->buffer);
}
void VisitStmt_(const EvaluateNode* op) final {
@@ -279,6 +251,19 @@ class LinearAccessPatternFinder final : public
StmtExprVisitor {
}
}
+ void RecordAccess(const BufferVar& buffer) {
+ const VarNode* buffer_var =
buffer_aliases_.Get(buffer.var()).value_or(buffer.var()).get();
+ auto it = alloc_info_.find(buffer_var);
+ if (it == alloc_info_.end() || !it->second.alloc) return;
+ TVM_FFI_ICHECK_LT(it->second.level, scope_.size())
+ << "Buffer access occurs outside a statement scope.";
+ scope_[it->second.level].touched.push_back(buffer_var);
+ TVM_FFI_ICHECK_EQ(1, it->second.num_physical_dimensions)
+ << "BufferVar " << buffer.name() << " is allocated with "
+ << it->second.num_physical_dimensions << " physical dimensions, but is
accessed as having "
+ << "1 physical dimension" << std::endl;
+ }
+
// linearized access sequence.
std::vector<StmtEntry> linear_seq_;
// The storage scope of each buffer
@@ -549,7 +534,31 @@ class StoragePlanRewriter : public StmtExprMutator {
}
}
Expr VisitExpr_(const CallNode* op) final {
- if (op->op.same_as(builtin::tvm_access_ptr())) {
+ if (op->op.same_as(builtin::masked_load()) ||
op->op.same_as(builtin::masked_store())) {
+ bool is_load = op->op.same_as(builtin::masked_load());
+ BufferVar buffer(op->args[0].as_or_throw<Var>());
+ PrimExpr value;
+ if (!is_load) value =
this->VisitPrimExpr(op->args[1].as_or_throw<PrimExpr>());
+ ffi::Array<PrimExpr> indices;
+ for (size_t i = is_load ? 1 : 2; i + 1 < op->args.size(); ++i) {
+
indices.push_back(this->VisitPrimExpr(op->args[i].as_or_throw<PrimExpr>()));
+ }
+ if (is_load) {
+ BufferLoad access(buffer, indices, op->span);
+ access = VisitBufferAccess(std::move(access));
+ ffi::Array<Expr> args{access->buffer.var()};
+ for (const PrimExpr& index : access->indices) args.push_back(index);
+ args.push_back(this->VisitExpr(op->args.back()));
+ return Call(access->ty, op->op, args, op->attrs, op->ty_args,
op->span);
+ } else {
+ BufferStore access(buffer, value, indices, op->span);
+ access = VisitBufferAccess(std::move(access));
+ ffi::Array<Expr> args{access->buffer.var(), access->value};
+ for (const PrimExpr& index : access->indices) args.push_back(index);
+ args.push_back(this->VisitExpr(op->args.back()));
+ return Call(PrimType::Void(), op->op, args, op->attrs, op->ty_args,
op->span);
+ }
+ } else if (op->op.same_as(builtin::tvm_access_ptr())) {
TVM_FFI_ICHECK_EQ(op->args.size(), 5U);
PrimExpr dtype_marker = op->args[0].as_or_throw<PrimExpr>();
PrimType dtype = dtype_marker.ty();
@@ -1311,7 +1320,17 @@ class VectorTypeAccessChecker : public StmtExprVisitor {
}
void VisitExpr_(const CallNode* op) final {
- if (op->op.same_as(builtin::tvm_access_ptr())) {
+ if (op->op.same_as(builtin::masked_load()) ||
op->op.same_as(builtin::masked_store())) {
+ bool is_load = op->op.same_as(builtin::masked_load());
+ BufferVar buffer(op->args[0].as_or_throw<Var>());
+ PrimType dtype =
+ is_load ? op->ty.as_or_throw<PrimType>() :
op->args[1].as_or_throw<PrimExpr>().ty();
+ ffi::Array<PrimExpr> indices;
+ for (size_t i = is_load ? 1 : 2; i + 1 < op->args.size(); ++i) {
+ indices.push_back(op->args[i].as_or_throw<PrimExpr>());
+ }
+ OnArrayAccess(dtype, buffer.get(), indices, is_load);
+ } else if (op->op.same_as(builtin::tvm_access_ptr())) {
PrimType dtype = op->args[0].as_or_throw<PrimExpr>().ty();
auto buffer_var = GetBufferDataVar(op->args[1]);
PrimExpr index = op->args[2].as_or_throw<PrimExpr>();
@@ -1699,6 +1718,42 @@ class VectorTypeRewriter : public StmtExprMutator {
return modified;
}
+ ffi::Optional<Expr> RewriteMaskedCall(const CallNode* op) {
+ if (op->op.same_as(builtin::masked_load())) {
+ BufferVar buffer(op->args[0].as_or_throw<Var>());
+ ffi::Array<PrimExpr> indices;
+ for (size_t i = 1; i + 1 < op->args.size(); ++i) {
+
indices.push_back(this->VisitPrimExpr(op->args[i].as_or_throw<PrimExpr>()));
+ }
+ BufferLoad access(buffer, indices, op->span);
+ auto [modified, shuffle_index] = VisitBufferAccess(access);
+ TVM_FFI_ICHECK_LT(shuffle_index, 0)
+ << "A masked vector load cannot be rewritten into a scalar shuffle.";
+ if (!modified.same_as(access)) modified.CopyOnWrite()->LegalizeDType();
+ ffi::Array<Expr> args{modified->buffer.var()};
+ for (const PrimExpr& index : modified->indices) args.push_back(index);
+ args.push_back(this->VisitExpr(op->args.back()));
+ return Call(modified->ty, op->op, args, op->attrs, op->ty_args,
op->span);
+ }
+ if (op->op.same_as(builtin::masked_store())) {
+ BufferVar buffer(op->args[0].as_or_throw<Var>());
+ PrimExpr value =
this->VisitPrimExpr(op->args[1].as_or_throw<PrimExpr>());
+ ffi::Array<PrimExpr> indices;
+ for (size_t i = 2; i + 1 < op->args.size(); ++i) {
+
indices.push_back(this->VisitPrimExpr(op->args[i].as_or_throw<PrimExpr>()));
+ }
+ BufferStore access(buffer, value, indices, op->span);
+ auto [modified, shuffle_index] = VisitBufferAccess(std::move(access));
+ TVM_FFI_ICHECK_LT(shuffle_index, 0)
+ << "A masked vector store cannot be rewritten into a scalar
shuffle.";
+ ffi::Array<Expr> args{modified->buffer.var(), modified->value};
+ for (const PrimExpr& index : modified->indices) args.push_back(index);
+ args.push_back(this->VisitExpr(op->args.back()));
+ return Call(PrimType::Void(), op->op, args, op->attrs, op->ty_args,
op->span);
+ }
+ return std::nullopt;
+ }
+
Stmt VisitStmt_(const BindNode* op) final {
auto it = rewrite_map_.find(op->var.get());
Expr value = this->VisitExpr(op->value);
@@ -1764,6 +1819,9 @@ class VectorTypeRewriter : public StmtExprMutator {
}
Expr VisitExpr_(const CallNode* op) final {
+ if (auto rewritten = RewriteMaskedCall(op)) {
+ return rewritten.value();
+ }
if (op->op.same_as(builtin::buffer_data()) && op->args.size() == 1) {
if (auto var = op->args[0].as<Var>();
var.has_value() && var.value()->ty.as<BufferTypeNode>()) {
diff --git a/src/tirx/transform/unsupported_dtype_legalize.cc
b/src/tirx/transform/unsupported_dtype_legalize.cc
index c9ba1736b0..815db4a864 100644
--- a/src/tirx/transform/unsupported_dtype_legalize.cc
+++ b/src/tirx/transform/unsupported_dtype_legalize.cc
@@ -140,7 +140,9 @@ class ComputeLegalizePlanner : public StmtExprVisitor {
void VisitExpr_(const VarNode* op) final {
StmtExprVisitor::VisitExpr_(op);
Var buffer_var = ffi::GetRef<Var>(op);
- if (buffer_var->ty.as<PointerTypeNode>()) {
+ if (buffer_var->ty.as<BufferTypeNode>()) {
+ this->PopulateBufferRemap(BufferVar(buffer_var));
+ } else if (buffer_var->ty.as<PointerTypeNode>()) {
opaque_var_access_.insert(buffer_var);
}
}
@@ -265,6 +267,39 @@ class ComputeLegalizer : public StmtExprMutator {
}
Expr VisitExpr_(const CallNode* op) final {
+ if (op->op.same_as(builtin::masked_load()) ||
op->op.same_as(builtin::masked_store())) {
+ bool is_load = op->op.same_as(builtin::masked_load());
+ BufferVar original(op->args[0].as_or_throw<Var>());
+ BufferVar buffer = GetRemappedBuffer(original);
+ ffi::Array<Expr> args{buffer.var()};
+ PrimExpr value;
+ if (!is_load) {
+ value = this->VisitPrimExpr(op->args[1].as_or_throw<PrimExpr>());
+ }
+ ffi::Array<PrimExpr> indices;
+ for (size_t i = is_load ? 1 : 2; i + 1 < op->args.size(); ++i) {
+
indices.push_back(this->VisitPrimExpr(op->args[i].as_or_throw<PrimExpr>()));
+ }
+ PrimExpr predicate =
this->VisitPrimExpr(op->args.back().as_or_throw<PrimExpr>());
+ if (is_load) {
+ for (const PrimExpr& index : indices) args.push_back(index);
+ args.push_back(predicate);
+ Type type = BufferLoad(buffer, indices).ty();
+ return Call(type, op->op, args, op->attrs, op->ty_args, op->span);
+ }
+ if (MatchType(buffer->dtype)) {
+ value = CastTargetToDType(value, BufferLoad(buffer, indices).ty());
+ }
+ PrimType storage_dtype = BufferLoad(buffer, indices).ty();
+ if (value.ty() != storage_dtype) {
+ TVM_FFI_ICHECK(MatchType(value.ty()));
+ value = DTypeConversion(value, storage_dtype);
+ }
+ args.push_back(value);
+ for (const PrimExpr& index : indices) args.push_back(index);
+ args.push_back(predicate);
+ return Call(PrimType::Void(), op->op, args, op->attrs, op->ty_args,
op->span);
+ }
if (!op->ty.as<PrimTypeNode>()) {
return StmtExprMutator::VisitExpr_(op);
}
@@ -365,30 +400,22 @@ class ComputeLegalizer : public StmtExprMutator {
auto fmutate = [this](const PrimExpr& e) { return this->VisitPrimExpr(e);
};
ffi::Array<PrimExpr> indices = op->indices.Map(fmutate);
- ffi::Optional<PrimExpr> predicate = std::nullopt;
- if (op->predicate.has_value()) {
- predicate = this->VisitPrimExpr(op->predicate.value());
- }
-
BufferVar new_buf = GetRemappedBuffer(op->buffer);
- if (value.same_as(op->value) && indices.same_as(op->indices) &&
- predicate.same_as(op->predicate) && new_buf.same_as(op->buffer)) {
+ if (value.same_as(op->value) && indices.same_as(op->indices) &&
new_buf.same_as(op->buffer)) {
return ffi::GetRef<Stmt>(op);
} else {
if (MatchType(new_buf->dtype)) {
- int index_lanes = indices.size() ? indices.back().ty().lanes() : 1;
- int buffer_lanes = new_buf->dtype.lanes();
- PrimType legalized_dtype = new_buf->dtype.WithLanes(index_lanes *
buffer_lanes);
- value = CastTargetToDType(value, legalized_dtype);
+ value = CastTargetToDType(value, BufferLoad(new_buf, indices).ty());
}
- if (value.ty() != new_buf->dtype) {
+ PrimType storage_dtype = BufferLoad(new_buf, indices).ty();
+ if (value.ty() != storage_dtype) {
// this happens when buffer get rewritten to f32
// but values remain as fp8/bf16
TVM_FFI_ICHECK(MatchType(value.ty()));
- value = DTypeConversion(value,
new_buf->dtype.WithLanes(value.ty().lanes()));
+ value = DTypeConversion(value, storage_dtype);
}
- return BufferStore(new_buf, value, indices, predicate);
+ return BufferStore(new_buf, value, indices);
}
}
@@ -477,7 +504,7 @@ class ComputeLegalizer : public StmtExprMutator {
if (new_buf.same_as(op->buffer)) {
return ret;
} else {
- return BufferLoad(new_buf, op->indices, op->predicate);
+ return BufferLoad(new_buf, op->indices);
}
}
@@ -648,18 +675,13 @@ class StorageLegalizer : public StmtExprMutator {
PrimExpr value = this->ChangeToUInt(VisitPrimExpr(op->value));
BufferVar new_buf = GetRemappedBuffer(op->buffer);
auto indices = op->indices.Map([this](PrimExpr expr) { return
this->VisitPrimExpr(expr); });
- ffi::Optional<PrimExpr> predicate = std::nullopt;
- if (op->predicate.has_value()) {
- predicate = this->VisitPrimExpr(op->predicate.value());
- }
- if (new_buf.same_as(op->buffer) && indices.same_as(op->indices) &&
- predicate.same_as(op->predicate) && value.same_as(op->value)) {
+ if (new_buf.same_as(op->buffer) && indices.same_as(op->indices) &&
value.same_as(op->value)) {
return ffi::GetRef<Stmt>(op);
} else {
if (MatchType(op->value.ty())) {
TVM_FFI_ICHECK(new_buf->dtype.MatchesCode(DLDataTypeCode::kDLUInt));
}
- return BufferStore(new_buf, value, indices, predicate);
+ return BufferStore(new_buf, value, indices);
}
}
@@ -688,11 +710,38 @@ class StorageLegalizer : public StmtExprMutator {
if (new_buf.same_as(op->buffer)) {
return ret;
} else {
- return BufferLoad(new_buf, op->indices, op->predicate);
+ return BufferLoad(new_buf, op->indices);
}
}
Expr VisitExpr_(const CallNode* op) final {
+ if (op->op.same_as(builtin::masked_load()) ||
op->op.same_as(builtin::masked_store())) {
+ bool is_load = op->op.same_as(builtin::masked_load());
+ BufferVar buffer =
GetRemappedBuffer(BufferVar(op->args[0].as_or_throw<Var>()));
+ ffi::Array<Expr> args{buffer.var()};
+ PrimExpr value;
+ if (!is_load) {
+ PrimExpr original_value = op->args[1].as_or_throw<PrimExpr>();
+ value = this->ChangeToUInt(this->VisitPrimExpr(original_value));
+ if (MatchType(original_value.ty())) {
+ TVM_FFI_ICHECK(buffer->dtype.MatchesCode(DLDataTypeCode::kDLUInt));
+ }
+ args.push_back(value);
+ }
+ ffi::Array<PrimExpr> indices;
+ for (size_t i = is_load ? 1 : 2; i + 1 < op->args.size(); ++i) {
+ PrimExpr index =
this->VisitPrimExpr(op->args[i].as_or_throw<PrimExpr>());
+ indices.push_back(index);
+ args.push_back(index);
+ }
+ args.push_back(this->VisitExpr(op->args.back()));
+ if (is_load) {
+ Type type = BufferLoad(buffer, indices).ty();
+ return Call(type, op->op, args, op->attrs, op->ty_args, op->span);
+ } else {
+ return Call(PrimType::Void(), op->op, args, op->attrs, op->ty_args,
op->span);
+ }
+ }
if (const auto* pointer_type = op->ty.as<PointerTypeNode>()) {
Expr ret = StmtExprMutator::VisitExpr_(op);
const auto* element_type = pointer_type->element_type.as<PrimTypeNode>();
diff --git a/src/tirx/transform/vectorize_loop.cc
b/src/tirx/transform/vectorize_loop.cc
index 0cc74d5405..715ac307e2 100644
--- a/src/tirx/transform/vectorize_loop.cc
+++ b/src/tirx/transform/vectorize_loop.cc
@@ -158,8 +158,10 @@ bool EnableBufferLevelPredication(Target target) {
* After:
* for i_0 in T.serial(4):
* predicate = T.get_active_lane_mask("uint1x4", i_0 * 4, 14)
- * A_load = T.meta_var(A.vload([T.Ramp(i_0 * 4, 1, 4)], predicate=predicate))
- * B.vstore([T.Ramp(i_0 * 4, 1, 4)], A_load, predicate=predicate)
+ * A_load = T.meta_var(T.call_intrin("float32x4", "tirx.masked_load", A,
+ * T.Ramp(i_0 * 4, 1, 4), predicate))
+ * T.evaluate(T.call_intrin("void", "tirx.masked_store", B, A_load,
+ * T.Ramp(i_0 * 4, 1, 4), predicate))
*/
class TryPredicateBufferAccesses : public StmtExprMutator {
public:
@@ -214,20 +216,39 @@ class TryPredicateBufferAccesses : public StmtExprMutator
{
return TryPredicateBufferAccess(store);
}
- template <typename AccessNode>
- AccessNode TryPredicateBufferAccess(AccessNode node) {
+ Expr VisitExpr_(const CallNode* op) final {
+ Call call = StmtExprMutator::VisitExpr_(op).as_or_throw<Call>();
+ if (!call->op.same_as(builtin::masked_load()) &&
!call->op.same_as(builtin::masked_store())) {
+ return call;
+ }
+
+ bool is_load = call->op.same_as(builtin::masked_load());
+ ffi::Array<PrimExpr> indices;
+ for (size_t i = is_load ? 1 : 2; i + 1 < call->args.size(); ++i) {
+ indices.push_back(call->args[i].as_or_throw<PrimExpr>());
+ }
+ if (auto lane_mask = GetLaneMask(indices)) {
+ PrimExpr predicate = call->args.back().as_or_throw<PrimExpr>();
+ predicate = allow_offset_predication_ ? predicate & lane_mask.value() :
lane_mask.value();
+ ffi::Array<Expr> args = call->args;
+ args.Set(args.size() - 1, predicate);
+ return Call(call->ty, call->op, args, call->attrs, call->ty_args,
call->span);
+ }
+ return call;
+ }
+
+ ffi::Optional<PrimExpr> GetLaneMask(const ffi::Array<PrimExpr>& indices) {
num_accesses_analyzed_ += 1;
// Do not try to predicate non-vectorized accesses
- ffi::Array<PrimExpr> indices = node->indices;
if (!indices.size() || !indices[0]->IsInstance<RampNode>()) {
- return node;
+ return std::nullopt;
}
- Ramp ramp = node->indices[0].template as_or_throw<Ramp>();
+ Ramp ramp = indices[0].as_or_throw<Ramp>();
if (!ffi::StructuralEqual()(ramp->stride, stride_) ||
!ffi::StructuralEqual()(ramp->lanes, lanes_)) {
- return node;
+ return std::nullopt;
}
bool same_base = ffi::StructuralEqual()(ramp->base, base_);
@@ -236,7 +257,7 @@ class TryPredicateBufferAccesses : public StmtExprMutator {
// memory base. This covers accesses such as A[offset + i] guarded by
// a predicate over i.
if (!allow_offset_predication_) {
- return node;
+ return std::nullopt;
}
}
@@ -249,15 +270,28 @@ class TryPredicateBufferAccesses : public StmtExprMutator
{
.as_or_throw<PrimExpr>();
num_accesses_rewritten_ += 1;
- auto writer = node.CopyOnWrite();
- if (node->predicate.has_value() && allow_offset_predication_) {
- // BufferVar predicates are uint1 lane masks, so mask merging uses
bitwise
- // and rather than logical &&.
- writer->predicate = node->predicate.value() & lane_mask;
- } else {
- writer->predicate = lane_mask;
+ return lane_mask;
+ }
+
+ Expr TryPredicateBufferAccess(BufferLoad load) {
+ if (auto mask = GetLaneMask(load->indices)) {
+ ffi::Array<Expr> args{load->buffer.var()};
+ for (const PrimExpr& index : load->indices) args.push_back(index);
+ args.push_back(mask.value());
+ return Call(load->ty, builtin::masked_load(), args, {}, {}, load->span);
}
- return node;
+ return load;
+ }
+
+ Stmt TryPredicateBufferAccess(BufferStore store) {
+ if (auto mask = GetLaneMask(store->indices)) {
+ ffi::Array<Expr> args{store->buffer.var(), store->value};
+ for (const PrimExpr& index : store->indices) args.push_back(index);
+ args.push_back(mask.value());
+ return Evaluate(Call(PrimType::Void(), builtin::masked_store(), args,
{}, {}, store->span),
+ store->span);
+ }
+ return store;
}
/*! \brief The variable base expr of the predicate. */
diff --git a/tests/python/codegen/test_target_codegen.py
b/tests/python/codegen/test_target_codegen.py
index b7fa32575c..16e9cf1fcc 100644
--- a/tests/python/codegen/test_target_codegen.py
+++ b/tests/python/codegen/test_target_codegen.py
@@ -30,7 +30,16 @@ def test_buffer_store_predicate_not_supported():
@T.prim_func(s_tir=True)
def func(b: T.handle):
B = T.match_buffer(b, (8,), "float32")
- B.vstore([T.Ramp(0, 2, 4)], T.Broadcast(1.0, 4),
predicate=T.Broadcast(T.bool(True), 4))
+ T.evaluate(
+ T.call_intrin(
+ "void",
+ "tirx.masked_store",
+ B,
+ T.Broadcast(1.0, 4),
+ T.Ramp(0, 2, 4),
+ T.Broadcast(T.bool(True), 4),
+ )
+ )
err_msg = "Predicated buffer store is not supported."
with pytest.raises(RuntimeError, match=err_msg):
@@ -58,8 +67,15 @@ def test_buffer_store_predicate_not_supported_gpu(target):
B = T.match_buffer(b, (6,), "float32")
T.func_attr({"global_symbol": "main"})
for i_0 in T.thread_binding(3, thread="threadIdx.x"):
- B.vstore(
- [T.Ramp(i_0, 1, 4)], T.Broadcast(1.0, 4),
predicate=T.Broadcast(T.bool(True), 4)
+ T.evaluate(
+ T.call_intrin(
+ "void",
+ "tirx.masked_store",
+ B,
+ T.Broadcast(1.0, 4),
+ T.Ramp(i_0, 1, 4),
+ T.Broadcast(T.bool(True), 4),
+ )
)
err_msg = "Predicated buffer store is not supported."
@@ -78,7 +94,13 @@ def test_buffer_load_predicate_not_supported():
for i_0 in range(4):
B.vstore(
[T.Ramp(0, 2, 4)],
- A.vload([T.Ramp(i_0, 1, 4)],
predicate=T.Broadcast(T.bool(True), 4)),
+ T.call_intrin(
+ "float32x4",
+ "tirx.masked_load",
+ A,
+ T.Ramp(i_0, 1, 4),
+ T.Broadcast(T.bool(True), 4),
+ ),
)
err_msg = "Predicated buffer load is not supported."
@@ -108,7 +130,13 @@ def test_buffer_load_predicate_not_supported_gpu(target):
for i_0 in T.thread_binding(3, thread="threadIdx.x"):
B.vstore(
[T.Ramp(0, 2, 4)],
- A.vload([T.Ramp(i_0, 1, 4)],
predicate=T.Broadcast(T.bool(True), 4)),
+ T.call_intrin(
+ "float32x4",
+ "tirx.masked_load",
+ A,
+ T.Ramp(i_0, 1, 4),
+ T.Broadcast(T.bool(True), 4),
+ ),
)
err_msg = "Predicated buffer load is not supported."
diff --git a/tests/python/codegen/test_target_codegen_llvm.py
b/tests/python/codegen/test_target_codegen_llvm.py
index 6f9f128a98..bdf617ff54 100644
--- a/tests/python/codegen/test_target_codegen_llvm.py
+++ b/tests/python/codegen/test_target_codegen_llvm.py
@@ -1255,7 +1255,13 @@ def test_invalid_volatile_masked_buffer_load():
def main(b: T.handle):
B = T.match_buffer(b, [4])
A = T.alloc_buffer((4,), annotations={"tirx.volatile": True})
- B[0:4] = A.vload([T.Ramp(0, 1, 4)],
predicate=T.Broadcast(T.bool(True), 4))
+ B[0:4] = T.call_intrin(
+ "float32x4",
+ "tirx.masked_load",
+ A,
+ T.Ramp(0, 1, 4),
+ T.Broadcast(T.bool(True), 4),
+ )
err_msg = "The masked load intrinsic does not support declaring load as
volatile."
with pytest.raises(RuntimeError, match=err_msg):
@@ -1271,7 +1277,13 @@ def test_invalid_volatile_masked_decl_buffer_load():
B = T.match_buffer(b, [4])
A = T.alloc_buffer((4,), annotations={"tirx.volatile": True})
A_alias = T.decl_buffer((4,), data=A.data)
- B[0:4] = A_alias.vload([T.Ramp(0, 1, 4)],
predicate=T.Broadcast(T.bool(True), 4))
+ B[0:4] = T.call_intrin(
+ "float32x4",
+ "tirx.masked_load",
+ A_alias,
+ T.Ramp(0, 1, 4),
+ T.Broadcast(T.bool(True), 4),
+ )
err_msg = "The masked load intrinsic does not support declaring load as
volatile."
with pytest.raises(RuntimeError, match=err_msg):
@@ -1285,10 +1297,15 @@ def test_invalid_volatile_masked_buffer_store():
@T.prim_func(s_tir=True)
def main():
A = T.alloc_buffer((4,), annotations={"tirx.volatile": True})
- A.vstore(
- [T.Ramp(0, 1, 4)],
- T.Broadcast(0.0, 4),
- predicate=T.Broadcast(T.bool(True), 4),
+ T.evaluate(
+ T.call_intrin(
+ "void",
+ "tirx.masked_store",
+ A,
+ T.Broadcast(0.0, 4),
+ T.Ramp(0, 1, 4),
+ T.Broadcast(T.bool(True), 4),
+ )
)
err_msg = "The masked store intrinsic does not support declaring store as
volatile."
diff --git a/tests/python/s_tir/analysis/test_sblock_access_region.py
b/tests/python/s_tir/analysis/test_sblock_access_region.py
index 15a84fae30..b9266be09a 100644
--- a/tests/python/s_tir/analysis/test_sblock_access_region.py
+++ b/tests/python/s_tir/analysis/test_sblock_access_region.py
@@ -45,6 +45,16 @@ def func() -> None:
T.evaluate(D.data)
[email protected]_func(s_tir=True)
+def masked_access_func() -> None:
+ A = T.sblock_alloc_buffer((16,), "float32")
+ B = T.sblock_alloc_buffer((16,), "float32")
+ with T.sblock():
+ mask = T.meta_var(T.Broadcast(T.bool(True), 4))
+ value = T.meta_var(T.masked_load("float32x4", A, T.Ramp(4, 1, 4),
mask))
+ T.masked_store(B, value, T.Ramp(8, 1, 4), mask)
+
+
@T.prim_func(s_tir=True)
def match_buffer_func() -> None:
with T.sblock("root"):
@@ -232,6 +242,18 @@ def test_block_access_region_detector():
)
+def test_masked_access_is_not_opaque():
+ root = masked_access_func.body.block
+ block = root.body.block
+ A, B = root.alloc_buffers
+ reads, writes, opaque = s_tir.analysis.get_sblock_access_region(block, {A:
A, B: B})
+ tvm.ir.assert_structural_equal(reads, [tvm.tirx.BufferRegion(A,
[Range.from_min_extent(4, 4)])])
+ tvm.ir.assert_structural_equal(
+ writes, [tvm.tirx.BufferRegion(B, [Range.from_min_extent(8, 4)])]
+ )
+ tvm.ir.assert_structural_equal(opaque, [])
+
+
def test_opaque_block():
alloc_buffers = opaque_block_func.body.block.alloc_buffers
buffer_var_map = {buf: buf for buf in alloc_buffers}
diff --git
a/tests/python/s_tir/transform/test_s_tir_transform_inject_virtual_thread.py
b/tests/python/s_tir/transform/test_s_tir_transform_inject_virtual_thread.py
index 5eaf1f5d37..6ba06251c9 100644
--- a/tests/python/s_tir/transform/test_s_tir_transform_inject_virtual_thread.py
+++ b/tests/python/s_tir/transform/test_s_tir_transform_inject_virtual_thread.py
@@ -213,5 +213,40 @@ def test_vthread_vectorized():
assert allocate_node.buffer.ty.dtype == "int32x4"
+def test_vthread_rewrites_masked_accesses():
+ @T.prim_func(s_tir=True)
+ def before_func():
+ vthread = T.env_thread("vthread")
+ T.launch_thread(vthread, 2)
+ B = T.alloc_buffer((4,), "float32", scope="shared")
+ mask = T.meta_var(T.Broadcast(T.bool(True), 4))
+ loaded = T.meta_var(T.masked_load("float32x4", B, T.Ramp(0, 1, 4),
mask))
+ value = T.meta_var(loaded + T.Broadcast(T.Cast("float32", vthread), 4))
+ T.masked_store(B, value, T.Ramp(0, 1, 4), mask)
+
+ after = tvm.s_tir.transform.InjectVirtualThread()(
+ tvm.IRModule.from_expr(before_func.with_attr("global_symbol", "main"))
+ )["main"]
+ masked_calls = []
+
+ def visitor(node):
+ if isinstance(node, tvm.ir.Call) and node.op.name in {
+ "tirx.masked_load",
+ "tirx.masked_store",
+ }:
+ masked_calls.append(node)
+
+ tvm.tirx.stmt_functor.post_order_visit(after.body, visitor)
+ assert len(masked_calls) == 4
+ assert all(list(call.args[0].ty.shape) == [8] for call in masked_calls)
+ analyzer = tvm.arith.Analyzer()
+ assert sorted(int(analyzer.simplify(call.args[-2].base)) for call in
masked_calls) == [
+ 0,
+ 0,
+ 4,
+ 4,
+ ]
+
+
if __name__ == "__main__":
tvm.testing.main()
diff --git
a/tests/python/s_tir/transform/test_s_tir_transform_lower_match_buffer.py
b/tests/python/s_tir/transform/test_s_tir_transform_lower_match_buffer.py
index 601931ab52..2f5184c69f 100644
--- a/tests/python/s_tir/transform/test_s_tir_transform_lower_match_buffer.py
+++ b/tests/python/s_tir/transform/test_s_tir_transform_lower_match_buffer.py
@@ -587,5 +587,21 @@ def test_scalar_match_buffer_type_coercion():
_check(scalar_match_buffer_type_coercion,
transformed_scalar_match_buffer_type_coercion)
[email protected]_func(s_tir=True)
+def masked_match_buffer(a: T.handle) -> None:
+ A = T.match_buffer(a, (8,), "float32")
+ with T.sblock():
+ T.reads(A[2:6])
+ sub_A = T.match_buffer(A[2:6], (4,), offset_factor=1)
+ mask = T.meta_var(T.Broadcast(T.bool(True), 4))
+ T.evaluate(T.masked_load("float32x4", sub_A, T.Ramp(0, 1, 4), mask))
+
+
+def test_masked_match_buffer_fails_explicitly():
+ mod = tvm.IRModule.from_expr(masked_match_buffer)
+ with pytest.raises(RuntimeError, match="Predicated buffer access is not
currently supported"):
+ tvm.s_tir.transform.LowerMatchBuffer()(mod)
+
+
if __name__ == "__main__":
tvm.testing.main()
diff --git a/tests/python/tirx-base/test_tir_nodes.py
b/tests/python/tirx-base/test_tir_nodes.py
index 4af595a783..3f35ae5bf6 100644
--- a/tests/python/tirx-base/test_tir_nodes.py
+++ b/tests/python/tirx-base/test_tir_nodes.py
@@ -14,7 +14,7 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
-# ruff: noqa: F811, F841
+# ruff: noqa: F841
import numpy as np
import pytest
@@ -427,75 +427,6 @@ def test_buffer_store_scalable_vec():
assert store.value.ty.dtype == "int32xvscalex4"
-def test_buffer_store_predicate_invalid_scalability():
- b = tvm.tirx.decl_buffer((24,), "int32")
- value = tvm.tirx.expr.Broadcast(1, 4 * tvm.tirx.vscale())
- index = tvm.tirx.expr.Ramp(0, 1, 4 * tvm.tirx.vscale())
- predicate = tvm.tirx.expr.Broadcast(tvm.tirx.IntImm("int1", 1), 4)
-
- err_msg = "Predicate mask dtype and value dtype must both be scalable."
- with pytest.raises(RuntimeError, match=err_msg):
- tvm.tirx.BufferStore(b, value, [index], predicate)
-
-
-def test_buffer_store_predicate_invalid_lanes():
- b = tvm.tirx.decl_buffer((24,), "int32")
- value = tvm.tirx.expr.Broadcast(1, 4 * tvm.tirx.vscale())
- index = tvm.tirx.expr.Ramp(0, 1, 4 * tvm.tirx.vscale())
- predicate = tvm.tirx.expr.Broadcast(tvm.tirx.IntImm("int1", 1), 8 *
tvm.tirx.vscale())
-
- err_msg = (
- "Got a predicate mask with 8 lanes, but trying to store a "
- "value with 4 lanes. The number of lanes must match."
- )
- with pytest.raises(RuntimeError, match=err_msg):
- tvm.tirx.BufferStore(b, value, [index], predicate)
-
-
-def test_buffer_store_predicate_elements_invalid_type():
- b = tvm.tirx.decl_buffer((24,), "int32")
- value = tvm.tirx.expr.Broadcast(1, 4 * tvm.tirx.vscale())
- index = tvm.tirx.expr.Ramp(0, 1, 4 * tvm.tirx.vscale())
- predicate = tvm.tirx.expr.Broadcast(1, 4 * tvm.tirx.vscale())
-
- err_msg = "Predicate mask elements must be boolean values, but got int32."
- with pytest.raises(RuntimeError, match=err_msg):
- tvm.tirx.BufferStore(b, value, [index], predicate)
-
-
-def test_buffer_load_predicate_elements_invalid_type():
- b = tvm.tirx.decl_buffer((24,), "int32")
- index = tvm.tirx.expr.Ramp(0, 1, 4 * tvm.tirx.vscale())
- predicate = tvm.tirx.expr.Broadcast(1, 4 * tvm.tirx.vscale())
-
- err_msg = "Predicate mask elements must be boolean values, but got int32."
- with pytest.raises(RuntimeError, match=err_msg):
- tvm.tirx.BufferLoad(b, [index], predicate)
-
-
-def test_buffer_store_predicate_invalid_scalability():
- b = tvm.tirx.decl_buffer((24,), "int32")
- index = tvm.tirx.expr.Ramp(0, 1, 4 * tvm.tirx.vscale())
- predicate = tvm.tirx.expr.Broadcast(tvm.tirx.IntImm("int1", 1), 4)
-
- err_msg = "Predicate mask dtype and load indices must both be scalable."
- with pytest.raises(RuntimeError, match=err_msg):
- tvm.tirx.BufferLoad(b, [index], predicate)
-
-
-def test_buffer_store_predicate_invalid_lanes():
- b = tvm.tirx.decl_buffer((24,), "int32")
- index = tvm.tirx.expr.Ramp(0, 1, 4 * tvm.tirx.vscale())
- predicate = tvm.tirx.expr.Broadcast(tvm.tirx.IntImm("int1", 1), 8 *
tvm.tirx.vscale())
-
- err_msg = (
- "Got a predicate mask with 8 lanes, but trying to load a "
- "vector with 4 lanes. The number of lanes must match."
- )
- with pytest.raises(RuntimeError, match=err_msg):
- tvm.tirx.BufferLoad(b, [index], predicate)
-
-
def test_scalable_vec_cast():
b = tvm.tirx.decl_buffer((24,), "float32")
value = tvm.tirx.expr.Broadcast(1, 12 *
tvm.tirx.vscale()).astype("float32xvscalex12")
diff --git a/tests/python/tirx-transform/test_tir_transform_bf16_legalize.py
b/tests/python/tirx-transform/test_tir_transform_bf16_legalize.py
index a75b87ec56..ce14378598 100644
--- a/tests/python/tirx-transform/test_tir_transform_bf16_legalize.py
+++ b/tests/python/tirx-transform/test_tir_transform_bf16_legalize.py
@@ -106,6 +106,86 @@ def test_bf16_simple_store_will_legalize():
tvm.ir.assert_structural_equal(after_storage,
BindTarget(target)(after_storage_legalize()))
+def test_bf16_masked_load_store_will_legalize():
+ def get_before():
+ @tvm.script.ir_module
+ class Before:
+ @T.prim_func(s_tir=True)
+ def main(Aptr: T.handle("bfloat16"), Cptr: T.handle("bfloat16")):
+ T.func_attr({"global_symbol": "main"})
+ A = T.decl_buffer((16,), "bfloat16", data=Aptr)
+ B = T.decl_buffer((16,), "bfloat16")
+ C = T.decl_buffer((16,), "bfloat16", data=Cptr)
+ mask = T.Broadcast(T.bool(True), 4)
+ T.evaluate(
+ T.call_intrin(
+ "void",
+ "tirx.masked_store",
+ B,
+ T.call_intrin("bfloat16x4", "tirx.masked_load", A,
T.Ramp(0, 1, 4), mask),
+ T.Ramp(0, 1, 4),
+ mask,
+ )
+ )
+ T.evaluate(
+ T.call_intrin(
+ "void",
+ "tirx.masked_store",
+ C,
+ T.call_intrin("bfloat16x4", "tirx.masked_load", B,
T.Ramp(0, 1, 4), mask),
+ T.Ramp(0, 1, 4),
+ mask,
+ )
+ )
+
+ return Before
+
+ target = Target("nvidia/geforce-rtx-2080-ti")
+ before = BindTarget(target)(get_before())
+ after_compute = tvm.tirx.transform.BF16ComputeLegalize()(before)
+ after_storage = tvm.tirx.transform.BF16StorageLegalize()(after_compute)
+
+ def collect(mod):
+ nodes = []
+ tvm.tirx.stmt_functor.post_order_visit(mod["main"].body, nodes.append)
+ buffers = {
+ node.buffer.name: str(node.buffer.dtype)
+ for node in nodes
+ if isinstance(node, tvm.tirx.DeclBuffer | tvm.tirx.AllocBuffer)
+ }
+ masked_loads = [
+ node
+ for node in nodes
+ if isinstance(node, tvm.ir.Call) and node.op.name ==
"tirx.masked_load"
+ ]
+ masked_stores = [
+ node
+ for node in nodes
+ if isinstance(node, tvm.ir.Call) and node.op.name ==
"tirx.masked_store"
+ ]
+ return buffers, masked_loads, masked_stores
+
+ compute_buffers, compute_loads, compute_stores = collect(after_compute)
+ assert compute_buffers == {"A": "bfloat16", "B": "float32", "C":
"bfloat16", "mask": "boolx4"}
+ assert sorted(str(load.ty) for load in compute_loads) == ["bfloat16x4",
"float32x4"]
+ assert sorted(str(store.args[1].ty) for store in compute_stores) == [
+ "bfloat16x4",
+ "float32x4",
+ ]
+
+ storage_buffers, storage_loads, storage_stores = collect(after_storage)
+ assert storage_buffers == {"A": "uint16", "B": "float32", "C": "uint16",
"mask": "boolx4"}
+ assert sorted(str(load.ty) for load in storage_loads) == [
+ "float32x4",
+ "float32x4",
+ "uint16x4",
+ ]
+ assert sorted(str(store.args[1].ty) for store in storage_stores) == [
+ "float32x4",
+ "uint16x4",
+ ]
+
+
def test_bf16_storage_compute_scope_will_legalize():
def get_before():
@tvm.script.ir_module
diff --git a/tests/python/tirx-transform/test_tir_transform_vectorize.py
b/tests/python/tirx-transform/test_tir_transform_vectorize.py
index 140655dbd5..94620ce7f5 100644
--- a/tests/python/tirx-transform/test_tir_transform_vectorize.py
+++ b/tests/python/tirx-transform/test_tir_transform_vectorize.py
@@ -193,10 +193,15 @@ def test_vectorize_if_scalable_extent():
T.float32(1), extent
)
else:
- A.vstore(
- [T.Ramp(0, 1, T.vscale() * 4)],
- T.Broadcast(T.float32(2), T.vscale() * 4),
- predicate=T.get_active_lane_mask("uint1xvscalex4", 0, n),
+ T.evaluate(
+ T.call_intrin(
+ "void",
+ "tirx.masked_store",
+ A,
+ T.Broadcast(T.float32(2), T.vscale() * 4),
+ T.Ramp(0, 1, T.vscale() * 4),
+ T.get_active_lane_mask("uint1xvscalex4", 0, n),
+ )
)
with tvm.target.Target(target):
@@ -537,16 +542,24 @@ def
test_vectorize_and_predicate_all_buffer_loads_stores():
T.func_attr({"global_symbol": "main", "tirx.noalias": True})
for i_0 in range(4):
load_a = T.meta_var(
- A.vload(
- [T.Ramp(i_0 * 4, 1, 4)],
- predicate=T.get_active_lane_mask("uint1x4", i_0 * 4, 14),
+ T.call_intrin(
+ "float32x4",
+ "tirx.masked_load",
+ A,
+ T.Ramp(i_0 * 4, 1, 4),
+ T.get_active_lane_mask("uint1x4", i_0 * 4, 14),
)
)
add_1 = T.meta_var(load_a + T.Broadcast(T.float32(1), 4))
- B.vstore(
- [T.Ramp(i_0 * 4, 1, 4)],
- add_1,
- predicate=T.get_active_lane_mask("uint1x4", i_0 * 4, 14),
+ T.evaluate(
+ T.call_intrin(
+ "void",
+ "tirx.masked_store",
+ B,
+ add_1,
+ T.Ramp(i_0 * 4, 1, 4),
+ T.get_active_lane_mask("uint1x4", i_0 * 4, 14),
+ )
)
mod = tvm.IRModule.from_expr(before)
@@ -601,15 +614,25 @@ def
test_vectorize_and_predicate_multiple_access_statements():
B = T.match_buffer(b, (16,), "float32")
T.func_attr({"global_symbol": "main", "tirx.noalias": True})
for i_0 in range(4):
- A.vstore(
- [T.Ramp(i_0 * 4, 1, 4)],
- T.Broadcast(T.float32(2), 4),
- predicate=T.get_active_lane_mask("uint1x4", i_0 * 4, 14),
+ T.evaluate(
+ T.call_intrin(
+ "void",
+ "tirx.masked_store",
+ A,
+ T.Broadcast(T.float32(2), 4),
+ T.Ramp(i_0 * 4, 1, 4),
+ T.get_active_lane_mask("uint1x4", i_0 * 4, 14),
+ )
)
- B.vstore(
- [T.Ramp(i_0 * 4, 1, 4)],
- T.Broadcast(T.float32(1), 4),
- predicate=T.get_active_lane_mask("uint1x4", i_0 * 4, 14),
+ T.evaluate(
+ T.call_intrin(
+ "void",
+ "tirx.masked_store",
+ B,
+ T.Broadcast(T.float32(1), 4),
+ T.Ramp(i_0 * 4, 1, 4),
+ T.get_active_lane_mask("uint1x4", i_0 * 4, 14),
+ )
)
before_mod = tvm.IRModule.from_expr(before)
@@ -618,6 +641,37 @@ def
test_vectorize_and_predicate_multiple_access_statements():
tvm.ir.assert_structural_equal(after, expected)
+def test_vectorize_nested_predicates_preserve_both_masks():
+ rvv_target = tvm.target.Target(
+ {"kind": "llvm", "mtriple": "riscv64-unknown-linux-gnu", "mattr":
["+v"]}
+ )
+
+ @T.prim_func(s_tir=True)
+ def before(A: T.Buffer((16,), "float32"), B: T.Buffer((16,), "float32")):
+ for i_0 in T.serial(4):
+ for i_1 in T.vectorized(4):
+ if i_0 * 4 + i_1 < 15:
+ if i_0 * 4 + i_1 < 14:
+ A[i_0 * 4 + i_1] = T.float32(1)
+ B[i_0 * 4 + i_1] = T.float32(2)
+
+ with tvm.target.Target(rvv_target):
+ after =
tvm.tirx.transform.VectorizeLoop()(tvm.IRModule.from_expr(before))["before"]
+
+ predicates = []
+
+ def collect_predicates(node):
+ if isinstance(node, tvm.ir.Call) and node.op.name ==
"tirx.masked_store":
+ predicates.append(node.args[-1])
+
+ tvm.tirx.stmt_functor.post_order_visit(after.body, collect_predicates)
+ assert len(predicates) == 2
+ assert any(
+ isinstance(predicate, tvm.ir.Call) and predicate.op.name ==
"tirx.bitwise_and"
+ for predicate in predicates
+ )
+
+
def test_vectorize_and_predicate_invalid_conditions():
@T.prim_func(s_tir=True)
def before(a: T.handle, b: T.handle):
@@ -703,16 +757,24 @@ def
test_vectorize_and_predicate_buffer_load_stores_with_sve_func_attr_target():
T.func_attr({"global_symbol": "main", "tirx.noalias": True, "target":
sve_target})
for i_0 in range(4):
load_a = T.meta_var(
- A.vload(
- [T.Ramp(i_0 * 4, 1, 4)],
- predicate=T.get_active_lane_mask("uint1x4", i_0 * 4, 14),
+ T.call_intrin(
+ "float32x4",
+ "tirx.masked_load",
+ A,
+ T.Ramp(i_0 * 4, 1, 4),
+ T.get_active_lane_mask("uint1x4", i_0 * 4, 14),
)
)
add_1 = T.meta_var(load_a + T.Broadcast(T.float32(1), 4))
- B.vstore(
- [T.Ramp(i_0 * 4, 1, 4)],
- add_1,
- predicate=T.get_active_lane_mask("uint1x4", i_0 * 4, 14),
+ T.evaluate(
+ T.call_intrin(
+ "void",
+ "tirx.masked_store",
+ B,
+ add_1,
+ T.Ramp(i_0 * 4, 1, 4),
+ T.get_active_lane_mask("uint1x4", i_0 * 4, 14),
+ )
)
mod = tvm.IRModule.from_expr(before)
@@ -740,16 +802,24 @@ def
test_vectorize_and_predicate_buffer_load_stores_with_sve_attr_scope_target()
with T.attr(sve_target, "target", 0):
for i_0 in range(4):
load_a = T.meta_var(
- A.vload(
- [T.Ramp(i_0 * 4, 1, 4)],
- predicate=T.get_active_lane_mask("uint1x4", i_0 * 4,
14),
+ T.call_intrin(
+ "float32x4",
+ "tirx.masked_load",
+ A,
+ T.Ramp(i_0 * 4, 1, 4),
+ T.get_active_lane_mask("uint1x4", i_0 * 4, 14),
)
)
add_1 = T.meta_var(load_a + T.Broadcast(T.float32(1), 4))
- B.vstore(
- [T.Ramp(i_0 * 4, 1, 4)],
- add_1,
- predicate=T.get_active_lane_mask("uint1x4", i_0 * 4, 14),
+ T.evaluate(
+ T.call_intrin(
+ "void",
+ "tirx.masked_store",
+ B,
+ add_1,
+ T.Ramp(i_0 * 4, 1, 4),
+ T.get_active_lane_mask("uint1x4", i_0 * 4, 14),
+ )
)
mod = tvm.IRModule.from_expr(before)
diff --git a/tests/python/tirx/transform/test_stmt_functor.py
b/tests/python/tirx/transform/test_stmt_functor.py
index bf845c6516..889a536a6b 100644
--- a/tests/python/tirx/transform/test_stmt_functor.py
+++ b/tests/python/tirx/transform/test_stmt_functor.py
@@ -1217,7 +1217,7 @@ def test_op_call_pointer_config_visited_and_mutated():
def visit_buffer_load_(self, op):
new_op = super().visit_buffer_load_(op)
if op.buffer.same_as(mbar_buffer):
- return tir.BufferLoad(replacement, new_op.indices,
new_op.predicate)
+ return tir.BufferLoad(replacement, new_op.indices)
return new_op
updated = ReplaceMbarLoad().visit_stmt(op_call)
diff --git a/tests/python/tvmscript/test_tvmscript_ir_builder_tir.py
b/tests/python/tvmscript/test_tvmscript_ir_builder_tir.py
index dd02640f30..1927e07e34 100644
--- a/tests/python/tvmscript/test_tvmscript_ir_builder_tir.py
+++ b/tests/python/tvmscript/test_tvmscript_ir_builder_tir.py
@@ -446,20 +446,6 @@ def test_ir_builder_tir_buffer_store_scalable_vec():
assert_structural_equal(ir_actual, ir_expected, map_free_vars=True)
-def test_ir_builder_tir_buffer_store_predicate():
- buffer_a = T.Buffer((30,), "float32")
- value = T.broadcast(0.11, T.vscale() * 4)
- index = T.ramp(0, 1, T.vscale() * 4)
- predicate = T.broadcast(T.bool(True), T.vscale() * 4)
-
- with IRBuilder() as ib:
- T.buffer_store(buffer_a, value, [index], predicate)
-
- ir_actual = ib.get()
- ir_expected = tirx.BufferStore(buffer_a, value, [index], predicate)
- assert_structural_equal(ir_actual, ir_expected, map_free_vars=True)
-
-
def test_ir_builder_tir_evaluate():
with IRBuilder() as ib:
T.evaluate(0)
diff --git a/tests/python/tvmscript/test_tvmscript_printer_tir.py
b/tests/python/tvmscript/test_tvmscript_printer_tir.py
index 0cd538fb65..0ecadaba67 100644
--- a/tests/python/tvmscript/test_tvmscript_printer_tir.py
+++ b/tests/python/tvmscript/test_tvmscript_printer_tir.py
@@ -993,8 +993,27 @@ def test_predicated_load_store():
A = T.match_buffer(a, (128, 128), "float32")
B = T.match_buffer(b, (256, 256), "float32")
T.func_attr({"global_symbol": "func"})
- a_load = T.meta_var(A.vload([0, T.Ramp(0, 4, 4)],
predicate=T.Broadcast(T.bool(False), 4)))
- A.vstore([0, T.Ramp(0, 2, 4)], a_load,
predicate=T.Broadcast(T.bool(False), 4))
+ a_load = T.meta_var(
+ T.call_intrin(
+ "float32x4",
+ "tirx.masked_load",
+ A,
+ 0,
+ T.Ramp(0, 4, 4),
+ T.Broadcast(T.bool(False), 4),
+ )
+ )
+ T.evaluate(
+ T.call_intrin(
+ "void",
+ "tirx.masked_store",
+ A,
+ a_load,
+ 0,
+ T.Ramp(0, 2, 4),
+ T.Broadcast(T.bool(False), 4),
+ )
+ )
expected_output = """
# from tvm.script import tirx as T
@@ -1002,7 +1021,7 @@ def test_predicated_load_store():
@T.prim_func(s_tir=True)
def func(A: T.Buffer((128, 128), "float32"), B: T.Buffer((256, 256),
"float32")):
- A.vstore([0, T.Ramp(0, 2, 4)], A.vload([0, T.Ramp(0, 4, 4)],
predicate=T.Broadcast(T.bool(False), 4)), predicate=T.Broadcast(T.bool(False),
4))
+ T.masked_store(A, T.masked_load("float32x4", A, 0, T.Ramp(0, 4, 4),
T.Broadcast(T.bool(False), 4)), 0, T.Ramp(0, 2, 4), T.Broadcast(T.bool(False),
4))
"""
_assert_print(main, expected_output)
@@ -1014,16 +1033,24 @@ def test_predicated_buffer_load_store():
a: tirx.decl_buffer(shape=[128, 128], dtype="float32", name="A"),
b: tirx.decl_buffer(shape=[256, 256], dtype="float32", name="B"),
}
- buffer_load = tirx.BufferLoad(
- buffer=buffers[b],
- indices=[0, tirx.Ramp(0, 4, 4)],
- predicate=tirx.Broadcast(tirx.IntImm("bool", 0), 4),
+ buffer_load = tirx.call_intrin(
+ "float32x4",
+ "tirx.masked_load",
+ buffers[b],
+ 0,
+ tirx.Ramp(0, 4, 4),
+ tirx.Broadcast(tirx.IntImm("bool", 0), 4),
)
- body = tirx.BufferStore(
- buffer=buffers[a],
- value=buffer_load,
- indices=[0, tirx.Ramp(0, 2, 4)],
- predicate=tirx.Broadcast(tirx.IntImm("bool", 0), 4),
+ body = tirx.Evaluate(
+ tirx.call_intrin(
+ "void",
+ "tirx.masked_store",
+ buffers[a],
+ buffer_load,
+ 0,
+ tirx.Ramp(0, 2, 4),
+ tirx.Broadcast(tirx.IntImm("bool", 0), 4),
+ )
)
func = tirx.PrimFunc(
params=[buffers[a], buffers[b]],
@@ -1037,7 +1064,7 @@ def test_predicated_buffer_load_store():
@T.prim_func(private=True, s_tir=True)
def main(A: T.Buffer((128, 128), "float32"), B: T.Buffer((256, 256),
"float32")):
- A.vstore([0, T.Ramp(0, 2, 4)], B.vload([0, T.Ramp(0, 4, 4)],
predicate=T.Broadcast(T.bool(False), 4)), predicate=T.Broadcast(T.bool(False),
4))
+ T.masked_store(A, T.masked_load("float32x4", B, 0, T.Ramp(0, 4, 4),
T.Broadcast(T.bool(False), 4)), 0, T.Ramp(0, 2, 4), T.Broadcast(T.bool(False),
4))
"""
_assert_print(func, expected_output)
@@ -1051,8 +1078,16 @@ def test_predicated_scalable_load_store():
B = T.match_buffer(b, (256, 256), "float32")
T.func_attr({"global_symbol": "func"})
mask = T.meta_var(T.get_active_lane_mask("uint1xvscalex4", 0, 13))
- a_load = T.meta_var(A.vload([0, T.Ramp(0, 4, T.vscale() * 4)],
predicate=mask))
- A.vstore([0, T.Ramp(0, 2, T.vscale() * 4)], a_load, predicate=mask)
+ a_load = T.meta_var(
+ T.call_intrin(
+ "float32xvscalex4", "tirx.masked_load", A, 0, T.Ramp(0, 4,
T.vscale() * 4), mask
+ )
+ )
+ T.evaluate(
+ T.call_intrin(
+ "void", "tirx.masked_store", A, a_load, 0, T.Ramp(0, 2,
T.vscale() * 4), mask
+ )
+ )
expected_output = """
# from tvm.script import tirx as T
@@ -1060,11 +1095,25 @@ def test_predicated_scalable_load_store():
@T.prim_func(s_tir=True)
def func(A: T.Buffer((128, 128), "float32"), B: T.Buffer((256, 256),
"float32")):
- A.vstore([0, T.Ramp(0, 2, T.vscale() * 4)], A.vload([0, T.Ramp(0, 4,
T.vscale() * 4)], predicate=T.get_active_lane_mask("uint1xvscalex4", 0, 13)),
predicate=T.get_active_lane_mask("uint1xvscalex4", 0, 13))
+ T.masked_store(A, T.masked_load("float32xvscalex4", A, 0, T.Ramp(0, 4,
T.vscale() * 4), T.get_active_lane_mask("uint1xvscalex4", 0, 13)), 0, T.Ramp(0,
2, T.vscale() * 4), T.get_active_lane_mask("uint1xvscalex4", 0, 13))
"""
_assert_print(main, expected_output)
+def test_masked_load_prevents_scalar_allocation_init_fusion():
+ from tvm.script import tirx as T
+
+ @T.prim_func(s_tir=True)
+ def main():
+ A = T.alloc_buffer((1,), "float32x4")
+ A[0] = T.masked_load("float32x4", A, 0, T.Broadcast(T.bool(True), 4))
+
+ source = main.script()
+ assert "A = T.alloc_buffer" in source
+ assert "A[0] = T.masked_load" in source
+ tvm.ir.assert_structural_equal(tvm.script.from_source(source), main)
+
+
def test_vload_with_explicit_scalable_data_type():
from tvm.script import tirx as T
diff --git a/tests/python/tvmscript/test_tvmscript_roundtrip.py
b/tests/python/tvmscript/test_tvmscript_roundtrip.py
index 94d4169e38..ddc2c09603 100644
--- a/tests/python/tvmscript/test_tvmscript_roundtrip.py
+++ b/tests/python/tvmscript/test_tvmscript_roundtrip.py
@@ -2456,9 +2456,24 @@ def predicated_buffer_load_store():
B = T.match_buffer(b, (8,), "float32")
for i_0 in range(4):
load_a = T.meta_var(
- A.vload([T.Ramp(i_0, 1, 4)],
predicate=T.Broadcast(T.bool(True), 4))
+ T.call_intrin(
+ "float32x4",
+ "tirx.masked_load",
+ A,
+ T.Ramp(i_0, 1, 4),
+ T.Broadcast(T.bool(True), 4),
+ )
+ )
+ T.evaluate(
+ T.call_intrin(
+ "void",
+ "tirx.masked_store",
+ B,
+ load_a,
+ T.Ramp(0, 2, 4),
+ T.Broadcast(T.bool(True), 4),
+ )
)
- B.vstore([T.Ramp(0, 2, 4)], load_a,
predicate=T.Broadcast(T.bool(True), 4))
return func