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))
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))
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])
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))
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
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))
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))
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
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))
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` '''