# 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. # ruff: noqa: F841 import tvm import tvm.testing from tvm import tirx from tvm.tirx import ( EQ, LT, Add, Cast, Evaluate, FloatImm, For, IfThenElse, IntImm, Max, Min, Mul, PyStmtExprMutator, PyStmtExprVisitor, StringImm, Sub, Var, ) class ASTLog: """Helper class to log AST""" def __init__(self) -> None: self.log = [] self.indent = "\t" self.level = 0 def push_scope(self): self.level += 1 def pop_scope(self): self.level -= 1 def add(self, s: str): self.log.append(self.indent * self.level + s) def __str__(self) -> str: return "\n".join(self.log) @tirx.functor.visitor class ASTPrinter(PyStmtExprVisitor): """Print tirx AST in structured format. The shape of Node is ignored.""" def __init__(self) -> None: super().__init__() self.log = ASTLog() def visit_var_(self, op: Var) -> None: self.log.add("Stmt: Var") super().visit_var_(op) def visit_add_(self, op: Add) -> None: self.log.add("Stmt: Add") super().visit_add_(op) @tirx.functor.visitor class SimpleExprCounter(PyStmtExprVisitor): """Count expressions without recursion""" def __init__(self): super().__init__() self.var_count = 0 self.add_count = 0 self.mul_count = 0 def visit_var_(self, op: Var): self.var_count += 1 # Don't recursively visit children to avoid infinite recursion def visit_add_(self, op: Add): self.add_count += 1 # Visit children manually super().visit_add_(op) def visit_mul_(self, op: Mul): self.mul_count += 1 # Visit children manually super().visit_mul_(op) @tirx.functor.mutator class VariableReplacer(PyStmtExprMutator): """Replace variables with constants""" def __init__(self, replacements): super().__init__() self.replacements = replacements def visit_var_(self, op: Var): if op.name in self.replacements: return IntImm("int32", self.replacements[op.name]) return op @tirx.functor.mutator class AddToSubMutator(PyStmtExprMutator): """Convert Add operations to Sub operations""" def visit_add_(self, op: Add): # First mutate the operands a = self.visit_expr(op.a) b = self.visit_expr(op.b) # Convert Add to Sub return Sub(a, b) @tirx.functor.visitor class SimpleStmtCounter(PyStmtExprVisitor): """Count statements without recursion""" def __init__(self): super().__init__() self.for_count = 0 self.if_count = 0 self.evaluate_count = 0 def visit_for_(self, op: For): self.for_count += 1 super().visit_for_(op) def visit_if_then_else_(self, op: IfThenElse): self.if_count += 1 super().visit_if_then_else_(op) def visit_evaluate_(self, op: Evaluate): self.evaluate_count += 1 super().visit_evaluate_(op) @tirx.functor.mutator class ForLoopUnroller(PyStmtExprMutator): """Simple loop unroller for demonstration""" def __init__(self, unroll_factor=2): super().__init__() self.unroll_factor = unroll_factor def visit_for_(self, op: For): # For demonstration, just return the original for now # In a real implementation, we would unroll small loops return super().visit_for_(op) @tirx.functor.visitor class SimpleStmtExprVisitor(PyStmtExprVisitor): """Visitor that handles both statements and expressions""" def __init__(self): super().__init__() self.expr_count = 0 self.stmt_count = 0 self.var_names = set() def visit_var_(self, op: Var): self.var_names.add(op.name) self.expr_count += 1 def visit_evaluate_(self, op: Evaluate): self.stmt_count += 1 # Visit the expression self.visit_expr(op.value) @tirx.functor.mutator class ComplexMutator(PyStmtExprMutator): """Mutator that handles both statements and expressions""" def __init__(self): super().__init__() self.modifications = 0 def visit_add_(self, op: Add): self.modifications += 1 # Convert a + b to a * 2 + b for demonstration a = self.visit_expr(op.a) b = self.visit_expr(op.b) return Add(Mul(a, IntImm("int32", 2)), b) def test_basic_visitor(): """Test the basic AST printer visitor""" expr = Add(Var("x", dtype="int32"), Var("y", dtype="int32")) printer = ASTPrinter() printer.visit_expr(expr) assert str(printer.log) == "\n".join(["Stmt: Add", "Stmt: Var", "Stmt: Var"]) def test_simple_expr_counter(): """Test simple expression counting visitor""" x = Var("x", dtype="int32") y = Var("y", dtype="int32") # Create simple expression: x + y expr = Add(x, y) counter = SimpleExprCounter() counter.visit_expr(expr) assert counter.var_count == 2 # x and y assert counter.add_count == 1 # one add def test_variable_replacer(): """Test expression mutator that replaces variables""" x = Var("x", dtype="int32") y = Var("y", dtype="int32") expr = Add(x, Mul(y, IntImm("int32", 3))) replacer = VariableReplacer({"x": 10, "y": 5}) result = replacer.visit_expr(expr) # Should be Add(IntImm(10), Mul(IntImm(5), IntImm(3))) assert isinstance(result, Add) assert isinstance(result.a, IntImm) assert result.a.value == 10 assert isinstance(result.b, Mul) assert isinstance(result.b.a, IntImm) assert result.b.a.value == 5 def test_add_to_sub_mutator(): """Test mutator that converts Add to Sub""" x = Var("x", dtype="int32") y = Var("y", dtype="int32") expr = Add(x, y) mutator = AddToSubMutator() result = mutator.visit_expr(expr) assert isinstance(result, Sub) assert isinstance(result.a, Var) assert isinstance(result.b, Var) assert result.a.name == "x" assert result.b.name == "y" def test_simple_stmt_counter(): """Test statement visitor that counts statements""" i = Var("i", dtype="int32") # Create a simple for loop loop_body = Evaluate(IntImm("int32", 0)) for_stmt = For(i, IntImm("int32", 0), IntImm("int32", 10), tirx.ForKind.SERIAL, loop_body) counter = SimpleStmtCounter() counter.visit_stmt(for_stmt) assert counter.for_count == 1 # One for loop assert counter.evaluate_count == 1 # One evaluate in the body def test_if_then_else_visitor(): """Test visitor with if-then-else statements""" x = Var("x", dtype="int32") condition = EQ(x, IntImm("int32", 0)) then_stmt = Evaluate(IntImm("int32", 1)) else_stmt = Evaluate(IntImm("int32", 2)) if_stmt = IfThenElse(condition, then_stmt, else_stmt) counter = SimpleStmtCounter() counter.visit_stmt(if_stmt) assert counter.if_count == 1 assert counter.for_count == 0 def test_simple_stmt_expr_visitor(): """Test stmt_expr_visitor with mixed statements and expressions""" x = Var("x", dtype="int32") y = Var("y", dtype="int32") # Create an evaluate statement with an expression expr = Add(x, y) stmt = Evaluate(expr) visitor = SimpleStmtExprVisitor() visitor.visit_stmt(stmt) assert visitor.stmt_count == 1 # One Evaluate statement assert visitor.expr_count == 2 # Two variables assert "x" in visitor.var_names assert "y" in visitor.var_names def test_complex_mutator(): """Test stmt_expr_mutator""" x = Var("x", dtype="int32") y = Var("y", dtype="int32") # Expression with Add operations expr = Add(x, y) stmt = Evaluate(expr) mutator = ComplexMutator() result = mutator.visit_stmt(stmt) print(type(mutator)) assert mutator.modifications == 1 # One Add operation modified assert isinstance(result, Evaluate) # Check that the expression was modified modified_expr = result.value assert isinstance(modified_expr, Add) assert isinstance(modified_expr.a, Mul) # First operand should be multiplied by 2 def test_different_expr_types(): """Test visitor with various expression types""" x = Var("x", dtype="int32") # Test different expression types individually exprs = [ IntImm("int32", 42), FloatImm("float32", 3.14), StringImm("hello"), Cast("float32", x), Min(x, IntImm("int32", 10)), Max(x, IntImm("int32", 0)), LT(x, IntImm("int32", 5)), ] # Just test that we can create and visit each type counter = SimpleExprCounter() for expr in exprs: try: counter.visit_expr(expr) except Exception as e: # Some expressions might not be supported, that's ok pass def test_decorator_functionality(): """Test that decorators work correctly""" # Test that decorated classes are properly wrapped visitor = SimpleExprCounter() assert hasattr(visitor, "_outer") # Should have the wrapper functionality mutator = VariableReplacer({}) assert hasattr(mutator, "_outer") def test_empty_expressions(): """Test handling of simple expressions""" counter = SimpleExprCounter() # Test with just a variable x = Var("x", dtype="int32") counter.visit_expr(x) assert counter.var_count == 1 # Test with just a constant counter = SimpleExprCounter() const = IntImm("int32", 5) counter.visit_expr(const) # Constants don't increase var_count assert counter.var_count == 0 def test_stmt_mutator(): """Test basic statement mutator functionality""" x = Var("x", dtype="int32") stmt = Evaluate(Add(x, IntImm("int32", 1))) unroller = ForLoopUnroller() result = unroller.visit_stmt(stmt) # Should return the same statement (no actual unrolling implemented) assert isinstance(result, Evaluate) def test_nested_expressions(): """Test with nested expressions""" x = Var("x", dtype="int32") y = Var("y", dtype="int32") z = Var("z", dtype="int32") # Create nested expression: (x + y) * z inner_add = Add(x, y) expr = Mul(inner_add, z) counter = SimpleExprCounter() counter.visit_expr(expr) assert counter.var_count == 3 # x, y, z assert counter.add_count == 1 # one add assert counter.mul_count == 1 # one mul def test_simple_mutations(): """Test simple expression mutations""" x = Var("x", dtype="int32") y = Var("y", dtype="int32") # Test multiple replacements expr = Add(x, y) replacer = VariableReplacer({"x": 1, "y": 2}) result = replacer.visit_expr(expr) assert isinstance(result, Add) assert isinstance(result.a, IntImm) assert isinstance(result.b, IntImm) assert result.a.value == 1 assert result.b.value == 2 if __name__ == "__main__": test_basic_visitor() tvm.testing.main()