1062 lines
34 KiB
Python
1062 lines
34 KiB
Python
# Licensed to the Apache Software Foundation (ASF) under one
|
|
# or more contributor license agreements. See the NOTICE file
|
|
# distributed with this work for additional information
|
|
# regarding copyright ownership. The ASF licenses this file
|
|
# to you under the Apache License, Version 2.0 (the
|
|
# "License"); you may not use this file except in compliance
|
|
# with the License. You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing,
|
|
# software distributed under the License is distributed on an
|
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
# KIND, either express or implied. See the License for the
|
|
# specific language governing permissions and limitations
|
|
# under the License.
|
|
"""Statement functor utilities for IR transformations"""
|
|
|
|
from typing import TypeVar
|
|
|
|
import tvm
|
|
from tvm.ir import Range
|
|
|
|
from . import _ffi_api
|
|
from .expr_functor import ExprMutator, ExprVisitor, _visit_array
|
|
from .function import PrimFunc
|
|
|
|
T = TypeVar("T")
|
|
|
|
|
|
class StmtFunctor:
|
|
"""An abstract visitor over Statement, with visiting functions defined for each Stmt type."""
|
|
|
|
def __init__(self):
|
|
self._dispatch_map = {
|
|
"tirx.Bind": self.visit_bind_,
|
|
"tirx.AttrStmt": self.visit_attr_,
|
|
"tirx.IfThenElse": self.visit_if_then_else_,
|
|
"tirx.For": self.visit_for_,
|
|
"tirx.While": self.visit_while_,
|
|
"tirx.Break": self.visit_break_,
|
|
"tirx.Continue": self.visit_continue_,
|
|
"tirx.Allocate": self.visit_allocate_,
|
|
"tirx.AllocateConst": self.visit_allocate_const_,
|
|
"tirx.DeclBuffer": self.visit_decl_buffer_,
|
|
"tirx.BufferStore": self.visit_buffer_store_,
|
|
"tirx.BufferRealize": self.visit_buffer_realize_,
|
|
"tirx.AssertStmt": self.visit_assert_,
|
|
"tirx.ProducerStore": self.visit_producer_store_,
|
|
"tirx.ProducerRealize": self.visit_producer_realize_,
|
|
"tirx.Prefetch": self.visit_prefetch_,
|
|
"tirx.SeqStmt": self.visit_seqstmt_,
|
|
"tirx.Evaluate": self.visit_evaluate_,
|
|
"tirx.SBlock": self.visit_block_,
|
|
"tirx.SBlockRealize": self.visit_block_realize_,
|
|
"tirx.ScopeIdDefStmt": self.visit_scope_id_def_stmt_,
|
|
"tirx.TilePrimitiveCall": self.visit_op_call_,
|
|
"tirx.AllocBuffer": self.visit_alloc_buffer_,
|
|
}
|
|
|
|
def visit_stmt(self, stmt):
|
|
"""Apply the visitor to a statement.
|
|
|
|
Parameters
|
|
----------
|
|
stmt : tvm.tirx.Stmt
|
|
The statement to be visited.
|
|
|
|
Returns
|
|
-------
|
|
result : Any
|
|
The result of the visit.
|
|
"""
|
|
if stmt is None:
|
|
return None
|
|
if isinstance(stmt, tvm.tirx.TilePrimitiveCall):
|
|
# subclass of TilePrimitiveCall only exists in python side
|
|
# and are not handled by dispatch map
|
|
key = "TilePrimitiveCall"
|
|
else:
|
|
key = stmt.__class__.__name__
|
|
if key.endswith("Node"):
|
|
key = key[:-4] # Remove the "Node" suffix
|
|
|
|
key = "tirx." + key
|
|
if key in self._dispatch_map:
|
|
return self._dispatch_map[key](stmt)
|
|
|
|
return self.visit_stmt_default_(stmt)
|
|
|
|
def visit_stmt_default_(self, op):
|
|
"""Default visitor implementation for statements."""
|
|
raise NotImplementedError(f"Do not have a default for {op.__class__.__name__}")
|
|
|
|
def visit_bind_(self, op):
|
|
"""Visitor for Bind nodes."""
|
|
return self.visit_stmt_default_(op)
|
|
|
|
def visit_attr_(self, op):
|
|
"""Visitor for AttrStmt nodes."""
|
|
return self.visit_stmt_default_(op)
|
|
|
|
def visit_if_then_else_(self, op):
|
|
"""Visitor for IfThenElse nodes."""
|
|
return self.visit_stmt_default_(op)
|
|
|
|
def visit_for_(self, op):
|
|
"""Visitor for For nodes."""
|
|
return self.visit_stmt_default_(op)
|
|
|
|
def visit_while_(self, op):
|
|
"""Visitor for While nodes."""
|
|
return self.visit_stmt_default_(op)
|
|
|
|
def visit_break_(self, op):
|
|
"""Visitor for Break nodes."""
|
|
return self.visit_stmt_default_(op)
|
|
|
|
def visit_continue_(self, op):
|
|
"""Visitor for Continue nodes."""
|
|
return self.visit_stmt_default_(op)
|
|
|
|
def visit_allocate_(self, op):
|
|
"""Visitor for Allocate nodes."""
|
|
return self.visit_stmt_default_(op)
|
|
|
|
def visit_allocate_const_(self, op):
|
|
"""Visitor for AllocateConst nodes."""
|
|
return self.visit_stmt_default_(op)
|
|
|
|
def visit_decl_buffer_(self, op):
|
|
"""Visitor for DeclBuffer nodes."""
|
|
return self.visit_stmt_default_(op)
|
|
|
|
def visit_buffer_store_(self, op):
|
|
"""Visitor for BufferStore nodes."""
|
|
return self.visit_stmt_default_(op)
|
|
|
|
def visit_buffer_realize_(self, op):
|
|
"""Visitor for BufferRealize nodes."""
|
|
raise ValueError("BufferRealize is not allowed")
|
|
|
|
def visit_assert_(self, op):
|
|
"""Visitor for AssertStmt nodes."""
|
|
return self.visit_stmt_default_(op)
|
|
|
|
def visit_producer_store_(self, op):
|
|
"""Visitor for ProducerStore nodes."""
|
|
raise ValueError("ProducerStore is not allowed")
|
|
|
|
def visit_producer_realize_(self, op):
|
|
"""Visitor for ProducerRealize nodes."""
|
|
raise ValueError("ProducerRealize is not allowed")
|
|
|
|
def visit_prefetch_(self, op):
|
|
"""Visitor for Prefetch nodes."""
|
|
raise ValueError("Prefetch is not allowed")
|
|
|
|
def visit_seqstmt_(self, op):
|
|
"""Visitor for SeqStmt nodes."""
|
|
return self.visit_stmt_default_(op)
|
|
|
|
def visit_evaluate_(self, op):
|
|
"""Visitor for Evaluate nodes."""
|
|
return self.visit_stmt_default_(op)
|
|
|
|
def visit_block_(self, op):
|
|
"""Visitor for Block nodes."""
|
|
return self.visit_stmt_default_(op)
|
|
|
|
def visit_block_realize_(self, op):
|
|
"""Visitor for BlockRealize nodes."""
|
|
return self.visit_stmt_default_(op)
|
|
|
|
def visit_scope_id_def_stmt_(self, op):
|
|
"""Visitor for ScopeIdDefStmt nodes."""
|
|
return self.visit_stmt_default_(op)
|
|
|
|
def visit_op_call_(self, op):
|
|
"""Visitor for TilePrimitiveCall nodes."""
|
|
return self.visit_stmt_default_(op)
|
|
|
|
def visit_buffer_region_(self, op):
|
|
"""Visitor for BufferRegion nodes."""
|
|
return self.visit_stmt_default_(op)
|
|
|
|
def visit_alloc_buffer_(self, op):
|
|
"""Visitor for AllocBuffer nodes."""
|
|
return self.visit_stmt_default_(op)
|
|
|
|
def __call__(self, stmt):
|
|
"""Call visitor on statement.
|
|
|
|
Parameters
|
|
----------
|
|
stmt : tvm.tirx.Stmt
|
|
The statement.
|
|
|
|
Returns
|
|
-------
|
|
result : Any
|
|
The result of visiting.
|
|
"""
|
|
return self.visit_stmt(stmt)
|
|
|
|
|
|
class StmtVisitor(StmtFunctor):
|
|
"""A visitor over Stmt.
|
|
|
|
This is a visitor that recursively traverses a statement. Subclasses can
|
|
override the visit methods to customize the behavior.
|
|
"""
|
|
|
|
def visit_expr(self, expr):
|
|
"""Visit expressions that occur in a statement.
|
|
|
|
This method can be overridden to implement expression
|
|
traversal in a statement visitor.
|
|
|
|
Parameters
|
|
----------
|
|
expr : Expr
|
|
The expression to be visited.
|
|
"""
|
|
pass
|
|
|
|
def visit_bind_(self, op):
|
|
"""Visitor implementation for Bind."""
|
|
self.visit_expr(op.value)
|
|
|
|
def visit_attr_(self, op):
|
|
"""Visitor implementation for AttrStmt."""
|
|
self.visit_expr(op.value)
|
|
self.visit_stmt(op.body)
|
|
|
|
def visit_if_then_else_(self, op):
|
|
"""Visitor implementation for IfThenElse."""
|
|
self.visit_expr(op.condition)
|
|
self.visit_stmt(op.then_case)
|
|
if op.else_case:
|
|
self.visit_stmt(op.else_case)
|
|
|
|
def visit_for_(self, op):
|
|
"""Visitor implementation for For."""
|
|
self.visit_expr(op.min)
|
|
self.visit_expr(op.extent)
|
|
if op.step is not None:
|
|
self.visit_expr(op.step)
|
|
self.visit_stmt(op.body)
|
|
|
|
def visit_while_(self, op):
|
|
"""Visitor implementation for While."""
|
|
self.visit_expr(op.condition)
|
|
self.visit_stmt(op.body)
|
|
|
|
def visit_break_(self, op):
|
|
"""Visitor implementation for Break."""
|
|
pass
|
|
|
|
def visit_continue_(self, op):
|
|
"""Visitor implementation for Continue."""
|
|
pass
|
|
|
|
def visit_allocate_(self, op):
|
|
"""Visitor implementation for Allocate."""
|
|
_visit_array(op.extents, lambda x: self.visit_expr(x))
|
|
self.visit_stmt(op.body)
|
|
self.visit_expr(op.condition)
|
|
|
|
def visit_allocate_const_(self, op):
|
|
"""Visitor implementation for AllocateConst."""
|
|
_visit_array(op.extents, lambda x: self.visit_expr(x))
|
|
self.visit_stmt(op.body)
|
|
|
|
def visit_decl_buffer_(self, op):
|
|
"""Visitor implementation for DeclBuffer."""
|
|
if hasattr(op, "body"):
|
|
self.visit_stmt(op.body)
|
|
return
|
|
return
|
|
|
|
def visit_buffer_store_(self, op):
|
|
"""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."""
|
|
self.visit_expr(op.condition)
|
|
for message_part in op.message_parts:
|
|
if tvm.ir.is_prim_expr(message_part):
|
|
self.visit_expr(message_part)
|
|
|
|
def visit_seqstmt_(self, op):
|
|
"""Visitor implementation for SeqStmt."""
|
|
_visit_array(op.seq, lambda s: self.visit_stmt(s))
|
|
|
|
def visit_evaluate_(self, op):
|
|
"""Visitor implementation for Evaluate."""
|
|
self.visit_expr(op.value)
|
|
|
|
def visit_block_(self, op):
|
|
"""Visitor implementation for Block."""
|
|
# Visit IterVars
|
|
for iter_var in op.iter_vars:
|
|
self.visit_expr(iter_var.dom.min)
|
|
self.visit_expr(iter_var.dom.extent)
|
|
|
|
# Visit buffer regions (reads and writes)
|
|
def _visit_buffer_region(buffer_region):
|
|
for r in buffer_region.region:
|
|
self.visit_expr(r.min)
|
|
self.visit_expr(r.extent)
|
|
|
|
_visit_array(op.reads, _visit_buffer_region)
|
|
_visit_array(op.writes, _visit_buffer_region)
|
|
|
|
# Visit match buffers
|
|
for match_buffer in op.match_buffers:
|
|
_visit_buffer_region(match_buffer.source)
|
|
|
|
# Visit init statement
|
|
if op.init is not None:
|
|
self.visit_stmt(op.init)
|
|
|
|
# Visit body
|
|
self.visit_stmt(op.body)
|
|
|
|
def visit_block_realize_(self, op):
|
|
"""Visitor implementation for BlockRealize."""
|
|
_visit_array(op.iter_values, lambda x: self.visit_expr(x))
|
|
self.visit_expr(op.predicate)
|
|
self.visit_stmt(op.block)
|
|
|
|
def visit_scope_id_def_stmt_(self, op):
|
|
"""Visitor implementation for ScopeIdDefStmt.
|
|
|
|
Mirrors the C++ visitor: walk extents and preferred_extents via
|
|
``visit_expr``; there is no body to recurse into (the def vars
|
|
themselves are leaves the visitor doesn't otherwise inspect).
|
|
"""
|
|
# The C++ field is named ``def``, which is a Python keyword,
|
|
# so it's accessed via ``getattr``.
|
|
sid = getattr(op, "def")
|
|
if sid.extents is not None:
|
|
for e in sid.extents:
|
|
self.visit_expr(e)
|
|
if sid.preferred_extents is not None:
|
|
for e in sid.preferred_extents:
|
|
self.visit_expr(e)
|
|
|
|
def visit_op_call_(self, op):
|
|
"""Visitor implementation for TilePrimitiveCall."""
|
|
for arg in op.args:
|
|
if tvm.ir.is_prim_expr(arg):
|
|
self.visit_expr(arg)
|
|
elif isinstance(arg, tvm.tirx.Stmt):
|
|
self.visit_stmt(arg)
|
|
elif isinstance(arg, tvm.tirx.BufferRegion):
|
|
self.visit_buffer_region_(arg)
|
|
for value in op.config.values():
|
|
if tvm.ir.is_prim_expr(value):
|
|
self.visit_expr(value)
|
|
elif isinstance(value, tvm.tirx.Stmt):
|
|
self.visit_stmt(value)
|
|
|
|
def visit_buffer_region_(self, op):
|
|
"""Visitor implementation for BufferRegion."""
|
|
|
|
def _visit_range(range):
|
|
self.visit_expr(range.min)
|
|
self.visit_expr(range.extent)
|
|
|
|
_visit_array(op.region, _visit_range)
|
|
|
|
def visit_alloc_buffer_(self, op):
|
|
"""Visitor implementation for AllocBuffer."""
|
|
if hasattr(op, "body"):
|
|
self.visit_stmt(op.body)
|
|
return
|
|
return
|
|
|
|
|
|
class StmtMutator(StmtFunctor):
|
|
"""A mutator over Stmt.
|
|
|
|
This is a mutator that recursively transforms a statement. Subclasses can
|
|
override the visit methods to customize the behavior.
|
|
"""
|
|
|
|
def visit_expr(self, expr):
|
|
"""Visit and mutate expressions that occur in a statement.
|
|
|
|
This method can be overridden to implement expression
|
|
mutation in a statement mutator.
|
|
|
|
Parameters
|
|
----------
|
|
expr : Expr
|
|
The expression to be visited.
|
|
|
|
Returns
|
|
-------
|
|
result : Expr
|
|
The mutated expression.
|
|
"""
|
|
return expr
|
|
|
|
def visit_bind_(self, op):
|
|
"""Mutator implementation for Bind."""
|
|
value = self.visit_expr(op.value)
|
|
|
|
if value is op.value:
|
|
return op
|
|
|
|
return tvm.tirx.Bind(op.var, value, op.span)
|
|
|
|
def visit_attr_(self, op):
|
|
"""Mutator implementation for AttrStmt."""
|
|
value = self.visit_expr(op.value)
|
|
body = self.visit_stmt(op.body)
|
|
|
|
if value is op.value and body is op.body:
|
|
return op
|
|
|
|
return tvm.tirx.AttrStmt(op.node, op.attr_key, value, body, op.span)
|
|
|
|
def visit_if_then_else_(self, op):
|
|
"""Mutator implementation for IfThenElse."""
|
|
condition = self.visit_expr(op.condition)
|
|
then_case = self.visit_stmt(op.then_case)
|
|
else_case = self.visit_stmt(op.else_case) if op.else_case else None
|
|
|
|
if condition is op.condition and then_case is op.then_case and else_case is op.else_case:
|
|
return op
|
|
|
|
return tvm.tirx.IfThenElse(condition, then_case, else_case, op.span)
|
|
|
|
def visit_for_(self, op):
|
|
"""Mutator implementation for For."""
|
|
min_val = self.visit_expr(op.min)
|
|
extent = self.visit_expr(op.extent)
|
|
step = self.visit_expr(op.step) if op.step is not None else None
|
|
body = self.visit_stmt(op.body)
|
|
|
|
if min_val is op.min and extent is op.extent and step is op.step and body is op.body:
|
|
return op
|
|
|
|
return tvm.tirx.For(
|
|
op.loop_var,
|
|
min_val,
|
|
extent,
|
|
op.kind,
|
|
body,
|
|
op.thread_binding,
|
|
op.annotations,
|
|
step,
|
|
op.span,
|
|
)
|
|
|
|
def visit_while_(self, op):
|
|
"""Mutator implementation for While."""
|
|
condition = self.visit_expr(op.condition)
|
|
body = self.visit_stmt(op.body)
|
|
|
|
if condition is op.condition and body is op.body:
|
|
return op
|
|
|
|
return tvm.tirx.While(condition, body, op.span)
|
|
|
|
def visit_break_(self, op):
|
|
"""Mutator implementation for Break."""
|
|
return op
|
|
|
|
def visit_continue_(self, op):
|
|
"""Mutator implementation for Continue."""
|
|
return op
|
|
|
|
def visit_allocate_(self, op):
|
|
"""Mutator implementation for Allocate."""
|
|
extents = [self.visit_expr(extent) for extent in op.extents]
|
|
body = self.visit_stmt(op.body)
|
|
condition = self.visit_expr(op.condition)
|
|
|
|
extents_changed = any(old is not new for old, new in zip(op.extents, extents))
|
|
|
|
if not extents_changed and body is op.body and condition is op.condition:
|
|
return op
|
|
|
|
return tvm.tirx.Allocate(
|
|
op.buffer_var, op.dtype, extents, condition, body, op.annotations, op.span
|
|
)
|
|
|
|
def visit_allocate_const_(self, op):
|
|
"""Mutator implementation for AllocateConst."""
|
|
extents = [self.visit_expr(extent) for extent in op.extents]
|
|
body = self.visit_stmt(op.body)
|
|
|
|
extents_changed = any(old is not new for old, new in zip(op.extents, extents))
|
|
|
|
if not extents_changed and body is op.body:
|
|
return op
|
|
|
|
# Create the data_or_idx parameter based on what's available
|
|
if op.data is not None:
|
|
data_or_idx = op.data
|
|
elif op.irmod_storage_idx is not None:
|
|
data_or_idx = op.irmod_storage_idx
|
|
else:
|
|
data_or_idx = None
|
|
|
|
return tvm.tirx.AllocateConst(
|
|
op.buffer_var, op.dtype, extents, data_or_idx, body, op.annotations, op.span
|
|
)
|
|
|
|
def visit_decl_buffer_(self, op):
|
|
"""Mutator implementation for DeclBuffer."""
|
|
if hasattr(op, "body"):
|
|
body = self.visit_stmt(op.body)
|
|
if body is op.body:
|
|
return op
|
|
return tvm.tirx.DeclBuffer(op.buffer, body, op.span)
|
|
return op
|
|
|
|
def visit_buffer_store_(self, op):
|
|
"""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:
|
|
return op
|
|
|
|
return tvm.tirx.BufferStore(op.buffer, value, indices, predicate, op.span)
|
|
|
|
def visit_buffer_realize_(self, op):
|
|
"""Mutator implementation for BufferRealize."""
|
|
bounds = []
|
|
bounds_changed = False
|
|
|
|
for r in op.bounds:
|
|
new_min = self.visit_expr(r.min)
|
|
new_extent = self.visit_expr(r.extent)
|
|
|
|
if new_min is not r.min or new_extent is not r.extent:
|
|
bounds_changed = True
|
|
bounds.append(tvm.ir.Range(new_min, new_extent))
|
|
else:
|
|
bounds.append(r)
|
|
|
|
condition = self.visit_expr(op.condition)
|
|
body = self.visit_stmt(op.body)
|
|
|
|
if not bounds_changed and condition is op.condition and body is op.body:
|
|
return op
|
|
|
|
return tvm.tirx.BufferRealize(op.buffer, bounds, condition, body, op.span)
|
|
|
|
def visit_assert_(self, op):
|
|
"""Mutator implementation for AssertStmt."""
|
|
condition = self.visit_expr(op.condition)
|
|
message_parts = []
|
|
message_parts_changed = False
|
|
for message_part in op.message_parts:
|
|
if tvm.ir.is_prim_expr(message_part):
|
|
new_message_part = self.visit_expr(message_part)
|
|
if new_message_part is not message_part:
|
|
message_parts_changed = True
|
|
message_parts.append(new_message_part)
|
|
else:
|
|
message_parts.append(message_part)
|
|
|
|
if condition is op.condition and not message_parts_changed:
|
|
return op
|
|
|
|
return tvm.tirx.AssertStmt(op.kind, condition, message_parts, op.span)
|
|
|
|
def visit_producer_store_(self, op):
|
|
"""Mutator implementation for ProducerStore."""
|
|
value = self.visit_expr(op.value)
|
|
indices = [self.visit_expr(idx) for idx in op.indices]
|
|
|
|
indices_changed = any(old is not new for old, new in zip(op.indices, indices))
|
|
|
|
if value is op.value and not indices_changed:
|
|
return op
|
|
|
|
return tvm.tirx.ProducerStore(op.producer, value, indices, op.span)
|
|
|
|
def visit_producer_realize_(self, op):
|
|
"""Mutator implementation for ProducerRealize."""
|
|
bounds = []
|
|
bounds_changed = False
|
|
|
|
for r in op.bounds:
|
|
new_min = self.visit_expr(r.min)
|
|
new_extent = self.visit_expr(r.extent)
|
|
|
|
if new_min is not r.min or new_extent is not r.extent:
|
|
bounds_changed = True
|
|
bounds.append(tvm.ir.Range(new_min, new_extent))
|
|
else:
|
|
bounds.append(r)
|
|
|
|
condition = self.visit_expr(op.condition)
|
|
body = self.visit_stmt(op.body)
|
|
|
|
if not bounds_changed and condition is op.condition and body is op.body:
|
|
return op
|
|
|
|
return tvm.tirx.ProducerRealize(
|
|
op.producer, bounds, condition, body, op.storage_scope, op.span
|
|
)
|
|
|
|
def visit_prefetch_(self, op):
|
|
"""Mutator implementation for Prefetch."""
|
|
bounds = []
|
|
bounds_changed = False
|
|
|
|
for r in op.bounds:
|
|
new_min = self.visit_expr(r.min)
|
|
new_extent = self.visit_expr(r.extent)
|
|
|
|
if new_min is not r.min or new_extent is not r.extent:
|
|
bounds_changed = True
|
|
bounds.append(tvm.ir.Range(new_min, new_extent))
|
|
else:
|
|
bounds.append(r)
|
|
|
|
if not bounds_changed:
|
|
return op
|
|
|
|
return tvm.tirx.Prefetch(op.buffer, bounds, op.span)
|
|
|
|
def visit_seqstmt_(self, op):
|
|
"""Mutator implementation for SeqStmt."""
|
|
new_seq = []
|
|
changed = False
|
|
|
|
for stmt in op.seq:
|
|
new_stmt = self.visit_stmt(stmt)
|
|
if new_stmt is not stmt:
|
|
changed = True
|
|
if isinstance(new_stmt, tvm.tirx.SeqStmt):
|
|
# Flatten nested SeqStmt
|
|
new_seq.extend(new_stmt.seq)
|
|
changed = True
|
|
else:
|
|
new_seq.append(new_stmt)
|
|
|
|
if not changed:
|
|
return op
|
|
|
|
if len(new_seq) == 1:
|
|
return new_seq[0]
|
|
|
|
return tvm.tirx.SeqStmt(new_seq, op.span)
|
|
|
|
def visit_evaluate_(self, op):
|
|
"""Mutator implementation for Evaluate."""
|
|
value = self.visit_expr(op.value)
|
|
|
|
if value is op.value:
|
|
return op
|
|
|
|
return tvm.tirx.Evaluate(value, op.span)
|
|
|
|
def visit_block_(self, op):
|
|
"""Mutator implementation for Block."""
|
|
# Process iter_vars
|
|
iter_vars = []
|
|
iter_vars_changed = False
|
|
|
|
for iv in op.iter_vars:
|
|
old_dom = iv.dom
|
|
new_min = self.visit_expr(old_dom.min)
|
|
new_extent = self.visit_expr(old_dom.extent)
|
|
|
|
if new_min is not old_dom.min or new_extent is not old_dom.extent:
|
|
iter_vars_changed = True
|
|
new_dom = tvm.ir.Range(new_min, new_extent)
|
|
iter_vars.append(tvm.tirx.IterVar(new_dom, iv.var, iv.iter_type, iv.thread_tag))
|
|
else:
|
|
iter_vars.append(iv)
|
|
|
|
# Process reads/writes buffer regions
|
|
def _mutate_buffer_regions(regions):
|
|
new_regions = []
|
|
regions_changed = False
|
|
|
|
for region in regions:
|
|
new_ranges = []
|
|
ranges_changed = False
|
|
|
|
for r in region.region:
|
|
new_min = self.visit_expr(r.min)
|
|
new_extent = self.visit_expr(r.extent)
|
|
|
|
if new_min is not r.min or new_extent is not r.extent:
|
|
ranges_changed = True
|
|
new_ranges.append(tvm.ir.Range(new_min, new_extent))
|
|
else:
|
|
new_ranges.append(r)
|
|
|
|
if ranges_changed:
|
|
regions_changed = True
|
|
new_regions.append(tvm.tirx.BufferRegion(region.buffer, new_ranges))
|
|
else:
|
|
new_regions.append(region)
|
|
|
|
return new_regions, regions_changed
|
|
|
|
reads, reads_changed = _mutate_buffer_regions(op.reads)
|
|
writes, writes_changed = _mutate_buffer_regions(op.writes)
|
|
|
|
# Process match buffers
|
|
match_buffers = []
|
|
match_buffers_changed = False
|
|
|
|
for match_buffer in op.match_buffers:
|
|
source_region = match_buffer.source
|
|
new_ranges = []
|
|
ranges_changed = False
|
|
|
|
for r in source_region.region:
|
|
new_min = self.visit_expr(r.min)
|
|
new_extent = self.visit_expr(r.extent)
|
|
|
|
if new_min is not r.min or new_extent is not r.extent:
|
|
ranges_changed = True
|
|
new_ranges.append(tvm.ir.Range(new_min, new_extent))
|
|
else:
|
|
new_ranges.append(r)
|
|
|
|
if ranges_changed:
|
|
match_buffers_changed = True
|
|
new_source = tvm.tirx.BufferRegion(source_region.buffer, new_ranges)
|
|
match_buffers.append(tvm.tirx.MatchBufferRegion(match_buffer.buffer, new_source))
|
|
else:
|
|
match_buffers.append(match_buffer)
|
|
|
|
# Process init and body
|
|
init = self.visit_stmt(op.init) if op.init is not None else None
|
|
body = self.visit_stmt(op.body)
|
|
|
|
# Check if anything changed
|
|
if (
|
|
not iter_vars_changed
|
|
and not reads_changed
|
|
and not writes_changed
|
|
and not match_buffers_changed
|
|
and (init is op.init or (init is None and op.init is None))
|
|
and body is op.body
|
|
):
|
|
return op
|
|
return tvm.tirx.SBlock(
|
|
iter_vars,
|
|
reads,
|
|
writes,
|
|
op.name_hint,
|
|
body,
|
|
init,
|
|
op.alloc_buffers,
|
|
match_buffers,
|
|
op.annotations,
|
|
)
|
|
|
|
def visit_block_realize_(self, op):
|
|
"""Mutator implementation for BlockRealize."""
|
|
iter_values = [self.visit_expr(val) for val in op.iter_values]
|
|
predicate = self.visit_expr(op.predicate)
|
|
block = self.visit_stmt(op.block)
|
|
|
|
iter_values_changed = any(old is not new for old, new in zip(op.iter_values, iter_values))
|
|
|
|
if not iter_values_changed and predicate is op.predicate and block is op.block:
|
|
return op
|
|
|
|
if not isinstance(block, tvm.tirx.SBlock):
|
|
raise TypeError(f"Expected SBlock, but got {type(block)}")
|
|
|
|
return tvm.tirx.SBlockRealize(iter_values, predicate, block)
|
|
|
|
def visit_scope_id_def_stmt_(self, op):
|
|
"""Mutator implementation for ScopeIdDefStmt.
|
|
|
|
Mirrors the C++ mutator: rewrite ``extents`` and
|
|
``preferred_extents`` via ``visit_expr``. Deferred-extent defs
|
|
(extents is None) and unchanged extents pass through.
|
|
"""
|
|
from .exec_scope import _SCOPE_BINDING_TO_PARENT_CUR, ScopeIdDef
|
|
|
|
# ``def`` is a Python keyword; access the C++ field via ``getattr``.
|
|
sid = getattr(op, "def")
|
|
changed = False
|
|
|
|
def _walk(arr):
|
|
nonlocal changed
|
|
if arr is None:
|
|
return None
|
|
out = []
|
|
for e in arr:
|
|
ne = self.visit_expr(e)
|
|
if ne is not e:
|
|
changed = True
|
|
out.append(ne)
|
|
return out
|
|
|
|
new_extents = _walk(sid.extents)
|
|
new_pref = _walk(sid.preferred_extents)
|
|
if not changed:
|
|
return op
|
|
parent, cur = _SCOPE_BINDING_TO_PARENT_CUR[sid.scope]
|
|
new_def = ScopeIdDef(sid.def_ids, new_extents, parent, cur, new_pref)
|
|
return tvm.tirx.ScopeIdDefStmt(new_def, op.span)
|
|
|
|
def visit_op_call_(self, op):
|
|
"""Mutator implementation for TilePrimitiveCall."""
|
|
new_args = []
|
|
args_changed = False
|
|
|
|
for arg in op.args:
|
|
if tvm.ir.is_prim_expr(arg):
|
|
new_arg = self.visit_expr(arg)
|
|
elif isinstance(arg, tvm.tirx.Stmt):
|
|
new_arg = self.visit_stmt(arg)
|
|
elif isinstance(arg, tvm.tirx.BufferRegion):
|
|
new_arg = self.visit_buffer_region_(arg)
|
|
else:
|
|
new_arg = arg
|
|
|
|
if new_arg is not arg:
|
|
args_changed = True
|
|
new_args.append(new_arg)
|
|
|
|
# Also mutate Expr values in the config map
|
|
new_config = {}
|
|
config_changed = False
|
|
for key, value in op.config.items():
|
|
if tvm.ir.is_prim_expr(value):
|
|
new_value = self.visit_expr(value)
|
|
elif isinstance(value, tvm.tirx.Stmt):
|
|
new_value = self.visit_stmt(value)
|
|
else:
|
|
new_value = value
|
|
if new_value is not value:
|
|
config_changed = True
|
|
new_config[key] = new_value
|
|
|
|
if not args_changed and not config_changed:
|
|
return op
|
|
|
|
return tvm.tirx.TilePrimitiveCall(
|
|
*new_args,
|
|
op=op.op,
|
|
workspace=op.workspace,
|
|
config=new_config,
|
|
dispatch=op.dispatch,
|
|
scope=op.scope,
|
|
)
|
|
|
|
def visit_buffer_region_(self, op):
|
|
"""Mutator implementation for BufferRegion."""
|
|
|
|
def _mutate_range(range):
|
|
new_min = self.visit_expr(range.min)
|
|
new_extent = self.visit_expr(range.extent)
|
|
|
|
if new_min is range.min and new_extent is range.extent:
|
|
return range
|
|
else:
|
|
return Range.from_min_extent(new_min, new_extent)
|
|
|
|
region = [_mutate_range(r) for r in op.region]
|
|
|
|
if all(old_r is new_r for old_r, new_r in zip(op.region, region)):
|
|
return op
|
|
else:
|
|
return tvm.tirx.BufferRegion(op.buffer, region)
|
|
|
|
def visit_alloc_buffer_(self, op):
|
|
"""Mutator implementation for AllocBuffer."""
|
|
if hasattr(op, "body"):
|
|
body = self.visit_stmt(op.body)
|
|
if body is op.body:
|
|
return op
|
|
return tvm.tirx.AllocBuffer(op.buffer, body, op.annotations, op.span)
|
|
return op
|
|
|
|
def __call__(self, stmt):
|
|
"""Call mutator on statement.
|
|
|
|
Parameters
|
|
----------
|
|
stmt : tvm.tirx.Stmt
|
|
The statement to be mutated.
|
|
|
|
Returns
|
|
-------
|
|
result : tvm.tirx.Stmt
|
|
The mutated statement
|
|
"""
|
|
return self.visit_stmt(stmt)
|
|
|
|
|
|
class StmtExprVisitor(StmtVisitor, ExprVisitor):
|
|
"""A visitor over both statements and expressions.
|
|
|
|
This class inherits from both StmtVisitor and ExprVisitor to recursively visit
|
|
both statements and expressions.
|
|
"""
|
|
|
|
def __init__(self):
|
|
StmtVisitor.__init__(self)
|
|
self._stmt_dispatch_map = self._dispatch_map.copy()
|
|
ExprVisitor.__init__(self)
|
|
self._expr_dispatch_map = self._dispatch_map.copy()
|
|
self._dispatch_map = {}
|
|
self._dispatch_map.update(self._stmt_dispatch_map)
|
|
self._dispatch_map.update(self._expr_dispatch_map)
|
|
|
|
def visit_expr(self, expr):
|
|
"""Visit an expression used in a statement.
|
|
|
|
Parameters
|
|
----------
|
|
expr : Expr
|
|
The expression to be visited.
|
|
"""
|
|
return ExprVisitor.visit_expr(self, expr)
|
|
|
|
|
|
class StmtExprMutator(StmtMutator, ExprMutator):
|
|
"""A mutator over both statements and expressions.
|
|
|
|
This class inherits from both StmtMutator and ExprMutator to recursively transform
|
|
both statements and expressions.
|
|
"""
|
|
|
|
def __init__(self):
|
|
StmtMutator.__init__(self)
|
|
self._stmt_dispatch_map = self._dispatch_map.copy()
|
|
ExprMutator.__init__(self)
|
|
self._expr_dispatch_map = self._dispatch_map.copy()
|
|
self._dispatch_map = {}
|
|
self._dispatch_map.update(self._stmt_dispatch_map)
|
|
self._dispatch_map.update(self._expr_dispatch_map)
|
|
|
|
def visit_expr(self, expr):
|
|
"""Mutate an expression used in a statement.
|
|
|
|
Parameters
|
|
----------
|
|
expr : Expr
|
|
The expression to be mutated.
|
|
|
|
Returns
|
|
-------
|
|
result : Expr
|
|
The mutated expression.
|
|
"""
|
|
return ExprMutator.visit_expr(self, expr)
|
|
|
|
|
|
def ir_transform(stmt, preorder, postorder, only_enable=None):
|
|
"""Recursively visit and transform ir nodes in post DFS order.
|
|
|
|
Parameters
|
|
----------
|
|
stmt : tvm.tirx.Stmt
|
|
The input to be transformed.
|
|
|
|
preorder: function
|
|
The function called in before recursive mutation
|
|
If preorder returns None, then the transform will proceed to recursive call.
|
|
If preorder returns a not None tvm.tirx.Stmt/Expr, the transformer will simply return it and
|
|
won't do further recursion.
|
|
|
|
postorder : function
|
|
The function called after recursive mutation.
|
|
|
|
only_enable : Optional[List[str]]
|
|
List of types that we only enable.
|
|
|
|
Returns
|
|
-------
|
|
result : tvm.tirx.Stmt
|
|
The result.
|
|
"""
|
|
return _ffi_api.IRTransform(stmt, preorder, postorder, only_enable) # type: ignore
|
|
|
|
|
|
def post_order_visit(node, fvisit):
|
|
"""Recursively visit a statement or expression in post DFS order, applying fvisit.
|
|
Each node is guaranteed to be visited only once.
|
|
|
|
Parameters
|
|
----------
|
|
node : tvm.tirx.Stmt or tvm.ir.Expr
|
|
The statement or expression to visit.
|
|
|
|
fvisit: function
|
|
The visitor function.
|
|
"""
|
|
return _ffi_api.PostOrderVisit(node, fvisit) # type: ignore
|
|
|
|
|
|
def pre_order_visit(node, fvisit):
|
|
"""Recursively visit a statement or expression in pre-order, applying fvisit.
|
|
If fvisit returns False, it won't visit the children of the node.
|
|
|
|
Parameters
|
|
----------
|
|
node : tvm.tirx.Stmt or tvm.ir.Expr
|
|
The statement or expression to visit.
|
|
|
|
fvisit: function of the signature Object -> bool
|
|
The visitor function.
|
|
"""
|
|
return _ffi_api.PreOrderVisit(node, fvisit) # type: ignore
|
|
|
|
|
|
def substitute(node, vmap):
|
|
"""Substitute the var specified by vmap.
|
|
|
|
Parameters
|
|
----------
|
|
node: ObjectRef
|
|
The input.
|
|
|
|
vmap : Dict[Var, Expr]
|
|
The variable mapping.
|
|
|
|
Returns
|
|
-------
|
|
result : tvm.tirx.Stmt
|
|
The result.
|
|
"""
|
|
return _ffi_api.Substitute(node, vmap) # type: ignore
|
|
|
|
|
|
def renew_defs(func: PrimFunc):
|
|
"""Re-generate the definition nodes for a TIR, including VarDef, BufferDef.
|
|
This pass works as a simple DeepCopy to duplicate a function with different Vars and
|
|
Buffers but the same behavior
|
|
|
|
Parameters
|
|
----------
|
|
func: PrimFunc
|
|
The input function
|
|
|
|
Returns
|
|
-------
|
|
result : PrimFunc
|
|
The new generated func.
|
|
"""
|
|
return _ffi_api.RenewDefs(func) # type: ignore
|