def inline(): ''' function exercising the module-inline transformation ''' from psyclone.parse.algorithm import parse from psyclone.psyGen import PSyFactory import os from psyclone.transformations import KernelModuleInlineTrans _, info = parse(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "..", "..", "src", "psyclone", "tests", "test_files", "dynamo0p1", "algorithm", "1_single_function.f90"), api="dynamo0.1") psy = PSyFactory("dynamo0.1").create(info) invokes = psy.invokes print(psy.invokes.names) invoke = invokes.get("invoke_0_testkern_type") schedule = invoke.schedule schedule.view() kern = schedule.children[0].loop_body[0] # setting module inline directly kern.module_inline = True schedule.view() # unsetting module inline via a transformation trans = KernelModuleInlineTrans() schedule, _ = trans.apply(kern, {"inline": False}) schedule.view() # setting module inline via a transformation schedule, _ = trans.apply(kern) schedule.view() print(str(psy.gen))
def test_no_inline_before_trans(monkeypatch, tmpdir): ''' Check that we reject attempts to transform kernels that have been marked for module in-lining. Issue #229. ''' # Even though this code will raise an exception at the end, it will # still create an (empty) file 'testkern_any_space_2_0_mod.f90'. # So change to tmpdir to avoid this. old_pwd = tmpdir.chdir() from psyclone.transformations import KernelModuleInlineTrans 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 inline_trans = KernelModuleInlineTrans() rtrans = ACCRoutineTrans() _, _ = inline_trans.apply(kernels[1]) with pytest.raises(TransformationError) as err: _, _ = rtrans.apply(kernels[1]) assert "because it will be module-inlined" in str(err) # Monkeypatch the validate() routine so we can check that we catch # the error at code-generation time monkeypatch.setattr(rtrans, "validate", lambda kern: None) _, _ = rtrans.apply(kernels[1]) with pytest.raises(NotImplementedError) as err: _ = str(psy.gen).lower() assert "Cannot module-inline a transformed kernel " in str(err) old_pwd.chdir()
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))
def test_transformation_inline_error_if_not_kernel(): ''' Test that the inline transformation fails if the object being passed is not a kernel''' _, invoke = get_invoke("single_invoke_three_kernels.f90", 0) schedule = invoke.schedule kern_call = schedule.children[0].children[0] inline_trans = KernelModuleInlineTrans() with pytest.raises(TransformationError): _, _ = inline_trans.apply(kern_call)
def test_module_inline_warning_no_change(): ''' test of the warning clause in the Kernel transformation when no change is made to the inlining of a Kernel i.e. the inlining request is already what is happening. No warning is currently made as we have not added logging to the code but this test covers the clause ''' _, invoke = get_invoke("test14_module_inline_same_kernel.f90", 0) schedule = invoke.schedule kern_call = schedule.children[0].children[0].children[0] inline_trans = KernelModuleInlineTrans() _, _ = inline_trans.apply(kern_call, inline=False)
def test_module_inline_with_transformation(): ''' Test that we can succesfully inline a basic kernel subroutine routine into the PSy layer module using a transformation ''' psy, invoke = get_invoke("single_invoke_three_kernels.f90", 0) schedule = invoke.schedule kern_call = schedule.children[1].children[0].children[0] inline_trans = KernelModuleInlineTrans() schedule, _ = inline_trans.apply(kern_call) gen = str(psy.gen) # check that the subroutine has been inlined assert 'SUBROUTINE compute_cv_code(i, j, cv, p, v)' in gen # check that the associated use no longer exists assert 'USE compute_cv_mod, ONLY: compute_cv_code' not in gen
def test_no_inline_after_trans(monkeypatch): ''' Check that we reject attempts to inline a previously transformed kernel. Issue #229. ''' from psyclone.transformations import KernelModuleInlineTrans _, 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 # Transform the kernel first inline_trans = KernelModuleInlineTrans() rtrans = ACCRoutineTrans() _, _ = rtrans.apply(kernels[1]) # Then attempt to inline it with pytest.raises(TransformationError) as err: _, _ = inline_trans.apply(kernels[1]) assert "because it has previously been transformed" in str(err) # Monkeypatch the validate() routine so we can check that we catch # the error at the psyGen level too. monkeypatch.setattr(inline_trans, "validate", lambda node, inline: None) with pytest.raises(NotImplementedError) as err: _, _ = inline_trans.apply(kernels[1]) assert "Cannot module-inline a transformed kernel " in str(err)
def test_module_inline_with_sub_use(): ''' Test that we can module inline a kernel subroutine which contains a use statement''' psy, invoke = get_invoke("single_invoke_scalar_int_arg.f90", 0) schedule = invoke.schedule kern_call = schedule.children[0].children[0].children[0] inline_trans = KernelModuleInlineTrans() schedule, _ = inline_trans.apply(kern_call) gen = str(psy.gen) # check that the subroutine has been inlined assert 'SUBROUTINE bc_ssh_code(ji, jj, istep, ssha, tmask)' in gen # check that the use within the subroutine exists assert 'USE model_mod, ONLY: rdt' in gen # check that the associated psy use does not exist assert 'USE bc_ssh_mod, ONLY: bc_ssh_code' not in gen
def test_no_inline_global_var(): ''' Check that we refuse to in-line a kernel that accesses a global variable. ''' from psyclone.transformations import KernelModuleInlineTrans inline_trans = KernelModuleInlineTrans() _, invoke = get_invoke("single_invoke_kern_with_global.f90", api="gocean1.0", idx=0) sched = invoke.schedule kernels = sched.walk(Kern) with pytest.raises(TransformationError) as err: _, _ = inline_trans.apply(kernels[0]) assert ("'kernel_with_global_code' contains accesses to data (variable " "'alpha') that are not captured in the PSyIR Symbol Table(s) " "within KernelSchedule scope." in str(err.value))
def trans(psy): ''' Take the supplied psy object, apply OpenACC transformations to the schedule of the first invoke and return the new psy object ''' ptrans = ACCParallelTrans() ltrans = ACCLoopTrans() dtrans = ACCEnterDataTrans() ktrans = ACCRoutineTrans() itrans = KernelModuleInlineTrans() g2localtrans = KernelGlobalsToArguments() invoke = psy.invokes.invoke_list[0] schedule = invoke.schedule schedule.view() # Apply the OpenACC Loop transformation to *every* loop # nest in the schedule for child in schedule.children: if isinstance(child, Loop): ltrans.apply(child, {"collapse": 2}) # Put all of the loops in a single parallel region ptrans.apply(schedule.children) # Add an enter-data directive dtrans.apply(schedule) # Convert any accesses to global data into kernel arguments, put an # 'acc routine' directive inside, and module-inline each kernel for kern in schedule.coded_kernels(): if kern.name == "kern_use_var_code": g2localtrans.apply(kern) ktrans.apply(kern) itrans.apply(kern) schedule.view() return psy
def test_module_inline_same_kernel(): '''Tests that correct results are obtained when an invoke that uses the same kernel subroutine more than once has that kernel inlined''' psy, invoke = get_invoke("test14_module_inline_same_kernel.f90", 0) schedule = invoke.schedule kern_call = schedule.children[0].children[0].children[0] inline_trans = KernelModuleInlineTrans() _, _ = inline_trans.apply(kern_call) gen = str(psy.gen) # check that the subroutine has been inlined assert 'SUBROUTINE time_smooth_code(' in gen # check that the associated psy "use" does not exist assert 'USE time_smooth_mod, ONLY: time_smooth_code' not in gen # check that the subroutine has only been inlined once count = count_lines(psy.gen, "SUBROUTINE time_smooth_code(") assert count == 1, "Expecting subroutine to be inlined once"
def test_module_no_inline_with_transformation(): ''' Test that we can switch off the inlining of a kernel routine into the PSy layer module using a transformation. Relies on the test_module_inline() test being successful to be a valid test. ''' psy, invoke = get_invoke("single_invoke_three_kernels.f90", 0) schedule = invoke.schedule kern_call = schedule.children[0].children[0].children[0] # directly switch on inlining kern_call.module_inline = True inline_trans = KernelModuleInlineTrans() # use a transformation to switch inlining off again schedule, _ = inline_trans.apply(kern_call, inline=False) gen = str(psy.gen) # check that the subroutine has not been inlined assert 'SUBROUTINE compute_cu_code(i, j, cu, p, u)' not in gen # check that the associated use exists (as this is removed when # inlining) assert 'USE compute_cu_mod, ONLY: compute_cu_code' in gen
def test_no_inline_before_trans(monkeypatch): ''' Check that we reject attempts to transform kernels that have been marked for module in-lining. Issue #229. ''' from psyclone.transformations import KernelModuleInlineTrans psy, invoke = get_invoke("4.5.2_multikernel_invokes.f90", api="dynamo0.3", idx=0) sched = invoke.schedule kernels = sched.walk(sched.children, Kern) assert len(kernels) == 5 inline_trans = KernelModuleInlineTrans() rtrans = ACCRoutineTrans() _, _ = inline_trans.apply(kernels[1]) with pytest.raises(TransformationError) as err: _, _ = rtrans.apply(kernels[1]) assert "because it will be module-inlined" in str(err) # Monkeypatch the validate() routine so we can check that we catch # the error at code-generation time monkeypatch.setattr(rtrans, "validate", lambda kern: None) _, _ = rtrans.apply(kernels[1]) with pytest.raises(NotImplementedError) as err: _ = str(psy.gen).lower() assert "Cannot module-inline a transformed kernel " in str(err)
def trans(psy): '''Transformation entry point''' config = Config.get() schedule = psy.invokes.get('invoke_0').schedule loop_trans = OMPTaskloopTrans(grainsize=32, nogroup=True) wait_trans = OMPTaskwaitTrans() module_inline_trans = KernelModuleInlineTrans() # Inline all kernels in this Schedule for kernel in schedule.kernels(): module_inline_trans.apply(kernel) for child in schedule.children: if isinstance(child, Loop): loop_trans.apply(child) single_trans = OMPSingleTrans() parallel_trans = OMPParallelTrans() sets = [] if not config.distributed_memory: single_trans.apply(schedule.children) parallel_trans.apply(schedule.children) wait_trans.apply(schedule.children[0]) return # Find all of the groupings of taskloop and taskwait directives. Each of # these groups needs its own parallel+single regions. This makes sure we # don't apply OpenMP transformations to the Halo Exchange operations. next_start = 0 next_end = 0 idx = 0 for idx, child in enumerate(schedule.children): # Loop through through the schedule until we find a non-OpenMP # node, and extend the grouping until we do. if isinstance(child, (OMPTaskloopDirective, OMPTaskwaitDirective)): next_end = next_end + 1 elif not isinstance(child, OMPDirective): # If we find a non OpenMP directive, if we're currently in a # grouping of OpenMP directives then we stop, and add it to # the set of groupings. Otherwise we just skip over this # node. if next_start == idx: next_end = idx + 1 next_start = idx + 1 else: sets.append((next_start, next_end)) next_end = idx + 1 next_start = idx + 1 else: next_end = next_end + 1 # If currently in a grouping of directives, add it to the list # of groupings if next_start <= idx: sets.append((next_start, idx + 1)) # Start from the last grouping to keep indexing correct, # so reverse the ordering sets.reverse() # For each of the groupings of OpenMP directives, surround them # with an OpenMP Single and an OpenMP Parallel directive set. for next_set in sets: single_trans.apply(schedule[next_set[0]:next_set[1]]) parallel_trans.apply(schedule[next_set[0]]) # Finally, we loop over the OMPParallelDirectives, and apply the # OMPTaskWaitTrans to ensure correctness. for child in schedule.children: if isinstance(child, OMPParallelDirective): wait_trans.apply(child)