Beispiel #1
0
def test_all_nemo_loop_trans_base_validate(monkeypatch):
    ''' Check that all transformations that sub-class LoopTrans call the
    base validate() method. '''
    # First get a valid Loop object that we can pass in.
    _, invoke = get_invoke("explicit_over_implicit.f90", api="nemo", idx=0)
    loop = invoke.schedule.walk(Loop)[0]

    # Get all transformations for the NEMO domain
    transmod = import_module("psyclone.domain.nemo.transformations")
    all_trans_classes = inspect.getmembers(transmod, inspect.isclass)

    # To ensure that we identify that the validate() method in the LoopTrans
    # base class has been called, we monkeypatch it to raise an exception.

    def fake_validate(_1, _2, options=None):
        raise NotImplementedError("validate test exception")
    monkeypatch.setattr(LoopTrans, "validate", fake_validate)

    for name, cls_type in all_trans_classes:
        trans = cls_type()
        if isinstance(trans, LoopTrans):
            # The Loop fuse validation function requires two
            # parameters (the two loops to fuse), so it needs
            # to be tested separately:
            if isinstance(trans, NemoLoopFuseTrans):
                with pytest.raises(NotImplementedError) as err:
                    trans.validate(loop, node2=loop)
            else:
                with pytest.raises(NotImplementedError) as err:
                    trans.validate(loop)
            assert "validate test exception" in str(err.value), \
                "{0}.validate() does not call LoopTrans.validate()".format(
                    name)
Beispiel #2
0
def test_invoke_function():
    ''' Check that we successfully construct an Invoke if the program
    unit is a function. '''
    psy, _ = get_invoke("afunction.f90", api=API, idx=0)
    assert len(psy.invokes.invoke_list) == 1
    invoke = psy.invokes.invoke_list[0]
    assert invoke.name == "afunction"
Beispiel #3
0
def test_gocean_omp_parallel():
    '''Test that an OMP PARALLEL directive in a 'classical' API (gocean here)
    is created correctly.
    '''

    from psyclone.transformations import OMPParallelTrans

    _, invoke = get_invoke("single_invoke.f90", "gocean1.0", idx=0)

    omp = OMPParallelTrans()
    omp_sched, _ = omp.apply(invoke.schedule[0])

    # Now remove the GOKern (since it's not yet supported in the
    # visitor pattern) and replace it with a simple assignment
    # TODO: #440 tracks this
    replace_child_with_assignment(omp_sched[0])

    # omp_sched is a GOInvokeSchedule, which is not yet supported.
    # So only convert starting from the OMPParallelDirective
    fvisitor = FortranWriter()
    result = fvisitor(omp_sched[0])
    correct = '''!$omp parallel
  a=b
!$omp end parallel'''
    assert correct in result

    cvisitor = CWriter()
    # Remove newlines for easier RE matching
    result = cvisitor(omp_sched[0])
    correct = '''#pragma omp parallel
{
  a = b;
}'''
    result = cvisitor(omp_sched[0])
    assert correct in result
def test_apply_existing_names():
    '''Check that the apply method uses existing iterators appropriately
    when their symbols are already defined.

    '''
    _, invoke_info = get_invoke("implicit_do.f90", api=API, idx=0)
    schedule = invoke_info.schedule
    assignment = schedule[0]
    array_ref = assignment.lhs
    trans = NemoArrayRange2LoopTrans()
    symbol_table = schedule.symbol_table
    symbol_table.add(DataSymbol("ji", INTEGER_TYPE))
    symbol_table.add(DataSymbol("jj", INTEGER_TYPE))
    symbol_table.add(DataSymbol("jk", INTEGER_TYPE))

    trans.apply(array_ref.children[2])
    trans.apply(array_ref.children[1])
    trans.apply(array_ref.children[0])

    writer = FortranWriter()
    result = writer(schedule)
    assert (
        "do jk = 1, jpk, 1\n"
        "  do jj = 1, jpj, 1\n"
        "    do ji = 1, jpi, 1\n"
        "      umask(ji,jj,jk) = 0.0e0\n"
        "    enddo\n"
        "  enddo\n"
        "enddo" in result)
Beispiel #5
0
def test_omp_private_declaration():
    ''' Check code generation and private/shared declaration when
    an assignment is parallelised. In this case the code is like:
    !$omp parallel default(shared), private()
    jpk = 100
    do k=1, jpk ...
    enddo
    !$omp end parallel
    do k=1, jpk ...

    In this case jpk should not be declared private, since then it
    is not defined in the next loop.'''

    psy, invoke_info = get_invoke("explicit_do_two_loops.f90", api=API, idx=0)
    schedule = invoke_info.schedule
    omp_parallel = TransInfo().get_trans_name('OMPParallelTrans')

    # Apply "omp parallel" around one assignment to a scalar variable
    # and a loop using this variable as loop boundary. Parallelising an
    # assignment statement is not allowed by default, so we need to disable
    # the node type check in order to apply the omp parallel transform.
    omp_parallel.apply(schedule.children[0:2], {'node-type-check': False})
    expected = "!$omp parallel default(shared), private(ji,jj,jk)"

    gen_code = str(psy.gen).lower()
    assert expected in gen_code
Beispiel #6
0
def test_2kern_trans(kernel_outputdir):
    ''' Check that we generate correct code when we transform two kernels
    within a single invoke. '''
    psy, invoke = get_invoke("4.5.2_multikernel_invokes.f90",
                             api="dynamo0.3",
                             idx=0)
    sched = invoke.schedule
    kernels = sched.walk(Kern)
    assert len(kernels) == 5
    ktrans = Dynamo0p3KernelConstTrans()
    _, _ = ktrans.apply(kernels[1], {"number_of_layers": 100})
    _, _ = ktrans.apply(kernels[2], {"number_of_layers": 100})
    # Generate the code (this triggers the generation of new kernels)
    code = str(psy.gen).lower()
    # Find the tags added to the kernel/module names
    for match in re.finditer('use testkern_any_space_2(.+?)_mod', code):
        tag = match.group(1)
        assert ("use testkern_any_space_2{0}_mod, only: "
                "testkern_any_space_2{0}_code".format(tag) in code)
        assert "call testkern_any_space_2{0}_code(".format(tag) in code
        filepath = os.path.join(str(kernel_outputdir),
                                "testkern_any_space_2{0}_mod.f90".format(tag))
        assert os.path.isfile(filepath)
        assert "nlayers = 100" in open(filepath).read()
    assert "use testkern_any_space_2_mod, only" not in code
    assert "call testkern_any_space_2_code(" not in code
    assert LFRicBuild(kernel_outputdir).code_compiles(psy)
Beispiel #7
0
def test_accroutine_err(monkeypatch):
    ''' Check that we raise the expected error if we can't find the
    source of the kernel subroutine. '''
    import fparser
    _, invoke = get_invoke("1_single_invoke.f90", api="dynamo0.3", idx=0)
    sched = invoke.schedule
    kernels = sched.coded_kernels()
    kern = kernels[0]
    assert isinstance(kern, Kern)
    # Edit the fparser1 AST of the kernel so that it does not have a
    # subroutine of the correct name
    ast = kern._module_code
    mod = ast.content[0]
    # Find the subroutine statement
    for child in mod.content:
        if isinstance(child, fparser.one.block_statements.Subroutine):
            sub = child
    # Find the end subroutine statement
    for child in sub.content:
        if isinstance(child, fparser.one.block_statements.EndSubroutine):
            end = child
    monkeypatch.setattr(sub, "name", "some_other_name")
    monkeypatch.setattr(end, "name", "some_other_name")
    rtrans = ACCRoutineTrans()
    with pytest.raises(TransformationError) as err:
        _ = rtrans.apply(kern)
    assert ("Failed to create PSyIR version of kernel code for kernel "
            "'testkern_code'. Error reported is Generation Error: Unexpected "
            "kernel AST. Could not find subroutine: testkern_code."
            in str(err.value))
Beispiel #8
0
def test_change_prefix(tmpdir, monkeypatch):
    '''
    This tests that the prefix of a gocean extract transformation
    can be changed, and that the new prefix is also used in the
    created driver.
    '''
    # Use tmpdir so that the driver is created in tmp
    tmpdir.chdir()

    psy, invoke = get_invoke("single_invoke_scalar_float_arg.f90",
                             GOCEAN_API,
                             idx=0,
                             dist_mem=False)

    # In order to use a different prefix, this prefix needs to be valid.
    # So monkeypatch the valid prefix names in the config object:
    config = Config.get()
    monkeypatch.setattr(config, "_valid_psy_data_prefixes", ["NEW"])

    etrans = GOceanExtractTrans()
    etrans.apply(invoke.schedule.children[0], {
        'create_driver': True,
        'region_name': ("main", "update"),
        'prefix': "NEW"
    })

    # Test that the extraction code contains the new prefix:
    assert 'CALL NEW_psy_data%PreStart("main", "update", 4, 3)' \
        in str(psy.gen)

    # Now test if the created driver has the right prefix:
    driver_name = tmpdir.join("driver-main-update.f90")
    with open(str(driver_name), "r") as driver_file:
        driver_code = driver_file.read()
    assert 'CALL NEW_psy_data%OpenRead("main", "update")' in driver_code
Beispiel #9
0
def test_goloop_field_accesses():
    ''' Check that for a GOcean kernel appropriate field accesses (based
    on the meta data) are added to the dependency analysis.

    '''
    _, invoke = get_invoke("large_stencil.f90",
                           "gocean1.0",
                           name="invoke_large_stencil",
                           dist_mem=False)
    do_loop = invoke.schedule.children[0]

    assert isinstance(do_loop, Loop)
    var_accesses = VariablesAccessInfo(invoke.schedule)

    # cu_fld has a pointwise write access in the first loop:
    cu_fld = var_accesses["cu_fld"]
    assert len(cu_fld.all_accesses) == 1
    assert cu_fld.all_accesses[0].access_type == AccessType.WRITE
    assert cu_fld.all_accesses[0].indices == ["i", "j"]

    # The stencil is defined to be GO_STENCIL(123,110,100)) for
    # p_fld. Make sure that these 9 accesses are indeed reported:
    p_fld = var_accesses["p_fld"]
    all_indices = [access.indices for access in p_fld.all_accesses]

    for test_index in [["i-1", "j+1"], ["i", "j+1"], ["i", "j+2"],
                       ["i+1", "j+1"], ["i+2", "j+2"], ["i+3", "j+3"],
                       ["i-1", "j"], ["i", "j"], ["i-1", "j-1"]]:
        assert test_index in all_indices

    # Since we have 9 different indices found (above), the following
    # test guarantees that we don't get any invalid accesses reported.
    assert len(p_fld.all_accesses) == 9
Beispiel #10
0
def test_driver_generation_flag(tmpdir, create_driver):
    '''Test that driver generation can be enabled and disabled, and
    that it is disabled by default. If create_driver is None, the
    default behaviour (don't create driver) is tested.

    '''
    # Use tmpdir so that the driver is created in tmp
    tmpdir.chdir()

    etrans = GOceanExtractTrans()
    psy, invoke = get_invoke("driver_test.f90",
                             GOCEAN_API,
                             idx=0,
                             dist_mem=False)
    schedule = invoke.schedule

    if create_driver is None:
        schedule, _ = etrans.apply(schedule.children[0:2])
    else:
        schedule, _ = etrans.apply(schedule.children[0:2],
                                   {'create_driver': create_driver})
    # We are only interested in the potentially triggered driver-creation.
    str(psy.gen)

    driver = tmpdir.join("driver-psy_extract_example_with_various_variable_"
                         "access_patterns-invoke_0_compute_kernel:compute_"
                         "kernel_code:r0.f90")
    # When create_driver is None, as a default no driver should be created.
    # Since "None or False" is "False", this simple test can be used in all
    # three cases.
    assert driver.isfile() == (create_driver or False)
Beispiel #11
0
def test_rename_region(tmpdir):
    '''
    This tests that an extract region can be renamed, and that the created
    driver will use the new names.
    '''
    # Use tmpdir so that the driver is created in tmp
    tmpdir.chdir()

    etrans = GOceanExtractTrans()
    psy, invoke = get_invoke("single_invoke_scalar_float_arg.f90",
                             GOCEAN_API,
                             idx=0,
                             dist_mem=False)

    etrans.apply(invoke.schedule.children[0], {
        'create_driver': True,
        'region_name': ("main", "update")
    })

    # Test that the extraction code contains the right names
    assert 'CALL extract_psy_data%PreStart("main", "update", 4, 3)' \
        in str(psy.gen)

    # Now test if the created driver has the right name, and will open the
    # right file:
    driver_name = tmpdir.join("driver-main-update.f90")
    with open(str(driver_name), "r") as driver_file:
        driver_code = driver_file.read()
    assert 'CALL extract_psy_data%OpenRead("main", "update")' in driver_code
Beispiel #12
0
def test_extract_node_position():
    ''' Test that Extract Transformation inserts the ExtractNode
    at the position of the first node in the Node list
    marked for extraction. '''

    # Test GOcean1.0 API for extraction of a single Node
    gocetrans = GOceanExtractTrans()
    _, invoke = get_invoke("single_invoke_three_kernels.f90",
                           GOCEAN_API,
                           idx=0,
                           dist_mem=False)
    schedule = invoke.schedule
    # Apply Extract transformation to the second Node and assert that
    # position and the absolute position of the ExtractNode are the same as
    # respective positions of the second Node before the transformation.
    pos = 1
    child = schedule.children[pos]
    abspos = child.abs_position
    dpth = child.depth
    schedule, _ = gocetrans.apply(child)
    extract_node = schedule.walk(ExtractNode)
    # The result is only one ExtractNode in the list with position 1
    assert extract_node[0].position == pos
    assert extract_node[0].abs_position == abspos
    assert extract_node[0].depth == dpth
    assert extract_node[0].dag_name == "gocean_extract_1"
Beispiel #13
0
def test_no_parent_accdirective():
    ''' Test that applying Extract Transformation on an orphaned
    ACCLoopDirective without its ancestor ACCParallelDirective
    when optimisations are applied raises a TransformationError. '''

    etrans = GOceanExtractTrans()
    acclpt = ACCLoopTrans()
    accpara = ACCParallelTrans()
    accdata = ACCEnterDataTrans()

    _, invoke = get_invoke("single_invoke_three_kernels.f90",
                           GOCEAN_API,
                           idx=0,
                           dist_mem=False)
    schedule = invoke.schedule

    # Apply the OpenACC Loop transformation to every loop in the Schedule
    for child in schedule.children:
        if isinstance(child, Loop):
            schedule, _ = acclpt.apply(child)
    # Enclose all of these loops within a single ACC Parallel region
    schedule, _ = accpara.apply(schedule.children)
    # Add a mandatory ACC enter-data directive
    schedule, _ = accdata.apply(schedule)

    orphaned_directive = schedule.children[1].children[0]
    with pytest.raises(TransformationError) as excinfo:
        _, _ = etrans.apply(orphaned_directive)
    assert "Error in GOceanExtractTrans: Application to Nodes enclosed " \
           "within a thread-parallel region is not allowed." \
           in str(excinfo.value)
def test_openmp_region():
    ''' Test the application of an OpenMP parallel region transformation
    to a single loop '''
    psy, invoke = get_invoke("algorithm/1_single_function.f90",
                             TEST_API,
                             name="invoke_0_testkern_type")
    schedule = invoke.schedule
    rtrans = OMPParallelTrans()
    rtrans.apply(schedule.children[0])
    gen = str(psy.gen)

    # Check that our list of private variables is correct
    assert "!$omp parallel default(shared), private(cell,map)" in gen

    for idx, line in enumerate(gen.split('\n')):
        if "!$omp parallel default(shared)" in line:
            startpara_idx = idx
        if "DO cell=1,f1%get_ncell()" in line:
            do_idx = idx
        if "CALL f1%vspace%get_cell_dofmap(cell, map)" in line:
            dmap_idx = idx
        if "CALL testkern_code(nlayers, ndf, map, f1%data, "\
           "f2%data, m1%data)" in line:
            kcall_idx = idx
        if "END DO" in line:
            enddo_idx = idx
        if "!$omp end parallel" in line:
            endpara_idx = idx
    assert do_idx == startpara_idx + 1
    assert dmap_idx == do_idx + 1
    assert kcall_idx == dmap_idx + 1
    assert enddo_idx == kcall_idx + 1
    assert endpara_idx == enddo_idx + 1
Beispiel #15
0
def test_new_kern_single_error(kernel_outputdir, monkeypatch):
    ''' Check that we do not overwrite an existing, different kernel if
    there is a name clash and kernel-naming is 'single'. '''
    # Ensure kernel-output directory is uninitialised
    config = Config.get()
    monkeypatch.setattr(config, "_kernel_naming", "single")
    _, invoke = get_invoke("1_single_invoke.f90", api="dynamo0.3", idx=0)
    sched = invoke.schedule
    kernels = sched.coded_kernels()
    kern = kernels[0]
    old_mod_name = kern.module_name[:].lower()
    if old_mod_name.endswith("_mod"):
        old_mod_name = old_mod_name[:-4]
    # Create a file with the same name as we would otherwise generate
    with open(os.path.join(str(kernel_outputdir), old_mod_name + "_0_mod.f90"),
              "w") as ffile:
        ffile.write("some code")
    rtrans = ACCRoutineTrans()
    new_kern, _ = rtrans.apply(kern)
    # Generate the code - this should raise an error as we get a name
    # clash and the content of the existing file is not the same as that
    # which we would generate
    with pytest.raises(GenerationError) as err:
        new_kern.rename_and_write()
    assert ("transformed version of this Kernel 'testkern_0_mod.f90' already "
            "exists in the kernel-output directory ({0}) but is not the same "
            "as the current, transformed kernel and the kernel-renaming "
            "scheme is set to 'single'".format(str(kernel_outputdir))
            in str(err.value))
Beispiel #16
0
def test_lfric_acc():
    '''Check variable usage detection when OpenACC is used.

    '''
    # Use the OpenACC transforms to enclose the kernels
    # with OpenACC directives.
    acc_par_trans = ACCParallelTrans()
    acc_enter_trans = ACCEnterDataTrans()
    _, invoke = get_invoke("1_single_invoke.f90",
                           "dynamo0.3",
                           name="invoke_0_testkern_type",
                           dist_mem=False)
    sched = invoke.schedule
    _ = acc_par_trans.apply(sched.children)
    _ = acc_enter_trans.apply(sched)

    # Find the first kernel:
    kern = invoke.schedule.walk(CodedKern)[0]
    create_acc_arg_list = KernCallAccArgList(kern)
    var_accesses = VariablesAccessInfo()
    create_acc_arg_list.generate(var_accesses=var_accesses)
    var_info = str(var_accesses)
    assert "f1: READ+WRITE" in var_info
    assert "f2: READ" in var_info
    assert "m1: READ" in var_info
    assert "m2: READ" in var_info
    assert "undf_w1: READ" in var_info
    assert "map_w1: READ" in var_info
    assert "undf_w2: READ" in var_info
    assert "map_w2: READ" in var_info
    assert "undf_w3: READ" in var_info
    assert "map_w3: READ" in var_info
Beispiel #17
0
def test_1kern_trans(kernel_outputdir):
    ''' Check that we generate the correct code when an invoke contains
    the same kernel more than once but only one of them is transformed. '''
    psy, invoke = get_invoke("4_multikernel_invokes.f90",
                             api="dynamo0.3",
                             idx=0)
    sched = invoke.schedule
    kernels = sched.coded_kernels()
    # We will transform the second kernel but not the first
    kern = kernels[1]
    rtrans = ACCRoutineTrans()
    _, _ = rtrans.apply(kern)
    # Generate the code (this triggers the generation of a new kernel)
    code = str(psy.gen).lower()
    tag = re.search('use testkern(.+?)_mod', code).group(1)
    # We should have a USE for the original kernel and a USE for the new one
    assert "use testkern{0}_mod, only: testkern{0}_code".format(tag) in code
    assert "use testkern_mod, only: testkern_code" in code
    # Similarly, we should have calls to both the original and new kernels
    assert "call testkern_code(" in code
    assert "call testkern{0}_code(".format(tag) in code
    first = code.find("call testkern_code(")
    second = code.find("call testkern{0}_code(".format(tag))
    assert first < second
    assert LFRicBuild(kernel_outputdir).code_compiles(psy)
Beispiel #18
0
def test_lfric_acc_operator():
    '''Check variable usage detection when OpenACC is used with
    a kernel that uses an operator.

    '''
    # Use the OpenACC transforms to enclose the kernels
    # with OpenACC directives.
    acc_par_trans = ACCParallelTrans()
    acc_enter_trans = ACCEnterDataTrans()
    _, invoke = get_invoke("20.0_cma_assembly.f90",
                           "dynamo0.3",
                           idx=0,
                           dist_mem=False)
    sched = invoke.schedule
    _ = acc_par_trans.apply(sched.children)
    _ = acc_enter_trans.apply(sched)

    # Find the first kernel:
    kern = invoke.schedule.walk(CodedKern)[0]
    create_acc_arg_list = KernCallAccArgList(kern)
    var_accesses = VariablesAccessInfo()
    create_acc_arg_list.generate(var_accesses=var_accesses)
    var_info = str(var_accesses)
    assert "lma_op1_proxy%ncell_3d: READ" in var_info
    assert "lma_op1_proxy%local_stencil: WRITE" in var_info
Beispiel #19
0
def test_kernel_trans_validate(monkeypatch):
    '''Check that the validate method in the class KernelTrans raises an
    exception if the reference is not found in any of the symbol
    tables. KernelTrans can't be instantiated as it is abstract so use
    a the subclass.

    '''
    from psyclone.transformations import KernelModuleInlineTrans
    kernel_trans = KernelModuleInlineTrans()
    _, invoke = get_invoke("single_invoke_kern_with_global.f90",
                           api="gocean1.0",
                           idx=0)
    sched = invoke.schedule
    kernels = sched.walk(Kern)
    kernel = kernels[0]

    def dummy_func():
        '''Simple Dummy function that raises SymbolError.'''
        from psyclone.psyir.symbols import SymbolError
        raise SymbolError("error")

    monkeypatch.setattr(kernel, "get_kernel_schedule", dummy_func)
    with pytest.raises(TransformationError) as err:
        _, _ = kernel_trans.apply(kernel)
    assert ("'kernel_with_global_code' contains accesses to data that are "
            "not captured in the PSyIR Symbol Table(s) (error)."
            "" in str(err.value))
Beispiel #20
0
def test_lfric_stencil():
    '''Check variable usage detection when OpenACC is used with a
    kernel that uses a stencil.

    '''
    # Use the OpenACC transforms to create the required kernels
    acc_par_trans = ACCParallelTrans()
    acc_enter_trans = ACCEnterDataTrans()
    _, invoke = get_invoke("14.4_halo_vector.f90",
                           "dynamo0.3",
                           idx=0,
                           dist_mem=False)
    sched = invoke.schedule
    _ = acc_par_trans.apply(sched.children)
    _ = acc_enter_trans.apply(sched)

    # Find the first kernel:
    kern = invoke.schedule.walk(CodedKern)[0]
    create_acc_arg_list = KernCallAccArgList(kern)
    var_accesses = VariablesAccessInfo()
    create_acc_arg_list.generate(var_accesses=var_accesses)
    var_info = str(var_accesses)
    assert "f1: READ+WRITE" in var_info
    assert "f2: READ" in var_info
    assert "f2_stencil_dofmap: READ" in var_info
def test_apply_reference_literal():
    '''Check that the apply method add bounds appropriately when the
    config file specifies a lower bound as a reference and an upper
    bound as a literal.

    '''
    _, invoke_info = get_invoke("implicit_many_dims.f90", api=API, idx=0)
    # Create a new config instance and load a test config file with
    # the bounds information set the way we want.
    config = Config.get(do_not_load_file=True)
    config.load(config_file=TEST_CONFIG)
    schedule = invoke_info.schedule
    assignment = schedule[0]
    array_ref = assignment.lhs
    trans = NemoArrayRange2LoopTrans()
    for index in range(4, -1, -1):
        range_node = array_ref.children[index]
        trans.apply(range_node)
    # Remove this config file so the next time the default one will be
    # loaded (in case we affect other tests)
    Config._instance = None
    writer = FortranWriter()
    result = writer(schedule)
    assert (
        "do idx = LBOUND(umask, 5), UBOUND(umask, 5), 1\n"
        "  do jt = 1, UBOUND(umask, 4), 1\n"
        "    do jk = jpk, 1, 1\n"
        "      do jj = 1, jpj, 1\n"
        "        do ji = jpi, 1, 1\n"
        "          umask(ji,jj,jk,jt,idx) = vmask(ji,jj,jk,jt,idx) + 1.0\n"
        "        enddo\n"
        "      enddo\n"
        "    enddo\n"
        "  enddo\n"
        "enddo" in result)
Beispiel #22
0
def test_transform_apply_mixed_implicit_do():
    '''Check that the PSyIR is transformed as expected for a lat,lon,levs
    loop with some of its indices accessed using array notation and
    some using explicit loops.  The resultant Fortran code is used to
    confirm the transformation has worked correctly.

    '''
    _, invoke_info = get_invoke("explicit_over_implicit.f90", api=API, idx=0)
    schedule = invoke_info.schedule
    assignment = schedule[0].loop_body[0]
    trans = NemoOuterArrayRange2LoopTrans()
    # Apply transformation 2 times, once for each implicit
    # dimension
    trans.apply(assignment)
    writer = FortranWriter()
    result = writer(schedule)
    expected = (
        "do jk = 1, jpk, 1\n"
        "  do jj = 1, jpj, 1\n"
        "    umask(:,jj,jk) = vmask(:,jj,jk) + 1.0\n"
        "  enddo\n"
        "enddo")
    assert expected in result
    trans.apply(assignment)
    result = writer(schedule)
    expected = (
        "do jk = 1, jpk, 1\n"
        "  do jj = 1, jpj, 1\n"
        "    do ji = 1, jpi, 1\n"
        "      umask(ji,jj,jk) = vmask(ji,jj,jk) + 1.0\n"
        "    enddo\n"
        "  enddo\n"
        "enddo")
    assert expected in result
Beispiel #23
0
def test_omp_explicit_gen():
    ''' Check code generation for a single explicit loop containing
    a kernel. '''
    psy, invoke_info = get_invoke("explicit_do.f90", api=API, idx=0)
    schedule = invoke_info.schedule
    omp_trans = TransInfo().get_trans_name('OMPParallelLoopTrans')

    for loop in schedule.loops():
        kernel = loop.kernel
        if kernel and loop.loop_type == "levels":
            schedule, _ = omp_trans.apply(loop)
    gen_code = str(psy.gen).lower()

    expected = (
        "program explicit_do\n"
        "  implicit none\n"
        "  integer :: ji, jj, jk\n"
        "  integer :: jpi, jpj, jpk\n"
        "  real :: r\n"
        "  real, dimension(jpi, jpj, jpk) :: umask\n"
        "  !$omp parallel do default(shared), private(ji,jj,jk), "
        "schedule(static)\n"
        "  do jk = 1, jpk\n"
        "    do jj = 1, jpj\n"
        "      do ji = 1, jpi\n"
        "        umask(ji, jj, jk) = ji * jj * jk / r\n"
        "      end do\n"
        "    end do\n"
        "  end do\n"
        "  !$omp end parallel do\n"
        "end program explicit_do")
    assert expected in gen_code
    # Check that calling gen a second time gives the same code
    gen_code = str(psy.gen).lower()
    assert expected in gen_code
Beispiel #24
0
def test_goloop_grid_property_psyir_expression():
    ''' Tests for the _grid_property_psyir_expression() method. '''
    _, invoke = get_invoke("test11_different_iterates_over_one_invoke.f90",
                           API,
                           idx=0)
    schedule = invoke.schedule
    loop = schedule.walk(GOLoop)[0]
    # A simple name should result in a new symbol and a suitable reference
    assert "hello" not in schedule.symbol_table
    href = loop._grid_property_psyir_expression("hello")
    hsym = schedule.symbol_table.lookup("hello")
    assert isinstance(hsym, DataSymbol)
    assert href.parent is loop
    assert hsym.datatype == INTEGER_TYPE
    assert isinstance(href, Reference)
    # A derived-type reference must be in the form of a format string with
    # "{0}" at the start.
    with pytest.raises(NotImplementedError) as err:
        loop._grid_property_psyir_expression("wrong%one")
    assert ("Supplied grid property is a derived-type reference but does "
            "not begin with '{0}': 'wrong%one'" in str(err.value))
    gref = loop._grid_property_psyir_expression("{0}%grid%xstart")
    assert isinstance(gref, StructureReference)
    assert gref.parent is None
    assert gref.symbol.name == "cv_fld"
Beispiel #25
0
def test_apply_multi_assignments():
    '''Check that the apply() method can be called for multiple assigments
    with and without ranges.

    '''
    _, invoke_info = get_invoke("array_syntax.f90", api=API, idx=0)
    schedule = invoke_info.schedule
    trans = NemoAllArrayRange2LoopTrans()
    for assignment in schedule.walk(Assignment):
        trans.apply(assignment)
    writer = FortranWriter()
    result = writer(schedule)
    expected = ("do jk = 1, jpk, 1\n"
                "  do jj = 1, jpj, 1\n"
                "    do ji = 1, jpi, 1\n"
                "      zftv(ji,jj,jk) = 0.0e0\n"
                "    enddo\n"
                "  enddo\n"
                "enddo\n"
                "if (l_ptr) then\n"
                "  call dia_ptr_hst(jn, ''ldf'', -zftv(:,:,:))\n"
                "end if\n"
                "call dia_ptr_hst(jn, ''ldf'', -zftv(:,:,:))\n"
                "do jj = 1, jpj, 1\n"
                "  do ji = 1, jpi, 1\n"
                "    zftu(ji,jj,1) = 1.0e0\n"
                "  enddo\n"
                "enddo\n"
                "do jj = 1, jpj, 1\n"
                "  do ji = 1, jpi, 1\n"
                "    tmask(ji,jj) = jpi\n"
                "  enddo\n"
                "enddo\n")
    assert expected in result
Beispiel #26
0
def test_lower_to_lang_level_multi_node():
    ''' Test the lower_to_language_level() method when a Schedule contains
    multiple ProfileNodes.

    '''
    # We use a GOcean example containing multiple kernel calls
    Profiler.set_options([Profiler.KERNELS])
    _, invoke = get_invoke("single_invoke_two_kernels.f90", "gocean1.0", idx=0)
    sched = invoke.schedule
    table = sched.symbol_table
    Profiler.add_profile_nodes(sched, Loop)
    sched.lower_to_language_level()
    sym0 = table.lookup("profile_psy_data")
    assert isinstance(sym0, DataSymbol)
    sym1 = table.lookup("profile_psy_data_1")
    assert isinstance(sym1, DataSymbol)
    cblocks = sched.walk(CodeBlock)
    ptree = cblocks[0].get_ast_nodes
    code = str(ptree[0]).lower()
    assert "call profile_psy_data % prestart('invoke_0', 'r0'" in code
    assert cblocks[0].annotations == ["profile-start"]
    assert cblocks[1].annotations == []
    ptree = cblocks[2].get_ast_nodes
    code = str(ptree[0]).lower()
    assert "call profile_psy_data_1 % prestart('invoke_0', 'r1'" in code
    assert cblocks[2].annotations == ["profile-start"]
    assert cblocks[3].annotations == []
Beispiel #27
0
def test_loop_trans_validate(monkeypatch):
    ''' Test the validation checks on the loop node provided to the
    transformation. '''
    # We have to use sub-class of LoopTrans as it itself is abstract.
    trans = OMPParallelLoopTrans()
    _, invoke = get_invoke("test27_loop_swap.f90", "gocean1.0", idx=1,
                           dist_mem=False)
    with pytest.raises(TransformationError) as err:
        trans.validate(invoke.schedule)
    assert ("Target of OMPParallelLoopTrans transformation must be a sub-"
            "class of Loop but got 'GOInvokeSchedule'" in str(err.value))
    # Check that validate is OK with a valid loop
    loop = invoke.schedule.walk(Loop)[0]
    trans.validate(loop)
    # Pretend that the loop is of 'null' type
    monkeypatch.setattr(loop, "_loop_type", "null")
    with pytest.raises(TransformationError) as err:
        trans.validate(loop)
    assert ("Cannot apply a OMPParallelLoopTrans transformation to a "
            "'null' loop" in str(err.value))
    monkeypatch.undo()
    # Break the contents of the loop
    loop.children = loop.pop_all_children()[0:1]
    with pytest.raises(TransformationError) as err:
        trans.validate(loop)
    assert ("Error in OMPParallelLoopTrans transformation. The target loop "
            "must have four children but found: ['Literal']" in
            str(err.value))
Beispiel #28
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)
Beispiel #29
0
def test_region():
    ''' Tests that the profiling transform works correctly when a region of
    code is specified that does not cover the full invoke and also
    contains multiple kernels.

    '''
    _, invoke = get_invoke("3.1_multi_functions_multi_invokes.f90",
                           "dynamo0.3",
                           name="invoke_0",
                           dist_mem=True)
    schedule = invoke.schedule
    prt = ProfileTrans()
    # Just halo exchanges.
    _ = prt.apply(schedule[0:4])
    # Two loops.
    _ = prt.apply(schedule[1:3])
    result = str(invoke.gen())
    assert ("CALL profile_psy_data%PreStart(\"multi_functions_multi_invokes_"
            "psy\", \"invoke_0:r0\", 0, 0)" in result)
    assert ("CALL profile_psy_data_1%PreStart(\"multi_functions_multi_"
            "invokes_psy\", \"invoke_0:r1\", 0, 0)" in result)
    # Make nested profiles.
    _ = prt.apply(schedule[1].profile_body[1])
    _ = prt.apply(schedule)
    result = str(invoke.gen())
    assert ("CALL profile_psy_data_3%PreStart(\"multi_functions_multi_"
            "invokes_psy\", \"invoke_0:r0\", 0, 0)" in result)
    assert ("CALL profile_psy_data%PreStart(\"multi_functions_multi_"
            "invokes_psy\", \"invoke_0:r1\", 0, 0)" in result)
    assert ("CALL profile_psy_data_1%PreStart(\"multi_functions_multi_"
            "invokes_psy\", \"invoke_0:r2\", 0, 0)" in result)
    assert ("CALL profile_psy_data_2%PreStart(\"multi_functions_multi_"
            "invokes_psy\", \"invoke_0:testkern_code:r3\", 0, 0)" in result)
def test_opencl_kernel_output_file(kernel_outputdir):
    '''Check that an OpenCL file named modulename_kernelname_0 is generated.
    '''
    psy, _ = get_invoke("single_invoke.f90", API, idx=0)
    sched = psy.invokes.invoke_list[0].schedule
    # Create dummy boundary symbols for the "name" kernel
    sched.symbol_table.new_symbol("a",
                                  tag="xstart_name",
                                  symbol_type=DataSymbol,
                                  datatype=INTEGER_TYPE)
    sched.symbol_table.new_symbol("b",
                                  tag="xstop_name",
                                  symbol_type=DataSymbol,
                                  datatype=INTEGER_TYPE)
    sched.symbol_table.new_symbol("c",
                                  tag="ystart_name",
                                  symbol_type=DataSymbol,
                                  datatype=INTEGER_TYPE)
    sched.symbol_table.new_symbol("d",
                                  tag="ystop_name",
                                  symbol_type=DataSymbol,
                                  datatype=INTEGER_TYPE)

    otrans = OCLTrans()
    otrans.apply(sched)
    sched.kernels()[0].name = "name"
    _ = psy.gen  # Generates the OpenCL kernels as a side-effect.

    assert os.path.exists(
        os.path.join(str(kernel_outputdir), "compute_cu_name_0.cl"))