def create_expr(symbol_table): '''Utility routine that creates and returns an expression containing a number of array references containing range nodes. The expression looks like the following (using Fortran array notation): x(2:n:2)*z(1,2:n:2)+a(1) :param symbol_table: the symbol table to which we add the array \ symbols. :type symbol_table: :py:class:`psyclone.psyir.symbol.SymbolTable` :returns: an expression containing 3 array references, 2 of which \ contain ranges. :rtype: :py:class:`psyclone.psyir.nodes.BinaryOperation` ''' array_symbol = DataSymbol("x", ArrayType(REAL_TYPE, [10])) symbol_table.add(array_symbol) symbol_n = symbol_table.lookup("n") array_x = Array.create(array_symbol, [create_stepped_range(symbol_n)]) array_symbol = DataSymbol("z", ArrayType(REAL_TYPE, [10, 10])) symbol_table.add(array_symbol) array_z = Array.create( array_symbol, [Literal("1", INTEGER_TYPE), create_stepped_range(symbol_n)]) array_symbol = DataSymbol("a", ArrayType(REAL_TYPE, [10])) array_a = Array.create(array_symbol, [Literal("1", INTEGER_TYPE)]) symbol_table.add(array_symbol) mult = BinaryOperation.create(BinaryOperation.Operator.MUL, array_x, array_z) add = BinaryOperation.create(BinaryOperation.Operator.ADD, mult, array_a) return add
def test_array_range_with_reduction(): ''' Test that we correctly identify an array range when it is the result of a reduction from an array, e.g x(1, INT(SUM(map(:, :), 1))) = 1.0 ''' one = Literal("1.0", REAL_TYPE) int_one = Literal("1", INTEGER_TYPE) int_two = Literal("2", INTEGER_TYPE) int_array_type = ArrayType(INTEGER_SINGLE_TYPE, [10, 10]) map_sym = DataSymbol("map", int_array_type) array_type = ArrayType(REAL_TYPE, [10, 10]) symbol = DataSymbol("x", array_type) lbound1 = BinaryOperation.create(BinaryOperation.Operator.LBOUND, Reference(map_sym), int_one.copy()) ubound1 = BinaryOperation.create(BinaryOperation.Operator.UBOUND, Reference(map_sym), int_one.copy()) my_range1 = Range.create(lbound1, ubound1) lbound2 = BinaryOperation.create(BinaryOperation.Operator.LBOUND, Reference(map_sym), int_two.copy()) ubound2 = BinaryOperation.create(BinaryOperation.Operator.UBOUND, Reference(map_sym), int_two.copy()) my_range2 = Range.create(lbound2, ubound2) bsum_op = BinaryOperation.create( BinaryOperation.Operator.SUM, ArrayReference.create(map_sym, [my_range1, my_range2]), int_one.copy()) int_op2 = UnaryOperation.create(UnaryOperation.Operator.INT, bsum_op) assignment = Assignment.create( ArrayReference.create(symbol, [int_one.copy(), int_op2]), one.copy()) assert assignment.is_array_range is True
def test_range_references_props(): ''' Test that the properties of a Range return what we expect when the start, stop and step are references or expressions. ''' from psyclone.psyir.nodes import BinaryOperation, KernelSchedule sched = KernelSchedule("test_sched") sym_table = sched.symbol_table start_symbol = DataSymbol("istart", INTEGER_SINGLE_TYPE) stop_symbol = DataSymbol("istop", INTEGER_SINGLE_TYPE) step_symbol = DataSymbol("istep", INTEGER_SINGLE_TYPE) sym_table.add(start_symbol) sym_table.add(stop_symbol) sym_table.add(step_symbol) startvar = Reference(start_symbol) stopvar = Reference(stop_symbol) start = BinaryOperation.create(BinaryOperation.Operator.SUB, startvar, Literal("1", INTEGER_SINGLE_TYPE)) stop = BinaryOperation.create(BinaryOperation.Operator.ADD, stopvar, Literal("1", INTEGER_SINGLE_TYPE)) step = Reference(step_symbol) erange = Range.create(start, stop, step) assert erange.start is start assert erange.stop is stop assert erange.step is step assert erange.children[0] is start assert erange.children[1] is stop assert erange.children[2] is step
def test_binaryoperation_create_invalid(): '''Test that the create method in a BinaryOperation class raises the expected exception if the provided input is invalid. ''' ref1 = Reference(DataSymbol("tmp1", REAL_SINGLE_TYPE)) ref2 = Reference(DataSymbol("tmp2", REAL_SINGLE_TYPE)) add = BinaryOperation.Operator.ADD # oper not a BinaryOperation.Operator. with pytest.raises(GenerationError) as excinfo: _ = BinaryOperation.create("invalid", ref1, ref2) assert ("oper argument in create method of BinaryOperation class should " "be a PSyIR BinaryOperation Operator but found 'str'." in str(excinfo.value)) # lhs not a Node. with pytest.raises(GenerationError) as excinfo: _ = BinaryOperation.create(add, "invalid", ref2) assert ("Item 'str' can't be child 0 of 'BinaryOperation'. The valid " "format is: 'DataNode, DataNode'.") in str(excinfo.value) # rhs not a Node. with pytest.raises(GenerationError) as excinfo: _ = BinaryOperation.create(add, ref1, "invalid") assert ("Item 'str' can't be child 1 of 'BinaryOperation'. The valid " "format is: 'DataNode, DataNode'.") in str(excinfo.value)
def test_type_convert_binaryop_create(operation, op_str): '''Test that the create method in the BinaryOperation class correctly creates a BinaryOperation instance for the REAL and INT type-conversion operations.. ''' sym = DataSymbol("tmp1", REAL_SINGLE_TYPE) lhs = Reference(sym) wp_sym = DataSymbol("wp", INTEGER_SINGLE_TYPE) # Reference to a kind parameter rhs = Reference(wp_sym) binaryoperation = BinaryOperation.create(operation, lhs, rhs) assert binaryoperation._operator is operation check_links(binaryoperation, [lhs, rhs]) result = FortranWriter().binaryoperation_node(binaryoperation) assert op_str + "(tmp1, wp)" in result.lower() # Kind specified with an integer literal rhs = Literal("4", INTEGER_SINGLE_TYPE) binaryoperation = BinaryOperation.create(operation, lhs.detach(), rhs) check_links(binaryoperation, [lhs, rhs]) result = FortranWriter().binaryoperation_node(binaryoperation) assert op_str + "(tmp1, 4)" in result.lower() # Kind specified as an arithmetic expression rhs = BinaryOperation.create(BinaryOperation.Operator.ADD, Reference(wp_sym), Literal("2", INTEGER_SINGLE_TYPE)) binaryoperation = BinaryOperation.create(operation, lhs.detach(), rhs) check_links(binaryoperation, [lhs, rhs]) result = FortranWriter().binaryoperation_node(binaryoperation) assert op_str + "(tmp1, wp + 2)" in result.lower()
def test_is_not_array_range(): ''' Test that is_array_range correctly rejects things that aren't an assignment to an array range. ''' int_one = Literal("1", INTEGER_SINGLE_TYPE) one = Literal("1.0", REAL_TYPE) var = DataSymbol("x", REAL_TYPE) reference = Reference(var) # lhs is not an array assignment = Assignment.create(reference, one) assert assignment.is_array_range is False # lhs is an array reference but has no range array_type = ArrayType(REAL_TYPE, [10, 10]) symbol = DataSymbol("y", array_type) array_ref = Reference(symbol) assignment = Assignment.create(array_ref, one.copy()) assert assignment.is_array_range is False # lhs is an array reference but the single index value is obtained # using an array range, y(1, SUM(map(:), 1)) = 1.0 int_array_type = ArrayType(INTEGER_SINGLE_TYPE, [10]) map_sym = DataSymbol("map", int_array_type) start = BinaryOperation.create(BinaryOperation.Operator.LBOUND, Reference(map_sym), int_one.copy()) stop = BinaryOperation.create(BinaryOperation.Operator.UBOUND, Reference(map_sym), int_one.copy()) my_range = Range.create(start, stop) sum_op = BinaryOperation.create(BinaryOperation.Operator.SUM, ArrayReference.create(map_sym, [my_range]), int_one.copy()) assignment = Assignment.create( ArrayReference.create(symbol, [int_one.copy(), sum_op]), one.copy()) assert assignment.is_array_range is False # When the slice has two operator ancestors, one of which is a reduction # e.g y(1, SUM(ABS(map(:)), 1)) = 1.0 abs_op = UnaryOperation.create( UnaryOperation.Operator.ABS, ArrayReference.create(map_sym, [my_range.copy()])) sum_op2 = BinaryOperation.create(BinaryOperation.Operator.SUM, abs_op, int_one.copy()) assignment = Assignment.create( ArrayReference.create(symbol, [int_one.copy(), sum_op2]), one.copy()) assert assignment.is_array_range is False # lhs is a scalar member of a structure grid_type = StructureType.create([ ("dx", REAL_SINGLE_TYPE, Symbol.Visibility.PUBLIC), ("dy", REAL_SINGLE_TYPE, Symbol.Visibility.PUBLIC) ]) grid_type_symbol = DataTypeSymbol("grid_type", grid_type) grid_sym = DataSymbol("grid", grid_type_symbol) assignment = Assignment.create(StructureReference.create(grid_sym, ["dx"]), one.copy()) assert assignment.is_array_range is False
def test_correct_2abs(tmpdir): '''Check that a valid example produces the expected output when there is more than one ABS() in an expression. ''' Config.get().api = "nemo" operation = example_psyir( lambda arg: BinaryOperation.create( BinaryOperation.Operator.MUL, arg, Literal("3.14", REAL_TYPE))) root = operation.root assignment = operation.parent abs_op = UnaryOperation.create(UnaryOperation.Operator.ABS, Literal("1.0", REAL_TYPE)) operation.detach() op1 = BinaryOperation.create(BinaryOperation.Operator.ADD, operation, abs_op) assignment.addchild(op1) writer = FortranWriter() result = writer(root) assert ( "subroutine abs_example(arg)\n" " real, intent(inout) :: arg\n" " real :: psyir_tmp\n\n" " psyir_tmp = ABS(arg * 3.14) + ABS(1.0)\n\n" "end subroutine abs_example\n") in result trans = Abs2CodeTrans() trans.apply(operation, root.symbol_table) trans.apply(abs_op, root.symbol_table) result = writer(root) assert ( "subroutine abs_example(arg)\n" " real, intent(inout) :: arg\n" " real :: psyir_tmp\n" " real :: res_abs\n" " real :: tmp_abs\n" " real :: res_abs_1\n" " real :: tmp_abs_1\n\n" " tmp_abs = arg * 3.14\n" " if (tmp_abs > 0.0) then\n" " res_abs = tmp_abs\n" " else\n" " res_abs = tmp_abs * -1.0\n" " end if\n" " tmp_abs_1 = 1.0\n" " if (tmp_abs_1 > 0.0) then\n" " res_abs_1 = tmp_abs_1\n" " else\n" " res_abs_1 = tmp_abs_1 * -1.0\n" " end if\n" " psyir_tmp = res_abs + res_abs_1\n\n" "end subroutine abs_example\n") in result assert Compile(tmpdir).string_compiles(result) # Remove the created config instance Config._instance = None
def test_correct_2min(tmpdir): '''Check that a valid example produces the expected output when there is more than one MIN() in an expression. ''' Config.get().api = "nemo" operation = example_psyir_binary(lambda arg: arg) root = operation.root assignment = operation.parent operation.detach() min_op = BinaryOperation.create(BinaryOperation.Operator.MIN, Literal("1.0", REAL_TYPE), Literal("2.0", REAL_TYPE)) op1 = BinaryOperation.create(BinaryOperation.Operator.ADD, min_op, operation) assignment.addchild(op1) writer = FortranWriter() result = writer(root) assert ("subroutine min_example(arg, arg_1)\n" " real, intent(inout) :: arg\n" " real, intent(inout) :: arg_1\n" " real :: psyir_tmp\n\n" " psyir_tmp = MIN(1.0, 2.0) + MIN(arg, arg_1)\n\n" "end subroutine min_example\n") in result trans = Min2CodeTrans() trans.apply(operation, root.symbol_table) trans.apply(min_op, root.symbol_table) result = writer(root) assert ("subroutine min_example(arg, arg_1)\n" " real, intent(inout) :: arg\n" " real, intent(inout) :: arg_1\n" " real :: psyir_tmp\n" " real :: res_min\n" " real :: tmp_min\n" " real :: res_min_1\n" " real :: tmp_min_1\n\n" " res_min = arg\n" " tmp_min = arg_1\n" " if (tmp_min < res_min) then\n" " res_min = tmp_min\n" " end if\n" " res_min_1 = 1.0\n" " tmp_min_1 = 2.0\n" " if (tmp_min_1 < res_min_1) then\n" " res_min_1 = tmp_min_1\n" " end if\n" " psyir_tmp = res_min_1 + res_min\n\n" "end subroutine min_example\n") in result assert Compile(tmpdir).string_compiles(result) # Remove the created config instance Config._instance = None
def test_correct_expr(tmpdir): '''Check that a valid example produces the expected output when SIGN is part of an expression. ''' Config.get().api = "nemo" operation = example_psyir(lambda arg: BinaryOperation.create( BinaryOperation.Operator.MUL, arg, Literal("3.14", REAL_TYPE))) root = operation.root assignment = operation.parent operation.detach() op1 = BinaryOperation.create(BinaryOperation.Operator.ADD, Literal("1.0", REAL_TYPE), operation) op2 = BinaryOperation.create(BinaryOperation.Operator.ADD, op1, Literal("2.0", REAL_TYPE)) assignment.addchild(op2) writer = FortranWriter() result = writer(root) assert ("subroutine sign_example(arg, arg_1)\n" " real, intent(inout) :: arg\n" " real, intent(inout) :: arg_1\n" " real :: psyir_tmp\n\n" " psyir_tmp = 1.0 + SIGN(arg * 3.14, arg_1) + 2.0\n\n" "end subroutine sign_example\n") in result trans = Sign2CodeTrans() trans.apply(operation, root.symbol_table) result = writer(root) assert ("subroutine sign_example(arg, arg_1)\n" " real, intent(inout) :: arg\n" " real, intent(inout) :: arg_1\n" " real :: psyir_tmp\n" " real :: res_sign\n" " real :: tmp_sign\n" " real :: res_abs\n" " real :: tmp_abs\n\n" " tmp_abs = arg * 3.14\n" " if (tmp_abs > 0.0) then\n" " res_abs = tmp_abs\n" " else\n" " res_abs = tmp_abs * -1.0\n" " end if\n" " res_sign = res_abs\n" " tmp_sign = arg_1\n" " if (tmp_sign < 0.0) then\n" " res_sign = res_sign * -1.0\n" " end if\n" " psyir_tmp = 1.0 + res_sign + 2.0\n\n" "end subroutine sign_example\n") in result assert Compile(tmpdir).string_compiles(result) # Remove the created config instance Config._instance = None
def test_bound_intrinsic_wrong_type(bound): ''' Check that attempting to create an L/UBOUND intrinsic operator with the wrong type of arguments raises the expected error. ''' with pytest.raises(TypeError) as err: # First argument must be an Array _ = BinaryOperation.create(bound, Literal("1", INTEGER_TYPE), Literal("1", INTEGER_TYPE)) assert "must be an Array but got: 'Literal" in str(err.value) with pytest.raises(TypeError) as err: # Second argument cannot be a real literal _ = BinaryOperation.create(bound, Array("array"), Literal("1.0", REAL_TYPE)) assert ("must be an integer but got a Literal of type REAL" in str(err.value))
def test_reference_accesses_bounds(operator_type): '''Test that the reference_accesses method behaves as expected when the reference is the first argument to either the lbound or ubound intrinsic as that is simply looking up the array bounds (therefore var_access_info should be empty) and when the reference is the second argument of either the lbound or ubound intrinsic (in which case the access should be a read). ''' # Note, one would usually expect UBOUND to provide the upper bound # of a range but to simplify the test both LBOUND and UBOUND are # used for the lower bound. This does not affect the test. one = Literal("1", INTEGER_TYPE) array_symbol = DataSymbol("test", ArrayType(REAL_TYPE, [10])) array_ref1 = Reference(array_symbol) array_ref2 = Reference(array_symbol) array_access = Array.create(array_symbol, [one]) # test when first or second argument to LBOUND or UBOUND is an # array reference operator = BinaryOperation.create(operator_type, array_ref1, array_ref2) array_access.children[0] = Range.create(operator, one, one) var_access_info = VariablesAccessInfo() array_ref1.reference_accesses(var_access_info) assert str(var_access_info) == "" var_access_info = VariablesAccessInfo() array_ref2.reference_accesses(var_access_info) assert str(var_access_info) == "test: READ"
def example_psyir_binary(create_expression): '''Utility function that creates a PSyIR tree containing a binary MIN intrinsic operator and returns the operator. :param function create_expresssion: function used to create the \ content of the first argument of the MIN operator. :returns: PSyIR MIN operator instance. :rtype: :py:class:`psyclone.psyGen.BinaryOperation` ''' symbol_table = SymbolTable() arg1 = symbol_table.new_symbol("arg", symbol_type=DataSymbol, datatype=REAL_TYPE, interface=ArgumentInterface( ArgumentInterface.Access.READWRITE)) arg2 = symbol_table.new_symbol("arg", symbol_type=DataSymbol, datatype=REAL_TYPE, interface=ArgumentInterface( ArgumentInterface.Access.READWRITE)) arg3 = symbol_table.new_symbol(symbol_type=DataSymbol, datatype=REAL_TYPE) symbol_table.specify_argument_list([arg1, arg2]) var1 = Reference(arg1) var2 = Reference(arg2) var3 = Reference(arg3) oper = BinaryOperation.Operator.MIN operation = BinaryOperation.create(oper, create_expression(var1), var2) assign = Assignment.create(var3, operation) _ = KernelSchedule.create("min_example", symbol_table, [assign]) return operation
def test_datasymbol_constant_value_setter(): '''Test that a DataSymbol constant value can be set if given a new valid constant value.''' # Test with valid constant values sym = DataSymbol('a', INTEGER_SINGLE_TYPE, constant_value=7) assert sym.constant_value.value == "7" sym.constant_value = 9 assert sym.constant_value.value == "9" sym = DataSymbol('a', REAL_SINGLE_TYPE, constant_value=3.1415) assert sym.constant_value.value == "3.1415" sym.constant_value = 1.0 assert sym.constant_value.value == "1.0" sym = DataSymbol('a', BOOLEAN_TYPE, constant_value=True) assert sym.constant_value.value == "true" sym.constant_value = False assert sym.constant_value.value == "false" # Test with valid constant expressions lhs = Literal('2', INTEGER_SINGLE_TYPE) rhs = Reference(DataSymbol('constval', INTEGER_SINGLE_TYPE)) ct_expr = BinaryOperation.create(BinaryOperation.Operator.ADD, lhs, rhs) sym = DataSymbol('a', INTEGER_SINGLE_TYPE, constant_value=ct_expr) assert isinstance(sym.constant_value, BinaryOperation) assert sym.constant_value is ct_expr
def test_arraytype(): '''Test that the ArrayType class __init__ works as expected. Test the different dimension datatypes that are supported.''' scalar_type = ScalarType(ScalarType.Intrinsic.INTEGER, 4) data_symbol = DataSymbol("var", scalar_type, constant_value=30) one = Literal("1", scalar_type) var_plus_1 = BinaryOperation.create( BinaryOperation.Operator.ADD, Reference(data_symbol), one) literal = Literal("20", scalar_type) array_type = ArrayType( scalar_type, [10, literal, var_plus_1, Reference(data_symbol), ArrayType.Extent.DEFERRED, ArrayType.Extent.ATTRIBUTE]) assert isinstance(array_type, ArrayType) assert len(array_type.shape) == 6 # Provided as an int but stored as a Literal shape0 = array_type.shape[0] assert isinstance(shape0, Literal) assert shape0.value == "10" assert shape0.datatype.intrinsic == ScalarType.Intrinsic.INTEGER assert shape0.datatype.precision == ScalarType.Precision.UNDEFINED # Provided and stored as a Literal (DataNode) assert array_type.shape[1] is literal # Provided and stored as an Operator (DataNode) assert array_type.shape[2] is var_plus_1 # Provided and stored as a Reference to a DataSymbol assert isinstance(array_type.shape[3], Reference) assert array_type.shape[3].symbol is data_symbol # Provided and stored as a deferred extent assert array_type.shape[4] == ArrayType.Extent.DEFERRED # Provided as an attribute extent assert array_type.shape[5] == ArrayType.Extent.ATTRIBUTE
def test_validate_intrinsic(): '''Check that the validate method returns an exception if the rhs of the assignment contains an operator that only returns an array i.e. can't be performed elementwise. At the moment MATMUL is the only operator of this type. ''' symbol_table = SymbolTable() array_x = create_array_x(symbol_table) array_y_2 = create_array_y_2d_slice(symbol_table) matmul = BinaryOperation.create(BinaryOperation.Operator.MATMUL, array_y_2, array_x) reference = ArrayReference.create( symbol_table.lookup("x"), [create_range(symbol_table.lookup("x"), 1)]) assignment = Assignment.create(reference, matmul) trans = ArrayRange2LoopTrans() with pytest.raises(TransformationError) as info: trans.validate(assignment) assert ("Error in ArrayRange2LoopTrans transformation. The rhs of the " "supplied Assignment node 'BinaryOperation[operator:'MATMUL']\n" "ArrayReference[name:'y2']\nRange[]\nRange[]\n\n" "ArrayReference[name:'x']\nRange[]\n' contains the " "MATMUL operator which can't be performed elementwise." in str(info.value))
def test_real_binaryop_invalid(): ''' Test that the create method rejects invalid precisions. ''' sym = DataSymbol("tmp1", REAL_SINGLE_TYPE) oper = BinaryOperation.Operator.REAL with pytest.raises(TypeError) as err: _ = BinaryOperation.create(oper, Reference(sym), Literal("1.0", REAL_SINGLE_TYPE)) assert ("Precision argument to REAL operation must be specified using a " "DataSymbol, ScalarType.PRECISION or integer Literal but got " "xxxx" in str(err.value)) # A Symbol of REAL type cannot be used to specify a precision wrong_kind = DataSymbol("not_wp", REAL_SINGLE_TYPE) with pytest.raises(TypeError) as err: _ = BinaryOperation.create(oper, Reference(sym), Reference(wrong_kind)) assert ("If the precision argument to a REAL operation is a Reference " "then it must be to a symbol of integer type but got: 'yyyy'" in str(err.value))
def _get_array_bound(array, index): '''A utility function that returns the appropriate loop bounds (lower, upper and step) for a particular index of an array reference. At the moment all entries for the index are assumed to be accessed. If the array symbol is declared with known bounds (an integer or a symbol) then these bound values are used. If the size is unknown (a deferred or attribute type) then the LBOUND and UBOUND PSyIR nodes are used. :param array: the reference that we are interested in. :type array: :py:class:`psyir.nodes.Reference` :param int index: the (array) reference index that we are \ interested in. :returns: the loop bounds for this array index. :rtype: (Literal, Literal, Literal) or \ (BinaryOperation, BinaryOperation, Literal) :raises TransformationError: if the shape of the array's symbol is \ not supported. ''' # Added import here to avoid circular dependencies. # pylint: disable=import-outside-toplevel from psyclone.psyir.transformations import TransformationError my_dim = array.symbol.shape[index] if isinstance(my_dim, DataNode): lower_bound = Literal("1", INTEGER_TYPE) upper_bound = my_dim elif isinstance(my_dim, DataSymbol): lower_bound = Literal("1", INTEGER_TYPE) upper_bound = Reference(my_dim) elif my_dim in [ArrayType.Extent.DEFERRED, ArrayType.Extent.ATTRIBUTE]: lower_bound = BinaryOperation.create(BinaryOperation.Operator.LBOUND, Reference(array.symbol), Literal(str(index), INTEGER_TYPE)) upper_bound = BinaryOperation.create(BinaryOperation.Operator.UBOUND, Reference(array.symbol), Literal(str(index), INTEGER_TYPE)) else: raise TransformationError( "Unsupported index type '{0}' found for dimension {1} of array " "'{2}'.".format(type(my_dim).__name__, index + 1, array.name)) step = Literal("1", INTEGER_TYPE) return (lower_bound, upper_bound, step)
def test_array_is_lower_bound(): '''Test that the is_lower_bound method in the Array Node works as expected. ''' one = Literal("1", INTEGER_TYPE) array = ArrayReference.create(DataSymbol("test", ArrayType(REAL_TYPE, [10])), [one]) with pytest.raises(TypeError) as info: array.is_lower_bound("hello") assert ("The index argument should be an integer but found 'str'." in str(info.value)) # not a range node at index 0 assert not array.is_lower_bound(0) # range node does not have a binary operator for its start value array.children[0] = Range.create(one, one, one) assert not array.is_lower_bound(0) # range node lbound references a different array array2 = ArrayReference.create(DataSymbol("test2", ArrayType(REAL_TYPE, [10])), [one]) operator = BinaryOperation.create( BinaryOperation.Operator.LBOUND, array2, Literal("1", INTEGER_TYPE)) array.children[0] = Range.create(operator, one, one) assert not array.is_lower_bound(0) # range node lbound references a different index operator = BinaryOperation.create( BinaryOperation.Operator.LBOUND, array, Literal("2", INTEGER_TYPE)) array.children[0] = Range.create(operator, one, one) assert not array.is_lower_bound(0) # all is well operator = BinaryOperation.create( BinaryOperation.Operator.LBOUND, array, one) array.children[0] = Range.create(operator, one, one) assert array.is_lower_bound(0)
def test_validate5(): '''Check that the Matmul2Code validate method raises the expected exception when the supplied node is a MATMUL binary operation but either or both arguments are not references. ''' trans = Matmul2CodeTrans() array_type = ArrayType(REAL_TYPE, [10]) array = Array.create(DataSymbol("x", array_type), [Literal("10", INTEGER_TYPE)]) mult = BinaryOperation.create(BinaryOperation.Operator.MUL, array, array) matmul = BinaryOperation.create(BinaryOperation.Operator.MATMUL, mult, mult) _ = Assignment.create(array, matmul) with pytest.raises(TransformationError) as excinfo: trans.validate(matmul) assert ("Expected children of a MATMUL BinaryOperation to be references, " "but found 'BinaryOperation', 'BinaryOperation'." in str(excinfo.value))
def test_correct_expr(tmpdir): '''Check that a valid example produces the expected output when MIN() is part of an expression. ''' Config.get().api = "nemo" operation = example_psyir_binary(lambda arg: arg) assignment = operation.parent op1 = BinaryOperation.create(BinaryOperation.Operator.ADD, Literal("1.0", REAL_TYPE), operation) op2 = BinaryOperation.create(BinaryOperation.Operator.ADD, op1, Literal("2.0", REAL_TYPE)) op2.parent = assignment assignment.children[1] = op2 writer = FortranWriter() result = writer(operation.root) assert ("subroutine min_example(arg,arg_1)\n" " real, intent(inout) :: arg\n" " real, intent(inout) :: arg_1\n" " real :: psyir_tmp\n\n" " psyir_tmp=1.0 + MIN(arg, arg_1) + 2.0\n\n" "end subroutine min_example\n") in result trans = Min2CodeTrans() trans.apply(operation, operation.root.symbol_table) result = writer(operation.root) assert ("subroutine min_example(arg,arg_1)\n" " real, intent(inout) :: arg\n" " real, intent(inout) :: arg_1\n" " real :: psyir_tmp\n" " real :: res_min\n" " real :: tmp_min\n\n" " res_min=arg\n" " tmp_min=arg_1\n" " if (tmp_min < res_min) then\n" " res_min=tmp_min\n" " end if\n" " psyir_tmp=1.0 + res_min + 2.0\n\n" "end subroutine min_example\n") in result assert Compile(tmpdir).string_compiles(result) # Remove the created config instance Config._instance = None
def test_validate4(): '''Check that the Matmul2Code validate method raises the expected exception when the supplied node is a MATMUL binary operation but it is not the only operation on the RHS of an assignment. ''' trans = Matmul2CodeTrans() vector_type = ArrayType(REAL_TYPE, [10]) array_type = ArrayType(REAL_TYPE, [10, 10]) vector = Reference(DataSymbol("x", vector_type)) array = Reference(DataSymbol("y", array_type)) matmul = BinaryOperation.create(BinaryOperation.Operator.MATMUL, array, vector) rhs = BinaryOperation.create(BinaryOperation.Operator.MUL, matmul, vector) _ = Assignment.create(array, rhs) with pytest.raises(TransformationError) as excinfo: trans.validate(matmul) assert ("Transformation Error: Matmul2CodeTrans only supports the " "transformation of a MATMUL operation when it is the sole " "operation on the rhs of an assignment." in str(excinfo.value))
def test_cw_binaryoperator(): '''Check the CWriter class binary_operation method correctly prints out the C representation of any given BinaryOperation. ''' cwriter = CWriter() # Test UnaryOperation without children. binary_operation = BinaryOperation(BinaryOperation.Operator.ADD) with pytest.raises(VisitorError) as err: _ = cwriter(binary_operation) assert ("BinaryOperation malformed or incomplete. It should have " "exactly 2 children, but found 0." in str(err.value)) # Test with children ref1 = Reference(DataSymbol("a", REAL_TYPE)) ref2 = Reference(DataSymbol("b", REAL_TYPE)) binary_operation = BinaryOperation.create(BinaryOperation.Operator.ADD, ref1, ref2) assert cwriter(binary_operation) == '(a + b)' # Test all supported Operators test_list = ((BinaryOperation.Operator.ADD, '(a + b)'), (BinaryOperation.Operator.SUB, '(a - b)'), (BinaryOperation.Operator.MUL, '(a * b)'), (BinaryOperation.Operator.DIV, '(a / b)'), (BinaryOperation.Operator.REM, '(a % b)'), (BinaryOperation.Operator.POW, 'pow(a, b)'), (BinaryOperation.Operator.EQ, '(a == b)'), (BinaryOperation.Operator.NE, '(a != b)'), (BinaryOperation.Operator.GT, '(a > b)'), (BinaryOperation.Operator.GE, '(a >= b)'), (BinaryOperation.Operator.LT, '(a < b)'), (BinaryOperation.Operator.LE, '(a <= b)'), (BinaryOperation.Operator.AND, '(a && b)'), (BinaryOperation.Operator.OR, '(a || b)'), (BinaryOperation.Operator.SIGN, 'copysign(a, b)')) for operator, expected in test_list: binary_operation._operator = operator assert cwriter(binary_operation) == expected # Test that an unsupported operator raises a error class Unsupported(object): '''Dummy class''' def __init__(self): pass binary_operation._operator = Unsupported with pytest.raises(VisitorError) as err: _ = cwriter(binary_operation) assert "The C backend does not support the '" in str(err.value) assert "' operator." in str(err.value)
def test_string_compare(): '''Check that the string_compare utility function in ArrayRange2LoopTrans works as expected. ''' with pytest.raises(TypeError) as info: ArrayRange2LoopTrans.string_compare(None, None) assert ("The first argument to the string_compare method should be a Node " "but found 'NoneType'." in str(info.value)) with pytest.raises(TypeError) as info: ArrayRange2LoopTrans.string_compare(Node(), None) assert ( "The second argument to the string_compare method should be a Node " "but found 'NoneType'." in str(info.value)) node1 = Literal("1.0", REAL_TYPE) node2 = BinaryOperation.create(BinaryOperation.Operator.MUL, node1, node1) node3 = BinaryOperation.create(BinaryOperation.Operator.MAX, node2, node2) assert ArrayRange2LoopTrans.string_compare(node3, node3) is True assert ArrayRange2LoopTrans.string_compare(node3, node2) is False
def create_range(array_symbol, dim): '''Utility routine that creates and returns a Range Node that specifies the full range of the supplied dimension (dim) in the array (array_symbol). This is done using the LBOUND and UBOUND intrinsics. :param array_symbol: the array of interest. :type array_symbol: :py:class:`psyclone.psyir.symbol.DataSymbol` :param int dim: the dimension of interest in the array. :returns: a range node specifying the full range of the supplied \ array dimension. :rtype: :py:class:`psyclone.psyir.nodes.Range` ''' int_dim = Literal(str(dim), INTEGER_TYPE) lbound = BinaryOperation.create(BinaryOperation.Operator.LBOUND, Reference(array_symbol), int_dim) ubound = BinaryOperation.create(BinaryOperation.Operator.UBOUND, Reference(array_symbol), int_dim) return Range.create(lbound, ubound)
def test_binaryoperation_create(): '''Test that the create method in the BinaryOperation class correctly creates a BinaryOperation instance. ''' lhs = Reference(DataSymbol("tmp1", REAL_SINGLE_TYPE)) rhs = Reference(DataSymbol("tmp2", REAL_SINGLE_TYPE)) oper = BinaryOperation.Operator.ADD binaryoperation = BinaryOperation.create(oper, lhs, rhs) check_links(binaryoperation, [lhs, rhs]) result = FortranWriter().binaryoperation_node(binaryoperation) assert result == "tmp1 + tmp2"
def test_validate2(): '''Check that the Matmul2Code validate method raises the expected exception when the supplied node is a binary operation but not a MATMUL. ''' trans = Matmul2CodeTrans() with pytest.raises(TransformationError) as excinfo: trans.validate(BinaryOperation.create( BinaryOperation.Operator.ADD, Literal("1.0", REAL_TYPE), Literal("1.0", REAL_TYPE))) assert ("Transformation Error: Error in Matmul2CodeTrans transformation. " "The supplied node operator is invalid, found 'Operator.ADD'." in str(excinfo.value))
def test_cw_size(): ''' Check the CWriter class SIZE method raises the expected error since there is no C equivalent. ''' cwriter = CWriter() arr = ArrayReference(DataSymbol('a', INTEGER_TYPE)) lit = Literal('1', INTEGER_TYPE) size = BinaryOperation.create(BinaryOperation.Operator.SIZE, arr, lit) lhs = Reference(DataSymbol('length', INTEGER_TYPE)) assignment = Assignment.create(lhs, size) with pytest.raises(VisitorError) as excinfo: cwriter(assignment) assert ("C backend does not support the 'Operator.SIZE' operator" in str(excinfo.value))
def test_arraytype_invalid_shape_dimension_3(): '''Test that the ArrayType class raises an exception when one of the dimensions of the shape list argument is a DataNode that contains a local datasymbol that does not have a constant value (as this will not be initialised). ''' scalar_type = ScalarType(ScalarType.Intrinsic.INTEGER, 4) data_symbol = DataSymbol("var", scalar_type) one = Literal("1", scalar_type) var_plus_1 = BinaryOperation.create( BinaryOperation.Operator.ADD, Reference(data_symbol), one) with pytest.raises(TypeError) as info: _ = ArrayType(scalar_type, [var_plus_1]) assert ("If a local datasymbol is used as part of a dimension " "declaration then it should be a constant, but 'var' is " "not." in str(info.value))
def test_validate6(): '''Check that the Matmul2Code validate method raises the expected exception when the supplied node is a MATMUL binary operation but either or both of its arguments are references to datasymbols that are not arrays. ''' trans = Matmul2CodeTrans() scalar = Reference(DataSymbol("x", REAL_TYPE)) matmul = BinaryOperation.create(BinaryOperation.Operator.MATMUL, scalar, scalar) _ = Assignment.create(scalar, matmul) with pytest.raises(TransformationError) as excinfo: trans.validate(matmul) assert ("Transformation Error: Expected children of a MATMUL " "BinaryOperation to be references to arrays, but found " "'DataSymbol', 'DataSymbol'." in str(excinfo.value))
def test_validate7(): '''Check that the Matmul2Code validate method raises the expected exception when the supplied node is a MATMUL binary operation but its first (matrix) argument has fewer than 2 dimensions. ''' trans = Matmul2CodeTrans() array_type = ArrayType(REAL_TYPE, [10]) array = Reference(DataSymbol("x", array_type)) matmul = BinaryOperation.create(BinaryOperation.Operator.MATMUL, array, array) _ = Assignment.create(array, matmul) with pytest.raises(TransformationError) as excinfo: trans.validate(matmul) assert ("Transformation Error: Expected 1st child of a MATMUL " "BinaryOperation to be a matrix with at least 2 dimensions, " "but found '1'." in str(excinfo.value))