# 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 relax as rx from tvm.relax.analysis import contains_impure_call from tvm.script import relax as R def test_simple_pure_case(): @tvm.script.ir_module class PureTest: @R.function def pure_func(x: R.Tensor((), "int32")) -> R.Tensor((), "int32"): y = R.add(x, x) z = R.multiply(x, y) return R.add(z, R.const(1, "int32")) assert not contains_impure_call(PureTest["pure_func"]) def test_simple_impure_case(): @tvm.script.ir_module class ImpureTest: @R.function(pure=False) def impure_func() -> R.Any: y = R.print(format="I am a message") return y assert contains_impure_call(ImpureTest["impure_func"]) def test_nested_function(): @tvm.script.ir_module class NestedTest: @R.function def pure_with_impure_nested() -> R.Tensor((), "int32"): # unused @R.function(pure=False) def impure_inner() -> R.Any: y = R.print(format="Another, worse, message") return y x = R.const(0, dtype="int32") return R.add(x, x) assert not contains_impure_call(NestedTest["pure_with_impure_nested"]) assert contains_impure_call( NestedTest["pure_with_impure_nested"].body.blocks[0].bindings[0].value ) def test_ignoring_recursive_call(): # Ignoring a recursive call. This can be useful if some transformation # removes an impure operation and the compiler needs to check if the impure # function has become pure @tvm.script.ir_module class RecursiveTest: @R.function(pure=False) def recursive_impure() -> R.Any: x = R.const(1, "int32") y = R.add(x, x) z = R.print(x, y, format="{} {}") w = RecursiveTest.recursive_impure() return w assert contains_impure_call(RecursiveTest["recursive_impure"]) # but if we remove the impure call... body = RecursiveTest["recursive_impure"].body own_name = body.blocks[0].bindings[-1].value.op # skipping the call to print... new_bindings = [ body.blocks[0].bindings[0], body.blocks[0].bindings[1], body.blocks[0].bindings[-1], ] # Note: we construct the function in this way so that we keep the old vars # with their current Type. That would get fixed during normalization. # However, this situation is meant to correspond to an intermediate state # that might arise within a pass. new_body = rx.SeqExpr([rx.BindingBlock(new_bindings)], body.body) # if we didn't ignore the recursive call, the fact the var's Type # calls it impure would throw it off assert not contains_impure_call(new_body, own_name=own_name) assert contains_impure_call(new_body) if __name__ == "__main__": tvm.testing.main()