def apply(self, node, options=None): '''Apply the NemoArrayRange2Loop transformation if the supplied node is the outermost Range node (specifying an access to an array index) within an Array Reference that is on the left-hand-side of an Assignment node. These constraints are required for correctness and an exception will be raised if they are not satisfied. If the constraints are satisfied then the outermost Range nodes within array references within the Assignment node are replaced with references to a loop index. A NemoLoop loop (with the same loop index) is also placed around the modified assignment statement. If the array reference on the left-hand-side of the assignment only had one range node as an index (so now has none) then the assigment is also placed within a NemoKern. The name of the loop index is taken from the PSyclone configuration file if a name exists for the particular array index, otherwise a new name is generated. The bounds of the loop are taken from the Range node if they are provided. If not, the loop bounds are taken from the PSyclone configuration file if bounds values are supplied. If not, the LBOUND or UBOUND intrinsics are used as appropriate. The type of the NemoLoop is also taken from the configuration file if it is supplied for that index, otherwise it is specified as being "unknown". :param node: a Range node. :type node: :py:class:`psyclone.psyir.nodes.Range` :param options: a dictionary with options for \ transformations. No options are used in this \ transformation. This is an optional argument that defaults \ to None. :type options: dict of string:values or None ''' self.validate(node) array_reference = node.parent array_index = node.position assignment = array_reference.parent parent = assignment.parent symbol_table = node.scope.symbol_table # See if there is any configuration information for this array index loop_type_order = Config.get().api_conf("nemo").get_index_order() # TODO: Add tests in get_loop_type_data() to make sure values # are strings that represent an integer or a valid variable # name, e.g. 1a should not be allowed. See issue #1035 loop_type_data = Config.get().api_conf("nemo").get_loop_type_data() try: loop_type = loop_type_order[array_index] loop_type_info = loop_type_data[loop_type] lower_bound_info = loop_type_info['start'] upper_bound_info = loop_type_info['stop'] loop_variable_name = loop_type_info['var'] except IndexError: lower_bound_info = None upper_bound_info = None loop_variable_name = symbol_table.next_available_name("idx") # Lower bound if not array_reference.is_lower_bound(array_index): # The range specifies a lower bound so use it lower_bound = node.start.copy() elif lower_bound_info: # The config metadata specifies a lower bound so use it try: _ = int(lower_bound_info) lower_bound = Literal(lower_bound_info, INTEGER_TYPE) except ValueError: lower_bound = Reference(symbol_table.lookup(lower_bound_info)) else: # The lower bound is not set or specified so use the # LBOUND() intrinsic lower_bound = node.start.copy() # Upper bound if not array_reference.is_upper_bound(array_index): # The range specifies an upper bound so use it upper_bound = node.stop.copy() elif upper_bound_info: # The config metadata specifies an upper bound so use it try: _ = int(upper_bound_info) upper_bound = Literal(upper_bound_info, INTEGER_TYPE) except ValueError: upper_bound = Reference(symbol_table.lookup(upper_bound_info)) else: # The upper bound is not set or specified so use the # UBOUND() intrinsic upper_bound = node.stop.copy() # Just use the specified step value step = node.step.copy() # Look up the loop variable in the symbol table. If it does # not exist then create it. try: loop_variable_symbol = symbol_table.lookup(loop_variable_name) except KeyError: # Add loop variable as it does not already exist loop_variable_symbol = DataSymbol(loop_variable_name, INTEGER_TYPE) symbol_table.add(loop_variable_symbol) # Replace the loop_idx array dimension with the loop variable. n_ranges = None for array in assignment.walk(ArrayReference): # Ignore the array reference if none of its index accesses # are Ranges if not any(child for child in array.children if isinstance(child, Range)): continue # Ignore the array reference if any of its parents up to # the Assignment node are not Operations that return # scalars. ignore = False current = array.parent while not isinstance(current, Assignment): # Ignore if not a scalar valued operation (vector # valued operations are excluded in the validate # method). if not isinstance(current, Operation): ignore = True break current = current.parent if ignore: continue current_n_ranges = len([ child for child in array.children if isinstance(child, Range) ]) if n_ranges is None: n_ranges = current_n_ranges elif n_ranges != current_n_ranges: raise InternalError( "The number of ranges in the arrays within this " "assignment are not equal. This is invalid PSyIR and " "should never happen.") idx = get_outer_index(array) array.children[idx] = Reference(loop_variable_symbol) position = assignment.position loop = NemoLoop.create(loop_variable_symbol, lower_bound, upper_bound, step, [assignment.detach()]) parent.children.insert(position, loop) try: _ = get_outer_index(array_reference) except IndexError: # All valid array ranges have been replaced with explicit # loops. We now need to take the content of the loop and # place it within a NemoKern (inlined kernel) node. CreateNemoKernelTrans().apply(assignment.parent)
def test_is_array_range(): '''test that the is_array_range method behaves as expected, returning true if the LHS of the assignment is an array range access. ''' one = Literal("1.0", REAL_TYPE) int_one = Literal("1", INTEGER_TYPE) int_ten = Literal("10", INTEGER_TYPE) # lhs is an array reference with a range array_type = ArrayType(REAL_TYPE, [10, 10]) symbol = DataSymbol("x", array_type) x_range = Range.create(int_one, int_ten.copy(), int_one.copy()) array_ref = ArrayReference.create(symbol, [x_range, int_one.copy()]) assignment = Assignment.create(array_ref, one.copy()) assert assignment.is_array_range is True # Check when lhs consists of various forms of structure access grid_type = StructureType.create([ ("dx", REAL_SINGLE_TYPE, Symbol.Visibility.PUBLIC), ("dy", REAL_SINGLE_TYPE, Symbol.Visibility.PUBLIC) ]) grid_type_symbol = TypeSymbol("grid_type", grid_type) # Create the definition of the 'field_type', contains array of grid_types field_type_def = StructureType.create([ ("data", ArrayType(REAL_SINGLE_TYPE, [10]), Symbol.Visibility.PUBLIC), ("sub_meshes", ArrayType(grid_type_symbol, [3]), Symbol.Visibility.PUBLIC) ]) field_type_symbol = TypeSymbol("field_type", field_type_def) field_symbol = DataSymbol("wind", field_type_symbol) # Array reference to component of derived type using a range lbound = BinaryOperation.create( BinaryOperation.Operator.LBOUND, StructureReference.create(field_symbol, ["data"]), int_one.copy()) ubound = BinaryOperation.create( BinaryOperation.Operator.UBOUND, StructureReference.create(field_symbol, ["data"]), int_one.copy()) my_range = Range.create(lbound, ubound) data_ref = StructureReference.create(field_symbol, [("data", [my_range])]) assign = Assignment.create(data_ref, one.copy()) assert assign.is_array_range is True # Access to slice of 'sub_meshes': wind%sub_meshes(1:3)%dx = 1.0 sub_range = Range.create(int_one.copy(), Literal("3", INTEGER_TYPE)) dx_ref = StructureReference.create(field_symbol, [("sub_meshes", [sub_range]), "dx"]) sub_assign = Assignment.create(dx_ref, one.copy()) assert sub_assign.is_array_range is True # Create an array of these derived types and assign to a slice: # chi(1:10)%data(1) = 1.0 field_bundle_symbol = DataSymbol("chi", ArrayType(field_type_symbol, [3])) fld_range = Range.create(int_one.copy(), Literal("10", INTEGER_TYPE)) fld_ref = ArrayOfStructuresReference.create(field_bundle_symbol, [fld_range], [("data", [int_one.copy()])]) fld_assign = Assignment.create(fld_ref, one.copy()) assert fld_assign.is_array_range is True # When the slice has two operator ancestors, none of which are a reduction # e.g y(1, INT(ABS(map(:, 1)))) = 1.0 int_array_type = ArrayType(INTEGER_SINGLE_TYPE, [10, 10]) map_sym = DataSymbol("map", int_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) abs_op = UnaryOperation.create( UnaryOperation.Operator.ABS, ArrayReference.create(map_sym, [my_range1, int_one.copy()])) int_op = UnaryOperation.create(UnaryOperation.Operator.INT, abs_op) assignment = Assignment.create( ArrayReference.create(symbol, [int_one.copy(), int_op]), one.copy()) assert assignment.is_array_range is True
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"
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_datasymbol_str(): '''Test that the DataSymbol __str__ method returns the expected string''' data_symbol = DataSymbol("a", INTEGER4_TYPE, constant_value=3) assert (data_symbol.__str__() == "a: <Scalar<INTEGER, 4>, Local, constant_value=Literal[value:'3', " "Scalar<INTEGER, 4>]>")
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]) # Routine routine = Routine.create("work", symbol_table, [assign1, call, assign2, loop, assign5, assign6]) # Container container_symbol_table = SymbolTable() container = Container.create("CONTAINER", container_symbol_table, [routine]) # 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) # Routine (specified as being a program) program_symbol_table = SymbolTable() work_symbol = RoutineSymbol("work") container_symbol = ContainerSymbol("CONTAINER") work_symbol.interface = GlobalInterface(container_symbol) arg_symbol = program_symbol_table.new_symbol(root_name="arg", symbol_type=DataSymbol, datatype=REAL_TYPE) program_symbol_table.add(container_symbol) program_symbol_table.add(work_symbol) call = Call.create(work_symbol, [Reference(arg_symbol)]) program = Routine.create("some_program", program_symbol_table, [call], is_program=True) # File container file_container = FileContainer.create("dummy", SymbolTable(), [container, program]) return file_container
def test_get_array_bound(): '''Test that the _get_array_bound utility function returns the expected bound values for different types of array declaration. ''' scalar_symbol = DataSymbol("n", INTEGER_TYPE, constant_value=20) array_type = ArrayType(REAL_TYPE, [ 10, scalar_symbol, ArrayType.Extent.DEFERRED, ArrayType.Extent.ATTRIBUTE ]) array_symbol = DataSymbol("x", array_type) reference = Reference(array_symbol) # literal value (lower_bound, upper_bound, step) = _get_array_bound(reference, 0) assert isinstance(lower_bound, Literal) assert lower_bound.value == "1" assert lower_bound.datatype.intrinsic == ScalarType.Intrinsic.INTEGER assert isinstance(upper_bound, Literal) assert upper_bound.value == "10" assert upper_bound.datatype.intrinsic == ScalarType.Intrinsic.INTEGER assert isinstance(step, Literal) assert step.value == "1" assert step.datatype.intrinsic == ScalarType.Intrinsic.INTEGER # symbol (lower_bound, upper_bound, step) = _get_array_bound(reference, 1) assert isinstance(lower_bound, Literal) assert lower_bound.value == "1" assert lower_bound.datatype.intrinsic == ScalarType.Intrinsic.INTEGER assert isinstance(upper_bound, Reference) assert upper_bound.symbol.name == "n" assert isinstance(step, Literal) assert step.value == "1" assert step.datatype.intrinsic == ScalarType.Intrinsic.INTEGER # deferred and attribute def _check_ulbound(lower_bound, upper_bound, step, index): '''Internal utility routine that checks LBOUND and UBOUND are used correctly for the lower and upper array bounds respectively. ''' assert isinstance(lower_bound, BinaryOperation) assert lower_bound.operator == BinaryOperation.Operator.LBOUND assert isinstance(lower_bound.children[0], Reference) assert lower_bound.children[0].symbol is array_symbol assert isinstance(lower_bound.children[1], Literal) assert (lower_bound.children[1].datatype.intrinsic == ScalarType.Intrinsic.INTEGER) assert lower_bound.children[1].value == str(index) assert isinstance(upper_bound, BinaryOperation) assert upper_bound.operator == BinaryOperation.Operator.UBOUND assert isinstance(upper_bound.children[0], Reference) assert upper_bound.children[0].symbol is array_symbol assert isinstance(upper_bound.children[1], Literal) assert (upper_bound.children[1].datatype.intrinsic == ScalarType.Intrinsic.INTEGER) assert upper_bound.children[1].value == str(index) assert isinstance(step, Literal) assert step.value == "1" assert step.datatype.intrinsic == ScalarType.Intrinsic.INTEGER (lower_bound, upper_bound, step) = _get_array_bound(reference, 2) _check_ulbound(lower_bound, upper_bound, step, 2) (lower_bound, upper_bound, step) = _get_array_bound(reference, 3) _check_ulbound(lower_bound, upper_bound, step, 3)
def create_data_symbol(arg): symbol = DataSymbol(arg.name, REAL_TYPE, interface=arg.interface) return symbol
def create_real(variable): return DataSymbol(variable.name, REAL_TYPE, interface=variable.interface)
def create_data_symbol(arg): symbol = DataSymbol(arg.name, INTEGER_TYPE, interface=arg.interface, constant_value=Literal("1", INTEGER_TYPE)) return symbol
def create_data_symbol(arg): symbol = DataSymbol(arg.name, CHARACTER_TYPE, interface=arg.interface) return symbol
def test_symtab_implementation_for_opencl(): ''' Tests that the GOcean specialised Symbol Table implements the abstract properties needed to generate OpenCL. ''' kschedule = GOKernelSchedule('test') # Test symbol table without any kernel argument with pytest.raises(GenerationError) as err: _ = kschedule.symbol_table.iteration_indices assert ("GOcean 1.0 API kernels should always have at least two " "arguments representing the iteration indices but the Symbol " "Table for kernel 'test' has only 0 argument(s)." in str(err.value)) # Test symbol table with 1 kernel argument arg1 = DataSymbol("arg1", INTEGER_TYPE, interface=ArgumentInterface( ArgumentInterface.Access.READ)) kschedule.symbol_table.add(arg1) kschedule.symbol_table.specify_argument_list([arg1]) with pytest.raises(GenerationError) as err: _ = kschedule.symbol_table.iteration_indices assert ("GOcean 1.0 API kernels should always have at least two " "arguments representing the iteration indices but the Symbol " "Table for kernel 'test' has only 1 argument(s)." in str(err.value)) # Test symbol table with 2 kernel argument arg2 = DataSymbol("arg2", INTEGER_TYPE, interface=ArgumentInterface( ArgumentInterface.Access.READ)) kschedule.symbol_table.add(arg2) kschedule.symbol_table.specify_argument_list([arg1, arg2]) iteration_indices = kschedule.symbol_table.iteration_indices assert iteration_indices[0] is arg1 assert iteration_indices[1] is arg2 # Test symbol table with 3 kernel argument array_type = ArrayType(REAL_TYPE, [10, 10]) arg3 = DataSymbol("buffer1", array_type, interface=ArgumentInterface( ArgumentInterface.Access.READ)) kschedule.symbol_table.add(arg3) kschedule.symbol_table.specify_argument_list([arg1, arg2, arg3]) iteration_indices = kschedule.symbol_table.iteration_indices data_args = kschedule.symbol_table.data_arguments assert iteration_indices[0] is arg1 assert iteration_indices[1] is arg2 assert data_args[0] is arg3 # Test gen_ocl with wrong iteration indices types and shapes. arg1._datatype._intrinsic = ScalarType.Intrinsic.REAL with pytest.raises(GenerationError) as err: _ = kschedule.symbol_table.iteration_indices assert ("GOcean 1.0 API kernels first argument should be a scalar " "integer but got 'Scalar<REAL, UNDEFINED>' for kernel 'test'." in str(err.value)) arg1._datatype._intrinsic = ScalarType.Intrinsic.INTEGER # restore arg2._datatype = ArrayType(INTEGER_TYPE, [10]) with pytest.raises(GenerationError) as err: _ = kschedule.symbol_table.iteration_indices assert ("GOcean 1.0 API kernels second argument should be a scalar integer" " but got 'Array<Scalar<INTEGER, UNDEFINED>, shape=[" "Literal[value:'10', Scalar<INTEGER, UNDEFINED>]]>' for " "kernel 'test'." in str(err.value))
def test_array_is_full_range(): '''Test that the is_full_range method in the Array Node works as expected. ''' # pylint: disable=too-many-statements zero = Literal("0", INTEGER_SINGLE_TYPE) one = Literal("1", INTEGER_SINGLE_TYPE) array_type = ArrayType(REAL_SINGLE_TYPE, [10]) symbol = DataSymbol("my_array", array_type) reference = Reference(symbol) lbound = BinaryOperation.create(BinaryOperation.Operator.LBOUND, reference, one) ubound = BinaryOperation.create(BinaryOperation.Operator.UBOUND, reference, one) symbol_error = DataSymbol("another_array", array_type) reference_error = Reference(symbol_error) # Index out of bounds array_reference = ArrayReference.create(symbol, [one]) with pytest.raises(ValueError) as excinfo: array_reference.is_full_range(1) assert ("In ArrayReference 'my_array' the specified index '1' must be " "less than the number of dimensions '1'." in str(excinfo.value)) # Array dimension is not a Range assert not array_reference.is_full_range(0) # Check LBOUND # Array dimension range lower bound is not a binary operation my_range = Range.create(one, one, one) array_reference = ArrayReference.create(symbol, [my_range]) assert not array_reference.is_full_range(0) # Array dimension range lower bound is not an LBOUND binary operation my_range = Range.create(ubound, one, one) array_reference = ArrayReference.create(symbol, [my_range]) assert not array_reference.is_full_range(0) # Array dimension range lower bound is an LBOUND binary operation # with the first value not being a reference lbound_error = BinaryOperation.create(BinaryOperation.Operator.LBOUND, zero, zero) my_range = Range.create(lbound_error, one, one) array_reference = ArrayReference.create(symbol, [my_range]) assert not array_reference.is_full_range(0) # Array dimension range lower bound is an LBOUND binary operation # with the first value being a reference to a different symbol lbound_error = BinaryOperation.create(BinaryOperation.Operator.LBOUND, reference_error, zero) my_range = Range.create(lbound_error, one, one) array_reference = ArrayReference.create(symbol, [my_range]) assert not array_reference.is_full_range(0) # Array dimension range lower bound is an LBOUND binary operation # with the second value not being a literal. lbound_error = BinaryOperation.create(BinaryOperation.Operator.LBOUND, reference, reference) my_range = Range.create(lbound_error, one, one) array_reference = ArrayReference.create(symbol, [my_range]) assert not array_reference.is_full_range(0) # Array dimension range lower bound is an LBOUND binary operation # with the second value not being an integer literal. lbound_error = BinaryOperation.create( BinaryOperation.Operator.LBOUND, reference, Literal("1.0", REAL_SINGLE_TYPE)) my_range = Range.create(lbound_error, one, one) array_reference = ArrayReference.create(symbol, [my_range]) assert not array_reference.is_full_range(0) # Array dimension range lower bound is an LBOUND binary operation # with the second value being an integer literal with the wrong # value (should be 0 as this dimension index is 0). lbound_error = BinaryOperation.create( BinaryOperation.Operator.LBOUND, reference, one) my_range = Range.create(lbound_error, one, one) array_reference = ArrayReference.create(symbol, [my_range]) assert not array_reference.is_full_range(0) # Check UBOUND # Array dimension range upper bound is not a binary operation my_range = Range.create(lbound, one, one) array_reference = ArrayReference.create(symbol, [my_range]) assert not array_reference.is_full_range(0) # Array dimension range upper bound is not a UBOUND binary operation my_range = Range.create(lbound, lbound, one) array_reference = ArrayReference.create(symbol, [my_range]) assert not array_reference.is_full_range(0) # Array dimension range upper bound is a UBOUND binary operation # with the first value not being a reference ubound_error = BinaryOperation.create(BinaryOperation.Operator.UBOUND, zero, zero) my_range = Range.create(lbound, ubound_error, one) array_reference = ArrayReference.create(symbol, [my_range]) assert not array_reference.is_full_range(0) # Array dimension range upper bound is a UBOUND binary operation # with the first value being a reference to a different symbol ubound_error = BinaryOperation.create(BinaryOperation.Operator.UBOUND, reference_error, zero) my_range = Range.create(lbound, ubound_error, one) array_reference = ArrayReference.create(symbol, [my_range]) assert not array_reference.is_full_range(0) # Array dimension range upper bound is a UBOUND binary operation # with the second value not being a literal. ubound_error = BinaryOperation.create(BinaryOperation.Operator.UBOUND, reference, reference) my_range = Range.create(lbound, ubound_error, one) array_reference = ArrayReference.create(symbol, [my_range]) assert not array_reference.is_full_range(0) # Array dimension range upper bound is a UBOUND binary operation # with the second value not being an integer literal. ubound_error = BinaryOperation.create( BinaryOperation.Operator.UBOUND, reference, Literal("1.0", REAL_SINGLE_TYPE)) my_range = Range.create(lbound, ubound_error, one) array_reference = ArrayReference.create(symbol, [my_range]) assert not array_reference.is_full_range(0) # Array dimension range upper bound is a UBOUND binary operation # with the second value being an integer literal with the wrong # value (should be 1 as this dimension is 1). ubound_error = BinaryOperation.create( BinaryOperation.Operator.UBOUND, reference, zero) my_range = Range.create(lbound, ubound_error, one) array_reference = ArrayReference.create(symbol, [my_range]) assert not array_reference.is_full_range(0) # Check Step # Array dimension range step is not a literal. my_range = Range.create(lbound, ubound, lbound) array_reference = ArrayReference.create(symbol, [my_range]) assert not array_reference.is_full_range(0) # Array dimension range step is not an integer literal. my_range = Range.create(lbound, ubound, one) # We have to change this to a non-integer manually as the create # function only accepts integer literals for the step argument. my_range.children[2] = Literal("1.0", REAL_SINGLE_TYPE) array_reference = ArrayReference.create(symbol, [my_range]) assert not array_reference.is_full_range(0) # Array dimension range step is is an integer literal with the # wrong value (not 1). my_range = Range.create(lbound, ubound, zero) array_reference = ArrayReference.create(symbol, [my_range]) assert not array_reference.is_full_range(0) # All is as it should be. # The full range is covered so return true. my_range = Range.create(lbound, ubound, one) array_reference = ArrayReference.create(symbol, [my_range]) assert array_reference.is_full_range(0)
def apply(self, node, options=None): '''Apply the ABS intrinsic conversion transformation to the specified node. This node must be an ABS UnaryOperation. The ABS UnaryOperation is converted to equivalent inline code. This is implemented as a PSyIR transform from: .. code-block:: python R = ... ABS(X) ... to: .. code-block:: python tmp_abs = X if tmp_abs < 0.0: res_abs = tmp_abs*-1.0 else: res_abs = tmp_abs R = ... res_abs ... where ``X`` could be an arbitrarily complex PSyIR expression and ``...`` could be arbitrary PSyIR code. This transformation requires the operation node to be a descendent of an assignment and will raise an exception if this is not the case. :param node: an ABS UnaryOperation node. :type node: :py:class:`psyclone.psyGen.UnaryOperation` :param options: a dictionary with options for transformations. :type options: dictionary of string:values or None ''' # pylint: disable=too-many-locals self.validate(node) schedule = node.root symbol_table = schedule.symbol_table oper_parent = node.parent assignment = node.ancestor(Assignment) # Create two temporary variables. There is an assumption here # that the ABS Operator returns a PSyIR real type. This might # not be what is wanted (e.g. the args might PSyIR integers), # or there may be errors (arguments are of different types) # but this can't be checked as we don't have the appropriate # methods to query nodes (see #658). res_var = symbol_table.new_symbol_name("res_abs") symbol_res_var = DataSymbol(res_var, REAL_TYPE) symbol_table.add(symbol_res_var) tmp_var = symbol_table.new_symbol_name("tmp_abs") symbol_tmp_var = DataSymbol(tmp_var, REAL_TYPE) symbol_table.add(symbol_tmp_var) # Replace operation with a temporary (res_X). oper_parent.children[node.position] = Reference(symbol_res_var, parent=oper_parent) # tmp_var=X lhs = Reference(symbol_tmp_var) rhs = node.children[0] new_assignment = Assignment.create(lhs, rhs) new_assignment.parent = assignment.parent assignment.parent.children.insert(assignment.position, new_assignment) # if condition: tmp_var>0.0 lhs = Reference(symbol_tmp_var) rhs = Literal("0.0", REAL_TYPE) if_condition = BinaryOperation.create(BinaryOperation.Operator.GT, lhs, rhs) # then_body: res_var=tmp_var lhs = Reference(symbol_res_var) rhs = Reference(symbol_tmp_var) then_body = [Assignment.create(lhs, rhs)] # else_body: res_var=-1.0*tmp_var lhs = Reference(symbol_res_var) lhs_child = Reference(symbol_tmp_var) rhs_child = Literal("-1.0", REAL_TYPE) rhs = BinaryOperation.create(BinaryOperation.Operator.MUL, lhs_child, rhs_child) else_body = [Assignment.create(lhs, rhs)] # if [if_condition] then [then_body] else [else_body] if_stmt = IfBlock.create(if_condition, then_body, else_body) if_stmt.parent = assignment.parent assignment.parent.children.insert(assignment.position, if_stmt)
def test_validate_kernel_code_arg(monkeypatch): '''Test that this method returns successfully if its two arguments have identical content, otherwise test that the expected exceptions are raised. ''' kernel = DynKern() # Kernel name needs to be set when testing exceptions. kernel._name = "dummy" read_access = ArgumentInterface(ArgumentInterface.Access.READ) real_scalar_symbol = DataSymbol("generic_real_scalar", REAL_TYPE, interface=read_access) int_scalar_symbol = DataSymbol("generic_int_scalar", INTEGER_TYPE, interface=read_access) real_scalar_rw_symbol = DataSymbol("generic_scalar_rw", REAL_TYPE, interface=ArgumentInterface( ArgumentInterface.Access.READWRITE)) lfric_real_scalar_symbol = LfricRealScalarDataSymbol("scalar", interface=read_access) lfric_int_scalar_symbol = LfricIntegerScalarDataSymbol( "scalar", interface=read_access) lfric_real_field_symbol = RealFieldDataDataSymbol("field", dims=[1], fs="w0", interface=read_access) kernel._validate_kernel_code_arg(lfric_real_scalar_symbol, lfric_real_scalar_symbol) with pytest.raises(GenerationError) as info: kernel._validate_kernel_code_arg(lfric_real_scalar_symbol, lfric_int_scalar_symbol) assert ("Kernel argument 'scalar' has datatype 'Intrinsic.REAL' in kernel " "'dummy' but the LFRic API expects 'Intrinsic.INTEGER'" in str(info.value)) with pytest.raises(GenerationError) as info: kernel._validate_kernel_code_arg(real_scalar_symbol, lfric_real_scalar_symbol) assert ("Kernel argument 'generic_real_scalar' has precision 'UNDEFINED' " "in kernel 'dummy' but the LFRic API expects 'r_def'." in str(info.value)) with pytest.raises(GenerationError) as info: kernel._validate_kernel_code_arg(real_scalar_symbol, real_scalar_rw_symbol) assert ("Kernel argument 'generic_real_scalar' has intent 'READ' in " "kernel 'dummy' but the LFRic API expects intent " "'READWRITE'." in str(info.value)) with pytest.raises(GenerationError) as info: kernel._validate_kernel_code_arg(lfric_real_field_symbol, lfric_real_scalar_symbol) assert ("Argument 'field' to kernel 'dummy' should be a scalar " "according to the LFRic API, but it is not." in str(info.value)) with pytest.raises(GenerationError) as info: kernel._validate_kernel_code_arg(lfric_real_scalar_symbol, lfric_real_field_symbol) assert ("Argument 'scalar' to kernel 'dummy' should be an array " "according to the LFRic API, but it is not." in str(info.value)) undf = NumberOfUniqueDofsDataSymbol("undf", fs="w0", interface=read_access) lfric_real_field_symbol2 = RealFieldDataDataSymbol("field", dims=[Reference(undf)], fs="w0", interface=read_access) # if one of the dimensions is not a datasymbol then the arguments # are not checked. kernel._validate_kernel_code_arg(lfric_real_field_symbol, lfric_real_field_symbol2) kernel._validate_kernel_code_arg(lfric_real_field_symbol2, lfric_real_field_symbol) lfric_real_field_symbol3 = RealFieldDataDataSymbol("field", dims=[Reference(undf)], fs="w0", interface=read_access) monkeypatch.setattr(lfric_real_field_symbol3.datatype, "_shape", [Reference(undf), Reference(undf)]) with pytest.raises(GenerationError) as info: kernel._validate_kernel_code_arg(lfric_real_field_symbol2, lfric_real_field_symbol3) assert ("Argument 'field' to kernel 'dummy' should be an array with 2 " "dimension(s) according to the LFRic API, but found 1." in str(info.value)) lfric_real_field_symbol4 = RealFieldDataDataSymbol( "field", dims=[Reference(int_scalar_symbol)], fs="w0", interface=read_access) with pytest.raises(GenerationError) as info: kernel._validate_kernel_code_arg(lfric_real_field_symbol4, lfric_real_field_symbol2) assert ("For dimension 1 in array argument 'field' to kernel 'dummy' the " "following error was found: Kernel argument 'generic_int_scalar' " "has precision 'UNDEFINED' in kernel 'dummy' but the LFRic API " "expects 'i_def'" in str(info.value)) # monkeypatch lfric_real_scalar_symbol to return that it is not a # scalar in order to force the required exception. We do this by # changing the ScalarType as it is used when determining whether # the symbol is a scalar. monkeypatch.setattr(psyclone.psyir.symbols, "ScalarType", str) with pytest.raises(InternalError) as info: kernel._validate_kernel_code_arg(lfric_real_scalar_symbol, lfric_real_scalar_symbol) assert ("unexpected argument type found for 'scalar' in kernel 'dummy'. " "Expecting a scalar or an array." in str(info.value))
SYMBOL_TABLE.add(DTYPE_SYMBOL) # Create the definition of the 'field_type' FIELD_TYPE_DEF = StructureType.create([ ("data", ArrayType(SCALAR_TYPE, [10]), Symbol.Visibility.PUBLIC), ("grid", GRID_TYPE_SYMBOL, Symbol.Visibility.PUBLIC), ("sub_meshes", ArrayType(GRID_TYPE_SYMBOL, [3]), Symbol.Visibility.PUBLIC), ("flag", INTEGER4_TYPE, Symbol.Visibility.PUBLIC) ]) FIELD_TYPE_SYMBOL = TypeSymbol("field_type", FIELD_TYPE_DEF) CONTAINER_SYMBOL_TABLE.add(FIELD_TYPE_SYMBOL) # Create an argument of this derived type. At this point we know only that # DTYPE_SYMBOL refers to a type defined in the CONT container. FIELD_SYMBOL = DataSymbol("wind", FIELD_TYPE_SYMBOL, interface=ArgumentInterface( ArgumentInterface.Access.READWRITE)) SYMBOL_TABLE.add(FIELD_SYMBOL) SYMBOL_TABLE.specify_argument_list([FIELD_SYMBOL]) # Create an array of these derived types FIELD_BUNDLE_SYMBOL = DataSymbol("chi", ArrayType(FIELD_TYPE_SYMBOL, [3])) SYMBOL_TABLE.add(FIELD_BUNDLE_SYMBOL) print("Container Symbol Table:") print(str(CONTAINER_SYMBOL_TABLE)) print("Kernel Symbol Table:") print(str(SYMBOL_TABLE)) INDEX_SYMBOL = SYMBOL_TABLE.new_symbol(root_name="i",
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_name = symbol_table.new_symbol_name("i") i_loop_symbol = DataSymbol(i_loop_name, INTEGER_TYPE) symbol_table.add(i_loop_symbol) j_loop_name = symbol_table.new_symbol_name("j") j_loop_symbol = DataSymbol(j_loop_name, INTEGER_TYPE) symbol_table.add(j_loop_symbol) # Create "result(i)" result_dims = [Reference(i_loop_symbol)] if len(result.children) > 1: # Add any additional dimensions (in case of an array slice) result_dims.extend(result.children[1:]) result = Array.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) vector_dims.extend(vector.children[1:]) vector_array_reference = Array.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) array_dims.extend(matrix.children[2:]) matrix_array_reference = Array.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, multiply) # Create "result(i) = result(i) + matrix(i,j) * vector(j)" assign = Assignment.create(result, 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, 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 iloop.parent = assignment.parent assignment.parent.children.insert(assignment.position, iloop) # remove the original matmul assignment.parent.children.remove(assignment)
def test_oclw_kernelschedule(): '''Check the OpenCLWriter class kernelschedule_node visitor produces the expected OpenCL code. ''' # The kernelschedule OpenCL Backend relies on abstract methods that # need to be implemented by the APIs. A generic kernelschedule will # produce a NotImplementedError. oclwriter = OpenCLWriter() kschedule = KernelSchedule("kname") with pytest.raises(NotImplementedError) as error: _ = oclwriter(kschedule) assert "Abstract property. Which symbols are data arguments is " \ "API-specific." in str(error.value) # Mock abstract properties. (pytest monkeypatch does not work # with properties, used sub-class instead) class MockSymbolTable(SymbolTable): ''' Mock needed abstract methods of the Symbol Table ''' @property def iteration_indices(self): return self.argument_list[:2] @property def data_arguments(self): return self.argument_list[2:] kschedule.symbol_table.__class__ = MockSymbolTable # Create a sample symbol table and kernel schedule interface = ArgumentInterface(ArgumentInterface.Access.UNKNOWN) i = DataSymbol('i', INTEGER_TYPE, interface=interface) j = DataSymbol('j', INTEGER_TYPE, interface=interface) array_type = ArrayType(REAL_TYPE, [10, 10]) data1 = DataSymbol('data1', array_type, interface=interface) data2 = DataSymbol('data2', array_type, interface=interface) kschedule.symbol_table.add(i) kschedule.symbol_table.add(j) kschedule.symbol_table.add(data1) kschedule.symbol_table.add(data2) kschedule.symbol_table.specify_argument_list([i, j, data1, data2]) kschedule.addchild(Return(parent=kschedule)) result = oclwriter(kschedule) assert result == "" \ "__kernel void kname(\n" \ " __global double * restrict data1,\n" \ " __global double * restrict data2\n" \ " ){\n" \ " int data1LEN1 = get_global_size(0);\n" \ " int data1LEN2 = get_global_size(1);\n" \ " int data2LEN1 = get_global_size(0);\n" \ " int data2LEN2 = get_global_size(1);\n" \ " int i = get_global_id(0);\n" \ " int j = get_global_id(1);\n" \ " return;\n" \ "}\n\n" # Set a local_size value different to 1 into the KernelSchedule oclwriter = OpenCLWriter(kernels_local_size=4) result = oclwriter(kschedule) assert result == "" \ "__attribute__((reqd_work_group_size(4, 1, 1)))\n" \ "__kernel void kname(\n" \ " __global double * restrict data1,\n" \ " __global double * restrict data2\n" \ " ){\n" \ " int data1LEN1 = get_global_size(0);\n" \ " int data1LEN2 = get_global_size(1);\n" \ " int data2LEN1 = get_global_size(0);\n" \ " int data2LEN2 = get_global_size(1);\n" \ " int i = get_global_id(0);\n" \ " int j = get_global_id(1);\n" \ " return;\n" \ "}\n\n" # Add a symbol with a deferred interface and check that this raises the # expected error array_type = ArrayType(REAL_TYPE, [10, 10]) kschedule.symbol_table.add( DataSymbol('broken', array_type, interface=UnresolvedInterface())) with pytest.raises(VisitorError) as err: _ = oclwriter(kschedule) assert ("symbol table contains unresolved data entries (i.e. that have no " "defined Interface) which are not used purely to define the " "precision of other symbols: 'broken'" in str(err.value))
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 MIN intrinsic conversion transformation to the specified node. This node must be an MIN NaryOperation. The MIN NaryOperation is converted to equivalent inline code. This is implemented as a PSyIR transform from: .. code-block:: python R = ... MIN(A, B, C ...) ... to: .. code-block:: python res_min = A tmp_min = B IF tmp_min < res_min: res_min = tmp_min tmp_min = C IF tmp_min < res_min: res_min = tmp_min ... R = ... res_min ... where ``A``, ``B``, ``C`` ... could be arbitrarily complex PSyIR expressions and the ``...`` before and after ``MIN(A, B, C ...)`` can be arbitrary PSyIR code. This transformation requires the operation node to be a descendent of an assignment and will raise an exception if this is not the case. :param node: a MIN Binary- or Nary-Operation node. :type node: :py:class:`psyclone.psyGen.BinaryOperation` or \ :py:class:`psyclone.psyGen.NaryOperation` :param symbol_table: the symbol table. :type symbol_table: :py:class:`psyclone.psyir.symbols.SymbolTable` :param options: a dictionary with options for transformations. :type options: dictionary of string:values or None ''' # pylint: disable=too-many-locals self.validate(node) schedule = node.root symbol_table = schedule.symbol_table oper_parent = node.parent assignment = node.ancestor(Assignment) # Create a temporary result variable. There is an assumption # here that the MIN Operator returns a PSyIR real type. This # might not be what is wanted (e.g. the args might PSyIR # integers), or there may be errors (arguments are of # different types) but this can't be checked as we don't have # appropriate methods to query nodes (see #658). res_var = symbol_table.new_symbol_name("res_min") res_var_symbol = DataSymbol(res_var, REAL_TYPE) symbol_table.add(res_var_symbol) # Create a temporary variable. Again there is an # assumption here about the datatype - please see previous # comment (associated issue #658). tmp_var = symbol_table.new_symbol_name("tmp_min") tmp_var_symbol = DataSymbol(tmp_var, REAL_TYPE) symbol_table.add(tmp_var_symbol) # Replace operation with a temporary (res_var). oper_parent.children[node.position] = Reference(res_var_symbol, parent=oper_parent) # res_var=A lhs = Reference(res_var_symbol) new_assignment = Assignment.create(lhs, node.children[0]) new_assignment.parent = assignment.parent assignment.parent.children.insert(assignment.position, new_assignment) # For each of the remaining min arguments (B,C...) for expression in node.children[1:]: # tmp_var=(B or C or ...) lhs = Reference(tmp_var_symbol) new_assignment = Assignment.create(lhs, expression) new_assignment.parent = assignment.parent assignment.parent.children.insert(assignment.position, new_assignment) # if_condition: tmp_var<res_var lhs = Reference(tmp_var_symbol) rhs = Reference(res_var_symbol) if_condition = BinaryOperation.create(BinaryOperation.Operator.LT, lhs, rhs) # then_body: res_var=tmp_var lhs = Reference(res_var_symbol) rhs = Reference(tmp_var_symbol) then_body = [Assignment.create(lhs, rhs)] # if [if_condition] then [then_body] if_stmt = IfBlock.create(if_condition, then_body) if_stmt.parent = assignment.parent assignment.parent.children.insert(assignment.position, if_stmt)
def test_cw_gen_declaration(): '''Check the CWriter class gen_declaration method produces the expected declarations. ''' cwriter = CWriter() # Basic entries symbol = DataSymbol("dummy1", INTEGER_TYPE) result = cwriter.gen_declaration(symbol) assert result == "int dummy1" symbol = DataSymbol("dummy1", CHARACTER_TYPE) result = cwriter.gen_declaration(symbol) assert result == "char dummy1" symbol = DataSymbol("dummy1", BOOLEAN_TYPE) result = cwriter.gen_declaration(symbol) assert result == "bool dummy1" # Array argument array_type = ArrayType(REAL_TYPE, [2, ArrayType.Extent.ATTRIBUTE, 2]) symbol = DataSymbol("dummy2", array_type, interface=ArgumentInterface( ArgumentInterface.Access.READ)) result = cwriter.gen_declaration(symbol) assert result == "double * restrict dummy2" # Array with unknown access array_type = ArrayType(INTEGER_TYPE, [2, ArrayType.Extent.ATTRIBUTE, 2]) symbol = DataSymbol("dummy2", array_type, interface=ArgumentInterface( ArgumentInterface.Access.UNKNOWN)) result = cwriter.gen_declaration(symbol) assert result == "int * restrict dummy2" # Check invalid datatype produces and error symbol._datatype = "invalid" with pytest.raises(NotImplementedError) as error: _ = cwriter.gen_declaration(symbol) assert "Could not generate the C definition for the variable 'dummy2', " \ "type 'invalid' is currently not supported." in str(error.value)
from __future__ import print_function from psyclone.psyir.nodes import Reference, Literal, UnaryOperation, \ BinaryOperation, NaryOperation, Assignment, IfBlock, Loop, \ Container, Range, Array, Call, KernelSchedule from psyclone.psyir.symbols import DataSymbol, RoutineSymbol, SymbolTable, \ ContainerSymbol, ArgumentInterface, ScalarType, ArrayType, \ GlobalInterface, REAL_TYPE, REAL4_TYPE, REAL_DOUBLE_TYPE, INTEGER_TYPE, \ INTEGER_SINGLE_TYPE, INTEGER4_TYPE, INTEGER8_TYPE from psyclone.psyir.backend.fortran import FortranWriter from psyclone.psyir.backend.c import CWriter # Symbol table, symbols and scalar datatypes SYMBOL_TABLE = SymbolTable() TMP_NAME1 = SYMBOL_TABLE.new_symbol_name() ARG1 = DataSymbol(TMP_NAME1, REAL_TYPE, interface=ArgumentInterface( ArgumentInterface.Access.READWRITE)) SYMBOL_TABLE.add(ARG1) TMP_NAME2 = SYMBOL_TABLE.new_symbol_name() TMP_SYMBOL = DataSymbol(TMP_NAME2, REAL_DOUBLE_TYPE) SYMBOL_TABLE.add(TMP_SYMBOL) INDEX_NAME = SYMBOL_TABLE.new_symbol_name(root_name="i") INDEX_SYMBOL = DataSymbol(INDEX_NAME, INTEGER4_TYPE) SYMBOL_TABLE.add(INDEX_SYMBOL) SYMBOL_TABLE.specify_argument_list([ARG1]) REAL_KIND_NAME = SYMBOL_TABLE.new_symbol_name(root_name="RKIND") REAL_KIND = DataSymbol(REAL_KIND_NAME, INTEGER_TYPE, constant_value=8) SYMBOL_TABLE.add(REAL_KIND) ROUTINE_SYMBOL = RoutineSymbol("my_sub") # Array using precision defined by another symbol
def test_datasymbol_shape(): ''' Test that shape returns [] if the symbol is a scalar.''' data_symbol = DataSymbol("a", REAL4_TYPE) assert data_symbol.shape == []
def test_same_range(): '''Test that the same_range utility function behaves in the expected way. ''' with pytest.raises(TypeError) as info: ArrayRange2LoopTrans.same_range(None, None, None, None) assert ("The first argument to the same_range() method should be an " "Array but found 'NoneType'." in str(info.value)) array_type = ArrayType(REAL_TYPE, [10]) array_value = Array.create(DataSymbol("dummy", array_type), children=[DataNode("x")]) array_range = Array.create(DataSymbol("dummy", array_type), children=[Range()]) with pytest.raises(TypeError) as info: ArrayRange2LoopTrans.same_range(array_value, None, None, None) assert ("The second argument to the same_range() method should be an " "int but found 'NoneType'." in str(info.value)) with pytest.raises(TypeError) as info: ArrayRange2LoopTrans.same_range(array_value, 1, None, None) assert ("The third argument to the same_range() method should be an " "Array but found 'NoneType'." in str(info.value)) with pytest.raises(TypeError) as info: ArrayRange2LoopTrans.same_range(array_value, 1, array_value, None) assert ("The fourth argument to the same_range() method should be an " "int but found 'NoneType'." in str(info.value)) with pytest.raises(IndexError) as info: ArrayRange2LoopTrans.same_range(array_value, 1, array_value, 2) assert ("The value of the second argument to the same_range() method " "'1' should be less than the number of dimensions '1' in the " "associated array 'array1'." in str(info.value)) with pytest.raises(IndexError) as info: ArrayRange2LoopTrans.same_range(array_value, 0, array_value, 2) assert ("The value of the fourth argument to the same_range() method " "'2' should be less than the number of dimensions '1' in the " "associated array 'array2'." in str(info.value)) with pytest.raises(TypeError) as info: ArrayRange2LoopTrans.same_range(array_value, 0, array_value, 0) assert ("The child of the first array argument at the specified index (0) " "should be a Range node, but found 'DataNode'" in str(info.value)) with pytest.raises(TypeError) as info: ArrayRange2LoopTrans.same_range(array_range, 0, array_value, 0) assert ("The child of the second array argument at the specified index " "(0) should be a Range node, but found 'DataNode'" in str(info.value)) # lower bounds both use lbound, upper bounds both use ubound and # step is the same so everything matches. array_x = create_array_x(SymbolTable()) array_x_2 = create_array_x(SymbolTable()) assert ArrayRange2LoopTrans.same_range(array_x, 0, array_x_2, 0) is True # steps are different (calls string_compare) tmp = array_x_2.children[0].step array_x_2.children[0].step = Literal("2", INTEGER_TYPE) assert ArrayRange2LoopTrans.same_range(array_x, 0, array_x_2, 0) is False # Put step value back to what it was in case it affects the ubound # and lbound tests array_x_2.children[0].step = tmp # one of upper bounds uses ubound, other does not tmp1 = array_x_2.children[0].stop array_x_2.children[0].stop = Literal("2", INTEGER_TYPE) assert ArrayRange2LoopTrans.same_range(array_x, 0, array_x_2, 0) is False # neither use upper bound and are different (calls string_compare) tmp2 = array_x.children[0].stop array_x.children[0].stop = Literal("1", INTEGER_TYPE) assert ArrayRange2LoopTrans.same_range(array_x, 0, array_x_2, 0) is False # Put upper bounds back to what they were in case they affect the # lbound tests array_x_2.children[0].stop = tmp1 array_x.children[0].stop = tmp2 # one of lower bounds uses lbound, other does not array_x_2.children[0].start = Literal("1", INTEGER_TYPE) assert ArrayRange2LoopTrans.same_range(array_x, 0, array_x_2, 0) is False # neither use lower bound and are different (calls string_compare) array_x.children[0].start = Literal("2", INTEGER_TYPE) assert ArrayRange2LoopTrans.same_range(array_x, 0, array_x_2, 0) is False
def test_datasymbol_initialisation(): '''Test that a DataSymbol instance can be created when valid arguments are given, otherwise raise relevant exceptions.''' # Test with valid arguments assert isinstance(DataSymbol('a', REAL_SINGLE_TYPE), DataSymbol) assert isinstance(DataSymbol('a', REAL_DOUBLE_TYPE), DataSymbol) assert isinstance(DataSymbol('a', REAL4_TYPE), DataSymbol) kind = DataSymbol('r_def', INTEGER_SINGLE_TYPE) real_kind_type = ScalarType(ScalarType.Intrinsic.REAL, kind) assert isinstance(DataSymbol('a', real_kind_type), DataSymbol) # real constants are not currently supported assert isinstance(DataSymbol('a', INTEGER_SINGLE_TYPE), DataSymbol) assert isinstance(DataSymbol('a', INTEGER_DOUBLE_TYPE, constant_value=0), DataSymbol) assert isinstance(DataSymbol('a', INTEGER4_TYPE), DataSymbol) assert isinstance(DataSymbol('a', CHARACTER_TYPE), DataSymbol) assert isinstance(DataSymbol('a', CHARACTER_TYPE, constant_value="hello"), DataSymbol) assert isinstance(DataSymbol('a', BOOLEAN_TYPE), DataSymbol) assert isinstance(DataSymbol('a', BOOLEAN_TYPE, constant_value=False), DataSymbol) array_type = ArrayType(REAL_SINGLE_TYPE, [ArrayType.Extent.ATTRIBUTE]) assert isinstance(DataSymbol('a', array_type), DataSymbol) array_type = ArrayType(REAL_SINGLE_TYPE, [3]) assert isinstance(DataSymbol('a', array_type), DataSymbol) array_type = ArrayType(REAL_SINGLE_TYPE, [3, ArrayType.Extent.ATTRIBUTE]) assert isinstance(DataSymbol('a', array_type), DataSymbol) assert isinstance(DataSymbol('a', REAL_SINGLE_TYPE), DataSymbol) assert isinstance(DataSymbol('a', REAL8_TYPE), DataSymbol) dim = DataSymbol('dim', INTEGER_SINGLE_TYPE) array_type = ArrayType(REAL_SINGLE_TYPE, [dim]) assert isinstance(DataSymbol('a', array_type), DataSymbol) array_type = ArrayType(REAL_SINGLE_TYPE, [3, dim, ArrayType.Extent.ATTRIBUTE]) assert isinstance(DataSymbol('a', array_type), DataSymbol) assert isinstance( DataSymbol('a', REAL_SINGLE_TYPE, interface=ArgumentInterface( ArgumentInterface.Access.READWRITE)), DataSymbol) assert isinstance( DataSymbol('a', REAL_SINGLE_TYPE, visibility=Symbol.Visibility.PRIVATE), DataSymbol)
def apply(self, node, options=None): '''Apply the SIGN intrinsic conversion transformation to the specified node. This node must be a SIGN BinaryOperation. The SIGN BinaryOperation is converted to equivalent inline code. This is implemented as a PSyIR transform from: .. code-block:: python R = ... SIGN(A, B) ... to: .. code-block:: python tmp_abs = A if tmp_abs < 0.0: res_abs = tmp_abs*-1.0 else: res_abs = tmp_abs res_sign = res_abs tmp_sign = B if tmp_sign < 0.0: res_sign = res_sign*-1.0 R = ... res_sign ... where ``A`` and ``B`` could be arbitrarily complex PSyIR expressions, ``...`` could be arbitrary PSyIR code and where ``ABS`` has been replaced with inline code by the NemoAbsTrans transformation. This transformation requires the operation node to be a descendent of an assignment and will raise an exception if this is not the case. :param node: a SIGN BinaryOperation node. :type node: :py:class:`psyclone.psyGen.BinaryOperation` :param symbol_table: the symbol table. :type symbol_table: :py:class:`psyclone.psyir.symbols.SymbolTable` :param options: a dictionary with options for transformations. :type options: dictionary of string:values or None ''' # pylint: disable=too-many-locals self.validate(node) schedule = node.root symbol_table = schedule.symbol_table oper_parent = node.parent assignment = node.ancestor(Assignment) # Create two temporary variables. There is an assumption here # that the SIGN Operator returns a PSyIR real type. This might # not be what is wanted (e.g. the args might PSyIR integers), # or there may be errors (arguments are of different types) # but this can't be checked as we don't have the appropriate # methods to query nodes (see #658). res_var = symbol_table.new_symbol_name("res_sign") res_var_symbol = DataSymbol(res_var, REAL_TYPE) symbol_table.add(res_var_symbol) tmp_var = symbol_table.new_symbol_name("tmp_sign") tmp_var_symbol = DataSymbol(tmp_var, REAL_TYPE) symbol_table.add(tmp_var_symbol) # Replace operator with a temporary (res_var). oper_parent.children[node.position] = Reference(res_var_symbol, parent=oper_parent) # res_var=ABS(A) lhs = Reference(res_var_symbol) rhs = UnaryOperation.create(UnaryOperation.Operator.ABS, node.children[0]) new_assignment = Assignment.create(lhs, rhs) new_assignment.parent = assignment.parent assignment.parent.children.insert(assignment.position, new_assignment) # Replace the ABS intrinsic with inline code. abs_trans = Abs2CodeTrans() abs_trans.apply(rhs, symbol_table) # tmp_var=B lhs = Reference(tmp_var_symbol) new_assignment = Assignment.create(lhs, node.children[1]) new_assignment.parent = assignment.parent assignment.parent.children.insert(assignment.position, new_assignment) # if_condition: tmp_var<0.0 lhs = Reference(tmp_var_symbol) rhs = Literal("0.0", REAL_TYPE) if_condition = BinaryOperation.create(BinaryOperation.Operator.LT, lhs, rhs) # then_body: res_var=res_var*-1.0 lhs = Reference(res_var_symbol) lhs_child = Reference(res_var_symbol) rhs_child = Literal("-1.0", REAL_TYPE) rhs = BinaryOperation.create(BinaryOperation.Operator.MUL, lhs_child, rhs_child) then_body = [Assignment.create(lhs, rhs)] # if [if_condition] then [then_body] if_stmt = IfBlock.create(if_condition, then_body) if_stmt.parent = assignment.parent assignment.parent.children.insert(assignment.position, if_stmt)