def test_constructor(): '''Test the optional constructor parameter (single node and list of nodes).''' from psyclone.tests.utilities import create_schedule code = '''module test contains subroutine tmp() integer :: a,b,c a = b/c c = a*b end subroutine tmp end module test''' schedule = create_schedule(code, "tmp") 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_reference_accesses_bounds(operator_type): '''Test that the reference_accesses method behaves as expected when the reference is the first argument to either the lbound or ubound intrinsic as that is simply looking up the array bounds (therefore var_access_info should be empty) and when the reference is the second argument of either the lbound or ubound intrinsic (in which case the access should be a read). ''' # Note, one would usually expect UBOUND to provide the upper bound # of a range but to simplify the test both LBOUND and UBOUND are # used for the lower bound. This does not affect the test. one = Literal("1", INTEGER_TYPE) array_symbol = DataSymbol("test", ArrayType(REAL_TYPE, [10])) array_ref1 = Reference(array_symbol) array_ref2 = Reference(array_symbol) array_access = Array.create(array_symbol, [one]) # test when first or second argument to LBOUND or UBOUND is an # array reference operator = BinaryOperation.create(operator_type, array_ref1, array_ref2) array_access.children[0] = Range.create(operator, one, one) var_access_info = VariablesAccessInfo() array_ref1.reference_accesses(var_access_info) assert str(var_access_info) == "" var_access_info = VariablesAccessInfo() array_ref2.reference_accesses(var_access_info) assert str(var_access_info) == "test: READ"
def test_lfric_stub_cma_operators(): '''Check variable usage detection cma operators. mesh_ncell2d, cma_operator ''' from psyclone.dynamo0p3 import DynKernMetadata, DynKern from psyclone.domain.lfric import KernStubArgList ast = get_ast("dynamo0.3", "columnwise_op_mul_2scalars_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) for num in ["1", "3", "5"]: assert "ncell_2d: READ" in var_info assert "cma_op_"+num+": READ" in var_info assert "cma_op_"+num+"_nrow: READ" in var_info assert "cma_op_"+num+"_ncol: READ" in var_info assert "cma_op_"+num+"_bandwidth: READ" in var_info assert "cma_op_"+num+"_alpha: READ" in var_info assert "cma_op_"+num+"_beta: READ" in var_info assert "cma_op_"+num+"_gamma_m: READ" in var_info assert "cma_op_"+num+"_gamma_p: READ" in var_info
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["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 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: from psyclone.core.access_info import VariablesAccessInfo 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} from psyclone.f2pygen import CommentGen 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(): '''Check variable usage detection when OpenACC is used. ''' # Use the OpenACC transforms to enclose the kernels # with OpenACC directives. from psyclone.transformations import ACCParallelTrans, ACCEnterDataTrans from psyclone.psyGen import CodedKern 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_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. from psyclone.transformations import ACCParallelTrans, ACCEnterDataTrans from psyclone.psyGen import CodedKern 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 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 from psyclone.transformations import ACCParallelTrans, ACCEnterDataTrans from psyclone.psyGen import CodedKern 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 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 names that are read. :rtype: list of str ''' # Collect the information about all variables used: if not variables_info: variables_info = VariablesAccessInfo(node_list) input_list = [] for var_name in variables_info.all_vars: # 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[var_name][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(var_name) return input_list
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_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["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].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["p_fld"] all_indices = [access.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 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_reference_accesses(): '''Test that the reference_accesses method behaves as expected in the usual case (see the next test for the unusual case). ''' reference = Reference(DataSymbol("test", REAL_TYPE)) var_access_info = VariablesAccessInfo() reference.reference_accesses(var_access_info) assert (str(var_access_info)) == "test: READ"
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_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_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_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_reference_accesses(): ''' Test that the reference_accesses method does nothing. This will be addressed by #1028. ''' var_access_info = VariablesAccessInfo() dref = nodes.StructureReference.create( symbols.DataSymbol( "grid", symbols.TypeSymbol("grid_type", symbols.DeferredType())), ["data"]) dref.reference_accesses(var_access_info) assert var_access_info.all_signatures == []
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 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_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 reference_accesses(self, var_accesses): '''Get all variable access information from this node. The assigned-to variable will be set to 'WRITE'. :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 that a new instance is used to handle the LHS, # since a check in 'change_read_to_write' makes sure that there # is only one access to the variable! accesses_left = VariablesAccessInfo() self.lhs.reference_accesses(accesses_left) # Now change the (one) access to the assigned variable to be WRITE: if isinstance(self.lhs, CodeBlock): # TODO #363: Assignment to user defined type, not supported yet. # Here an absolute hack to get at least some information out # from the AST - though indices are just strings, which will # likely cause problems later as well. name = str(self.lhs.ast) # A regular expression that tries to find the last parenthesis # pair in the name ("a(i,j)" --> "(i,j)") ind = re.search(r"\([^\(]+\)$", name) if ind: # Remove the index part of the name name = name.replace(ind.group(0), "") # The index must be added as a list accesses_left.add_access(name, AccessType.WRITE, self, [ind.group(0)]) else: accesses_left.add_access(name, AccessType.WRITE, self) else: var_info = accesses_left[self.lhs.name] try: var_info.change_read_to_write() except InternalError: # An internal error typically indicates that the same variable # is used twice on the LHS, e.g.: g(g(1)) = ... This is not # supported in PSyclone. from psyclone.parse.utils import ParseError raise ParseError( "The variable '{0}' appears more than once " "on the left-hand side of an assignment.".format( self.lhs.name)) # Merge the data (that shows now WRITE for the variable) with the # parameter to this function. It is important that first the # RHS is added, so that in statements like 'a=a+1' the read on # the RHS comes before the write on the LHS (they have the same # location otherwise, but the order is still important) self.rhs.reference_accesses(var_accesses) var_accesses.merge(accesses_left) var_accesses.next_location()
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 [ var_name for var_name in variables_info.all_vars if variables_info.is_written(var_name) ]
def test_indirect_addressing(parser): ''' Check that we correctly handle indirect addressing, especially on the LHS. ''' reader = FortranStringReader('''program test_prog 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.reference_accesses(var_accesses) assert str(var_accesses) == "a: READ, g: WRITE, h: READ, i: READ"
def test_lfric_stub_boundary_dofmap(): '''Check variable usage detection in boundary_dofs array fix for operators. ''' from psyclone.dynamo0p3 import DynKernMetadata, DynKern from psyclone.domain.lfric import KernStubArgList ast = get_ast("dynamo0.3", "enforce_operator_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_op_1: READ" in str(var_accesses)
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 genCode 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 "orientation_w2: READ" in var_info 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