Example #1
0
def test_omploop_no_collapse():
    ''' Check that the OMPLoopTrans.directive() method rejects the
    collapse argument '''
    trans = OMPLoopTrans()
    cnode = Node()
    with pytest.raises(NotImplementedError) as err:
        _ = trans._directive(cnode, collapse=2)
    assert ("The COLLAPSE clause is not yet supported for '!$omp do' "
            "directives" in str(err.value))
Example #2
0
def test_omploop_no_collapse():
    ''' Check that the OMPLoopTrans.directive() method rejects the
    collapse argument '''
    from psyclone.psyGen import Node
    from psyclone.transformations import OMPLoopTrans
    trans = OMPLoopTrans()
    pnode = Node()
    cnode = Node()
    with pytest.raises(NotImplementedError) as err:
        _ = trans._directive(pnode, cnode, collapse=2)
    assert ("The COLLAPSE clause is not yet supported for '!$omp do' "
            "directives" in str(err))
Example #3
0
def test_nemo_omp_do(fortran_reader):
    '''Tests if an OpenMP do directive in NEMO is handled correctly.
    '''
    # Generate fparser2 parse tree from Fortran code.
    code = '''
        module test
        contains
        subroutine tmp()
          integer :: i, a
          integer, dimension(:) :: b
          do i = 1, 20, 2
            a = 2 * i
            b(i) = b(i) + a
          enddo
        end subroutine tmp
        end module test'''
    psyir = fortran_reader.psyir_from_source(code)
    schedule = psyir.children[0].children[0]

    # Now apply a parallel transform
    omp_loop = OMPLoopTrans()
    omp_loop.apply(schedule[0])
    # By default the visitor should raise an exception because the loop
    # directive is not inside a parallel region
    fvisitor_with_checks = FortranWriter()
    with pytest.raises(GenerationError) as err:
        fvisitor_with_checks(schedule)
    assert ("OMPDoDirective must be inside an OMP parallel region but could "
            "not find an ancestor OMPParallelDirective" in str(err.value))
    # Disable checks on global constraints to remove need for parallel region
    fvisitor = FortranWriter(check_global_constraints=False)
    result = fvisitor(schedule)
    correct = '''  !$omp do schedule(static)
  do i = 1, 20, 2
    a = 2 * i
    b(i) = b(i) + a
  enddo
  !$omp end do'''
    assert correct in result

    cvisitor = CWriter(check_global_constraints=False)
    result = cvisitor(schedule[0])
    correct = '''#pragma omp do schedule(static)
{
  for(i=1; i<=20; i+=2)
  {
    a = (2 * i);
    b[i] = (b[i] + a);
  }
}'''
    assert correct in result
def test_omp_loop_applied_to_non_loop():
    ''' Test that we raise a TransformationError if we attempt
    to apply an OMP DO transformation to something that
    is not a loop '''
    _, invoke = get_invoke("single_invoke_three_kernels.f90", 0)
    schedule = invoke.schedule

    from psyclone.transformations import OMPLoopTrans
    ompl = OMPLoopTrans()
    omp_schedule, _ = ompl.apply(schedule.children[0])

    # Attempt to (erroneously) apply the OMP Loop transformation
    # to the first node in the schedule (which is now itself an
    # OMP Loop transformation)
    with pytest.raises(TransformationError):
        _, _ = ompl.apply(omp_schedule.children[0])
Example #5
0
def test_omp_do_update():
    '''Check the OMPDoDirective update function.'''
    from psyclone.transformations import OMPLoopTrans, OMPParallelTrans
    from psyclone.psyGen import OMPDoDirective
    _, 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
    par_trans = OMPParallelTrans()
    loop_trans = OMPLoopTrans()
    new_sched, _ = par_trans.apply(
        schedule[0].loop_body[1].else_body[0].else_body[0])
    new_sched, _ = loop_trans.apply(
        new_sched[0].loop_body[1].else_body[0].else_body[0].children[0])
    gen_code = str(psy.gen).lower()
    correct = '''      !$omp parallel default(shared), private(ji,jj)
      !$omp do schedule(static)
      do jj = 1, jpj, 1
        do ji = 1, jpi, 1
          zdkt(ji, jj) = (ptb(ji, jj, jk - 1, jn) - ptb(ji, jj, jk, jn)) * \
wmask(ji, jj, jk)
        end do
      end do
      !$omp end do
      !$omp end parallel'''
    assert correct in gen_code
    directive = new_sched[0].loop_body[1].else_body[0].else_body[0]\
        .children[0]
    assert isinstance(directive, OMPDoDirective)

    # Call update a second time and make sure that this does not
    # trigger the whole update process again, and we get the same ast
    old_ast = directive.ast
    directive.update()
    assert directive.ast is old_ast

    # Remove the existing AST, so we can do more tests:
    directive.ast = None
    # Make the schedule invalid by adding a second child to the
    # OMPParallelDoDirective
    directive.children.append(new_sched[0].loop_body[3])

    with pytest.raises(GenerationError) as err:
        _ = directive.update()
    assert ("An OpenMP DO can only be applied to a single loop but "
            "this Node has 2 children:" in str(err))
Example #6
0
def test_nemo_omp_do():
    '''Tests if an OpenMP do directive in NEMO is handled correctly.
    '''
    # Generate fparser2 parse tree from Fortran code.
    code = '''
        module test
        contains
        subroutine tmp()
          integer :: i, a
          integer, dimension(:) :: b
          do i = 1, 20, 2
            a = 2 * i
            b(i) = b(i) + a
          enddo
        end subroutine tmp
        end module test'''
    schedule = create_schedule(code, "tmp")
    from psyclone.transformations import OMPLoopTrans

    # Now apply a parallel transform
    omp_loop = OMPLoopTrans()
    omp_loop.apply(schedule[0])

    fvisitor = FortranWriter()
    result = fvisitor(schedule)
    correct = '''!$omp do schedule(static)
    do i = 1, 20, 2
      a=2 * i
      b(i)=b(i) + a
    enddo
!$omp end do'''
    assert correct in result

    cvisitor = CWriter()
    result = cvisitor(schedule[0])
    correct = '''#pragma omp do schedule(static)
{
  for(i=1; i<=20; i+=2)
  {
    a = (2 * i);
    b[i] = (b[i] + a);
  }
}'''
    assert correct in result
Example #7
0
def test_omp_do_update():
    '''Check the OMPDoDirective update function.'''
    psy, invoke = get_invoke("imperfect_nest.f90", api=API, idx=0)
    schedule = invoke.schedule
    par_trans = OMPParallelTrans()
    loop_trans = OMPLoopTrans()
    new_sched, _ = par_trans.apply(schedule[0].loop_body[1]
                                   .else_body[0].else_body[0])
    new_sched, _ = loop_trans.apply(new_sched[0].loop_body[1]
                                    .else_body[0].else_body[0].dir_body[0])
    gen_code = str(psy.gen).lower()
    correct = '''      !$omp parallel default(shared), private(ji,jj)
      !$omp do schedule(static)
      do jj = 1, jpj, 1
        do ji = 1, jpi, 1
          zdkt(ji, jj) = (ptb(ji, jj, jk - 1, jn) - ptb(ji, jj, jk, jn)) * \
wmask(ji, jj, jk)
        end do
      end do
      !$omp end do
      !$omp end parallel'''
    assert correct in gen_code
    directive = new_sched[0].loop_body[1].else_body[0].else_body[0]\
        .dir_body[0]
    assert isinstance(directive, OMPDoDirective)

    # Call update a second time and make sure that this does not
    # trigger the whole update process again, and we get the same ast
    old_ast = directive.ast
    directive.update()
    assert directive.ast is old_ast

    # Remove the existing AST, so we can do more tests:
    directive.ast = None
    # Make the schedule invalid by adding a second child to the
    # OMPParallelDoDirective
    directive.dir_body.children.append(new_sched[0].loop_body[3])

    with pytest.raises(GenerationError) as err:
        _ = directive.update()
    assert ("An OpenMP DO can only be applied to a single loop but "
            "this Node has 2 children:" in str(err.value))
Example #8
0
def test_omp_do_missing_region(parser):
    ''' Check that the correct error is raised if an OMPDoDirective is
    found outside an OMP parallel region at code-generation time. '''
    reader = FortranStringReader("program do_loop\n"
                                 "integer :: ji\n"
                                 "integer, parameter :: jpj=32\n"
                                 "real :: sto_tmp(jpj)\n"
                                 "do ji = 1,jpj\n"
                                 "  sto_tmp(ji) = 1.0d0\n"
                                 "end do\n"
                                 "end program do_loop\n")
    code = parser(reader)
    psy = PSyFactory(API, distributed_memory=False).create(code)
    schedule = psy.invokes.invoke_list[0].schedule
    loop_trans = OMPLoopTrans()
    loop_trans.apply(schedule[0])
    with pytest.raises(GenerationError) as err:
        str(psy.gen)
    assert ("OMPDoDirective must be inside an OMP parallel region but could "
            "not find an ancestor OMPParallelDirective" in str(err.value))
Example #9
0
def test_gocean_omp_do():
    '''Test that an OMP DO directive in a 'classical' API (gocean here)
    is created correctly.
    '''

    from psyclone.transformations import OMPLoopTrans

    _, invoke = get_invoke("single_invoke.f90",
                           "gocean1.0",
                           idx=0,
                           dist_mem=False)
    omp = OMPLoopTrans()
    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.
    # While this is invalid usage of OMP (omp must have a loop,
    # not an assignment inside), it is necessary because GOLoops
    # are not supported yet, and it is sufficient to test that the
    # visitor pattern creates correct OMP DO directives.
    # TODO #440 fixes this.
    replace_child_with_assignment(omp_sched[0].dir_body)
    fvisitor = FortranWriter()
    # GOInvokeSchedule is not yet supported, so start with
    # the OMP node:
    result = fvisitor(omp_sched[0])
    correct = '''!$omp do schedule(static)
  a = b
!$omp end do'''
    assert correct in result

    cvisitor = CWriter()
    # Remove newlines for easier RE matching
    result = cvisitor(omp_sched[0])
    correct = '''#pragma omp do schedule(static)
{
  a = b;
}'''
    assert correct in result
Example #10
0
def test_omp_do_update_error():
    '''Check if the OMPDoDirective update function raises exception as
    expected.'''
    from psyclone.transformations import OMPLoopTrans
    from psyclone.psyGen import OMPDoDirective
    _, 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_trans = OMPLoopTrans()
    new_sched, _ = loop_trans.apply(
        schedule.children[0].loop_body[1].else_body[0].else_body[0])
    directive = new_sched[0].loop_body[1].else_body[0].else_body[0]
    assert isinstance(directive, OMPDoDirective)

    # Note that the ast does NOT have a parent property defined!
    # pylint: disable=protected-access
    directive.parent.ast._parent = None
    with pytest.raises(InternalError) as err:
        directive.update()
    assert ("Failed to find parent node in which to "
            "insert OpenMP parallel do directive" in str(err))
Example #11
0
def test_gocean_omp_do():
    '''Test that an OMP DO directive in a 'classical' API (gocean here)
    is created correctly.

    '''
    _, invoke = get_invoke("single_invoke.f90",
                           "gocean1.0",
                           idx=0,
                           dist_mem=False)
    omp = OMPLoopTrans()
    _, _ = 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.
    # While this is invalid usage of OMP (omp must have a loop,
    # not an assignment inside), it is necessary because GOLoops
    # are not supported yet, and it is sufficient to test that the
    # visitor pattern creates correct OMP DO directives.
    # TODO #440 fixes this.
    replace_child_with_assignment(invoke.schedule[0].dir_body)
    # Disable validation checks to avoid having to add a parallel region
    fvisitor = FortranWriter(check_global_constraints=False)
    # GOInvokeSchedule is not yet supported, so start with
    # the OMP node:
    result = fvisitor(invoke.schedule[0])
    correct = '''!$omp do schedule(static)
a = b
!$omp end do'''
    assert correct in result

    cvisitor = CWriter(check_global_constraints=False)
    result = cvisitor(invoke.schedule[0])
    correct = '''#pragma omp do schedule(static)
{
  a = b;
}'''
    assert correct in result
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`

    '''