def test_globalstoargumentstrans_clash_symboltable(monkeypatch): ''' Check the GlobalsToArguments transformation with a symbol name clash produces the expected error.''' trans = KernelGlobalsToArguments() # Construct a testing InvokeSchedule _, invoke_info = parse(os.path.join(BASEPATH, "gocean1p0", "single_invoke_kern_with_use.f90"), api=API) psy = PSyFactory(API).create(invoke_info) invoke = psy.invokes.invoke_list[0] kernel = invoke.schedule.coded_kernels()[0] # Monkeypatch Symbol.resolve_deferred to avoid module searching and # importing in this test. In this case we assume the symbol is a # DataSymbol of REAL type. def create_real(variable): return DataSymbol(variable.name, REAL_TYPE, interface=variable.interface) monkeypatch.setattr(Symbol, "resolve_deferred", create_real) # Add 'rdt' into the symbol table kernel.root.symbol_table.add(DataSymbol("rdt", REAL_TYPE)) # Test transforming a single kernel with pytest.raises(KeyError) as err: trans.apply(kernel) assert ("Couldn't copy 'rdt: <Scalar<REAL, UNDEFINED>, " "Global(container='model_mod')>' into the SymbolTable. The name " "'rdt' is already used by another symbol." in str(err.value))
def trans(psy): ''' Transformation routine for use with PSyclone. Applies the OpenCL transform to the first Invoke in the psy object. :param psy: the PSy object which this script will transform. :type psy: :py:class:`psyclone.psyGen.PSy` :returns: the transformed PSy object. :rtype: :py:class:`psyclone.psyGen.PSy` ''' # Get the Schedule associated with the first Invoke invoke = psy.invokes.invoke_list[0] sched = invoke.schedule # Convert any kernel accesses to global data into arguments ktrans = KernelGlobalsToArguments() for kern in sched.kernels(): ktrans.apply(kern) # Transform the Schedule cltrans = OCLTrans() cltrans.apply(sched, options={"end_barrier": True}) # Provide kernel-specific OpenCL optimization options move_boundaries_trans = GOMoveIterationBoundariesInsideKernelTrans() for kern in sched.kernels(): # Move the PSy-layer loop boundaries inside the kernel as a kernel # mask, this allows to iterate through the whole domain move_boundaries_trans.apply(kern) # Specify the OpenCL queue and workgroup size of the kernel kern.set_opencl_options({"queue_number": 1, 'local_size': 4}) return psy
def test_globalstoargumentstrans_unsupported_gocean_scalar(monkeypatch): ''' Check the GlobalsToArguments transformation when the global is a type not supported by the GOcean infrastructure raises an Error''' trans = KernelGlobalsToArguments() # Construct a testing InvokeSchedule _, invoke_info = parse(os.path.join(BASEPATH, "gocean1p0", "single_invoke_kern_with_use.f90"), api=API) psy = PSyFactory(API).create(invoke_info) invoke = psy.invokes.invoke_list[0] kernel = invoke.schedule.coded_kernels()[0] # In this case we set it to be of type CHARACTER as that is not supported # in the GOcean infrastructure. def create_data_symbol(arg): symbol = DataSymbol(arg.name, CHARACTER_TYPE, interface=arg.interface) return symbol monkeypatch.setattr(Symbol, "resolve_deferred", create_data_symbol) # Test transforming a single kernel with pytest.raises(TypeError) as err: trans.apply(kernel) assert ("The global variable 'rdt' could not be promoted to an argument " "because the GOcean infrastructure does not have any scalar type " "equivalent to the PSyIR Scalar<CHARACTER, UNDEFINED> type." in str(err.value))
def trans(psy): ''' Transformation routine for use with PSyclone. Applies the OpenCL transform to the first Invoke in the psy object. :param psy: the PSy object which this script will transform. :type psy: :py:class:`psyclone.psyGen.PSy` :returns: the transformed PSy object. :rtype: :py:class:`psyclone.psyGen.PSy` ''' from psyclone.transformations import OCLTrans, KernelGlobalsToArguments # Get the Schedule associated with the first Invoke invoke = psy.invokes.invoke_list[0] sched = invoke.schedule # Convert any kernel accesses to global data into arguments ktrans = KernelGlobalsToArguments() for kern in sched.kernels(): ktrans.apply(kern) # Transform the Schedule cltrans = OCLTrans() cltrans.apply(sched, options={"end_barrier": True}) # Provide kernel-specific OpenCL optimization options for kern in sched.kernels(): kern.set_opencl_options({"queue_number": 1, 'local_size': 4}) return psy
def test_globalstoargumentstrans_no_wildcard_import(): ''' Check that the transformation rejects kernels with wildcard imports. ''' trans = KernelGlobalsToArguments() path = os.path.join(BASEPATH, "gocean1p0") _, invoke_info = parse(os.path.join( path, "single_invoke_kern_with_unqualified_use.f90"), api=API) psy = PSyFactory(API).create(invoke_info) invoke = psy.invokes.invoke_list[0] kernel = invoke.schedule.coded_kernels()[0] with pytest.raises(TransformationError) as err: trans.apply(kernel) assert ("'kernel_with_use_code' has a wildcard import of symbols from " "container 'model_mod'" in str(err.value))
def test_globalstoargumentstrans_no_outer_module_import(): ''' Check that we reject kernels that access data that is declared in the enclosing module. ''' trans = KernelGlobalsToArguments() path = os.path.join(BASEPATH, "gocean1p0") _, invoke_info = parse(os.path.join(path, "single_invoke_kern_with_global.f90"), api=API) psy = PSyFactory(API).create(invoke_info) invoke = psy.invokes.invoke_list[0] kernel = invoke.schedule.coded_kernels()[0] with pytest.raises(TransformationError) as err: trans.apply(kernel) assert ("accesses a variable that is not declared in local scope" in str(err.value))
def test_globalstoargumentstrans_wrongapi(): ''' Check the KernelGlobalsToArguments with an API other than GOcean1p0''' trans = KernelGlobalsToArguments() path = os.path.join(BASEPATH, "dynamo0p3") _, invoke_info = parse(os.path.join(path, "1_single_invoke.f90"), api="dynamo0.3") psy = PSyFactory("dynamo0.3").create(invoke_info) invoke = psy.invokes.invoke_list[0] kernel = invoke.schedule.coded_kernels()[0] with pytest.raises(TransformationError) as err: trans.apply(kernel) assert "The KernelGlobalsToArguments transformation is currently only " \ "supported for the GOcean API but got an InvokeSchedule of " \ "type:" in str(err.value)
def test_globalstoarguments_noglobals(): ''' Check the KernelGlobalsToArguments transformation can be applied to a kernel that does not contain any global without any effect ''' # Parse a file to get an initialised GOKernelsArguments object _, invoke_info = parse(os.path.join(BASEPATH, "gocean1p0", "single_invoke.f90"), api=API) psy = PSyFactory(API).create(invoke_info) invoke = psy.invokes.invoke_list[0] kernel = invoke.schedule.coded_kernels()[0] trans = KernelGlobalsToArguments() before_code = str(psy.gen) trans.apply(kernel) after_code = str(psy.gen) assert before_code == after_code
def trans(psy): ''' Take the supplied psy object, apply OpenACC transformations to the schedule of the first invoke and return the new psy object ''' from psyclone.transformations import ACCParallelTrans, \ ACCEnterDataTrans, ACCLoopTrans, ACCRoutineTrans, \ KernelGlobalsToArguments ptrans = ACCParallelTrans() ltrans = ACCLoopTrans() dtrans = ACCEnterDataTrans() ktrans = ACCRoutineTrans() 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 from psyclone.psyir.nodes import Loop for child in schedule.children: if isinstance(child, Loop): newschedule, _ = ltrans.apply(child, {"collapse": 2}) schedule = newschedule # Put all of the loops in a single parallel region newschedule, _ = ptrans.apply(schedule.children) # Add an enter-data directive newschedule, _ = dtrans.apply(schedule) # Convert any accesses to global data into kernel arguments and then # put an 'acc routine' directive inside each kernel for kern in schedule.coded_kernels(): if kern.name == "kern_use_var_code": # TODO #490 and #663. This currently won't work because the # KernelGlobalsToArguments transformation works on the PSyIR but # the subsequent ACCRoutineTrans works on the fparser2 parse tree. g2localtrans.apply(kern) _, _ = ktrans.apply(kern) # Ideally we would module-inline the kernel here (to save having to # rely on the compiler to do it) but this does not currently work # for the fparser2 AST (issue #229). # _, _ = itrans.apply(kern) invoke.schedule = newschedule newschedule.view() return psy
def trans(psy): ''' Take the supplied psy object, apply OpenACC transformations to the schedule of the first invoke and return the new psy object ''' from psyclone.transformations import ACCParallelTrans, \ ACCEnterDataTrans, ACCLoopTrans, ACCRoutineTrans, \ KernelGlobalsToArguments, KernelModuleInlineTrans 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 from psyclone.psyir.nodes import Loop for child in schedule.children: if isinstance(child, Loop): newschedule, _ = ltrans.apply(child, {"collapse": 2}) schedule = newschedule # Put all of the loops in a single parallel region newschedule, _ = ptrans.apply(schedule.children) # Add an enter-data directive newschedule, _ = 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) invoke.schedule = newschedule newschedule.view() return psy
def test_globalstoargumentstrans_constant(monkeypatch): ''' Check the GlobalsToArguments transformation when the global is also a constant value, in this case the argument should be read-only.''' from psyclone.psyir.backend.fortran import FortranWriter from psyclone.psyir.nodes import Literal trans = KernelGlobalsToArguments() # Construct a testing InvokeSchedule _, invoke_info = parse(os.path.join(BASEPATH, "gocean1p0", "single_invoke_kern_with_use.f90"), api=API) psy = PSyFactory(API).create(invoke_info) invoke = psy.invokes.invoke_list[0] kernel = invoke.schedule.coded_kernels()[0] # Monkeypatch resolve_deferred to avoid module searching and importing # in this test. In this case we assume it is a constant INTEGER def create_data_symbol(arg): symbol = DataSymbol(arg.name, INTEGER_TYPE, interface=arg.interface, constant_value=Literal("1", INTEGER_TYPE)) return symbol monkeypatch.setattr(DataSymbol, "resolve_deferred", create_data_symbol) monkeypatch.setattr(Symbol, "resolve_deferred", create_data_symbol) # Test transforming a single kernel trans.apply(kernel) fwriter = FortranWriter() kernel_code = fwriter(kernel.get_kernel_schedule()) assert "subroutine kernel_with_use_code(ji, jj, istep, ssha, tmask, rdt)" \ in kernel_code assert "integer, intent(in) :: rdt" in kernel_code
def test_globalstoarguments_multiple_kernels(monkeypatch): ''' Check the KernelGlobalsToArguments transformation with an invoke with three kernel calls, two of them duplicated and the third one sharing the same imported module''' from psyclone.psyir.backend.fortran import FortranWriter fwriter = FortranWriter() # Construct a testing InvokeSchedule _, invoke_info = parse(os.path.join( BASEPATH, "gocean1p0", "single_invoke_three_kernels_with_use.f90"), api=API) psy = PSyFactory(API).create(invoke_info) invoke = psy.invokes.invoke_list[0] trans = KernelGlobalsToArguments() # The kernels are checked before the psy.gen, so they don't include the # modified suffix. expected = [ [ "subroutine kernel_with_use_code(ji, jj, istep, ssha, tmask, rdt)", "real, intent(inout) :: rdt" ], [ "subroutine kernel_with_use2_code(ji, jj, istep, ssha, tmask, cbfr," " rdt)", "real, intent(inout) :: cbfr\n real, intent(inout) :: rdt" ], [ "subroutine kernel_with_use_code(ji, jj, istep, ssha, tmask, rdt)", "real, intent(inout) :: rdt" ] ] # Monkeypatch the resolve_deferred() methods to avoid searching and # importing of module during this test. def create_data_symbol(arg): symbol = DataSymbol(arg.name, REAL_TYPE, interface=arg.interface) return symbol monkeypatch.setattr(Symbol, "resolve_deferred", create_data_symbol) monkeypatch.setattr(DataSymbol, "resolve_deferred", create_data_symbol) for num, kernel in enumerate(invoke.schedule.coded_kernels()): kschedule = kernel.get_kernel_schedule() trans.apply(kernel) # Check the kernel code is generated as expected kernel_code = fwriter(kschedule) assert expected[num][0] in kernel_code assert expected[num][1] in kernel_code generated_code = str(psy.gen) # The following assert checks that globals from the same module are # imported, since the kernels are marked as modified, new suffixes are # given in order to differentiate each of them. assert "SUBROUTINE invoke_0(oldu_fld, cu_fld)\n" \ " USE kernel_with_use_1_mod, ONLY: kernel_with_use_1_code\n" \ " USE kernel_with_use2_0_mod, ONLY:" \ " kernel_with_use2_0_code\n" \ " USE kernel_with_use_0_mod, ONLY:" \ " kernel_with_use_0_code\n" in generated_code # Check the kernel calls have the global passed as last argument assert "CALL kernel_with_use_0_code(i, j, oldu_fld, cu_fld%data, " \ "cu_fld%grid%tmask, rdt)" in generated_code assert "CALL kernel_with_use_1_code(i, j, oldu_fld, cu_fld%data, " \ "cu_fld%grid%tmask, rdt)" in generated_code assert "CALL kernel_with_use2_0_code(i, j, oldu_fld, cu_fld%data, " \ "cu_fld%grid%tmask, cbfr, rdt)" in generated_code
def test_globalstoargumentstrans(monkeypatch): ''' Check the GlobalsToArguments transformation with a single kernel invoke and a global variable.''' from psyclone.psyGen import Argument from psyclone.psyir.backend.fortran import FortranWriter trans = KernelGlobalsToArguments() assert trans.name == "KernelGlobalsToArguments" assert str(trans) == "Convert the global variables used inside the " \ "kernel into arguments and modify the InvokeSchedule to pass them" \ " in the kernel call." # Construct a testing InvokeSchedule _, invoke_info = parse(os.path.join(BASEPATH, "gocean1p0", "single_invoke_kern_with_use.f90"), api=API) psy = PSyFactory(API).create(invoke_info) invoke = psy.invokes.invoke_list[0] notkernel = invoke.schedule.children[0] kernel = invoke.schedule.coded_kernels()[0] # Monkeypatch resolve_deferred to avoid module searching and importing # in this test. In this case we assume it is a REAL def set_to_real(variable): variable._datatype = REAL_TYPE monkeypatch.setattr(DataSymbol, "resolve_deferred", set_to_real) # Test with invalid node with pytest.raises(TransformationError) as err: trans.apply(notkernel) assert ("The KernelGlobalsToArguments transformation can only be applied" " to CodedKern nodes but found 'GOLoop' instead." in str(err.value)) # Test transforming a single kernel trans.apply(kernel) assert kernel.modified # The transformation; # 1) Has imported the symbol into the InvokeSchedule assert invoke.schedule.symbol_table.lookup("rdt") assert invoke.schedule.symbol_table.lookup("model_mod") var = invoke.schedule.symbol_table.lookup("rdt") container = invoke.schedule.symbol_table.lookup("model_mod") assert var.is_global assert var.interface.container_symbol == container # 2) Has added the symbol as the last argument in the kernel call assert isinstance(kernel.args[-1], Argument) assert kernel.args[-1].name == "rdt" # 3) Has converted the Kernel Schedule symbol into an argument which is # in also the last position ksymbol = kernel.get_kernel_schedule().symbol_table.lookup("rdt") assert ksymbol.is_argument assert kernel.get_kernel_schedule().symbol_table.argument_list[-1] == \ ksymbol assert len(kernel.get_kernel_schedule().symbol_table.argument_list) == \ len(kernel.args) + 2 # GOcean kernels have 2 implicit arguments # Check the kernel code is generated as expected fwriter = FortranWriter() kernel_code = fwriter(kernel.get_kernel_schedule()) assert "subroutine kernel_with_use_code(ji,jj,istep,ssha,tmask,rdt)" \ in kernel_code assert "real, intent(inout) :: rdt" in kernel_code # Check that the PSy-layer generated code now contains the use statement # and argument call generated_code = str(psy.gen) assert "USE model_mod, ONLY: rdt" in generated_code assert "CALL kernel_with_use_code(i, j, oldu_fld, cu_fld%data, " \ "cu_fld%grid%tmask, rdt)" in generated_code assert invoke.schedule.symbol_table.lookup("model_mod") assert invoke.schedule.symbol_table.lookup("rdt")