def test_fusetrans_error_not_same_parent(): ''' Check that we reject attempts to fuse loops which don't share the same parent ''' loop1 = Loop.create(DataSymbol("i", INTEGER_TYPE), Literal("1", INTEGER_TYPE), Literal("10", INTEGER_TYPE), Literal("1", INTEGER_TYPE), [Return()]) sch1 = Schedule() sch1.addchild(loop1) sch2 = Schedule() loop2 = Loop.create(DataSymbol("j", INTEGER_TYPE), Literal("1", INTEGER_TYPE), Literal("10", INTEGER_TYPE), Literal("1", INTEGER_TYPE), [Return()]) sch2.addchild(loop2) fuse = LoopFuseTrans() # Try to fuse loops with different parents with pytest.raises(TransformationError) as err: fuse.validate(loop1, loop2) assert ("Error in LoopFuseTrans transformation. Loops do not have the " "same parent" in str(err.value))
def test_parallellooptrans_refuse_codeblock(): ''' Check that ParallelLoopTrans.validate() rejects a loop nest that encloses a CodeBlock. We have to use OMPParallelLoopTrans as ParallelLoopTrans is abstract. ''' otrans = OMPParallelLoopTrans() # Construct a valid Loop in the PSyIR with a CodeBlock in its body parent = Loop.create(DataSymbol("ji", INTEGER_TYPE), Literal("1", INTEGER_TYPE), Literal("10", INTEGER_TYPE), Literal("1", INTEGER_TYPE), [CodeBlock([], CodeBlock.Structure.STATEMENT, None)]) with pytest.raises(TransformationError) as err: otrans.validate(parent) assert ("Nodes of type 'CodeBlock' cannot be enclosed " "by a OMPParallelLoopTrans transformation" in str(err.value))
def test_loop_create(): '''Test that the create method in the Loop class correctly creates a Loop instance. ''' start = Literal("0", INTEGER_SINGLE_TYPE) stop = Literal("1", INTEGER_SINGLE_TYPE) step = Literal("1", INTEGER_SINGLE_TYPE) child_node = Assignment.create( Reference(DataSymbol("tmp", REAL_SINGLE_TYPE)), Reference(DataSymbol("i", REAL_SINGLE_TYPE))) loop = Loop.create(DataSymbol("i", INTEGER_SINGLE_TYPE), start, stop, step, [child_node]) schedule = loop.children[3] assert isinstance(schedule, Schedule) check_links(loop, [start, stop, step, schedule]) check_links(schedule, [child_node]) result = FortranWriter().loop_node(loop) assert result == "do i = 0, 1, 1\n tmp = i\nenddo\n"
def test_replace_with_error1(): '''Check that the replace_with method raises the expected exception if the type of node is invalid for the location it is being added to. ''' iterator = DataSymbol("i", INTEGER_TYPE) start = Literal("0", INTEGER_TYPE) stop = Literal("1", INTEGER_TYPE) step = Literal("1", INTEGER_TYPE) loop = Loop.create(iterator, start, stop, step, []) new_node = Assignment() # The first child of a loop is the loop start value which should # be a DataNode. with pytest.raises(GenerationError) as info: loop.children[0].replace_with(new_node) assert ("Item 'Assignment' can't be child 0 of 'Loop'. The valid " "format is: 'DataNode, DataNode, DataNode, Schedule'" in str(info.value))
def test_loop_create_invalid(): '''Test that the create method in a Loop class raises the expected exception if the provided input is invalid. ''' zero = Literal("0", INTEGER_SINGLE_TYPE) one = Literal("1", INTEGER_SINGLE_TYPE) children = [ Assignment.create(Reference(DataSymbol("x", INTEGER_SINGLE_TYPE)), one) ] # invalid variable (test_check_variable tests check all ways a # variable could be invalid. Here we just check that the # _check_variable() method is called correctly) with pytest.raises(GenerationError) as excinfo: _ = Loop.create(1, zero, one, one, children) assert ("variable property in Loop class should be a DataSymbol but " "found 'int'.") in str(excinfo.value) variable = DataSymbol("i", INTEGER_TYPE) # start not a Node. with pytest.raises(GenerationError) as excinfo: _ = Loop.create(variable, "invalid", one, one, children) assert ("Item 'str' can't be child 0 of 'Loop'. The valid format is: " "'DataNode, DataNode, DataNode, Schedule'.") in str(excinfo.value) # stop not a Node. with pytest.raises(GenerationError) as excinfo: _ = Loop.create(variable, zero, "invalid", one, children) assert ("Item 'str' can't be child 1 of 'Loop'. The valid format is: " "'DataNode, DataNode, DataNode, Schedule'.") in str(excinfo.value) # step not a Node. with pytest.raises(GenerationError) as excinfo: _ = Loop.create(variable, zero, one, "invalid", children) assert ("Item 'str' can't be child 2 of 'Loop'. The valid format is: " "'DataNode, DataNode, DataNode, Schedule'.") in str(excinfo.value) # children not a list with pytest.raises(GenerationError) as excinfo: _ = Loop.create(variable, zero, one, one, "invalid") assert ("children argument in create method of Loop class should " "be a list but found 'str'." in str(excinfo.value)) # contents of children list are not Node. with pytest.raises(GenerationError) as excinfo: _ = Loop.create(variable, zero, one, one, ["invalid"]) assert ("Item 'str' can't be child 0 of 'Schedule'. The valid format is: " "'[Statement]*'." in str(excinfo.value))
def apply(self, node, options=None): '''Apply the ArrayRange2Loop transformation to the specified node. The node must be an assignment. The rightmost range node in each array within the assignment is replaced with a loop index and the assignment is placed within a loop iterating over that index. The bounds of the loop are determined from the bounds of the array range on the left hand side of the assignment. :param node: an Assignment node. :type node: :py:class:`psyclone.psyir.nodes.Assignment` ''' self.validate(node) parent = node.parent symbol_table = node.scope.symbol_table loop_variable_name = symbol_table.new_symbol_name(root_name="idx") loop_variable_symbol = DataSymbol(loop_variable_name, INTEGER_TYPE) symbol_table.add(loop_variable_symbol) # Replace the rightmost range found in all arrays with the # iterator and use the range from the LHS range for the loop # iteration space. for array in node.walk(Array): for idx, child in reversed(list(enumerate(array.children))): if isinstance(child, Range): if array is node.lhs: # Save this range to determine indexing lhs_range = child array.children[idx] = Reference(loop_variable_symbol, parent=array) break position = node.position # Issue #806: If Loop bounds were a Range we would just # need to provide the range node which would be simpler. loop = Loop.create(loop_variable_symbol, lhs_range.children[0], lhs_range.children[1], lhs_range.children[2], [node]) parent.children[position] = loop loop.parent = parent
def create_psyir_tree(): ''' Create an example PSyIR Tree. :returns: an example PSyIR tree. :rtype: :py:class:`psyclone.psyir.nodes.Container` ''' # Symbol table, symbols and scalar datatypes symbol_table = SymbolTable() arg1 = symbol_table.new_symbol(symbol_type=DataSymbol, datatype=REAL_TYPE, interface=ArgumentInterface( ArgumentInterface.Access.READWRITE)) symbol_table.specify_argument_list([arg1]) tmp_symbol = symbol_table.new_symbol(symbol_type=DataSymbol, datatype=REAL_DOUBLE_TYPE) index_symbol = symbol_table.new_symbol(root_name="i", symbol_type=DataSymbol, datatype=INTEGER4_TYPE) real_kind = symbol_table.new_symbol(root_name="RKIND", symbol_type=DataSymbol, datatype=INTEGER_TYPE, constant_value=8) routine_symbol = RoutineSymbol("my_sub") # Array using precision defined by another symbol scalar_type = ScalarType(ScalarType.Intrinsic.REAL, real_kind) array = symbol_table.new_symbol(root_name="a", symbol_type=DataSymbol, datatype=ArrayType(scalar_type, [10])) # Make generators for nodes which do not have other Nodes as children, # with some predefined scalar datatypes def zero(): return Literal("0.0", REAL_TYPE) def one(): return Literal("1.0", REAL4_TYPE) def two(): return Literal("2.0", scalar_type) def int_zero(): return Literal("0", INTEGER_SINGLE_TYPE) def int_one(): return Literal("1", INTEGER8_TYPE) def tmp1(): return Reference(arg1) def tmp2(): return Reference(tmp_symbol) # Unary Operation oper = UnaryOperation.Operator.SIN unaryoperation = UnaryOperation.create(oper, tmp2()) # Binary Operation oper = BinaryOperation.Operator.ADD binaryoperation = BinaryOperation.create(oper, one(), unaryoperation) # Nary Operation oper = NaryOperation.Operator.MAX naryoperation = NaryOperation.create(oper, [tmp1(), tmp2(), one()]) # Array reference using a range lbound = BinaryOperation.create(BinaryOperation.Operator.LBOUND, Reference(array), int_one()) ubound = BinaryOperation.create(BinaryOperation.Operator.UBOUND, Reference(array), int_one()) my_range = Range.create(lbound, ubound) tmparray = ArrayReference.create(array, [my_range]) # Assignments assign1 = Assignment.create(tmp1(), zero()) assign2 = Assignment.create(tmp2(), zero()) assign3 = Assignment.create(tmp2(), binaryoperation) assign4 = Assignment.create(tmp1(), tmp2()) assign5 = Assignment.create(tmp1(), naryoperation) assign6 = Assignment.create(tmparray, two()) # Call call = Call.create(routine_symbol, [tmp1(), binaryoperation.copy()]) # If statement if_condition = BinaryOperation.create(BinaryOperation.Operator.GT, tmp1(), zero()) ifblock = IfBlock.create(if_condition, [assign3, assign4]) # Loop loop = Loop.create(index_symbol, int_zero(), int_one(), int_one(), [ifblock]) # KernelSchedule kernel_schedule = KernelSchedule.create( "work", symbol_table, [assign1, call, assign2, loop, assign5, assign6]) # Container container_symbol_table = SymbolTable() container = Container.create("CONTAINER", container_symbol_table, [kernel_schedule]) # Import data from another container external_container = ContainerSymbol("some_mod") container_symbol_table.add(external_container) external_var = DataSymbol("some_var", INTEGER_TYPE, interface=GlobalInterface(external_container)) container_symbol_table.add(external_var) routine_symbol.interface = GlobalInterface(external_container) container_symbol_table.add(routine_symbol) return container
def create_psyir_tree(): ''' Create an example PSyIR Tree. :returns: an example PSyIR tree. :rtype: :py:class:`psyclone.psyir.nodes.Container` ''' # Symbol table, symbols and scalar datatypes symbol_table = SymbolTable() arg1 = symbol_table.new_symbol(symbol_type=DataSymbol, datatype=REAL_TYPE, interface=ArgumentInterface( ArgumentInterface.Access.READWRITE)) symbol_table.specify_argument_list([arg1]) tmp_symbol = symbol_table.new_symbol(symbol_type=DataSymbol, datatype=REAL_DOUBLE_TYPE) index_symbol = symbol_table.new_symbol(root_name="i", symbol_type=DataSymbol, datatype=INTEGER4_TYPE) real_kind = symbol_table.new_symbol(root_name="RKIND", symbol_type=DataSymbol, datatype=INTEGER_TYPE, constant_value=8) routine_symbol = RoutineSymbol("my_sub") # Array using precision defined by another symbol scalar_type = ScalarType(ScalarType.Intrinsic.REAL, real_kind) array = symbol_table.new_symbol(root_name="a", symbol_type=DataSymbol, datatype=ArrayType(scalar_type, [10])) # Nodes which do not have Nodes as children and (some) predefined # scalar datatypes # TODO: Issue #1136 looks at how to avoid all of the _x versions zero_1 = Literal("0.0", REAL_TYPE) zero_2 = Literal("0.0", REAL_TYPE) zero_3 = Literal("0.0", REAL_TYPE) one_1 = Literal("1.0", REAL4_TYPE) one_2 = Literal("1.0", REAL4_TYPE) one_3 = Literal("1.0", REAL4_TYPE) two = Literal("2.0", scalar_type) int_zero = Literal("0", INTEGER_SINGLE_TYPE) int_one_1 = Literal("1", INTEGER8_TYPE) int_one_2 = Literal("1", INTEGER8_TYPE) int_one_3 = Literal("1", INTEGER8_TYPE) int_one_4 = Literal("1", INTEGER8_TYPE) tmp1_1 = Reference(arg1) tmp1_2 = Reference(arg1) tmp1_3 = Reference(arg1) tmp1_4 = Reference(arg1) tmp1_5 = Reference(arg1) tmp1_6 = Reference(arg1) tmp2_1 = Reference(tmp_symbol) tmp2_2 = Reference(tmp_symbol) tmp2_3 = Reference(tmp_symbol) tmp2_4 = Reference(tmp_symbol) tmp2_5 = Reference(tmp_symbol) tmp2_6 = Reference(tmp_symbol) # Unary Operation oper = UnaryOperation.Operator.SIN unaryoperation_1 = UnaryOperation.create(oper, tmp2_1) unaryoperation_2 = UnaryOperation.create(oper, tmp2_2) # Binary Operation oper = BinaryOperation.Operator.ADD binaryoperation_1 = BinaryOperation.create(oper, one_1, unaryoperation_1) binaryoperation_2 = BinaryOperation.create(oper, one_2, unaryoperation_2) # Nary Operation oper = NaryOperation.Operator.MAX naryoperation = NaryOperation.create(oper, [tmp1_1, tmp2_3, one_3]) # Array reference using a range lbound = BinaryOperation.create(BinaryOperation.Operator.LBOUND, Reference(array), int_one_1) ubound = BinaryOperation.create(BinaryOperation.Operator.UBOUND, Reference(array), int_one_2) my_range = Range.create(lbound, ubound) tmparray = ArrayReference.create(array, [my_range]) # Assignments assign1 = Assignment.create(tmp1_2, zero_1) assign2 = Assignment.create(tmp2_4, zero_2) assign3 = Assignment.create(tmp2_5, binaryoperation_1) assign4 = Assignment.create(tmp1_3, tmp2_6) assign5 = Assignment.create(tmp1_4, naryoperation) assign6 = Assignment.create(tmparray, two) # Call call = Call.create(routine_symbol, [tmp1_5, binaryoperation_2]) # If statement if_condition = BinaryOperation.create(BinaryOperation.Operator.GT, tmp1_6, zero_3) ifblock = IfBlock.create(if_condition, [assign3, assign4]) # Loop loop = Loop.create(index_symbol, int_zero, int_one_3, int_one_4, [ifblock]) # KernelSchedule kernel_schedule = KernelSchedule.create( "work", symbol_table, [assign1, call, assign2, loop, assign5, assign6]) # Container container_symbol_table = SymbolTable() container = Container.create("CONTAINER", container_symbol_table, [kernel_schedule]) # Import data from another container external_container = ContainerSymbol("some_mod") container_symbol_table.add(external_container) external_var = DataSymbol("some_var", INTEGER_TYPE, interface=GlobalInterface(external_container)) container_symbol_table.add(external_var) routine_symbol.interface = GlobalInterface(external_container) container_symbol_table.add(routine_symbol) return container
def apply(self, node, options=None): '''Apply the MATMUL intrinsic conversion transformation to the specified node. This node must be a MATMUL BinaryOperation. Currently only the matrix vector version of MATMUL is supported. The arguments are permitted to have additional dimensions (i.e. more than 2 for the matrix and more than 1 for the vector) but the matrix can only have two indices which are ranges and these must be the first two indices and the vector can only have one index that is a range and this must be the first index. Further, the ranges must be for the full index space for that dimension (i.e. array subsections are not supported). If the transformation is successful then an assignment which includes a MATMUL BinaryOperation node is converted to equivalent inline code. :param node: a MATMUL Binary-Operation node. :type node: :py:class:`psyclone.psyGen.BinaryOperation` :param options: a dictionary with options for transformations. :type options: dictionary of string:values or None ''' # pylint: disable=too-many-locals self.validate(node) assignment = node.parent matrix = node.children[0] vector = node.children[1] result = node.parent.lhs result_symbol = result.symbol # Create new i and j loop iterators. symbol_table = node.scope.symbol_table i_loop_symbol = symbol_table.new_symbol("i", symbol_type=DataSymbol, datatype=INTEGER_TYPE) j_loop_symbol = symbol_table.new_symbol("j", symbol_type=DataSymbol, datatype=INTEGER_TYPE) # Create "result(i)" result_dims = [Reference(i_loop_symbol)] if len(result.children) > 1: # Add any additional dimensions (in case of an array slice) for child in result.children[1:]: result_dims.append(child.copy()) result_ref = ArrayReference.create(result_symbol, result_dims) # Create "vector(j)" vector_dims = [Reference(j_loop_symbol)] if len(vector.children) > 1: # Add any additional dimensions (in case of an array slice) for child in vector.children[1:]: vector_dims.append(child.copy()) vector_array_reference = ArrayReference.create(vector.symbol, vector_dims) # Create "matrix(i,j)" array_dims = [Reference(i_loop_symbol), Reference(j_loop_symbol)] if len(matrix.children) > 2: # Add any additional dimensions (in case of an array slice) for child in matrix.children[2:]: array_dims.append(child.copy()) matrix_array_reference = ArrayReference.create(matrix.symbol, array_dims) # Create "matrix(i,j) * vector(j)" multiply = BinaryOperation.create(BinaryOperation.Operator.MUL, matrix_array_reference, vector_array_reference) # Create "result(i) + matrix(i,j) * vector(j)" rhs = BinaryOperation.create(BinaryOperation.Operator.ADD, result_ref, multiply) # Create "result(i) = result(i) + matrix(i,j) * vector(j)" assign = Assignment.create(result_ref.copy(), rhs) # Create j loop and add the above code as a child # Work out the bounds lower_bound, upper_bound, step = _get_array_bound(vector, 0) jloop = Loop.create(j_loop_symbol, lower_bound, upper_bound, step, [assign]) # Create "result(i) = 0.0" assign = Assignment.create(result_ref.copy(), Literal("0.0", REAL_TYPE)) # Create i loop and add assigment and j loop as children lower_bound, upper_bound, step = _get_array_bound(matrix, 0) iloop = Loop.create(i_loop_symbol, lower_bound, upper_bound, step, [assign, jloop]) # Add the new code to the PSyIR assignment.parent.children.insert(assignment.position, iloop) # remove the original matmul assignment.parent.children.remove(assignment)
def test_children_validation(): ''' Test that nodes are validated when inserted as children of other nodes. For simplicity we use Node subclasses to test this functionality across a range of possible operations. The specific logic of each validate method will be tested individually inside each Node test file. ''' assignment = Assignment() return_stmt = Return() reference = Reference(DataSymbol("a", INTEGER_TYPE)) assert isinstance(assignment.children, (ChildrenList, list)) # Try adding a invalid child (e.g. a return_stmt into an assingment) with pytest.raises(GenerationError) as error: assignment.addchild(return_stmt) assert "Item 'Return' can't be child 0 of 'Assignment'. The valid format" \ " is: 'DataNode, DataNode'." in str(error.value) # The same behaviour occurs when list insertion operations are used. with pytest.raises(GenerationError): assignment.children.append(return_stmt) with pytest.raises(GenerationError): assignment.children[0] = return_stmt with pytest.raises(GenerationError): assignment.children.insert(0, return_stmt) with pytest.raises(GenerationError): assignment.children.extend([return_stmt]) with pytest.raises(GenerationError): assignment.children = assignment.children + [return_stmt] # Valid nodes are accepted assignment.addchild(reference) # Check displaced items are also be checked when needed start = Literal("0", INTEGER_TYPE) stop = Literal("1", INTEGER_TYPE) step = Literal("2", INTEGER_TYPE) child_node = Assignment.create(Reference(DataSymbol("tmp", REAL_TYPE)), Reference(DataSymbol("i", REAL_TYPE))) loop_variable = DataSymbol("idx", INTEGER_TYPE) loop = Loop.create(loop_variable, start, stop, step, [child_node]) with pytest.raises(GenerationError): loop.children.insert(1, Literal("0", INTEGER_TYPE)) with pytest.raises(GenerationError): loop.children.remove(stop) with pytest.raises(GenerationError): del loop.children[2] with pytest.raises(GenerationError): loop.children.pop(2) with pytest.raises(GenerationError): loop.children.reverse() # But the in the right circumstances they work fine assert isinstance(loop.children.pop(), Schedule) loop.children.reverse() assert loop.children[0].value == "2"
ASSIGN1 = Assignment.create(TMP1, ZERO) ASSIGN2 = Assignment.create(TMP2, ZERO) ASSIGN3 = Assignment.create(TMP2, BINARYOPERATION) ASSIGN4 = Assignment.create(TMP1, TMP2) ASSIGN5 = Assignment.create(TMP1, NARYOPERATION) ASSIGN6 = Assignment.create(TMPARRAY, TWO) # Call CALL = Call.create(ROUTINE_SYMBOL, [TMP1, BINARYOPERATION]) # If statement IF_CONDITION = BinaryOperation.create(BinaryOperation.Operator.GT, TMP1, ZERO) IFBLOCK = IfBlock.create(IF_CONDITION, [ASSIGN3, ASSIGN4]) # Loop LOOP = Loop.create(INDEX_SYMBOL, INT_ZERO, INT_ONE, INT_ONE, [IFBLOCK]) # KernelSchedule KERNEL_SCHEDULE = KernelSchedule.create( "work", SYMBOL_TABLE, [CALL, ASSIGN2, LOOP, ASSIGN5, ASSIGN6]) # Container CONTAINER_SYMBOL_TABLE = SymbolTable() CONTAINER = Container.create("CONTAINER", CONTAINER_SYMBOL_TABLE, [KERNEL_SCHEDULE]) # Import data from another container EXTERNAL_CONTAINER = ContainerSymbol("some_mod") CONTAINER_SYMBOL_TABLE.add(EXTERNAL_CONTAINER) EXTERNAL_VAR = DataSymbol("some_var", INTEGER_TYPE,