Esempio n. 1
0
    def match(node):
        '''
        Whether or not the AST fragment pointed to by node represents a
        kernel. A kernel is defined as a section of code that sits
        within a recognised loop structure and does not itself contain
        loops or IO operations.

        :param node: Node in fparser2 AST to check.
        :type node: :py:class:`fparser.two.Fortran2003.Base`
        :returns: True if this node conforms to the rules for a kernel.
        :rtype: bool
        '''
        from fparser.two.Fortran2003 import Subscript_Triplet,  \
            Block_Nonlabel_Do_Construct, Write_Stmt, Read_Stmt
        child_loops = walk_ast(
            node.content, [Block_Nonlabel_Do_Construct, Write_Stmt, Read_Stmt])
        if child_loops:
            # A kernel cannot contain other loops or reads or writes
            return False

        # Currently a kernel cannot contain implicit loops.
        # TODO we may have to differentiate between implicit loops over
        # grid points and any other implicit loop. Possibly using the
        # scope of the array being accessed?
        impl_loops = walk_ast(node.content, [Subscript_Triplet])
        if impl_loops:
            return False

        return True
Esempio n. 2
0
def test_subroutine_comments():
    ''' Tests for comments in subroutines '''
    source = '''\
subroutine my_mod()
  ! First comment
  implicit none
  integer my_mod
  ! Body comment
  my_mod = 1 ! Inline comment
  ! Ending comment
end subroutine my_mod
'''
    from fparser.two.Fortran2003 import Subroutine_Subprogram
    reader = get_reader(source, isfree=True, ignore_comments=False)
    fn_unit = Subroutine_Subprogram(reader)
    assert isinstance(fn_unit, Subroutine_Subprogram)
    walk_ast(fn_unit.content, [Comment], debug=True)
    spec_part = fn_unit.content[1]
    comment = spec_part.content[0].content[0]
    assert isinstance(comment, Comment)
    assert "! First comment" in str(comment)
    comment = spec_part.content[2].content[0]
    assert isinstance(comment, Comment)
    assert "! Body comment" in str(comment)
    exec_part = fn_unit.content[2]
    comment = exec_part.content[1]
    assert isinstance(comment, Comment)
    assert "! Inline comment" in str(comment)
Esempio n. 3
0
    def match(node):
        '''
        Checks whether the supplied node in the fparser2 AST represents
        an implicit loop (using Fortran array syntax).

        :param node: node in the fparser2 AST to check
        :type node: :py:class:`fparser.two.Fortran2003.Assignment_Stmt`
        :returns: True if the node does represent an implicit loop.
        :rtype: bool

        '''
        if not isinstance(node, Fortran2003.Assignment_Stmt):
            return False
        # We are expecting something like:
        #    array(:,:,jk) = some_expression
        # but we have to beware of cases like the following:
        #   array(1,:,:) = a_func(array2(:,:,:), mask(:,:))
        # where a_func is an array-valued function and `array2(:,:,:)`
        # could just be `array2`.
        # We check the left-hand side...
        lhs = node.items[0]
        if not isinstance(lhs, Fortran2003.Part_Ref):
            # LHS is not an array reference
            return False
        colons = walk_ast(lhs.items, [Fortran2003.Subscript_Triplet])
        if not colons:
            # LHS does not use array syntax
            return False
        # Now check the right-hand side...
        rhs = node.items[2]
        colons = walk_ast(rhs.items, [Fortran2003.Subscript_Triplet])
        if not colons:
            # We don't have any array syntax on the RHS
            return True
        # Check that we haven't got array syntax used within the index
        # expression to another array. Array references are represented by
        # Part_Ref nodes in the fparser2 AST. This would be easier to do
        # if the fparser2 AST carried parent information with each node.
        # As it is we have to walk down the tree rather than come back up
        # from each colon.
        # Find all array references
        array_refs = []
        if isinstance(rhs, Fortran2003.Part_Ref):
            # Since walk_ast is slightly clunky we have to manually allow
            # for the top-level "rhs" node being an array reference
            array_refs.append(rhs)
        array_refs += walk_ast(rhs.items, [Fortran2003.Part_Ref])
        for ref in array_refs:
            nested_refs = walk_ast(ref.items, [Fortran2003.Part_Ref])
            # Do any of these nested array references use array syntax?
            for nested_ref in nested_refs:
                colons = walk_ast(nested_ref.items,
                                  [Fortran2003.Subscript_Triplet])
                if colons:
                    return False
        return True
def test_new_kernel_file(tmpdir, monkeypatch):
    ''' Check that we write out the transformed kernel to the CWD. '''
    from fparser.two import Fortran2003, parser
    from fparser.common.readfortran import FortranFileReader
    # Ensure kernel-output directory is uninitialised
    config = Config.get()
    monkeypatch.setattr(config, "_kernel_output_dir", "")
    monkeypatch.setattr(config, "_kernel_naming", "multiple")
    # Change to temp dir (so kernel written there)
    old_cwd = tmpdir.chdir()
    psy, invoke = get_invoke("nemolite2d_alg_mod.f90", api="gocean1.0", idx=0)
    sched = invoke.schedule
    kern = sched.children[0].loop_body[0].loop_body[0]
    rtrans = ACCRoutineTrans()
    _, _ = rtrans.apply(kern)
    # Generate the code (this triggers the generation of a new kernel)
    code = str(psy.gen).lower()
    # Work out the value of the tag used to re-name the kernel
    tag = re.search('use continuity(.+?)_mod', code).group(1)
    assert ("use continuity{0}_mod, only: continuity{0}_code".format(tag)
            in code)
    assert "call continuity{0}_code(".format(tag) in code
    # The kernel and module name should have gained the tag just identified
    # and be written to the CWD
    filename = os.path.join(str(tmpdir), "continuity{0}_mod.f90".format(tag))
    assert os.path.isfile(filename)
    # Parse the new kernel file
    f2003_parser = parser.ParserFactory().create()
    reader = FortranFileReader(filename)
    prog = f2003_parser(reader)
    # Check that the module has the right name
    modules = walk_ast(prog.content, [Fortran2003.Module_Stmt])
    assert str(modules[0].items[1]) == "continuity{0}_mod".format(tag)
    # Check that the subroutine has the right name
    subs = walk_ast(prog.content, [Fortran2003.Subroutine_Stmt])
    found = False
    for sub in subs:
        if str(sub.items[1]) == "continuity{0}_code".format(tag):
            found = True
            break
    assert found
    # Check that the kernel type has been re-named
    dtypes = walk_ast(prog.content, [Fortran2003.Derived_Type_Def])
    names = walk_ast(dtypes[0].content, [Fortran2003.Type_Name])
    assert str(names[0]) == "continuity{0}_type".format(tag)

    from gocean1p0_build import GOcean1p0Build
    # If compilation fails this will raise an exception
    GOcean1p0Build(tmpdir).compile_file(filename)

    old_cwd.chdir()
Esempio n. 5
0
def test_prog_comments():
    ''' Unit tests for lines in programs containing comments '''
    cls = Program
    reader = get_reader('''\
   ! A troublesome comment
   program foo
     ! A full comment line
     write(*,*) my_int ! An in-line comment
    end program foo
! A really problematic comment
''',
                        isfree=True,
                        ignore_comments=False)

    obj = cls(reader)
    assert type(obj) == Program
    # Check that the AST has the expected structure:
    # Program
    #   |--> Comment
    #   |--> Main_Program
    #   .    |--> Program_Stmt
    #   .    |--> Specification_Part
    #   .    .    \--> Implicit_Part
    #   .    .         \--> Comment
    #        |--> Execution_Part
    #        |    |--> Write_Stmt
    #        |    \--> Comment
    #   .    .
    #   .
    #   |--> Comment
    from fparser.two.Fortran2003 import Main_Program, Write_Stmt, \
        End_Program_Stmt
    walk_ast(obj.content, [Comment], debug=True)
    assert type(obj.content[0]) == Comment
    assert str(obj.content[0]) == "! A troublesome comment"
    assert type(obj.content[1]) == Main_Program
    main_prog = obj.content[1]
    assert type(main_prog.content[1].content[0].content[0]) == Comment
    assert (str(
        main_prog.content[1].content[0].content[0]) == "! A full comment line")
    exec_part = main_prog.content[2]
    assert type(exec_part.content[0]) == Write_Stmt
    # Check that we have the in-line comment as a second statement
    assert len(exec_part.content) == 2
    assert type(exec_part.content[1]) == Comment
    assert type(main_prog.content[3]) == End_Program_Stmt
    assert "! An in-line comment" in str(obj)
    # Check that we still have the ending comment
    assert type(obj.content[-1]) == Comment
    assert str(obj).endswith("! A really problematic comment")
Esempio n. 6
0
def valid_kernel(node):
    '''
    Whether the sub-tree that has `node` at its root is eligible to be
    enclosed within an OpenACC KERNELS directive.

    :param node: the node in the PSyIR to check.
    :type node: :py:class:`psyclone.psyGen.Node`

    :returns: True if the sub-tree can be enclosed in a KERNELS region.
    :rtype: bool

    '''
    from psyclone.psyGen import CodeBlock, IfBlock
    from fparser.two.utils import walk_ast
    from fparser.two import Fortran2003
    # PGI (18.10) often produces code that fails at run time if a Kernels
    # region includes If constructs.
    excluded_node_types = (CodeBlock, IfBlock)
    if node.walk(excluded_node_types):
        return False
    # Check that there are no derived-type references in the sub-tree (because
    # PGI deep-copy doesn't like them).
    # TODO #365 - this check should be part of our identification of valid
    # NEMO kernels.
    if walk_ast([node.ast], [Fortran2003.Data_Ref]):
        return False
    return True
def test_accroutine():
    ''' Test that we can transform a kernel by adding a "!$acc routine"
    directive to it. '''
    from psyclone.gocean1p0 import GOKern
    from fparser.two import Fortran2003
    _, invoke = get_invoke("nemolite2d_alg_mod.f90", api="gocean1.0", idx=0)
    sched = invoke.schedule
    kern = sched.children[0].loop_body[0].loop_body[0]
    assert isinstance(kern, GOKern)
    rtrans = ACCRoutineTrans()
    assert rtrans.name == "ACCRoutineTrans"
    new_kern, _ = rtrans.apply(kern)
    # The transformation should have populated the fparser2 AST of
    # the kernel...
    assert new_kern._fp2_ast
    assert isinstance(new_kern._fp2_ast, Fortran2003.Program)
    # Check AST contains directive
    comments = walk_ast(new_kern._fp2_ast.content, [Fortran2003.Comment])
    assert len(comments) == 1
    assert str(comments[0]) == "!$acc routine"
    # Check that directive is in correct place (end of declarations)
    gen = str(new_kern._fp2_ast)
    assert ("REAL(KIND = go_wp), DIMENSION(:, :), INTENT(IN) :: sshn, sshn_u, "
            "sshn_v, hu, hv, un, vn\n"
            "    !$acc routine\n"
            "    ssha (ji, jj) = 0.0_go_wp\n" in gen)
Esempio n. 8
0
    def __init__(self, ast):
        # pylint: disable=super-init-not-called
        from fparser.two.Fortran2003 import Main_Program,  \
            Subroutine_Subprogram, Function_Subprogram, Function_Stmt, Name

        self.invoke_map = {}
        self.invoke_list = []
        # Keep a pointer to the whole fparser2 AST
        self._ast = ast

        # Find all the subroutines contained in the file
        routines = walk_ast(ast.content,
                            [Subroutine_Subprogram, Function_Subprogram])
        # Add the main program as a routine to analyse - take care
        # here as the Fortran source file might not contain a
        # main program (might just be a subroutine in a module)
        main_prog = get_child(ast, Main_Program)
        if main_prog:
            routines.append(main_prog)

        # Analyse each routine we've found
        for subroutine in routines:
            # Get the name of this subroutine, program or function
            substmt = subroutine.content[0]
            if isinstance(substmt, Function_Stmt):
                for item in substmt.items:
                    if isinstance(item, Name):
                        sub_name = str(item)
                        break
            else:
                sub_name = str(substmt.get_name())

            my_invoke = NemoInvoke(subroutine, name=sub_name)
            self.invoke_map[sub_name] = my_invoke
            self.invoke_list.append(my_invoke)
Esempio n. 9
0
    def get_integer_array(self, name):
        ''' Parse the kernel meta-data and find the values of the
        integer array variable with the supplied name. Returns an empty list
        if no matching variable is found.

        :param str name: the name of the integer array to find.
        :returns: list of values.
        :rtype: list of str.

        :raises InternalError: if we fail to parse the LHS of the array \
                               declaration or the array constructor.
        :raises ParseError: if the RHS of the declaration is not an array \
                            constructor.

        '''
        # Ensure the classes are setup for the Fortran2003 parser
        _ = ParserFactory().create()

        for statement, _ in fpapi.walk(self._ktype, -1):
            if not isinstance(statement, fparser1.typedecl_statements.Integer):
                # This isn't an integer declaration so skip it
                continue
            # fparser only goes down to the statement level. We use fparser2 to
            # parse the statement itself.
            assign = Fortran2003.Assignment_Stmt(statement.entity_decls[0])
            names = walk_ast(assign.items, [Fortran2003.Name])
            if not names:
                raise InternalError(
                    "Unsupported assignment statement: '{0}'".format(
                        str(assign)))
            if str(names[0]) == name:
                # This is the variable declaration we're looking for
                if not isinstance(assign.items[2],
                                  Fortran2003.Array_Constructor):
                    raise ParseError(
                        "get_integer_array: RHS of assignment is not "
                        "an array constructor: '{0}'".format(str(assign)))
                # fparser2 AST for Array_Constructor is:
                # Array_Constructor('[', Ac_Value_List(',', (Name('w0'),
                #                                      Name('w1'))), ']')
                # Construct a list of the names in the array constructor
                names = walk_ast(assign.items[2].items, [Fortran2003.Name])
                if not names:
                    raise InternalError("Failed to parse array constructor: "
                                        "'{0}'".format(str(assign.items[2])))
                return [str(name) for name in names]
        return []
Esempio n. 10
0
    def __init__(self, ast):
        names = walk_ast(ast.content, [Fortran2003.Name])
        # The name of the program unit will be the first in the list
        if not names:
            raise InternalError("Found no names in supplied Fortran - should "
                                "be impossible!")
        self._name = str(names[0]) + "_psy"

        self._invokes = NemoInvokes(ast)
        self._ast = ast
Esempio n. 11
0
def test_identify_implicit_loop(parser):
    ''' Check that we correctly identify implicit loops in the fparser2 AST '''
    reader = FortranStringReader("program test_prog\n"
                                 "umask(:, :, :, :) = 0.0D0\n"
                                 "do jk = 1, jpk\n"
                                 "  umask(1,1,jk) = -1.0d0\n"
                                 "end do\n"
                                 "end program test_prog\n")
    ast = parser(reader)
    assert not nemo.NemoImplicitLoop.match(ast)
    stmts = walk_ast(ast.content, [Fortran2003.Assignment_Stmt])
    assert not nemo.NemoImplicitLoop.match(stmts[1])
    assert nemo.NemoImplicitLoop.match(stmts[0])
Esempio n. 12
0
    def gen(self):
        '''Return modified algorithm code.

        :returns: The modified algorithm specification as an fparser2 \
        parse tree.
        :rtype: :py:class:`fparser.two.utils.Base`

        '''

        from fparser.two.utils import walk_ast
        # pylint: disable=no-name-in-module
        from fparser.two.Fortran2003 import Call_Stmt, Section_Subscript_List

        idx = 0
        # Walk through all statements looking for procedure calls
        for statement in walk_ast(self._ast.content, [Call_Stmt]):
            # found a Fortran call statement
            call_name = str(statement.items[0])
            if call_name.lower() == self._invoke_name.lower():
                # The call statement is an invoke

                # Get the PSy callee name and argument list and
                # replace the existing algorithm invoke call with
                # these.
                psy_invoke_info = self._psy.invokes.invoke_list[idx]
                new_name = psy_invoke_info.name
                new_args = Section_Subscript_List(", ".join(
                    psy_invoke_info.alg_unique_args))
                statement.items = (new_name, new_args)

                # The PSy-layer generates a subroutine within a module
                # so we need to add a 'use module_name, only :
                # subroutine_name' to the algorithm layer.
                adduse(self._ast,
                       statement,
                       self._psy.name,
                       only=True,
                       funcnames=[psy_invoke_info.name])
                idx += 1

        if idx == 0:
            raise NoInvokesError(
                "Algorithm file contains no invoke() calls: refusing to "
                "generate empty PSy code")

        return self._ast
Esempio n. 13
0
    def __init__(self, ast, parent=None):
        from fparser.two.Fortran2003 import Loop_Control
        Loop.__init__(self, parent=parent, valid_loop_types=VALID_LOOP_TYPES)
        NemoFparser2ASTProcessor.__init__(self)
        # Keep a ptr to the corresponding node in the AST
        self._ast = ast

        # Get the loop variable
        ctrl = walk_ast(ast.content, [Loop_Control])
        # Second element of items member of Loop Control is itself a tuple
        # containing:
        #   Loop variable, [start value expression, end value expression, step
        #   expression]
        # Loop variable will be an instance of Fortran2003.Name
        loop_var = str(ctrl[0].items[1][0])
        self._variable_name = str(loop_var)

        # Identify the type of loop
        if self._variable_name in NEMO_LOOP_TYPE_MAPPING:
            self.loop_type = NEMO_LOOP_TYPE_MAPPING[self._variable_name]
        else:
            self.loop_type = "unknown"

        # Get the loop limits. These are given in a list which is the second
        # element of a tuple which is itself the second element of the items
        # tuple:
        # (None, (Name('jk'), [Int_Literal_Constant('1', None), Name('jpk'),
        #                      Int_Literal_Constant('1', None)]), None)
        limits_list = ctrl[0].items[1][1]
        self._start = str(limits_list[0])
        self._stop = str(limits_list[1])
        if len(limits_list) == 3:
            self._step = str(limits_list[2])
        else:
            # Default loop increment is 1
            self._step = "1"

        # Is this loop body a kernel?
        if NemoKern.match(self._ast):
            self.addchild(NemoKern(self._ast, parent=self))
            return
        # It's not - walk on down the AST...
        self.process_nodes(self, self._ast.content, self._ast)
Esempio n. 14
0
    def perform(self, targs):

        loops = {}

        for path, tree in self.env['astlist'].items():
            l = []
            loops[path] = l
            for node in walk_ast(tree.content):

                for x in getattr(node, "content", []):
                    if isinstance(x, Base):
                        x.parent = node
                for x in getattr(node, "items", []):
                    if isinstance(x, Base):
                        x.parent = node

                if isinstance(node, (Label_Do_Stmt, Nonlabel_Do_Stmt)):
                    l.append(node)

        return 0, {"astlist": self.env['astlist'], "loops": loops}
Esempio n. 15
0
def test_get_child(f2003_create):
    ''' Test the get_child() utility. '''
    from fparser.two import Fortran2003
    from fparser.two.utils import get_child, walk_ast
    reader = get_reader("program hello\n"
                        "write(*,*) 'hello'\n"
                        "write(*,*) 'goodbye'\n"
                        "end program hello\n")
    main = Fortran2003.Program(reader)
    prog = get_child(main, Fortran2003.Main_Program)
    exe = get_child(prog, Fortran2003.Execution_Part)
    assert isinstance(exe, Fortran2003.Execution_Part)
    write_stmt = get_child(exe, Fortran2003.Write_Stmt)
    # Check that we got the first write and not the second
    assert "goodbye" not in str(write_stmt)
    # The top level has no Io_Control_Spec children
    assert not get_child(main, Fortran2003.Io_Control_Spec)
    # Check functionality when node has children in `items` and
    # not in `content`
    io_nodes = walk_ast(main.content, my_types=[Fortran2003.Io_Control_Spec])
    assert not hasattr(io_nodes[0], "content")
    io_unit = get_child(io_nodes[0], Fortran2003.Io_Unit)
    assert isinstance(io_unit, Fortran2003.Io_Unit)
Esempio n. 16
0
def test_exp_loop_missing_spec(parser):
    '''Test that the ExplicitLoop transformation still works when the
    fparser2 AST is missing a Specification_Part for the routine.

    '''
    from fparser.two.utils import walk_ast
    reader = FortranStringReader("program atest\nreal :: umask(1,1,1,1)\n"
                                 "umask(:, :, :) = 0.0\nend program atest\n")
    prog = parser(reader)
    psy = PSyFactory(API).create(prog)
    sched = psy.invokes.invoke_list[0].schedule
    # Remove the specification part
    spec = walk_ast(prog.content, [Fortran2003.Specification_Part])
    prog.content[0].content.remove(spec[0])
    # Check that we can transform OK
    exp_trans = TransInfo().get_trans_name('NemoExplicitLoopTrans')
    _, _ = exp_trans.apply(sched.children[0])
    gen_code = str(psy.gen)
    assert ("PROGRAM atest\n"
            "  INTEGER :: jk\n"
            "  DO jk = 1, jpk, 1\n"
            "    umask(:, :, jk) = 0.0\n"
            "  END DO\n"
            "END PROGRAM atest" in gen_code)
Esempio n. 17
0
    def match(node):
        '''
        Checks whether the supplied fparser2 AST represents an if-block
        that must be represented in the Schedule AST. If-blocks that do
        not contain kernels are just treated as code blocks.

        :param node: the node in the fparser2 AST representing an if-block
        :type node: :py:class:`fparser.two.Fortran2003.If_Construct`
        :returns: True if this if-block must be represented in the PSyIRe
        :rtype: bool

        '''
        if not isinstance(node, Fortran2003.If_Construct):
            return False

        # We only care about if-blocks if they contain something significant
        # i.e. a recognised type of loop (whether implicit or explicit).
        loops = walk_ast(node.content, [
            Fortran2003.Subscript_Triplet,
            Fortran2003.Block_Nonlabel_Do_Construct
        ])
        if loops:
            return True
        return False
Esempio n. 18
0
    def update(self):
        '''
        Update the underlying fparser2 parse tree to implement the profiling
        region represented by this Node. This involves adding the necessary
        module use statement as well as the calls to the profiling API.

        TODO #435 - remove this whole method once the NEMO API uses the
        Fortran backend of the PSyIR.

        :raises NotImplementedError: if the routine which is to have \
                             profiling added to it does not already have a \
                             Specification Part (i.e. some declarations).
        :raises NotImplementedError: if there would be a name clash with \
                             existing variable/module names in the code to \
                             be transformed.
        :raises InternalError: if we fail to find the node in the parse tree \
                             corresponding to the end of the profiling region.

        '''
        from fparser.common.sourceinfo import FortranFormat
        from fparser.common.readfortran import FortranStringReader
        from fparser.two.utils import walk_ast
        from fparser.two import Fortran2003
        from psyclone.psyGen import object_index, Schedule, InternalError

        # Ensure child nodes are up-to-date
        super(ProfileNode, self).update()

        # Get the parse tree of the routine containing this region
        ptree = self.root.invoke._ast
        # Rather than repeatedly walk the tree, we do it once for all of
        # the node types we will be interested in...
        node_list = walk_ast([ptree], [
            Fortran2003.Main_Program, Fortran2003.Subroutine_Stmt,
            Fortran2003.Function_Stmt, Fortran2003.Specification_Part,
            Fortran2003.Use_Stmt, Fortran2003.Name
        ])
        for node in node_list:
            if isinstance(
                    node,
                (Fortran2003.Main_Program, Fortran2003.Subroutine_Stmt,
                 Fortran2003.Function_Stmt)):
                names = walk_ast([node], [Fortran2003.Name])
                routine_name = str(names[0]).lower()
                break

        for node in node_list:
            if isinstance(node, Fortran2003.Specification_Part):
                spec_part = node
                break
        else:
            # This limitation will be removed when we use the Fortran
            # backend of the PSyIR (#435)
            raise NotImplementedError(
                "Addition of profiling regions to routines without any "
                "existing declarations is not supported and '{0}' has no "
                "Specification-Part".format(routine_name))

        # Get the existing use statements
        found = False
        for node in node_list[:]:
            if isinstance(node, Fortran2003.Use_Stmt) and \
               self.fortran_module == str(node.items[2]).lower():
                # Check that the use statement matches the one we would
                # insert (i.e. the code doesn't already contain a module
                # with the same name as that used by the profiling API)
                if str(node).lower() != self.use_stmt.lower():
                    raise NotImplementedError(
                        "Cannot add profiling to '{0}' because it already "
                        "'uses' a module named '{1}'".format(
                            routine_name, self.fortran_module))
                found = True
                # To make our check on name clashes below easier, remove
                # the Name nodes associated with this use from our
                # list of nodes.
                names = walk_ast([node], [Fortran2003.Name])
                for name in names:
                    node_list.remove(name)

        if not found:
            # We don't already have a use for the profiling module so
            # add one.
            reader = FortranStringReader(
                "use profile_mod, only: ProfileData, ProfileStart, ProfileEnd")
            # Tell the reader that the source is free format
            reader.set_format(FortranFormat(True, False))
            use = Fortran2003.Use_Stmt(reader)
            spec_part.content.insert(0, use)

        # Check that we won't have any name-clashes when we insert the
        # symbols required for profiling. This check uses the list of symbols
        # that we created before adding the `use profile_mod...` statement.
        if not self.root.profiling_name_clashes_checked:
            for node in node_list:
                if isinstance(node, Fortran2003.Name):
                    text = str(node).lower()
                    # Check for the symbols we import from the profiling module
                    for symbol in self.profiling_symbols:
                        if text == symbol.lower():
                            raise NotImplementedError(
                                "Cannot add profiling to '{0}' because it "
                                "already contains a symbol that clashes with "
                                "one of those ('{1}') that must be imported "
                                "from the PSyclone profiling module.".format(
                                    routine_name, symbol))
                    # Check for the name of the profiling module itself
                    if text == self.fortran_module:
                        raise NotImplementedError(
                            "Cannot add profiling to '{0}' because it already "
                            "contains a symbol that clashes with the name of "
                            "the PSyclone profiling module ('profile_mod')".
                            format(routine_name))
                    # Check for the names of profiling variables
                    if text.startswith(self.profiling_var):
                        raise NotImplementedError(
                            "Cannot add profiling to '{0}' because it already"
                            " contains symbols that potentially clash with "
                            "the variables we will insert for each profiling "
                            "region ('{1}*').".format(routine_name,
                                                      self.profiling_var))
        # Flag that we have now checked for name clashes so that if there's
        # more than one profiling node we don't fall over on the symbols
        # we've previous inserted.
        self.root.profiling_name_clashes_checked = True

        # Create a name for this region by finding where this profiling
        # node is in the list of profiling nodes in this Invoke.
        sched = self.root
        pnodes = sched.walk(ProfileNode)
        region_idx = pnodes.index(self)
        region_name = "r{0}".format(region_idx)
        var_name = "psy_profile{0}".format(region_idx)

        # Create a variable for this profiling region
        reader = FortranStringReader(
            "type(ProfileData), save :: {0}".format(var_name))
        # Tell the reader that the source is free format
        reader.set_format(FortranFormat(True, False))
        decln = Fortran2003.Type_Declaration_Stmt(reader)
        spec_part.content.append(decln)

        # Find the parent in the parse tree - first get a pointer to the
        # AST for the content of this region.
        if isinstance(self.children[0], Schedule) and \
           not self.children[0].ast:
            # TODO #435 Schedule should really have a valid ast pointer.
            content_ast = self.children[0][0].ast
        else:
            content_ast = self.children[0].ast
        # Now store the parent of this region
        fp_parent = content_ast._parent
        # Find the location of the AST of our first child node in the
        # list of child nodes of our parent in the fparser parse tree.
        ast_start_index = object_index(fp_parent.content, content_ast)
        # Finding the location of the end is harder as it might be the
        # end of a clause within an If or Select block. We therefore
        # work back up the fparser2 parse tree until we find a node that is
        # a direct child of the parent node.
        ast_end_index = None
        if self.children[-1].ast_end:
            ast_end = self.children[-1].ast_end
        else:
            ast_end = self.children[-1].ast
        # Keep a copy of the pointer into the parse tree in case of errors
        ast_end_copy = ast_end

        while ast_end_index is None:
            try:
                ast_end_index = object_index(fp_parent.content, ast_end)
            except ValueError:
                # ast_end is not a child of fp_parent so go up to its parent
                # and try again
                if hasattr(ast_end, "_parent") and ast_end._parent:
                    ast_end = ast_end._parent
                else:
                    raise InternalError(
                        "Failed to find the location of '{0}' in the fparser2 "
                        "Parse Tree:\n{1}\n".format(str(ast_end_copy),
                                                    str(fp_parent.content)))

        # Add the profiling-end call
        reader = FortranStringReader("CALL ProfileEnd({0})".format(var_name))
        # Tell the reader that the source is free format
        reader.set_format(FortranFormat(True, False))
        pecall = Fortran2003.Call_Stmt(reader)
        fp_parent.content.insert(ast_end_index + 1, pecall)

        # Add the profiling-start call
        reader = FortranStringReader(
            "CALL ProfileStart('{0}', '{1}', {2})".format(
                routine_name, region_name, var_name))
        reader.set_format(FortranFormat(True, False))
        pscall = Fortran2003.Call_Stmt(reader)
        fp_parent.content.insert(ast_start_index, pscall)
Esempio n. 19
0
def adduse(parse_tree, location, name, only=None, funcnames=None):
    '''Add a Fortran 'use' statement to an existing fparser2 parse
    tree. This will be added at the first valid location before the
    current location.

    This function should be part of the fparser2 replacement for
    f2pygen (which uses fparser1) but is kept here until this is
    developed, see issue #240.

    The 'parse_tree' argument is only required as fparser2 currently
    does not connect a child to a parent. This will be addressed in
    issue fparser:#102.

    :param parse_tree: The full parse tree of the associated code
    :type parse_tree: :py:class:`fparser.two.utils.Base`
    :param location: The current location (node) in the parse tree \
    provided in the parse_tree argument
    :type location: :py:class:`fparser.two.utils.Base`
    :param str name: The name of the use statement
    :param bool only: Whether to include the 'only' clause in the use \
    statement or not. Defaults to None which will result in only being \
    added if funcnames has content and not being added otherwise.
    :param funcnames: A list of names to include in the use statement's \
    only list. If the list is empty or None then nothing is \
    added. Defaults to None.
    :type funcnames: list of str

    :raises GenerationError: if the location is not part of the parse \
    tree.
    :raises GenerationError: if the location is not a valid location \
    to add a use statement.
    :raises NotImplementedError: if the type of parent node is not \
    supported.
    :raises InternalError: if the type of parent node does not have \
    the expected structure.

    '''
    # pylint: disable=too-many-locals
    # pylint: disable=too-many-branches
    from fparser.two.utils import walk_ast
    from fparser.two.Fortran2003 import Main_Program, Module, \
        Subroutine_Subprogram, Function_Subprogram, Use_Stmt, \
        Specification_Part
    from psyclone.psyGen import GenerationError, InternalError

    if location is None:
        raise GenerationError("alg_gen.py:adduse: Location argument must "
                              "not be None.")
    if funcnames:
        # funcnames have been provided for the only clause.
        if only is False:
            # However, the only clause has been explicitly set to False.
            raise GenerationError(
                "alg_gen.py:adduse: If the 'funcnames' argument is provided "
                "and has content, then the 'only' argument must not be set "
                "to 'False'.")
        if only is None:
            # only has not been specified so set it to True as it is
            # required when funcnames has content.
            only = True

    if only is None:
        # only has not been specified and we can therefore infer that
        # funcnames is empty or is not provided (as earlier code would
        # have set only to True otherwise) so only is not required.
        only = False

    # Create the specified use statement
    only_str = ""
    if only:
        only_str = ", only :"
    my_funcnames = funcnames
    if funcnames is None:
        my_funcnames = []
    use = Use_Stmt("use {0}{1} {2}".format(name, only_str,
                                           ", ".join(my_funcnames)))

    # find the parent program statement containing the specified location
    parent_prog_statement = None
    found = False
    for child in walk_ast(parse_tree.content):
        if child == location:
            found = True
            break
        if isinstance(child, (Main_Program, Module, Subroutine_Subprogram,
                              Function_Subprogram)):
            parent_prog_statement = child

    if not found:
        raise GenerationError("alg_gen.py:adduse: The specified location is "
                              "not in the parse tree.")
    if not parent_prog_statement:
        raise GenerationError(
            "alg_gen.py:adduse: The specified location is invalid as it has "
            "no parent in the parse tree that is a program, module, "
            "subroutine or function.")
    if not isinstance(
            parent_prog_statement,
        (Main_Program, Subroutine_Subprogram, Function_Subprogram)):
        # We currently only support program, subroutine and function
        # as ancestors
        raise NotImplementedError(
            "alg_gen.py:adduse: Unsupported parent code found '{0}'. "
            "Currently support is limited to program, subroutine and "
            "function.".format(str(type(parent_prog_statement))))
    if not isinstance(parent_prog_statement.content[1], Specification_Part):
        raise InternalError(
            "alg_gen.py:adduse: The second child of the parent code "
            "(content[1]) is expected to be a specification part but "
            "found '{0}'.".format(repr(parent_prog_statement.content[1])))

    # add the use statement as the first child of the specification
    # part of the program
    spec_part = parent_prog_statement.content[1]
    spec_part.content.insert(0, use)

    return parse_tree
Esempio n. 20
0
    def parse(self, alg_filename):
        '''Takes a PSyclone conformant algorithm file as input and outputs a
        parse tree of the code contained therein and an object containing
        information about the 'invoke' calls in the algorithm file and any
        associated kernels within the invoke calls.

        :param str alg_filename: The file containing the algorithm code.
        :returns: 2-tuple consisting of the fparser2 parse tree of the \
        algorithm code and an object holding details of the algorithm \
        code and the invokes found within it.
        :rtype: (:py:class:`fparser.two.Fortran2003.Program`, \
                 :py:class:`psyclone.parse.FileInfo`)
        :raises ParseError: if a program, module, subroutine or \
        function is not found in the input file.

        '''
        self._alg_filename = alg_filename

        if self._line_length:
            # Make sure the code conforms to the line length limit.
            check_line_length(alg_filename)

        alg_parse_tree = parse_fp2(alg_filename)

        if self._api == "nemo":
            # For this API we just parse the NEMO code and return the resulting
            # fparser2 AST with None for the Algorithm AST.
            return None, alg_parse_tree

        # Find the first program, module, subroutine or function in the
        # parse tree. The assumption here is that the first is the one
        # that is required. See issue #307.
        container_name = None
        for child in alg_parse_tree.content:
            if isinstance(child, (Main_Program, Module, Subroutine_Subprogram,
                                  Function_Subprogram)):
                container_name = str(child.content[0].items[1])
                break

        if not container_name:
            # Nothing relevant found.
            raise ParseError(
                "algorithm.py:parser:parse: Program, module, function or "
                "subroutine not found in parse tree for file "
                "'{0}'".format(alg_filename))

        self._unique_invoke_labels = []
        self._arg_name_to_module_name = {}
        invoke_calls = []

        for statement in walk_ast(alg_parse_tree.content):

            if isinstance(statement, Use_Stmt):
                # found a Fortran use statement
                self.update_arg_to_module_map(statement)

            if isinstance(statement, Call_Stmt):
                # found a Fortran call statement
                call_name = str(statement.items[0])
                if call_name.lower() == self._invoke_name.lower():
                    # The call statement is an invoke
                    invoke_call = self.create_invoke_call(statement)
                    invoke_calls.append(invoke_call)

        return alg_parse_tree, FileInfo(container_name, invoke_calls)
Esempio n. 21
0
def test_intrinsic_recognised(f2003_create):
    '''Test that an intrinsic is picked up when used in a program.'''

    reader = get_reader("subroutine sub()\na = sin(b)\nend subroutine sub\n")
    ast = Program(reader)
    assert walk_ast([ast], [Intrinsic_Function_Reference])
Esempio n. 22
0
def get_kernel(parse_tree, alg_filename):
    '''Takes the parse tree of an invoke kernel argument and returns the
    name of the kernel and a list of Arg instances which capture the
    relevant information about the arguments associated with the
    kernel.

    :param parse_tree: Parse tree of an invoke argument. This \
    should contain a kernel name and associated arguments.
    :type argument: :py:class:`fparser.two.Fortran2003.Part_Ref`
    :param str alg_filename: The file containing the algorithm code.

    :returns: a 2-tuple with the name of the kernel being called and a \
    list of 'Arg' instances containing the required information for \
    the arguments being passed from the algorithm layer. The list \
    order is the same as the argument order.

    :rtype: (str, list of :py:class:`psyclone.parse.algorithm.Arg`)
    :raises InternalError: if the parse tree is of the wrong type.
    :raises InternalError: if an unsupported argument format is found.

    '''
    # pylint: disable=too-many-branches
    if not isinstance(parse_tree, Part_Ref):
        raise InternalError(
            "algorithm.py:get_kernel: Expected a parse tree (type Part_Ref) "
            "but found instance of '{0}'.".format(type(parse_tree)))

    if len(parse_tree.items) != 2:
        raise InternalError(
            "algorithm.py:get_kernel: Expected Part_Ref to have 2 children "
            "but found {0}.".format(len(parse_tree.items)))

    kernel_name = str(parse_tree.items[0])

    # Extract argument list. This can be removed when
    # fparser#170 is implemented
    argument_list = []
    if isinstance(parse_tree.items[1], Section_Subscript_List):
        argument_list = parse_tree.items[1].items
    else:
        # Expecting a single entry rather than a list
        argument_list = [parse_tree.items[1]]

    arguments = []
    for argument in argument_list:
        if isinstance(argument, (Real_Literal_Constant, Int_Literal_Constant)):
            # A simple constant e.g. 1.0, or 1_i_def
            arguments.append(Arg('literal', argument.tostr().lower()))
        elif isinstance(argument, Name):
            # A simple variable e.g. arg
            full_text = str(argument).lower()
            var_name = full_text
            arguments.append(Arg('variable', full_text, var_name))
        elif isinstance(argument, Part_Ref):
            # An indexed variable e.g. arg(n)
            full_text = argument.tostr().lower()
            var_name = str(argument.items[0]).lower()
            arguments.append(Arg('indexed_variable', full_text, var_name))
        elif isinstance(argument, Function_Reference):
            # A function reference e.g. func()
            full_text = argument.tostr().lower()
            designator = argument.items[0]
            lhs = designator.items[0]
            lhs = create_var_name(lhs)
            rhs = str(designator.items[2])
            var_name = "{0}_{1}".format(lhs, rhs)
            var_name = var_name.lower()
            arguments.append(Arg('indexed_variable', full_text, var_name))
        elif isinstance(argument, Data_Ref):
            # A structure dereference e.g. base%arg, base%arg(n)
            full_text = argument.tostr().lower()
            var_name = create_var_name(argument).lower()
            arguments.append(Arg('variable', full_text, var_name))
        elif isinstance(argument,
                        (Level_2_Unary_Expr, Add_Operand, Parenthesis)):
            # An expression e.g. -1, 1*n, ((1*n)/m). Note, for some
            # reason Add_Operation represents binary expressions in
            # fparser2.  Walk the tree to look for an argument.
            if not walk_ast([argument], [Name]):
                # This is a literal so store the full expression as a
                # string
                arguments.append(Arg('literal', argument.tostr().lower()))
            else:
                raise NotImplementedError(
                    "algorithm.py:get_kernel: Expressions containing "
                    "variables are not yet supported '{0}', value '{1}', "
                    "kernel '{2}' in file '{3}'.".format(
                        type(argument), str(argument), parse_tree,
                        alg_filename))
        else:
            raise InternalError(
                "algorithm.py:get_kernel: Unsupported argument structure "
                "'{0}', value '{1}', kernel '{2}' in file '{3}'.".format(
                    type(argument), str(argument), parse_tree, alg_filename))

    return kernel_name, arguments
Esempio n. 23
0
mod_index = fortran_module(libuq, 'mod_index')

code = """
subroutine test(nfterme, npterme, np, mmax)
      integer, dimension(0:np) :: jterme, jtermo
      integer :: jterme2(0:np), jterme3(4)
      integer nfterme, npterme, mmax
      integer np
end subroutine
"""

fortmod = mod_index
fcode = code

tree = f2003_parser(FortranStringReader(fcode))
func = walk_ast(tree.content, [Subroutine_Stmt])[0]
arglist = walk_ast(func.items, [Dummy_Arg_List])[0]
args = [a.string for a in arglist.items]

"""
Possiblilities:
  * dtype :: varname
  * dtype :: varname(dim)
  * dtype :: var1name(dim1), var2name(dim2)
  * dtype, dimension(dim) :: varname
  * dtype, dimension(dim) :: var1name, var2name
"""


typelist = walk_ast(tree.content, [Type_Declaration_Stmt])
for typestmt in typelist: