Ejemplo n.º 1
0
def test_allocate_incorrect_arg_type():
    '''check that an allocate raises an error if an unknown type is
    passed.'''
    module = ModuleGen(name="testmodule")
    content = 3
    with pytest.raises(RuntimeError):
        _ = AllocateGen(module, content)
Ejemplo n.º 2
0
def test_allocate_arg_str():
    '''check that an allocate gets created succesfully with content being
    a string.'''
    module = ModuleGen(name="testmodule")
    content = "hello"
    allocate = AllocateGen(module, content)
    module.add(allocate)
    lines = str(module.root).splitlines()
    assert "ALLOCATE (" + content + ")" in lines[3]
Ejemplo n.º 3
0
def test_allocate_arg_list():
    '''check that an allocate gets created succesfully with content being
    a list.'''
    module = ModuleGen(name="testmodule")
    content = ["hello", "how", "are", "you"]
    content_str = ""
    for idx, name in enumerate(content):
        content_str += name
        if idx + 1 < len(content):
            content_str += ", "
    allocate = AllocateGen(module, content)
    module.add(allocate)
    lines = str(module.root).splitlines()
    assert "ALLOCATE (" + content_str + ")" in lines[3]
Ejemplo n.º 4
0
    def create_driver(self, input_list, output_list):
        # pylint: disable=too-many-locals, too-many-statements
        '''This function creates a driver that can read the
        output created by the extraction code. This is a stand-alone
        program that will read the input data, calls the kernels/
        instrumented region, and then compares the results with the
        stored results in the file.

        TODO: #644: we need type information here.

        :param input_list: list of variables that are input parameters.
        :type input_list: list of str
        :param output_list: list of variables that are output parameters.
        :type output_list: list or str
        '''

        from psyclone.f2pygen import AllocateGen, AssignGen, CallGen,\
            CommentGen, DeclGen, ModuleGen, SubroutineGen, UseGen, \
            TypeDeclGen
        from psyclone.gocean1p0 import GOSymbolTable
        from psyclone.psyir.symbols import Symbol

        all_vars = list(set(input_list).union(set(output_list)))
        all_vars.sort()

        module_name, region_name = self.region_identifier
        module = ModuleGen(name=module_name)
        prog = SubroutineGen(parent=module, name=module_name+"_code",
                             implicitnone=True)
        module.add(prog)
        use = UseGen(prog, self.add_psydata_class_prefix("psy_data_mod"),
                     only=True,
                     funcnames=[self.add_psydata_class_prefix("PSyDataType")])
        prog.add(use)

        # Use a symbol table to make sure all variable names are unique
        sym_table = GOSymbolTable()
        sym = Symbol("PSyDataType")
        sym_table.add(sym)

        psy_data = sym_table.new_symbol_name(self.add_psydata_class_prefix
                                             ("psy_data"))
        sym_table.add(Symbol(psy_data))
        var_decl = TypeDeclGen(prog, datatype=self.add_psydata_class_prefix
                               ("PSyDataType"),
                               entity_decls=[psy_data])
        prog.add(var_decl)

        call = CallGen(prog,
                       "{0}%OpenRead(\"{1}\", \"{2}\")"
                       .format(psy_data, module_name, region_name))
        prog.add(call)

        post_suffix = self._post_name

        # Variables might need to be renamed in order to guarantee unique
        # variable names in the driver: An example of this would be if the
        # user code contains a variable 'dx', and the kernel takes a
        # property 'dx' as well. In the original code that is no problem,
        # since the property is used via field%grid%dx. But the stand-alone
        # driver renames field%grid%dx to dx, which can cause a name clash.
        # Similar problems can exist with any user defined type, since all
        # user defined types are rewritten to just use the field name.
        # We use a mapping to support renaming of variables: it takes as
        # key the variable as used in the original program (e.g. 'dx' from
        # an expression like field%grid%dx), and maps it to a unique local
        # name (e.g. dx_0).

        rename_variable = {}
        for var_name in all_vars:
            # TODO #644: we need to identify arrays!!
            # Support GOcean properties, which are accessed via a
            # derived type (e.g. 'fld%grid%dx'). In this stand-alone
            # driver we don't have the derived type, instead we create
            # variable based on the field in the derived type ('dx'
            # in the example above), and pass this variable to the
            # instrumented code.
            last_percent = var_name.rfind("%")
            if last_percent > -1:
                # Strip off the derived type, and only leave the last
                # field, which is used as the local variable name.
                local_name = var_name[last_percent+1:]
            else:
                # No derived type, so we can just use the
                # variable name directly in the driver
                local_name = var_name
            unique_local_name = sym_table.new_symbol_name(local_name)
            rename_variable[local_name] = unique_local_name
            sym_table.add(Symbol(unique_local_name))
            local_name = unique_local_name

            # TODO: #644 - we need to identify arrays!!
            # Any variable used needs to be defined. We also need
            # to handle the kind property better and not rely on
            # a hard-coded value.
            decl = DeclGen(prog, "real", [local_name], kind="8",
                           dimension=":,:", allocatable=True)
            prog.add(decl)
            is_input = var_name in input_list
            is_output = var_name in output_list

            if is_input and not is_output:
                # We only need the pre-variable, and we can read
                # it from the file (this call also allocates space for it).
                call = CallGen(prog,
                               "{0}%ReadVariable(\"{1}\", {2})"
                               .format(psy_data, var_name, local_name))
                prog.add(call)
            elif is_input:
                # Now must be input and output:
                # First read the pre-variable (which also allocates it):
                call = CallGen(prog,
                               "{0}%ReadVariable(\"{1}\", {2})"
                               .format(psy_data, var_name, local_name))
                prog.add(call)
                # Then declare the post variable, and and read its values
                # (ReadVariable will also allocate it)
                sym = Symbol(local_name+post_suffix)
                sym_table.add(sym)
                decl = DeclGen(prog, "real", [local_name+post_suffix],
                               dimension=":,:", kind="8", allocatable=True)
                prog.add(decl)
                call = CallGen(prog,
                               "{0}%ReadVariable(\"{1}{3}\", {2}{3})"
                               .format(psy_data, var_name, local_name,
                                       post_suffix))
                prog.add(call)
            else:
                # Now the variable is output only. We need to read the
                # post variable in, and create and allocate a pre variable
                # with the same size as the post
                sym = Symbol(local_name+post_suffix)
                sym_table.add(sym)
                decl = DeclGen(prog, "real", [local_name+post_suffix],
                               dimension=":,:", kind="8", allocatable=True)
                prog.add(decl)
                call = CallGen(prog,
                               "{0}%ReadVariable(\"{1}{3}\", {2}{3})"
                               .format(psy_data, var_name, local_name,
                                       post_suffix))
                prog.add(call)
                decl = DeclGen(prog, "real", [local_name], kind="8",
                               dimension=":,:", allocatable=True)
                prog.add(decl)
                alloc = AllocateGen(prog, [var_name],
                                    mold="{0}".format(local_name +
                                                      post_suffix))
                prog.add(alloc)
                # Initialise the variable with 0, since it might contain
                # values that are not set at all (halo regions, or a
                # kernel might not set all values). This way the array
                # comparison with the post value works as expected
                # TODO #644 - create the right "0.0" type here (e.g.
                # 0.0d0, ...)
                assign = AssignGen(prog, local_name, "0.0d0")
                prog.add(assign)

        # Now add the region that was extracted here:
        prog.add(CommentGen(prog, ""))
        prog.add(CommentGen(prog, " RegionStart"))

        # For the driver we have to re-create the code of the
        # instrumented region, but in this stand-alone driver the
        # arguments are not dl_esm_inf fields anymore, but simple arrays.
        # Similarly, for properties we cannot use e.g. 'fld%grid%dx'
        # anymore, we have to use e.g. a local variable 'dx' that has
        # been created. Since we are using the existing way of creating
        # the code for the instrumented region, we need to modify how
        # these variables are created. We do this by temporarily
        # modifying the properties in the config file.
        api_config = Config.get().api_conf("gocean1.0")
        all_props = api_config.grid_properties
        # Keep a copy of the original values, so we can restore
        # them later
        orig_props = dict(all_props)

        # 1) A grid property is defined like "{0}%grid%dx". This is
        #    changed to be just 'dx', i.e. the final component of
        #    the current value (but we also take renaming into account,
        #    so 'dx' might become 'dx_0').
        #    If a property is not used, it doesn't matter if we modify
        #    its definition, so we just change all properties.
        for name, prop in all_props.items():
            last_percent = prop.fortran.rfind("%")
            if last_percent > -1:
                # Get the last field name, which will be the
                # local variable name
                local_name = prop.fortran[last_percent+1:]
                unique_name = rename_variable.get(local_name, local_name)
                all_props[name] = GOceanConfig.make_property(
                    unique_name, prop.type, prop.intrinsic_type)

        # 2) The property 'grid_data' is a reference to the data on the
        #    grid (i.e. the actual field) , and it is defined as "{0}%data".
        #    This just becomes {0} ('a_fld%data' in the original program
        #    becomes just 'a_fld', and 'a_fld' is declared to be a plain
        #    Fortran 2d-array)
        all_props["go_grid_data"] = GOceanConfig.make_property(
            "{0}", "array", "real")

        # Each kernel caches the argument code, so we also
        # need to clear this cached data to make sure the new
        # value for "go_grid_data" is actually used.
        from psyclone.psyGen import CodedKern
        for kernel in self.psy_data_body.walk(CodedKern):
            kernel.clear_cached_data()

        # Recreate the instrumented region. Due to the changes in the
        # config files, fields and properties will now become local
        # plain arrays and variables:
        for child in self.psy_data_body:
            child.gen_code(prog)

        # Now reset all properties back to the original values:
        for name in all_props.keys():
            all_props[name] = orig_props[name]

        prog.add(CommentGen(prog, " RegionEnd"))
        prog.add(CommentGen(prog, ""))

        for var_name in output_list:
            prog.add(CommentGen(prog, " Check {0}".format(var_name)))

        code = str(module.root)

        with open("driver-{0}-{1}.f90".
                  format(module_name, region_name), "w") as out:
            out.write(code)