Esempio n. 1
0
def test_omp_do_within_if():
    ''' Check that we can insert an OpenMP parallel do within an if block. '''
    from psyclone.transformations import OMPParallelLoopTrans
    otrans = OMPParallelLoopTrans()
    _, invoke_info = parse(os.path.join(BASE_PATH, "imperfect_nest.f90"),
                           api=API,
                           line_length=False)
    psy = PSyFactory(API, distributed_memory=False).create(invoke_info)
    schedule = psy.invokes.get('imperfect_nest').schedule
    loop = schedule[0].loop_body[1].else_body[0].else_body[0]
    assert isinstance(loop, nemo.NemoLoop)
    # Apply the transformation to a loop within an else clause
    schedule, _ = otrans.apply(loop)
    gen = str(psy.gen)
    expected = ("    ELSE\n"
                "      !$omp parallel do default(shared), private(ji,jj), "
                "schedule(static)\n"
                "      DO jj = 1, jpj, 1\n"
                "        DO ji = 1, jpi, 1\n"
                "          zdkt(ji, jj) = (ptb(ji, jj, jk - 1, jn) - "
                "ptb(ji, jj, jk, jn)) * wmask(ji, jj, jk)\n"
                "        END DO\n"
                "      END DO\n"
                "      !$omp end parallel do\n"
                "    END IF\n")
    assert expected in gen
Esempio n. 2
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))
Esempio n. 3
0
def test_loop_trans_name():
    ''' Check that the name method works as expected. '''
    # We have to use sub-classes of LoopTrans as it itself is abstract.
    trans1 = OMPParallelLoopTrans()
    assert trans1.name == "OMPParallelLoopTrans"
    trans2 = LoopFuseTrans()
    assert trans2.name == "LoopFuseTrans"
Esempio n. 4
0
def test_loop_trans_validate_nemo_specific(monkeypatch):
    ''' Test the NEMO-specifc part of the validation routine.
    TODO #435 remove this test. '''
    trans = OMPParallelLoopTrans()
    _, invoke_info = get_invoke("explicit_do.f90", api="nemo", idx=0)
    schedule = invoke_info.schedule
    loop = schedule.loops()[0]
    monkeypatch.setattr(loop, "_annotations", ["was_where"])
    with pytest.raises(TransformationError) as err:
        trans.validate(loop)
    assert ("In the NEMO API a transformation cannot be applied to a PSyIR "
            "loop representing a WHERE construct." in str(err.value))
Esempio n. 5
0
def test_parallellooptrans_refuse_codeblock():
    ''' Check that ParallelLoopTrans.validate() rejects a loop nest that
    encloses a CodeBlock. We have to use OMPParallelLoopTrans as
    ParallelLoopTrans is abstract. '''
    otrans = OMPParallelLoopTrans()
    # Construct a valid Loop in the PSyIR with a CodeBlock in its body
    parent = Loop.create(DataSymbol("ji", INTEGER_TYPE),
                         Literal("1",
                                 INTEGER_TYPE), Literal("10", INTEGER_TYPE),
                         Literal("1", INTEGER_TYPE),
                         [CodeBlock([], CodeBlock.Structure.STATEMENT, None)])
    with pytest.raises(TransformationError) as err:
        otrans.validate(parent)
    assert ("Nodes of type 'CodeBlock' cannot be enclosed "
            "by a OMPParallelLoopTrans transformation" in str(err.value))
Esempio n. 6
0
def test_omp_do_missing_parent(monkeypatch):
    ''' Check that we raise the expected error when we cannot find the
    parent node in the fparser2 AST. '''
    from psyclone.transformations import OMPParallelLoopTrans
    otrans = OMPParallelLoopTrans()
    _, invoke_info = parse(os.path.join(BASE_PATH, "imperfect_nest.f90"),
                           api=API, line_length=False)
    psy = PSyFactory(API, distributed_memory=False).create(invoke_info)
    schedule = psy.invokes.get('imperfect_nest').schedule
    schedule, _ = otrans.apply(schedule.children[0])
    # Remove the reference to the fparser2 AST from the Schedule node
    monkeypatch.setattr(schedule, "_ast", None)
    with pytest.raises(InternalError) as err:
        _ = psy.gen
    assert ("Failed to find parent node in which to insert OpenMP parallel "
            "do directive" in str(err))
Esempio n. 7
0
def test_omp_do_children_err():
    ''' Tests that we raise the expected error when an OpenMP parallel do
    directive has more than one child. '''
    from psyclone.transformations import OMPParallelLoopTrans
    from psyclone.psyGen import OMPParallelDoDirective
    otrans = OMPParallelLoopTrans()
    psy, invoke_info = get_invoke("imperfect_nest.f90", api=API, idx=0)
    schedule = invoke_info.schedule
    otrans.apply(schedule[0].loop_body[2])
    directive = schedule[0].loop_body[2]
    assert isinstance(directive, OMPParallelDoDirective)
    # Make the schedule invalid by adding a second child to the
    # OMPParallelDoDirective
    directive.dir_body.children.append(Statement())
    with pytest.raises(GenerationError) as err:
        _ = psy.gen
    assert ("An OpenMP PARALLEL DO can only be applied to a single loop but "
            "this Node has 2 children:" in str(err.value))
Esempio n. 8
0
def test_loop_trans_validate_options(monkeypatch):
    ''' Test the options argument to the validate method. '''
    trans = OMPParallelLoopTrans()
    _, invoke = get_invoke("test27_loop_swap.f90", "gocean1.0", idx=1,
                           dist_mem=False)
    loop = invoke.schedule.walk(Loop)[0]
    with pytest.raises(TransformationError) as err:
        trans.validate(loop, options="hello")
    assert ("method 'options' argument must be a dictionary but found 'str'"
            in str(err.value))
    # Monkeypatch the transformation to make it appear that we wish to
    # exclude CodedKern nodes.
    monkeypatch.setattr(trans, "excluded_node_types", (CodedKern, ))
    with pytest.raises(TransformationError) as err:
        trans.validate(loop)
    assert ("Nodes of type 'GOKern' cannot be enclosed by a "
            "OMPParallelLoopTrans transformation" in str(err.value))
    # Now disable this check on excluded node types
    trans.validate(loop, options={"node-type-check": False})
Esempio n. 9
0
def test_omp_do_children_err():
    ''' Tests that we raise the expected error when an OpenMP parallel do
    directive has more than one child. '''
    from psyclone.transformations import OMPParallelLoopTrans
    from psyclone.psyGen import OMPParallelDoDirective
    otrans = OMPParallelLoopTrans()
    _, invoke_info = parse(os.path.join(BASE_PATH, "imperfect_nest.f90"),
                           api=API, line_length=False)
    psy = PSyFactory(API, distributed_memory=False).create(invoke_info)
    schedule = psy.invokes.get('imperfect_nest').schedule
    new_sched, _ = otrans.apply(schedule.children[0].children[2])
    directive = new_sched.children[0].children[2]
    assert isinstance(directive, OMPParallelDoDirective)
    # Make the schedule invalid by adding a second child to the
    # OMPParallelDoDirective
    directive.children.append(new_sched.children[0].children[3])
    with pytest.raises(GenerationError) as err:
        _ = psy.gen
    assert ("An OpenMP PARALLEL DO can only be applied to a single loop but "
            "this Node has 2 children:" in str(err))
Esempio n. 10
0
def test_invalid_apply():
    '''Test the exceptions that should be raised by ReadOnlyVerifyTrans.

    '''
    _, invoke = get_invoke("test11_different_iterates_over_one_invoke.f90",
                           "gocean1.0",
                           idx=0)
    read_only = ReadOnlyVerifyTrans()
    omp = OMPParallelLoopTrans()
    _, _ = omp.apply(invoke.schedule[0])
    with pytest.raises(TransformationError) as err:
        _, _ = read_only.apply(invoke.schedule[0].dir_body[0],
                               options={"region_name": ("a", "b")})
    assert "Error in ReadOnlyVerifyTrans: Application to a Loop without its "\
           "parent Directive is not allowed." in str(err.value)

    with pytest.raises(TransformationError) as err:
        _, _ = read_only.apply(invoke.schedule[0].dir_body[0].loop_body[0],
                               options={"region_name": ("a", "b")})
    assert "Error in ReadOnlyVerifyTrans: Application to Nodes enclosed " \
           "within a thread-parallel region is not allowed." in str(err.value)
Esempio n. 11
0
def test_loop_trans_validate():
    ''' Test the validation checks on the loop node provided to the
    transformation. '''
    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)
    # Break the loop
    loop.children = loop.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))
Esempio n. 12
0
def test_omp_do_within_if():
    ''' Check that we can insert an OpenMP parallel do within an if block. '''
    from psyclone.transformations import OMPParallelLoopTrans
    otrans = OMPParallelLoopTrans()
    psy, invoke_info = get_invoke("imperfect_nest.f90", api=API, idx=0)
    schedule = invoke_info.schedule
    loop = schedule[0].loop_body[1].else_body[0].else_body[0]
    assert isinstance(loop, nemo.NemoLoop)
    # Apply the transformation to a loop within an else clause
    otrans.apply(loop)
    gen = str(psy.gen).lower()
    expected = ("    else\n"
                "      !$omp parallel do default(shared), private(ji,jj), "
                "schedule(static)\n"
                "      do jj = 1, jpj, 1\n"
                "        do ji = 1, jpi, 1\n"
                "          zdkt(ji, jj) = (ptb(ji, jj, jk - 1, jn) - "
                "ptb(ji, jj, jk, jn)) * wmask(ji, jj, jk)\n"
                "        end do\n"
                "      end do\n"
                "      !$omp end parallel do\n"
                "    end if\n")
    assert expected in gen
Esempio n. 13
0
def test_profile_nemo_openmp(parser):
    ''' Check that the automatic kernel-level profiling handles a
    tightly-nested loop that has been parallelised using OpenMP. '''
    omptrans = OMPParallelLoopTrans()
    Profiler.set_options([Profiler.KERNELS])
    psy, schedule = get_nemo_schedule(
        parser, "program do_loop\n"
        "integer, parameter :: jpi=5, jpj=5\n"
        "integer :: ji, jj\n"
        "real :: sto_tmp(jpi,jpj)\n"
        "do jj = 1, jpj\n"
        "  do ji = 1,jpi\n"
        "    sto_tmp(ji,jj) = 1.0d0\n"
        "  end do\n"
        "end do\n"
        "end program do_loop\n")
    omptrans.apply(schedule[0])
    Profiler.add_profile_nodes(schedule, Loop)
    code = str(psy.gen).lower()
    assert ("  type(profile_psydatatype), target, save :: profile_psy_data0\n"
            "  call profile_psy_data0 % prestart('do_loop', 'r0', 0, 0)\n"
            "  !$omp parallel do default(shared), private(ji,jj), "
            "schedule(static)\n"
            "  do jj = 1, jpj" in code)
Once you have PSyclone installed, this script may be used by doing:

 >>> psyclone -api "nemo" -s ./omp_trans.py my_file.F90

This should produce a lot of output, ending with generated
Fortran.

'''
from psyclone.psyir.nodes import Loop
from psyclone.psyGen import Directive
from psyclone.transformations import OMPParallelLoopTrans, OMPLoopTrans, \
    OMPParallelTrans, TransformationError

# Get the transformation we will apply
OMP_TRANS = OMPParallelLoopTrans()
OMP_LOOP_TRANS = OMPLoopTrans()
OMP_PARALLEL_TRANS = OMPParallelTrans()


def trans(psy):
    ''' Transform a specific Schedule by making all loops
    over vertical levels OpenMP parallel.

    :param psy: the object holding all information on the PSy layer \
                to be modified.
    :type psy: :py:class:`psyclone.psyGen.PSy`

    :returns: the transformed PSy object
    :rtype:  :py:class:`psyclone.psyGen.PSy`