def test_goloop_field_accesses(): ''' Check that for a GOcean kernel appropriate field accesses (based on the meta data) are added to the dependency analysis. ''' _, invoke = get_invoke("large_stencil.f90", "gocean1.0", name="invoke_large_stencil", dist_mem=False) do_loop = invoke.schedule.children[0] assert isinstance(do_loop, Loop) var_accesses = VariablesAccessInfo(invoke.schedule) # cu_fld has a pointwise write access in the first loop: cu_fld = var_accesses[Signature("cu_fld")] assert len(cu_fld.all_accesses) == 1 assert cu_fld.all_accesses[0].access_type == AccessType.WRITE assert cu_fld.all_accesses[0].component_indices == [["i", "j"]] # The stencil is defined to be GO_STENCIL(123,110,100)) for # p_fld. Make sure that these 9 accesses are indeed reported: p_fld = var_accesses[Signature("p_fld")] all_indices = [access.component_indices for access in p_fld.all_accesses] for test_index in [["i-1", "j+1"], ["i", "j+1"], ["i", "j+2"], ["i+1", "j+1"], ["i+2", "j+2"], ["i+3", "j+3"], ["i-1", "j"], ["i", "j"], ["i-1", "j-1"]]: assert [test_index] in all_indices # Since we have 9 different indices found (above), the following # test guarantees that we don't get any invalid accesses reported. assert len(p_fld.all_accesses) == 9
def reference_accesses(self, var_accesses): '''Get all variable access information. It combines the data from the loop bounds (start, stop and step), as well as the loop body. The loop variable is marked as 'READ+WRITE' and references in start, stop and step are marked as 'READ'. :param var_accesses: VariablesAccessInfo instance that stores the \ information about variable accesses. :type var_accesses: \ :py:class:`psyclone.core.access_info.VariablesAccessInfo` ''' # It is important to first add the WRITE access, since this way # the dependency analysis for declaring openmp private variables # will automatically declare the loop variables to be private # (write access before read) var_accesses.add_access(Signature(self.variable.name), AccessType.WRITE, self) var_accesses.add_access(Signature(self.variable.name), AccessType.READ, self) # Accesses of the start/stop/step expressions self.start_expr.reference_accesses(var_accesses) self.stop_expr.reference_accesses(var_accesses) self.step_expr.reference_accesses(var_accesses) var_accesses.next_location() for child in self.loop_body.children: child.reference_accesses(var_accesses) var_accesses.next_location()
def test_var_name(): '''Test that the variable name is returned as expected.''' sig_a = Signature("a") assert sig_a.var_name == "a" sig_a_b = Signature(sig_a, Signature("b")) assert str(sig_a_b) == "a%b" assert sig_a_b.var_name == "a"
def append(self, var_name, var_accesses=None, var_access_name=None, mode=AccessType.READ): '''Appends the specified variable name to the list of all arguments. If var_accesses is given, it will also record the access to the variable. The name of the variable accessed can be overwritten by specifying var_access_name. By default it is assumed that access mode is READ (which can be set with mode). :param str var_name: the name of the variable. :param var_accesses: optional class to store variable access \ information. :type var_accesses: \ :py:class:`psyclone.core.access_info.VariablesAccessInfo` :param str var_access_name: optional name of the variable for \ which access information is stored (used e.g. when the \ actual argument is field_proxy, but the access is to be \ recorded for field). :param mode: optional access mode (defaults to READ). :type mode: :py:class:`psyclone.core.access_type.AccessType` ''' self._arglist.append(var_name) if var_accesses is not None: if var_access_name: var_accesses.add_access(Signature(var_access_name), mode, self._kern) else: var_accesses.add_access(Signature(var_name), mode, self._kern)
def test_concatenate_signature(): '''Tests that signature can be concatenated.''' sig_b = Signature("b") sig_a_b = Signature("a", sig_b) assert str(sig_a_b) == "a%b" sig_b_a_b = Signature(sig_b, sig_a_b) assert str(sig_b_a_b) == "b%a%b" sig_c_d_b_a_b = Signature(("c", "d"), sig_b_a_b) assert str(sig_c_d_b_a_b) == "c%d%b%a%b"
def test_symbol_array_detection(fortran_reader): '''Verifies the handling of arrays together with access information. ''' code = '''program test_prog use some_mod real, dimension(5,5) :: b, c integer :: i a = b(i) + c end program test_prog''' psyir = fortran_reader.psyir_from_source(code) scalar_assignment = psyir.children[0] symbol_table = scalar_assignment.scope.symbol_table sym_a = symbol_table.lookup("a") with pytest.raises(InternalError) as error: sym_a.is_array_access(index_variable="j") assert "In Symbol.is_array_access: index variable 'j' specified, but " \ "no access information given." in str(error.value) vai = VariablesAccessInfo() scalar_assignment.reference_accesses(vai) # For 'a' we don't have access information, nor symbol table information access_info_a = vai[Signature("a")] with pytest.raises(ValueError) as error: sym_a.is_array_access(access_info=access_info_a) assert "No array information is available for the symbol 'a'" \ in str(error.value) # For the access to 'b' we will find array access information: access_info_b = vai[Signature("b")] sym_b = symbol_table.lookup("b") b_is_array = sym_b.is_array_access(access_info=access_info_b) assert b_is_array # For the access to 'c' we don't have access information, but # have symbol table information. access_info_c = vai[Signature("c")] sym_c = symbol_table.lookup("c") c_is_array = sym_c.is_array_access(access_info=access_info_c) assert c_is_array # Test specifying the index variable. The access to 'b' is # considered an array access when ysing the index variable 'i'. access_info_b = vai[Signature("b")] sym_b = symbol_table.lookup("b") b_is_array = sym_b.is_array_access(access_info=access_info_b, index_variable="i") assert b_is_array # Verify that the access to 'b' is not considered to be an # array access regarding the loop variable 'j' (the access # is loop independent): b_is_array = sym_b.is_array_access(access_info=access_info_b, index_variable="j") assert not b_is_array
def get_signature_and_indices(self): ''':returns: the Signature of this array of structure reference, \ and a list of lists of the indices used for each component. :rtype: tuple(:py:class:`psyclone.core.Signature`, list of \ lists of indices) ''' sub_sig, indices = self.children[0].get_signature_and_indices() sig = Signature(self.name) return (Signature(sig, sub_sig), [self.indices] + indices)
def test_derived_type(parser): ''' Tests assignment to derived type variables. ''' reader = FortranStringReader('''program test use my_mod, only: my_type type(my_type) :: a, b integer :: ji, jj, jpi, jpj do jj = 1, jpj ! loop 0 do ji = 1, jpi a%b(ji, jj) = a%b(ji, jj-1)+1 end do end do do jj = 1, jpj ! loop 0 do ji = 1, jpi a%b(ji, jj) = a%b(ji, jj-1)+1 b%b(ji, jj) = b%b(ji, jj-1)+1 end do end do end program test''') prog = parser(reader) psy = PSyFactory("nemo", distributed_memory=False).create(prog) loops = psy.invokes.get("test").schedule dep_tools = DependencyTools(["levels", "lat"]) parallel = dep_tools.can_loop_be_parallelised(loops[0], "jj") assert not parallel # Test that testing is stopped with the first unparallelisable statement parallel = dep_tools.can_loop_be_parallelised(loops[1], "jj") assert not parallel # Test that only one message is stored, i.e. no message for the # next assignment to a derived type. assert len(dep_tools.get_all_messages()) == 1 parallel = dep_tools.can_loop_be_parallelised(loops[1], "jj", test_all_variables=True) assert not parallel # Now we must have two messages, one for each of the two assignments assert len(dep_tools.get_all_messages()) == 2 # Test that variables are ignored as expected. parallel = dep_tools.\ can_loop_be_parallelised(loops[1], "jj", signatures_to_ignore=[Signature(("a", "b"))]) assert not parallel assert len(dep_tools.get_all_messages()) == 1 # If both derived types are ignored, the loop should be marked # to be parallelisable parallel = dep_tools.\ can_loop_be_parallelised(loops[1], "jj", signatures_to_ignore=[Signature(("a", "b")), Signature(("b", "b"))]) assert len(dep_tools.get_all_messages()) == 0 assert parallel
def test_const_argument(): '''Check that using a const scalar as parameter works, i.e. is not listed as input variable.''' _, invoke = get_invoke("test00.1_invoke_kernel_using_const_scalar.f90", api="gocean1.0", idx=0) dep_tools = DependencyTools() input_list = dep_tools.get_input_parameters(invoke.schedule) # Make sure the constant '0' is not listed assert input_list == [ Signature('p_fld'), Signature(('p_fld', 'grid', 'subdomain', 'internal', 'xstop')), Signature(('p_fld', 'grid', 'tmask')) ]
def test_signature_errors(): '''Tests error handling of Signature class. ''' with pytest.raises(InternalError) as err: _ = Signature(1) assert "Got unexpected type 'int' in Signature" in str(err.value)
def test_if_statement(parser): ''' Tests handling an if statement ''' reader = FortranStringReader('''program test_prog integer :: a, b, i real, dimension(5) :: p, q, r if (a .eq. b) then p(i) = q(i) else q(i) = r(i) endif end program test_prog''') ast = parser(reader) psy = PSyFactory(API).create(ast) schedule = psy.invokes.get("test_prog").schedule if_stmt = schedule.children[0] assert isinstance(if_stmt, IfBlock) var_accesses = VariablesAccessInfo(if_stmt) assert str(var_accesses) == "a: READ, b: READ, i: READ, p: WRITE, "\ "q: READ+WRITE, r: READ" # Test that the two accesses to 'q' indeed show up as q_accesses = var_accesses[Signature("q")].all_accesses assert len(q_accesses) == 2 assert q_accesses[0].access_type == AccessType.READ assert q_accesses[1].access_type == AccessType.WRITE assert q_accesses[0].location < q_accesses[1].location
def test_derived_type_array(array, indices, fortran_writer, fortran_reader): '''This function tests the handling of derived array types. ''' code = '''module test contains subroutine tmp() use my_mod !use my_mod, only: something !type(something) :: a, b, c integer :: i, j, k c(i)%e(j,k) = {0} end subroutine tmp end module test'''.format(array) schedule = fortran_reader.psyir_from_source(code).children[0] node1 = schedule.children[0][0] vai1 = VariablesAccessInfo(node1) assert isinstance(node1, Assignment) assert str(vai1) == "a%b%c: READ, c%e: WRITE, i: READ, j: READ, k: READ" # Verify that the index expression is correct. Convert the index # expression to a list of list of strings to make this easier: sig = Signature(("a", "b", "c")) access = vai1[sig][0] assert to_fortran(fortran_writer, access.component_indices) == indices
def get_signature_and_indices(self): ''':returns: the Signature of this reference, and \ an empty list of lists as 'indices' since this reference does \ not represent an array access. :rtype: tuple(:py:class:`psyclone.core.Signature`, list of \ list of indices) ''' return (Signature(self.name), [[]])
def get_signature_and_indices(self): ''':returns: the Signature of this member access, and a list \ of list of the indices used for each component, which is empty \ in this case since it is not an array access. :rtype: tuple(:py:class:`psyclone.core.Signature`, list of \ lists of indices) ''' return (Signature(self.name), [[]])
def test_user_defined_variables(parser): ''' Test reading and writing to user defined variables. ''' reader = FortranStringReader('''program test_prog use some_mod, only: my_type type(my_type) :: a, e integer :: ji, jj, d a%b(ji)%c(ji, jj) = d e%f = d end program test_prog''') prog = parser(reader) psy = PSyFactory("nemo", distributed_memory=False).create(prog) loops = psy.invokes.get("test_prog").schedule var_accesses = VariablesAccessInfo(loops) assert var_accesses[Signature(("a", "b", "c"))].is_written assert var_accesses[Signature(("e", "f"))].is_written
def test_assignment(parser): ''' Check that assignments set the right read/write accesses. ''' reader = FortranStringReader('''program test_prog use some_mod, only: f integer :: i, j real :: a, b, e, x, y real, dimension(5,5) :: c, d a = b c(i,j) = d(i,j+1)+e+f(x,y) c(i) = c(i) + 1 d(i,j) = sqrt(e(i,j)) end program test_prog''') ast = parser(reader) psy = PSyFactory(API).create(ast) schedule = psy.invokes.get("test_prog").schedule # Simple scalar assignment: a = b scalar_assignment = schedule.children[0] assert isinstance(scalar_assignment, Assignment) var_accesses = VariablesAccessInfo(scalar_assignment) # Test some test functions explicitly: assert var_accesses.is_written(Signature("a")) assert not var_accesses.is_read(Signature("a")) assert not var_accesses.is_written(Signature("b")) assert var_accesses.is_read(Signature("b")) # Array element assignment: c(i,j) = d(i,j+1)+e+f(x,y) array_assignment = schedule.children[1] assert isinstance(array_assignment, Assignment) var_accesses = VariablesAccessInfo(array_assignment) assert str(var_accesses) == "c: WRITE, d: READ, e: READ, f: READ, "\ "i: READ, j: READ, x: READ, y: READ" # Increment operation: c(i) = c(i)+1 increment_access = schedule.children[2] assert isinstance(increment_access, Assignment) var_accesses = VariablesAccessInfo(increment_access) assert str(var_accesses) == "c: READ+WRITE, i: READ" # Using an intrinsic: sqrt_access = schedule.children[3] assert isinstance(sqrt_access, Assignment) var_accesses = VariablesAccessInfo(sqrt_access) assert str(var_accesses) == "d: WRITE, e: READ, i: READ, j: READ"
def get_signature_and_indices(self): ''':returns: the Signature of this structure member, and \ a list of the indices used for each component (empty list \ for this component, since the access is not an array - but \ other components might have indices). :rtype: tuple(:py:class:`psyclone.core.Signature`, list of \ list of indices) ''' sub_sig, indices = self.children[0].get_signature_and_indices() return (Signature(self.name, sub_sig), [[]] + indices)
def test_signature_dict(): '''Test that Signature instances work as expected as dictionary keys. ''' sig1 = Signature("a") sig2 = Signature("a") assert sig1 is not sig2 # Make sure that different instances representing the same signature # will have the same hash: test_dict = {} test_dict[sig1] = "a" assert test_dict[sig2] == "a" sig3 = Signature(("a", "b")) test_dict[sig3] = "ab" sig4 = Signature(("a", "c")) test_dict[sig4] = "ac" assert len(test_dict) == 3
def test_signature(): '''Test the Signature class. ''' assert str(Signature("a")) == "a" assert str(Signature(("a", ))) == "a" assert str(Signature(("a", "b", "c"))) == "a%b%c" assert repr(Signature("a")) == "Signature(a)" assert repr(Signature(("a", ))) == "Signature(a)" assert repr(Signature(("a", "b", "c"))) == "Signature(a%b%c)" assert Signature("a") != "a"
def test_user_defined_variables(parser): ''' Test reading and writing to user defined variables. This is not supported atm because the dependence analysis for these PSyIR nodes has not yet been implemented (#1028). Also TODO #1028: is this a duplicate of test_derived_type in tests/psyir/dependency_tools_test.py? ''' reader = FortranStringReader('''program test_prog use some_mod, only: my_type type(my_type) :: a, e integer :: ji, jj, d a%b%c(ji, jj) = d e%f = d end program test_prog''') prog = parser(reader) psy = PSyFactory("nemo", distributed_memory=False).create(prog) loops = psy.invokes.get("test_prog").schedule var_accesses = VariablesAccessInfo(loops) assert var_accesses[Signature("a % b % c")].is_written assert var_accesses[Signature("e % f")].is_written
def test_reference_accesses(): ''' Test the reference_accesses method. ''' dref = nodes.StructureReference.create( symbols.DataSymbol( "grid", symbols.DataTypeSymbol("grid_type", symbols.DeferredType())), ["data"]) var_access_info = VariablesAccessInfo() dref.reference_accesses(var_access_info) assert var_access_info.all_signatures == [Signature(("grid", "data"))] # By default all accesses are marked as read assert str(var_access_info) == "grid%data: READ"
def get_signature_and_indices(self): ''':returns: the Signature of this structure reference, and \ a list of the indices used for each component (empty list \ if an access is not an array). :rtype: tuple(:py:class:`psyclone.core.Signature`, list of \ list of indices) ''' # Get the signature of self: my_sig, my_index = \ super(StructureReference, self).get_signature_and_indices() # Then the sub-signature of the member, and indices used: sub_sig, indices = self.children[0].get_signature_and_indices() # Combine signature and indices return (Signature(my_sig, sub_sig), my_index + indices)
def reference_accesses(self, var_accesses): '''Get all variable access information from this node, i.e. it sets this variable to be read. :param var_accesses: VariablesAccessInfo instance that stores the \ information about variable accesses. :type var_accesses: \ :py:class:`psyclone.core.access_info.VariablesAccessInfo` ''' if (self.parent and isinstance(self.parent, Operation) and self.parent.operator in [BinaryOperation.Operator.LBOUND, BinaryOperation.Operator.UBOUND] and self.parent.children.index(self) == 0): # This reference is the first argument to a lbound or # ubound intrinsic. These intrinsics do not access the # array elements, they determine the array # bounds. Therefore there is no data dependence. return sig = Signature(self.name) var_accesses.add_access(sig, AccessType.READ, self)
def extend(self, list_var_name, var_accesses=None, mode=AccessType.READ): '''Appends all variable names in the argument list to the list of all arguments. If var_accesses is given, it will also record the access to the variables. By default any access will be recorded as a read-only access, but this can be changed (for all variables included) using mode. :param list_var_name: the list with name of the variables to append. :type list_var_name: list of str. :param var_accesses: optional class to store variable access \ information. :type var_accesses: \ :py:class:`psyclone.core.access_info.VariablesAccessInfo` :param mode: optional access mode (defaults to READ). :type mode: :py:class:`psyclone.core.access_type.AccessType` ''' self._arglist.extend(list_var_name) if var_accesses: for var_name in list_var_name: var_accesses.add_access(Signature(var_name), mode, self._kern)
def test_signature_sort(): '''Test that signatures can be sorted.''' sig_list = [ Signature("c"), Signature("a"), Signature("b"), Signature(("b", "a")), Signature(("a", "c")), Signature(("a", "b")) ] assert str(sig_list) == "[Signature(c), Signature(a), Signature(b), " \ "Signature(b%a), Signature(a%c), Signature(a%b)]" sig_list.sort() assert str(sig_list) == "[Signature(a), Signature(a%b), Signature(a%c), " \ "Signature(b), Signature(b%a), Signature(c)]"
def reference_accesses(self, var_accesses): '''Get all variable access information. All variables used as indices in the access of the array will be added as READ. :param var_accesses: variable access information. :type var_accesses: \ :py:class:`psyclone.core.access_info.VariablesAccessInfo` ''' # This will set the array-name as READ super(ArrayMixin, self).reference_accesses(var_accesses) # Now add all children: Note that the class Reference # does not recurse to the children (which store the indices), so at # this stage no index information has been stored: list_indices = [] for child in self.children: child.reference_accesses(var_accesses) list_indices.append(child) if list_indices: var_info = var_accesses[Signature(self.name)] # The last entry in all_accesses is the one added above # in super(ArrayReference...). Add the indices to that entry. var_info.all_accesses[-1].indices = list_indices
def test_inout_parameters_nemo(parser): '''Test detection of input and output parameters in NEMO. ''' reader = FortranStringReader('''program test integer :: ji, jj, jpi, jpj real :: a(5,5), c(5,5), b do jj = 1, jpj ! loop 0 do ji = 1, jpi a(ji, jj) = b+c(ji, jj) end do end do end program test''') prog = parser(reader) psy = PSyFactory("nemo", distributed_memory=False).create(prog) loops = psy.invokes.get("test").schedule dep_tools = DependencyTools() input_list = dep_tools.get_input_parameters(loops) # Use set to be order independent input_set = set(input_list) assert input_set == set( [Signature("b"), Signature("c"), Signature("jpi"), Signature("jpj")]) output_list = dep_tools.get_output_parameters(loops) # Use set to be order independent output_set = set(output_list) assert output_set == set( [Signature("jj"), Signature("ji"), Signature("a")]) in_list1, out_list1 = dep_tools.get_in_out_parameters(loops) assert in_list1 == input_list assert out_list1 == output_list
def test_signature_comparison(): ''' Test that two Signatures can be compared for equality and not equality. ''' # pylint: disable=unneeded-not assert Signature(("a", "b")) == Signature(("a", "b")) assert not Signature(("a", "b")) == Signature(("a", "c")) assert Signature(("a", "b")) != Signature(("a", "c")) assert not Signature(("a", "b")) != Signature(("a", "b")) assert Signature(("a", "c")) >= Signature(("a", "b")) assert not Signature(("a", "b")) >= Signature(("a", "c")) assert Signature(("a", "c")) > Signature(("a", "b")) assert not Signature(("a", "b")) > Signature(("a", "c")) assert Signature(("a", "b")) <= Signature(("a", "c")) assert not Signature(("a", "c")) <= Signature(("a", "b")) assert Signature(("a", "b")) < Signature(("a", "c")) assert not Signature(("a", "c")) < Signature(("a", "b")) # Comparison with other types should work for == and !=: assert not Signature(("a", "b")) == 2 assert Signature(("a", "b")) != 2 # pylint: enable=unneeded-not # Error cases: comparison of signature with other type. with pytest.raises(TypeError) as err: _ = Signature(("a", "b")) < 1 assert "'<' not supported between instances of 'Signature' and 'int'" \ in str(err.value) with pytest.raises(TypeError) as err: _ = Signature(("a", "b")) <= "a" assert "'<=' not supported between instances of 'Signature' and 'str'" \ in str(err.value) with pytest.raises(TypeError) as err: _ = Signature(("a", "b")) > [1] assert "'>' not supported between instances of 'Signature' and 'list'" \ in str(err.value) with pytest.raises(TypeError) as err: _ = Signature(("a", "b")) >= (1, 2) assert "'>=' not supported between instances of 'Signature' and 'tuple'" \ in str(err.value)
def test_signature(): '''Test the Signature class. ''' assert str(Signature("a")) == "a" assert str(Signature(("a", ))) == "a" assert str(Signature(("a", "b", "c"))) == "a%b%c" assert repr(Signature("a")) == "Signature(a)" assert repr(Signature(("a", ))) == "Signature(a)" assert repr(Signature(("a", "b", "c"))) == "Signature(a%b%c)" assert repr(Signature(["a", "b", "c"])) == "Signature(a%b%c)" assert Signature("a") != "a" sig = Signature(("a", "b", "c")) assert sig.is_structure assert sig[0] == "a" assert sig[2] == "c" assert sig[-1] == "c" assert sig[0:2] == ("a", "b") assert Signature(["a", "b", "c"]).is_structure assert not Signature(("a")).is_structure
def create_driver(self, input_list, output_list): # pylint: disable=too-many-locals, too-many-statements '''This function creates a driver that can read the output created by the extraction code. This is a stand-alone program that will read the input data, calls the kernels/ instrumented region, and then compares the results with the stored results in the file. TODO: #644: we need type information here. :param input_list: list of variables that are input parameters. :type input_list: list of str :param output_list: list of variables that are output parameters. :type output_list: list or str ''' all_sigs = list(set(input_list).union(set(output_list))) all_sigs.sort() module_name, region_name = self.region_identifier module = ModuleGen(name=module_name) prog = SubroutineGen(parent=module, name=module_name + "_code", implicitnone=True) module.add(prog) use = UseGen(prog, self.add_psydata_class_prefix("psy_data_mod"), only=True, funcnames=[self.add_psydata_class_prefix("PSyDataType")]) prog.add(use) # Use a symbol table to make sure all variable names are unique sym_table = GOSymbolTable() sym = Symbol("PSyDataType") sym_table.add(sym) root_name = self.add_psydata_class_prefix("psy_data") psy_data = sym_table.new_symbol(root_name).name var_decl = TypeDeclGen( prog, datatype=self.add_psydata_class_prefix("PSyDataType"), entity_decls=[psy_data]) prog.add(var_decl) call = CallGen( prog, "{0}%OpenRead(\"{1}\", \"{2}\")".format(psy_data, module_name, region_name)) prog.add(call) post_suffix = self._post_name # Variables might need to be renamed in order to guarantee unique # variable names in the driver: An example of this would be if the # user code contains a variable 'dx', and the kernel takes a # property 'dx' as well. In the original code that is no problem, # since the property is used via field%grid%dx. But the stand-alone # driver renames field%grid%dx to dx, which can cause a name clash. # Similar problems can exist with any user defined type, since all # user defined types are rewritten to just use the field name. # We use a mapping to support renaming of variables: it takes as # key the variable as used in the original program (e.g. 'dx' from # an expression like field%grid%dx), and maps it to a unique local # name (e.g. dx_0). rename_variable = {} for signature in all_sigs: # TODO #644: we need to identify arrays!! # Support GOcean properties, which are accessed via a # derived type (e.g. 'fld%grid%dx'). In this stand-alone # driver we don't have the derived type, instead we create # variable based on the field in the derived type ('dx' # in the example above), and pass this variable to the # instrumented code. var_name = str(signature) # In case of a derived type, this will only leave the last # component, which is used as the local variable name. For # non-derived type this is just the name of the variable anyway. local_name = signature[-1] unique_local_name = sym_table.new_symbol(local_name).name rename_variable[local_name] = unique_local_name local_name = unique_local_name # TODO: #644 - we need to identify arrays!! # Any variable used needs to be defined. We also need # to handle the kind property better and not rely on # a hard-coded value. decl = DeclGen(prog, "real", [local_name], kind="8", dimension=":,:", allocatable=True) prog.add(decl) is_input = signature in input_list is_output = signature in output_list if is_input and not is_output: # We only need the pre-variable, and we can read # it from the file (this call also allocates space for it). call = CallGen( prog, "{0}%ReadVariable(\"{1}\", {2})".format( psy_data, var_name, local_name)) prog.add(call) elif is_input: # Now must be input and output: # First read the pre-variable (which also allocates it): call = CallGen( prog, "{0}%ReadVariable(\"{1}\", {2})".format( psy_data, var_name, local_name)) prog.add(call) # Then declare the post variable, and and read its values # (ReadVariable will also allocate it) sym = Symbol(local_name + post_suffix) sym_table.add(sym) decl = DeclGen(prog, "real", [local_name + post_suffix], dimension=":,:", kind="8", allocatable=True) prog.add(decl) call = CallGen( prog, "{0}%ReadVariable(\"{1}{3}\", {2}{3})".format( psy_data, var_name, local_name, post_suffix)) prog.add(call) else: # Now the variable is output only. We need to read the # post variable in, and create and allocate a pre variable # with the same size as the post sym = Symbol(local_name + post_suffix) sym_table.add(sym) decl = DeclGen(prog, "real", [local_name + post_suffix], dimension=":,:", kind="8", allocatable=True) prog.add(decl) call = CallGen( prog, "{0}%ReadVariable(\"{1}{3}\", {2}{3})".format( psy_data, var_name, local_name, post_suffix)) prog.add(call) decl = DeclGen(prog, "real", [local_name], kind="8", dimension=":,:", allocatable=True) prog.add(decl) alloc = AllocateGen(prog, [var_name], mold="{0}".format(local_name + post_suffix)) prog.add(alloc) # Initialise the variable with 0, since it might contain # values that are not set at all (halo regions, or a # kernel might not set all values). This way the array # comparison with the post value works as expected # TODO #644 - create the right "0.0" type here (e.g. # 0.0d0, ...) assign = AssignGen(prog, local_name, "0.0d0") prog.add(assign) # Now add the region that was extracted here: prog.add(CommentGen(prog, "")) prog.add(CommentGen(prog, " RegionStart")) # For the driver we have to re-create the code of the # instrumented region, but in this stand-alone driver the # arguments are not dl_esm_inf fields anymore, but simple arrays. # Similarly, for properties we cannot use e.g. 'fld%grid%dx' # anymore, we have to use e.g. a local variable 'dx' that has # been created. Since we are using the existing way of creating # the code for the instrumented region, we need to modify how # these variables are created. We do this by temporarily # modifying the properties in the config file. api_config = Config.get().api_conf("gocean1.0") all_props = api_config.grid_properties # Keep a copy of the original values, so we can restore # them later orig_props = dict(all_props) # 1) A grid property is defined like "{0}%grid%dx". This is # changed to be just 'dx', i.e. the final component of # the current value (but we also take renaming into account, # so 'dx' might become 'dx_0'). # If a property is not used, it doesn't matter if we modify # its definition, so we just change all properties. for name, prop in all_props.items(): new_sig = Signature(prop.fortran.split("%")) if new_sig.is_structure: # Get the last field name, which will be the # local variable name local_name = new_sig[-1] unique_name = rename_variable.get(local_name, local_name) all_props[name] = GOceanConfig.make_property( unique_name, prop.type, prop.intrinsic_type) # 2) The property 'grid_data' is a reference to the data on the # grid (i.e. the actual field) , and it is defined as "{0}%data". # This just becomes {0} ('a_fld%data' in the original program # becomes just 'a_fld', and 'a_fld' is declared to be a plain # Fortran 2d-array) all_props["go_grid_data"] = GOceanConfig.make_property( "{0}", "array", "real") # Each kernel caches the argument code, so we also # need to clear this cached data to make sure the new # value for "go_grid_data" is actually used. for kernel in self.psy_data_body.walk(CodedKern): kernel.clear_cached_data() # Recreate the instrumented region. Due to the changes in the # config files, fields and properties will now become local # plain arrays and variables: for child in self.psy_data_body: child.gen_code(prog) # Now reset all properties back to the original values: for name in all_props.keys(): all_props[name] = orig_props[name] prog.add(CommentGen(prog, " RegionEnd")) prog.add(CommentGen(prog, "")) for var_name in output_list: prog.add(CommentGen(prog, " Check {0}".format(var_name))) code = str(module.root) with open("driver-{0}-{1}.f90".format(module_name, region_name), "w") as out: out.write(code)