# 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. """Iterator (quasi)affine mapping patterns.""" from enum import IntEnum import tvm_ffi from tvm.ir import Expr from tvm.runtime import Object from . import _ffi_api @tvm_ffi.register_object("arith.IterMapExpr") class IterMapExpr(Expr): """Base class of all IterMap expressions.""" @tvm_ffi.register_object("arith.IterMark") class IterMark(Object): """Mark the source as an iterator in [0, extent). Parameters ---------- source : Expr. The source expression. extent : Expr The extent of the iterator. """ def __init__(self, source, extent): self.__init_handle_by_constructor__(_ffi_api.IterMark, source, extent) @tvm_ffi.register_object("arith.IterSplitExpr") class IterSplitExpr(IterMapExpr): """Split of an iterator. result = floormod(floordiv(source, lower_factor), extent) * scale Parameters ---------- source : IterMark The source marked iterator. lower_factor : Expr The lower factor to split the domain. extent : Expr The extent of the split. scale : Expr Additional scale to the split. """ def __init__(self, source, lower_factor, extent, scale): self.__init_handle_by_constructor__( _ffi_api.IterSplitExpr, source, lower_factor, extent, scale ) @tvm_ffi.register_object("arith.IterSumExpr") class IterSumExpr(IterMapExpr): """Fuse multiple iterators by summing them with scaling. result = sum(args) + base Parameters ---------- args : List[IterSplitExpr] The input to the sum expression. base : Expr The base offset. """ def __init__(self, args, base): self.__init_handle_by_constructor__(_ffi_api.IterSumExpr, args, base) @tvm_ffi.register_object("arith.IterMapResult") class IterMapResult(Object): """Result of iter map detection.""" class IterMapLevel(IntEnum): """Possible kinds of iter mapping check level.""" Bijective = 0 Surjective = 1 NoCheck = 3 @staticmethod def from_str(name: str): """Helper to create level enum from string""" if name is None: return IterMapLevel.NoCheck name = name.lower() if name == "bijective": check_level = IterMapLevel.Bijective elif name == "surjective": check_level = IterMapLevel.Surjective elif name == "nocheck": check_level = IterMapLevel.NoCheck else: raise ValueError(f"Unknown check level {name}") return check_level def detect_iter_map( indices, input_iters, predicate=True, check_level=IterMapLevel.Surjective, simplify_trivial_iterators=True, analyzer=None, ): """Detect if indices can be written as mapped iters from input iters Parameters ---------- indices : List[Expr] The input indices input_iters : Map[tvm.tirx.Var, Range] The domain of each input iterators. predicate : Expr The predicate constraints on the input iterators check_level : Union[str, IterMapLevel] Checking level of iteration mapping simplify_trivial_iterators: bool If true, iterators with extent of 1 will be replaced with a constant value. analyzer : Optional[tvm.arith.Analyzer] The analyzer to use. When provided, its accumulated bindings and constraints are reused; otherwise a fresh analyzer is created. Returns ------- results : IterMapResult The iter map matching result. The result's .indices is empty array if no match can be found. """ if isinstance(check_level, str): check_level = IterMapLevel.from_str(check_level) elif check_level is None: check_level = IterMapLevel.NoCheck return _ffi_api.DetectIterMap( indices, input_iters, predicate, check_level, simplify_trivial_iterators, analyzer ) def normalize_to_iter_sum(index, input_iters, analyzer=None): """Normalize expr to iter sum. The normalized result ensures that each scale is in the form of (symbol_prod) * cscale It will also sort in desc order by cscale then len(symbol_prod). Parameters ---------- index : Expr The input index input_iters : Map[tvm.tirx.Var, Range] The domain of each input iterators. analyzer : Optional[tvm.arith.Analyzer] The analyzer to use. When provided, its accumulated bindings and constraints are reused; otherwise a fresh analyzer is created. Returns ------- iter_sum: IterSumExpr The result iter sum Note ---- This function does best effort detection, so some undetected part can go into iter_sum.base This function is useful to decide the stride multiplier and division factor in buffer access patterns. """ return _ffi_api.NormalizeToIterSum(index, input_iters, analyzer) def iter_map_simplify( indices, input_iters, predicate=True, check_level=IterMapLevel.Surjective, simplify_trivial_iterators=True, analyzer=None, ): """Simplify the indices using iter map detection. Parameters ---------- indices : List[Expr] The input indices input_iters : Map[tvm.tirx.Var, Range] The domain of each input iterators. predicate : Expr The predicate constraints on the input iterators check_level : Union[str, IterMapLevel] Checking level of iteration mapping simplify_trivial_iterators: bool If true, iterators with extent of 1 will be replaced with a constant value. analyzer : Optional[tvm.arith.Analyzer] The analyzer to use. When provided, its accumulated bindings and constraints are reused; otherwise a fresh analyzer is created. Returns ------- results : IterMapResult The iter map matching result. The result's .indices is empty array if no match can be found. """ if isinstance(check_level, str): check_level = IterMapLevel.from_str(check_level) elif check_level is None: check_level = IterMapLevel.NoCheck return _ffi_api.IterMapSimplify( indices, input_iters, predicate, check_level, simplify_trivial_iterators, analyzer ) def normalize_iter_map_to_expr(expr): """Given an IterMapExpr, transform it to normal Expr Parameters ---------- expr : IterMapExpr the input IterMapExpr Returns ------- result : Expr the corresponding normal Expr """ return _ffi_api.NormalizeIterMapToExpr(expr) def subspace_divide( bindings, input_iters, sub_iters, predicate=True, check_level=IterMapLevel.Surjective, simplify_trivial_iterators=True, analyzer=None, ): """Detect if bindings can be written as ``[a_0*e_0 + b_0 + c_0, a_1*e_1 + b_1, ..., a_n*e_n + b_n]`` where:: a = some-quasi-affine-iter-map(input_iters set_minus sub_iters) b = some-quasi-affine-iter-map(sub_iters) c is constant symbols e is the extent of b For example:: z*12 + y*3 + x + c = (z*4+y)*3 + x bindings = [z*12 + y*3 + x + c] input_iters = [z, y, x] sub_iter = [x] Then the result will be [a, b] where a = [z*4 + y] b = [x] Parameters ---------- bindings : List[Expr] The input bindings input_iters : Map[tvm.tirx.Var, Range] The domain of input iterator, which is the basis of the whole space sub_iters : Array[tvm.tirx.Var] The subset of input_iters, which is the basis of the subspace predicate : Expr The predicate constraints on the input iterators check_level : Union[str, IterMapLevel] Checking level of iteration mapping simplify_trivial_iterators: bool If true, iterators with extent of 1 will be replaced with a constant value. analyzer : Optional[tvm.arith.Analyzer] The analyzer to use. When provided, its accumulated bindings and constraints are reused; otherwise a fresh analyzer is created. Returns ------- results : List[List[Expr]] The result list has length ``len(bindings) + 1``. - ``[0, len(bindings))``: The iter map matching result. The inner list is of length 2. The first expr is the basis of the quotient space. The second expr is the basis of the subspace. - ``len(bindings)``: the predicate of outer space and inner space. - Empty array if no match can be found. """ if isinstance(check_level, str): check_level = IterMapLevel.from_str(check_level) return _ffi_api.SubspaceDivide( bindings, input_iters, sub_iters, predicate, check_level, simplify_trivial_iterators, analyzer, ) def inverse_affine_iter_map(iter_map, outputs): """Apply the inverse of the affine transformation to the outputs. Similar to the back-propagation, starting from the outputs, it visits the DAG of the expressions in reverse topology order and applies the inverse of the affine transformation until it reaches the input. The affine iter map is required to be bijective. For example, iter_map = [l0 // 16, l0 % 16], outputs = [output_0, output_1], the affine transformation specified by `iter_map` will be applied to `outputs` and the result will be {l0: ((output_0*16) + output_1)}. See also :any:`detect_iter_map`. Parameters ---------- iter_map : List[IterSumExpr] The bijective affine iter map. outputs : List[Expr] The outputs of the affine transformation. Returns ------- results : Map[tvm.tirx.Var, Expr] The map from the input to the transformed result. """ return _ffi_api.InverseAffineIterMap(iter_map, outputs)