def test_constructor(fortran_reader): '''Test the optional constructor parameter (single node and list of nodes).''' code = '''module test contains subroutine tmp() integer :: a,b,c a = b/c c = a*b end subroutine tmp end module test''' psyir = fortran_reader.psyir_from_source(code) schedule = psyir.children[0].children[0] node1 = schedule[0] node2 = schedule[1] vai1 = VariablesAccessInfo(node1) assert str(vai1) == "a: WRITE, b: READ, c: READ" vai2 = VariablesAccessInfo([node1, node2]) assert str(vai2) == "a: READ+WRITE, b: READ, c: READ+WRITE" with pytest.raises(InternalError) as err: VariablesAccessInfo([node1, node2, 3]) assert "One element in the node list is not a Node, but of type " in \ str(err.value) # The error message is slightly different between python 2 and 3 # so only test for the part that is the same in both: assert "'int'>" in str(err.value) with pytest.raises(InternalError) as err: VariablesAccessInfo(1) assert "Error in VariablesAccessInfo" in str(err.value) # The error message is slightly different between python 2 and 3 # so only test for the part that is the same in both: assert "'int'>" in str(err.value)
def test_lfric_stencil(): '''Check variable usage detection when OpenACC is used with a kernel that uses a stencil. ''' # Use the OpenACC transforms to create the required kernels acc_par_trans = ACCParallelTrans() acc_enter_trans = ACCEnterDataTrans() _, invoke = get_invoke("14.4_halo_vector.f90", "dynamo0.3", idx=0, dist_mem=False) sched = invoke.schedule _ = acc_par_trans.apply(sched.children) _ = acc_enter_trans.apply(sched) # Find the first kernel: kern = invoke.schedule.walk(CodedKern)[0] create_acc_arg_list = KernCallAccArgList(kern) var_accesses = VariablesAccessInfo() create_acc_arg_list.generate(var_accesses=var_accesses) var_info = str(var_accesses) assert "f1: READ+WRITE" in var_info assert "f2: READ" in var_info assert "f2_stencil_dofmap: READ" in var_info
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 gen_code(self, parent): # pylint: disable=arguments-differ ''' Generates the code required for read-only verification of one or more Nodes. It uses the PSyData API (via the base class PSyDataNode) to create the required callbacks that will allow a library to validate that read-only data is not modified. :param parent: the parent of this Node in the PSyIR. :type parent: :py:class:`psyclone.psyir.nodes.Node`. ''' # Determine the variables to validate: variables_info = VariablesAccessInfo(self) read_only = [] for var_name in variables_info: if variables_info[var_name].is_read_only(): read_only.append(var_name) # Add a callback here so that derived classes can adjust the list # of variables to provide, or the suffix used (which might # depend on the variable name which could create clashes). self.update_vars_and_postname() options = {'pre_var_list': read_only, 'post_var_list': read_only} parent.add(CommentGen(parent, "")) parent.add(CommentGen(parent, " ReadOnlyVerifyStart")) parent.add(CommentGen(parent, "")) super(ReadOnlyVerifyNode, self).gen_code(parent, options) parent.add(CommentGen(parent, "")) parent.add(CommentGen(parent, " ReadOnlyVerifyEnd")) parent.add(CommentGen(parent, ""))
def test_lfric_acc_operator(): '''Check variable usage detection when OpenACC is used with a kernel that uses an operator. ''' # Use the OpenACC transforms to enclose the kernels # with OpenACC directives. acc_par_trans = ACCParallelTrans() acc_enter_trans = ACCEnterDataTrans() _, invoke = get_invoke("20.0_cma_assembly.f90", "dynamo0.3", idx=0, dist_mem=False) sched = invoke.schedule _ = acc_par_trans.apply(sched.children) _ = acc_enter_trans.apply(sched) # Find the first kernel: kern = invoke.schedule.walk(CodedKern)[0] create_acc_arg_list = KernCallAccArgList(kern) var_accesses = VariablesAccessInfo() create_acc_arg_list.generate(var_accesses=var_accesses) var_info = str(var_accesses) assert "lma_op1_proxy%ncell_3d: READ" in var_info assert "lma_op1_proxy%local_stencil: WRITE" in var_info
def get_input_parameters(self, node_list, variables_info=None): # pylint: disable=no-self-use '''Return all variables that are input parameters, i.e. are read (before potentially being written). :param node_list: list of PSyIR nodes to be analysed. :type node_list: list of :py:class:`psyclone.psyir.nodes.Node` :param variables_info: optional variable usage information, \ can be used to avoid repeatedly collecting this information. :type variables_info: \ :py:class:`psyclone.core.variables_info.VariablesAccessInfo` :returns: a list of all variable signatures that are read. :rtype: list of :py:class:`psyclone.core.Signature` ''' # Collect the information about all variables used: if not variables_info: variables_info = VariablesAccessInfo(node_list) input_list = [] for signature in variables_info.all_signatures: # Take the first access (index 0) of this variable. Note that # loop variables have a WRITE before a READ access, so they # will be ignored first_access = variables_info[signature][0] # If the first access is a write, the variable is not an input # parameter and does not need to be saved. if first_access.access_type != AccessType.WRITE: input_list.append(signature) return input_list
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_lfric_acc(): '''Check variable usage detection when OpenACC is used. ''' # Use the OpenACC transforms to enclose the kernels # with OpenACC directives. acc_par_trans = ACCParallelTrans() acc_enter_trans = ACCEnterDataTrans() _, invoke = get_invoke("1_single_invoke.f90", "dynamo0.3", name="invoke_0_testkern_type", dist_mem=False) sched = invoke.schedule _ = acc_par_trans.apply(sched.children) _ = acc_enter_trans.apply(sched) # Find the first kernel: kern = invoke.schedule.walk(CodedKern)[0] create_acc_arg_list = KernCallAccArgList(kern) var_accesses = VariablesAccessInfo() create_acc_arg_list.generate(var_accesses=var_accesses) var_info = str(var_accesses) assert "f1: READ+WRITE" in var_info assert "f2: READ" in var_info assert "m1: READ" in var_info assert "m2: READ" in var_info assert "undf_w1: READ" in var_info assert "map_w1: READ" in var_info assert "undf_w2: READ" in var_info assert "map_w2: READ" in var_info assert "undf_w3: READ" in var_info assert "map_w3: READ" in var_info
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 test_lfric_stub_args(): '''Check that correct stub code is produced when there are multiple stencils. ''' ast = get_ast("dynamo0.3", "testkern_stencil_multi_mod.f90") metadata = DynKernMetadata(ast) kernel = DynKern() kernel.load_meta(metadata) var_accesses = VariablesAccessInfo() create_arg_list = KernStubArgList(kernel) create_arg_list.generate(var_accesses=var_accesses) var_info = str(var_accesses) assert "field_1_w1: READ+WRITE" in var_info assert "field_2_stencil_dofmap: READ" in var_info assert "field_2_stencil_size: READ" in var_info assert "field_2_w2: READ" in var_info assert "field_3_direction: READ" in var_info assert "field_3_stencil_dofmap: READ" in var_info assert "field_3_stencil_size: READ" in var_info assert "field_3_w2: READ" in var_info assert "field_4_stencil_dofmap: READ" in var_info assert "field_4_stencil_size: READ" in var_info assert "field_4_w3: READ" in var_info assert "map_w1: READ" in var_info assert "map_w2: READ" in var_info assert "map_w3: READ" in var_info assert "ndf_w1: READ" in var_info assert "ndf_w2: READ" in var_info assert "ndf_w3: READ" in var_info assert "nlayers: READ" in var_info assert "undf_w1: READ" in var_info assert "undf_w2: READ" in var_info assert "undf_w3: READ" in var_info
def test_lfric_stencils(): '''Test that stencil parameters are correctly detected. ''' _, invoke_info = get_invoke("14.4_halo_vector.f90", "dynamo0.3", idx=0) var_info = str(VariablesAccessInfo(invoke_info.schedule)) assert "f2_stencil_size: READ" in var_info assert "f2_stencil_dofmap: READ" in var_info
def test_lfric_cma2(): '''Test that parameters related to CMA operators are handled correctly in the variable usage analysis. ''' _, invoke_info = get_invoke("20.1_cma_apply.f90", "dynamo0.3", idx=0) var_info = str(VariablesAccessInfo(invoke_info.schedule)) assert "cma_indirection_map_aspc1_field_a: READ" in var_info assert "cma_indirection_map_aspc2_field_b: READ" in var_info
def test_lfric_operator(): '''Check if implicit basis and differential basis variables are handled correctly. ''' _, invoke_info = get_invoke("6.1_eval_invoke.f90", "dynamo0.3", idx=0) var_info = str(VariablesAccessInfo(invoke_info.schedule)) assert "basis_w0_on_w0: READ" in var_info assert "diff_basis_w1_on_w0: READ" in var_info
def test_lfric_stencil_xory_vector(): '''Test that the implicit parameters for a stencil access of type x or y with a vector field are created. ''' _, invoke_info = get_invoke("14.4.2_halo_vector_xory.f90", "dynamo0.3", idx=0) var_info = str(VariablesAccessInfo(invoke_info.schedule)) assert "f2_direction: READ" in var_info
def test_lfric_operator_bc_kernel(): '''Tests that a kernel that applies boundary conditions to operators detects the right implicit paramaters. ''' _, invoke_info = get_invoke("12.4_enforce_op_bc_kernel.f90", "dynamo0.3", idx=0) var_info = str(VariablesAccessInfo(invoke_info.schedule)) assert "boundary_dofs_op_a: READ" in var_info
def test_lfric_field_bc_kernel(): '''Tests that implicit parameters in case of a boundary_dofs array fix are created correctly. ''' _, invoke_info = get_invoke("12.2_enforce_bc_kernel.f90", "dynamo0.3", idx=0) var_info = str(VariablesAccessInfo(invoke_info.schedule)) assert "boundary_dofs_a: READ" in var_info
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 test_lfric_ref_element(): '''Test handling of variables if an LFRic's RefElement is used. ''' _, invoke_info = get_invoke("23.4_ref_elem_all_faces_invoke.f90", "dynamo0.3", idx=0) var_info = str(VariablesAccessInfo(invoke_info.schedule)) assert "normals_to_faces: READ" in var_info assert "out_normals_to_faces: READ" in var_info assert "nfaces_re: READ" in var_info
def test_lfric_stub_boundary_dofs(): '''Check variable usage detection for boundary dofs. ''' ast = get_ast("dynamo0.3", "enforce_bc_kernel_mod.f90") metadata = DynKernMetadata(ast) kernel = DynKern() kernel.load_meta(metadata) var_accesses = VariablesAccessInfo() create_arg_list = KernStubArgList(kernel) create_arg_list.generate(var_accesses=var_accesses) assert "boundary_dofs_field_1: READ" in str(var_accesses)
def get_output_parameters(self, node_list, variables_info=None): # pylint: disable=no-self-use '''Return all variables that are output parameters, i.e. are written. :param node_list: list of PSyIR nodes to be analysed. :type node_list: list of :py:class:`psyclone.psyir.nodes.Node` :param variables_info: optional variable usage information, \ can be used to avoid repeatedly collecting this information. :type variables_info: \ :py:class:`psyclone.core.variables_info.VariablesAccessInfo` :returns: a list of all variable names that are written. :rtype: list of str ''' # Collect the information about all variables used: if not variables_info: variables_info = VariablesAccessInfo(node_list) return [str(signature) for signature in variables_info.all_signatures if variables_info.is_written(signature)]
def test_lfric_stub_indirection_dofmap(): '''Check variable usage detection in indirection dofmap. ''' ast = get_ast("dynamo0.3", "columnwise_op_app_kernel_mod.F90") metadata = DynKernMetadata(ast) kernel = DynKern() kernel.load_meta(metadata) var_accesses = VariablesAccessInfo() create_arg_list = KernStubArgList(kernel) create_arg_list.generate(var_accesses=var_accesses) var_info = str(var_accesses) assert "cma_indirection_map_aspc1_field_1: READ" in var_info assert "cma_indirection_map_aspc2_field_2: READ" in var_info
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 test_lfric_stub_banded_dofmap(): '''Check variable usage detection for banded dofmaps. ''' ast = get_ast("dynamo0.3", "columnwise_op_asm_kernel_mod.F90") metadata = DynKernMetadata(ast) kernel = DynKern() kernel.load_meta(metadata) var_accesses = VariablesAccessInfo() create_arg_list = KernStubArgList(kernel) create_arg_list.generate(var_accesses=var_accesses) var_info = str(var_accesses) assert "cbanded_map_adspc1_op_1: READ" in var_info assert "cbanded_map_adspc2_op_1: READ" in var_info
def test_call(parser): ''' Check that we correctly handle a call in a program ''' reader = FortranStringReader('''program test_prog real :: a, b call sub(a,b) end program test_prog''') ast = parser(reader) psy = PSyFactory(API).create(ast) schedule = psy.invokes.get("test_prog").schedule code_block = schedule.children[0] call_stmt = code_block.statements[0] var_accesses = VariablesAccessInfo(call_stmt) assert str(var_accesses) == "a: UNKNOWN, b: UNKNOWN"
def test_goloop(): ''' Check the handling of non-NEMO do loops. TODO #440: Does not work atm, GOLoops also have start/stop as strings, which are even not defined. Only after gen_code() is called will they be defined. ''' _, invoke = get_invoke("single_invoke_two_kernels_scalars.f90", "gocean1.0", name="invoke_0") do_loop = invoke.schedule.children[0] assert isinstance(do_loop, Loop) var_accesses = VariablesAccessInfo(do_loop) assert str(var_accesses) == ": READ, a_scalar: READ, i: READ+WRITE, "\ "j: READ+WRITE, " "ssh_fld: READ+WRITE, "\ "tmask: READ"
def test_lfric_various_basis(): ''' Tests that implicit parameters for various basis related functionality work as expected. ''' _, invoke_info = get_invoke("10.3_operator_different_spaces.f90", "dynamo0.3", idx=0) var_info = str(VariablesAccessInfo(invoke_info.schedule)) assert "basis_w3_qr: READ" in var_info assert "diff_basis_w0_qr: READ" in var_info assert "diff_basis_w2_qr: READ" in var_info assert "np_xy_qr: READ" in var_info assert "np_z_qr: READ" in var_info assert "weights_xy_qr: READ" in var_info assert "weights_z_qr: READ" in var_info
def test_indirect_addressing(parser): ''' Check that we correctly handle indirect addressing, especially on the LHS. ''' reader = FortranStringReader('''program test_prog integer :: i, h(10) real :: a, g(10) g(h(i)) = a end program test_prog''') ast = parser(reader) psy = PSyFactory(API).create(ast) schedule = psy.invokes.get("test_prog").schedule indirect_addressing = schedule[0] assert isinstance(indirect_addressing, Assignment) var_accesses = VariablesAccessInfo(indirect_addressing) assert str(var_accesses) == "a: READ, g: WRITE, h: READ, i: READ"
def get_in_out_parameters(self, node_list): '''Return a 2-tuple of lists that contains all variables that are input parameters (first entry) and output parameters (second entry). This function calls get_input_parameter and get_output_parameter, but avoids the repeated computation of the variable usage. :param node_list: list of PSyIR nodes to be analysed. :type node_list: list of :py:class:`psyclone.psyir.nodes.Node` :returns: a 2-tuple of two lists, the first one containing \ the input parameters, the second the output paramters. :rtype: 2-tuple of list of :py:class:`psyclone.core.Signature` ''' variables_info = VariablesAccessInfo(node_list) return (self.get_input_parameters(node_list, variables_info), self.get_output_parameters(node_list, variables_info))
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_lfric_stub_args3(): '''Check variable usage detection for cell position, operator ''' ast = get_ast("dynamo0.3", "testkern_any_discontinuous_space_op_1_mod.f90") metadata = DynKernMetadata(ast) kernel = DynKern() kernel.load_meta(metadata) var_accesses = VariablesAccessInfo() create_arg_list = KernStubArgList(kernel) create_arg_list.generate(var_accesses=var_accesses) var_info = str(var_accesses) assert "cell: READ" in var_info assert "op_3: READ" in var_info assert "op_3_ncell_3d: READ" in var_info assert "op_4: READ" in var_info assert "op_4_ncell_3d: READ" in var_info