stfc / PSyclone

Domain-specific compiler and code transformation system for Finite Difference/Volume/Element Earth-system models in Fortran
BSD 3-Clause "New" or "Revised" License
104 stars 28 forks source link

Add support for marking routines as inquiry #2156

Open sergisiso opened 1 year ago

sergisiso commented 1 year ago

2147 is adding a flag that marks subroutines as elemental and pure.

Similarly it would be good to mark inquiry routines (that have arrays as arguments but the data of the array is not accessed).

This impacts the way the array2loop transformations treats them (currently there are manual exceptions for LBOUND, UBOUND, ...) and how the GPU offloading considers dependencies to their data (again we have manual exceptions)

arporter commented 11 months ago

Although we do now support these concepts, the fparser2 frontend is still ignoring them, e.g. Simon reports:

module m

contains

  impure elemental subroutine s     ! -> Issue 1: both "impure" and "elemental" disappear
    ...
LonelyCat124 commented 5 months ago

I think this is a pretty small fix (though I'm not super comfortable with the fparser2 frontend). I added an else and print statement for now:

        if prefix:
            for child in prefix.children:
                if isinstance(child, Fortran2003.Prefix_Spec):
                    if child.string not in SUPPORTED_ROUTINE_PREFIXES:
                        raise NotImplementedError(
                            f"Routine has unsupported prefix: {child.string}")
                    else:
                        print("SUPPORTED BUT WE DIDNT DO ANYTHIGN")
                else:
                     base_type, _ = self._process_type_spec(routine, child)

and the SUPPORTED BUT WE DIDNT DO ANYTHING happens for impure and elemental. I can try to draft a fix together but I might need help putting together (unit) tests from someone more familiar with fparser2's frontend (unless you're actively working on this already @arporter )

LonelyCat124 commented 5 months ago

Turns out (as usual with fparser2) i didn't understand it properly.

The above code is in subroutine handler, which I think would come up if we had a subroutine outside a module. I'm unclear what in the fparser2 frontend creates the routine symbol for such a scoping.

If we have a module containing it, then we have _process_routine_symbols, which does pull out is_elemental or is_pure and add them to the routine symbol. This is not dropped by fparser (if a module is containing it) but actually by the fortran backend, which just has this code:

            args = [symbol.name for symbol in node.symbol_table.argument_list]
            suffix = ""
            if node.return_symbol:
                # This Routine has a return value and is therefore a Function
                routine_type = "function"
                if node.return_symbol.name.lower() != node.name.lower():
                    suffix = f" result({node.return_symbol.name})"
            else:
                routine_type = "subroutine"
            result = f"{self._nindent}{routine_type} {node.name}("
            result += ", ".join(args) + f"){suffix}\n"

The difficulty here is that the Routine doesn't seem to have any way to easily locate the symbol that belongs to it (You'd just have to search up the tree for any symbol tables with its name) in? In theory its possible to search for it with a = node.ancestor(Container).symbol_table.get_symbols()[node.name] inside routine_node, however I then get these print statements - the first is from fparser2.py (frontend) and the second is from fortran.py (backend) on the same symbol:

s: RoutineSymbol<NoType, pure=False, elemental=True>
s: RoutineSymbol<NoType, pure=unknown, elemental=unknown>

It looks like somewhere (I have no idea where) we lose this information. Its still in the symbol as of the end of _module_handler, so I think we lose it in the PSyIR somewhere.

This test will show this behaviour: Add this to line 1160 in backend/fortran.py (in routine_node) - it makes some other tests fail but not important for now

            # Find RoutineSymbol
            from psyclone.psyir.nodes.container import Container
            a = node.ancestor(Container).symbol_table.get_symbols().get(node.name, None)
            print(a)

Test code:

def test_elemental(parser, fortran_reader, fortran_writer):
    code = '''
    module mymod
    contains
      impure elemental subroutine s()
      end subroutine
    end module
    '''
    psyir = fortran_reader.psyir_from_source(code)
    print(psyir.children[0].symbol_table)
    print(fortran_writer(psyir))                                                                                                                     
    assert False

My output here is:

Symbol Table of Container 'mymod':
----------------------------------
s: RoutineSymbol<NoType, pure=False, elemental=True>

Output:
s: RoutineSymbol<NoType, pure=unknown, elemental=unknown>
module mymod
  implicit none
  public

  contains
  subroutine s()

  end subroutine s

end module mymod

So I correctly had the pure/impure/elemental information coming out of fparser (as long as the subroutine is contained within a module), but it gets lost - I think this is in copy, we can fix this information getting lost by adding a copy function to RoutineSymbol:

    def copy(self):
        '''Create and return a copy of this object. Any references to the
        original will not be affected so the copy will not be referred
        to by any other object.

        :returns: A symbol object with the same properties as this \
                  symbol object.
        :rtype: :py:class:`psyclone.psyir.symbols.RoutineSymbol`                                                                                                                                                                                                                                          '''
    return type(self)(self.name, self.datatype, visibility=self.visibility,                                                                                            interface=self.interface, is_pure=self.is_pure,                                                                                                  is_elemental=self.is_elemental)

I'll try to start working this into a branch tomorrow but I can't fix it for things outside of a module for now I think.