예제 #1
0
def test_walk_debug(capsys):
    ''' Test the debug output of the walk() utility. '''
    import six
    reader = get_reader("program just_a_test\n"
                        "if(.true.)then\n"
                        "  b = 1\n"
                        "end if\n"
                        "end program just_a_test\n")
    main = Fortran2003.Program(reader)
    _ = walk(main, debug=True)
    stdout, _ = capsys.readouterr()
    if six.PY2:
        # Output of capsys under Python 2 is not the same as under 3
        assert stdout.startswith("('child type = ")
    else:
        assert stdout.startswith("child type = ")
    assert "Main_Program" in stdout
    assert "If_Construct" in stdout
    assert "Assignment" in stdout
    assert "Int_Literal_Constant" in stdout
    assert "End_If_Stmt" in stdout
    # Walk only part of the tree and specify an indent
    if_constructs = walk(main, Fortran2003.If_Construct)
    _ = walk(if_constructs[0], indent=4, debug=True)
    stdout, _ = capsys.readouterr()
    if six.PY2:
        # Output of capsys under Python 2 is not the same as under 3
        assert stdout.startswith("('" + 8 * " " + "child type =")
    else:
        assert stdout.startswith(8 * " " + "child type =")
    assert "Program" not in stdout
예제 #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
'''
    reader = get_reader(source, isfree=True, ignore_comments=False)
    fn_unit = Subroutine_Subprogram(reader)
    assert isinstance(fn_unit, Subroutine_Subprogram)
    walk(fn_unit.children, 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 comment.parent is spec_part.content[2]
    assert "! Body comment" in str(comment)
    exec_part = fn_unit.content[2]
    comment = exec_part.content[1]
    assert isinstance(comment, Comment)
    assert comment.parent is exec_part
    assert "! Inline comment" in str(comment)
예제 #3
0
def test_children_property():
    ''' Test that the children property of Base returns the correct
    results for both statements and expressions. '''
    reader = get_reader(TEST_CODE)
    main = Fortran2003.Program(reader)
    # Check that children returns items when we have an expression
    writes = walk(main, Fortran2003.Write_Stmt)
    assert writes[0].children is writes[0].items
    assert len(writes[0].children) == 2
    # Check that it returns content when we have a statement
    do_stmts = walk(main, Fortran2003.Block_Nonlabel_Do_Construct)
    assert do_stmts[0].children is do_stmts[0].content
    assert len(do_stmts[0].children) == 3
예제 #4
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(obj.children, 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")
예제 #5
0
def contains_unsupported_sum(intrinsics):
    '''
    Examines the supplied list of intrinsic nodes in the fparser2 parse tree
    and returns True if it contains a use of the SUM intrinisc with a 'dim'
    argument. (If such a construct is included in a KERNELS region then the
    code produced by v. 18.10 of the PGI compiler seg. faults.)

    :param intrinsics: list of intrinsic function calls in fparser2 parse tree.
    :type intrinsics: list of \
                     :py:class:`Fortran2003.Intrinsic_Function_Reference`

    :returns: True if SUM(array(:,:), dim=blah) is found, False otherwise.
    :rtype: bool

    '''
    for intrinsic in intrinsics:
        if str(intrinsic.items[0]).lower() == "sum":
            # If there's only one argument then we'll just have a Name
            # and not an Actual_Arg_Spec_List (in which case we don't need to
            # check for the 'dim' argument).
            if isinstance(intrinsic.items[1],
                          Fortran2003.Actual_Arg_Spec_List):
                # items[1] contains the Actual_Arg_Spec_List
                actual_args = walk(intrinsic.items[1].items,
                                   Fortran2003.Actual_Arg_Spec)
                for arg in actual_args:
                    if str(arg.items[0]).lower() == "dim":
                        return True
    return False
예제 #6
0
def test_get_child():
    ''' Test the get_child() utility. '''
    from fparser.two import Fortran2003
    from fparser.two.utils import walk, get_child
    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(main.content, 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)
    missing = get_child(io_nodes[0], Fortran2003.Execution_Part)
    assert missing is None
예제 #7
0
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.coded_kernels()[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(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)
예제 #8
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.psyir.nodes.Node`

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

    '''
    from psyclone.psyir.nodes import CodeBlock, IfBlock
    from fparser.two.utils import walk
    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(node.ast, Fortran2003.Data_Ref):
        return False
    return True
예제 #9
0
파일: nemo.py 프로젝트: stfc/PSyclone
 def __init__(self, ast):
     # pylint: disable=super-init-not-called
     names = walk(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
예제 #10
0
def test_new_kernel_file(kernel_outputdir, 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_naming", "multiple")
    psy, invoke = get_invoke("nemolite2d_alg_mod.f90", api="gocean1.0", idx=0)
    sched = invoke.schedule
    kern = sched.coded_kernels()[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(kernel_outputdir),
                            "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(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(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

    from psyclone.tests.gocean1p0_build import GOcean1p0Build
    # If compilation fails this will raise an exception
    GOcean1p0Build(kernel_outputdir).compile_file(filename)
예제 #11
0
def test_implicit_loop_constructor():
    ''' Test array constructor with implicit loop containing an intrinsic
    call. '''
    fcode = "WHERE((/(JBODY,JBODY=1,SIZE(ARR1(:)))/)/=1) ARR1(:)=1.0"
    reader = FortranStringReader(fcode)
    ast = Fortran2003.Where_Stmt(reader)
    assert isinstance(ast, Fortran2003.Where_Stmt)
    constructor = walk(ast, Fortran2003.Array_Constructor)[0]
    do_control = constructor.children[1].children[0].children[1]
    assert isinstance(do_control, Fortran2003.Ac_Implied_Do_Control)
    assert "(JBODY, JBODY = 1, SIZE(ARR1(:)))" in str(ast)
예제 #12
0
def test_walk():
    ''' Test the walk() utility. '''
    reader = get_reader("program hello\n"
                        "write(*,*) 'hello'\n"
                        "write(*,*) 'goodbye'\n"
                        "end program hello\n")
    main = Fortran2003.Program(reader)
    # Check that walk produces the same result whether or not the
    # starting node is in a list.
    all_nodes = walk(main)
    all_nodes2 = walk([main])
    assert all_nodes == all_nodes2
    # Check that we can pull out nodes of a particular type
    all_writes = walk(main, types=Fortran2003.Write_Stmt)
    assert len(all_writes) == 2
    assert "hello" in str(all_writes[0])
    assert "goodbye" in str(all_writes[1])
    # Walk from a list of sibling nodes
    io_units = walk(all_writes, types=Fortran2003.Io_Unit)
    assert len(io_units) == 2
    # Should get empty list if no matching nodes found
    node_list = walk(all_writes, Fortran2003.Execution_Part)
    assert node_list == []
예제 #13
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
        # 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(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(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
예제 #14
0
def test_parent_info():
    ''' Check that parent information is correctly set-up in the
    parse tree. '''
    from fparser.two.utils import Base
    reader = get_reader(TEST_CODE)
    main = Fortran2003.Program(reader)
    node_list = walk(main)

    # Root node in the parse tree has no parent
    parent_prog = node_list[0]
    assert parent_prog.parent is None

    # Check connectivity of all non-string nodes
    for node in node_list[1:]:
        if isinstance(node, Base):
            for child in node.children:
                if isinstance(child, Base):
                    assert child.parent is node
                    assert child.get_root() is parent_prog
예제 #15
0
파일: nemo.py 프로젝트: stfc/PSyclone
    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

        # TODO #737 - this routine should really process generic PSyIR to
        # create domain-specific PSyIR (D-PSyIR) for the NEMO domain.
        # Use the fparser2 frontend to construct the PSyIR from the parse tree
        processor = NemoFparser2Reader()
        # First create a Container representing any Fortran module
        # contained in the parse tree.
        self._container = processor.generate_container(ast)

        # Find all the subroutines contained in the file
        routines = walk(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, sub_name, self)
            self.invoke_map[sub_name] = my_invoke
            self.invoke_list.append(my_invoke)
예제 #16
0
def test_blockbase_tofortran_non_ascii():
    ''' Check that the tofortran() method works when we have a program
    containing non-ascii characters within a sub-class of BlockBase. We
    use a Case Construct for this purpose. '''
    from fparser.common.readfortran import FortranStringReader
    from fparser.two.utils import BlockBase, walk
    from fparser.two.Fortran2003 import Program, Case_Construct
    code = (u"program my_test\n"
            u"! A comment outside the select block\n"
            u"SELECT CASE(iflag)\n"
            u"CASE(  30  )\n"
            u"  IF(lwp) WRITE(*,*) ' for e1=1\xb0'\n"
            u"END SELECT\n"
            u"end program\n")
    reader = FortranStringReader(code, ignore_comments=False)
    obj = Program(reader)
    bbase = walk(obj.content, Case_Construct)[0]
    # Explicitly call tofortran() on the BlockBase class.
    out_str = BlockBase.tofortran(bbase)
    assert "for e1=1" in out_str
예제 #17
0
    for filename in all_files:

        # Parse the current source file:
        try:
            reader = FortranFileReader(filename)
        except IOError:
            print("Could not open file '{0}'.".format(filename),
                  file=sys.stderr)
            sys.exit(-1)

        parser = ParserFactory().create(std="f2003")
        parse_tree = parser(reader)

        # Collect all used modules in a list
        all_use = []
        for node in walk(parse_tree, Use_Stmt):
            use_name = str(node.items[2])
            # Nothing else to do if the name is already in the list:
            if use_name + ".o" in all_use:
                continue

            # If you want to implement a specific naming convention,
            # you can modify the content of 'use_name' here. For example,
            # you could remove a '_mod' at the end if your file names do
            # not contains this.
            if os.path.isfile(use_name+".f90") or \
                    os.path.isfile(use_name+".x90") or \
                    os.path.isfile(use_name+".F90"):
                all_use.append(use_name + ".o")

        # Now output all dependencies for this file (if any):
예제 #18
0
    def validate(self, nodes, options=None):
        '''
        Calls the validate method of the base class and then checks that,
        for the NEMO API, the routine that will contain the instrumented
        region already has a Specification_Part (because we've not yet
        implemented the necessary support if it doesn't).
        TODO: #435

        :param nodes: a node or list of nodes to be instrumented with \
            PSyData API calls.
        :type nodes: (list of) :py:class:`psyclone.psyir.nodes.Loop`

        :param options: a dictionary with options for transformations.
        :type options: dictionary of string:values or None
        :param str options["prefix"]: a prefix to use for the PSyData module \
            name (``PREFIX_psy_data_mod``) and the PSyDataType \
            (``PREFIX_PSYDATATYPE``) - a "_" will be added automatically. \
            It defaults to "".
        :param (str,str) options["region_name"]: an optional name to \
            use for this PSyData area, provided as a 2-tuple containing a \
            location name followed by a local name. The pair of strings \
            should uniquely identify a region unless aggregate information \
            is required (and is supported by the runtime library).

        :raises TransformationError: if we're using the NEMO API and the \
            target routine has no Specification_Part.
        :raises TransformationError: if the PSyData node is inserted \
            between an OpenMP/ACC directive and the loop(s) to which it \
            applies.

        '''
        node_list = self.get_node_list(nodes)

        if not node_list:
            raise TransformationError("Cannot apply transformation to an "
                                      "empty list of nodes.")

        node_parent = node_list[0].parent
        if isinstance(node_parent, Schedule) and \
           isinstance(node_parent.parent, (OMPDoDirective, ACCLoopDirective)):
            raise TransformationError("A PSyData node cannot be inserted "
                                      "between an OpenMP/ACC directive and "
                                      "the loop(s) to which it applies!")

        if node_list[0].ancestor(ACCDirective):
            raise TransformationError("A PSyData node cannot be inserted "
                                      "inside an OpenACC region.")

        if options:
            if "region_name" in options:
                name = options["region_name"]
                # pylint: disable=too-many-boolean-expressions
                if not isinstance(name, tuple) or not len(name) == 2 or \
                   not name[0] or not isinstance(name[0], str) or \
                   not name[1] or not isinstance(name[1], str):
                    raise TransformationError(
                        "Error in {0}. User-supplied region name must be a "
                        "tuple containing two non-empty strings."
                        "".format(self.name))
                # pylint: enable=too-many-boolean-expressions
            if "prefix" in options:
                prefix = options["prefix"]
                if prefix not in Config.get().valid_psy_data_prefixes:
                    raise TransformationError(
                        "Error in 'prefix' parameter: found '{0}', expected "
                        "one of {1} as defined in {2}".format(
                            prefix,
                            Config.get().valid_psy_data_prefixes,
                            Config.get().filename))

        # We have to create an instance of the node that will be inserted in
        # order to find out what module name it will use.
        pdata_node = self._node_class(options=options)
        table = node_list[0].scope.symbol_table
        for name in ([sym.name for sym in pdata_node.imported_symbols] +
                     [pdata_node.fortran_module]):
            try:
                _ = table.lookup_with_tag(name)
            except KeyError:
                # The tag doesn't exist which means that we haven't already
                # added this symbol as part of a PSyData transformation. Check
                # for any clashes with existing symbols.
                try:
                    _ = table.lookup(name)
                    raise TransformationError(
                        "Cannot add PSyData calls because there is already a "
                        "symbol named '{0}' which clashes with one of those "
                        "used by the PSyclone PSyData API. ".format(name))
                except KeyError:
                    pass

        super(PSyDataTrans, self).validate(node_list, options)

        # The checks below are only for the NEMO API and can be removed
        # once #435 is done.
        sched = node_list[0].ancestor(InvokeSchedule)
        if not sched:
            # Some tests construct PSyIR fragments that do not have an
            # InvokeSchedule
            return
        invoke = sched.invoke
        if not isinstance(invoke, NemoInvoke):
            return

        # Get the parse tree of the routine containing this region
        # pylint: disable=protected-access
        ptree = invoke._ast
        # pylint: enable=protected-access
        # Search for the Specification_Part
        if not walk([ptree], Fortran2003.Specification_Part):
            raise TransformationError(
                "For the NEMO API, PSyData can only be added to routines "
                "which contain existing variable declarations (i.e. a "
                "Specification Part) but '{0}' does not have any.".format(
                    invoke.name))
예제 #19
0
    def update(self):
        # pylint: disable=too-many-branches, too-many-statements
        # pylint: disable=too-many-locals
        '''
        Update the underlying fparser2 parse tree to implement the PSyData
        region represented by this Node. This involves adding the necessary
        module use statement as well as the calls to the PSyData 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 \
                             PSyData calls 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 PSyData region.

        '''
        # Avoid circular dependencies
        # pylint: disable=import-outside-toplevel
        from psyclone.psyGen import object_index, InvokeSchedule
        from psyclone.psyir.nodes import ProfileNode

        # The update function at this stage only supports profiling
        if not isinstance(self, ProfileNode):
            raise InternalError("PSyData.update is only supported for a "
                                "ProfileNode, not for a node of type {0}."
                                .format(type(self)))

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

        # Get the parse tree of the routine containing this region
        routine_schedule = self.ancestor(InvokeSchedule)
        ptree = routine_schedule.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([ptree], (Fortran2003.Main_Program,
                                   Fortran2003.Subroutine_Stmt,
                                   Fortran2003.Function_Stmt,
                                   Fortran2003.Specification_Part,
                                   Fortran2003.Use_Stmt,
                                   Fortran2003.Name))
        if self._module_name:
            routine_name = self._module_name
        else:
            for node in node_list:
                if isinstance(node, (Fortran2003.Main_Program,
                                     Fortran2003.Subroutine_Stmt,
                                     Fortran2003.Function_Stmt)):
                    names = walk([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 PSyData regions to routines without any "
                "existing declarations is not supported and '{0}' has no "
                "Specification-Part".format(routine_name))

        # TODO #703: Rename the PSyDataType instead of
        # aborting.
        # 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 PSyData API)
                if str(node).lower() != self.use_stmt.lower():
                    raise NotImplementedError(
                        "Cannot add PSyData calls 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([node], Fortran2003.Name)
                for name in names:
                    node_list.remove(name)

        if not found:
            # We don't already have a use for the PSyData module so
            # add one.
            reader = FortranStringReader(
                "use {0}, only: {1}"
                .format(self.add_psydata_class_prefix("psy_data_mod"),
                        self.type_name))
            # 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 the PSyData API. This check uses the list of
        # symbols that we created before adding the `use psy_data_mod...`
        # statement.
        if not routine_schedule.psy_data_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 PSyData module
                    for symbol in self.imported_symbols:
                        if text == symbol.name.lower():
                            raise NotImplementedError(
                                "Cannot add PSyData calls to '{0}' because it "
                                "already contains a symbol that clashes with "
                                "one of those ('{1}') that must be imported "
                                "from the PSyclone PSyData module.".
                                format(routine_name, symbol.name))
                    # Check for the name of the PSyData module itself
                    if text == self.fortran_module:
                        raise NotImplementedError(
                            "Cannot add PSyData calls to '{0}' because it "
                            "already contains a symbol that clashes with the "
                            "name of the PSyclone PSyData module "
                            "('{1}')". format(routine_name,
                                              self.fortran_module))
                    # Check for the names of PSyData variables
                    if text.startswith(self._psy_data_symbol_with_prefix):
                        raise NotImplementedError(
                            "Cannot add PSyData calls to '{0}' because it "
                            "already contains symbols that potentially clash "
                            "with the variables we will insert for each "
                            "PSyData region ('{1}*').".
                            format(routine_name,
                                   self._psy_data_symbol_with_prefix))
        # Flag that we have now checked for name clashes so that if there's
        # more than one PSyData node we don't fall over on the symbols
        # we've previous inserted.
        # TODO #435 the psy_data_name_clashes_checked attribute only exists
        # for a NemoInvokeSchedule. Since this whole `update()` routine will
        # be removed once we are able to use the PSyIR backend to re-generate
        # NEMO code, the pylint warning is disabled.
        # pylint: disable=attribute-defined-outside-init
        routine_schedule.psy_data_name_clashes_checked = True
        # pylint: enable=attribute-defined-outside-init

        # Create a name for this region by finding where this PSyDataNode
        # is in the list of PSyDataNodes in this Invoke.
        pnodes = routine_schedule.walk(PSyDataNode)
        region_idx = pnodes.index(self)
        if self._region_name:
            region_name = self._region_name
        else:
            region_name = "r{0}".format(region_idx)
        var_name = "{0}{1}".format(self.add_psydata_class_prefix("psy_data"),
                                   region_idx)

        # Create a variable for this PSyData region
        reader = FortranStringReader(
            "type({0}), target, save :: {1}".format(self.type_name, 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.
        content_ast = self.psy_data_body.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)
        # Do the same for our last child node
        if self.psy_data_body[-1].ast_end:
            ast_end = self.psy_data_body[-1].ast_end
        else:
            ast_end = self.psy_data_body[-1].ast

        if ast_end.parent is not fp_parent:
            raise InternalError(
                "The beginning ({0}) and end ({1}) nodes of the PSyData "
                "region in the fparser2 parse tree do not have the same "
                "parent.".format(content_ast, ast_end))
        ast_end_index = object_index(fp_parent.content, ast_end)

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

        # Add the PSyData start call
        reader = FortranStringReader(
            "CALL {2}%PreStart('{0}', '{1}', 0, 0)".format(
                routine_name, region_name, var_name))
        reader.set_format(FortranFormat(True, False))
        pscall = Fortran2003.Call_Stmt(reader)
        pscall.parent = fp_parent
        fp_parent.content.insert(ast_start_index, pscall)
        # Set the pointers back into the modified parse tree
        self.ast = pscall
        self.ast_end = pecall
        self.set_region_identifier(routine_name, region_name)
예제 #20
0
def test_intrinsic_recognised():
    '''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, Intrinsic_Function_Reference)
예제 #21
0
파일: algorithm.py 프로젝트: xyuan/PSyclone
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#211 is fixed.
    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(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
예제 #22
0
# The configurable list of substitutions
mods = 'field_mod', 'rsolver_field_mod'
types = 'field_type', 'rsolver_field_type'
kinds = 'r_def', 'r_solver_def'

# Loop over configurable list
for i in range(0, 2):

    # Parse the fortran into an AST
    READER = FortranStringReader(MYFILE)
    F2008_PARSER = ParserFactory().create(std="f2008")
    past = F2008_PARSER(READER)

    # Walk through the content
    for statement in walk(past.content):
        # Assume everything is wrapped in a module
        # There are three cases to catch, the (end) module statement
        # The (end) type statement
        # The kind statements
        # In in case, the fparser2 object is a tuple. Convert it to a list
        # after check, replace the text (in each case item 1 from list)
        # from the substitution list
        # Convert back to a tuple in the AST
        if isinstance(statement, Module):
            # walk through the statements of the module
            for child in walk(statement):
                # If it is the (end) module statement
                if isinstance(child, Module_Stmt) or isinstance(
                        child, End_Module_Stmt):
                    children = list(child.items)
예제 #23
0
파일: algorithm.py 프로젝트: xyuan/PSyclone
    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 = OrderedDict()
        invoke_calls = []

        for statement in walk(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)
예제 #24
0
def valid_acc_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 PSyIRe to check.
    :type node: :py:class:`psyclone.psyGen.Node`

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

    '''
    # The Fortran routine which our parent Invoke represents
    sched = node.ancestor(NemoInvokeSchedule)
    routine_name = sched.invoke.name

    # Allow for per-routine setting of what to exclude from within KERNELS
    # regions. This is because sometimes things work in one context but not
    # in another (with the PGI compiler).
    excluding = EXCLUDING.get(routine_name, EXCLUDING["default"])

    # Rather than walk the tree multiple times, look for both excluded node
    # types and possibly problematic operations
    excluded_node_types = (CodeBlock, IfBlock, BinaryOperation, NaryOperation,
                           NemoLoop, NemoKern, Loop, Return, Assignment, Call)
    excluded_nodes = node.walk(excluded_node_types)
    # Ensure we check inside Kernels too (but only for IfBlocks)
    if excluding.inside_kernels:
        for kernel in [
                enode for enode in excluded_nodes
                if isinstance(enode, NemoKern)
        ]:
            ksched = kernel.get_kernel_schedule()
            excluded_nodes += ksched.walk(IfBlock)

    # Since SELECT blocks are mapped to nested IfBlocks in the PSyIR,
    # we have to be careful to exclude attempts to put multiple
    # CASE branches inside a single region. (Once we are no longer
    # reliant on the original fparser2 parse tree this restriction
    # can be lifted - #435.)
    # Is the supplied node a child of an IfBlock originating from a
    # SELECT block?
    if isinstance(node, IfBlock) and "was_case" in node.annotations:
        if isinstance(node.parent.parent, IfBlock) and \
           "was_case" in node.parent.parent.annotations:
            log_msg(routine_name, "cannot split children of a SELECT", node)
            return False

    if (isinstance(node.parent.parent, IfBlock)
            and "was_where" in node.parent.parent.annotations):
        # Cannot put KERNELS *within* a loop nest tht originated from
        # a WHERE construct.
        log_msg(routine_name, "cannot put KERNELs *inside* a WHERE", node)
        return False

    for enode in excluded_nodes:
        if isinstance(enode, (CodeBlock, Return, Call)):
            log_msg(routine_name,
                    "region contains {0}".format(type(enode).__name__), enode)
            return False

        if isinstance(enode, IfBlock):
            # We permit IF blocks that originate from WHERE constructs inside
            # KERNELS regions
            if "was_where" in enode.annotations:
                continue

            # Exclude things of the form IF(var == 0.0) because that causes
            # deadlock in the code generated by the PGI compiler (<=19.4).
            if PGI_VERSION <= 1940:
                opn = enode.children[0]
                if (excluding.ifs_real_scalars
                        and isinstance(opn, BinaryOperation)
                        and opn.operator == BinaryOperation.Operator.EQ
                        and isinstance(opn.children[1], Literal)
                        and opn.children[1].datatype.intrinsic
                        == ScalarType.Intrinsic.REAL):
                    log_msg(routine_name,
                            "IF performs comparison with REAL scalar", enode)
                    return False

            # We also permit single-statement IF blocks that contain a Loop
            if "was_single_stmt" in enode.annotations and enode.walk(Loop):
                continue
            # When using CUDA managed memory, only allocated arrays are
            # automatically put onto the GPU (although
            # this includes those that are created by compiler-generated allocs
            # e.g. for automatic arrays). We assume that all arrays of rank 2
            # or greater are dynamically allocated.
            arrays = enode.children[0].walk(ArrayReference)
            # We also exclude if statements where the condition expression does
            # not refer to arrays at all as this seems to cause issues for
            # 19.4 of the compiler (get "Missing branch target block").
            if not arrays and \
               (excluding.ifs_scalars and not isinstance(enode.children[0],
                                                         BinaryOperation)):
                log_msg(routine_name, "IF references scalars", enode)
                return False
            if excluding.ifs_1d_arrays and \
               any([len(array.children) == 1 for array in arrays]):
                log_msg(routine_name,
                        "IF references 1D arrays that may be static", enode)
                return False

        elif (isinstance(enode, Assignment) and enode.is_array_range):
            intrinsics = walk(enode.ast,
                              Fortran2003.Intrinsic_Function_Reference)
            if PGI_VERSION < 1940:
                # Need to check for SUM inside implicit loop, e.g.:
                #     vt_i(:, :) = SUM(v_i(:, :, :), dim = 3)
                if contains_unsupported_sum(intrinsics):
                    log_msg(routine_name,
                            "Implicit loop contains unsupported SUM", enode)
                    return False
            # Check for MINLOC inside implicit loop
            if contains_minloc(intrinsics):
                log_msg(routine_name, "Implicit loop contains MINLOC call",
                        enode)
                return False
            # Need to check for RESHAPE inside implicit loop
            if contains_reshape(intrinsics):
                log_msg(routine_name, "Implicit loop contains RESHAPE call",
                        enode)
                return False

        elif isinstance(enode, NemoLoop):
            # Heuristic:
            # We don't want to put loops around 3D loops into KERNELS regions
            # and nor do we want to put loops over levels into KERNELS regions
            # if they themselves contain several 2D loops.
            # In general, this heuristic will depend upon how many levels the
            # model configuration will contain.
            child = enode.loop_body[0]
            if isinstance(child, Loop) and child.loop_type == "levels":
                # We have a loop around a loop over levels
                log_msg(routine_name, "Loop is around a loop over levels",
                        enode)
                return False
            if enode.loop_type == "levels" and \
               len(enode.loop_body.children) > 1:
                # The body of the loop contains more than one statement.
                # How many distinct loop nests are there?
                loop_count = 0
                for child in enode.loop_body.children:
                    if child.walk(Loop):
                        loop_count += 1
                        if loop_count > 1:
                            log_msg(
                                routine_name,
                                "Loop over levels contains several "
                                "other loops", enode)
                            return False

    # For now we don't support putting *just* the implicit loop assignment in
    # things like:
    #    if(do_this)my_array(:,:) = 1.0
    # inside a kernels region. Once we generate Fortran instead of modifying
    # the fparser2 parse tree this will become possible.
    if isinstance(node.parent, Schedule) and \
       isinstance(node.parent.parent, IfBlock) and \
       "was_single_stmt" in node.parent.parent.annotations:
        log_msg(routine_name, "Would split single-line If statement", node)
        return False

    # Finally, check that we haven't got any 'array accesses' that are in
    # fact function calls.
    refs = node.walk(ArrayReference)
    # Since kernels are leaves in the PSyIR, we need to separately check
    # their schedules for array references too.
    kernels = node.walk(NemoKern)
    for kern in kernels:
        sched = kern.get_kernel_schedule()
        refs += sched.walk((ArrayReference, NemoLoop))
    for ref in refs:
        if (isinstance(ref, ArrayReference)
                and ref.name.lower() in NEMO_FUNCTIONS):
            # This reference has the name of a known function. Is it on
            # the LHS or RHS of an assignment?
            ref_parent = ref.parent
            if isinstance(ref_parent, Assignment) and ref is ref_parent.lhs:
                # We're writing to it so it's not a function call.
                continue
            log_msg(routine_name,
                    "Loop contains function call: {0}".format(ref.name), ref)
            return False
        if isinstance(ref, NemoLoop):
            if contains_unsupported_sum(ref.ast):
                log_msg(routine_name, "Loop contains unsupport SUM", ref)
                return False
    return True
예제 #25
0
파일: kernel.py 프로젝트: stfc/PSyclone
    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. The search is not case sensitive.

        :param str name: the name of the integer array to find.

        :return: list of values (lower-case).
        :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 array is not of rank 1.
        :raises ParseError: if the array extent is not specified using an \
                            integer literal.
        :raises ParseError: if the RHS of the declaration is not an array \
                            constructor.
        :raises InternalError: if the parse tree for the array constructor \
                               does not have the expected structure.
        :raises ParseError: if the number of items in the array constructor \
                            does not match the extent of the array.

        '''
        # Ensure the classes are setup for the Fortran2008 parser
        _ = ParserFactory().create(std="f2008")
        # Fortran is not case sensitive so nor is our matching
        lower_name = name.lower()

        for statement, _ in fpapi.walk(self._ktype):
            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(assign.children, Fortran2003.Name)
            if not names:
                raise InternalError("Unsupported assignment statement: '{0}'".
                                    format(str(assign)))

            if str(names[0]).lower() != lower_name:
                # This is not the variable declaration we're looking for
                continue

            if not isinstance(assign.children[0], Fortran2003.Part_Ref):
                # Not an array declaration
                return []

            if not isinstance(assign.children[0].children[1],
                              Fortran2003.Section_Subscript_List):
                raise InternalError(
                    "get_integer_array: expected array declaration to have a "
                    "Section_Subscript_List but found '{0}' for: '{1}'".format(
                        type(assign.children[0].children[1]).__name__,
                        str(assign)))

            dim_stmt = assign.children[0].children[1]
            if len(dim_stmt.children) != 1:
                raise ParseError(
                    "get_integer_array: array must be 1D but found an array "
                    "with {0} dimensions for name '{1}'".format(
                        len(dim_stmt.children), name))
            if not isinstance(dim_stmt.children[0],
                              Fortran2003.Int_Literal_Constant):
                raise ParseError(
                    "get_integer_array: array extent must be specified using "
                    "an integer literal but found '{0}' for array '{1}'".
                    format(str(dim_stmt.children[0]), name))
            # Get the declared size of the array
            array_extent = int(str(dim_stmt.children[0]))

            if not isinstance(assign.children[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(assign.children[2].children, Fortran2003.Name)
            if not names:
                raise InternalError("Failed to parse array constructor: "
                                    "'{0}'".format(str(assign.items[2])))
            if len(names) != array_extent:
                # Ideally fparser would catch this but it isn't yet mature
                # enough.
                raise ParseError(
                    "get_integer_array: declared length of array '{0}' is {1} "
                    "but constructor only contains {2} names: '{3}'".format(
                        name, array_extent, len(names), str(assign)))
            return [str(name).lower() for name in names]
        # No matching declaration for the provided name was found
        return []
예제 #26
0
파일: algorithm.py 프로젝트: stfc/PSyclone
def get_kernel(parse_tree, alg_filename, arg_type_defns):
    '''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 parse_tree: :py:class:`fparser.two.Fortran2003.Part_Ref` or \
        :py:class:`fparser.two.Fortran2003.Structure_Constructor`
    :param str alg_filename: The file containing the algorithm code.

    :param arg_type_defns: dictionary holding a 2-tuple consisting of \
        type and precision information for each variable declared in \
        the algorithm layer, indexed by variable name.
    :type arg_type_defns: dict[str] = (str, str or NoneType)

    :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 Part_Ref or Structure_Constructor do not \
        have two children.
    :raises InternalError: if Proc_Component_Ref has a child with an \
        unexpected type.
    :raises InternalError: if Data_Ref has a child with an unexpected \
        type.
    :raises NotImplementedError: if an expression contains a variable.
    :raises InternalError: if an unsupported argument format is found.

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

    if len(parse_tree.items) != 2:
        raise InternalError("algorithm.py:get_kernel: Expected Part_Ref or "
                            "Structure_Constructor 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#211 is fixed.
    argument_list = []
    if isinstance(parse_tree.items[1],
                  (Section_Subscript_List, Component_Spec_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
            datatype = arg_type_defns.get(var_name)
            arguments.append(
                Arg('variable', full_text, varname=var_name,
                    datatype=datatype))
        elif isinstance(argument, Part_Ref):
            # An indexed variable e.g. arg(n)
            full_text = argument.tostr().lower()
            var_name = str(argument.items[0]).lower()
            datatype = arg_type_defns.get(var_name)
            arguments.append(
                Arg('indexed_variable',
                    full_text,
                    varname=var_name,
                    datatype=datatype))
        elif isinstance(argument, Function_Reference):
            # A function reference e.g. func(). The datatype of this
            # function is not determined so datatype in Arg is None.
            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, varname=var_name))
        elif isinstance(argument, (Data_Ref, Proc_Component_Ref)):
            # A structure access e.g. a % b or self % c
            if isinstance(argument, Proc_Component_Ref):
                if isinstance(argument.children[2], Name):
                    arg = argument.children[2].string.lower()
                else:
                    # It does not appear to be possible to get to here
                    # as an array (e.g. self%a(10)) is not treated as
                    # being a Proc_Component_Ref by fparser2 and
                    # Data_Ref otherwise always has a Name on the rhs
                    # (3rd argument).
                    raise InternalError(
                        "The third argument to to a Proc_Component_Ref is "
                        "expected to be a Name, but found '{0}'."
                        "".format(type(argument.children[2]).__name__))
            elif isinstance(argument, Data_Ref):
                rhs_node = argument.children[-1]
                if isinstance(rhs_node, Part_Ref):
                    rhs_node = rhs_node.children[0]
                if not isinstance(rhs_node, Name):
                    raise InternalError(
                        "The last child of a Data_Ref is expected to be "
                        "a Name or a Part_Ref whose first child is a "
                        "Name, but found '{0}'."
                        "".format(type(rhs_node).__name__))
                arg = rhs_node.string.lower()
            datatype = arg_type_defns.get(arg)
            full_text = argument.tostr().lower()
            var_name = create_var_name(argument).lower()
            arguments.append(
                Arg('variable', full_text, varname=var_name,
                    datatype=datatype))
        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(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
예제 #27
0
파일: algorithm.py 프로젝트: stfc/PSyclone
    def invoke_info(self, alg_parse_tree):
        '''Takes an fparser2 representation of a PSyclone-conformant algorithm
        code as input and returns an object containing information
        about the 'invoke' calls in the algorithm file and any
        associated kernels within the invoke calls. Also captures the
        type and precision of every variable declaration within the
        parse tree.

        :param alg_parse_tree: the fparser2 representation of the \
            algorithm code.
        :type: :py:class:`fparser.two.Fortran2003.Program`

        :returns: an object holding details of the algorithm \
            code and the invokes found within it.
        :rtype: :py:class:`psyclone.parse.FileInfo`

        :raises ParseError: if a program, module, subroutine or \
            function is not found in the fparser2 tree.
        :raises InternalError: if the fparser2 tree representing the \
            type declaration statements is not in the expected form.
        :raises NotImplementedError: if the algorithm code contains \
            two different datatypes with the same name.

        '''
        # 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 fparser2 parse tree.")

        self._unique_invoke_labels = []
        self._arg_name_to_module_name = OrderedDict()
        # Dict holding a 2-tuple consisting of type and precision
        # information for each variable declared in the algorithm
        # file, indexed by variable name.
        self._arg_type_defns = dict()
        invoke_calls = []

        # Find all invoke calls and capture information about
        # them. Also find information about use statements and find
        # all declarations within the supplied parse
        # tree. Declarations will include the definitions of any
        # components of derived types that are defined within the
        # code.
        for statement in walk(alg_parse_tree.content,
                              types=(Type_Declaration_Stmt,
                                     Data_Component_Def_Stmt, Use_Stmt,
                                     Call_Stmt)):
            if isinstance(statement,
                          (Type_Declaration_Stmt, Data_Component_Def_Stmt)):
                # Capture datatype information for the variable
                spec = statement.children[0]
                if isinstance(spec, Declaration_Type_Spec):
                    # This is a type declaration
                    my_type = spec.children[1].string.lower()
                    my_precision = None
                elif isinstance(spec, Intrinsic_Type_Spec):
                    # This is an intrinsic declaration
                    my_type = spec.children[0].lower()
                    my_precision = None
                    if isinstance(spec.children[1], Kind_Selector):
                        my_precision = \
                            spec.children[1].children[1].string.lower()
                else:
                    raise InternalError(
                        "Expected first child of Type_Declaration_Stmt or "
                        "Data_Component_Def_Stmt to be Declaration_Type_Spec "
                        "or Intrinsic_Type_Spec but found '{0}'"
                        "".format(type(spec).__name__))
                for decl in walk(statement.children[2],
                                 (Entity_Decl, Component_Decl)):
                    # Determine the variables names. Note that if a
                    # variable declaration is a component of a derived
                    # type, its name is stored 'as is'. This means
                    # that e.g. real :: a will clash with a
                    # derived-type definition if the latter has a
                    # component named 'a' and their datatypes differ.
                    my_var_name = decl.children[0].string.lower()
                    if my_var_name in self._arg_type_defns and (
                            self._arg_type_defns[my_var_name][0] != my_type
                            or self._arg_type_defns[my_var_name][1] !=
                            my_precision):
                        raise NotImplementedError(
                            "The same symbol '{0}' is used for different "
                            "datatypes, '{1}, {2}' and '{3}, {4}'. This is "
                            "not currently supported.".format(
                                my_var_name,
                                self._arg_type_defns[my_var_name][0],
                                self._arg_type_defns[my_var_name][1], my_type,
                                my_precision))
                    # Store the variable name and information about its type
                    self._arg_type_defns[my_var_name] = (my_type, my_precision)

            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 FileInfo(container_name, invoke_calls)