From bb0a42ad4cd0cdea95769291048654eaf8d4b419 Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Thu, 24 May 2018 11:25:30 +0100 Subject: [PATCH 01/60] #174 add opencl option and generate use statements in psy module --- src/psyclone/generator.py | 8 ++- src/psyclone/gocean1p0.py | 7 ++- src/psyclone/psyGen.py | 29 +++++---- src/psyclone/tests/gocean1p0_opencl_test.py | 65 +++++++++++++++++++++ 4 files changed, 94 insertions(+), 15 deletions(-) create mode 100644 src/psyclone/tests/gocean1p0_opencl_test.py diff --git a/src/psyclone/generator.py b/src/psyclone/generator.py index a7507dbfe6..b13a429250 100644 --- a/src/psyclone/generator.py +++ b/src/psyclone/generator.py @@ -106,7 +106,8 @@ def handle_script(script_name, psy): def generate(filename, api="", kernel_path="", script_name=None, line_length=False, - distributed_memory=DISTRIBUTED_MEMORY): + distributed_memory=DISTRIBUTED_MEMORY, + opencl=True): # pylint: disable=too-many-arguments '''Takes a GungHo algorithm specification as input and outputs the associated generated algorithm and psy codes suitable for @@ -133,6 +134,7 @@ def generate(filename, api="", kernel_path="", script_name=None, :param bool distributed_memory: A logical flag specifying whether to generate distributed memory code. The default is set in the config.py file. + :param bool opencl: Whether or not to generate OpenCL for the PSy layer :return: The algorithm code and the psy code. :rtype: ast :raises IOError: if the filename or search path do not exist @@ -165,8 +167,8 @@ def generate(filename, api="", kernel_path="", script_name=None, ast, invoke_info = parse(filename, api=api, invoke_name="invoke", kernel_path=kernel_path, line_length=line_length) - psy = PSyFactory(api, distributed_memory=distributed_memory).\ - create(invoke_info) + psy = PSyFactory(api, distributed_memory=distributed_memory, + opencl=opencl).create(invoke_info) if script_name is not None: handle_script(script_name, psy) alg = Alg(ast, psy) diff --git a/src/psyclone/gocean1p0.py b/src/psyclone/gocean1p0.py index f1911ba9dc..0ea5921f60 100644 --- a/src/psyclone/gocean1p0.py +++ b/src/psyclone/gocean1p0.py @@ -101,8 +101,8 @@ class GOPSy(PSy): invokes object (which controls all the required invocation calls). Also overrides the PSy gen method so that we generate GOcean- specific PSy module code. ''' - def __init__(self, invoke_info): - PSy.__init__(self, invoke_info) + def __init__(self, invoke_info, opencl=False): + PSy.__init__(self, invoke_info, opencl) self._invokes = GOInvokes(invoke_info.calls) @property @@ -121,6 +121,9 @@ def gen(self): psy_module.add(UseGen(psy_module, name="kind_params_mod")) # include the field_mod module psy_module.add(UseGen(psy_module, name="field_mod")) + if self._opencl: + psy_module.add(UseGen(psy_module, name="iso_c_binding")) + psy_module.add(UseGen(psy_module, name="clfortran")) # add in the subroutines for each invocation self.invokes.gen_code(psy_module) # inline kernels where requested diff --git a/src/psyclone/psyGen.py b/src/psyclone/psyGen.py index 4cb9b34c9f..b47e0b0c3e 100644 --- a/src/psyclone/psyGen.py +++ b/src/psyclone/psyGen.py @@ -193,20 +193,27 @@ def __str__(self): class PSyFactory(object): - '''Creates a specific version of the PSy. If a particular api is not - provided then the default api, as specified in the config.py - file, is chosen. Note, for pytest to work we need to set - distributed_memory to the same default as the value found in - config.DISTRIBUTED_MEMORY. If we set it to None and then test - the value, it then fails. I've no idea why. ''' - - def __init__(self, api="", distributed_memory=config.DISTRIBUTED_MEMORY): + ''' + Creates a specific version of the PSy. If a particular api is not + provided then the default api, as specified in the config.py + file, is chosen. Note, for pytest to work we need to set + distributed_memory to the same default as the value found in + config.DISTRIBUTED_MEMORY. If we set it to None and then test + the value, it then fails. I've no idea why. + + :param bool opencl: #TODO + ''' + + def __init__(self, api="", + distributed_memory=config.DISTRIBUTED_MEMORY, + opencl=False): if distributed_memory not in [True, False]: raise GenerationError( "The distributed_memory flag in PSyFactory must be set to" " 'True' or 'False'") config.DISTRIBUTED_MEMORY = distributed_memory self._type = get_api(api) + self._opencl = opencl def create(self, invoke_info): ''' Return the specified version of PSy. ''' @@ -224,7 +231,7 @@ def create(self, invoke_info): return GOPSy(invoke_info) elif self._type == "gocean1.0": from psyclone.gocean1p0 import GOPSy - return GOPSy(invoke_info) + return GOPSy(invoke_info, opencl=self._opencl) else: raise GenerationError("PSyFactory: Internal Error: Unsupported " "api type '{0}' found. Should not be " @@ -242,6 +249,7 @@ class PSy(object): invocation information for code optimisation and generation. Produced by the function :func:`parse.parse`. + :param bool opencl: #TODO For example: @@ -254,10 +262,11 @@ class PSy(object): >>> print(psy.gen) ''' - def __init__(self, invoke_info): + def __init__(self, invoke_info, opencl): self._name = invoke_info.name self._invokes = None + self._opencl = opencl def __str__(self): return "PSy" diff --git a/src/psyclone/tests/gocean1p0_opencl_test.py b/src/psyclone/tests/gocean1p0_opencl_test.py new file mode 100644 index 0000000000..0898ea0bf1 --- /dev/null +++ b/src/psyclone/tests/gocean1p0_opencl_test.py @@ -0,0 +1,65 @@ +# ----------------------------------------------------------------------------- +# BSD 3-Clause License +# +# Copyright (c) 2018, Science and Technology Facilities Council +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# * Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# ---------------------------------------------------------------------------- +# Author A. R. Porter, STFC Daresbury Lab + +'''Tests for OpenCL PSy-layer code generation that are specific to the +GOcean 1.0 API.''' + +import os +import pytest +from psyclone.parse import parse +from psyclone.psyGen import PSyFactory +from psyclone.generator import GenerationError, ParseError + + +API = "gocean1.0" + + +def test_use_stmts(): + ''' Test that generating code for OpenCL results in the correct + module use statements ''' + _, invoke_info = parse(os.path.join(os.path. + dirname(os.path. + abspath(__file__)), + "test_files", "gocean1p0", + "single_invoke.f90"), + api=API) + psy = PSyFactory(API, opencl=True).create(invoke_info) + generated_code = str(psy.gen) + print generated_code + expected = '''\ + MODULE psy_single_invoke_test + USE clfortran + USE iso_c_binding''' + assert expected in generated_code From dd3309c65bf3920b671c274399942afe4d04b842 Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Mon, 4 Jun 2018 10:54:05 +0100 Subject: [PATCH 02/60] #174 add first OCL test for gocean --- .../tests/gocean1p0_transformations_test.py | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/psyclone/tests/gocean1p0_transformations_test.py b/src/psyclone/tests/gocean1p0_transformations_test.py index 1d253d7409..81935baeb0 100644 --- a/src/psyclone/tests/gocean1p0_transformations_test.py +++ b/src/psyclone/tests/gocean1p0_transformations_test.py @@ -1316,3 +1316,24 @@ def test_go_loop_swap_errors(): assert re.search("Given node .* is not a GOLoop, " "but an instance of .*DynLoop", str(error.value)) is not None + + +def test_ocl_apply(): + ''' Check that OCLTrans generates correct code ''' + from psyclone.transformations import OCLTrans + psy, invoke = get_invoke("test11_different_iterates_over_" + "one_invoke.f90", 0) + schedule = invoke.schedule + ocl = OCLTrans() + + # Check that we raise the correct error if we attempt to apply the + # transformation to something that is not a Schedule + with pytest.raises(TransformationError) as err: + _, _ = ocl.apply(schedule.children[0]) + assert "the supplied node must be a (sub-class of) Schedule " in str(err) + + new_sched, _ = ocl.apply(schedule) + assert new_sched.opencl + + gen = str(psy.gen) + assert "USE clfortran" in gen From 585eafb162c3df8482ffd9fa5b779713dd8db8da Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Mon, 4 Jun 2018 10:54:34 +0100 Subject: [PATCH 03/60] #174 add new OCLTrans() transformation --- src/psyclone/transformations.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/psyclone/transformations.py b/src/psyclone/transformations.py index 8ca00d9de5..dda6e12537 100644 --- a/src/psyclone/transformations.py +++ b/src/psyclone/transformations.py @@ -1669,3 +1669,35 @@ def apply(self, outer): # pylint: disable=arguments-differ outer.parent = inner return schedule, keep + + +class OCLTrans(Transformation): + ''' + Switches on/off the generation of an OpenCL PSy layer for a given + Schedule. For example: + + >>> invoke = ... + >>> schedule = invoke.schedule + >>> + >>> ocl_trans = OCLTrans() + >>> new_sched, _ = ocl_trans.apply(schedule) + ''' + + def name(self): + '''Returns the name of this transformation as a string.''' + return "OCLTrans" + + def apply(self, sched, opencl=True): + from psyclone.psyGen import Schedule + if not isinstance(sched, Schedule): + raise TransformationError( + "Error in OCLTrans: the supplied node must be a (sub-class " + "of) Schedule but got {0}".format(type(sched))) + + # create a memento of the schedule and the proposed transformation + from psyclone.undoredo import Memento + keep = Memento(sched, self, [sched]) + + sched.opencl = opencl + + return sched, keep From 6c0a570e9a09897cb884bcae99f9b316c843475d Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Mon, 4 Jun 2018 10:56:06 +0100 Subject: [PATCH 04/60] #174 rm opencl from constructor and have as member of Schedule --- src/psyclone/gocean1p0.py | 35 +++++++++++++++++++++++++++-------- src/psyclone/psyGen.py | 34 +++++++++++++++++++++++++++------- 2 files changed, 54 insertions(+), 15 deletions(-) diff --git a/src/psyclone/gocean1p0.py b/src/psyclone/gocean1p0.py index 0ea5921f60..b068665768 100644 --- a/src/psyclone/gocean1p0.py +++ b/src/psyclone/gocean1p0.py @@ -101,8 +101,8 @@ class GOPSy(PSy): invokes object (which controls all the required invocation calls). Also overrides the PSy gen method so that we generate GOcean- specific PSy module code. ''' - def __init__(self, invoke_info, opencl=False): - PSy.__init__(self, invoke_info, opencl) + def __init__(self, invoke_info): + PSy.__init__(self, invoke_info) self._invokes = GOInvokes(invoke_info.calls) @property @@ -121,10 +121,6 @@ def gen(self): psy_module.add(UseGen(psy_module, name="kind_params_mod")) # include the field_mod module psy_module.add(UseGen(psy_module, name="field_mod")) - if self._opencl: - psy_module.add(UseGen(psy_module, name="iso_c_binding")) - psy_module.add(UseGen(psy_module, name="clfortran")) - # add in the subroutines for each invocation self.invokes.gen_code(psy_module) # inline kernels where requested self.inline(psy_module) @@ -132,8 +128,13 @@ def gen(self): class GOInvokes(Invokes): - ''' The GOcean specific invokes class. This passes the GOcean specific - invoke class to the base class so it creates the one we require. ''' + ''' + The GOcean specific invokes class. This passes the GOcean specific + invoke class to the base class so it creates the one we require. + :param alg_calls: list of the Invoke calls discovered in the Algorithm + layer + :type alg_calls: list of :py:class:`psyclone.TBD` + ''' def __init__(self, alg_calls): if False: # pylint: disable=using-constant-test self._0_to_n = GOInvoke(None, None) # for pyreverse @@ -709,6 +710,12 @@ def gen_code(self, parent): kernel instance. ''' from psyclone.f2pygen import CallGen, UseGen + if self._opencl: + # OpenCL is completely different so has its own gen method. + # TODO is there a nicer way of doing this? + self.gen_ocl(parent) + return + # Before we do anything else, go through the arguments and # determine the best one from which to obtain the grid properties. grid_arg = self._find_grid_access() @@ -745,6 +752,18 @@ def gen_code(self, parent): parent.add(UseGen(parent, name=self._module_name, only=True, funcnames=[self._name])) + def gen_ocl(self, parent): + ''' + :param parent: + ''' + cnull = "C_NULL_PTR" + cmd_queue = "cmd_queues(1)" # TODO use namespace manager + kernel = "kernel_" + self._name # TODO use namespace manager + gsize = "globalsize" # TODO use namespace manager + args = [cmd_queue, kernel, "2", cnull, "C_LOC({0})".format(gsize), + cnull, "0", cnull, cnull] + parent.add(CallGen(parent, "clEnqueueNDRangeKernel", args)) + @property def index_offset(self): ''' The grid index-offset convention that this kernel expects ''' diff --git a/src/psyclone/psyGen.py b/src/psyclone/psyGen.py index b47e0b0c3e..07773f3764 100644 --- a/src/psyclone/psyGen.py +++ b/src/psyclone/psyGen.py @@ -201,19 +201,16 @@ class PSyFactory(object): config.DISTRIBUTED_MEMORY. If we set it to None and then test the value, it then fails. I've no idea why. - :param bool opencl: #TODO ''' def __init__(self, api="", - distributed_memory=config.DISTRIBUTED_MEMORY, - opencl=False): + distributed_memory=config.DISTRIBUTED_MEMORY): if distributed_memory not in [True, False]: raise GenerationError( "The distributed_memory flag in PSyFactory must be set to" " 'True' or 'False'") config.DISTRIBUTED_MEMORY = distributed_memory self._type = get_api(api) - self._opencl = opencl def create(self, invoke_info): ''' Return the specified version of PSy. ''' @@ -231,7 +228,7 @@ def create(self, invoke_info): return GOPSy(invoke_info) elif self._type == "gocean1.0": from psyclone.gocean1p0 import GOPSy - return GOPSy(invoke_info, opencl=self._opencl) + return GOPSy(invoke_info) else: raise GenerationError("PSyFactory: Internal Error: Unsupported " "api type '{0}' found. Should not be " @@ -262,11 +259,10 @@ class PSy(object): >>> print(psy.gen) ''' - def __init__(self, invoke_info, opencl): + def __init__(self, invoke_info): self._name = invoke_info.name self._invokes = None - self._opencl = opencl def __str__(self): return "PSy" @@ -1036,6 +1032,7 @@ def __init__(self, children=None, parent=None): else: self._children = children self._parent = parent + self._opencl = False def __str__(self): raise NotImplementedError("Please implement me") @@ -1278,6 +1275,7 @@ def __init__(self, KernFactory, BuiltInFactory, alg_calls=[]): sequence.append(KernFactory.create(call, parent=self)) Node.__init__(self, children=sequence) self._invoke = None + self._opencl = False # Whether or not to generate OpenCL def view(self, indent=0): ''' @@ -1311,9 +1309,31 @@ def __str__(self): return result def gen_code(self, parent): + ''' + TODO + ''' + if self._opencl: + from psyclone.f2pygen import UseGen + parent.add(UseGen(parent, name="iso_c_binding")) + parent.add(UseGen(parent, name="clfortran")) + for entity in self._children: entity.gen_code(parent) + @property + def opencl(self): + return self._opencl + + @opencl.setter + def opencl(self, value): + ''' + :param bool value: whether or not to generate OpenCL for this schedule + ''' + if not isinstance(value, bool): + raise ValueError("Schedule.opencl must be a bool but got {0}". + format(type(value))) + self._opencl = value + class Directive(Node): From 9f161c01ce8082304e852b19094db27507bc18a9 Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Fri, 15 Jun 2018 11:12:11 +0100 Subject: [PATCH 05/60] #174 generate code to set kernel args --- src/psyclone/psyGen.py | 56 +++++++++++++++++++++ src/psyclone/tests/gocean1p0_opencl_test.py | 10 ++-- 2 files changed, 63 insertions(+), 3 deletions(-) diff --git a/src/psyclone/psyGen.py b/src/psyclone/psyGen.py index 07773f3764..d435737401 100644 --- a/src/psyclone/psyGen.py +++ b/src/psyclone/psyGen.py @@ -320,8 +320,24 @@ def get(self, invoke_name): str(self.names))) def gen_code(self, parent): + ''' + Create the f2pygen AST for each Invoke in the PSy layer. + + :param parent: the parent node in the AST to which to add content. + :type parent: `psyclone.f2pygen.ModuleGen` + ''' + opencl_kernels = [] for invoke in self.invoke_list: invoke.gen_code(parent) + # If we are generating OpenCL for an Invoke then we need to + # create routine(s) to set the arguments of the Kernel(s) it + # calls. We do it here as this enables us to prevent + # duplication. + if invoke.schedule._opencl: + for kern in invoke.schedule.kern_calls(): + if kern.name not in opencl_kernels: + opencl_kernels.append(kern.name) + kern.gen_arg_setter_code(parent) class NameSpaceFactory(object): @@ -2488,6 +2504,34 @@ def gen_code(self, parent): parent.add(UseGen(parent, name=self._module_name, only=True, funcnames=[self._name])) + def gen_arg_setter_code(self, parent): + ''' + Creates a Fortran routine to set the arguments of the OpenCL + version of this kernel. + + :param parent: Parent node of the set-kernel-arguments routine + :type parent: :py:class:`psyclone.f2pygen.moduleGen` + ''' + from psyclone.f2pygen import SubroutineGen, UseGen, DeclGen, \ + AssignGen, CommentGen + # TODO take care with literal arguments + sub = SubroutineGen(parent, name=self.name+"_set_args", + args=[arg.name for arg in self._arguments.args]) + parent.add(sub) + # TODO add a use for check_status() routine + sub.add(UseGen(sub, name="iso_c_binding", only=True, + funcnames=["sizeof", "c_loc"])) + sub.add(UseGen(sub, name="clfortran", only=True, + funcnames=["clSetKernelArg"])) + sub.add(DeclGen(sub, datatype="integer", entity_decls=["ierr", + "arg_idx"])) + sub.add(CommentGen( + sub, + " Set the arguments for the {0} OpenCL Kernel".format(self.name))) + sub.add(AssignGen(sub, lhs="arg_idx", rhs="0")) + for arg in self.arguments.args: + arg.set_kernel_arg(sub) + def incremented_arg(self, mapping={}): ''' Returns the argument that has INC access. Raises a FieldNotFoundError if none is found. @@ -2713,6 +2757,18 @@ def call(self, value): ''' set the node that this argument is associated with ''' self._call = value + def set_kernel_arg(self, parent): + ''' + Generate the code to set this argument for an OpenCL kernel + ''' + from psyclone.f2pygen import AssignGen, CallGen + parent.add(AssignGen( + parent, lhs="ierr", + rhs="clSetKernelArg({0}, arg_idx, sizeof({1}), C_LOC({2}))". + format("kernel_obj", self.name, self.name))) + parent.add(CallGen(parent, "check_status", ["clSetKernelArg", "ierr"])) + parent.add(AssignGen(parent, lhs="arg_idx", rhs="arg_idx + 1")) + def backward_dependence(self): '''Returns the preceding argument that this argument has a direct dependence with, or None if there is not one. The argument may diff --git a/src/psyclone/tests/gocean1p0_opencl_test.py b/src/psyclone/tests/gocean1p0_opencl_test.py index 0898ea0bf1..22cb78e560 100644 --- a/src/psyclone/tests/gocean1p0_opencl_test.py +++ b/src/psyclone/tests/gocean1p0_opencl_test.py @@ -32,6 +32,7 @@ # POSSIBILITY OF SUCH DAMAGE. # ---------------------------------------------------------------------------- # Author A. R. Porter, STFC Daresbury Lab +from __future__ import print_function '''Tests for OpenCL PSy-layer code generation that are specific to the GOcean 1.0 API.''' @@ -42,7 +43,6 @@ from psyclone.psyGen import PSyFactory from psyclone.generator import GenerationError, ParseError - API = "gocean1.0" @@ -55,9 +55,13 @@ def test_use_stmts(): "test_files", "gocean1p0", "single_invoke.f90"), api=API) - psy = PSyFactory(API, opencl=True).create(invoke_info) + psy = PSyFactory(API).create(invoke_info) + sched = psy.invokes.invoke_list[0].schedule + from psyclone.transformations import OCLTrans + otrans = OCLTrans() + otrans.apply(sched) generated_code = str(psy.gen) - print generated_code + print(generated_code) expected = '''\ MODULE psy_single_invoke_test USE clfortran From e71012f8031509acd4afab223c63ce66b584745b Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Fri, 15 Jun 2018 12:19:46 +0100 Subject: [PATCH 06/60] #174 no explicit loops for ocl --- src/psyclone/psyGen.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/psyclone/psyGen.py b/src/psyclone/psyGen.py index d435737401..10b7e535ed 100644 --- a/src/psyclone/psyGen.py +++ b/src/psyclone/psyGen.py @@ -1326,7 +1326,11 @@ def __str__(self): def gen_code(self, parent): ''' - TODO + Generate the Nodes in the f2pygen AST for this schedule. + + :param parent: the parent Node (i.e. the enclosing subroutine) to \ + which to add content. + :type parent: :py:class:`psyclone.f2pygen.SubroutineGen` ''' if self._opencl: from psyclone.f2pygen import UseGen @@ -1338,6 +1342,11 @@ def gen_code(self, parent): @property def opencl(self): + ''' + Whether or not we are generating OpenCL for this Schedule. + + :rtype: bool + ''' return self._opencl @opencl.setter @@ -2155,7 +2164,8 @@ def gen_code(self, parent): calls = self.reductions() zero_reduction_variables(calls, parent) - if self._start == "1" and self._stop == "1": # no need for a loop + if self.root.opencl or (self._start == "1" and self._stop == "1"): + # no need for a loop for child in self.children: child.gen_code(parent) else: From b540c7586473f7053b499ccb17654cf527397e75 Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Fri, 15 Jun 2018 12:20:34 +0100 Subject: [PATCH 07/60] #174 add call to set kernel args before each kernel call --- src/psyclone/gocean1p0.py | 21 +++++++++++++-- src/psyclone/tests/gocean1p0_opencl_test.py | 30 ++++++++++++++++++--- 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/src/psyclone/gocean1p0.py b/src/psyclone/gocean1p0.py index b068665768..85cbaba6f1 100644 --- a/src/psyclone/gocean1p0.py +++ b/src/psyclone/gocean1p0.py @@ -710,7 +710,7 @@ def gen_code(self, parent): kernel instance. ''' from psyclone.f2pygen import CallGen, UseGen - if self._opencl: + if self.root.opencl: # OpenCL is completely different so has its own gen method. # TODO is there a nicer way of doing this? self.gen_ocl(parent) @@ -756,13 +756,30 @@ def gen_ocl(self, parent): ''' :param parent: ''' + from psyclone.f2pygen import CallGen, DeclGen, AssignGen, CommentGen + + garg = self._find_grid_access() + parent.add(DeclGen(parent, datatype="integer", + entity_decls=["globalsize(2)"])) + parent.add(AssignGen( + parent, lhs="globalsize", + rhs="(/{0}%grid%nx, {0}%grid%ny/)".format(garg.name))) + + kernel = "kernel_" + self._name # TODO use namespace manager + + # First we have to set the kernel arguments + parent.add(CallGen( + parent, "{0}_set_args".format(self.name), + [kernel] + [arg.name for arg in self._arguments.args])) + # Then we call clEnqueueNDRangeKernel cnull = "C_NULL_PTR" cmd_queue = "cmd_queues(1)" # TODO use namespace manager - kernel = "kernel_" + self._name # TODO use namespace manager + gsize = "globalsize" # TODO use namespace manager args = [cmd_queue, kernel, "2", cnull, "C_LOC({0})".format(gsize), cnull, "0", cnull, cnull] parent.add(CallGen(parent, "clEnqueueNDRangeKernel", args)) + parent.add(CommentGen(parent, "")) @property def index_offset(self): diff --git a/src/psyclone/tests/gocean1p0_opencl_test.py b/src/psyclone/tests/gocean1p0_opencl_test.py index 22cb78e560..1bcee96bb4 100644 --- a/src/psyclone/tests/gocean1p0_opencl_test.py +++ b/src/psyclone/tests/gocean1p0_opencl_test.py @@ -63,7 +63,31 @@ def test_use_stmts(): generated_code = str(psy.gen) print(generated_code) expected = '''\ - MODULE psy_single_invoke_test - USE clfortran - USE iso_c_binding''' + SUBROUTINE invoke_0_compute_cu(cu_fld, p_fld, u_fld) + USE compute_cu_mod, ONLY: compute_cu_code + USE clfortran + USE iso_c_binding''' assert expected in generated_code + + +def test_set_kern_args(): + ''' Check that we generate the necessary code to set kernel arguments ''' + _, invoke_info = parse(os.path.join(os.path. + dirname(os.path. + abspath(__file__)), + "test_files", "gocean1p0", + "single_invoke_two_kernels.f90"), + api=API) + psy = PSyFactory(API).create(invoke_info) + sched = psy.invokes.invoke_list[0].schedule + from psyclone.transformations import OCLTrans + otrans = OCLTrans() + otrans.apply(sched) + generated_code = str(psy.gen) + print(generated_code) + assert generated_code.count("SUBROUTINE compute_cu_code_set_args(cu_fld, " + "p_fld, u_fld)") == 1 + assert generated_code.count("SUBROUTINE time_smooth_code_set_args(u_fld, " + "unew_fld, uold_fld)") == 1 + assert ("CALL compute_cu_code_set_args(cu_fld%data, p_fld%data, " + "u_fld%data)" in generated_code) From c998f56860351004724ec1e792392fa554398721 Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Fri, 15 Jun 2018 15:01:05 +0100 Subject: [PATCH 08/60] #174 add Fortran OpenCLn utility code --- lib/opencl/Makefile | 14 + lib/opencl/README.md | 11 + lib/opencl/clfortran.f90 | 1931 +++++++++++++++++++++++++++++++++ lib/opencl/ocl_env_mod.f90 | 147 +++ lib/opencl/ocl_params_mod.f90 | 10 + lib/opencl/ocl_utils_mod.f90 | 433 ++++++++ 6 files changed, 2546 insertions(+) create mode 100644 lib/opencl/Makefile create mode 100644 lib/opencl/README.md create mode 100644 lib/opencl/clfortran.f90 create mode 100644 lib/opencl/ocl_env_mod.f90 create mode 100644 lib/opencl/ocl_params_mod.f90 create mode 100644 lib/opencl/ocl_utils_mod.f90 diff --git a/lib/opencl/Makefile b/lib/opencl/Makefile new file mode 100644 index 0000000000..02031e3158 --- /dev/null +++ b/lib/opencl/Makefile @@ -0,0 +1,14 @@ +# Makefile for OpenCL utility modules +OBJECTS = clfortran.o ocl_params_mod.o ocl_utils_mod.o ocl_env_mod.o + +all: ${OBJECTS} + +%.o: %.f90 + ${F90} ${F90FLAGS} -c $< + +clean: + rm -f *.o + rm -f *~ + +allclean: clean + rm -f *.mod *.MOD diff --git a/lib/opencl/README.md b/lib/opencl/README.md new file mode 100644 index 0000000000..925e6907f9 --- /dev/null +++ b/lib/opencl/README.md @@ -0,0 +1,11 @@ +# Introduction # + +This directory contains Fortran source code that wraps the OpenCL +functionality required by PSyclone when generating code that +targets an OpenCL device. It uses the "clFortran" interface code +available from https://github.com/cass-support/clfortran. + +# Compiling # + +The Makefile picks-up the compiler, flags etc. from environment +variables. You will need to set F90 (and optionally, F90FLAGS). diff --git a/lib/opencl/clfortran.f90 b/lib/opencl/clfortran.f90 new file mode 100644 index 0000000000..4f12873f39 --- /dev/null +++ b/lib/opencl/clfortran.f90 @@ -0,0 +1,1931 @@ +! ----------------------------------------------------------------------------- +! CLFORTRAN - OpenCL bindings module for Fortran. +! +! This is the main module file and contains all OpenCL API definitions to be +! invoked from Fortran programs. +! +! ----------------------------------------------------------------------------- +! +! Copyright (C) 2013 Company for Advanced Supercomputing Solutions LTD +! Bosmat 2a St. +! Shoham +! Israel 60850 +! http://www.cass-hpc.com +! +! Author: Mordechai Butrashvily +! +! ----------------------------------------------------------------------------- +! +! This program is free software: you can redistribute it and/or modify +! it under the terms of the GNU Lesser General Public License as published by +! the Free Software Foundation, either version 3 of the License, or +! (at your option) any later version. +! +! This program is distributed in the hope that it will be useful, +! but WITHOUT ANY WARRANTY; without even the implied warranty of +! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +! GNU Lesser General Public License for more details. +! +! You should have received a copy of the GNU Lesser General Public License +! along with this program. If not, see . +! +! ----------------------------------------------------------------------------- + +module clfortran + USE ISO_C_BINDING + implicit none + + ! Error Codes + integer(c_int32_t), parameter :: CL_SUCCESS = 0 + integer(c_int32_t), parameter :: CL_DEVICE_NOT_FOUND = -1 + integer(c_int32_t), parameter :: CL_DEVICE_NOT_AVAILABLE = -2 + integer(c_int32_t), parameter :: CL_COMPILER_NOT_AVAILABLE = -3 + integer(c_int32_t), parameter :: CL_MEM_OBJECT_ALLOCATION_FAILURE = -4 + integer(c_int32_t), parameter :: CL_OUT_OF_RESOURCES = -5 + integer(c_int32_t), parameter :: CL_OUT_OF_HOST_MEMORY = -6 + integer(c_int32_t), parameter :: CL_PROFILING_INFO_NOT_AVAILABLE = -7 + integer(c_int32_t), parameter :: CL_MEM_COPY_OVERLAP = -8 + integer(c_int32_t), parameter :: CL_IMAGE_FORMAT_MISMATCH = -9 + integer(c_int32_t), parameter :: CL_IMAGE_FORMAT_NOT_SUPPORTED = -10 + integer(c_int32_t), parameter :: CL_BUILD_PROGRAM_FAILURE = -11 + integer(c_int32_t), parameter :: CL_MAP_FAILURE = -12 + integer(c_int32_t), parameter :: CL_MISALIGNED_SUB_BUFFER_OFFSET = -13 + integer(c_int32_t), parameter :: CL_EXEC_STATUS_ERROR_FOR_EVENTS_IN_WAIT_LIST = -14 + integer(c_int32_t), parameter :: CL_COMPILE_PROGRAM_FAILURE = -15 + integer(c_int32_t), parameter :: CL_LINKER_NOT_AVAILABLE = -16 + integer(c_int32_t), parameter :: CL_LINK_PROGRAM_FAILURE = -17 + integer(c_int32_t), parameter :: CL_DEVICE_PARTITION_FAILED = -18 + integer(c_int32_t), parameter :: CL_KERNEL_ARG_INFO_NOT_AVAILABLE = -19 + + integer(c_int32_t), parameter :: CL_INVALID_VALUE = -30 + integer(c_int32_t), parameter :: CL_INVALID_DEVICE_TYPE = -31 + integer(c_int32_t), parameter :: CL_INVALID_PLATFORM = -32 + integer(c_int32_t), parameter :: CL_INVALID_DEVICE = -33 + integer(c_int32_t), parameter :: CL_INVALID_CONTEXT = -34 + integer(c_int32_t), parameter :: CL_INVALID_QUEUE_PROPERTIES = -35 + integer(c_int32_t), parameter :: CL_INVALID_COMMAND_QUEUE = -36 + integer(c_int32_t), parameter :: CL_INVALID_HOST_PTR = -37 + integer(c_int32_t), parameter :: CL_INVALID_MEM_OBJECT = -38 + integer(c_int32_t), parameter :: CL_INVALID_IMAGE_FORMAT_DESCRIPTOR = -39 + integer(c_int32_t), parameter :: CL_INVALID_IMAGE_SIZE = -40 + integer(c_int32_t), parameter :: CL_INVALID_SAMPLER = -41 + integer(c_int32_t), parameter :: CL_INVALID_BINARY = -42 + integer(c_int32_t), parameter :: CL_INVALID_BUILD_OPTIONS = -43 + integer(c_int32_t), parameter :: CL_INVALID_PROGRAM = -44 + integer(c_int32_t), parameter :: CL_INVALID_PROGRAM_EXECUTABLE = -45 + integer(c_int32_t), parameter :: CL_INVALID_KERNEL_NAME = -46 + integer(c_int32_t), parameter :: CL_INVALID_KERNEL_DEFINITION = -47 + integer(c_int32_t), parameter :: CL_INVALID_KERNEL = -48 + integer(c_int32_t), parameter :: CL_INVALID_ARG_INDEX = -49 + integer(c_int32_t), parameter :: CL_INVALID_ARG_VALUE = -50 + integer(c_int32_t), parameter :: CL_INVALID_ARG_SIZE = -51 + integer(c_int32_t), parameter :: CL_INVALID_KERNEL_ARGS = -52 + integer(c_int32_t), parameter :: CL_INVALID_WORK_DIMENSION = -53 + integer(c_int32_t), parameter :: CL_INVALID_WORK_GROUP_SIZE = -54 + integer(c_int32_t), parameter :: CL_INVALID_WORK_ITEM_SIZE = -55 + integer(c_int32_t), parameter :: CL_INVALID_GLOBAL_OFFSET = -56 + integer(c_int32_t), parameter :: CL_INVALID_EVENT_WAIT_LIST = -57 + integer(c_int32_t), parameter :: CL_INVALID_EVENT = -58 + integer(c_int32_t), parameter :: CL_INVALID_OPERATION = -59 + integer(c_int32_t), parameter :: CL_INVALID_GL_OBJECT = -60 + integer(c_int32_t), parameter :: CL_INVALID_BUFFER_SIZE = -61 + integer(c_int32_t), parameter :: CL_INVALID_MIP_LEVEL = -62 + integer(c_int32_t), parameter :: CL_INVALID_GLOBAL_WORK_SIZE = -63 + integer(c_int32_t), parameter :: CL_INVALID_PROPERTY = -64 + integer(c_int32_t), parameter :: CL_INVALID_IMAGE_DESCRIPTOR = -65 + integer(c_int32_t), parameter :: CL_INVALID_COMPILER_OPTIONS = -66 + integer(c_int32_t), parameter :: CL_INVALID_LINKER_OPTIONS = -67 + integer(c_int32_t), parameter :: CL_INVALID_DEVICE_PARTITION_COUNT = -68 + + ! OpenCL Version + integer(c_int32_t), parameter :: CL_VERSION_1_0 = 1 + integer(c_int32_t), parameter :: CL_VERSION_1_1 = 1 + integer(c_int32_t), parameter :: CL_VERSION_1_2 = 1 + + ! cl_bool + integer(c_int32_t), parameter :: CL_FALSE = 0 + integer(c_int32_t), parameter :: CL_TRUE = 1 + integer(c_int32_t), parameter :: CL_BLOCKING = CL_TRUE + integer(c_int32_t), parameter :: CL_NON_BLOCKING = CL_FALSE + + ! cl_platform_info + integer(c_int32_t), parameter :: CL_PLATFORM_PROFILE = Z'0900' + integer(c_int32_t), parameter :: CL_PLATFORM_VERSION = Z'0901' + integer(c_int32_t), parameter :: CL_PLATFORM_NAME = Z'0902' + integer(c_int32_t), parameter :: CL_PLATFORM_VENDOR = Z'0903' + integer(c_int32_t), parameter :: CL_PLATFORM_EXTENSIONS = Z'0904' + + ! cl_device_type - bitfield + integer(c_int64_t), parameter :: CL_DEVICE_TYPE_DEFAULT = b'00001' + integer(c_int64_t), parameter :: CL_DEVICE_TYPE_CPU = b'00010' + integer(c_int64_t), parameter :: CL_DEVICE_TYPE_GPU = b'00100' + integer(c_int64_t), parameter :: CL_DEVICE_TYPE_ACCELERATOR = b'01000' + integer(c_int64_t), parameter :: CL_DEVICE_TYPE_CUSTOM = b'10000' + integer(c_int64_t), parameter :: CL_DEVICE_TYPE_ALL = Z'FFFFFFFF' + + ! cl_device_info + integer(c_int32_t), parameter :: CL_DEVICE_TYPE = Z'1000' + integer(c_int32_t), parameter :: CL_DEVICE_VENDOR_ID = Z'1001' + integer(c_int32_t), parameter :: CL_DEVICE_MAX_COMPUTE_UNITS = Z'1002' + integer(c_int32_t), parameter :: CL_DEVICE_MAX_WORK_ITEM_DIMENSIONS = Z'1003' + integer(c_int32_t), parameter :: CL_DEVICE_MAX_WORK_GROUP_SIZE = Z'1004' + integer(c_int32_t), parameter :: CL_DEVICE_MAX_WORK_ITEM_SIZES = Z'1005' + integer(c_int32_t), parameter :: CL_DEVICE_PREFERRED_VECTOR_WIDTH_CHAR = Z'1006' + integer(c_int32_t), parameter :: CL_DEVICE_PREFERRED_VECTOR_WIDTH_SHORT = Z'1007' + integer(c_int32_t), parameter :: CL_DEVICE_PREFERRED_VECTOR_WIDTH_INT = Z'1008' + integer(c_int32_t), parameter :: CL_DEVICE_PREFERRED_VECTOR_WIDTH_LONG = Z'1009' + integer(c_int32_t), parameter :: CL_DEVICE_PREFERRED_VECTOR_WIDTH_FLOAT = Z'100A' + integer(c_int32_t), parameter :: CL_DEVICE_PREFERRED_VECTOR_WIDTH_DOUBLE = Z'100B' + integer(c_int32_t), parameter :: CL_DEVICE_MAX_CLOCK_FREQUENCY = Z'100C' + integer(c_int32_t), parameter :: CL_DEVICE_ADDRESS_BITS = Z'100D' + integer(c_int32_t), parameter :: CL_DEVICE_MAX_READ_IMAGE_ARGS = Z'100E' + integer(c_int32_t), parameter :: CL_DEVICE_MAX_WRITE_IMAGE_ARGS = Z'100F' + integer(c_int32_t), parameter :: CL_DEVICE_MAX_MEM_ALLOC_SIZE = Z'1010' + integer(c_int32_t), parameter :: CL_DEVICE_IMAGE2D_MAX_WIDTH = Z'1011' + integer(c_int32_t), parameter :: CL_DEVICE_IMAGE2D_MAX_HEIGHT = Z'1012' + integer(c_int32_t), parameter :: CL_DEVICE_IMAGE3D_MAX_WIDTH = Z'1013' + integer(c_int32_t), parameter :: CL_DEVICE_IMAGE3D_MAX_HEIGHT = Z'1014' + integer(c_int32_t), parameter :: CL_DEVICE_IMAGE3D_MAX_DEPTH = Z'1015' + integer(c_int32_t), parameter :: CL_DEVICE_IMAGE_SUPPORT = Z'1016' + integer(c_int32_t), parameter :: CL_DEVICE_MAX_PARAMETER_SIZE = Z'1017' + integer(c_int32_t), parameter :: CL_DEVICE_MAX_SAMPLERS = Z'1018' + integer(c_int32_t), parameter :: CL_DEVICE_MEM_BASE_ADDR_ALIGN = Z'1019' + integer(c_int32_t), parameter :: CL_DEVICE_MIN_DATA_TYPE_ALIGN_SIZE = Z'101A' + integer(c_int32_t), parameter :: CL_DEVICE_SINGLE_FP_CONFIG = Z'101B' + integer(c_int32_t), parameter :: CL_DEVICE_GLOBAL_MEM_CACHE_TYPE = Z'101C' + integer(c_int32_t), parameter :: CL_DEVICE_GLOBAL_MEM_CACHELINE_SIZE = Z'101D' + integer(c_int32_t), parameter :: CL_DEVICE_GLOBAL_MEM_CACHE_SIZE = Z'101E' + integer(c_int32_t), parameter :: CL_DEVICE_GLOBAL_MEM_SIZE = Z'101F' + integer(c_int32_t), parameter :: CL_DEVICE_MAX_CONSTANT_BUFFER_SIZE = Z'1020' + integer(c_int32_t), parameter :: CL_DEVICE_MAX_CONSTANT_ARGS = Z'1021' + integer(c_int32_t), parameter :: CL_DEVICE_LOCAL_MEM_TYPE = Z'1022' + integer(c_int32_t), parameter :: CL_DEVICE_LOCAL_MEM_SIZE = Z'1023' + integer(c_int32_t), parameter :: CL_DEVICE_ERROR_CORRECTION_SUPPORT = Z'1024' + integer(c_int32_t), parameter :: CL_DEVICE_PROFILING_TIMER_RESOLUTION = Z'1025' + integer(c_int32_t), parameter :: CL_DEVICE_ENDIAN_LITTLE = Z'1026' + integer(c_int32_t), parameter :: CL_DEVICE_AVAILABLE = Z'1027' + integer(c_int32_t), parameter :: CL_DEVICE_COMPILER_AVAILABLE = Z'1028' + integer(c_int32_t), parameter :: CL_DEVICE_EXECUTION_CAPABILITIES = Z'1029' + integer(c_int32_t), parameter :: CL_DEVICE_QUEUE_PROPERTIES = Z'102A' + integer(c_int32_t), parameter :: CL_DEVICE_NAME = Z'102B' + integer(c_int32_t), parameter :: CL_DEVICE_VENDOR = Z'102C' + integer(c_int32_t), parameter :: CL_DRIVER_VERSION = Z'102D' + integer(c_int32_t), parameter :: CL_DEVICE_PROFILE = Z'102E' + integer(c_int32_t), parameter :: CL_DEVICE_VERSION = Z'102F' + integer(c_int32_t), parameter :: CL_DEVICE_EXTENSIONS = Z'1030' + integer(c_int32_t), parameter :: CL_DEVICE_PLATFORM = Z'1031' + integer(c_int32_t), parameter :: CL_DEVICE_DOUBLE_FP_CONFIG = Z'1032' + ! 0x1033 reserved for CL_DEVICE_HALF_FP_CONFIG + integer(c_int32_t), parameter :: CL_DEVICE_PREFERRED_VECTOR_WIDTH_HALF = Z'1034' + integer(c_int32_t), parameter :: CL_DEVICE_HOST_UNIFIED_MEMORY = Z'1035' + integer(c_int32_t), parameter :: CL_DEVICE_NATIVE_VECTOR_WIDTH_CHAR = Z'1036' + integer(c_int32_t), parameter :: CL_DEVICE_NATIVE_VECTOR_WIDTH_SHORT = Z'1037' + integer(c_int32_t), parameter :: CL_DEVICE_NATIVE_VECTOR_WIDTH_INT = Z'1038' + integer(c_int32_t), parameter :: CL_DEVICE_NATIVE_VECTOR_WIDTH_LONG = Z'1039' + integer(c_int32_t), parameter :: CL_DEVICE_NATIVE_VECTOR_WIDTH_FLOAT = Z'103A' + integer(c_int32_t), parameter :: CL_DEVICE_NATIVE_VECTOR_WIDTH_DOUBLE = Z'103B' + integer(c_int32_t), parameter :: CL_DEVICE_NATIVE_VECTOR_WIDTH_HALF = Z'103C' + integer(c_int32_t), parameter :: CL_DEVICE_OPENCL_C_VERSION = Z'103D' + integer(c_int32_t), parameter :: CL_DEVICE_LINKER_AVAILABLE = Z'103E' + integer(c_int32_t), parameter :: CL_DEVICE_BUILT_IN_KERNELS = Z'103F' + integer(c_int32_t), parameter :: CL_DEVICE_IMAGE_MAX_BUFFER_SIZE = Z'1040' + integer(c_int32_t), parameter :: CL_DEVICE_IMAGE_MAX_ARRAY_SIZE = Z'1041' + integer(c_int32_t), parameter :: CL_DEVICE_PARENT_DEVICE = Z'1042' + integer(c_int32_t), parameter :: CL_DEVICE_PARTITION_MAX_SUB_DEVICES = Z'1043' + integer(c_int32_t), parameter :: CL_DEVICE_PARTITION_PROPERTIES = Z'1044' + integer(c_int32_t), parameter :: CL_DEVICE_PARTITION_AFFINITY_DOMAIN = Z'1045' + integer(c_int32_t), parameter :: CL_DEVICE_PARTITION_TYPE = Z'1046' + integer(c_int32_t), parameter :: CL_DEVICE_REFERENCE_COUNT = Z'1047' + integer(c_int32_t), parameter :: CL_DEVICE_PREFERRED_INTEROP_USER_SYNC = Z'1048' + integer(c_int32_t), parameter :: CL_DEVICE_PRINTF_BUFFER_SIZE = Z'1049' + + ! cl_device_fp_config - bitfield + integer(c_int64_t), parameter :: CL_FP_DENORM = b'00000001' + integer(c_int64_t), parameter :: CL_FP_INF_NAN = b'00000010' + integer(c_int64_t), parameter :: CL_FP_ROUND_TO_NEAREST = b'00000100' + integer(c_int64_t), parameter :: CL_FP_ROUND_TO_ZERO = b'00001000' + integer(c_int64_t), parameter :: CL_FP_ROUND_TO_INF = b'00010000' + integer(c_int64_t), parameter :: CL_FP_FMA = b'00100000' + integer(c_int64_t), parameter :: CL_FP_SOFT_FLOAT = b'01000000' + integer(c_int64_t), parameter :: CL_FP_CORRECTLY_ROUNDED_DIVIDE_SQRT = b'10000000' + + ! cl_device_mem_cache_type + integer(c_int32_t), parameter :: CL_NONE = Z'0' + integer(c_int32_t), parameter :: CL_READ_ONLY_CACHE = Z'1' + integer(c_int32_t), parameter :: CL_READ_WRITE_CACHE = Z'2' + + ! cl_device_local_mem_type + integer(c_int32_t), parameter :: CL_LOCAL = Z'1' + integer(c_int32_t), parameter :: CL_GLOBAL = Z'2' + + ! cl_device_exec_capabilities - bitfield + integer(c_int64_t), parameter :: CL_EXEC_KERNEL = b'01' + integer(c_int64_t), parameter :: CL_EXEC_NATIVE_KERNEL = b'10' + + ! cl_command_queue_properties - bitfield + integer(c_int64_t), parameter :: CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE = b'01' + integer(c_int64_t), parameter :: CL_QUEUE_PROFILING_ENABLE = b'10' + + ! cl_context_info + integer(c_int32_t), parameter :: CL_CONTEXT_REFERENCE_COUNT = Z'1080' + integer(c_int32_t), parameter :: CL_CONTEXT_DEVICES = Z'1081' + integer(c_int32_t), parameter :: CL_CONTEXT_PROPERTIES = Z'1082' + integer(c_int32_t), parameter :: CL_CONTEXT_NUM_DEVICES = Z'1083' + + ! cl_context_properties type(c_ptr) + integer(c_intptr_t), parameter :: CL_CONTEXT_PLATFORM = Z'1084' + integer(c_intptr_t), parameter :: CL_CONTEXT_INTEROP_USER_SYNC = Z'1085' + + ! cl_command_queue_info + integer(c_int32_t), parameter :: CL_QUEUE_CONTEXT = Z'1090' + integer(c_int32_t), parameter :: CL_QUEUE_DEVICE = Z'1091' + integer(c_int32_t), parameter :: CL_QUEUE_REFERENCE_COUNT = Z'1092' + integer(c_int32_t), parameter :: CL_QUEUE_PROPERTIES = Z'1093' + + ! cl_mem_flags - bitfield (int64) + integer(c_int64_t), parameter :: CL_MEM_READ_WRITE = b'0000000001' + integer(c_int64_t), parameter :: CL_MEM_WRITE_ONLY = b'0000000010' + integer(c_int64_t), parameter :: CL_MEM_READ_ONLY = b'0000000100' + integer(c_int64_t), parameter :: CL_MEM_USE_HOST_PTR = b'0000001000' + integer(c_int64_t), parameter :: CL_MEM_ALLOC_HOST_PTR = b'0000010000' + integer(c_int64_t), parameter :: CL_MEM_COPY_HOST_PTR = b'0000100000' + !integer(c_int64_t), parameter :: reserved = b'0001000000' + integer(c_int64_t), parameter :: CL_MEM_HOST_WRITE_ONLY = b'0010000000' + integer(c_int64_t), parameter :: CL_MEM_HOST_READ_ONLY = b'0100000000' + integer(c_int64_t), parameter :: CL_MEM_HOST_NO_ACCESS = b'1000000000' + + ! cl_buffer_create_type + integer(c_int32_t), parameter :: CL_BUFFER_CREATE_TYPE_REGION = Z'1220' + + ! cl_channel_order + integer(c_int32_t), parameter :: CL_R = Z'10B0' + integer(c_int32_t), parameter :: CL_A = Z'10B1' + integer(c_int32_t), parameter :: CL_RG = Z'10B2' + integer(c_int32_t), parameter :: CL_RA = Z'10B3' + integer(c_int32_t), parameter :: CL_RGB = Z'10B4' + integer(c_int32_t), parameter :: CL_RGBA = Z'10B5' + integer(c_int32_t), parameter :: CL_BGRA = Z'10B6' + integer(c_int32_t), parameter :: CL_ARGB = Z'10B7' + integer(c_int32_t), parameter :: CL_INTENSITY = Z'10B8' + integer(c_int32_t), parameter :: CL_LUMINANCE = Z'10B9' + integer(c_int32_t), parameter :: CL_Rx = Z'10BA' + integer(c_int32_t), parameter :: CL_RGx = Z'10BB' + integer(c_int32_t), parameter :: CL_RGBx = Z'10BC' + integer(c_int32_t), parameter :: CL_DEPTH = Z'10BD' + integer(c_int32_t), parameter :: CL_DEPTH_STENCIL = Z'10BE' + + ! cl_channel_type + integer(c_int32_t), parameter :: CL_SNORM_INT8 = Z'10D0' + integer(c_int32_t), parameter :: CL_SNORM_INT16 = Z'10D1' + integer(c_int32_t), parameter :: CL_UNORM_INT8 = Z'10D2' + integer(c_int32_t), parameter :: CL_UNORM_INT16 = Z'10D3' + integer(c_int32_t), parameter :: CL_UNORM_SHORT_565 = Z'10D4' + integer(c_int32_t), parameter :: CL_UNORM_SHORT_555 = Z'10D5' + integer(c_int32_t), parameter :: CL_UNORM_INT_101010 = Z'10D6' + integer(c_int32_t), parameter :: CL_SIGNED_INT8 = Z'10D7' + integer(c_int32_t), parameter :: CL_SIGNED_INT16 = Z'10D8' + integer(c_int32_t), parameter :: CL_SIGNED_INT32 = Z'10D9' + integer(c_int32_t), parameter :: CL_UNSIGNED_INT8 = Z'10DA' + integer(c_int32_t), parameter :: CL_UNSIGNED_INT16 = Z'10DB' + integer(c_int32_t), parameter :: CL_UNSIGNED_INT32 = Z'10DC' + integer(c_int32_t), parameter :: CL_HALF_FLOAT = Z'10DD' + integer(c_int32_t), parameter :: CL_FLOAT = Z'10DE' + integer(c_int32_t), parameter :: CL_UNORM_INT24 = Z'10DF' + + ! cl_mem_object_type + integer(c_int32_t), parameter :: CL_MEM_OBJECT_BUFFER = Z'10F0' + integer(c_int32_t), parameter :: CL_MEM_OBJECT_IMAGE2D = Z'10F1' + integer(c_int32_t), parameter :: CL_MEM_OBJECT_IMAGE3D = Z'10F2' + integer(c_int32_t), parameter :: CL_MEM_OBJECT_IMAGE2D_ARRAY = Z'10F3' + integer(c_int32_t), parameter :: CL_MEM_OBJECT_IMAGE1D = Z'10F4' + integer(c_int32_t), parameter :: CL_MEM_OBJECT_IMAGE1D_ARRAY = Z'10F5' + integer(c_int32_t), parameter :: CL_MEM_OBJECT_IMAGE1D_BUFFER = Z'10F6' + + ! cl_mem_info + integer(c_int32_t), parameter :: CL_MEM_TYPE = Z'1100' + integer(c_int32_t), parameter :: CL_MEM_FLAGS = Z'1101' + integer(c_int32_t), parameter :: CL_MEM_SIZE = Z'1102' + integer(c_int32_t), parameter :: CL_MEM_HOST_PTR = Z'1103' + integer(c_int32_t), parameter :: CL_MEM_MAP_COUNT = Z'1104' + integer(c_int32_t), parameter :: CL_MEM_REFERENCE_COUNT = Z'1105' + integer(c_int32_t), parameter :: CL_MEM_CONTEXT = Z'1106' + integer(c_int32_t), parameter :: CL_MEM_ASSOCIATED_MEMOBJECT = Z'1107' + integer(c_int32_t), parameter :: CL_MEM_OFFSET = Z'1108' + + ! cl_image_info - Note that INFO was added to resolve naming conflicts. + integer(c_int32_t), parameter :: CL_IMAGE_INFO_FORMAT = Z'1110' + integer(c_int32_t), parameter :: CL_IMAGE_INFO_ELEMENT_SIZE = Z'1111' + integer(c_int32_t), parameter :: CL_IMAGE_INFO_ROW_PITCH = Z'1112' + integer(c_int32_t), parameter :: CL_IMAGE_INFO_SLICE_PITCH = Z'1113' + integer(c_int32_t), parameter :: CL_IMAGE_INFO_WIDTH = Z'1114' + integer(c_int32_t), parameter :: CL_IMAGE_INFO_HEIGHT = Z'1115' + integer(c_int32_t), parameter :: CL_IMAGE_INFO_DEPTH = Z'1116' + integer(c_int32_t), parameter :: CL_IMAGE_INFO_ARRAY_SIZE = Z'1117' + integer(c_int32_t), parameter :: CL_IMAGE_INFO_BUFFER = Z'1118' + integer(c_int32_t), parameter :: CL_IMAGE_INFO_NUM_MIP_LEVELS = Z'1119' + integer(c_int32_t), parameter :: CL_IMAGE_INFO_NUM_SAMPLES = Z'111A' + + ! cl_addressing_mode + integer(c_int32_t), parameter :: CL_ADDRESS_NONE = Z'1130' + integer(c_int32_t), parameter :: CL_ADDRESS_CLAMP_TO_EDGE = Z'1131' + integer(c_int32_t), parameter :: CL_ADDRESS_CLAMP = Z'1132' + integer(c_int32_t), parameter :: CL_ADDRESS_REPEAT = Z'1133' + integer(c_int32_t), parameter :: CL_ADDRESS_MIRRORED_REPEAT = Z'1134' + + ! cl_filter_mode + integer(c_int32_t), parameter :: CL_FILTER_NEAREST = Z'1140' + integer(c_int32_t), parameter :: CL_FILTER_LINEAR = Z'1141' + + ! cl_sampler_info + integer(c_int32_t), parameter :: CL_SAMPLER_REFERENCE_COUNT = Z'1150' + integer(c_int32_t), parameter :: CL_SAMPLER_CONTEXT = Z'1151' + integer(c_int32_t), parameter :: CL_SAMPLER_NORMALIZED_COORDS = Z'1152' + integer(c_int32_t), parameter :: CL_SAMPLER_ADDRESSING_MODE = Z'1153' + integer(c_int32_t), parameter :: CL_SAMPLER_FILTER_MODE = Z'1154' + + ! cl_program_info + integer(c_int32_t), parameter :: CL_PROGRAM_REFERENCE_COUNT = Z'1160' + integer(c_int32_t), parameter :: CL_PROGRAM_CONTEXT = Z'1161' + integer(c_int32_t), parameter :: CL_PROGRAM_NUM_DEVICES = Z'1162' + integer(c_int32_t), parameter :: CL_PROGRAM_DEVICES = Z'1163' + integer(c_int32_t), parameter :: CL_PROGRAM_SOURCE = Z'1164' + integer(c_int32_t), parameter :: CL_PROGRAM_BINARY_SIZES = Z'1165' + integer(c_int32_t), parameter :: CL_PROGRAM_BINARIES = Z'1166' + integer(c_int32_t), parameter :: CL_PROGRAM_NUM_KERNELS = Z'1167' + integer(c_int32_t), parameter :: CL_PROGRAM_KERNEL_NAMES = Z'1168' + + ! cl_program_build_info + integer(c_int32_t), parameter :: CL_PROGRAM_BUILD_STATUS = Z'1181' + integer(c_int32_t), parameter :: CL_PROGRAM_BUILD_OPTIONS = Z'1182' + integer(c_int32_t), parameter :: CL_PROGRAM_BUILD_LOG = Z'1183' + integer(c_int32_t), parameter :: CL_PROGRAM_BINARY_TYPE = Z'1184' + + ! cl_program_binary_type + integer(c_int32_t), parameter :: CL_PROGRAM_BINARY_TYPE_NONE = Z'0' + integer(c_int32_t), parameter :: CL_PROGRAM_BINARY_TYPE_COMPILED_OBJECT = Z'1' + integer(c_int32_t), parameter :: CL_PROGRAM_BINARY_TYPE_LIBRARY = Z'2' + integer(c_int32_t), parameter :: CL_PROGRAM_BINARY_TYPE_EXECUTABLE = Z'4' + + ! cl_build_status + integer(c_int32_t), parameter :: CL_BUILD_SUCCESS = 0 + integer(c_int32_t), parameter :: CL_BUILD_NONE = -1 + integer(c_int32_t), parameter :: CL_BUILD_ERROR = -2 + integer(c_int32_t), parameter :: CL_BUILD_IN_PROGRESS = -3 + + ! cl_kernel_info + integer(c_int32_t), parameter :: CL_KERNEL_FUNCTION_NAME = Z'1190' + integer(c_int32_t), parameter :: CL_KERNEL_NUM_ARGS = Z'1191' + integer(c_int32_t), parameter :: CL_KERNEL_REFERENCE_COUNT = Z'1192' + integer(c_int32_t), parameter :: CL_KERNEL_CONTEXT = Z'1193' + integer(c_int32_t), parameter :: CL_KERNEL_PROGRAM = Z'1194' + integer(c_int32_t), parameter :: CL_KERNEL_ATTRIBUTES = Z'1195' + + ! cl_kernel_arg_info + integer(c_int32_t), parameter :: CL_KERNEL_ARG_ADDRESS_QUALIFIER = Z'1196' + integer(c_int32_t), parameter :: CL_KERNEL_ARG_ACCESS_QUALIFIER = Z'1197' + integer(c_int32_t), parameter :: CL_KERNEL_ARG_TYPE_NAME = Z'1198' + integer(c_int32_t), parameter :: CL_KERNEL_ARG_TYPE_QUALIFIER = Z'1199' + integer(c_int32_t), parameter :: CL_KERNEL_ARG_NAME = Z'119A' + + ! cl_kernel_arg_address_qualifier + integer(c_int32_t), parameter :: CL_KERNEL_ARG_ADDRESS_GLOBAL = Z'119B' + integer(c_int32_t), parameter :: CL_KERNEL_ARG_ADDRESS_LOCAL = Z'119C' + integer(c_int32_t), parameter :: CL_KERNEL_ARG_ADDRESS_CONSTANT = Z'119D' + integer(c_int32_t), parameter :: CL_KERNEL_ARG_ADDRESS_PRIVATE = Z'119E' + + ! cl_kernel_arg_access_qualifier + integer(c_int32_t), parameter :: CL_KERNEL_ARG_ACCESS_READ_ONLY = Z'11A0' + integer(c_int32_t), parameter :: CL_KERNEL_ARG_ACCESS_WRITE_ONLY = Z'11A1' + integer(c_int32_t), parameter :: CL_KERNEL_ARG_ACCESS_READ_WRITE = Z'11A2' + integer(c_int32_t), parameter :: CL_KERNEL_ARG_ACCESS_NONE = Z'11A3' + + ! cl_kernel_arg_type_qualifer - bitfield (int64) + integer(c_int64_t), parameter :: CL_KERNEL_ARG_TYPE_NONE = b'000' + integer(c_int64_t), parameter :: CL_KERNEL_ARG_TYPE_CONST = b'001' + integer(c_int64_t), parameter :: CL_KERNEL_ARG_TYPE_RESTRICT = b'010' + integer(c_int64_t), parameter :: CL_KERNEL_ARG_TYPE_VOLATILE = b'100' + + ! cl_kernel_work_group_info + integer(c_int32_t), parameter :: CL_KERNEL_WORK_GROUP_SIZE = Z'11B0' + integer(c_int32_t), parameter :: CL_KERNEL_COMPILE_WORK_GROUP_SIZE = Z'11B1' + integer(c_int32_t), parameter :: CL_KERNEL_LOCAL_MEM_SIZE = Z'11B2' + integer(c_int32_t), parameter :: CL_KERNEL_PREFERRED_WORK_GROUP_SIZE_MULTIPLE= Z'11B3' + integer(c_int32_t), parameter :: CL_KERNEL_PRIVATE_MEM_SIZE = Z'11B4' + integer(c_int32_t), parameter :: CL_KERNEL_GLOBAL_WORK_SIZE = Z'11B5' + + ! cl_event_info + integer(c_int32_t), parameter :: CL_EVENT_COMMAND_QUEUE = Z'11D0' + integer(c_int32_t), parameter :: CL_EVENT_COMMAND_TYPE = Z'11D1' + integer(c_int32_t), parameter :: CL_EVENT_REFERENCE_COUNT = Z'11D2' + integer(c_int32_t), parameter :: CL_EVENT_COMMAND_EXECUTION_STATUS = Z'11D3' + integer(c_int32_t), parameter :: CL_EVENT_CONTEXT = Z'11D4' + + ! cl_profiling_info + integer(c_int32_t), parameter :: CL_PROFILING_COMMAND_QUEUED = Z'1280' + integer(c_int32_t), parameter :: CL_PROFILING_COMMAND_SUBMIT = Z'1281' + integer(c_int32_t), parameter :: CL_PROFILING_COMMAND_START = Z'1282' + integer(c_int32_t), parameter :: CL_PROFILING_COMMAND_END = Z'1283' + + ! ------------ + ! Types + ! ------------ + + type, BIND(C) :: cl_image_format + integer(c_int32_t) :: image_channel_order + integer(c_int32_t) :: image_channel_data_type + end type + + type, BIND(C) :: cl_image_desc + integer(c_int32_t) :: image_type + integer(c_size_t) :: image_width + integer(c_size_t) :: image_height + integer(c_size_t) :: image_depth + integer(c_size_t) :: image_array_size + integer(c_size_t) :: image_row_pitch + integer(c_size_t) :: image_slice_pitch + integer(c_int32_t) :: num_mip_levels + integer(c_int32_t) :: num_samples + integer(c_intptr_t) :: buffer + end type + + + ! + ! Start interfaces. + ! + contains + + ! ------------ + ! Platform API + ! ------------ + + ! clGetPlatformIDs + integer(c_int32_t) function clGetPlatformIDs(num_entries, & + platforms, num_platforms) & + BIND(C, NAME='clGetPlatformIDs') + USE ISO_C_BINDING + + integer(c_int32_t), value, intent(in) :: num_entries + type(c_ptr), value, intent(in) :: platforms + integer(c_int32_t), intent(out) :: num_platforms + end function + + ! clGetPlatformInfo + integer(c_int32_t) function clGetPlatformInfo(platform, param_name, & + param_value_size, param_value, param_value_size_ret) & + BIND(C, NAME='clGetPlatformInfo') + USE ISO_C_BINDING + + integer(c_intptr_t), value, intent(in) :: platform + integer(c_int32_t), value, intent(in) :: param_name + integer(c_size_t), value, intent(in) :: param_value_size + type(c_ptr), value, intent(in) :: param_value + integer(c_size_t), intent(out) :: param_value_size_ret + end function + + ! ---------- + ! Device API + ! ---------- + + ! clGetDeviceIDs + integer(c_int32_t) function clGetDeviceIDs(platform, & + device_type, & + num_entries, & + devices, & + num_devices) & + BIND(C, NAME='clGetDeviceIDs') + USE ISO_C_BINDING + + ! Define parameters. + integer(c_intptr_t), value :: platform + integer(c_int64_t), value :: device_type + integer(c_int32_t), value :: num_entries + type(c_ptr), value :: devices + integer(c_int32_t), intent(out) :: num_devices + + end function + + ! clGetDeviceInfo + integer(c_int) function clGetDeviceInfo(device, & + param_name, & + param_value_size, & + param_value, & + param_value_size_ret) & + BIND(C, NAME='clGetDeviceInfo') + USE ISO_C_BINDING + + ! Define parameters. + integer(c_intptr_t), value :: device + integer(c_int32_t), value :: param_name + integer(c_size_t), value :: param_value_size + type(c_ptr), value :: param_value + integer(c_size_t), intent(out) :: param_value_size_ret + + end function + + ! clCreateSubDevices + integer(c_int32_t) function clCreateSubDevices(in_device, & + properties, & + num_devices, & + out_devices, & + num_devices_ret) & + BIND(C, NAME='clCreateSubDevices') + USE ISO_C_BINDING + + ! Define parameters. + integer(c_intptr_t), value :: in_device + type(c_ptr), value :: properties + integer(c_int32_t), value :: num_devices + type(c_ptr), value :: out_devices + integer(c_int32_t), intent(out) :: num_devices_ret + + end function + + ! clRetainDevice + integer(c_int32_t) function clRetainDevice(device) & + BIND(C, NAME='clRetainDevice') + USE ISO_C_BINDING + + ! Define parameters. + integer(c_intptr_t), value :: device + end function + + ! clReleaseDevice + integer(c_int32_t) function clReleaseDevice(device) & + BIND(C, NAME='clReleaseDevice') + USE ISO_C_BINDING + + ! Define parameters. + integer(c_intptr_t), value :: device + end function + + ! ------------ + ! Context APIs + ! ------------ + + ! clCreateContext + integer(c_intptr_t) function clCreateContext(properties, & + num_devices, & + devices, & + pfn_notify, & + user_data, & + errcode_ret) & + BIND(C, NAME='clCreateContext') + USE ISO_C_BINDING + + ! Define parameters. + type(c_ptr), value :: properties + integer(c_int32_t), value :: num_devices + type(c_ptr), value :: devices + type(c_funptr), value :: pfn_notify + type(c_ptr), value :: user_data + integer(c_int32_t), intent(out) :: errcode_ret + + end function + + ! clCreateContextFromType + integer(c_intptr_t) function clCreateContextFromType(properties, & + device_type, & + pfn_notify, & + user_data, & + errcode_ret) & + BIND(C, NAME='clCreateContextFromType') + USE ISO_C_BINDING + + ! Define parameters. + type(c_ptr), value :: properties + integer(c_int64_t), value :: device_type + type(c_funptr), value :: pfn_notify + type(c_ptr), value :: user_data + integer(c_int32_t), intent(out) :: errcode_ret + + end function + + ! clRetainContext + integer(c_int32_t) function clRetainContext(context) & + BIND(C, NAME='clRetainContext') + USE ISO_C_BINDING + + ! Define parameters. + integer(c_intptr_t), value :: context + + end function + + ! clReleaseContext + integer(c_int32_t) function clReleaseContext(context) & + BIND(C, NAME='clReleaseContext') + USE ISO_C_BINDING + + ! Define parameters. + integer(c_intptr_t), value :: context + + end function + + ! clGetContextInfo + integer(c_int32_t) function clGetContextInfo(context, & + param_name, & + param_value_size, & + param_value, & + param_value_size_ret) & + BIND(C, NAME='clGetContextInfo') + USE ISO_C_BINDING + + ! Define parameters. + integer(c_intptr_t), value :: context + integer(c_int32_t), value :: param_name + integer(c_size_t), value :: param_value_size + type(c_ptr), value :: param_value + integer(c_size_t), intent(out) :: param_value_size_ret + + end function + + ! ------------------ + ! Command Queue APIs + ! ------------------ + + ! clCreateCommandQueue + integer(c_intptr_t) function clCreateCommandQueue(context, & + device, & + properties, & + errcode_ret) & + BIND(C, NAME='clCreateCommandQueue') + USE ISO_C_BINDING + + ! Define parameters. + integer(c_intptr_t), value :: context + integer(c_intptr_t), value :: device + integer(c_int64_t), value :: properties + integer(c_int32_t), intent(out) :: errcode_ret + + end function + + ! clRetainCommandQueue + integer(c_int32_t) function clRetainCommandQueue(command_queue) & + BIND(C, NAME='clRetainCommandQueue') + USE ISO_C_BINDING + + ! Define parameters. + integer(c_intptr_t), value :: command_queue + + end function + + ! clReleaseCommandQueue + integer(c_int32_t) function clReleaseCommandQueue(command_queue) & + BIND(C, NAME='clReleaseCommandQueue') + USE ISO_C_BINDING + + ! Define parameters. + integer(c_intptr_t), value :: command_queue + + end function + + ! clGetCommandQueueInfo + integer(c_int32_t) function clGetCommandQueueInfo(command_queue, & + param_name, & + param_value_size, & + param_value, & + param_value_size_ret) & + BIND(C, NAME='clGetCommandQueueInfo') + USE ISO_C_BINDING + + ! Define parameters. + integer(c_intptr_t), value :: command_queue + integer(c_int32_t), value :: param_name + integer(c_size_t), value :: param_value_size + type(c_ptr), value :: param_value + integer(c_size_t), intent(out) :: param_value_size_ret + + end function + + ! ------------------ + ! Memory Object APIs + ! ------------------ + + ! clCreateBuffer + integer(c_intptr_t) function clCreateBuffer(context, & + flags, & + sizeb, & + host_ptr, & + errcode_ret) & + BIND(C, NAME='clCreateBuffer') + USE ISO_C_BINDING + + ! Define parameters. + integer(c_intptr_t), value :: context + integer(c_int64_t), value :: flags + integer(c_size_t), value :: sizeb + type(c_ptr), value :: host_ptr + integer(c_int32_t), intent(out) :: errcode_ret + + end function + + ! clCreateSubBuffer + integer(c_intptr_t) function clCreateSubBuffer(buffer, & + flags, & + buffer_create_type, & + buffer_create_info, & + errcode_ret) & + BIND(C, NAME='clCreateSubBuffer') + USE ISO_C_BINDING + + ! Define parameters. + integer(c_intptr_t), value :: buffer + integer(c_int64_t), value :: flags + integer(c_int32_t), value :: buffer_create_type + type(c_ptr), value :: buffer_create_info + integer(c_int32_t), intent(out) :: errcode_ret + + end function + + ! clCreateImage + integer(c_intptr_t) function clCreateImage(context, & + flags, & + image_format, & + image_desc, & + host_ptr, & + errcode_ret) & + BIND(C, NAME='clCreateImage') + USE ISO_C_BINDING + + ! Define parameters. + integer(c_intptr_t), value :: context + integer(c_int64_t), value :: flags + type(cl_image_format) :: image_format + type(cl_image_desc) :: image_desc + type(c_ptr), value :: host_ptr + integer(c_int32_t), intent(out) :: errcode_ret + + end function + + ! clRetainMemObject + integer(c_int32_t) function clRetainMemObject(mem_obj) & + BIND(C, NAME='clRetainMemObject') + + ! Define parameters. + integer(c_intptr_t), value :: mem_obj + + end function + + ! clReleaseMemObject + integer(c_int32_t) function clReleaseMemObject(mem_obj) & + BIND(C, NAME='clReleaseMemObject') + + ! Define parameters. + integer(c_intptr_t), value :: mem_obj + + end function + + ! clGetSupportedImageFormats + integer(c_int32_t) function clGetSupportedImageFormats(context, & + flags, & + image_type, & + num_entries, & + image_formats, & + num_image_formats) & + BIND(C, NAME='clGetSupportedImageFormats') + + ! Define parameters. + integer(c_intptr_t), value :: context + integer(c_int64_t), value :: flags + integer(c_int32_t), value :: image_type + integer(c_int32_t), value :: num_entries + type(c_ptr), value :: image_formats + integer(c_int32_t), intent(out) :: num_image_formats + + end function + + ! clGetMemObjectInfo + integer(c_int32_t) function clGetMemObjectInfo(memobj, & + param_name, & + param_value_size, & + param_value, & + param_value_size_ret) & + BIND(C, NAME='clGetMemObjectInfo') + + ! Define parameters. + integer(c_intptr_t), value :: memobj + integer(c_int32_t), value :: param_name + integer(c_size_t), value :: param_value_size + type(c_ptr), value :: param_value + integer(c_size_t), intent(out) :: param_value_size_ret + + end function + + ! clGetImageInfo + integer(c_int32_t) function clGetImageInfo(image, & + param_name, & + param_value_size, & + param_value, & + param_value_size_ret) & + BIND(C, NAME='clGetImageInfo') + + ! Define parameters. + integer(c_intptr_t), value :: image + integer(c_int32_t), value :: param_name + integer(c_size_t), value :: param_value_size + type(c_ptr), value :: param_value + integer(c_size_t), intent(out) :: param_value_size_ret + + end function + + ! clSetMemObjectDestructorCallback + integer(c_int32_t) function clSetMemObjectDestructorCallback(memobj, & + pfn_notify, & + user_data) & + BIND(C, NAME='clSetMemObjectDestructorCallback') + + ! Define parameters. + integer(c_intptr_t), value :: memobj + type(c_funptr), value :: pfn_notify + type(c_ptr), value :: user_data + + end function + + ! ------------ + ! Sampler APIs + ! ------------ + + ! clCreateSampler + integer(c_intptr_t) function clCreateSampler(context, & + normalized_coords, & + addressing_mode, & + filter_mode, & + errcode_ret) & + BIND(C, NAME='clCreateSampler') + + ! Define parameters. + integer(c_intptr_t), value :: context + integer(c_int32_t), value :: normalized_coords + integer(c_int32_t), value :: addressing_mode + integer(c_int32_t), value :: filter_mode + integer(c_int32_t), intent(out) :: errcode_ret + + end function + + ! clRetainSampler + integer(c_int32_t) function clRetainSampler(sampler) & + BIND(C, NAME='clRetainSampler') + + ! Define parameters. + integer(c_intptr_t), value :: sampler + + end function + + ! clReleaseSampler + integer(c_int32_t) function clReleaseSampler(sampler) & + BIND(C, NAME='clReleaseSampler') + + ! Define parameters. + integer(c_intptr_t), value :: sampler + + end function + + ! clGetSamplerInfo + integer(c_int32_t) function clGetSamplerInfo(sampler, & + param_name, & + param_value_size, & + param_value, & + param_value_size_ret) & + BIND(C, NAME='clGetSamplerInfo') + + ! Define parameters. + integer(c_intptr_t), value :: sampler + integer(c_int32_t), value :: param_name + integer(c_size_t), value :: param_value_size + type(c_ptr), value :: param_value + integer(c_size_t), intent(out) :: param_value_size_ret + + end function + + ! ------------------- + ! Program Object APIs + ! ------------------- + + ! clCreateProgramWithSource + integer(c_intptr_t) function clCreateProgramWithSource(context, & + count, & + strings, & + lengths, & + errcode_ret) & + BIND(C, NAME='clCreateProgramWithSource') + + ! Define parameters. + integer(c_intptr_t), value :: context + integer(c_int32_t), value :: count + type(c_ptr), value :: strings + type(c_ptr), value :: lengths + integer(c_int32_t), intent(out) :: errcode_ret + + end function + + ! clCreateProgramWithBinary + integer(c_intptr_t) function clCreateProgramWithBinary(context, & + num_devices, & + device_list, & + lengths, & + binaries, & + binary_status, & + errcode_ret) & + BIND(C, NAME='clCreateProgramWithBinary') + + ! Define parameters. + integer(c_intptr_t), value :: context + integer(c_int32_t), value :: num_devices + type(c_ptr), value :: device_list + type(c_ptr), value :: lengths + type(c_ptr), value :: binaries + type(c_ptr), value :: binary_status + integer(c_int32_t), intent(out) :: errcode_ret + + end function + + ! clCreateProgramWithBuiltInKernels + integer(c_intptr_t) function clCreateProgramWithBuiltInKernels(context, & + num_devices, & + device_list, & + kernel_names, & + errcode_ret) & + BIND(C, NAME='clCreateProgramWithBuiltInKernels') + + ! Define parameters. + integer(c_intptr_t), value :: context + integer(c_int32_t), value :: num_devices + type(c_ptr), value :: device_list + type(c_ptr), value :: kernel_names + integer(c_int32_t), intent(out) :: errcode_ret + + end function + + ! clRetainProgram + integer(c_int32_t) function clRetainProgram(program) & + BIND(C, NAME='clRetainProgram') + + ! Define parameters. + integer(c_intptr_t), value :: program + + end function + + ! clReleaseProgram + integer(c_int32_t) function clReleaseProgram(program) & + BIND(C, NAME='clReleaseProgram') + + ! Define parameters. + integer(c_intptr_t), value :: program + + end function + + ! clBuildProgram + integer(c_int32_t) function clBuildProgram(program, & + num_devices, & + device_list, & + options, & + pfn_notify, & + user_data) & + BIND(C, NAME='clBuildProgram') + + ! Define parameters. + integer(c_intptr_t), value :: program + integer(c_int32_t), value :: num_devices + type(c_ptr), value :: device_list + type(c_ptr), value :: options + type(c_funptr), value :: pfn_notify + type(c_ptr), value :: user_data + + end function + + ! clCompileProgram + integer(c_int32_t) function clCompileProgram(program, & + num_devices, & + device_list, & + options, & + num_input_headers, & + input_headers, & + header_include_names, & + pfn_notify, & + user_data) & + BIND(C, NAME='clCompileProgram') + + ! Define parameters. + integer(c_intptr_t), value :: program + integer(c_int32_t), value :: num_devices + type(c_ptr), value :: device_list + type(c_ptr), value :: options + integer(c_int32_t), value :: num_input_headers + type(c_ptr), value :: input_headers + type(c_ptr), value :: header_include_names + type(c_funptr), value :: pfn_notify + type(c_ptr), value :: user_data + + end function + + ! clLinkProgram + integer(c_intptr_t) function clLinkProgram(context, & + num_devices, & + device_list, & + options, & + num_input_programs, & + input_programs, & + pfn_notify, & + user_data, & + errcode_ret) & + BIND(C, NAME='clLinkProgram') + + ! Define parameters. + integer(c_intptr_t), value :: context + integer(c_int32_t), value :: num_devices + type(c_ptr), value :: device_list + type(c_ptr), value :: options + integer(c_int32_t), value :: num_input_programs + type(c_ptr), value :: input_programs + type(c_funptr), value :: pfn_notify + type(c_ptr), value :: user_data + integer(c_int32_t), intent(out) :: errcode_ret + + end function + + ! clUnloadPlatformCompiler + integer(c_int32_t) function clUnloadPlatformCompiler(platform) & + BIND(C, NAME='clUnloadPlatformCompiler') + + ! Define parameters. + integer(c_intptr_t), value :: platform + + end function + + ! clGetProgramInfo + integer(c_int32_t) function clGetProgramInfo(program, & + param_name, & + param_value_size, & + param_value, & + param_value_size_ret) & + BIND(C, NAME='clGetProgramInfo') + + ! Define parameters. + integer(c_intptr_t), value :: program + integer(c_int32_t), value :: param_name + integer(c_size_t), value :: param_value_size + type(c_ptr), value :: param_value + integer(c_size_t), intent(out) :: param_value_size_ret + + end function + + ! clGetProgramBuildInfo + integer(c_int32_t) function clGetProgramBuildInfo(program, & + device, & + param_name, & + param_value_size, & + param_value, & + param_value_size_ret) & + BIND(C, NAME='clGetProgramBuildInfo') + + ! Define parameters. + integer(c_intptr_t), value :: program + integer(c_intptr_t), value :: device + integer(c_int32_t), value :: param_name + integer(c_size_t), value :: param_value_size + type(c_ptr), value :: param_value + integer(c_size_t), intent(out) :: param_value_size_ret + + end function + + ! ------------------ + ! Kernel Object APIs + ! ------------------ + + ! clCreateKernel + integer(c_intptr_t) function clCreateKernel(program, & + kernel_name, & + errcode_ret) & + BIND(C, NAME='clCreateKernel') + + ! Define parameters. + integer(c_intptr_t), value :: program + type(c_ptr), value :: kernel_name + integer(c_int32_t), intent(out) :: errcode_ret + + end function + + ! clCreateKernelsInProgram + integer(c_int32_t) function clCreateKernelsInProgram(program, & + num_kernels, & + kernels, & + num_kernels_ret) & + BIND(C, NAME='clCreateKernelsInProgram') + + ! Define parameters. + integer(c_intptr_t), value :: program + integer(c_int32_t), value :: num_kernels + type(c_ptr), value :: kernels + integer(c_int32_t), intent(out) :: num_kernels_ret + + end function + + ! clRetainKernel + integer(c_int32_t) function clRetainKernel(kernel) & + BIND(C, NAME='clRetainKernel') + + ! Define parameters. + integer(c_intptr_t), value :: kernel + + end function + + ! clReleaseKernel + integer(c_int32_t) function clReleaseKernel(kernel) & + BIND(C, NAME='clReleaseKernel') + + ! Define parameters. + integer(c_intptr_t), value :: kernel + + end function + + ! clSetKernelArg + integer(c_int32_t) function clSetKernelArg(kernel, & + arg_index, & + arg_size, & + arg_value) & + BIND(C, NAME='clSetKernelArg') + + ! Define parameters. + integer(c_intptr_t), value :: kernel + integer(c_int32_t), value :: arg_index + integer(c_size_t), value :: arg_size + type(c_ptr), value :: arg_value + + end function + + ! clGetKernelInfo + integer(c_int32_t) function clGetKernelInfo(kernel, & + param_name, & + param_value_size, & + param_value, & + param_value_size_ret) & + BIND(C, NAME='clGetKernelInfo') + + ! Define parameters. + integer(c_intptr_t), value :: kernel + integer(c_int32_t), value :: param_name + integer(c_size_t), value :: param_value_size + type(c_ptr), value :: param_value + integer(c_size_t), intent(out) :: param_value_size_ret + + end function + + ! clGetKernelArgInfo + integer(c_int32_t) function clGetKernelArgInfo(kernel, & + arg_index, & + param_name, & + param_value_size, & + param_value, & + param_value_size_ret) & + BIND(C, NAME='clGetKernelArgInfo') + + ! Define parameters. + integer(c_intptr_t), value :: kernel + integer(c_int32_t), value :: arg_index + integer(c_int32_t), value :: param_name + integer(c_size_t), value :: param_value_size + type(c_ptr), value :: param_value + integer(c_size_t), intent(out) :: param_value_size_ret + + end function + + ! clGetKernelWorkGroupInfo + integer(c_int32_t) function clGetKernelWorkGroupInfo(kernel, & + device, & + param_name, & + param_value_size, & + param_value, & + param_value_size_ret) & + BIND(C, NAME='clGetKernelWorkGroupInfo') + + ! Define parameters. + integer(c_intptr_t), value :: kernel + integer(c_intptr_t), value :: device + integer(c_int32_t), value :: param_name + integer(c_size_t), value :: param_value_size + type(c_ptr), value :: param_value + integer(c_size_t), intent(out) :: param_value_size_ret + + end function + + ! ----------------- + ! Event Object APIs + ! ----------------- + + ! clWaitForEvents + integer(c_int32_t) function clWaitForEvents(num_events, & + event_list) & + BIND(C, NAME='clWaitForEvents') + + ! Define parameters. + integer(c_int32_t), value :: num_events + type(c_ptr), value :: event_list + + end function + + ! clGetEventInfo + integer(c_int32_t) function clGetEventInfo(event, & + param_name, & + param_value_size, & + param_value, & + param_value_size_ret) & + BIND(C, NAME='clGetEventInfo') + + ! Define parameters. + integer(c_intptr_t), value :: event + integer(c_int32_t), value :: param_name + integer(c_size_t), value :: param_value_size + type(c_ptr), value :: param_value + integer(c_size_t), intent(out) :: param_value_size_ret + + end function + + ! clCreateUserEvent + integer(c_intptr_t) function clCreateUserEvent(context, & + errcode_ret) & + BIND(C, NAME='clCreateUserEvent') + + ! Define parameters. + integer(c_int32_t), value :: context + integer(c_int32_t), intent(out) :: errcode_ret + + end function + + ! clRetainEvent + integer(c_int32_t) function clRetainEvent(event) & + BIND(C, NAME='clRetainEvent') + + ! Define parameters. + integer(c_intptr_t), value :: event + + end function + + ! clReleaseEvent + integer(c_int32_t) function clReleaseEvent(event) & + BIND(C, NAME='clReleaseEvent') + + ! Define parameters. + integer(c_intptr_t), value :: event + + end function + + ! clSetUserEventStatus + integer(c_int32_t) function clSetUserEventStatus(event, & + execution_status) & + BIND(C, NAME='clSetUserEventStatus') + + ! Define parameters. + integer(c_intptr_t), value :: event + integer(c_int32_t), value :: execution_status + + end function + + ! clSetEventCallback + integer(c_int32_t) function clSetEventCallback(event, & + command_exec_callback_type, & + pfn_notify, & + user_data) & + BIND(C, NAME='clSetEventCallback') + + ! Define parameters. + integer(c_intptr_t), value :: event + integer(c_int32_t), value :: command_exec_callback_type + type(c_funptr), value :: pfn_notify + type(c_ptr), value :: user_data + + end function + + ! -------------- + ! Profiling APIs + ! -------------- + + ! clGetEventProfilingInfo + integer(c_int32_t) function clGetEventProfilingInfo(event, & + param_name, & + param_value_size, & + param_value, & + param_value_size_ret) & + BIND(C, NAME='clGetEventProfilingInfo') + + ! Define parameters. + integer(c_intptr_t), value :: event + integer(c_int32_t), value :: param_name + integer(c_size_t), value :: param_value_size + type(c_ptr), value :: param_value + integer(c_size_t), intent(out) :: param_value_size_ret + + end function + + ! --------------------- + ! Flush and Finish APIs + ! --------------------- + + ! clFlush + integer(c_int32_t) function clFlush(command_queue) & + BIND(C, NAME='clFlush') + + ! Define parameters. + integer(c_intptr_t), value :: command_queue + + end function + + ! clFinish + integer(c_int32_t) function clFinish(command_queue) & + BIND(C, NAME='clFinish') + + ! Define parameters. + integer(c_intptr_t), value :: command_queue + + end function + + ! ---------------------- + ! Enqueued Commands APIs + ! ---------------------- + + ! clEnqueueReadBuffer + integer(c_int32_t) function clEnqueueReadBuffer(command_queue, & + buffer, & + blocking_read, & + offset, & + size, & + ptr, & + num_events_in_wait_list, & + event_wait_list, & + event) & + BIND(C, NAME='clEnqueueReadBuffer') + + ! Define parameters. + integer(c_intptr_t), value :: command_queue + integer(c_intptr_t), value :: buffer + integer(c_int32_t), value :: blocking_read + integer(c_size_t), value :: offset + integer(c_size_t), value :: size + type(c_ptr), value :: ptr + integer(c_int32_t), value :: num_events_in_wait_list + type(c_ptr), value :: event_wait_list + type(c_ptr), value :: event + + end function + + ! clEnqueueReadBufferRect + integer(c_int32_t) function clEnqueueReadBufferRect(command_queue, & + buffer, & + blocking_read, & + buffer_offset, & + host_offset, & + region, & + buffer_row_pitch, & + buffer_slice_pitch, & + host_row_pitch, & + host_slice_pitch, & + ptr, & + num_events_in_wait_list, & + event_wait_list, & + event) & + BIND(C, NAME='clEnqueueReadBufferRect') + + ! Define parameters. + integer(c_intptr_t), value :: command_queue + + integer(c_intptr_t), value :: buffer + integer(c_int32_t), value :: blocking_read + type(c_ptr), value :: buffer_offset + type(c_ptr), value :: host_offset + type(c_ptr), value :: region + integer(c_size_t), value :: buffer_row_pitch + integer(c_size_t), value :: buffer_slice_pitch + integer(c_size_t), value :: host_row_pitch + integer(c_size_t), value :: host_slice_pitch + type(c_ptr), value :: ptr + integer(c_int32_t), value :: num_events_in_wait_list + type(c_ptr), value :: event_wait_list + type(c_ptr), value :: event + + end function + + ! clEnqueueWriteBuffer + integer(c_int32_t) function clEnqueueWriteBuffer(command_queue, & + buffer, & + blocking_write, & + offset, & + size, & + ptr, & + num_events_in_wait_list, & + event_wait_list, & + event) & + BIND(C, NAME='clEnqueueWriteBuffer') + + ! Define parameters. + integer(c_intptr_t), value :: command_queue + integer(c_intptr_t), value :: buffer + integer(c_int32_t), value :: blocking_write + integer(c_size_t), value :: offset + integer(c_size_t), value :: size + type(c_ptr), value :: ptr + integer(c_int32_t), value :: num_events_in_wait_list + type(c_ptr), value :: event_wait_list + type(c_ptr), value :: event + + end function + + ! clEnqueueWriteBufferRect + integer(c_int32_t) function clEnqueueWriteBufferRect(command_queue, & + buffer, & + blocking_write, & + buffer_offset, & + host_offset, & + region, & + buffer_row_pitch, & + buffer_slice_pitch, & + host_row_pitch, & + host_slice_pitch, & + ptr, & + num_events_in_wait_list, & + event_wait_list, & + event) & + BIND(C, NAME='clEnqueueWriteBufferRect') + + ! Define parameters. + integer(c_intptr_t), value :: command_queue + integer(c_intptr_t), value :: buffer + integer(c_int32_t), value :: blocking_write + type(c_ptr), value :: buffer_offset + type(c_ptr), value :: host_offset + type(c_ptr), value :: region + integer(c_size_t), value :: buffer_row_pitch + integer(c_size_t), value :: buffer_slice_pitch + integer(c_size_t), value :: host_row_pitch + integer(c_size_t), value :: host_slice_pitch + type(c_ptr), value :: ptr + integer(c_int32_t), value :: num_events_in_wait_list + type(c_ptr), value :: event_wait_list + type(c_ptr), value :: event + + end function + + ! clEnqueueFillBuffer + integer(c_int32_t) function clEnqueueFillBuffer(command_queue, & + buffer, & + pattern, & + pattern_size, & + offset, & + size, & + num_events_in_wait_list, & + event_wait_list, & + event) & + BIND(C, NAME='clEnqueueFillBuffer') + + ! Define parameters. + integer(c_intptr_t), value :: command_queue + integer(c_intptr_t), value :: buffer + type(c_ptr), value :: pattern + integer(c_size_t), value :: pattern_size + integer(c_size_t), value :: offset + integer(c_size_t), value :: size + integer(c_int32_t), value :: num_events_in_wait_list + type(c_ptr), value :: event_wait_list + type(c_ptr), value :: event + + end function + + ! clEnqueueCopyBuffer + integer(c_int32_t) function clEnqueueCopyBuffer(command_queue, & + src_buffer, & + dst_buffer, & + src_offset, & + dst_offset, & + size, & + num_events_in_wait_list, & + event_wait_list, & + event) & + BIND(C, NAME='clEnqueueCopyBuffer') + + ! Define parameters. + integer(c_intptr_t), value :: command_queue + integer(c_intptr_t), value :: src_buffer + integer(c_intptr_t), value :: dst_buffer + integer(c_size_t), value :: src_offset + integer(c_size_t), value :: dst_offset + integer(c_size_t), value :: size + integer(c_int32_t), value :: num_events_in_wait_list + type(c_ptr), value :: event_wait_list + type(c_ptr), value :: event + + end function + + ! clEnqueueCopyBufferRect + integer(c_int32_t) function clEnqueueCopyBufferRect(command_queue, & + src_buffer, & + dst_buffer, & + src_origin, & + dst_origin, & + region, & + src_row_pitch, & + src_slice_pitch, & + dst_row_pitch, & + dst_slice_pitch, & + num_events_in_wait_list, & + event_wait_list, & + event) & + BIND(C, NAME='clEnqueueCopyBufferRect') + + ! Define parameters. + integer(c_intptr_t), value :: command_queue + integer(c_intptr_t), value :: src_buffer + integer(c_intptr_t), value :: dst_buffer + type(c_ptr), value :: src_origin + type(c_ptr), value :: dst_origin + type(c_ptr), value :: region + integer(c_size_t), value :: src_row_pitch + integer(c_size_t), value :: src_slice_pitch + integer(c_size_t), value :: dst_row_pitch + integer(c_size_t), value :: dst_slice_pitch + integer(c_int32_t), value :: num_events_in_wait_list + type(c_ptr), value :: event_wait_list + type(c_ptr), value :: event + + end function + + ! clEnqueueReadImage + integer(c_int32_t) function clEnqueueReadImage(command_queue, & + image, & + blocking_read, & + origin, & + region, & + row_pitch, & + slice_pitch, & + ptr, & + num_events_in_wait_list, & + event_wait_list, & + event) & + BIND(C, NAME='clEnqueueReadImage') + + ! Define parameters. + integer(c_intptr_t), value :: command_queue + integer(c_intptr_t), value :: image + integer(c_int32_t), value :: blocking_read + type(c_ptr), value :: origin + type(c_ptr), value :: region + integer(c_size_t), value :: row_pitch + integer(c_size_t), value :: slice_pitch + type(c_ptr), value :: ptr + integer(c_int32_t), value :: num_events_in_wait_list + type(c_ptr), value :: event_wait_list + type(c_ptr), value :: event + + end function + + ! clEnqueueWriteImage + integer(c_int32_t) function clEnqueueWriteImage(command_queue, & + image, & + blocking_write, & + origin, & + region, & + input_row_pitch, & + input_slice_pitch, & + ptr, & + num_events_in_wait_list, & + event_wait_list, & + event) & + BIND(C, NAME='clEnqueueWriteImage') + + ! Define parameters. + integer(c_intptr_t), value :: command_queue + integer(c_intptr_t), value :: image + integer(c_int32_t), value :: blocking_write + type(c_ptr), value :: origin + type(c_ptr), value :: region + integer(c_size_t), value :: input_row_pitch + integer(c_size_t), value :: input_slice_pitch + type(c_ptr), value :: ptr + integer(c_int32_t), value :: num_events_in_wait_list + type(c_ptr), value :: event_wait_list + type(c_ptr), value :: event + + end function + + ! clEnqueueFillImage + integer(c_int32_t) function clEnqueueFillImage(command_queue, & + image, & + fill_color, & + origin, & + region, & + num_events_in_wait_list, & + event_wait_list, & + event) & + BIND(C, NAME='clEnqueueFillImage') + + ! Define parameters. + integer(c_intptr_t), value :: command_queue + integer(c_intptr_t), value :: image + type(c_ptr), value :: fill_color + type(c_ptr), value :: origin + type(c_ptr), value :: region + integer(c_int32_t), value :: num_events_in_wait_list + type(c_ptr), value :: event_wait_list + type(c_ptr), value :: event + + end function + + ! clEnqueueCopyImage + integer(c_int32_t) function clEnqueueCopyImage(command_queue, & + src_image, & + dst_image, & + src_origin, & + dst_origin, & + region, & + num_events_in_wait_list, & + event_wait_list, & + event) & + BIND(C, NAME='clEnqueueCopyImage') + + ! Define parameters. + integer(c_intptr_t), value :: command_queue + integer(c_intptr_t), value :: src_image + integer(c_intptr_t), value :: dst_image + type(c_ptr), value :: src_origin + type(c_ptr), value :: dst_origin + type(c_ptr), value :: region + integer(c_int32_t), value :: num_events_in_wait_list + type(c_ptr), value :: event_wait_list + type(c_ptr), value :: event + + end function + + ! clEnqueueCopyImageToBuffer + integer(c_int32_t) function clEnqueueCopyImageToBuffer(command_queue, & + src_image, & + dst_buffer, & + src_origin, & + region, & + dst_offset, & + num_events_in_wait_list, & + event_wait_list, & + event) & + BIND(C, NAME='clEnqueueCopyImageToBuffer') + + ! Define parameters. + integer(c_intptr_t), value :: command_queue + integer(c_intptr_t), value :: src_image + integer(c_intptr_t), value :: dst_buffer + type(c_ptr), value :: src_origin + type(c_ptr), value :: region + integer(c_size_t), value :: dst_offset + integer(c_int32_t), value :: num_events_in_wait_list + type(c_ptr), value :: event_wait_list + type(c_ptr), value :: event + + end function + + ! clEnqueueCopyBufferToImage + integer(c_int32_t) function clEnqueueCopyBufferToImage(command_queue, & + src_buffer, & + dst_image, & + src_offset, & + dst_origin, & + region, & + num_events_in_wait_list, & + event_wait_list, & + event) & + BIND(C, NAME='clEnqueueCopyBufferToImage') + + ! Define parameters. + integer(c_intptr_t), value :: command_queue + integer(c_intptr_t), value :: src_buffer + integer(c_intptr_t), value :: dst_image + integer(c_size_t), value :: src_offset + type(c_ptr), value :: dst_origin + type(c_ptr), value :: region + integer(c_int32_t), value :: num_events_in_wait_list + type(c_ptr), value :: event_wait_list + type(c_ptr), value :: event + + end function + + ! clEnqueueMapBuffer + type(c_ptr) function clEnqueueMapBuffer(command_queue, & + buffer, & + blocking_map, & + map_flags, & + offset, & + size, & + num_events_in_wait_list, & + event_wait_list, & + event, & + errcode_ret) & + BIND(C, NAME='clEnqueueMapBuffer') + + ! Define parameters. + integer(c_intptr_t), value :: command_queue + integer(c_intptr_t), value :: buffer + integer(c_int32_t), value :: blocking_map + integer(c_int64_t), value :: map_flags + integer(c_size_t), value :: offset + integer(c_size_t), value :: size + integer(c_int32_t), value :: num_events_in_wait_list + type(c_ptr), value :: event_wait_list + type(c_ptr), value :: event + integer(c_int32_t), intent(out) :: errcode_ret + + end function + + ! clEnqueueMapImage + type(c_ptr) function clEnqueueMapImage(command_queue, & + image, & + blocking_map, & + map_flags, & + origin, & + region, & + image_row_pitch, & + image_slice_pitch, & + num_events_in_wait_list, & + event_wait_list, & + event, & + errcode_ret) & + BIND(C, NAME='clEnqueueMapImage') + + ! Define parameters. + integer(c_intptr_t), value :: command_queue + integer(c_intptr_t), value :: image + integer(c_int32_t), value :: blocking_map + integer(c_int64_t), value :: map_flags + type(c_ptr), value :: origin + type(c_ptr), value :: region + integer(c_size_t), intent(out) :: image_row_pitch + integer(c_size_t), intent(out) :: image_slice_pitch + integer(c_int32_t), value :: num_events_in_wait_list + type(c_ptr), value :: event_wait_list + type(c_ptr), value :: event + integer(c_int32_t), intent(out) :: errcode_ret + + end function + + ! clEnqueueUnmapMemObject + integer(c_int32_t) function clEnqueueUnmapMemObject(command_queue, & + memobj, & + mapped_ptr, & + num_events_in_wait_list, & + event_wait_list, & + event) & + BIND(C, NAME='clEnqueueUnmapMemObject') + + ! Define parameters. + integer(c_intptr_t), value :: command_queue + integer(c_intptr_t), value :: memobj + type(c_ptr), value :: mapped_ptr + integer(c_int32_t), value :: num_events_in_wait_list + type(c_ptr), value :: event_wait_list + type(c_ptr), value :: event + + end function + + ! clEnqueueMigrateMemObjects + integer(c_int32_t) function clEnqueueMigrateMemObjects(command_queue, & + num_mem_objects, & + mem_objects, & + flags, & + num_events_in_wait_list, & + event_wait_list, & + event) & + BIND(C, NAME='clEnqueueMigrateMemObjects') + + ! Define parameters. + integer(c_intptr_t), value :: command_queue + integer(c_int32_t), value :: num_mem_objects + type(c_ptr), value :: mem_objects + integer(c_int64_t), value :: flags + integer(c_int32_t), value :: num_events_in_wait_list + type(c_ptr), value :: event_wait_list + type(c_ptr), value :: event + + end function + + ! clEnqueueNDRangeKernel. + integer(c_int32_t) function clEnqueueNDRangeKernel(command_queue, & + kernel, & + work_dim, & + global_work_offset, & + global_work_size, & + local_work_size, & + num_events_in_wait_list, & + event_wait_list, & + event) & + BIND(C, NAME='clEnqueueNDRangeKernel') + + ! Define parameters. + integer(c_intptr_t), value :: command_queue + integer(c_intptr_t), value :: kernel + integer(c_int32_t), value :: work_dim + type(c_ptr), value :: global_work_offset + type(c_ptr), value :: global_work_size + type(c_ptr), value :: local_work_size + integer(c_int32_t), value :: num_events_in_wait_list + type(c_ptr), value :: event_wait_list + type(c_ptr), value :: event + + end function + + ! clEnqueueTask + integer(c_int32_t) function clEnqueueTask(command_queue, & + kernel, & + num_events_in_wait_list, & + event_wait_list, & + event) & + BIND(C, NAME='clEnqueueTask') + + ! Define parameters. + integer(c_intptr_t), value :: command_queue + integer(c_intptr_t), value :: kernel + integer(c_int32_t), value :: num_events_in_wait_list + type(c_ptr), value :: event_wait_list + type(c_ptr), value :: event + + end function + + ! clEnqueueNativeKernel + integer(c_int32_t) function clEnqueueNativeKernel(command_queue, & + user_func, & + args, & + cb_args, & + num_mem_objects, & + mem_list, & + args_mem_loc, & + num_events_in_wait_list, & + event_wait_list, & + event) & + BIND(C, NAME='clEnqueueNativeKernel') + + ! Define parameters. + integer(c_intptr_t), value :: command_queue + type(c_funptr), value :: user_func + type(c_ptr), value :: args + integer(c_size_t), value :: cb_args + integer(c_int32_t), value :: num_mem_objects + type(c_ptr), value :: mem_list + type(c_ptr), value :: args_mem_loc + integer(c_int32_t), value :: num_events_in_wait_list + type(c_ptr), value :: event_wait_list + type(c_ptr), value :: event + + end function + + ! clEnqueueMarkerWithWaitList + integer(c_int32_t) function clEnqueueMarkerWithWaitList(command_queue, & + num_events_in_wait_list, & + event_wait_list, & + event) & + BIND(C, NAME='clEnqueueMarkerWithWaitList') + + ! Define parameters. + integer(c_intptr_t), value :: command_queue + integer(c_int32_t), value :: num_events_in_wait_list + type(c_ptr), value :: event_wait_list + type(c_ptr), value :: event + + end function + + ! clEnqueueMarkerWithWaitList + integer(c_int32_t) function clEnqueueBarrierWithWaitList(command_queue, & + num_events_in_wait_list, & + event_wait_list, & + event) & + BIND(C, NAME='clEnqueueBarrierWithWaitList') + + ! Define parameters. + integer(c_intptr_t), value :: command_queue + integer(c_int32_t), value :: num_events_in_wait_list + type(c_ptr), value :: event_wait_list + type(c_ptr), value :: event + + end function + + ! clSetPrintfCallback + integer(c_int32_t) function clSetPrintfCallback(context, & + pfn_notify, & + user_data) & + BIND(C, NAME='clSetPrintfCallback') + + ! Define parameters. + integer(c_intptr_t), value :: context + type(c_funptr), value :: pfn_notify + type(c_ptr), value :: user_data + + end function + + ! ------------------------- + ! Extension function access + ! ------------------------- + type(c_funptr) function clGetExtensionFunctionAddressForPlatform(platform, & + func_name) & + BIND(C, NAME='clGetExtensionFunctionAddressForPlatform') + + ! Define parameters. + integer(c_intptr_t), value :: platform + type(c_ptr), value :: func_name + + end function + + ! + !end interface + ! + +end module clfortran diff --git a/lib/opencl/ocl_env_mod.f90 b/lib/opencl/ocl_env_mod.f90 new file mode 100644 index 0000000000..5d17b63950 --- /dev/null +++ b/lib/opencl/ocl_env_mod.f90 @@ -0,0 +1,147 @@ +!> Module containing state and utilities for managing OpenCL device +module ocl_env_mod + use iso_c_binding, only: c_intptr_t + use ocl_utils_mod, only: CL_UTIL_STR_LEN, init_device + use ocl_params_mod + implicit none + + private + + !> Pointer to the OpenCL device being used + integer(c_intptr_t) :: cl_device + !> The OpenCL context + integer(c_intptr_t) :: cl_context + !> Version of OpenCL supported by the device + character(len=CL_UTIL_STR_LEN) :: cl_version_str + + !> Number of OpenCL command queues + integer, save :: cl_num_queues + !> Array of command queues - used to achieve concurrent execution + integer(c_intptr_t), allocatable, target :: cl_cmd_queues(:) + + !> The OpenCL kernels used by the model + integer, save :: cl_num_kernels + character(len=CL_UTIL_STR_LEN), allocatable :: cl_kernel_names(:) + integer(c_intptr_t), target, allocatable :: cl_kernels(:) + + public ocl_env_init + public cl_context, cl_device, get_num_cmd_queues, get_cmd_queues + +contains + + !=================================================== + + !> Initialise the GOcean environment + subroutine ocl_env_init(opencl) + use ocl_utils_mod, only: init_device + implicit none + !> Whether or not to use OpenCL + logical, optional :: opencl + integer :: ierr + + ! Initialise the OpenCL device + call init_device(cl_device, cl_version_str, cl_context) + ! Create command queue(s) + cl_num_queues = 4 + allocate(cl_cmd_queues(cl_num_queues), Stat=ierr) + if(ierr /= 0)then + stop "Failed to allocate list for OpenCL command queues" + end if + call init_cmd_queues(cl_num_queues, cl_cmd_queues, cl_context, cl_device) + + end subroutine ocl_env_init + + !=================================================== + + subroutine add_kernels(nkernels, kernel_names, filename) + use iso_c_binding, only: c_intptr_t + use ocl_utils_mod, only: get_program, get_kernel, release_program + integer, intent(in) :: nkernels + character(len=*), intent(in) :: kernel_names(nkernels) + character(len=*), intent(in) :: filename + ! Locals + integer :: ik, ierr + integer(c_intptr_t), target :: prog + + ! Get a program object containing all of our kernels + prog = get_program(cl_context, cl_device, cl_version_str, filename) + + cl_num_kernels = nkernels + + allocate(cl_kernels(cl_num_kernels), cl_kernel_names(cl_num_kernels), & + Stat=ierr) + if(ierr /= 0)then + stop "Failed to allocate memory for kernel table" + end if + + do ik = 1, cl_num_kernels + cl_kernels(ik) = get_kernel(prog, kernel_names(ik)) + end do + + ! Release the program now that we've created the kernels + call release_program(prog) + + end subroutine add_kernels + + !=================================================== + + function get_kernel_by_name(name) result(kern) + integer(c_intptr_t), target :: kern + character(len=*), intent(in) :: name + ! Locals + integer :: ik, match + character(len=256) :: msg + + !> \TODO is there a better way to do this that reduces the need for + !! string comparisons? + match = 0 + do ik = 1, cl_num_kernels + if(name == cl_kernel_names(ik))then + ! We can't just return out of this loop because this is a + ! function + match = ik + exit + end if + end do + + if(match == 0)then + !> \TODO add check that we don't go out of bounds when writing to msg + write(*, "('get_kernel_by_name: no kernel with name ',(A),' found')")& + name + stop + end if + + kern = cl_kernels(match) + + end function get_kernel_by_name + + !=================================================== + + function get_num_cmd_queues() result(num) + integer :: num + num = cl_num_queues + end function get_num_cmd_queues + + !=================================================== + + function get_cmd_queues() result(queues) + integer(c_intptr_t), pointer :: queues(:) + queues => cl_cmd_queues + end function get_cmd_queues + + !=================================================== + + subroutine gocean_release() + integer :: i + + do i=1, cl_num_kernels + call release_kernel(cl_kernels(i)) + end do + + call release_queues(cl_num_queues, cl_cmd_queues) + + call release_context(cl_context) + + end subroutine gocean_release + +end module ocl_env_mod diff --git a/lib/opencl/ocl_params_mod.f90 b/lib/opencl/ocl_params_mod.f90 new file mode 100644 index 0000000000..2e15047cd4 --- /dev/null +++ b/lib/opencl/ocl_params_mod.f90 @@ -0,0 +1,10 @@ +!> Module holding basic KIND parameters +module ocl_params_mod + implicit none + + public + + !> Douple precision kind parameter + integer, parameter :: wp = SELECTED_REAL_KIND(12,307) + +end module ocl_params_mod diff --git a/lib/opencl/ocl_utils_mod.f90 b/lib/opencl/ocl_utils_mod.f90 new file mode 100644 index 0000000000..a88d84ff2a --- /dev/null +++ b/lib/opencl/ocl_utils_mod.f90 @@ -0,0 +1,433 @@ +module ocl_utils_mod + use clfortran + use iso_c_binding + implicit none + + integer, parameter :: CL_UTIL_STR_LEN = 64 + +contains + + subroutine init_device(device, version_str, context) + !> Initialise an OpenCL device + integer(c_intptr_t), intent(inout) :: device, context + character(len=CL_UTIL_STR_LEN), intent(inout) :: version_str + ! Locals + integer :: iplatform, idevice, iallocerr + integer(c_intptr_t), target :: ctx_props(3) + integer(c_int32_t), target :: device_cu + integer(c_size_t) :: iret, zero_size = 0 + integer(c_int32_t) :: ierr, num_devices, num_platforms + integer(c_intptr_t), allocatable, target :: & + platform_ids(:), device_ids(:) + character(len=1,kind=c_char), allocatable, target :: device_name(:) + + ierr = clGetPlatformIDs(0, C_NULL_PTR, num_platforms) + call check_status('clGetPlatformIDs', ierr) + if (num_platforms < 1)then + write (*,*) "Failed to get any OpenCL platform IDs" + stop + end if + print '(a,i2)','Num Platforms: ',num_platforms + + allocate(platform_ids(num_platforms), stat=iallocerr) + if (iallocerr.ne.0) stop 'memory allocation error' + + ! whenever "&" appears in C subroutine (address-of) call, + ! then C_LOC has to be used in Fortran + ierr = clGetPlatformIDs(num_platforms, C_LOC(platform_ids), & + num_platforms) + call check_status('clGetPlatformIDs', ierr) + + ! Get device IDs only for platform 1 + iplatform=1 + + ierr=clGetDeviceIDs(platform_ids(iplatform), CL_DEVICE_TYPE_ALL, & + 0, C_NULL_PTR, num_devices) + call check_status('clGetDeviceIDs', ierr) + if (num_devices < 1)then + stop 'Failed to find any OpenCL devices' + end if + print '(a,i2)','Num Devices: ',num_devices + + allocate(device_ids(num_devices), stat=iallocerr) + if (iallocerr.ne.0) stop 'memory allocation error' + + ! whenever "&" appears in C subroutine (address-off) call, + ! then C_LOC has to be used in Fortran + ierr = clGetDeviceIDs(platform_ids(iplatform), CL_DEVICE_TYPE_ALL, & + num_devices, C_LOC(device_ids), num_devices) + call check_status('clGetDeviceIDs', ierr) + + ! Get device info only for device 1 + idevice=1 + device = device_ids(idevice) + + ierr=clGetDeviceInfo(device_ids(idevice), & + CL_DEVICE_MAX_COMPUTE_UNITS, sizeof(device_cu), & + C_LOC(device_cu), iret) + call check_status('clGetDeviceInfo', ierr) + ierr=clGetDeviceInfo(device_ids(idevice), & + CL_DEVICE_NAME, zero_size, C_NULL_PTR,iret) + call check_status('clGetDeviceInfo', ierr) + + allocate(device_name(iret), stat=iallocerr) + if (iallocerr.ne.0) stop 'allocate' + + ierr=clGetDeviceInfo(device_ids(idevice), CL_DEVICE_NAME, & + sizeof(device_name), C_LOC(device_name), iret) + if (ierr.ne.CL_SUCCESS) stop 'clGetDeviceInfo' + + write (*,'(a,i2,a,i3,a)',advance='no') & + ' Device (#',idevice,', Compute Units: ',device_cu,') - ' + print *,device_name(1:iret) + deallocate(device_name) + + print '(a,i2,a)', 'Creating context for: ', num_devices,' devices' + print '(a,i2)', 'for platform: ',iplatform + ctx_props(1) = CL_CONTEXT_PLATFORM + ctx_props(2) = platform_ids(iplatform) + ctx_props(3) = 0 + context = clCreateContext(C_LOC(ctx_props), num_devices, & + C_LOC(device_ids), C_NULL_FUNPTR, C_NULL_PTR, & + ierr) + call check_status('clCreateContext', ierr) + + end subroutine init_device + + !=================================================== + + subroutine release_context(context) + integer(c_intptr_t), intent(inout) :: context + ! Locals + integer(c_int32_t) :: ierr + + ierr=clReleaseContext(context) + call check_status('clReleaseContext', ierr) + + end subroutine release_context + + !=================================================== + + function get_program(context, device, version_str, filename) result(prog) + integer(c_intptr_t), target :: prog + integer(c_intptr_t), intent(inout), target :: device, context + character(len=CL_UTIL_STR_LEN), intent(in) :: version_str, filename + ! Locals + character(len=1,kind=c_char), allocatable, target :: source(:) + type(c_ptr), target :: psource + character(len=1024) :: options + character(len=1, kind=c_char), target :: retinfo(1:1024), c_options(1:1024) + integer :: i, irec, iallocerr + integer(c_int32_t) :: ierr + integer, parameter :: iunit=10 + integer(c_size_t), target :: binary_size, iret + character, dimension(1) :: char + + ! read kernel from disk + open(iunit, file=filename, access='direct', & + status='old', action='read', iostat=ierr, recl=1) + if (ierr.ne.0)then + write(*,*) 'Cannot open file: ', TRIM(filename) + stop + end if + irec=1 + do + read(iunit, rec=irec, iostat=ierr) char + if (ierr /= 0) exit + irec = irec+1 + end do + + if (irec.eq.0) stop 'nothing read' + allocate(source(irec+1),stat=iallocerr) + if (iallocerr.ne.0) stop 'allocate' + do i=1,irec + read(iunit,rec=i,iostat=ierr) source(i:i) + enddo + close(iunit) + + print '(a,i7)','size of source code in bytes: ',irec + + psource=C_LOC(source) ! pointer to source code + binary_size = irec + prog = clCreateProgramWithBinary(context, 1, C_LOC(device), & + C_LOC(binary_size), C_LOC(psource), & + C_NULL_PTR, ierr) + + call check_status('clCreateProgramWithSource', ierr) + + options = "" !'-cl-opt-disable' ! compiler options + irec = len(trim(options)) + do i=1, irec + c_options(i)=options(i:i) + enddo + c_options(irec+1) = C_NULL_CHAR + ierr=clBuildProgram(prog, 0, C_NULL_PTR, C_LOC(c_options), & + C_NULL_FUNPTR,C_NULL_PTR) + if (ierr.ne.CL_SUCCESS) then + print *,'clBuildProgram',ierr + ierr=clGetProgramBuildInfo(prog, device, CL_PROGRAM_BUILD_LOG, & + sizeof(retinfo), C_LOC(retinfo),iret) + if (ierr.ne.0) stop 'clGetProgramBuildInfo' + print '(a)','build log start' + print '(1024a)',retinfo(1:min(iret,1024)) + print '(a)','build log end' + stop + endif + + end function get_program + + + !> Release an OpenCL program object + subroutine release_program(prog) + integer(c_intptr_t), target, intent(inout) :: prog + ! Locals + integer(c_int32_t) :: ierr + + ierr = clReleaseProgram(prog) + call check_status('clReleaseProgram', ierr) + end subroutine release_program + + + !> Get a kernel object from a program object + !> + !> @param [in] prog OpenCL program object obtained from + !> get_program + !> @return the kernel object + !> + function get_kernel(prog, kernel_name) result(kernel) + integer(c_intptr_t), target, intent(in) :: prog + character(len=*), intent(in) :: kernel_name + integer(c_intptr_t), target :: kernel + ! Locals + integer :: irec, i + character(len=1, kind=c_char), target :: c_kernel_name(1:1024) + + irec = len(trim(kernel_name)) + do i=1, irec + c_kernel_name(i) = kernel_name(i:i) + enddo + c_kernel_name(irec+1) = C_NULL_CHAR + + kernel = clCreateKernel(prog, C_LOC(c_kernel_name), irec) + call check_status('clCreateKernel', irec) + + end function get_kernel + + !=================================================== + + subroutine release_kernel(kern) + integer(c_intptr_t), target, intent(inout) :: kern + integer(c_int32_t) :: ierr + + ierr = clReleaseKernel(kern) + call check_status('clReleaseKernel', ierr) + + end subroutine release_kernel + + !=================================================== + + !> Set-up the specified number of OpenCL command queues for the specified + !! context and device. + subroutine init_cmd_queues(nqueues, queues, context, device) + !> The number of command queues to create + integer, intent(in) :: nqueues + integer(c_intptr_t), target, intent(inout) :: queues(nqueues) + integer(c_intptr_t), intent(in) :: device + integer(c_intptr_t), intent(in) :: context + ! Locals + integer :: i + integer(c_int32_t) :: ierr + + do i=1, nqueues + queues(i) = clCreateCommandQueue(context, device, & + CL_QUEUE_PROFILING_ENABLE, ierr) + call check_status('clCreateCommandQueue', ierr) + end do + + end subroutine init_cmd_queues + + !=================================================== + + subroutine release_cmd_queues(nqueues, queues) + integer, intent(in) :: nqueues + integer(c_intptr_t), target, intent(inout) :: queues(nqueues) + ! Locals + integer :: iq + integer(c_int32_t) :: ierr + + do iq=1, nqueues + ierr=clReleaseCommandQueue(queues(iq)) + call check_status('clReleaseCommandQueue', ierr) + end do + + end subroutine release_cmd_queues + + !=================================================== + + !> Create a buffer in the supplied OpenCL context + function create_buffer(context, access, nbytes) result(buffer) + integer(c_intptr_t), intent(in) :: context + integer(c_int64_t), intent(in) :: access + integer(c_size_t), intent(in) :: nbytes + integer(c_intptr_t), target :: buffer + ! Locals + integer(c_int32_t) :: ierr + + buffer = clCreateBuffer(context, access, & + nbytes, C_NULL_PTR, ierr) + call check_status('clCreateBuffer', ierr) + + end function create_buffer + + !===================================================== + + !> Read a buffer (containing 64-bit floats) from an OpenCL device. Call + !! blocks until read is complete. + subroutine read_buffer(queue, device_ptr, local_array, nelem) + use ocl_params_mod, only: wp + integer(c_intptr_t), intent(in) :: queue, device_ptr + real(kind=wp), target, intent(in) :: local_array + integer(8), intent(in) :: nelem + ! Locals + integer(c_int32_t) :: ierr + integer(c_intptr_t), target :: event + integer(8) :: nbytes + + nbytes = nelem * 8_8 + ierr = clEnqueueReadBuffer(queue, device_ptr, CL_TRUE, 0_8, & + nbytes, C_LOC(local_array), & + 0, C_NULL_PTR, C_LOC(event)) + call check_status('clEnqueueReadBuffer', ierr) + + !> \todo implement an asynchronous read so we don't have to wait + !! for each one to complete. + ierr = clWaitForEvents(1, C_LOC(event)) + call check_status('clWaitForEvents', ierr) + + end subroutine read_buffer + + !===================================================== + + !> Check the return code of an OpenCL API cal + subroutine check_status(text, ierr) + implicit none + character(len=*), intent(in) :: text + integer, intent(in) :: ierr + + logical, parameter :: verbose = .TRUE. + + if(ierr /= CL_SUCCESS)then + write(*,'("Hit error: ",(A),": ",(A))') text, OCL_GetErrorString(ierr) + stop + end if + if(verbose)then + write(*,'("Called ",(A)," OK")') text + end if + end subroutine check_status + +function OCL_GetErrorString(error) + implicit none + character(len=64) :: OCL_GetErrorString + integer, intent(in) :: error + select case(error) + + case (CL_SUCCESS) + OCL_GetErrorString = "CL_SUCCESS" + case (CL_DEVICE_NOT_FOUND) + OCL_GetErrorString = "CL_DEVICE_NOT_FOUND" + case (CL_DEVICE_NOT_AVAILABLE) + OCL_GetErrorString = "CL_DEVICE_NOT_AVAILABLE" + case (CL_COMPILER_NOT_AVAILABLE) + OCL_GetErrorString = "CL_COMPILER_NOT_AVAILABLE" + case (CL_MEM_OBJECT_ALLOCATION_FAILURE) + OCL_GetErrorString = "CL_MEM_OBJECT_ALLOCATION_FAILURE" + case (CL_OUT_OF_RESOURCES) + OCL_GetErrorString = "CL_OUT_OF_RESOURCES" + case (CL_OUT_OF_HOST_MEMORY) + OCL_GetErrorString = "CL_OUT_OF_HOST_MEMORY" + case (CL_PROFILING_INFO_NOT_AVAILABLE) + OCL_GetErrorString = "CL_PROFILING_INFO_NOT_AVAILABLE" + case (CL_MEM_COPY_OVERLAP) + OCL_GetErrorString = "CL_MEM_COPY_OVERLAP" + case (CL_IMAGE_FORMAT_MISMATCH) + OCL_GetErrorString = "CL_IMAGE_FORMAT_MISMATCH" + case (CL_IMAGE_FORMAT_NOT_SUPPORTED) + OCL_GetErrorString = "CL_IMAGE_FORMAT_NOT_SUPPORTED" + case (CL_BUILD_PROGRAM_FAILURE) + OCL_GetErrorString = "CL_BUILD_PROGRAM_FAILURE" + case (CL_MAP_FAILURE) + OCL_GetErrorString = "CL_MAP_FAILURE" + case (CL_INVALID_VALUE) + OCL_GetErrorString = "CL_INVALID_VALUE" + case (CL_INVALID_DEVICE_TYPE) + OCL_GetErrorString = "CL_INVALID_DEVICE_TYPE" + case (CL_INVALID_PLATFORM) + OCL_GetErrorString = "CL_INVALID_PLATFORM" + case (CL_INVALID_DEVICE) + OCL_GetErrorString = "CL_INVALID_DEVICE" + case (CL_INVALID_CONTEXT) + OCL_GetErrorString = "CL_INVALID_CONTEXT" + case (CL_INVALID_QUEUE_PROPERTIES) + OCL_GetErrorString = "CL_INVALID_QUEUE_PROPERTIES" + case (CL_INVALID_COMMAND_QUEUE) + OCL_GetErrorString = "CL_INVALID_COMMAND_QUEUE" + case (CL_INVALID_HOST_PTR) + OCL_GetErrorString = "CL_INVALID_HOST_PTR" + case (CL_INVALID_MEM_OBJECT) + OCL_GetErrorString = "CL_INVALID_MEM_OBJECT" + case (CL_INVALID_IMAGE_FORMAT_DESCRIPTOR) + OCL_GetErrorString = "CL_INVALID_IMAGE_FORMAT_DESCRIPTOR" + case (CL_INVALID_IMAGE_SIZE) + OCL_GetErrorString = "CL_INVALID_IMAGE_SIZE" + case (CL_INVALID_SAMPLER) + OCL_GetErrorString = "CL_INVALID_SAMPLER" + case (CL_INVALID_BINARY) + OCL_GetErrorString = "CL_INVALID_BINARY" + case (CL_INVALID_BUILD_OPTIONS) + OCL_GetErrorString = "CL_INVALID_BUILD_OPTIONS" + case (CL_INVALID_PROGRAM) + OCL_GetErrorString = "CL_INVALID_PROGRAM" + case (CL_INVALID_PROGRAM_EXECUTABLE) + OCL_GetErrorString = "CL_INVALID_PROGRAM_EXECUTABLE" + case (CL_INVALID_KERNEL_NAME) + OCL_GetErrorString = "CL_INVALID_KERNEL_NAME" + case (CL_INVALID_KERNEL_DEFINITION) + OCL_GetErrorString = "CL_INVALID_KERNEL_DEFINITION" + case (CL_INVALID_KERNEL) + OCL_GetErrorString = "CL_INVALID_KERNEL" + case (CL_INVALID_ARG_INDEX) + OCL_GetErrorString = "CL_INVALID_ARG_INDEX" + case (CL_INVALID_ARG_VALUE) + OCL_GetErrorString = "CL_INVALID_ARG_VALUE" + case (CL_INVALID_ARG_SIZE) + OCL_GetErrorString = "CL_INVALID_ARG_SIZE" + case (CL_INVALID_KERNEL_ARGS) + OCL_GetErrorString = "CL_INVALID_KERNEL_ARGS" + case (CL_INVALID_WORK_DIMENSION) + OCL_GetErrorString = "CL_INVALID_WORK_DIMENSION" + case (CL_INVALID_WORK_GROUP_SIZE) + OCL_GetErrorString = "CL_INVALID_WORK_GROUP_SIZE" + case (CL_INVALID_WORK_ITEM_SIZE) + OCL_GetErrorString = "CL_INVALID_WORK_ITEM_SIZE" + case (CL_INVALID_GLOBAL_OFFSET) + OCL_GetErrorString = "CL_INVALID_GLOBAL_OFFSET" + case (CL_INVALID_EVENT_WAIT_LIST) + OCL_GetErrorString = "CL_INVALID_EVENT_WAIT_LIST" + case (CL_INVALID_EVENT) + OCL_GetErrorString = "CL_INVALID_EVENT" + case (CL_INVALID_OPERATION) + OCL_GetErrorString = "CL_INVALID_OPERATION" + case (CL_INVALID_GL_OBJECT) + OCL_GetErrorString = "CL_INVALID_GL_OBJECT" + case (CL_INVALID_BUFFER_SIZE) + OCL_GetErrorString = "CL_INVALID_BUFFER_SIZE" + case (CL_INVALID_MIP_LEVEL) + OCL_GetErrorString = "CL_INVALID_MIP_LEVEL" + case(CL_INVALID_GLOBAL_WORK_SIZE) + OCL_GetErrorString = "CL_INVALID_GLOBAL_WORK_SIZE" + case default + OCL_GetErrorString = "unknown error code" + end select + end function OCL_GetErrorString + +end module ocl_utils_mod From aeb35639bf9611851050cdd965d3d6542b779ce0 Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Fri, 15 Jun 2018 15:33:12 +0100 Subject: [PATCH 09/60] #174 add option to read kernel filename from env var. Allow add_kernels() to be called multiple times. --- lib/opencl/ocl_env_mod.f90 | 56 ++++++++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 17 deletions(-) diff --git a/lib/opencl/ocl_env_mod.f90 b/lib/opencl/ocl_env_mod.f90 index 5d17b63950..b951f0e57a 100644 --- a/lib/opencl/ocl_env_mod.f90 +++ b/lib/opencl/ocl_env_mod.f90 @@ -7,6 +7,8 @@ module ocl_env_mod private + !> Whether or not this module has been initialised + logical, save :: cl_env_initialised = .False. !> Pointer to the OpenCL device being used integer(c_intptr_t) :: cl_device !> The OpenCL context @@ -21,8 +23,10 @@ module ocl_env_mod !> The OpenCL kernels used by the model integer, save :: cl_num_kernels - character(len=CL_UTIL_STR_LEN), allocatable :: cl_kernel_names(:) - integer(c_intptr_t), target, allocatable :: cl_kernels(:) + !> The maximum number of kernels we can store + integer, parameter :: cl_max_num_kernels = 30 + character(len=CL_UTIL_STR_LEN) :: cl_kernel_names(cl_max_num_kernels) + integer(c_intptr_t), target :: cl_kernels(cl_max_num_kernels) public ocl_env_init public cl_context, cl_device, get_num_cmd_queues, get_cmd_queues @@ -32,15 +36,16 @@ module ocl_env_mod !=================================================== !> Initialise the GOcean environment - subroutine ocl_env_init(opencl) + subroutine ocl_env_init() use ocl_utils_mod, only: init_device implicit none - !> Whether or not to use OpenCL - logical, optional :: opencl integer :: ierr + if(cl_env_initialised)return + ! Initialise the OpenCL device call init_device(cl_device, cl_version_str, cl_context) + ! Create command queue(s) cl_num_queues = 4 allocate(cl_cmd_queues(cl_num_queues), Stat=ierr) @@ -49,6 +54,12 @@ subroutine ocl_env_init(opencl) end if call init_cmd_queues(cl_num_queues, cl_cmd_queues, cl_context, cl_device) + ! At this point we have no kernels + cl_num_kernels = 0 + + ! Environment now initialised + cl_env_initialised = .True. + end subroutine ocl_env_init !=================================================== @@ -58,25 +69,36 @@ subroutine add_kernels(nkernels, kernel_names, filename) use ocl_utils_mod, only: get_program, get_kernel, release_program integer, intent(in) :: nkernels character(len=*), intent(in) :: kernel_names(nkernels) - character(len=*), intent(in) :: filename + character(len=*), intent(in), optional :: filename ! Locals integer :: ik, ierr integer(c_intptr_t), target :: prog + character(len=300) :: lfilename - ! Get a program object containing all of our kernels - prog = get_program(cl_context, cl_device, cl_version_str, filename) + if(.not. cl_env_initialised)then + call ocl_env_init() + end if - cl_num_kernels = nkernels + if(.not. present(filename))then + call get_environment_variable("PSYCLONE_KERNELS_FILE", lfilename) + else + lfilename = filename + end if - allocate(cl_kernels(cl_num_kernels), cl_kernel_names(cl_num_kernels), & - Stat=ierr) - if(ierr /= 0)then - stop "Failed to allocate memory for kernel table" + if((nkernels + cl_num_kernels) > cl_max_num_kernels)then + write(*,"('add_kernels: Adding ',I2,' kernels will exceed the & +&maximum number of ',I3,' - increase ocl_env_mod::cl_max_num_kernels')") & + nkernels, cl_max_num_kernels + stop end if - do ik = 1, cl_num_kernels - cl_kernels(ik) = get_kernel(prog, kernel_names(ik)) + ! Get a program object containing all of our kernels + prog = get_program(cl_context, cl_device, cl_version_str, lfilename) + + do ik = 1, nkernels + cl_kernels(cl_num_kernels+ik) = get_kernel(prog, kernel_names(ik)) end do + cl_num_kernels = cl_num_kernels + nkernels ! Release the program now that we've created the kernels call release_program(prog) @@ -131,7 +153,7 @@ end function get_cmd_queues !=================================================== - subroutine gocean_release() + subroutine ocl_release() integer :: i do i=1, cl_num_kernels @@ -142,6 +164,6 @@ subroutine gocean_release() call release_context(cl_context) - end subroutine gocean_release + end subroutine ocl_release end module ocl_env_mod From 1de9969aeee84b114444f5ac3768e0984bc1af8b Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Fri, 15 Jun 2018 16:08:31 +0100 Subject: [PATCH 10/60] #174 add the target attribute to DeclGen --- src/psyclone/f2pygen.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/psyclone/f2pygen.py b/src/psyclone/f2pygen.py index bb691d8b64..8532631486 100644 --- a/src/psyclone/f2pygen.py +++ b/src/psyclone/f2pygen.py @@ -775,16 +775,17 @@ class DeclGen(BaseGen): :param list entity_decls: list of variable names to declare :param str intent: the INTENT attribute of this declaration :param bool pointer: whether or not this is a pointer declaration + :param bool target: whether this declaration has the TARGET attribute :param str kind: the KIND attribute to use for this declaration - :param str dimension: the DIMENSION specifier (i.e. the xx in + :param str dimension: the DIMENSION specifier (i.e. the xx in \ DIMENSION(xx)) - :param bool allocatable: whether this declaration is for an + :param bool allocatable: whether this declaration is for an \ ALLOCATABLE quantity :param bool save: whether this declaration should have the SAVE attribute :param initial_values: Initial value to give each variable. :type initial_values: list of str with same no. of elements as entity_decls :raises RuntimeError: if no variable names are specified - :raises RuntimeError: if datatype is not one of "integer", "real" or + :raises RuntimeError: if datatype is not one of "integer", "real" or \ "logical" ''' @@ -792,8 +793,8 @@ class DeclGen(BaseGen): SUPPORTED_TYPES = ["integer", "real", "logical"] def __init__(self, parent, datatype="", entity_decls=None, intent="", - pointer=False, kind="", dimension="", allocatable=False, - save=False, initial_values=None): + pointer=False, target=False, kind="", dimension="", + allocatable=False, save=False, initial_values=None): if entity_decls is None: raise RuntimeError( "Cannot create a variable declaration without specifying the " @@ -859,6 +860,8 @@ def __init__(self, parent, datatype="", entity_decls=None, intent="", my_attrspec.append("intent({0})".format(intent)) if pointer: my_attrspec.append("pointer") + if target: + my_attrspec.append("target") if allocatable: my_attrspec.append("allocatable") if save: From 7b776ffb01787ee1632ae625ae3ae232d29b7c46 Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Fri, 15 Jun 2018 16:09:43 +0100 Subject: [PATCH 11/60] #174 add if(first_time) section to psy --- src/psyclone/psyGen.py | 64 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 61 insertions(+), 3 deletions(-) diff --git a/src/psyclone/psyGen.py b/src/psyclone/psyGen.py index 10b7e535ed..7380b68d60 100644 --- a/src/psyclone/psyGen.py +++ b/src/psyclone/psyGen.py @@ -1332,14 +1332,59 @@ def gen_code(self, parent): which to add content. :type parent: :py:class:`psyclone.f2pygen.SubroutineGen` ''' + from psyclone.f2pygen import UseGen, DeclGen, AssignGen, CommentGen, \ + IfThenGen + if self._opencl: - from psyclone.f2pygen import UseGen parent.add(UseGen(parent, name="iso_c_binding")) parent.add(UseGen(parent, name="clfortran")) + parent.add(UseGen(parent, name="ocl_env_mod", only=True, + funcnames=["get_num_cmd_queues", + "get_cmd_queues", + "get_kernel_by_name"])) + # Command queues + parent.add(DeclGen(parent, datatype="integer", save=True, + entity_decls=["num_cmd_queues"])) + parent.add(DeclGen(parent, datatype="integer", save=True, + pointer=True, kind="c_intptr_t", + entity_decls=["cmd_queues(:)"])) + parent.add(DeclGen(parent, datatype="integer", + entity_decls=["ierr"])) + parent.add(DeclGen(parent, datatype="logical", save=True, + entity_decls=["first_time"], + initial_values=[".true."])) + if_first = IfThenGen(parent, "first_time") + parent.add(if_first) + if_first.add(AssignGen(if_first, lhs="first_time", rhs=".false.")) + if_first.add(AssignGen(if_first, lhs="num_cmd_queues", + rhs="get_num_cmd_queues()")) + if_first.add(AssignGen(if_first, lhs="cmd_queues", + rhs="get_cmd_queues()")) + # Kernel pointers + kernels = self.walk(self._children, Call) + for kern in kernels: + kernel = "kernel_" + kern.name # TODO use namespace manager + parent.add( + DeclGen(parent, datatype="integer", kind="c_intptr_t", + save=True, target=True, entity_decls=[kernel])) + if_first.add( + AssignGen( + if_first, lhs=kernel, + rhs='get_kernel_by_name("{0}")'.format(kern.name))) for entity in self._children: entity.gen_code(parent) + if self.opencl: + # Ensure we block at the end of the invoke to ensure all + # kernels have completed before we return. + # TODO can we lift this restriction? + # BUG this assumes only the first command queue is used + parent.add(CommentGen(parent, + " Block until all kernels have finished")) + parent.add(AssignGen(parent, lhs="ierr", + rhs="clFinish(cmd_queues(1))")) + @property def opencl(self): ''' @@ -2525,14 +2570,27 @@ def gen_arg_setter_code(self, parent): from psyclone.f2pygen import SubroutineGen, UseGen, DeclGen, \ AssignGen, CommentGen # TODO take care with literal arguments + arguments = ["kern"] + [arg.name for arg in self._arguments.args] sub = SubroutineGen(parent, name=self.name+"_set_args", - args=[arg.name for arg in self._arguments.args]) + args=arguments) parent.add(sub) # TODO add a use for check_status() routine sub.add(UseGen(sub, name="iso_c_binding", only=True, - funcnames=["sizeof", "c_loc"])) + funcnames=["sizeof", "c_loc", "c_intptr_t"])) sub.add(UseGen(sub, name="clfortran", only=True, funcnames=["clSetKernelArg"])) + # Declare arguments + sub.add(DeclGen(sub, datatype="integer", kind="c_intptr_t", + target=True, entity_decls=["kern"])) + fld_args = [] + for arg in self._arguments.args: + if arg.type == "field": + fld_args.append(arg.name) + if fld_args: + sub.add(DeclGen(sub, datatype="integer", kind="c_intptr_t", + target=True, entity_decls=fld_args)) + + # Declare local variables sub.add(DeclGen(sub, datatype="integer", entity_decls=["ierr", "arg_idx"])) sub.add(CommentGen( From 26331cb49533b18c5ff7cda643c6b9fc1220b0d9 Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Fri, 15 Jun 2018 16:10:27 +0100 Subject: [PATCH 12/60] #174 add calls to copy fields to device --- src/psyclone/gocean1p0.py | 60 +++++++++++++++++++++++++++++++++------ 1 file changed, 52 insertions(+), 8 deletions(-) diff --git a/src/psyclone/gocean1p0.py b/src/psyclone/gocean1p0.py index 85cbaba6f1..447d86ba9b 100644 --- a/src/psyclone/gocean1p0.py +++ b/src/psyclone/gocean1p0.py @@ -706,13 +706,21 @@ def _find_grid_access(self): return None def gen_code(self, parent): - ''' Generates GOcean v1.0 specific psy code for a call to the dynamo - kernel instance. ''' + ''' + Generates GOcean v1.0 specific psy code for a call to the + kernel instance. + + :param parent: parent node in the f2pygen AST being created. + :type parent: :py:class:`psyclone.f2pygen.LoopGen` + :raises GenerationError: if the kernel requires a grid property but \ + does not have any field arguments. + :raises GenerationError: if it encounters a kernel argument of \ + unrecognised type. + ''' from psyclone.f2pygen import CallGen, UseGen if self.root.opencl: # OpenCL is completely different so has its own gen method. - # TODO is there a nicer way of doing this? self.gen_ocl(parent) return @@ -754,9 +762,11 @@ def gen_code(self, parent): def gen_ocl(self, parent): ''' - :param parent: + :param parent: Parent node in the f2pygen AST to which to add content. + :type parent: :py:class:`psyclone.f2pygen.SubroutineGen` ''' - from psyclone.f2pygen import CallGen, DeclGen, AssignGen, CommentGen + from psyclone.f2pygen import CallGen, DeclGen, AssignGen, CommentGen, \ + IfThenGen garg = self._find_grid_access() parent.add(DeclGen(parent, datatype="integer", @@ -767,10 +777,44 @@ def gen_ocl(self, parent): kernel = "kernel_" + self._name # TODO use namespace manager - # First we have to set the kernel arguments + # Ensure fields are on device TODO this belongs somewhere else! + parent.add(CommentGen(parent, + " Ensure field data is on device")) + for arg in self._arguments.args: + if arg.type == "field": + ifthen = IfThenGen(parent, + ".NOT. {0}%data_on_device".format(arg.name)) + parent.add(ifthen) + ifthen.add(AssignGen( + ifthen, lhs="ierr", + rhs="clEnqueueWriteBuffer(cmd_queues(1), {0}%device_ptr, " + "CL_TRUE, 0_8, size_in_bytes, C_LOC({0}%data), " + "0, C_NULL_PTR, C_LOC(write_event))".format(arg.name))) + ifthen.add(AssignGen(ifthen, + lhs="{0}.data_on_device".format(arg.name), + rhs=".true.")) + # Ensure data copies have finished + parent.add(CommentGen(parent, + " Block until data copies have finished")) + parent.add(AssignGen(parent, lhs="ierr", + rhs="clFinish(cmd_queues(1))")) + # Then we set the kernel arguments + arguments = [kernel] + # TODO this argument-list generation duplicates that in + # GOKern.gen_code(). We need to re-factor ala dynamo0p3.ArgOrdering. + for arg in self._arguments.args: + if arg.type == "scalar": + arguments.append(arg.name) + elif arg.type == "field": + arguments.append(arg.name + "%device_ptr") + elif arg.type == "grid_property": + # TODO where to store device pointers for grid properties? + #arguments.append(garg.name+"%grid%"+arg.name) + raise NotImplementedError( + "Grid property arrays not yet on OCL device") parent.add(CallGen( - parent, "{0}_set_args".format(self.name), - [kernel] + [arg.name for arg in self._arguments.args])) + parent, "{0}_set_args".format(self.name), arguments)) + # Then we call clEnqueueNDRangeKernel cnull = "C_NULL_PTR" cmd_queue = "cmd_queues(1)" # TODO use namespace manager From 26b074b57a8025c60e757975ac8df741b61f8fa1 Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Fri, 15 Jun 2018 16:14:46 +0100 Subject: [PATCH 13/60] #174 add expected arg-setting code to test [skip ci] --- src/psyclone/tests/gocean1p0_opencl_test.py | 32 +++++++++++++++++---- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/src/psyclone/tests/gocean1p0_opencl_test.py b/src/psyclone/tests/gocean1p0_opencl_test.py index 1bcee96bb4..426aa9500c 100644 --- a/src/psyclone/tests/gocean1p0_opencl_test.py +++ b/src/psyclone/tests/gocean1p0_opencl_test.py @@ -64,10 +64,11 @@ def test_use_stmts(): print(generated_code) expected = '''\ SUBROUTINE invoke_0_compute_cu(cu_fld, p_fld, u_fld) - USE compute_cu_mod, ONLY: compute_cu_code + USE ocl_env_mod, ONLY: get_num_cmd_queues, get_cmd_queues, get_kernel_by_name USE clfortran USE iso_c_binding''' assert expected in generated_code + assert "if(first_time)then" in generated_code def test_set_kern_args(): @@ -85,9 +86,30 @@ def test_set_kern_args(): otrans.apply(sched) generated_code = str(psy.gen) print(generated_code) - assert generated_code.count("SUBROUTINE compute_cu_code_set_args(cu_fld, " + assert generated_code.count("SUBROUTINE compute_cu_code_set_args(kern, cu_fld, " "p_fld, u_fld)") == 1 - assert generated_code.count("SUBROUTINE time_smooth_code_set_args(u_fld, " + expected = '''\ + SUBROUTINE compute_cu_code_set_args(kern, cu_fld, p_fld, u_fld) + USE clfortran, ONLY: clSetKernelArg + USE iso_c_binding, ONLY: sizeof, c_loc, c_intptr_t + INTEGER ierr, arg_idx + INTEGER(KIND=c_intptr_t), target :: cu_fld, p_fld, u_fld + INTEGER(KIND=c_intptr_t), target :: kern + ! Set the arguments for the compute_cu_code OpenCL Kernel + arg_idx = 0 + ierr = clSetKernelArg(kernel_obj, arg_idx, sizeof(cu_fld), C_LOC(cu_fld)) + CALL check_status(clSetKernelArg, ierr) + arg_idx = arg_idx + 1 + ierr = clSetKernelArg(kernel_obj, arg_idx, sizeof(p_fld), C_LOC(p_fld)) + CALL check_status(clSetKernelArg, ierr) + arg_idx = arg_idx + 1 + ierr = clSetKernelArg(kernel_obj, arg_idx, sizeof(u_fld), C_LOC(u_fld)) + CALL check_status(clSetKernelArg, ierr) + arg_idx = arg_idx + 1 + END SUBROUTINE compute_cu_code_set_args +''' + assert expected in generated_code + assert generated_code.count("SUBROUTINE time_smooth_code_set_args(kern, u_fld, " "unew_fld, uold_fld)") == 1 - assert ("CALL compute_cu_code_set_args(cu_fld%data, p_fld%data, " - "u_fld%data)" in generated_code) + assert ("CALL compute_cu_code_set_args(kernel_compute_cu_code, " + "cu_fld%device_ptr, p_fld%device_ptr, u_fld%device_ptr)" in generated_code) From 0ccd814599266c313f5d16fbe4755fb7e01600d8 Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Fri, 15 Jun 2018 16:47:52 +0100 Subject: [PATCH 14/60] #174 add new gen_ocl_init() to create ocl-initialisation routine in PSy module --- lib/opencl/ocl_env_mod.f90 | 44 ++++++++++++++++++++++++++------------ src/psyclone/psyGen.py | 27 +++++++++++++++++++++++ 2 files changed, 57 insertions(+), 14 deletions(-) diff --git a/lib/opencl/ocl_env_mod.f90 b/lib/opencl/ocl_env_mod.f90 index b951f0e57a..e6fcad2fcb 100644 --- a/lib/opencl/ocl_env_mod.f90 +++ b/lib/opencl/ocl_env_mod.f90 @@ -71,7 +71,7 @@ subroutine add_kernels(nkernels, kernel_names, filename) character(len=*), intent(in) :: kernel_names(nkernels) character(len=*), intent(in), optional :: filename ! Locals - integer :: ik, ierr + integer :: ik, ierr, new_kern_count integer(c_intptr_t), target :: prog character(len=300) :: lfilename @@ -95,10 +95,15 @@ subroutine add_kernels(nkernels, kernel_names, filename) ! Get a program object containing all of our kernels prog = get_program(cl_context, cl_device, cl_version_str, lfilename) + new_kern_count = 0 do ik = 1, nkernels - cl_kernels(cl_num_kernels+ik) = get_kernel(prog, kernel_names(ik)) + ! Skip any kernels we've already got + if(get_kernel_index(kernel_names(ik)) /= 0)cycle + new_kern_count = new_kern_count + 1 + cl_kernels(cl_num_kernels+new_kern_count) = get_kernel(prog, & + kernel_names(ik)) end do - cl_num_kernels = cl_num_kernels + nkernels + cl_num_kernels = cl_num_kernels + new_kern_count ! Release the program now that we've created the kernels call release_program(prog) @@ -114,17 +119,7 @@ function get_kernel_by_name(name) result(kern) integer :: ik, match character(len=256) :: msg - !> \TODO is there a better way to do this that reduces the need for - !! string comparisons? - match = 0 - do ik = 1, cl_num_kernels - if(name == cl_kernel_names(ik))then - ! We can't just return out of this loop because this is a - ! function - match = ik - exit - end if - end do + match = get_kernel_index(name) if(match == 0)then !> \TODO add check that we don't go out of bounds when writing to msg @@ -139,6 +134,27 @@ end function get_kernel_by_name !=================================================== + function get_kernel_index(name) result index + integer :: index + character(len=*), intent(in) :: name + integer :: ik + !> Helper routine to search for a kernel by name. Returns the + !! index of the kernel (in the cl_kernels list) if found or 0. + index = 0 + !> \TODO is there a better way to do this that reduces the need for + !! string comparisons? + do ik = 1, cl_num_kernels + if(name == cl_kernel_names(ik))then + ! We can't just return out of this loop because this is a + ! function + index = ik + exit + end if + end do + end function get_kernel_index + + !=================================================== + function get_num_cmd_queues() result(num) integer :: num num = cl_num_queues diff --git a/src/psyclone/psyGen.py b/src/psyclone/psyGen.py index 7380b68d60..910eff9f34 100644 --- a/src/psyclone/psyGen.py +++ b/src/psyclone/psyGen.py @@ -338,6 +338,33 @@ def gen_code(self, parent): if kern.name not in opencl_kernels: opencl_kernels.append(kern.name) kern.gen_arg_setter_code(parent) + # We must also ensure that we have a kernel object for + # each kernel called from the PSy layer + self.gen_ocl_init(parent, opencl_kernels) + + def gen_ocl_init(self, parent, kernels): + from psyclone.f2pygen import SubroutineGen, DeclGen, AssignGen, \ + CallGen, UseGen, CommentGen + #import pdb; pdb.set_trace() + sub = SubroutineGen(parent, "psy_init") + parent.add(sub) + sub.add(UseGen(sub, name="ocl_env_mod", only=True, + funcnames=["ocl_env_init", "add_kernels"])) + # Initialise the OpenCL environment + sub.add(CallGen(sub, "ocl_env_init")) + # Create a list of our kernels + nkernstr = str(len(kernels)) + # TODO extend DeclGen to support character! + sub.add(DeclGen(sub, datatype="integer", + entity_decls=["kernel_names({0})".format(nkernstr)])) + for idx, kern in enumerate(kernels): + sub.add(AssignGen(sub, lhs="kernel_names({0})".format(idx+1), + rhs='"{0}"'.format(kern))) + sub.add(CommentGen(sub, + " Create the OpenCL kernel objects. Expects " + "to find all of the compiled ")) + sub.add(CommentGen(sub, " kernels in PSYCLONE_KERNELS_FILE.")) + sub.add(CallGen(sub, "add_kernels", [nkernstr, "kernel_names"])) class NameSpaceFactory(object): From 646f8f01361b39efc943bcc35a71debec567f442 Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Fri, 15 Jun 2018 17:07:03 +0100 Subject: [PATCH 15/60] #174 add x-failing test for psy_init() --- src/psyclone/psyGen.py | 5 ++++ src/psyclone/tests/gocean1p0_opencl_test.py | 33 +++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/src/psyclone/psyGen.py b/src/psyclone/psyGen.py index 910eff9f34..d6291cde36 100644 --- a/src/psyclone/psyGen.py +++ b/src/psyclone/psyGen.py @@ -350,10 +350,15 @@ def gen_ocl_init(self, parent, kernels): parent.add(sub) sub.add(UseGen(sub, name="ocl_env_mod", only=True, funcnames=["ocl_env_init", "add_kernels"])) + # Initialise the OpenCL environment + sub.add(CommentGen(sub, " Initialise the OpenCL environment/device")) sub.add(CallGen(sub, "ocl_env_init")) + # Create a list of our kernels + sub.add(CommentGen(sub, " The kernels this PSy layer module requires")) nkernstr = str(len(kernels)) + # TODO extend DeclGen to support character! sub.add(DeclGen(sub, datatype="integer", entity_decls=["kernel_names({0})".format(nkernstr)])) diff --git a/src/psyclone/tests/gocean1p0_opencl_test.py b/src/psyclone/tests/gocean1p0_opencl_test.py index 426aa9500c..9208071e99 100644 --- a/src/psyclone/tests/gocean1p0_opencl_test.py +++ b/src/psyclone/tests/gocean1p0_opencl_test.py @@ -71,6 +71,39 @@ def test_use_stmts(): assert "if(first_time)then" in generated_code +@pytest.mark.xfail(reason="DeclGen does not support character") +def test_psy_init(): + ''' Check that we create a psy_init() routine that sets-up the + OpenCL environment ''' + _, invoke_info = parse(os.path.join(os.path. + dirname(os.path. + abspath(__file__)), + "test_files", "gocean1p0", + "single_invoke.f90"), + api=API) + psy = PSyFactory(API).create(invoke_info) + sched = psy.invokes.invoke_list[0].schedule + from psyclone.transformations import OCLTrans + otrans = OCLTrans() + otrans.apply(sched) + generated_code = str(psy.gen) + print(generated_code) + expected = '''\ + SUBROUTINE psy_init() + USE ocl_env_mod, ONLY: ocl_env_init, add_kernels + CHARACTER, LEN(40) :: kernel_names(1) + ! Initialise the OpenCL environment/device + CALL ocl_env_init + ! The kernels this PSy layer module requires + kernel_names(1) = "compute_cu_code" + ! Create the OpenCL kernel objects. Expects to find all of the compiled + ! kernels in PSYCLONE_KERNELS_FILE. + CALL add_kernels(1, kernel_names) + END SUBROUTINE psy_init +''' + assert expected in generated_code + + def test_set_kern_args(): ''' Check that we generate the necessary code to set kernel arguments ''' _, invoke_info = parse(os.path.join(os.path. From a9fd14d46238cc060b8ab8af14bedef9af255c3e Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Fri, 22 Jun 2018 12:39:55 +0100 Subject: [PATCH 16/60] #174 rm opencl arg from factory and generate --- src/psyclone/generator.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/psyclone/generator.py b/src/psyclone/generator.py index b13a429250..132ca0ff9c 100644 --- a/src/psyclone/generator.py +++ b/src/psyclone/generator.py @@ -106,8 +106,7 @@ def handle_script(script_name, psy): def generate(filename, api="", kernel_path="", script_name=None, line_length=False, - distributed_memory=DISTRIBUTED_MEMORY, - opencl=True): + distributed_memory=DISTRIBUTED_MEMORY): # pylint: disable=too-many-arguments '''Takes a GungHo algorithm specification as input and outputs the associated generated algorithm and psy codes suitable for @@ -134,7 +133,6 @@ def generate(filename, api="", kernel_path="", script_name=None, :param bool distributed_memory: A logical flag specifying whether to generate distributed memory code. The default is set in the config.py file. - :param bool opencl: Whether or not to generate OpenCL for the PSy layer :return: The algorithm code and the psy code. :rtype: ast :raises IOError: if the filename or search path do not exist @@ -167,8 +165,8 @@ def generate(filename, api="", kernel_path="", script_name=None, ast, invoke_info = parse(filename, api=api, invoke_name="invoke", kernel_path=kernel_path, line_length=line_length) - psy = PSyFactory(api, distributed_memory=distributed_memory, - opencl=opencl).create(invoke_info) + psy = PSyFactory(api, distributed_memory=distributed_memory).\ + create(invoke_info) if script_name is not None: handle_script(script_name, psy) alg = Alg(ast, psy) From 5973b30d6e31ee3e3676887c282745f29eee4ea6 Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Fri, 22 Jun 2018 12:40:37 +0100 Subject: [PATCH 17/60] #174 support grid-property kernel args --- src/psyclone/gocean1p0.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/psyclone/gocean1p0.py b/src/psyclone/gocean1p0.py index 447d86ba9b..0e83007980 100644 --- a/src/psyclone/gocean1p0.py +++ b/src/psyclone/gocean1p0.py @@ -791,7 +791,7 @@ def gen_ocl(self, parent): "CL_TRUE, 0_8, size_in_bytes, C_LOC({0}%data), " "0, C_NULL_PTR, C_LOC(write_event))".format(arg.name))) ifthen.add(AssignGen(ifthen, - lhs="{0}.data_on_device".format(arg.name), + lhs="{0}%data_on_device".format(arg.name), rhs=".true.")) # Ensure data copies have finished parent.add(CommentGen(parent, @@ -808,10 +808,10 @@ def gen_ocl(self, parent): elif arg.type == "field": arguments.append(arg.name + "%device_ptr") elif arg.type == "grid_property": - # TODO where to store device pointers for grid properties? - #arguments.append(garg.name+"%grid%"+arg.name) - raise NotImplementedError( - "Grid property arrays not yet on OCL device") + # TODO dl_esm_inf stores the pointers to device memory + # for grid properties in "grid-prop-name_device" which + # is a bit hacky but works for now. + arguments.append(garg.name+"%grid%"+arg.name+"_device") parent.add(CallGen( parent, "{0}_set_args".format(self.name), arguments)) From 97c2186f5b0f310a7b2c561b339d8d5225b2b8a2 Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Fri, 22 Jun 2018 13:19:30 +0100 Subject: [PATCH 18/60] #174 add property decorator to OCLTrans.name --- src/psyclone/transformations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/psyclone/transformations.py b/src/psyclone/transformations.py index dda6e12537..94f73eb1e3 100644 --- a/src/psyclone/transformations.py +++ b/src/psyclone/transformations.py @@ -1682,7 +1682,7 @@ class OCLTrans(Transformation): >>> ocl_trans = OCLTrans() >>> new_sched, _ = ocl_trans.apply(schedule) ''' - + @property def name(self): '''Returns the name of this transformation as a string.''' return "OCLTrans" From 5838fc4bde05fef7aea735e2f9331ed12aff847b Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Wed, 27 Jun 2018 14:21:03 +0100 Subject: [PATCH 19/60] #174 use c_sizeof instead of sizeof. Avoid setting scalar args [skip ci] --- src/psyclone/psyGen.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/psyclone/psyGen.py b/src/psyclone/psyGen.py index d6291cde36..9c8beff490 100644 --- a/src/psyclone/psyGen.py +++ b/src/psyclone/psyGen.py @@ -2602,25 +2602,28 @@ def gen_arg_setter_code(self, parent): from psyclone.f2pygen import SubroutineGen, UseGen, DeclGen, \ AssignGen, CommentGen # TODO take care with literal arguments - arguments = ["kern"] + [arg.name for arg in self._arguments.args] + # TODO use name-space manager for name of kernel-object arg + arguments = ["kernel_obj"] + [arg.name for arg in self._arguments.args if arg.type != "scalar"] sub = SubroutineGen(parent, name=self.name+"_set_args", args=arguments) parent.add(sub) # TODO add a use for check_status() routine sub.add(UseGen(sub, name="iso_c_binding", only=True, - funcnames=["sizeof", "c_loc", "c_intptr_t"])) + funcnames=["c_sizeof", "c_loc", "c_intptr_t"])) sub.add(UseGen(sub, name="clfortran", only=True, funcnames=["clSetKernelArg"])) # Declare arguments sub.add(DeclGen(sub, datatype="integer", kind="c_intptr_t", - target=True, entity_decls=["kern"])) - fld_args = [] + target=True, entity_decls=["kernel_obj"])) + args = [] for arg in self._arguments.args: if arg.type == "field": - fld_args.append(arg.name) - if fld_args: + args.append(arg.name) + elif arg.type == "grid_property": + args.append(arg.name) + if args: sub.add(DeclGen(sub, datatype="integer", kind="c_intptr_t", - target=True, entity_decls=fld_args)) + target=True, entity_decls=args)) # Declare local variables sub.add(DeclGen(sub, datatype="integer", entity_decls=["ierr", @@ -2630,7 +2633,8 @@ def gen_arg_setter_code(self, parent): " Set the arguments for the {0} OpenCL Kernel".format(self.name))) sub.add(AssignGen(sub, lhs="arg_idx", rhs="0")) for arg in self.arguments.args: - arg.set_kernel_arg(sub) + if arg.type != "scalar": + arg.set_kernel_arg(sub) def incremented_arg(self, mapping={}): ''' Returns the argument that has INC access. Raises a @@ -2864,7 +2868,7 @@ def set_kernel_arg(self, parent): from psyclone.f2pygen import AssignGen, CallGen parent.add(AssignGen( parent, lhs="ierr", - rhs="clSetKernelArg({0}, arg_idx, sizeof({1}), C_LOC({2}))". + rhs="clSetKernelArg({0}, arg_idx, C_SIZEOF({1}), C_LOC({2}))". format("kernel_obj", self.name, self.name))) parent.add(CallGen(parent, "check_status", ["clSetKernelArg", "ierr"])) parent.add(AssignGen(parent, lhs="arg_idx", rhs="arg_idx + 1")) From 7fa79e5a7856233578c257288ff2dc81d3a838ed Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Wed, 27 Jun 2018 14:21:46 +0100 Subject: [PATCH 20/60] #174 WIP updating declarations of quantities in PSy layer [skip ci] --- src/psyclone/gocean1p0.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/psyclone/gocean1p0.py b/src/psyclone/gocean1p0.py index 0e83007980..9f9a5d6380 100644 --- a/src/psyclone/gocean1p0.py +++ b/src/psyclone/gocean1p0.py @@ -241,10 +241,15 @@ def gen_code(self, parent): # Generate the code body of this subroutine self.schedule.gen_code(invoke_sub) + if self.schedule.opencl: + target = True + else: + target = False + # add the subroutine argument declarations for fields if len(self.unique_args_arrays) > 0: my_decl_arrays = TypeDeclGen(invoke_sub, datatype="r2d_field", - intent="inout", + intent="inout", target=target, entity_decls=self.unique_args_arrays) invoke_sub.add(my_decl_arrays) @@ -785,6 +790,14 @@ def gen_ocl(self, parent): ifthen = IfThenGen(parent, ".NOT. {0}%data_on_device".format(arg.name)) parent.add(ifthen) + parent.add(DeclGen(parent, datatype="integer", kind="c_size_t", + entity_decls=["size_in_bytes"])) + parent.add(DeclGen(parent, datatype="integer", + kind="c_intptr_t", target=True, + entity_decls=["write_event"])) + ifthen.add(AssignGen( + ifthen, lhs="size_in_bytes", + rhs="int({0}%grid%nx*{0}%grid%ny, 8)*8_8".format(garg.name))) ifthen.add(AssignGen( ifthen, lhs="ierr", rhs="clEnqueueWriteBuffer(cmd_queues(1), {0}%device_ptr, " From 125f173c8dd8065d3a63eaa67d39c890d385b393 Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Wed, 11 Jul 2018 18:03:37 +0100 Subject: [PATCH 21/60] #174 rm duplicate target attribute after merge [skip ci] --- src/psyclone/f2pygen.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/psyclone/f2pygen.py b/src/psyclone/f2pygen.py index 7450457beb..5a3c57b7cf 100644 --- a/src/psyclone/f2pygen.py +++ b/src/psyclone/f2pygen.py @@ -872,8 +872,6 @@ def __init__(self, parent, datatype="", entity_decls=None, intent="", my_attrspec.append("allocatable") if save: my_attrspec.append("save") - if target: - my_attrspec.append("target") if dimension != "": my_attrspec.append("dimension({0})".format(dimension)) self._decl.attrspec = my_attrspec From 17e0b550b35b30871b246958a323e0c9c6f9e8b3 Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Wed, 11 Jul 2018 18:05:23 +0100 Subject: [PATCH 22/60] #174 correct type of kernel-name list [skip ci] --- src/psyclone/psyGen.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/psyclone/psyGen.py b/src/psyclone/psyGen.py index c2ffaec4f3..08dda7398c 100644 --- a/src/psyclone/psyGen.py +++ b/src/psyclone/psyGen.py @@ -375,7 +375,7 @@ def gen_code(self, parent): def gen_ocl_init(self, parent, kernels): from psyclone.f2pygen import SubroutineGen, DeclGen, AssignGen, \ - CallGen, UseGen, CommentGen + CallGen, UseGen, CommentGen, CharDeclGen #import pdb; pdb.set_trace() sub = SubroutineGen(parent, "psy_init") parent.add(sub) @@ -390,9 +390,9 @@ def gen_ocl_init(self, parent, kernels): sub.add(CommentGen(sub, " The kernels this PSy layer module requires")) nkernstr = str(len(kernels)) - # TODO extend DeclGen to support character! - sub.add(DeclGen(sub, datatype="integer", - entity_decls=["kernel_names({0})".format(nkernstr)])) + # Declare array of character strings + sub.add(CharDeclGen(sub, length="30", + entity_decls=["kernel_names({0})".format(nkernstr)])) for idx, kern in enumerate(kernels): sub.add(AssignGen(sub, lhs="kernel_names({0})".format(idx+1), rhs='"{0}"'.format(kern))) @@ -1421,7 +1421,7 @@ def gen_code(self, parent): if_first.add(AssignGen(if_first, lhs="first_time", rhs=".false.")) if_first.add(AssignGen(if_first, lhs="num_cmd_queues", rhs="get_num_cmd_queues()")) - if_first.add(AssignGen(if_first, lhs="cmd_queues", + if_first.add(AssignGen(if_first, lhs="cmd_queues", pointer=True, rhs="get_cmd_queues()")) # Kernel pointers kernels = self.walk(self._children, Call) From 9d86acec30e472af59edd4975bafe9b3b950da9e Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Thu, 12 Jul 2018 11:46:23 +0100 Subject: [PATCH 23/60] #174 mv gen of set-kern-args into gocean1p0 [skip ci] --- src/psyclone/gocean1p0.py | 91 ++++++++++++++++++++++++++++++++------- src/psyclone/psyGen.py | 52 ++++++---------------- 2 files changed, 88 insertions(+), 55 deletions(-) diff --git a/src/psyclone/gocean1p0.py b/src/psyclone/gocean1p0.py index a2467dc75b..9105d43097 100644 --- a/src/psyclone/gocean1p0.py +++ b/src/psyclone/gocean1p0.py @@ -47,7 +47,7 @@ from __future__ import print_function from psyclone.parse import Descriptor, KernelType, ParseError -from psyclone.psyGen import PSy, Invokes, Invoke, Schedule, \ +from psyclone.psyGen import PSy, Invokes, Invoke, Schedule, args_filter, \ Loop, Kern, Arguments, Argument, KernelArgument, GenerationError import psyclone.expression as expr @@ -209,10 +209,9 @@ def unique_args_rscalars(self): as those that are r_scalar 'space'. ''' result = [] for call in self._schedule.calls(): - for arg in call.arguments.args: - if arg.type == 'scalar' and \ - arg.space.lower() == "r_scalar" and \ - not arg.is_literal and arg.name not in result: + for arg in args_filter(call.arguments.args, arg_types=["scalar"], + is_literal=False): + if arg.space.lower() == "r_scalar" and arg.name not in result: result.append(arg.name) return result @@ -222,10 +221,9 @@ def unique_args_iscalars(self): as those that are i_scalar 'space'). ''' result = [] for call in self._schedule.calls(): - for arg in call.arguments.args: - if arg.type == 'scalar' and \ - arg.space.lower() == "i_scalar" and \ - not arg.is_literal and arg.name not in result: + for arg in args_filter(call.arguments.args, arg_types=["scalar"], + is_literal=False): + if arg.space.lower() == "i_scalar" and arg.name not in result: result.append(arg.name) return result @@ -784,7 +782,7 @@ def gen_ocl(self, parent): IfThenGen garg = self._find_grid_access() - parent.add(DeclGen(parent, datatype="integer", + parent.add(DeclGen(parent, datatype="integer", target=True, entity_decls=["globalsize(2)"])) parent.add(AssignGen( parent, lhs="globalsize", @@ -805,9 +803,10 @@ def gen_ocl(self, parent): parent.add(DeclGen(parent, datatype="integer", kind="c_intptr_t", target=True, entity_decls=["write_event"])) - ifthen.add(AssignGen( - ifthen, lhs="size_in_bytes", - rhs="int({0}%grid%nx*{0}%grid%ny, 8)*8_8".format(garg.name))) + size_expr = "int({0}%grid%nx*{0}%grid%ny, 8)*8_8".\ + format(garg.name) + ifthen.add(AssignGen(ifthen, lhs="size_in_bytes", + rhs=size_expr)) ifthen.add(AssignGen( ifthen, lhs="ierr", rhs="clEnqueueWriteBuffer(cmd_queues(1), {0}%device_ptr, " @@ -839,13 +838,17 @@ def gen_ocl(self, parent): parent, "{0}_set_args".format(self.name), arguments)) # Then we call clEnqueueNDRangeKernel + parent.add(CommentGen(parent, " Launch the kernel")) cnull = "C_NULL_PTR" cmd_queue = "cmd_queues(1)" # TODO use namespace manager gsize = "globalsize" # TODO use namespace manager - args = [cmd_queue, kernel, "2", cnull, "C_LOC({0})".format(gsize), - cnull, "0", cnull, cnull] - parent.add(CallGen(parent, "clEnqueueNDRangeKernel", args)) + args = ", ".join([cmd_queue, kernel, "2", cnull, + "C_LOC({0})".format(gsize), + cnull, "0", cnull, cnull]) + parent.add( + AssignGen(parent, lhs="ierr", + rhs="clEnqueueNDRangeKernel({0})".format(args))) parent.add(CommentGen(parent, "")) @property @@ -853,6 +856,62 @@ def index_offset(self): ''' The grid index-offset convention that this kernel expects ''' return self._index_offset + def gen_arg_setter_code(self, parent): + ''' + Creates a Fortran routine to set the arguments of the OpenCL + version of this kernel. + + :param parent: Parent node of the set-kernel-arguments routine + :type parent: :py:class:`psyclone.f2pygen.moduleGen` + ''' + from psyclone.f2pygen import SubroutineGen, UseGen, DeclGen, \ + AssignGen, CommentGen + # TODO take care with literal arguments + # TODO use name-space manager for name of kernel-object arg + arguments = ["kernel_obj"] + [arg.name for arg in self._arguments.args] + sub = SubroutineGen(parent, name=self.name+"_set_args", + args=arguments) + parent.add(sub) + sub.add(UseGen(sub, name="ocl_utils_mod", only=True, + funcnames=["check_status"])) + sub.add(UseGen(sub, name="iso_c_binding", only=True, + funcnames=["c_sizeof", "c_loc", "c_intptr_t"])) + sub.add(UseGen(sub, name="clfortran", only=True, + funcnames=["clSetKernelArg"])) + # Declare arguments + sub.add(DeclGen(sub, datatype="integer", kind="c_intptr_t", + target=True, entity_decls=["kernel_obj"])) + + # Arrays (grid properties and fields) + args = args_filter(self._arguments.args, + arg_types=["field", "grid_property"]) + if args: + sub.add(DeclGen(sub, datatype="integer", kind="c_intptr_t", + target=True, + entity_decls=[arg.name for arg in args])) + # Scalars + args = args_filter(self._arguments.args, + arg_types=["scalar"], + is_literal=False) + for arg in args: + if arg.space.lower() == "r_scalar": + sub.add(DeclGen( + sub, datatype="REAL", intent="in", kind="wp", + target=True, entity_decls=[arg.name])) + else: + sub.add(DeclGen(sub, datatype="INTEGER", intent="in", + target=True, entity_decls=[arg.name])) + + # Declare local variables + sub.add(DeclGen(sub, datatype="integer", entity_decls=["ierr", + "arg_idx"])) + sub.add(CommentGen( + sub, + " Set the arguments for the {0} OpenCL Kernel".format(self.name))) + sub.add(AssignGen(sub, lhs="arg_idx", rhs="0")) + for arg in self.arguments.args: + arg.set_kernel_arg(sub) + class GOKernelArguments(Arguments): '''Provides information about GOcean kernel-call arguments diff --git a/src/psyclone/psyGen.py b/src/psyclone/psyGen.py index 08dda7398c..f29f8bf896 100644 --- a/src/psyclone/psyGen.py +++ b/src/psyclone/psyGen.py @@ -146,7 +146,8 @@ def zero_reduction_variables(red_call_list, parent): parent.add(CommentGen(parent, "")) -def args_filter(arg_list, arg_types=None, arg_accesses=None, arg_meshes=None): +def args_filter(arg_list, arg_types=None, arg_accesses=None, arg_meshes=None, + is_literal=True): ''' Return all arguments in the supplied list that are of type arg_types and with access in arg_accesses. If these are not set @@ -160,7 +161,8 @@ def args_filter(arg_list, arg_types=None, arg_accesses=None, arg_meshes=None): :type arg_accesses: list of str :param arg_meshes: List of meshes that arguments must be on :type arg_meshes: list of str - + :param bool is_literal: Whether or not to include literal arguments in \ + the returned list. :returns: list of kernel arguments matching the requirements :rtype: list of :py:class:`psyclone.parse.Descriptor` ''' @@ -175,6 +177,11 @@ def args_filter(arg_list, arg_types=None, arg_accesses=None, arg_meshes=None): if arg_meshes: if argument.mesh not in arg_meshes: continue + if not is_literal: + # We're not including literal arguments so skip this argument + # if it is literal. + if argument.is_literal: + continue arguments.append(argument) return arguments @@ -2654,42 +2661,8 @@ def gen_arg_setter_code(self, parent): :param parent: Parent node of the set-kernel-arguments routine :type parent: :py:class:`psyclone.f2pygen.moduleGen` ''' - from psyclone.f2pygen import SubroutineGen, UseGen, DeclGen, \ - AssignGen, CommentGen - # TODO take care with literal arguments - # TODO use name-space manager for name of kernel-object arg - arguments = ["kernel_obj"] + [arg.name for arg in self._arguments.args if arg.type != "scalar"] - sub = SubroutineGen(parent, name=self.name+"_set_args", - args=arguments) - parent.add(sub) - # TODO add a use for check_status() routine - sub.add(UseGen(sub, name="iso_c_binding", only=True, - funcnames=["c_sizeof", "c_loc", "c_intptr_t"])) - sub.add(UseGen(sub, name="clfortran", only=True, - funcnames=["clSetKernelArg"])) - # Declare arguments - sub.add(DeclGen(sub, datatype="integer", kind="c_intptr_t", - target=True, entity_decls=["kernel_obj"])) - args = [] - for arg in self._arguments.args: - if arg.type == "field": - args.append(arg.name) - elif arg.type == "grid_property": - args.append(arg.name) - if args: - sub.add(DeclGen(sub, datatype="integer", kind="c_intptr_t", - target=True, entity_decls=args)) - - # Declare local variables - sub.add(DeclGen(sub, datatype="integer", entity_decls=["ierr", - "arg_idx"])) - sub.add(CommentGen( - sub, - " Set the arguments for the {0} OpenCL Kernel".format(self.name))) - sub.add(AssignGen(sub, lhs="arg_idx", rhs="0")) - for arg in self.arguments.args: - if arg.type != "scalar": - arg.set_kernel_arg(sub) + raise NotImplementedError("gen_arg_setter_code must be implemented " + "by sub-class.") def incremented_arg(self, mapping={}): ''' Returns the argument that has INC access. Raises a @@ -2925,7 +2898,8 @@ def set_kernel_arg(self, parent): parent, lhs="ierr", rhs="clSetKernelArg({0}, arg_idx, C_SIZEOF({1}), C_LOC({2}))". format("kernel_obj", self.name, self.name))) - parent.add(CallGen(parent, "check_status", ["clSetKernelArg", "ierr"])) + parent.add(CallGen(parent, "check_status", + ["'clSetKernelArg'", "ierr"])) parent.add(AssignGen(parent, lhs="arg_idx", rhs="arg_idx + 1")) def backward_dependence(self): From 594fdbe50222208b2988d8b9263a57add3259015 Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Tue, 17 Jul 2018 09:59:28 +0100 Subject: [PATCH 24/60] #174 ensure ocl-init routine only executed once [skip ci] --- src/psyclone/psyGen.py | 49 +++++++++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/src/psyclone/psyGen.py b/src/psyclone/psyGen.py index f29f8bf896..355d29fda6 100644 --- a/src/psyclone/psyGen.py +++ b/src/psyclone/psyGen.py @@ -381,33 +381,48 @@ def gen_code(self, parent): self.gen_ocl_init(parent, opencl_kernels) def gen_ocl_init(self, parent, kernels): + ''' + ''' from psyclone.f2pygen import SubroutineGen, DeclGen, AssignGen, \ - CallGen, UseGen, CommentGen, CharDeclGen - #import pdb; pdb.set_trace() + CallGen, UseGen, CommentGen, CharDeclGen, IfThenGen sub = SubroutineGen(parent, "psy_init") parent.add(sub) sub.add(UseGen(sub, name="ocl_env_mod", only=True, funcnames=["ocl_env_init", "add_kernels"])) + # Add a logical variable used to ensure that this routine is only + # executed once. + sub.add(DeclGen(sub, datatype="logical", save=True, + entity_decls=["initialised"], + initial_values=[".False."])) + # Check whether or not this is our first time in the routine + sub.add(CommentGen(sub, " Check to make sure we only execute this " + "routine once")) + ifthen = IfThenGen(sub, ".not. initialised") + sub.add(ifthen) + ifthen.add(AssignGen(ifthen, lhs="initialised", rhs=".True.")) # Initialise the OpenCL environment - sub.add(CommentGen(sub, " Initialise the OpenCL environment/device")) - sub.add(CallGen(sub, "ocl_env_init")) + ifthen.add(CommentGen(ifthen, + " Initialise the OpenCL environment/device")) + ifthen.add(CallGen(ifthen, "ocl_env_init")) # Create a list of our kernels - sub.add(CommentGen(sub, " The kernels this PSy layer module requires")) + ifthen.add(CommentGen(ifthen, + " The kernels this PSy layer module requires")) nkernstr = str(len(kernels)) # Declare array of character strings - sub.add(CharDeclGen(sub, length="30", - entity_decls=["kernel_names({0})".format(nkernstr)])) + ifthen.add(CharDeclGen( + ifthen, length="30", + entity_decls=["kernel_names({0})".format(nkernstr)])) for idx, kern in enumerate(kernels): - sub.add(AssignGen(sub, lhs="kernel_names({0})".format(idx+1), - rhs='"{0}"'.format(kern))) - sub.add(CommentGen(sub, - " Create the OpenCL kernel objects. Expects " - "to find all of the compiled ")) - sub.add(CommentGen(sub, " kernels in PSYCLONE_KERNELS_FILE.")) - sub.add(CallGen(sub, "add_kernels", [nkernstr, "kernel_names"])) + ifthen.add(AssignGen(ifthen, lhs="kernel_names({0})".format(idx+1), + rhs='"{0}"'.format(kern))) + ifthen.add(CommentGen(ifthen, + " Create the OpenCL kernel objects. Expects " + "to find all of the compiled ")) + ifthen.add(CommentGen(ifthen, " kernels in PSYCLONE_KERNELS_FILE.")) + ifthen.add(CallGen(ifthen, "add_kernels", [nkernstr, "kernel_names"])) class NameSpaceFactory(object): @@ -1403,7 +1418,7 @@ def gen_code(self, parent): :type parent: :py:class:`psyclone.f2pygen.SubroutineGen` ''' from psyclone.f2pygen import UseGen, DeclGen, AssignGen, CommentGen, \ - IfThenGen + IfThenGen, CallGen if self._opencl: parent.add(UseGen(parent, name="iso_c_binding")) @@ -1426,6 +1441,10 @@ def gen_code(self, parent): if_first = IfThenGen(parent, "first_time") parent.add(if_first) if_first.add(AssignGen(if_first, lhs="first_time", rhs=".false.")) + if_first.add(CommentGen(if_first, + " Ensure OpenCL run-time is initialised " + "for this PSy-layer module")) + if_first.add(CallGen(if_first, "psy_init")) if_first.add(AssignGen(if_first, lhs="num_cmd_queues", rhs="get_num_cmd_queues()")) if_first.add(AssignGen(if_first, lhs="cmd_queues", pointer=True, From 91ec86c1e999bcad9d6e65c49021e5b419f9e134 Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Fri, 20 Jul 2018 10:46:24 +0100 Subject: [PATCH 25/60] #174 create device buffer for each field --- src/psyclone/gocean1p0.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/psyclone/gocean1p0.py b/src/psyclone/gocean1p0.py index 9105d43097..b2167699c2 100644 --- a/src/psyclone/gocean1p0.py +++ b/src/psyclone/gocean1p0.py @@ -779,7 +779,7 @@ def gen_ocl(self, parent): :type parent: :py:class:`psyclone.f2pygen.SubroutineGen` ''' from psyclone.f2pygen import CallGen, DeclGen, AssignGen, CommentGen, \ - IfThenGen + IfThenGen, UseGen garg = self._find_grid_access() parent.add(DeclGen(parent, datatype="integer", target=True, @@ -790,6 +790,8 @@ def gen_ocl(self, parent): kernel = "kernel_" + self._name # TODO use namespace manager + parent.add(UseGen(parent, name="ocl_env_mod", only=True, + funcnames=["create_buffer"])) # Ensure fields are on device TODO this belongs somewhere else! parent.add(CommentGen(parent, " Ensure field data is on device")) @@ -807,6 +809,10 @@ def gen_ocl(self, parent): format(garg.name) ifthen.add(AssignGen(ifthen, lhs="size_in_bytes", rhs=size_expr)) + ifthen.add(CommentGen(ifthen, " Create buffer on device")) + ifthen.add(AssignGen( + ifthen, lhs="{0}%device_ptr".format(arg.name), + rhs="create_buffer(CL_MEM_READ_WRITE, size_in_bytes)")) ifthen.add(AssignGen( ifthen, lhs="ierr", rhs="clEnqueueWriteBuffer(cmd_queues(1), {0}%device_ptr, " From 3dc8fee41aea93ab1703d6d0d4be774da6efbc49 Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Fri, 20 Jul 2018 11:30:30 +0100 Subject: [PATCH 26/60] #174 add arg index and name to setkernelarg message [skip ci] --- src/psyclone/psyGen.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/psyclone/psyGen.py b/src/psyclone/psyGen.py index 355d29fda6..7259dcd8f6 100644 --- a/src/psyclone/psyGen.py +++ b/src/psyclone/psyGen.py @@ -2908,18 +2908,24 @@ def call(self, value): ''' set the node that this argument is associated with ''' self._call = value - def set_kernel_arg(self, parent): + def set_kernel_arg(self, parent, index, kname): ''' - Generate the code to set this argument for an OpenCL kernel + Generate the code to set this argument for an OpenCL kernel. + + :param parent: the node in the Schedule to which to add the code. + :param int index: the (zero-based) index of this argument in the + list of kernel arguments. ''' from psyclone.f2pygen import AssignGen, CallGen parent.add(AssignGen( parent, lhs="ierr", - rhs="clSetKernelArg({0}, arg_idx, C_SIZEOF({1}), C_LOC({2}))". - format("kernel_obj", self.name, self.name))) - parent.add(CallGen(parent, "check_status", - ["'clSetKernelArg'", "ierr"])) - parent.add(AssignGen(parent, lhs="arg_idx", rhs="arg_idx + 1")) + rhs="clSetKernelArg({0}, {1}, C_SIZEOF({2}), C_LOC({2}))". + format("kernel_obj", index, self.name))) + parent.add(CallGen( + parent, "check_status", + ["'clSetKernelArg: arg {0} of {1}'".format(index, + kname), + "ierr"])) def backward_dependence(self): '''Returns the preceding argument that this argument has a direct From 1fd2f24dc43a35701c89b6db99ab296354d5602a Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Fri, 20 Jul 2018 11:31:10 +0100 Subject: [PATCH 27/60] #174 add code to set kernel argument for nx [skip ci] --- src/psyclone/gocean1p0.py | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/psyclone/gocean1p0.py b/src/psyclone/gocean1p0.py index b2167699c2..94d96fe446 100644 --- a/src/psyclone/gocean1p0.py +++ b/src/psyclone/gocean1p0.py @@ -827,7 +827,7 @@ def gen_ocl(self, parent): parent.add(AssignGen(parent, lhs="ierr", rhs="clFinish(cmd_queues(1))")) # Then we set the kernel arguments - arguments = [kernel] + arguments = [kernel, garg.name+"%grid%nx"] # TODO this argument-list generation duplicates that in # GOKern.gen_code(). We need to re-factor ala dynamo0p3.ArgOrdering. for arg in self._arguments.args: @@ -874,17 +874,20 @@ def gen_arg_setter_code(self, parent): AssignGen, CommentGen # TODO take care with literal arguments # TODO use name-space manager for name of kernel-object arg - arguments = ["kernel_obj"] + [arg.name for arg in self._arguments.args] + arguments = ["kernel_obj", "nx"] + \ + [arg.name for arg in self._arguments.args] sub = SubroutineGen(parent, name=self.name+"_set_args", args=arguments) parent.add(sub) sub.add(UseGen(sub, name="ocl_utils_mod", only=True, - funcnames=["check_status"])) + funcnames=["check_status"])) sub.add(UseGen(sub, name="iso_c_binding", only=True, funcnames=["c_sizeof", "c_loc", "c_intptr_t"])) sub.add(UseGen(sub, name="clfortran", only=True, funcnames=["clSetKernelArg"])) # Declare arguments + sub.add(DeclGen(sub, datatype="integer", target=True, + entity_decls=["nx"])) sub.add(DeclGen(sub, datatype="integer", kind="c_intptr_t", target=True, entity_decls=["kernel_obj"])) @@ -909,14 +912,21 @@ def gen_arg_setter_code(self, parent): target=True, entity_decls=[arg.name])) # Declare local variables - sub.add(DeclGen(sub, datatype="integer", entity_decls=["ierr", - "arg_idx"])) + sub.add(DeclGen(sub, datatype="integer", entity_decls=["ierr"])) sub.add(CommentGen( sub, " Set the arguments for the {0} OpenCL Kernel".format(self.name))) - sub.add(AssignGen(sub, lhs="arg_idx", rhs="0")) + # We must always pass "nx" (the horizontal dimension of the grid) into + # a kernel + index = 0 + sub.add(AssignGen( + sub, lhs="ierr", + rhs="clSetKernelArg({0}, {1}, C_SIZEOF({2}), C_LOC({2}))". + format("kernel_obj", index, "nx"))) + # Now all of the 'standard' kernel arguments for arg in self.arguments.args: - arg.set_kernel_arg(sub) + index += 1 + arg.set_kernel_arg(sub, index, self.name) class GOKernelArguments(Arguments): From d3fd8d5bc4dabb3eb3dc62fff62363dbb0ad3b2c Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Fri, 20 Jul 2018 11:59:19 +0100 Subject: [PATCH 28/60] #174 add code to set-up grid property arrays on device [skip ci] --- src/psyclone/gocean1p0.py | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/src/psyclone/gocean1p0.py b/src/psyclone/gocean1p0.py index 94d96fe446..49919d93ff 100644 --- a/src/psyclone/gocean1p0.py +++ b/src/psyclone/gocean1p0.py @@ -796,31 +796,46 @@ def gen_ocl(self, parent): parent.add(CommentGen(parent, " Ensure field data is on device")) for arg in self._arguments.args: - if arg.type == "field": - ifthen = IfThenGen(parent, - ".NOT. {0}%data_on_device".format(arg.name)) + if arg.type == "field" or arg.type == "grid_property": + + if arg.type == "field": + condition = ".NOT. {0}%data_on_device".format(arg.name) + device_buff = "{0}%device_ptr".format(arg.name) + host_buff = "{0}%data".format(arg.name) + else: + device_buff = "{0}%grid%{1}_device".format(garg.name, + arg.name) + condition = device_buff + " == 0" + host_buff = "{0}%grid%{1}".format(garg.name, arg.name) + + ifthen = IfThenGen(parent, condition) parent.add(ifthen) parent.add(DeclGen(parent, datatype="integer", kind="c_size_t", entity_decls=["size_in_bytes"])) parent.add(DeclGen(parent, datatype="integer", kind="c_intptr_t", target=True, entity_decls=["write_event"])) + size_expr = "int({0}%grid%nx*{0}%grid%ny, 8)*8_8".\ format(garg.name) ifthen.add(AssignGen(ifthen, lhs="size_in_bytes", rhs=size_expr)) ifthen.add(CommentGen(ifthen, " Create buffer on device")) + ifthen.add(AssignGen( - ifthen, lhs="{0}%device_ptr".format(arg.name), + ifthen, lhs=device_buff, rhs="create_buffer(CL_MEM_READ_WRITE, size_in_bytes)")) ifthen.add(AssignGen( ifthen, lhs="ierr", - rhs="clEnqueueWriteBuffer(cmd_queues(1), {0}%device_ptr, " - "CL_TRUE, 0_8, size_in_bytes, C_LOC({0}%data), " - "0, C_NULL_PTR, C_LOC(write_event))".format(arg.name))) - ifthen.add(AssignGen(ifthen, - lhs="{0}%data_on_device".format(arg.name), - rhs=".true.")) + rhs="clEnqueueWriteBuffer(cmd_queues(1), {0}, " + "CL_TRUE, 0_8, size_in_bytes, C_LOC({1}), " + "0, C_NULL_PTR, C_LOC(write_event))".format(device_buff, + host_buff))) + if arg.type == "field": + ifthen.add(AssignGen(ifthen, + lhs="{0}%data_on_device".format(arg.name), + rhs=".true.")) + # Ensure data copies have finished parent.add(CommentGen(parent, " Block until data copies have finished")) From 92b3319ef05174f18c33e32b5a59b658a31f079d Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Fri, 20 Jul 2018 14:00:17 +0100 Subject: [PATCH 29/60] #174 correct kind of globalsize and use c_sizeof to calculate device buffer size [skip ci] --- src/psyclone/gocean1p0.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/psyclone/gocean1p0.py b/src/psyclone/gocean1p0.py index 49919d93ff..e077f5366d 100644 --- a/src/psyclone/gocean1p0.py +++ b/src/psyclone/gocean1p0.py @@ -783,7 +783,7 @@ def gen_ocl(self, parent): garg = self._find_grid_access() parent.add(DeclGen(parent, datatype="integer", target=True, - entity_decls=["globalsize(2)"])) + kind="c_size_t", entity_decls=["globalsize(2)"])) parent.add(AssignGen( parent, lhs="globalsize", rhs="(/{0}%grid%nx, {0}%grid%ny/)".format(garg.name))) @@ -816,8 +816,8 @@ def gen_ocl(self, parent): kind="c_intptr_t", target=True, entity_decls=["write_event"])) - size_expr = "int({0}%grid%nx*{0}%grid%ny, 8)*8_8".\ - format(garg.name) + size_expr = "int({0}%grid%nx*{0}%grid%ny, 8)*c_sizeof({1}(1,1))".\ + format(garg.name, host_buff) ifthen.add(AssignGen(ifthen, lhs="size_in_bytes", rhs=size_expr)) ifthen.add(CommentGen(ifthen, " Create buffer on device")) From 5b1b707c7fd74f70177e81b0c406e765827532c2 Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Fri, 20 Jul 2018 17:13:14 +0100 Subject: [PATCH 30/60] #174 change to generate code for new FortCL interface [skip ci] --- src/psyclone/gocean1p0.py | 14 ++++++++------ src/psyclone/psyGen.py | 4 ++-- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/psyclone/gocean1p0.py b/src/psyclone/gocean1p0.py index e077f5366d..bda307dd02 100644 --- a/src/psyclone/gocean1p0.py +++ b/src/psyclone/gocean1p0.py @@ -790,8 +790,8 @@ def gen_ocl(self, parent): kernel = "kernel_" + self._name # TODO use namespace manager - parent.add(UseGen(parent, name="ocl_env_mod", only=True, - funcnames=["create_buffer"])) + parent.add(UseGen(parent, name="fortcl", only=True, + funcnames=["create_rw_buffer"])) # Ensure fields are on device TODO this belongs somewhere else! parent.add(CommentGen(parent, " Ensure field data is on device")) @@ -815,16 +815,18 @@ def gen_ocl(self, parent): parent.add(DeclGen(parent, datatype="integer", kind="c_intptr_t", target=True, entity_decls=["write_event"])) - - size_expr = "int({0}%grid%nx*{0}%grid%ny, 8)*c_sizeof({1}(1,1))".\ - format(garg.name, host_buff) + # Use c_sizeof() on first element of array to be copied over in + # order to cope with the fact that some grid properties are + # integer. + size_expr = ("int({0}%grid%nx*{0}%grid%ny, 8)*" + "c_sizeof({1}(1,1))".format(garg.name, host_buff)) ifthen.add(AssignGen(ifthen, lhs="size_in_bytes", rhs=size_expr)) ifthen.add(CommentGen(ifthen, " Create buffer on device")) ifthen.add(AssignGen( ifthen, lhs=device_buff, - rhs="create_buffer(CL_MEM_READ_WRITE, size_in_bytes)")) + rhs="create_rw_buffer(size_in_bytes)")) ifthen.add(AssignGen( ifthen, lhs="ierr", rhs="clEnqueueWriteBuffer(cmd_queues(1), {0}, " diff --git a/src/psyclone/psyGen.py b/src/psyclone/psyGen.py index 7259dcd8f6..0650f7e1fe 100644 --- a/src/psyclone/psyGen.py +++ b/src/psyclone/psyGen.py @@ -387,7 +387,7 @@ def gen_ocl_init(self, parent, kernels): CallGen, UseGen, CommentGen, CharDeclGen, IfThenGen sub = SubroutineGen(parent, "psy_init") parent.add(sub) - sub.add(UseGen(sub, name="ocl_env_mod", only=True, + sub.add(UseGen(sub, name="fortcl", only=True, funcnames=["ocl_env_init", "add_kernels"])) # Add a logical variable used to ensure that this routine is only # executed once. @@ -1423,7 +1423,7 @@ def gen_code(self, parent): if self._opencl: parent.add(UseGen(parent, name="iso_c_binding")) parent.add(UseGen(parent, name="clfortran")) - parent.add(UseGen(parent, name="ocl_env_mod", only=True, + parent.add(UseGen(parent, name="fortcl", only=True, funcnames=["get_num_cmd_queues", "get_cmd_queues", "get_kernel_by_name"])) From ab91844c48455a1b6f146b7dab8584f6b4e0aa25 Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Thu, 20 Sep 2018 10:35:34 +0100 Subject: [PATCH 31/60] #174 bring tests up-to-date now that we set the nx argument [skip ci] --- src/psyclone/tests/gocean1p0_opencl_test.py | 56 +++++++++++---------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/src/psyclone/tests/gocean1p0_opencl_test.py b/src/psyclone/tests/gocean1p0_opencl_test.py index 9208071e99..c60ba032c7 100644 --- a/src/psyclone/tests/gocean1p0_opencl_test.py +++ b/src/psyclone/tests/gocean1p0_opencl_test.py @@ -60,15 +60,16 @@ def test_use_stmts(): from psyclone.transformations import OCLTrans otrans = OCLTrans() otrans.apply(sched) - generated_code = str(psy.gen) + generated_code = str(psy.gen).lower() print(generated_code) expected = '''\ - SUBROUTINE invoke_0_compute_cu(cu_fld, p_fld, u_fld) - USE ocl_env_mod, ONLY: get_num_cmd_queues, get_cmd_queues, get_kernel_by_name - USE clfortran - USE iso_c_binding''' + subroutine invoke_0_compute_cu(cu_fld, p_fld, u_fld) + use fortcl, only: create_rw_buffer + use fortcl, only: get_num_cmd_queues, get_cmd_queues, get_kernel_by_name + use clfortran + use iso_c_binding''' assert expected in generated_code - assert "if(first_time)then" in generated_code + assert "if (first_time) then" in generated_code @pytest.mark.xfail(reason="DeclGen does not support character") @@ -119,30 +120,33 @@ def test_set_kern_args(): otrans.apply(sched) generated_code = str(psy.gen) print(generated_code) - assert generated_code.count("SUBROUTINE compute_cu_code_set_args(kern, cu_fld, " - "p_fld, u_fld)") == 1 + # Check we've only generated one set-args routine + assert generated_code.count("SUBROUTINE compute_cu_code_set_args(" + "kernel_obj, nx, cu_fld, p_fld, u_fld)") == 1 + # Declarations expected = '''\ - SUBROUTINE compute_cu_code_set_args(kern, cu_fld, p_fld, u_fld) + SUBROUTINE compute_cu_code_set_args(kernel_obj, nx, cu_fld, p_fld, u_fld) USE clfortran, ONLY: clSetKernelArg - USE iso_c_binding, ONLY: sizeof, c_loc, c_intptr_t - INTEGER ierr, arg_idx + USE iso_c_binding, ONLY: c_sizeof, c_loc, c_intptr_t + USE ocl_utils_mod, ONLY: check_status + INTEGER ierr INTEGER(KIND=c_intptr_t), target :: cu_fld, p_fld, u_fld - INTEGER(KIND=c_intptr_t), target :: kern + INTEGER(KIND=c_intptr_t), target :: kernel_obj''' + assert expected in generated_code + expected = '''\ ! Set the arguments for the compute_cu_code OpenCL Kernel - arg_idx = 0 - ierr = clSetKernelArg(kernel_obj, arg_idx, sizeof(cu_fld), C_LOC(cu_fld)) - CALL check_status(clSetKernelArg, ierr) - arg_idx = arg_idx + 1 - ierr = clSetKernelArg(kernel_obj, arg_idx, sizeof(p_fld), C_LOC(p_fld)) - CALL check_status(clSetKernelArg, ierr) - arg_idx = arg_idx + 1 - ierr = clSetKernelArg(kernel_obj, arg_idx, sizeof(u_fld), C_LOC(u_fld)) - CALL check_status(clSetKernelArg, ierr) - arg_idx = arg_idx + 1 - END SUBROUTINE compute_cu_code_set_args -''' + ierr = clSetKernelArg(kernel_obj, 0, C_SIZEOF(nx), C_LOC(nx)) + ierr = clSetKernelArg(kernel_obj, 1, C_SIZEOF(cu_fld), C_LOC(cu_fld)) + CALL check_status('clSetKernelArg: arg 1 of compute_cu_code', ierr) + ierr = clSetKernelArg(kernel_obj, 2, C_SIZEOF(p_fld), C_LOC(p_fld)) + CALL check_status('clSetKernelArg: arg 2 of compute_cu_code', ierr) + ierr = clSetKernelArg(kernel_obj, 3, C_SIZEOF(u_fld), C_LOC(u_fld)) + CALL check_status('clSetKernelArg: arg 3 of compute_cu_code', ierr) + END SUBROUTINE compute_cu_code_set_args''' assert expected in generated_code - assert generated_code.count("SUBROUTINE time_smooth_code_set_args(kern, u_fld, " + assert generated_code.count("SUBROUTINE time_smooth_code_set_args(" + "kernel_obj, nx, u_fld, " "unew_fld, uold_fld)") == 1 assert ("CALL compute_cu_code_set_args(kernel_compute_cu_code, " - "cu_fld%device_ptr, p_fld%device_ptr, u_fld%device_ptr)" in generated_code) + "p_fld%grid%nx, cu_fld%device_ptr, p_fld%device_ptr, " + "u_fld%device_ptr)" in generated_code) From 8091518e327d1ee069f3ad29f1bf1547d406e817 Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Thu, 20 Sep 2018 11:18:52 +0100 Subject: [PATCH 32/60] #216 fix test failures --- src/psyclone/gocean1p0.py | 2 +- src/psyclone/tests/gocean1p0_transformations_test.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/psyclone/gocean1p0.py b/src/psyclone/gocean1p0.py index 9b31897cb4..bfb1f2a43b 100644 --- a/src/psyclone/gocean1p0.py +++ b/src/psyclone/gocean1p0.py @@ -824,7 +824,7 @@ def gen_ocl(self, parent): from psyclone.f2pygen import CallGen, DeclGen, AssignGen, CommentGen, \ IfThenGen, UseGen - garg = self._find_grid_access() + garg = self.find_grid_access() parent.add(DeclGen(parent, datatype="integer", target=True, kind="c_size_t", entity_decls=["globalsize(2)"])) parent.add(AssignGen( diff --git a/src/psyclone/tests/gocean1p0_transformations_test.py b/src/psyclone/tests/gocean1p0_transformations_test.py index c593a673be..1559b2406d 100644 --- a/src/psyclone/tests/gocean1p0_transformations_test.py +++ b/src/psyclone/tests/gocean1p0_transformations_test.py @@ -1371,7 +1371,7 @@ def test_ocl_apply(): ''' Check that OCLTrans generates correct code ''' from psyclone.transformations import OCLTrans psy, invoke = get_invoke("test11_different_iterates_over_" - "one_invoke.f90", 0) + "one_invoke.f90", API, idx=0) schedule = invoke.schedule ocl = OCLTrans() From 9fd8c5dc1d66f5b4553e134539dca22882e9cddb Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Thu, 20 Sep 2018 11:52:18 +0100 Subject: [PATCH 33/60] #216 add test for setting float scalar arg [skip ci] --- src/psyclone/tests/gocean1p0_opencl_test.py | 63 +++++++++++++-------- 1 file changed, 40 insertions(+), 23 deletions(-) diff --git a/src/psyclone/tests/gocean1p0_opencl_test.py b/src/psyclone/tests/gocean1p0_opencl_test.py index c60ba032c7..60b3eb1ca6 100644 --- a/src/psyclone/tests/gocean1p0_opencl_test.py +++ b/src/psyclone/tests/gocean1p0_opencl_test.py @@ -32,7 +32,7 @@ # POSSIBILITY OF SUCH DAMAGE. # ---------------------------------------------------------------------------- # Author A. R. Porter, STFC Daresbury Lab -from __future__ import print_function +from __future__ import print_function, absolute_import '''Tests for OpenCL PSy-layer code generation that are specific to the GOcean 1.0 API.''' @@ -42,6 +42,7 @@ from psyclone.parse import parse from psyclone.psyGen import PSyFactory from psyclone.generator import GenerationError, ParseError +from psyclone_test_utils import get_invoke API = "gocean1.0" @@ -49,13 +50,7 @@ def test_use_stmts(): ''' Test that generating code for OpenCL results in the correct module use statements ''' - _, invoke_info = parse(os.path.join(os.path. - dirname(os.path. - abspath(__file__)), - "test_files", "gocean1p0", - "single_invoke.f90"), - api=API) - psy = PSyFactory(API).create(invoke_info) + psy, _ = get_invoke("single_invoke.f90", API, idx=0) sched = psy.invokes.invoke_list[0].schedule from psyclone.transformations import OCLTrans otrans = OCLTrans() @@ -76,13 +71,7 @@ def test_use_stmts(): def test_psy_init(): ''' Check that we create a psy_init() routine that sets-up the OpenCL environment ''' - _, invoke_info = parse(os.path.join(os.path. - dirname(os.path. - abspath(__file__)), - "test_files", "gocean1p0", - "single_invoke.f90"), - api=API) - psy = PSyFactory(API).create(invoke_info) + psy, _ = get_invoke("single_invoke.f90", API, idx=0) sched = psy.invokes.invoke_list[0].schedule from psyclone.transformations import OCLTrans otrans = OCLTrans() @@ -107,19 +96,12 @@ def test_psy_init(): def test_set_kern_args(): ''' Check that we generate the necessary code to set kernel arguments ''' - _, invoke_info = parse(os.path.join(os.path. - dirname(os.path. - abspath(__file__)), - "test_files", "gocean1p0", - "single_invoke_two_kernels.f90"), - api=API) - psy = PSyFactory(API).create(invoke_info) + psy, _ = get_invoke("single_invoke_two_kernels.f90", API, idx=0) sched = psy.invokes.invoke_list[0].schedule from psyclone.transformations import OCLTrans otrans = OCLTrans() otrans.apply(sched) generated_code = str(psy.gen) - print(generated_code) # Check we've only generated one set-args routine assert generated_code.count("SUBROUTINE compute_cu_code_set_args(" "kernel_obj, nx, cu_fld, p_fld, u_fld)") == 1 @@ -150,3 +132,38 @@ def test_set_kern_args(): assert ("CALL compute_cu_code_set_args(kernel_compute_cu_code, " "p_fld%grid%nx, cu_fld%device_ptr, p_fld%device_ptr, " "u_fld%device_ptr)" in generated_code) + + +def test_set_kern_float_arg(): + ''' Check that we generate correct code to set a real, scalar kernel + argument. ''' + psy, _ = get_invoke("single_invoke_scalar_float_arg.f90", API, idx=0) + sched = psy.invokes.invoke_list[0].schedule + from psyclone.transformations import OCLTrans + otrans = OCLTrans() + otrans.apply(sched) + generated_code = str(psy.gen) + print(generated_code) + expected = '''\ + SUBROUTINE bc_ssh_code_set_args(kernel_obj, nx, a_scalar, ssh_fld, tmask) + USE clfortran, ONLY: clSetKernelArg + USE iso_c_binding, ONLY: c_sizeof, c_loc, c_intptr_t + USE ocl_utils_mod, ONLY: check_status + REAL(KIND=wp), intent(in), target :: a_scalar + INTEGER ierr + INTEGER(KIND=c_intptr_t), target :: ssh_fld, tmask + INTEGER(KIND=c_intptr_t), target :: kernel_obj + INTEGER, target :: nx +''' + assert expected in generated_code + expected = '''\ + ! Set the arguments for the bc_ssh_code OpenCL Kernel + ierr = clSetKernelArg(kernel_obj, 0, C_SIZEOF(nx), C_LOC(nx)) + ierr = clSetKernelArg(kernel_obj, 1, C_SIZEOF(a_scalar), C_LOC(a_scalar)) + CALL check_status('clSetKernelArg: arg 1 of bc_ssh_code', ierr) + ierr = clSetKernelArg(kernel_obj, 2, C_SIZEOF(ssh_fld), C_LOC(ssh_fld)) + CALL check_status('clSetKernelArg: arg 2 of bc_ssh_code', ierr) + ierr = clSetKernelArg(kernel_obj, 3, C_SIZEOF(tmask), C_LOC(tmask)) + CALL check_status('clSetKernelArg: arg 3 of bc_ssh_code', ierr) + END SUBROUTINE bc_ssh_code_set_args''' + assert expected in generated_code From b552e76df77d35d9544c7657bc3ff98ae44fb823 Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Thu, 20 Sep 2018 12:22:17 +0100 Subject: [PATCH 34/60] #216 cover missed lines and remove _opencl from Node class --- src/psyclone/psyGen.py | 3 +-- src/psyclone/tests/psyGen_test.py | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/psyclone/psyGen.py b/src/psyclone/psyGen.py index 2fe87be348..78d026540c 100644 --- a/src/psyclone/psyGen.py +++ b/src/psyclone/psyGen.py @@ -371,7 +371,7 @@ def gen_code(self, parent): # create routine(s) to set the arguments of the Kernel(s) it # calls. We do it here as this enables us to prevent # duplication. - if invoke.schedule._opencl: + if invoke.schedule.opencl: for kern in invoke.schedule.kern_calls(): if kern.name not in opencl_kernels: opencl_kernels.append(kern.name) @@ -1133,7 +1133,6 @@ def __init__(self, children=None, parent=None): else: self._children = children self._parent = parent - self._opencl = False def __str__(self): raise NotImplementedError("Please implement me") diff --git a/src/psyclone/tests/psyGen_test.py b/src/psyclone/tests/psyGen_test.py index 22f6715e92..e1ea2dbdbf 100644 --- a/src/psyclone/tests/psyGen_test.py +++ b/src/psyclone/tests/psyGen_test.py @@ -557,6 +557,17 @@ def test_sched_view(capsys): assert colored("Schedule", SCHEDULE_COLOUR_MAP["Schedule"]) in output +def test_sched_ocl_setter(): + ''' Check that the opencl setter raises the expected error if not passed + a bool. ''' + _, invoke_info = parse(os.path.join(BASE_PATH, + "15.9.1_X_innerproduct_Y_builtin.f90"), + api="dynamo0.3") + psy = PSyFactory("dynamo0.3", distributed_memory=True).create(invoke_info) + with pytest.raises(ValueError) as err: + psy.invokes.invoke_list[0].schedule.opencl = "a string" + assert "Schedule.opencl must be a bool but got " in str(err) + # Kern class test @@ -587,6 +598,20 @@ def test_kern_coloured_text(): assert colored("KernCall", SCHEDULE_COLOUR_MAP["KernCall"]) in ret_str +def test_kern_abstract_methods(): + ''' Check that the abstract methods of the Kern class raise the + NotImplementedError. ''' + # We need to get a valid kernel object + from psyclone import dynamo0p3 + ast = fpapi.parse(FAKE_KERNEL_METADATA, ignore_comments=False) + metadata = DynKernMetadata(ast) + my_kern = DynKern() + my_kern.load_meta(metadata) + with pytest.raises(NotImplementedError) as err: + super(dynamo0p3.DynKern, my_kern).gen_arg_setter_code(None) + assert "gen_arg_setter_code must be implemented by sub-class" in str(err) + + def test_call_abstract_methods(): ''' Check that calling the abstract methods of Call raises the expected exceptions ''' From d70c698ea654db2b3f7e3c187d9696fa596c5ed1 Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Thu, 20 Sep 2018 13:27:35 +0100 Subject: [PATCH 35/60] #216 rm OpenCL wrapper code now in FortCL [skip ci] --- lib/opencl/Makefile | 14 - lib/opencl/README.md | 11 - lib/opencl/clfortran.f90 | 1931 --------------------------------- lib/opencl/ocl_env_mod.f90 | 185 ---- lib/opencl/ocl_params_mod.f90 | 10 - lib/opencl/ocl_utils_mod.f90 | 433 -------- 6 files changed, 2584 deletions(-) delete mode 100644 lib/opencl/Makefile delete mode 100644 lib/opencl/README.md delete mode 100644 lib/opencl/clfortran.f90 delete mode 100644 lib/opencl/ocl_env_mod.f90 delete mode 100644 lib/opencl/ocl_params_mod.f90 delete mode 100644 lib/opencl/ocl_utils_mod.f90 diff --git a/lib/opencl/Makefile b/lib/opencl/Makefile deleted file mode 100644 index 02031e3158..0000000000 --- a/lib/opencl/Makefile +++ /dev/null @@ -1,14 +0,0 @@ -# Makefile for OpenCL utility modules -OBJECTS = clfortran.o ocl_params_mod.o ocl_utils_mod.o ocl_env_mod.o - -all: ${OBJECTS} - -%.o: %.f90 - ${F90} ${F90FLAGS} -c $< - -clean: - rm -f *.o - rm -f *~ - -allclean: clean - rm -f *.mod *.MOD diff --git a/lib/opencl/README.md b/lib/opencl/README.md deleted file mode 100644 index 925e6907f9..0000000000 --- a/lib/opencl/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Introduction # - -This directory contains Fortran source code that wraps the OpenCL -functionality required by PSyclone when generating code that -targets an OpenCL device. It uses the "clFortran" interface code -available from https://github.com/cass-support/clfortran. - -# Compiling # - -The Makefile picks-up the compiler, flags etc. from environment -variables. You will need to set F90 (and optionally, F90FLAGS). diff --git a/lib/opencl/clfortran.f90 b/lib/opencl/clfortran.f90 deleted file mode 100644 index 4f12873f39..0000000000 --- a/lib/opencl/clfortran.f90 +++ /dev/null @@ -1,1931 +0,0 @@ -! ----------------------------------------------------------------------------- -! CLFORTRAN - OpenCL bindings module for Fortran. -! -! This is the main module file and contains all OpenCL API definitions to be -! invoked from Fortran programs. -! -! ----------------------------------------------------------------------------- -! -! Copyright (C) 2013 Company for Advanced Supercomputing Solutions LTD -! Bosmat 2a St. -! Shoham -! Israel 60850 -! http://www.cass-hpc.com -! -! Author: Mordechai Butrashvily -! -! ----------------------------------------------------------------------------- -! -! This program is free software: you can redistribute it and/or modify -! it under the terms of the GNU Lesser General Public License as published by -! the Free Software Foundation, either version 3 of the License, or -! (at your option) any later version. -! -! This program is distributed in the hope that it will be useful, -! but WITHOUT ANY WARRANTY; without even the implied warranty of -! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -! GNU Lesser General Public License for more details. -! -! You should have received a copy of the GNU Lesser General Public License -! along with this program. If not, see . -! -! ----------------------------------------------------------------------------- - -module clfortran - USE ISO_C_BINDING - implicit none - - ! Error Codes - integer(c_int32_t), parameter :: CL_SUCCESS = 0 - integer(c_int32_t), parameter :: CL_DEVICE_NOT_FOUND = -1 - integer(c_int32_t), parameter :: CL_DEVICE_NOT_AVAILABLE = -2 - integer(c_int32_t), parameter :: CL_COMPILER_NOT_AVAILABLE = -3 - integer(c_int32_t), parameter :: CL_MEM_OBJECT_ALLOCATION_FAILURE = -4 - integer(c_int32_t), parameter :: CL_OUT_OF_RESOURCES = -5 - integer(c_int32_t), parameter :: CL_OUT_OF_HOST_MEMORY = -6 - integer(c_int32_t), parameter :: CL_PROFILING_INFO_NOT_AVAILABLE = -7 - integer(c_int32_t), parameter :: CL_MEM_COPY_OVERLAP = -8 - integer(c_int32_t), parameter :: CL_IMAGE_FORMAT_MISMATCH = -9 - integer(c_int32_t), parameter :: CL_IMAGE_FORMAT_NOT_SUPPORTED = -10 - integer(c_int32_t), parameter :: CL_BUILD_PROGRAM_FAILURE = -11 - integer(c_int32_t), parameter :: CL_MAP_FAILURE = -12 - integer(c_int32_t), parameter :: CL_MISALIGNED_SUB_BUFFER_OFFSET = -13 - integer(c_int32_t), parameter :: CL_EXEC_STATUS_ERROR_FOR_EVENTS_IN_WAIT_LIST = -14 - integer(c_int32_t), parameter :: CL_COMPILE_PROGRAM_FAILURE = -15 - integer(c_int32_t), parameter :: CL_LINKER_NOT_AVAILABLE = -16 - integer(c_int32_t), parameter :: CL_LINK_PROGRAM_FAILURE = -17 - integer(c_int32_t), parameter :: CL_DEVICE_PARTITION_FAILED = -18 - integer(c_int32_t), parameter :: CL_KERNEL_ARG_INFO_NOT_AVAILABLE = -19 - - integer(c_int32_t), parameter :: CL_INVALID_VALUE = -30 - integer(c_int32_t), parameter :: CL_INVALID_DEVICE_TYPE = -31 - integer(c_int32_t), parameter :: CL_INVALID_PLATFORM = -32 - integer(c_int32_t), parameter :: CL_INVALID_DEVICE = -33 - integer(c_int32_t), parameter :: CL_INVALID_CONTEXT = -34 - integer(c_int32_t), parameter :: CL_INVALID_QUEUE_PROPERTIES = -35 - integer(c_int32_t), parameter :: CL_INVALID_COMMAND_QUEUE = -36 - integer(c_int32_t), parameter :: CL_INVALID_HOST_PTR = -37 - integer(c_int32_t), parameter :: CL_INVALID_MEM_OBJECT = -38 - integer(c_int32_t), parameter :: CL_INVALID_IMAGE_FORMAT_DESCRIPTOR = -39 - integer(c_int32_t), parameter :: CL_INVALID_IMAGE_SIZE = -40 - integer(c_int32_t), parameter :: CL_INVALID_SAMPLER = -41 - integer(c_int32_t), parameter :: CL_INVALID_BINARY = -42 - integer(c_int32_t), parameter :: CL_INVALID_BUILD_OPTIONS = -43 - integer(c_int32_t), parameter :: CL_INVALID_PROGRAM = -44 - integer(c_int32_t), parameter :: CL_INVALID_PROGRAM_EXECUTABLE = -45 - integer(c_int32_t), parameter :: CL_INVALID_KERNEL_NAME = -46 - integer(c_int32_t), parameter :: CL_INVALID_KERNEL_DEFINITION = -47 - integer(c_int32_t), parameter :: CL_INVALID_KERNEL = -48 - integer(c_int32_t), parameter :: CL_INVALID_ARG_INDEX = -49 - integer(c_int32_t), parameter :: CL_INVALID_ARG_VALUE = -50 - integer(c_int32_t), parameter :: CL_INVALID_ARG_SIZE = -51 - integer(c_int32_t), parameter :: CL_INVALID_KERNEL_ARGS = -52 - integer(c_int32_t), parameter :: CL_INVALID_WORK_DIMENSION = -53 - integer(c_int32_t), parameter :: CL_INVALID_WORK_GROUP_SIZE = -54 - integer(c_int32_t), parameter :: CL_INVALID_WORK_ITEM_SIZE = -55 - integer(c_int32_t), parameter :: CL_INVALID_GLOBAL_OFFSET = -56 - integer(c_int32_t), parameter :: CL_INVALID_EVENT_WAIT_LIST = -57 - integer(c_int32_t), parameter :: CL_INVALID_EVENT = -58 - integer(c_int32_t), parameter :: CL_INVALID_OPERATION = -59 - integer(c_int32_t), parameter :: CL_INVALID_GL_OBJECT = -60 - integer(c_int32_t), parameter :: CL_INVALID_BUFFER_SIZE = -61 - integer(c_int32_t), parameter :: CL_INVALID_MIP_LEVEL = -62 - integer(c_int32_t), parameter :: CL_INVALID_GLOBAL_WORK_SIZE = -63 - integer(c_int32_t), parameter :: CL_INVALID_PROPERTY = -64 - integer(c_int32_t), parameter :: CL_INVALID_IMAGE_DESCRIPTOR = -65 - integer(c_int32_t), parameter :: CL_INVALID_COMPILER_OPTIONS = -66 - integer(c_int32_t), parameter :: CL_INVALID_LINKER_OPTIONS = -67 - integer(c_int32_t), parameter :: CL_INVALID_DEVICE_PARTITION_COUNT = -68 - - ! OpenCL Version - integer(c_int32_t), parameter :: CL_VERSION_1_0 = 1 - integer(c_int32_t), parameter :: CL_VERSION_1_1 = 1 - integer(c_int32_t), parameter :: CL_VERSION_1_2 = 1 - - ! cl_bool - integer(c_int32_t), parameter :: CL_FALSE = 0 - integer(c_int32_t), parameter :: CL_TRUE = 1 - integer(c_int32_t), parameter :: CL_BLOCKING = CL_TRUE - integer(c_int32_t), parameter :: CL_NON_BLOCKING = CL_FALSE - - ! cl_platform_info - integer(c_int32_t), parameter :: CL_PLATFORM_PROFILE = Z'0900' - integer(c_int32_t), parameter :: CL_PLATFORM_VERSION = Z'0901' - integer(c_int32_t), parameter :: CL_PLATFORM_NAME = Z'0902' - integer(c_int32_t), parameter :: CL_PLATFORM_VENDOR = Z'0903' - integer(c_int32_t), parameter :: CL_PLATFORM_EXTENSIONS = Z'0904' - - ! cl_device_type - bitfield - integer(c_int64_t), parameter :: CL_DEVICE_TYPE_DEFAULT = b'00001' - integer(c_int64_t), parameter :: CL_DEVICE_TYPE_CPU = b'00010' - integer(c_int64_t), parameter :: CL_DEVICE_TYPE_GPU = b'00100' - integer(c_int64_t), parameter :: CL_DEVICE_TYPE_ACCELERATOR = b'01000' - integer(c_int64_t), parameter :: CL_DEVICE_TYPE_CUSTOM = b'10000' - integer(c_int64_t), parameter :: CL_DEVICE_TYPE_ALL = Z'FFFFFFFF' - - ! cl_device_info - integer(c_int32_t), parameter :: CL_DEVICE_TYPE = Z'1000' - integer(c_int32_t), parameter :: CL_DEVICE_VENDOR_ID = Z'1001' - integer(c_int32_t), parameter :: CL_DEVICE_MAX_COMPUTE_UNITS = Z'1002' - integer(c_int32_t), parameter :: CL_DEVICE_MAX_WORK_ITEM_DIMENSIONS = Z'1003' - integer(c_int32_t), parameter :: CL_DEVICE_MAX_WORK_GROUP_SIZE = Z'1004' - integer(c_int32_t), parameter :: CL_DEVICE_MAX_WORK_ITEM_SIZES = Z'1005' - integer(c_int32_t), parameter :: CL_DEVICE_PREFERRED_VECTOR_WIDTH_CHAR = Z'1006' - integer(c_int32_t), parameter :: CL_DEVICE_PREFERRED_VECTOR_WIDTH_SHORT = Z'1007' - integer(c_int32_t), parameter :: CL_DEVICE_PREFERRED_VECTOR_WIDTH_INT = Z'1008' - integer(c_int32_t), parameter :: CL_DEVICE_PREFERRED_VECTOR_WIDTH_LONG = Z'1009' - integer(c_int32_t), parameter :: CL_DEVICE_PREFERRED_VECTOR_WIDTH_FLOAT = Z'100A' - integer(c_int32_t), parameter :: CL_DEVICE_PREFERRED_VECTOR_WIDTH_DOUBLE = Z'100B' - integer(c_int32_t), parameter :: CL_DEVICE_MAX_CLOCK_FREQUENCY = Z'100C' - integer(c_int32_t), parameter :: CL_DEVICE_ADDRESS_BITS = Z'100D' - integer(c_int32_t), parameter :: CL_DEVICE_MAX_READ_IMAGE_ARGS = Z'100E' - integer(c_int32_t), parameter :: CL_DEVICE_MAX_WRITE_IMAGE_ARGS = Z'100F' - integer(c_int32_t), parameter :: CL_DEVICE_MAX_MEM_ALLOC_SIZE = Z'1010' - integer(c_int32_t), parameter :: CL_DEVICE_IMAGE2D_MAX_WIDTH = Z'1011' - integer(c_int32_t), parameter :: CL_DEVICE_IMAGE2D_MAX_HEIGHT = Z'1012' - integer(c_int32_t), parameter :: CL_DEVICE_IMAGE3D_MAX_WIDTH = Z'1013' - integer(c_int32_t), parameter :: CL_DEVICE_IMAGE3D_MAX_HEIGHT = Z'1014' - integer(c_int32_t), parameter :: CL_DEVICE_IMAGE3D_MAX_DEPTH = Z'1015' - integer(c_int32_t), parameter :: CL_DEVICE_IMAGE_SUPPORT = Z'1016' - integer(c_int32_t), parameter :: CL_DEVICE_MAX_PARAMETER_SIZE = Z'1017' - integer(c_int32_t), parameter :: CL_DEVICE_MAX_SAMPLERS = Z'1018' - integer(c_int32_t), parameter :: CL_DEVICE_MEM_BASE_ADDR_ALIGN = Z'1019' - integer(c_int32_t), parameter :: CL_DEVICE_MIN_DATA_TYPE_ALIGN_SIZE = Z'101A' - integer(c_int32_t), parameter :: CL_DEVICE_SINGLE_FP_CONFIG = Z'101B' - integer(c_int32_t), parameter :: CL_DEVICE_GLOBAL_MEM_CACHE_TYPE = Z'101C' - integer(c_int32_t), parameter :: CL_DEVICE_GLOBAL_MEM_CACHELINE_SIZE = Z'101D' - integer(c_int32_t), parameter :: CL_DEVICE_GLOBAL_MEM_CACHE_SIZE = Z'101E' - integer(c_int32_t), parameter :: CL_DEVICE_GLOBAL_MEM_SIZE = Z'101F' - integer(c_int32_t), parameter :: CL_DEVICE_MAX_CONSTANT_BUFFER_SIZE = Z'1020' - integer(c_int32_t), parameter :: CL_DEVICE_MAX_CONSTANT_ARGS = Z'1021' - integer(c_int32_t), parameter :: CL_DEVICE_LOCAL_MEM_TYPE = Z'1022' - integer(c_int32_t), parameter :: CL_DEVICE_LOCAL_MEM_SIZE = Z'1023' - integer(c_int32_t), parameter :: CL_DEVICE_ERROR_CORRECTION_SUPPORT = Z'1024' - integer(c_int32_t), parameter :: CL_DEVICE_PROFILING_TIMER_RESOLUTION = Z'1025' - integer(c_int32_t), parameter :: CL_DEVICE_ENDIAN_LITTLE = Z'1026' - integer(c_int32_t), parameter :: CL_DEVICE_AVAILABLE = Z'1027' - integer(c_int32_t), parameter :: CL_DEVICE_COMPILER_AVAILABLE = Z'1028' - integer(c_int32_t), parameter :: CL_DEVICE_EXECUTION_CAPABILITIES = Z'1029' - integer(c_int32_t), parameter :: CL_DEVICE_QUEUE_PROPERTIES = Z'102A' - integer(c_int32_t), parameter :: CL_DEVICE_NAME = Z'102B' - integer(c_int32_t), parameter :: CL_DEVICE_VENDOR = Z'102C' - integer(c_int32_t), parameter :: CL_DRIVER_VERSION = Z'102D' - integer(c_int32_t), parameter :: CL_DEVICE_PROFILE = Z'102E' - integer(c_int32_t), parameter :: CL_DEVICE_VERSION = Z'102F' - integer(c_int32_t), parameter :: CL_DEVICE_EXTENSIONS = Z'1030' - integer(c_int32_t), parameter :: CL_DEVICE_PLATFORM = Z'1031' - integer(c_int32_t), parameter :: CL_DEVICE_DOUBLE_FP_CONFIG = Z'1032' - ! 0x1033 reserved for CL_DEVICE_HALF_FP_CONFIG - integer(c_int32_t), parameter :: CL_DEVICE_PREFERRED_VECTOR_WIDTH_HALF = Z'1034' - integer(c_int32_t), parameter :: CL_DEVICE_HOST_UNIFIED_MEMORY = Z'1035' - integer(c_int32_t), parameter :: CL_DEVICE_NATIVE_VECTOR_WIDTH_CHAR = Z'1036' - integer(c_int32_t), parameter :: CL_DEVICE_NATIVE_VECTOR_WIDTH_SHORT = Z'1037' - integer(c_int32_t), parameter :: CL_DEVICE_NATIVE_VECTOR_WIDTH_INT = Z'1038' - integer(c_int32_t), parameter :: CL_DEVICE_NATIVE_VECTOR_WIDTH_LONG = Z'1039' - integer(c_int32_t), parameter :: CL_DEVICE_NATIVE_VECTOR_WIDTH_FLOAT = Z'103A' - integer(c_int32_t), parameter :: CL_DEVICE_NATIVE_VECTOR_WIDTH_DOUBLE = Z'103B' - integer(c_int32_t), parameter :: CL_DEVICE_NATIVE_VECTOR_WIDTH_HALF = Z'103C' - integer(c_int32_t), parameter :: CL_DEVICE_OPENCL_C_VERSION = Z'103D' - integer(c_int32_t), parameter :: CL_DEVICE_LINKER_AVAILABLE = Z'103E' - integer(c_int32_t), parameter :: CL_DEVICE_BUILT_IN_KERNELS = Z'103F' - integer(c_int32_t), parameter :: CL_DEVICE_IMAGE_MAX_BUFFER_SIZE = Z'1040' - integer(c_int32_t), parameter :: CL_DEVICE_IMAGE_MAX_ARRAY_SIZE = Z'1041' - integer(c_int32_t), parameter :: CL_DEVICE_PARENT_DEVICE = Z'1042' - integer(c_int32_t), parameter :: CL_DEVICE_PARTITION_MAX_SUB_DEVICES = Z'1043' - integer(c_int32_t), parameter :: CL_DEVICE_PARTITION_PROPERTIES = Z'1044' - integer(c_int32_t), parameter :: CL_DEVICE_PARTITION_AFFINITY_DOMAIN = Z'1045' - integer(c_int32_t), parameter :: CL_DEVICE_PARTITION_TYPE = Z'1046' - integer(c_int32_t), parameter :: CL_DEVICE_REFERENCE_COUNT = Z'1047' - integer(c_int32_t), parameter :: CL_DEVICE_PREFERRED_INTEROP_USER_SYNC = Z'1048' - integer(c_int32_t), parameter :: CL_DEVICE_PRINTF_BUFFER_SIZE = Z'1049' - - ! cl_device_fp_config - bitfield - integer(c_int64_t), parameter :: CL_FP_DENORM = b'00000001' - integer(c_int64_t), parameter :: CL_FP_INF_NAN = b'00000010' - integer(c_int64_t), parameter :: CL_FP_ROUND_TO_NEAREST = b'00000100' - integer(c_int64_t), parameter :: CL_FP_ROUND_TO_ZERO = b'00001000' - integer(c_int64_t), parameter :: CL_FP_ROUND_TO_INF = b'00010000' - integer(c_int64_t), parameter :: CL_FP_FMA = b'00100000' - integer(c_int64_t), parameter :: CL_FP_SOFT_FLOAT = b'01000000' - integer(c_int64_t), parameter :: CL_FP_CORRECTLY_ROUNDED_DIVIDE_SQRT = b'10000000' - - ! cl_device_mem_cache_type - integer(c_int32_t), parameter :: CL_NONE = Z'0' - integer(c_int32_t), parameter :: CL_READ_ONLY_CACHE = Z'1' - integer(c_int32_t), parameter :: CL_READ_WRITE_CACHE = Z'2' - - ! cl_device_local_mem_type - integer(c_int32_t), parameter :: CL_LOCAL = Z'1' - integer(c_int32_t), parameter :: CL_GLOBAL = Z'2' - - ! cl_device_exec_capabilities - bitfield - integer(c_int64_t), parameter :: CL_EXEC_KERNEL = b'01' - integer(c_int64_t), parameter :: CL_EXEC_NATIVE_KERNEL = b'10' - - ! cl_command_queue_properties - bitfield - integer(c_int64_t), parameter :: CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE = b'01' - integer(c_int64_t), parameter :: CL_QUEUE_PROFILING_ENABLE = b'10' - - ! cl_context_info - integer(c_int32_t), parameter :: CL_CONTEXT_REFERENCE_COUNT = Z'1080' - integer(c_int32_t), parameter :: CL_CONTEXT_DEVICES = Z'1081' - integer(c_int32_t), parameter :: CL_CONTEXT_PROPERTIES = Z'1082' - integer(c_int32_t), parameter :: CL_CONTEXT_NUM_DEVICES = Z'1083' - - ! cl_context_properties type(c_ptr) - integer(c_intptr_t), parameter :: CL_CONTEXT_PLATFORM = Z'1084' - integer(c_intptr_t), parameter :: CL_CONTEXT_INTEROP_USER_SYNC = Z'1085' - - ! cl_command_queue_info - integer(c_int32_t), parameter :: CL_QUEUE_CONTEXT = Z'1090' - integer(c_int32_t), parameter :: CL_QUEUE_DEVICE = Z'1091' - integer(c_int32_t), parameter :: CL_QUEUE_REFERENCE_COUNT = Z'1092' - integer(c_int32_t), parameter :: CL_QUEUE_PROPERTIES = Z'1093' - - ! cl_mem_flags - bitfield (int64) - integer(c_int64_t), parameter :: CL_MEM_READ_WRITE = b'0000000001' - integer(c_int64_t), parameter :: CL_MEM_WRITE_ONLY = b'0000000010' - integer(c_int64_t), parameter :: CL_MEM_READ_ONLY = b'0000000100' - integer(c_int64_t), parameter :: CL_MEM_USE_HOST_PTR = b'0000001000' - integer(c_int64_t), parameter :: CL_MEM_ALLOC_HOST_PTR = b'0000010000' - integer(c_int64_t), parameter :: CL_MEM_COPY_HOST_PTR = b'0000100000' - !integer(c_int64_t), parameter :: reserved = b'0001000000' - integer(c_int64_t), parameter :: CL_MEM_HOST_WRITE_ONLY = b'0010000000' - integer(c_int64_t), parameter :: CL_MEM_HOST_READ_ONLY = b'0100000000' - integer(c_int64_t), parameter :: CL_MEM_HOST_NO_ACCESS = b'1000000000' - - ! cl_buffer_create_type - integer(c_int32_t), parameter :: CL_BUFFER_CREATE_TYPE_REGION = Z'1220' - - ! cl_channel_order - integer(c_int32_t), parameter :: CL_R = Z'10B0' - integer(c_int32_t), parameter :: CL_A = Z'10B1' - integer(c_int32_t), parameter :: CL_RG = Z'10B2' - integer(c_int32_t), parameter :: CL_RA = Z'10B3' - integer(c_int32_t), parameter :: CL_RGB = Z'10B4' - integer(c_int32_t), parameter :: CL_RGBA = Z'10B5' - integer(c_int32_t), parameter :: CL_BGRA = Z'10B6' - integer(c_int32_t), parameter :: CL_ARGB = Z'10B7' - integer(c_int32_t), parameter :: CL_INTENSITY = Z'10B8' - integer(c_int32_t), parameter :: CL_LUMINANCE = Z'10B9' - integer(c_int32_t), parameter :: CL_Rx = Z'10BA' - integer(c_int32_t), parameter :: CL_RGx = Z'10BB' - integer(c_int32_t), parameter :: CL_RGBx = Z'10BC' - integer(c_int32_t), parameter :: CL_DEPTH = Z'10BD' - integer(c_int32_t), parameter :: CL_DEPTH_STENCIL = Z'10BE' - - ! cl_channel_type - integer(c_int32_t), parameter :: CL_SNORM_INT8 = Z'10D0' - integer(c_int32_t), parameter :: CL_SNORM_INT16 = Z'10D1' - integer(c_int32_t), parameter :: CL_UNORM_INT8 = Z'10D2' - integer(c_int32_t), parameter :: CL_UNORM_INT16 = Z'10D3' - integer(c_int32_t), parameter :: CL_UNORM_SHORT_565 = Z'10D4' - integer(c_int32_t), parameter :: CL_UNORM_SHORT_555 = Z'10D5' - integer(c_int32_t), parameter :: CL_UNORM_INT_101010 = Z'10D6' - integer(c_int32_t), parameter :: CL_SIGNED_INT8 = Z'10D7' - integer(c_int32_t), parameter :: CL_SIGNED_INT16 = Z'10D8' - integer(c_int32_t), parameter :: CL_SIGNED_INT32 = Z'10D9' - integer(c_int32_t), parameter :: CL_UNSIGNED_INT8 = Z'10DA' - integer(c_int32_t), parameter :: CL_UNSIGNED_INT16 = Z'10DB' - integer(c_int32_t), parameter :: CL_UNSIGNED_INT32 = Z'10DC' - integer(c_int32_t), parameter :: CL_HALF_FLOAT = Z'10DD' - integer(c_int32_t), parameter :: CL_FLOAT = Z'10DE' - integer(c_int32_t), parameter :: CL_UNORM_INT24 = Z'10DF' - - ! cl_mem_object_type - integer(c_int32_t), parameter :: CL_MEM_OBJECT_BUFFER = Z'10F0' - integer(c_int32_t), parameter :: CL_MEM_OBJECT_IMAGE2D = Z'10F1' - integer(c_int32_t), parameter :: CL_MEM_OBJECT_IMAGE3D = Z'10F2' - integer(c_int32_t), parameter :: CL_MEM_OBJECT_IMAGE2D_ARRAY = Z'10F3' - integer(c_int32_t), parameter :: CL_MEM_OBJECT_IMAGE1D = Z'10F4' - integer(c_int32_t), parameter :: CL_MEM_OBJECT_IMAGE1D_ARRAY = Z'10F5' - integer(c_int32_t), parameter :: CL_MEM_OBJECT_IMAGE1D_BUFFER = Z'10F6' - - ! cl_mem_info - integer(c_int32_t), parameter :: CL_MEM_TYPE = Z'1100' - integer(c_int32_t), parameter :: CL_MEM_FLAGS = Z'1101' - integer(c_int32_t), parameter :: CL_MEM_SIZE = Z'1102' - integer(c_int32_t), parameter :: CL_MEM_HOST_PTR = Z'1103' - integer(c_int32_t), parameter :: CL_MEM_MAP_COUNT = Z'1104' - integer(c_int32_t), parameter :: CL_MEM_REFERENCE_COUNT = Z'1105' - integer(c_int32_t), parameter :: CL_MEM_CONTEXT = Z'1106' - integer(c_int32_t), parameter :: CL_MEM_ASSOCIATED_MEMOBJECT = Z'1107' - integer(c_int32_t), parameter :: CL_MEM_OFFSET = Z'1108' - - ! cl_image_info - Note that INFO was added to resolve naming conflicts. - integer(c_int32_t), parameter :: CL_IMAGE_INFO_FORMAT = Z'1110' - integer(c_int32_t), parameter :: CL_IMAGE_INFO_ELEMENT_SIZE = Z'1111' - integer(c_int32_t), parameter :: CL_IMAGE_INFO_ROW_PITCH = Z'1112' - integer(c_int32_t), parameter :: CL_IMAGE_INFO_SLICE_PITCH = Z'1113' - integer(c_int32_t), parameter :: CL_IMAGE_INFO_WIDTH = Z'1114' - integer(c_int32_t), parameter :: CL_IMAGE_INFO_HEIGHT = Z'1115' - integer(c_int32_t), parameter :: CL_IMAGE_INFO_DEPTH = Z'1116' - integer(c_int32_t), parameter :: CL_IMAGE_INFO_ARRAY_SIZE = Z'1117' - integer(c_int32_t), parameter :: CL_IMAGE_INFO_BUFFER = Z'1118' - integer(c_int32_t), parameter :: CL_IMAGE_INFO_NUM_MIP_LEVELS = Z'1119' - integer(c_int32_t), parameter :: CL_IMAGE_INFO_NUM_SAMPLES = Z'111A' - - ! cl_addressing_mode - integer(c_int32_t), parameter :: CL_ADDRESS_NONE = Z'1130' - integer(c_int32_t), parameter :: CL_ADDRESS_CLAMP_TO_EDGE = Z'1131' - integer(c_int32_t), parameter :: CL_ADDRESS_CLAMP = Z'1132' - integer(c_int32_t), parameter :: CL_ADDRESS_REPEAT = Z'1133' - integer(c_int32_t), parameter :: CL_ADDRESS_MIRRORED_REPEAT = Z'1134' - - ! cl_filter_mode - integer(c_int32_t), parameter :: CL_FILTER_NEAREST = Z'1140' - integer(c_int32_t), parameter :: CL_FILTER_LINEAR = Z'1141' - - ! cl_sampler_info - integer(c_int32_t), parameter :: CL_SAMPLER_REFERENCE_COUNT = Z'1150' - integer(c_int32_t), parameter :: CL_SAMPLER_CONTEXT = Z'1151' - integer(c_int32_t), parameter :: CL_SAMPLER_NORMALIZED_COORDS = Z'1152' - integer(c_int32_t), parameter :: CL_SAMPLER_ADDRESSING_MODE = Z'1153' - integer(c_int32_t), parameter :: CL_SAMPLER_FILTER_MODE = Z'1154' - - ! cl_program_info - integer(c_int32_t), parameter :: CL_PROGRAM_REFERENCE_COUNT = Z'1160' - integer(c_int32_t), parameter :: CL_PROGRAM_CONTEXT = Z'1161' - integer(c_int32_t), parameter :: CL_PROGRAM_NUM_DEVICES = Z'1162' - integer(c_int32_t), parameter :: CL_PROGRAM_DEVICES = Z'1163' - integer(c_int32_t), parameter :: CL_PROGRAM_SOURCE = Z'1164' - integer(c_int32_t), parameter :: CL_PROGRAM_BINARY_SIZES = Z'1165' - integer(c_int32_t), parameter :: CL_PROGRAM_BINARIES = Z'1166' - integer(c_int32_t), parameter :: CL_PROGRAM_NUM_KERNELS = Z'1167' - integer(c_int32_t), parameter :: CL_PROGRAM_KERNEL_NAMES = Z'1168' - - ! cl_program_build_info - integer(c_int32_t), parameter :: CL_PROGRAM_BUILD_STATUS = Z'1181' - integer(c_int32_t), parameter :: CL_PROGRAM_BUILD_OPTIONS = Z'1182' - integer(c_int32_t), parameter :: CL_PROGRAM_BUILD_LOG = Z'1183' - integer(c_int32_t), parameter :: CL_PROGRAM_BINARY_TYPE = Z'1184' - - ! cl_program_binary_type - integer(c_int32_t), parameter :: CL_PROGRAM_BINARY_TYPE_NONE = Z'0' - integer(c_int32_t), parameter :: CL_PROGRAM_BINARY_TYPE_COMPILED_OBJECT = Z'1' - integer(c_int32_t), parameter :: CL_PROGRAM_BINARY_TYPE_LIBRARY = Z'2' - integer(c_int32_t), parameter :: CL_PROGRAM_BINARY_TYPE_EXECUTABLE = Z'4' - - ! cl_build_status - integer(c_int32_t), parameter :: CL_BUILD_SUCCESS = 0 - integer(c_int32_t), parameter :: CL_BUILD_NONE = -1 - integer(c_int32_t), parameter :: CL_BUILD_ERROR = -2 - integer(c_int32_t), parameter :: CL_BUILD_IN_PROGRESS = -3 - - ! cl_kernel_info - integer(c_int32_t), parameter :: CL_KERNEL_FUNCTION_NAME = Z'1190' - integer(c_int32_t), parameter :: CL_KERNEL_NUM_ARGS = Z'1191' - integer(c_int32_t), parameter :: CL_KERNEL_REFERENCE_COUNT = Z'1192' - integer(c_int32_t), parameter :: CL_KERNEL_CONTEXT = Z'1193' - integer(c_int32_t), parameter :: CL_KERNEL_PROGRAM = Z'1194' - integer(c_int32_t), parameter :: CL_KERNEL_ATTRIBUTES = Z'1195' - - ! cl_kernel_arg_info - integer(c_int32_t), parameter :: CL_KERNEL_ARG_ADDRESS_QUALIFIER = Z'1196' - integer(c_int32_t), parameter :: CL_KERNEL_ARG_ACCESS_QUALIFIER = Z'1197' - integer(c_int32_t), parameter :: CL_KERNEL_ARG_TYPE_NAME = Z'1198' - integer(c_int32_t), parameter :: CL_KERNEL_ARG_TYPE_QUALIFIER = Z'1199' - integer(c_int32_t), parameter :: CL_KERNEL_ARG_NAME = Z'119A' - - ! cl_kernel_arg_address_qualifier - integer(c_int32_t), parameter :: CL_KERNEL_ARG_ADDRESS_GLOBAL = Z'119B' - integer(c_int32_t), parameter :: CL_KERNEL_ARG_ADDRESS_LOCAL = Z'119C' - integer(c_int32_t), parameter :: CL_KERNEL_ARG_ADDRESS_CONSTANT = Z'119D' - integer(c_int32_t), parameter :: CL_KERNEL_ARG_ADDRESS_PRIVATE = Z'119E' - - ! cl_kernel_arg_access_qualifier - integer(c_int32_t), parameter :: CL_KERNEL_ARG_ACCESS_READ_ONLY = Z'11A0' - integer(c_int32_t), parameter :: CL_KERNEL_ARG_ACCESS_WRITE_ONLY = Z'11A1' - integer(c_int32_t), parameter :: CL_KERNEL_ARG_ACCESS_READ_WRITE = Z'11A2' - integer(c_int32_t), parameter :: CL_KERNEL_ARG_ACCESS_NONE = Z'11A3' - - ! cl_kernel_arg_type_qualifer - bitfield (int64) - integer(c_int64_t), parameter :: CL_KERNEL_ARG_TYPE_NONE = b'000' - integer(c_int64_t), parameter :: CL_KERNEL_ARG_TYPE_CONST = b'001' - integer(c_int64_t), parameter :: CL_KERNEL_ARG_TYPE_RESTRICT = b'010' - integer(c_int64_t), parameter :: CL_KERNEL_ARG_TYPE_VOLATILE = b'100' - - ! cl_kernel_work_group_info - integer(c_int32_t), parameter :: CL_KERNEL_WORK_GROUP_SIZE = Z'11B0' - integer(c_int32_t), parameter :: CL_KERNEL_COMPILE_WORK_GROUP_SIZE = Z'11B1' - integer(c_int32_t), parameter :: CL_KERNEL_LOCAL_MEM_SIZE = Z'11B2' - integer(c_int32_t), parameter :: CL_KERNEL_PREFERRED_WORK_GROUP_SIZE_MULTIPLE= Z'11B3' - integer(c_int32_t), parameter :: CL_KERNEL_PRIVATE_MEM_SIZE = Z'11B4' - integer(c_int32_t), parameter :: CL_KERNEL_GLOBAL_WORK_SIZE = Z'11B5' - - ! cl_event_info - integer(c_int32_t), parameter :: CL_EVENT_COMMAND_QUEUE = Z'11D0' - integer(c_int32_t), parameter :: CL_EVENT_COMMAND_TYPE = Z'11D1' - integer(c_int32_t), parameter :: CL_EVENT_REFERENCE_COUNT = Z'11D2' - integer(c_int32_t), parameter :: CL_EVENT_COMMAND_EXECUTION_STATUS = Z'11D3' - integer(c_int32_t), parameter :: CL_EVENT_CONTEXT = Z'11D4' - - ! cl_profiling_info - integer(c_int32_t), parameter :: CL_PROFILING_COMMAND_QUEUED = Z'1280' - integer(c_int32_t), parameter :: CL_PROFILING_COMMAND_SUBMIT = Z'1281' - integer(c_int32_t), parameter :: CL_PROFILING_COMMAND_START = Z'1282' - integer(c_int32_t), parameter :: CL_PROFILING_COMMAND_END = Z'1283' - - ! ------------ - ! Types - ! ------------ - - type, BIND(C) :: cl_image_format - integer(c_int32_t) :: image_channel_order - integer(c_int32_t) :: image_channel_data_type - end type - - type, BIND(C) :: cl_image_desc - integer(c_int32_t) :: image_type - integer(c_size_t) :: image_width - integer(c_size_t) :: image_height - integer(c_size_t) :: image_depth - integer(c_size_t) :: image_array_size - integer(c_size_t) :: image_row_pitch - integer(c_size_t) :: image_slice_pitch - integer(c_int32_t) :: num_mip_levels - integer(c_int32_t) :: num_samples - integer(c_intptr_t) :: buffer - end type - - - ! - ! Start interfaces. - ! - contains - - ! ------------ - ! Platform API - ! ------------ - - ! clGetPlatformIDs - integer(c_int32_t) function clGetPlatformIDs(num_entries, & - platforms, num_platforms) & - BIND(C, NAME='clGetPlatformIDs') - USE ISO_C_BINDING - - integer(c_int32_t), value, intent(in) :: num_entries - type(c_ptr), value, intent(in) :: platforms - integer(c_int32_t), intent(out) :: num_platforms - end function - - ! clGetPlatformInfo - integer(c_int32_t) function clGetPlatformInfo(platform, param_name, & - param_value_size, param_value, param_value_size_ret) & - BIND(C, NAME='clGetPlatformInfo') - USE ISO_C_BINDING - - integer(c_intptr_t), value, intent(in) :: platform - integer(c_int32_t), value, intent(in) :: param_name - integer(c_size_t), value, intent(in) :: param_value_size - type(c_ptr), value, intent(in) :: param_value - integer(c_size_t), intent(out) :: param_value_size_ret - end function - - ! ---------- - ! Device API - ! ---------- - - ! clGetDeviceIDs - integer(c_int32_t) function clGetDeviceIDs(platform, & - device_type, & - num_entries, & - devices, & - num_devices) & - BIND(C, NAME='clGetDeviceIDs') - USE ISO_C_BINDING - - ! Define parameters. - integer(c_intptr_t), value :: platform - integer(c_int64_t), value :: device_type - integer(c_int32_t), value :: num_entries - type(c_ptr), value :: devices - integer(c_int32_t), intent(out) :: num_devices - - end function - - ! clGetDeviceInfo - integer(c_int) function clGetDeviceInfo(device, & - param_name, & - param_value_size, & - param_value, & - param_value_size_ret) & - BIND(C, NAME='clGetDeviceInfo') - USE ISO_C_BINDING - - ! Define parameters. - integer(c_intptr_t), value :: device - integer(c_int32_t), value :: param_name - integer(c_size_t), value :: param_value_size - type(c_ptr), value :: param_value - integer(c_size_t), intent(out) :: param_value_size_ret - - end function - - ! clCreateSubDevices - integer(c_int32_t) function clCreateSubDevices(in_device, & - properties, & - num_devices, & - out_devices, & - num_devices_ret) & - BIND(C, NAME='clCreateSubDevices') - USE ISO_C_BINDING - - ! Define parameters. - integer(c_intptr_t), value :: in_device - type(c_ptr), value :: properties - integer(c_int32_t), value :: num_devices - type(c_ptr), value :: out_devices - integer(c_int32_t), intent(out) :: num_devices_ret - - end function - - ! clRetainDevice - integer(c_int32_t) function clRetainDevice(device) & - BIND(C, NAME='clRetainDevice') - USE ISO_C_BINDING - - ! Define parameters. - integer(c_intptr_t), value :: device - end function - - ! clReleaseDevice - integer(c_int32_t) function clReleaseDevice(device) & - BIND(C, NAME='clReleaseDevice') - USE ISO_C_BINDING - - ! Define parameters. - integer(c_intptr_t), value :: device - end function - - ! ------------ - ! Context APIs - ! ------------ - - ! clCreateContext - integer(c_intptr_t) function clCreateContext(properties, & - num_devices, & - devices, & - pfn_notify, & - user_data, & - errcode_ret) & - BIND(C, NAME='clCreateContext') - USE ISO_C_BINDING - - ! Define parameters. - type(c_ptr), value :: properties - integer(c_int32_t), value :: num_devices - type(c_ptr), value :: devices - type(c_funptr), value :: pfn_notify - type(c_ptr), value :: user_data - integer(c_int32_t), intent(out) :: errcode_ret - - end function - - ! clCreateContextFromType - integer(c_intptr_t) function clCreateContextFromType(properties, & - device_type, & - pfn_notify, & - user_data, & - errcode_ret) & - BIND(C, NAME='clCreateContextFromType') - USE ISO_C_BINDING - - ! Define parameters. - type(c_ptr), value :: properties - integer(c_int64_t), value :: device_type - type(c_funptr), value :: pfn_notify - type(c_ptr), value :: user_data - integer(c_int32_t), intent(out) :: errcode_ret - - end function - - ! clRetainContext - integer(c_int32_t) function clRetainContext(context) & - BIND(C, NAME='clRetainContext') - USE ISO_C_BINDING - - ! Define parameters. - integer(c_intptr_t), value :: context - - end function - - ! clReleaseContext - integer(c_int32_t) function clReleaseContext(context) & - BIND(C, NAME='clReleaseContext') - USE ISO_C_BINDING - - ! Define parameters. - integer(c_intptr_t), value :: context - - end function - - ! clGetContextInfo - integer(c_int32_t) function clGetContextInfo(context, & - param_name, & - param_value_size, & - param_value, & - param_value_size_ret) & - BIND(C, NAME='clGetContextInfo') - USE ISO_C_BINDING - - ! Define parameters. - integer(c_intptr_t), value :: context - integer(c_int32_t), value :: param_name - integer(c_size_t), value :: param_value_size - type(c_ptr), value :: param_value - integer(c_size_t), intent(out) :: param_value_size_ret - - end function - - ! ------------------ - ! Command Queue APIs - ! ------------------ - - ! clCreateCommandQueue - integer(c_intptr_t) function clCreateCommandQueue(context, & - device, & - properties, & - errcode_ret) & - BIND(C, NAME='clCreateCommandQueue') - USE ISO_C_BINDING - - ! Define parameters. - integer(c_intptr_t), value :: context - integer(c_intptr_t), value :: device - integer(c_int64_t), value :: properties - integer(c_int32_t), intent(out) :: errcode_ret - - end function - - ! clRetainCommandQueue - integer(c_int32_t) function clRetainCommandQueue(command_queue) & - BIND(C, NAME='clRetainCommandQueue') - USE ISO_C_BINDING - - ! Define parameters. - integer(c_intptr_t), value :: command_queue - - end function - - ! clReleaseCommandQueue - integer(c_int32_t) function clReleaseCommandQueue(command_queue) & - BIND(C, NAME='clReleaseCommandQueue') - USE ISO_C_BINDING - - ! Define parameters. - integer(c_intptr_t), value :: command_queue - - end function - - ! clGetCommandQueueInfo - integer(c_int32_t) function clGetCommandQueueInfo(command_queue, & - param_name, & - param_value_size, & - param_value, & - param_value_size_ret) & - BIND(C, NAME='clGetCommandQueueInfo') - USE ISO_C_BINDING - - ! Define parameters. - integer(c_intptr_t), value :: command_queue - integer(c_int32_t), value :: param_name - integer(c_size_t), value :: param_value_size - type(c_ptr), value :: param_value - integer(c_size_t), intent(out) :: param_value_size_ret - - end function - - ! ------------------ - ! Memory Object APIs - ! ------------------ - - ! clCreateBuffer - integer(c_intptr_t) function clCreateBuffer(context, & - flags, & - sizeb, & - host_ptr, & - errcode_ret) & - BIND(C, NAME='clCreateBuffer') - USE ISO_C_BINDING - - ! Define parameters. - integer(c_intptr_t), value :: context - integer(c_int64_t), value :: flags - integer(c_size_t), value :: sizeb - type(c_ptr), value :: host_ptr - integer(c_int32_t), intent(out) :: errcode_ret - - end function - - ! clCreateSubBuffer - integer(c_intptr_t) function clCreateSubBuffer(buffer, & - flags, & - buffer_create_type, & - buffer_create_info, & - errcode_ret) & - BIND(C, NAME='clCreateSubBuffer') - USE ISO_C_BINDING - - ! Define parameters. - integer(c_intptr_t), value :: buffer - integer(c_int64_t), value :: flags - integer(c_int32_t), value :: buffer_create_type - type(c_ptr), value :: buffer_create_info - integer(c_int32_t), intent(out) :: errcode_ret - - end function - - ! clCreateImage - integer(c_intptr_t) function clCreateImage(context, & - flags, & - image_format, & - image_desc, & - host_ptr, & - errcode_ret) & - BIND(C, NAME='clCreateImage') - USE ISO_C_BINDING - - ! Define parameters. - integer(c_intptr_t), value :: context - integer(c_int64_t), value :: flags - type(cl_image_format) :: image_format - type(cl_image_desc) :: image_desc - type(c_ptr), value :: host_ptr - integer(c_int32_t), intent(out) :: errcode_ret - - end function - - ! clRetainMemObject - integer(c_int32_t) function clRetainMemObject(mem_obj) & - BIND(C, NAME='clRetainMemObject') - - ! Define parameters. - integer(c_intptr_t), value :: mem_obj - - end function - - ! clReleaseMemObject - integer(c_int32_t) function clReleaseMemObject(mem_obj) & - BIND(C, NAME='clReleaseMemObject') - - ! Define parameters. - integer(c_intptr_t), value :: mem_obj - - end function - - ! clGetSupportedImageFormats - integer(c_int32_t) function clGetSupportedImageFormats(context, & - flags, & - image_type, & - num_entries, & - image_formats, & - num_image_formats) & - BIND(C, NAME='clGetSupportedImageFormats') - - ! Define parameters. - integer(c_intptr_t), value :: context - integer(c_int64_t), value :: flags - integer(c_int32_t), value :: image_type - integer(c_int32_t), value :: num_entries - type(c_ptr), value :: image_formats - integer(c_int32_t), intent(out) :: num_image_formats - - end function - - ! clGetMemObjectInfo - integer(c_int32_t) function clGetMemObjectInfo(memobj, & - param_name, & - param_value_size, & - param_value, & - param_value_size_ret) & - BIND(C, NAME='clGetMemObjectInfo') - - ! Define parameters. - integer(c_intptr_t), value :: memobj - integer(c_int32_t), value :: param_name - integer(c_size_t), value :: param_value_size - type(c_ptr), value :: param_value - integer(c_size_t), intent(out) :: param_value_size_ret - - end function - - ! clGetImageInfo - integer(c_int32_t) function clGetImageInfo(image, & - param_name, & - param_value_size, & - param_value, & - param_value_size_ret) & - BIND(C, NAME='clGetImageInfo') - - ! Define parameters. - integer(c_intptr_t), value :: image - integer(c_int32_t), value :: param_name - integer(c_size_t), value :: param_value_size - type(c_ptr), value :: param_value - integer(c_size_t), intent(out) :: param_value_size_ret - - end function - - ! clSetMemObjectDestructorCallback - integer(c_int32_t) function clSetMemObjectDestructorCallback(memobj, & - pfn_notify, & - user_data) & - BIND(C, NAME='clSetMemObjectDestructorCallback') - - ! Define parameters. - integer(c_intptr_t), value :: memobj - type(c_funptr), value :: pfn_notify - type(c_ptr), value :: user_data - - end function - - ! ------------ - ! Sampler APIs - ! ------------ - - ! clCreateSampler - integer(c_intptr_t) function clCreateSampler(context, & - normalized_coords, & - addressing_mode, & - filter_mode, & - errcode_ret) & - BIND(C, NAME='clCreateSampler') - - ! Define parameters. - integer(c_intptr_t), value :: context - integer(c_int32_t), value :: normalized_coords - integer(c_int32_t), value :: addressing_mode - integer(c_int32_t), value :: filter_mode - integer(c_int32_t), intent(out) :: errcode_ret - - end function - - ! clRetainSampler - integer(c_int32_t) function clRetainSampler(sampler) & - BIND(C, NAME='clRetainSampler') - - ! Define parameters. - integer(c_intptr_t), value :: sampler - - end function - - ! clReleaseSampler - integer(c_int32_t) function clReleaseSampler(sampler) & - BIND(C, NAME='clReleaseSampler') - - ! Define parameters. - integer(c_intptr_t), value :: sampler - - end function - - ! clGetSamplerInfo - integer(c_int32_t) function clGetSamplerInfo(sampler, & - param_name, & - param_value_size, & - param_value, & - param_value_size_ret) & - BIND(C, NAME='clGetSamplerInfo') - - ! Define parameters. - integer(c_intptr_t), value :: sampler - integer(c_int32_t), value :: param_name - integer(c_size_t), value :: param_value_size - type(c_ptr), value :: param_value - integer(c_size_t), intent(out) :: param_value_size_ret - - end function - - ! ------------------- - ! Program Object APIs - ! ------------------- - - ! clCreateProgramWithSource - integer(c_intptr_t) function clCreateProgramWithSource(context, & - count, & - strings, & - lengths, & - errcode_ret) & - BIND(C, NAME='clCreateProgramWithSource') - - ! Define parameters. - integer(c_intptr_t), value :: context - integer(c_int32_t), value :: count - type(c_ptr), value :: strings - type(c_ptr), value :: lengths - integer(c_int32_t), intent(out) :: errcode_ret - - end function - - ! clCreateProgramWithBinary - integer(c_intptr_t) function clCreateProgramWithBinary(context, & - num_devices, & - device_list, & - lengths, & - binaries, & - binary_status, & - errcode_ret) & - BIND(C, NAME='clCreateProgramWithBinary') - - ! Define parameters. - integer(c_intptr_t), value :: context - integer(c_int32_t), value :: num_devices - type(c_ptr), value :: device_list - type(c_ptr), value :: lengths - type(c_ptr), value :: binaries - type(c_ptr), value :: binary_status - integer(c_int32_t), intent(out) :: errcode_ret - - end function - - ! clCreateProgramWithBuiltInKernels - integer(c_intptr_t) function clCreateProgramWithBuiltInKernels(context, & - num_devices, & - device_list, & - kernel_names, & - errcode_ret) & - BIND(C, NAME='clCreateProgramWithBuiltInKernels') - - ! Define parameters. - integer(c_intptr_t), value :: context - integer(c_int32_t), value :: num_devices - type(c_ptr), value :: device_list - type(c_ptr), value :: kernel_names - integer(c_int32_t), intent(out) :: errcode_ret - - end function - - ! clRetainProgram - integer(c_int32_t) function clRetainProgram(program) & - BIND(C, NAME='clRetainProgram') - - ! Define parameters. - integer(c_intptr_t), value :: program - - end function - - ! clReleaseProgram - integer(c_int32_t) function clReleaseProgram(program) & - BIND(C, NAME='clReleaseProgram') - - ! Define parameters. - integer(c_intptr_t), value :: program - - end function - - ! clBuildProgram - integer(c_int32_t) function clBuildProgram(program, & - num_devices, & - device_list, & - options, & - pfn_notify, & - user_data) & - BIND(C, NAME='clBuildProgram') - - ! Define parameters. - integer(c_intptr_t), value :: program - integer(c_int32_t), value :: num_devices - type(c_ptr), value :: device_list - type(c_ptr), value :: options - type(c_funptr), value :: pfn_notify - type(c_ptr), value :: user_data - - end function - - ! clCompileProgram - integer(c_int32_t) function clCompileProgram(program, & - num_devices, & - device_list, & - options, & - num_input_headers, & - input_headers, & - header_include_names, & - pfn_notify, & - user_data) & - BIND(C, NAME='clCompileProgram') - - ! Define parameters. - integer(c_intptr_t), value :: program - integer(c_int32_t), value :: num_devices - type(c_ptr), value :: device_list - type(c_ptr), value :: options - integer(c_int32_t), value :: num_input_headers - type(c_ptr), value :: input_headers - type(c_ptr), value :: header_include_names - type(c_funptr), value :: pfn_notify - type(c_ptr), value :: user_data - - end function - - ! clLinkProgram - integer(c_intptr_t) function clLinkProgram(context, & - num_devices, & - device_list, & - options, & - num_input_programs, & - input_programs, & - pfn_notify, & - user_data, & - errcode_ret) & - BIND(C, NAME='clLinkProgram') - - ! Define parameters. - integer(c_intptr_t), value :: context - integer(c_int32_t), value :: num_devices - type(c_ptr), value :: device_list - type(c_ptr), value :: options - integer(c_int32_t), value :: num_input_programs - type(c_ptr), value :: input_programs - type(c_funptr), value :: pfn_notify - type(c_ptr), value :: user_data - integer(c_int32_t), intent(out) :: errcode_ret - - end function - - ! clUnloadPlatformCompiler - integer(c_int32_t) function clUnloadPlatformCompiler(platform) & - BIND(C, NAME='clUnloadPlatformCompiler') - - ! Define parameters. - integer(c_intptr_t), value :: platform - - end function - - ! clGetProgramInfo - integer(c_int32_t) function clGetProgramInfo(program, & - param_name, & - param_value_size, & - param_value, & - param_value_size_ret) & - BIND(C, NAME='clGetProgramInfo') - - ! Define parameters. - integer(c_intptr_t), value :: program - integer(c_int32_t), value :: param_name - integer(c_size_t), value :: param_value_size - type(c_ptr), value :: param_value - integer(c_size_t), intent(out) :: param_value_size_ret - - end function - - ! clGetProgramBuildInfo - integer(c_int32_t) function clGetProgramBuildInfo(program, & - device, & - param_name, & - param_value_size, & - param_value, & - param_value_size_ret) & - BIND(C, NAME='clGetProgramBuildInfo') - - ! Define parameters. - integer(c_intptr_t), value :: program - integer(c_intptr_t), value :: device - integer(c_int32_t), value :: param_name - integer(c_size_t), value :: param_value_size - type(c_ptr), value :: param_value - integer(c_size_t), intent(out) :: param_value_size_ret - - end function - - ! ------------------ - ! Kernel Object APIs - ! ------------------ - - ! clCreateKernel - integer(c_intptr_t) function clCreateKernel(program, & - kernel_name, & - errcode_ret) & - BIND(C, NAME='clCreateKernel') - - ! Define parameters. - integer(c_intptr_t), value :: program - type(c_ptr), value :: kernel_name - integer(c_int32_t), intent(out) :: errcode_ret - - end function - - ! clCreateKernelsInProgram - integer(c_int32_t) function clCreateKernelsInProgram(program, & - num_kernels, & - kernels, & - num_kernels_ret) & - BIND(C, NAME='clCreateKernelsInProgram') - - ! Define parameters. - integer(c_intptr_t), value :: program - integer(c_int32_t), value :: num_kernels - type(c_ptr), value :: kernels - integer(c_int32_t), intent(out) :: num_kernels_ret - - end function - - ! clRetainKernel - integer(c_int32_t) function clRetainKernel(kernel) & - BIND(C, NAME='clRetainKernel') - - ! Define parameters. - integer(c_intptr_t), value :: kernel - - end function - - ! clReleaseKernel - integer(c_int32_t) function clReleaseKernel(kernel) & - BIND(C, NAME='clReleaseKernel') - - ! Define parameters. - integer(c_intptr_t), value :: kernel - - end function - - ! clSetKernelArg - integer(c_int32_t) function clSetKernelArg(kernel, & - arg_index, & - arg_size, & - arg_value) & - BIND(C, NAME='clSetKernelArg') - - ! Define parameters. - integer(c_intptr_t), value :: kernel - integer(c_int32_t), value :: arg_index - integer(c_size_t), value :: arg_size - type(c_ptr), value :: arg_value - - end function - - ! clGetKernelInfo - integer(c_int32_t) function clGetKernelInfo(kernel, & - param_name, & - param_value_size, & - param_value, & - param_value_size_ret) & - BIND(C, NAME='clGetKernelInfo') - - ! Define parameters. - integer(c_intptr_t), value :: kernel - integer(c_int32_t), value :: param_name - integer(c_size_t), value :: param_value_size - type(c_ptr), value :: param_value - integer(c_size_t), intent(out) :: param_value_size_ret - - end function - - ! clGetKernelArgInfo - integer(c_int32_t) function clGetKernelArgInfo(kernel, & - arg_index, & - param_name, & - param_value_size, & - param_value, & - param_value_size_ret) & - BIND(C, NAME='clGetKernelArgInfo') - - ! Define parameters. - integer(c_intptr_t), value :: kernel - integer(c_int32_t), value :: arg_index - integer(c_int32_t), value :: param_name - integer(c_size_t), value :: param_value_size - type(c_ptr), value :: param_value - integer(c_size_t), intent(out) :: param_value_size_ret - - end function - - ! clGetKernelWorkGroupInfo - integer(c_int32_t) function clGetKernelWorkGroupInfo(kernel, & - device, & - param_name, & - param_value_size, & - param_value, & - param_value_size_ret) & - BIND(C, NAME='clGetKernelWorkGroupInfo') - - ! Define parameters. - integer(c_intptr_t), value :: kernel - integer(c_intptr_t), value :: device - integer(c_int32_t), value :: param_name - integer(c_size_t), value :: param_value_size - type(c_ptr), value :: param_value - integer(c_size_t), intent(out) :: param_value_size_ret - - end function - - ! ----------------- - ! Event Object APIs - ! ----------------- - - ! clWaitForEvents - integer(c_int32_t) function clWaitForEvents(num_events, & - event_list) & - BIND(C, NAME='clWaitForEvents') - - ! Define parameters. - integer(c_int32_t), value :: num_events - type(c_ptr), value :: event_list - - end function - - ! clGetEventInfo - integer(c_int32_t) function clGetEventInfo(event, & - param_name, & - param_value_size, & - param_value, & - param_value_size_ret) & - BIND(C, NAME='clGetEventInfo') - - ! Define parameters. - integer(c_intptr_t), value :: event - integer(c_int32_t), value :: param_name - integer(c_size_t), value :: param_value_size - type(c_ptr), value :: param_value - integer(c_size_t), intent(out) :: param_value_size_ret - - end function - - ! clCreateUserEvent - integer(c_intptr_t) function clCreateUserEvent(context, & - errcode_ret) & - BIND(C, NAME='clCreateUserEvent') - - ! Define parameters. - integer(c_int32_t), value :: context - integer(c_int32_t), intent(out) :: errcode_ret - - end function - - ! clRetainEvent - integer(c_int32_t) function clRetainEvent(event) & - BIND(C, NAME='clRetainEvent') - - ! Define parameters. - integer(c_intptr_t), value :: event - - end function - - ! clReleaseEvent - integer(c_int32_t) function clReleaseEvent(event) & - BIND(C, NAME='clReleaseEvent') - - ! Define parameters. - integer(c_intptr_t), value :: event - - end function - - ! clSetUserEventStatus - integer(c_int32_t) function clSetUserEventStatus(event, & - execution_status) & - BIND(C, NAME='clSetUserEventStatus') - - ! Define parameters. - integer(c_intptr_t), value :: event - integer(c_int32_t), value :: execution_status - - end function - - ! clSetEventCallback - integer(c_int32_t) function clSetEventCallback(event, & - command_exec_callback_type, & - pfn_notify, & - user_data) & - BIND(C, NAME='clSetEventCallback') - - ! Define parameters. - integer(c_intptr_t), value :: event - integer(c_int32_t), value :: command_exec_callback_type - type(c_funptr), value :: pfn_notify - type(c_ptr), value :: user_data - - end function - - ! -------------- - ! Profiling APIs - ! -------------- - - ! clGetEventProfilingInfo - integer(c_int32_t) function clGetEventProfilingInfo(event, & - param_name, & - param_value_size, & - param_value, & - param_value_size_ret) & - BIND(C, NAME='clGetEventProfilingInfo') - - ! Define parameters. - integer(c_intptr_t), value :: event - integer(c_int32_t), value :: param_name - integer(c_size_t), value :: param_value_size - type(c_ptr), value :: param_value - integer(c_size_t), intent(out) :: param_value_size_ret - - end function - - ! --------------------- - ! Flush and Finish APIs - ! --------------------- - - ! clFlush - integer(c_int32_t) function clFlush(command_queue) & - BIND(C, NAME='clFlush') - - ! Define parameters. - integer(c_intptr_t), value :: command_queue - - end function - - ! clFinish - integer(c_int32_t) function clFinish(command_queue) & - BIND(C, NAME='clFinish') - - ! Define parameters. - integer(c_intptr_t), value :: command_queue - - end function - - ! ---------------------- - ! Enqueued Commands APIs - ! ---------------------- - - ! clEnqueueReadBuffer - integer(c_int32_t) function clEnqueueReadBuffer(command_queue, & - buffer, & - blocking_read, & - offset, & - size, & - ptr, & - num_events_in_wait_list, & - event_wait_list, & - event) & - BIND(C, NAME='clEnqueueReadBuffer') - - ! Define parameters. - integer(c_intptr_t), value :: command_queue - integer(c_intptr_t), value :: buffer - integer(c_int32_t), value :: blocking_read - integer(c_size_t), value :: offset - integer(c_size_t), value :: size - type(c_ptr), value :: ptr - integer(c_int32_t), value :: num_events_in_wait_list - type(c_ptr), value :: event_wait_list - type(c_ptr), value :: event - - end function - - ! clEnqueueReadBufferRect - integer(c_int32_t) function clEnqueueReadBufferRect(command_queue, & - buffer, & - blocking_read, & - buffer_offset, & - host_offset, & - region, & - buffer_row_pitch, & - buffer_slice_pitch, & - host_row_pitch, & - host_slice_pitch, & - ptr, & - num_events_in_wait_list, & - event_wait_list, & - event) & - BIND(C, NAME='clEnqueueReadBufferRect') - - ! Define parameters. - integer(c_intptr_t), value :: command_queue - - integer(c_intptr_t), value :: buffer - integer(c_int32_t), value :: blocking_read - type(c_ptr), value :: buffer_offset - type(c_ptr), value :: host_offset - type(c_ptr), value :: region - integer(c_size_t), value :: buffer_row_pitch - integer(c_size_t), value :: buffer_slice_pitch - integer(c_size_t), value :: host_row_pitch - integer(c_size_t), value :: host_slice_pitch - type(c_ptr), value :: ptr - integer(c_int32_t), value :: num_events_in_wait_list - type(c_ptr), value :: event_wait_list - type(c_ptr), value :: event - - end function - - ! clEnqueueWriteBuffer - integer(c_int32_t) function clEnqueueWriteBuffer(command_queue, & - buffer, & - blocking_write, & - offset, & - size, & - ptr, & - num_events_in_wait_list, & - event_wait_list, & - event) & - BIND(C, NAME='clEnqueueWriteBuffer') - - ! Define parameters. - integer(c_intptr_t), value :: command_queue - integer(c_intptr_t), value :: buffer - integer(c_int32_t), value :: blocking_write - integer(c_size_t), value :: offset - integer(c_size_t), value :: size - type(c_ptr), value :: ptr - integer(c_int32_t), value :: num_events_in_wait_list - type(c_ptr), value :: event_wait_list - type(c_ptr), value :: event - - end function - - ! clEnqueueWriteBufferRect - integer(c_int32_t) function clEnqueueWriteBufferRect(command_queue, & - buffer, & - blocking_write, & - buffer_offset, & - host_offset, & - region, & - buffer_row_pitch, & - buffer_slice_pitch, & - host_row_pitch, & - host_slice_pitch, & - ptr, & - num_events_in_wait_list, & - event_wait_list, & - event) & - BIND(C, NAME='clEnqueueWriteBufferRect') - - ! Define parameters. - integer(c_intptr_t), value :: command_queue - integer(c_intptr_t), value :: buffer - integer(c_int32_t), value :: blocking_write - type(c_ptr), value :: buffer_offset - type(c_ptr), value :: host_offset - type(c_ptr), value :: region - integer(c_size_t), value :: buffer_row_pitch - integer(c_size_t), value :: buffer_slice_pitch - integer(c_size_t), value :: host_row_pitch - integer(c_size_t), value :: host_slice_pitch - type(c_ptr), value :: ptr - integer(c_int32_t), value :: num_events_in_wait_list - type(c_ptr), value :: event_wait_list - type(c_ptr), value :: event - - end function - - ! clEnqueueFillBuffer - integer(c_int32_t) function clEnqueueFillBuffer(command_queue, & - buffer, & - pattern, & - pattern_size, & - offset, & - size, & - num_events_in_wait_list, & - event_wait_list, & - event) & - BIND(C, NAME='clEnqueueFillBuffer') - - ! Define parameters. - integer(c_intptr_t), value :: command_queue - integer(c_intptr_t), value :: buffer - type(c_ptr), value :: pattern - integer(c_size_t), value :: pattern_size - integer(c_size_t), value :: offset - integer(c_size_t), value :: size - integer(c_int32_t), value :: num_events_in_wait_list - type(c_ptr), value :: event_wait_list - type(c_ptr), value :: event - - end function - - ! clEnqueueCopyBuffer - integer(c_int32_t) function clEnqueueCopyBuffer(command_queue, & - src_buffer, & - dst_buffer, & - src_offset, & - dst_offset, & - size, & - num_events_in_wait_list, & - event_wait_list, & - event) & - BIND(C, NAME='clEnqueueCopyBuffer') - - ! Define parameters. - integer(c_intptr_t), value :: command_queue - integer(c_intptr_t), value :: src_buffer - integer(c_intptr_t), value :: dst_buffer - integer(c_size_t), value :: src_offset - integer(c_size_t), value :: dst_offset - integer(c_size_t), value :: size - integer(c_int32_t), value :: num_events_in_wait_list - type(c_ptr), value :: event_wait_list - type(c_ptr), value :: event - - end function - - ! clEnqueueCopyBufferRect - integer(c_int32_t) function clEnqueueCopyBufferRect(command_queue, & - src_buffer, & - dst_buffer, & - src_origin, & - dst_origin, & - region, & - src_row_pitch, & - src_slice_pitch, & - dst_row_pitch, & - dst_slice_pitch, & - num_events_in_wait_list, & - event_wait_list, & - event) & - BIND(C, NAME='clEnqueueCopyBufferRect') - - ! Define parameters. - integer(c_intptr_t), value :: command_queue - integer(c_intptr_t), value :: src_buffer - integer(c_intptr_t), value :: dst_buffer - type(c_ptr), value :: src_origin - type(c_ptr), value :: dst_origin - type(c_ptr), value :: region - integer(c_size_t), value :: src_row_pitch - integer(c_size_t), value :: src_slice_pitch - integer(c_size_t), value :: dst_row_pitch - integer(c_size_t), value :: dst_slice_pitch - integer(c_int32_t), value :: num_events_in_wait_list - type(c_ptr), value :: event_wait_list - type(c_ptr), value :: event - - end function - - ! clEnqueueReadImage - integer(c_int32_t) function clEnqueueReadImage(command_queue, & - image, & - blocking_read, & - origin, & - region, & - row_pitch, & - slice_pitch, & - ptr, & - num_events_in_wait_list, & - event_wait_list, & - event) & - BIND(C, NAME='clEnqueueReadImage') - - ! Define parameters. - integer(c_intptr_t), value :: command_queue - integer(c_intptr_t), value :: image - integer(c_int32_t), value :: blocking_read - type(c_ptr), value :: origin - type(c_ptr), value :: region - integer(c_size_t), value :: row_pitch - integer(c_size_t), value :: slice_pitch - type(c_ptr), value :: ptr - integer(c_int32_t), value :: num_events_in_wait_list - type(c_ptr), value :: event_wait_list - type(c_ptr), value :: event - - end function - - ! clEnqueueWriteImage - integer(c_int32_t) function clEnqueueWriteImage(command_queue, & - image, & - blocking_write, & - origin, & - region, & - input_row_pitch, & - input_slice_pitch, & - ptr, & - num_events_in_wait_list, & - event_wait_list, & - event) & - BIND(C, NAME='clEnqueueWriteImage') - - ! Define parameters. - integer(c_intptr_t), value :: command_queue - integer(c_intptr_t), value :: image - integer(c_int32_t), value :: blocking_write - type(c_ptr), value :: origin - type(c_ptr), value :: region - integer(c_size_t), value :: input_row_pitch - integer(c_size_t), value :: input_slice_pitch - type(c_ptr), value :: ptr - integer(c_int32_t), value :: num_events_in_wait_list - type(c_ptr), value :: event_wait_list - type(c_ptr), value :: event - - end function - - ! clEnqueueFillImage - integer(c_int32_t) function clEnqueueFillImage(command_queue, & - image, & - fill_color, & - origin, & - region, & - num_events_in_wait_list, & - event_wait_list, & - event) & - BIND(C, NAME='clEnqueueFillImage') - - ! Define parameters. - integer(c_intptr_t), value :: command_queue - integer(c_intptr_t), value :: image - type(c_ptr), value :: fill_color - type(c_ptr), value :: origin - type(c_ptr), value :: region - integer(c_int32_t), value :: num_events_in_wait_list - type(c_ptr), value :: event_wait_list - type(c_ptr), value :: event - - end function - - ! clEnqueueCopyImage - integer(c_int32_t) function clEnqueueCopyImage(command_queue, & - src_image, & - dst_image, & - src_origin, & - dst_origin, & - region, & - num_events_in_wait_list, & - event_wait_list, & - event) & - BIND(C, NAME='clEnqueueCopyImage') - - ! Define parameters. - integer(c_intptr_t), value :: command_queue - integer(c_intptr_t), value :: src_image - integer(c_intptr_t), value :: dst_image - type(c_ptr), value :: src_origin - type(c_ptr), value :: dst_origin - type(c_ptr), value :: region - integer(c_int32_t), value :: num_events_in_wait_list - type(c_ptr), value :: event_wait_list - type(c_ptr), value :: event - - end function - - ! clEnqueueCopyImageToBuffer - integer(c_int32_t) function clEnqueueCopyImageToBuffer(command_queue, & - src_image, & - dst_buffer, & - src_origin, & - region, & - dst_offset, & - num_events_in_wait_list, & - event_wait_list, & - event) & - BIND(C, NAME='clEnqueueCopyImageToBuffer') - - ! Define parameters. - integer(c_intptr_t), value :: command_queue - integer(c_intptr_t), value :: src_image - integer(c_intptr_t), value :: dst_buffer - type(c_ptr), value :: src_origin - type(c_ptr), value :: region - integer(c_size_t), value :: dst_offset - integer(c_int32_t), value :: num_events_in_wait_list - type(c_ptr), value :: event_wait_list - type(c_ptr), value :: event - - end function - - ! clEnqueueCopyBufferToImage - integer(c_int32_t) function clEnqueueCopyBufferToImage(command_queue, & - src_buffer, & - dst_image, & - src_offset, & - dst_origin, & - region, & - num_events_in_wait_list, & - event_wait_list, & - event) & - BIND(C, NAME='clEnqueueCopyBufferToImage') - - ! Define parameters. - integer(c_intptr_t), value :: command_queue - integer(c_intptr_t), value :: src_buffer - integer(c_intptr_t), value :: dst_image - integer(c_size_t), value :: src_offset - type(c_ptr), value :: dst_origin - type(c_ptr), value :: region - integer(c_int32_t), value :: num_events_in_wait_list - type(c_ptr), value :: event_wait_list - type(c_ptr), value :: event - - end function - - ! clEnqueueMapBuffer - type(c_ptr) function clEnqueueMapBuffer(command_queue, & - buffer, & - blocking_map, & - map_flags, & - offset, & - size, & - num_events_in_wait_list, & - event_wait_list, & - event, & - errcode_ret) & - BIND(C, NAME='clEnqueueMapBuffer') - - ! Define parameters. - integer(c_intptr_t), value :: command_queue - integer(c_intptr_t), value :: buffer - integer(c_int32_t), value :: blocking_map - integer(c_int64_t), value :: map_flags - integer(c_size_t), value :: offset - integer(c_size_t), value :: size - integer(c_int32_t), value :: num_events_in_wait_list - type(c_ptr), value :: event_wait_list - type(c_ptr), value :: event - integer(c_int32_t), intent(out) :: errcode_ret - - end function - - ! clEnqueueMapImage - type(c_ptr) function clEnqueueMapImage(command_queue, & - image, & - blocking_map, & - map_flags, & - origin, & - region, & - image_row_pitch, & - image_slice_pitch, & - num_events_in_wait_list, & - event_wait_list, & - event, & - errcode_ret) & - BIND(C, NAME='clEnqueueMapImage') - - ! Define parameters. - integer(c_intptr_t), value :: command_queue - integer(c_intptr_t), value :: image - integer(c_int32_t), value :: blocking_map - integer(c_int64_t), value :: map_flags - type(c_ptr), value :: origin - type(c_ptr), value :: region - integer(c_size_t), intent(out) :: image_row_pitch - integer(c_size_t), intent(out) :: image_slice_pitch - integer(c_int32_t), value :: num_events_in_wait_list - type(c_ptr), value :: event_wait_list - type(c_ptr), value :: event - integer(c_int32_t), intent(out) :: errcode_ret - - end function - - ! clEnqueueUnmapMemObject - integer(c_int32_t) function clEnqueueUnmapMemObject(command_queue, & - memobj, & - mapped_ptr, & - num_events_in_wait_list, & - event_wait_list, & - event) & - BIND(C, NAME='clEnqueueUnmapMemObject') - - ! Define parameters. - integer(c_intptr_t), value :: command_queue - integer(c_intptr_t), value :: memobj - type(c_ptr), value :: mapped_ptr - integer(c_int32_t), value :: num_events_in_wait_list - type(c_ptr), value :: event_wait_list - type(c_ptr), value :: event - - end function - - ! clEnqueueMigrateMemObjects - integer(c_int32_t) function clEnqueueMigrateMemObjects(command_queue, & - num_mem_objects, & - mem_objects, & - flags, & - num_events_in_wait_list, & - event_wait_list, & - event) & - BIND(C, NAME='clEnqueueMigrateMemObjects') - - ! Define parameters. - integer(c_intptr_t), value :: command_queue - integer(c_int32_t), value :: num_mem_objects - type(c_ptr), value :: mem_objects - integer(c_int64_t), value :: flags - integer(c_int32_t), value :: num_events_in_wait_list - type(c_ptr), value :: event_wait_list - type(c_ptr), value :: event - - end function - - ! clEnqueueNDRangeKernel. - integer(c_int32_t) function clEnqueueNDRangeKernel(command_queue, & - kernel, & - work_dim, & - global_work_offset, & - global_work_size, & - local_work_size, & - num_events_in_wait_list, & - event_wait_list, & - event) & - BIND(C, NAME='clEnqueueNDRangeKernel') - - ! Define parameters. - integer(c_intptr_t), value :: command_queue - integer(c_intptr_t), value :: kernel - integer(c_int32_t), value :: work_dim - type(c_ptr), value :: global_work_offset - type(c_ptr), value :: global_work_size - type(c_ptr), value :: local_work_size - integer(c_int32_t), value :: num_events_in_wait_list - type(c_ptr), value :: event_wait_list - type(c_ptr), value :: event - - end function - - ! clEnqueueTask - integer(c_int32_t) function clEnqueueTask(command_queue, & - kernel, & - num_events_in_wait_list, & - event_wait_list, & - event) & - BIND(C, NAME='clEnqueueTask') - - ! Define parameters. - integer(c_intptr_t), value :: command_queue - integer(c_intptr_t), value :: kernel - integer(c_int32_t), value :: num_events_in_wait_list - type(c_ptr), value :: event_wait_list - type(c_ptr), value :: event - - end function - - ! clEnqueueNativeKernel - integer(c_int32_t) function clEnqueueNativeKernel(command_queue, & - user_func, & - args, & - cb_args, & - num_mem_objects, & - mem_list, & - args_mem_loc, & - num_events_in_wait_list, & - event_wait_list, & - event) & - BIND(C, NAME='clEnqueueNativeKernel') - - ! Define parameters. - integer(c_intptr_t), value :: command_queue - type(c_funptr), value :: user_func - type(c_ptr), value :: args - integer(c_size_t), value :: cb_args - integer(c_int32_t), value :: num_mem_objects - type(c_ptr), value :: mem_list - type(c_ptr), value :: args_mem_loc - integer(c_int32_t), value :: num_events_in_wait_list - type(c_ptr), value :: event_wait_list - type(c_ptr), value :: event - - end function - - ! clEnqueueMarkerWithWaitList - integer(c_int32_t) function clEnqueueMarkerWithWaitList(command_queue, & - num_events_in_wait_list, & - event_wait_list, & - event) & - BIND(C, NAME='clEnqueueMarkerWithWaitList') - - ! Define parameters. - integer(c_intptr_t), value :: command_queue - integer(c_int32_t), value :: num_events_in_wait_list - type(c_ptr), value :: event_wait_list - type(c_ptr), value :: event - - end function - - ! clEnqueueMarkerWithWaitList - integer(c_int32_t) function clEnqueueBarrierWithWaitList(command_queue, & - num_events_in_wait_list, & - event_wait_list, & - event) & - BIND(C, NAME='clEnqueueBarrierWithWaitList') - - ! Define parameters. - integer(c_intptr_t), value :: command_queue - integer(c_int32_t), value :: num_events_in_wait_list - type(c_ptr), value :: event_wait_list - type(c_ptr), value :: event - - end function - - ! clSetPrintfCallback - integer(c_int32_t) function clSetPrintfCallback(context, & - pfn_notify, & - user_data) & - BIND(C, NAME='clSetPrintfCallback') - - ! Define parameters. - integer(c_intptr_t), value :: context - type(c_funptr), value :: pfn_notify - type(c_ptr), value :: user_data - - end function - - ! ------------------------- - ! Extension function access - ! ------------------------- - type(c_funptr) function clGetExtensionFunctionAddressForPlatform(platform, & - func_name) & - BIND(C, NAME='clGetExtensionFunctionAddressForPlatform') - - ! Define parameters. - integer(c_intptr_t), value :: platform - type(c_ptr), value :: func_name - - end function - - ! - !end interface - ! - -end module clfortran diff --git a/lib/opencl/ocl_env_mod.f90 b/lib/opencl/ocl_env_mod.f90 deleted file mode 100644 index e6fcad2fcb..0000000000 --- a/lib/opencl/ocl_env_mod.f90 +++ /dev/null @@ -1,185 +0,0 @@ -!> Module containing state and utilities for managing OpenCL device -module ocl_env_mod - use iso_c_binding, only: c_intptr_t - use ocl_utils_mod, only: CL_UTIL_STR_LEN, init_device - use ocl_params_mod - implicit none - - private - - !> Whether or not this module has been initialised - logical, save :: cl_env_initialised = .False. - !> Pointer to the OpenCL device being used - integer(c_intptr_t) :: cl_device - !> The OpenCL context - integer(c_intptr_t) :: cl_context - !> Version of OpenCL supported by the device - character(len=CL_UTIL_STR_LEN) :: cl_version_str - - !> Number of OpenCL command queues - integer, save :: cl_num_queues - !> Array of command queues - used to achieve concurrent execution - integer(c_intptr_t), allocatable, target :: cl_cmd_queues(:) - - !> The OpenCL kernels used by the model - integer, save :: cl_num_kernels - !> The maximum number of kernels we can store - integer, parameter :: cl_max_num_kernels = 30 - character(len=CL_UTIL_STR_LEN) :: cl_kernel_names(cl_max_num_kernels) - integer(c_intptr_t), target :: cl_kernels(cl_max_num_kernels) - - public ocl_env_init - public cl_context, cl_device, get_num_cmd_queues, get_cmd_queues - -contains - - !=================================================== - - !> Initialise the GOcean environment - subroutine ocl_env_init() - use ocl_utils_mod, only: init_device - implicit none - integer :: ierr - - if(cl_env_initialised)return - - ! Initialise the OpenCL device - call init_device(cl_device, cl_version_str, cl_context) - - ! Create command queue(s) - cl_num_queues = 4 - allocate(cl_cmd_queues(cl_num_queues), Stat=ierr) - if(ierr /= 0)then - stop "Failed to allocate list for OpenCL command queues" - end if - call init_cmd_queues(cl_num_queues, cl_cmd_queues, cl_context, cl_device) - - ! At this point we have no kernels - cl_num_kernels = 0 - - ! Environment now initialised - cl_env_initialised = .True. - - end subroutine ocl_env_init - - !=================================================== - - subroutine add_kernels(nkernels, kernel_names, filename) - use iso_c_binding, only: c_intptr_t - use ocl_utils_mod, only: get_program, get_kernel, release_program - integer, intent(in) :: nkernels - character(len=*), intent(in) :: kernel_names(nkernels) - character(len=*), intent(in), optional :: filename - ! Locals - integer :: ik, ierr, new_kern_count - integer(c_intptr_t), target :: prog - character(len=300) :: lfilename - - if(.not. cl_env_initialised)then - call ocl_env_init() - end if - - if(.not. present(filename))then - call get_environment_variable("PSYCLONE_KERNELS_FILE", lfilename) - else - lfilename = filename - end if - - if((nkernels + cl_num_kernels) > cl_max_num_kernels)then - write(*,"('add_kernels: Adding ',I2,' kernels will exceed the & -&maximum number of ',I3,' - increase ocl_env_mod::cl_max_num_kernels')") & - nkernels, cl_max_num_kernels - stop - end if - - ! Get a program object containing all of our kernels - prog = get_program(cl_context, cl_device, cl_version_str, lfilename) - - new_kern_count = 0 - do ik = 1, nkernels - ! Skip any kernels we've already got - if(get_kernel_index(kernel_names(ik)) /= 0)cycle - new_kern_count = new_kern_count + 1 - cl_kernels(cl_num_kernels+new_kern_count) = get_kernel(prog, & - kernel_names(ik)) - end do - cl_num_kernels = cl_num_kernels + new_kern_count - - ! Release the program now that we've created the kernels - call release_program(prog) - - end subroutine add_kernels - - !=================================================== - - function get_kernel_by_name(name) result(kern) - integer(c_intptr_t), target :: kern - character(len=*), intent(in) :: name - ! Locals - integer :: ik, match - character(len=256) :: msg - - match = get_kernel_index(name) - - if(match == 0)then - !> \TODO add check that we don't go out of bounds when writing to msg - write(*, "('get_kernel_by_name: no kernel with name ',(A),' found')")& - name - stop - end if - - kern = cl_kernels(match) - - end function get_kernel_by_name - - !=================================================== - - function get_kernel_index(name) result index - integer :: index - character(len=*), intent(in) :: name - integer :: ik - !> Helper routine to search for a kernel by name. Returns the - !! index of the kernel (in the cl_kernels list) if found or 0. - index = 0 - !> \TODO is there a better way to do this that reduces the need for - !! string comparisons? - do ik = 1, cl_num_kernels - if(name == cl_kernel_names(ik))then - ! We can't just return out of this loop because this is a - ! function - index = ik - exit - end if - end do - end function get_kernel_index - - !=================================================== - - function get_num_cmd_queues() result(num) - integer :: num - num = cl_num_queues - end function get_num_cmd_queues - - !=================================================== - - function get_cmd_queues() result(queues) - integer(c_intptr_t), pointer :: queues(:) - queues => cl_cmd_queues - end function get_cmd_queues - - !=================================================== - - subroutine ocl_release() - integer :: i - - do i=1, cl_num_kernels - call release_kernel(cl_kernels(i)) - end do - - call release_queues(cl_num_queues, cl_cmd_queues) - - call release_context(cl_context) - - end subroutine ocl_release - -end module ocl_env_mod diff --git a/lib/opencl/ocl_params_mod.f90 b/lib/opencl/ocl_params_mod.f90 deleted file mode 100644 index 2e15047cd4..0000000000 --- a/lib/opencl/ocl_params_mod.f90 +++ /dev/null @@ -1,10 +0,0 @@ -!> Module holding basic KIND parameters -module ocl_params_mod - implicit none - - public - - !> Douple precision kind parameter - integer, parameter :: wp = SELECTED_REAL_KIND(12,307) - -end module ocl_params_mod diff --git a/lib/opencl/ocl_utils_mod.f90 b/lib/opencl/ocl_utils_mod.f90 deleted file mode 100644 index a88d84ff2a..0000000000 --- a/lib/opencl/ocl_utils_mod.f90 +++ /dev/null @@ -1,433 +0,0 @@ -module ocl_utils_mod - use clfortran - use iso_c_binding - implicit none - - integer, parameter :: CL_UTIL_STR_LEN = 64 - -contains - - subroutine init_device(device, version_str, context) - !> Initialise an OpenCL device - integer(c_intptr_t), intent(inout) :: device, context - character(len=CL_UTIL_STR_LEN), intent(inout) :: version_str - ! Locals - integer :: iplatform, idevice, iallocerr - integer(c_intptr_t), target :: ctx_props(3) - integer(c_int32_t), target :: device_cu - integer(c_size_t) :: iret, zero_size = 0 - integer(c_int32_t) :: ierr, num_devices, num_platforms - integer(c_intptr_t), allocatable, target :: & - platform_ids(:), device_ids(:) - character(len=1,kind=c_char), allocatable, target :: device_name(:) - - ierr = clGetPlatformIDs(0, C_NULL_PTR, num_platforms) - call check_status('clGetPlatformIDs', ierr) - if (num_platforms < 1)then - write (*,*) "Failed to get any OpenCL platform IDs" - stop - end if - print '(a,i2)','Num Platforms: ',num_platforms - - allocate(platform_ids(num_platforms), stat=iallocerr) - if (iallocerr.ne.0) stop 'memory allocation error' - - ! whenever "&" appears in C subroutine (address-of) call, - ! then C_LOC has to be used in Fortran - ierr = clGetPlatformIDs(num_platforms, C_LOC(platform_ids), & - num_platforms) - call check_status('clGetPlatformIDs', ierr) - - ! Get device IDs only for platform 1 - iplatform=1 - - ierr=clGetDeviceIDs(platform_ids(iplatform), CL_DEVICE_TYPE_ALL, & - 0, C_NULL_PTR, num_devices) - call check_status('clGetDeviceIDs', ierr) - if (num_devices < 1)then - stop 'Failed to find any OpenCL devices' - end if - print '(a,i2)','Num Devices: ',num_devices - - allocate(device_ids(num_devices), stat=iallocerr) - if (iallocerr.ne.0) stop 'memory allocation error' - - ! whenever "&" appears in C subroutine (address-off) call, - ! then C_LOC has to be used in Fortran - ierr = clGetDeviceIDs(platform_ids(iplatform), CL_DEVICE_TYPE_ALL, & - num_devices, C_LOC(device_ids), num_devices) - call check_status('clGetDeviceIDs', ierr) - - ! Get device info only for device 1 - idevice=1 - device = device_ids(idevice) - - ierr=clGetDeviceInfo(device_ids(idevice), & - CL_DEVICE_MAX_COMPUTE_UNITS, sizeof(device_cu), & - C_LOC(device_cu), iret) - call check_status('clGetDeviceInfo', ierr) - ierr=clGetDeviceInfo(device_ids(idevice), & - CL_DEVICE_NAME, zero_size, C_NULL_PTR,iret) - call check_status('clGetDeviceInfo', ierr) - - allocate(device_name(iret), stat=iallocerr) - if (iallocerr.ne.0) stop 'allocate' - - ierr=clGetDeviceInfo(device_ids(idevice), CL_DEVICE_NAME, & - sizeof(device_name), C_LOC(device_name), iret) - if (ierr.ne.CL_SUCCESS) stop 'clGetDeviceInfo' - - write (*,'(a,i2,a,i3,a)',advance='no') & - ' Device (#',idevice,', Compute Units: ',device_cu,') - ' - print *,device_name(1:iret) - deallocate(device_name) - - print '(a,i2,a)', 'Creating context for: ', num_devices,' devices' - print '(a,i2)', 'for platform: ',iplatform - ctx_props(1) = CL_CONTEXT_PLATFORM - ctx_props(2) = platform_ids(iplatform) - ctx_props(3) = 0 - context = clCreateContext(C_LOC(ctx_props), num_devices, & - C_LOC(device_ids), C_NULL_FUNPTR, C_NULL_PTR, & - ierr) - call check_status('clCreateContext', ierr) - - end subroutine init_device - - !=================================================== - - subroutine release_context(context) - integer(c_intptr_t), intent(inout) :: context - ! Locals - integer(c_int32_t) :: ierr - - ierr=clReleaseContext(context) - call check_status('clReleaseContext', ierr) - - end subroutine release_context - - !=================================================== - - function get_program(context, device, version_str, filename) result(prog) - integer(c_intptr_t), target :: prog - integer(c_intptr_t), intent(inout), target :: device, context - character(len=CL_UTIL_STR_LEN), intent(in) :: version_str, filename - ! Locals - character(len=1,kind=c_char), allocatable, target :: source(:) - type(c_ptr), target :: psource - character(len=1024) :: options - character(len=1, kind=c_char), target :: retinfo(1:1024), c_options(1:1024) - integer :: i, irec, iallocerr - integer(c_int32_t) :: ierr - integer, parameter :: iunit=10 - integer(c_size_t), target :: binary_size, iret - character, dimension(1) :: char - - ! read kernel from disk - open(iunit, file=filename, access='direct', & - status='old', action='read', iostat=ierr, recl=1) - if (ierr.ne.0)then - write(*,*) 'Cannot open file: ', TRIM(filename) - stop - end if - irec=1 - do - read(iunit, rec=irec, iostat=ierr) char - if (ierr /= 0) exit - irec = irec+1 - end do - - if (irec.eq.0) stop 'nothing read' - allocate(source(irec+1),stat=iallocerr) - if (iallocerr.ne.0) stop 'allocate' - do i=1,irec - read(iunit,rec=i,iostat=ierr) source(i:i) - enddo - close(iunit) - - print '(a,i7)','size of source code in bytes: ',irec - - psource=C_LOC(source) ! pointer to source code - binary_size = irec - prog = clCreateProgramWithBinary(context, 1, C_LOC(device), & - C_LOC(binary_size), C_LOC(psource), & - C_NULL_PTR, ierr) - - call check_status('clCreateProgramWithSource', ierr) - - options = "" !'-cl-opt-disable' ! compiler options - irec = len(trim(options)) - do i=1, irec - c_options(i)=options(i:i) - enddo - c_options(irec+1) = C_NULL_CHAR - ierr=clBuildProgram(prog, 0, C_NULL_PTR, C_LOC(c_options), & - C_NULL_FUNPTR,C_NULL_PTR) - if (ierr.ne.CL_SUCCESS) then - print *,'clBuildProgram',ierr - ierr=clGetProgramBuildInfo(prog, device, CL_PROGRAM_BUILD_LOG, & - sizeof(retinfo), C_LOC(retinfo),iret) - if (ierr.ne.0) stop 'clGetProgramBuildInfo' - print '(a)','build log start' - print '(1024a)',retinfo(1:min(iret,1024)) - print '(a)','build log end' - stop - endif - - end function get_program - - - !> Release an OpenCL program object - subroutine release_program(prog) - integer(c_intptr_t), target, intent(inout) :: prog - ! Locals - integer(c_int32_t) :: ierr - - ierr = clReleaseProgram(prog) - call check_status('clReleaseProgram', ierr) - end subroutine release_program - - - !> Get a kernel object from a program object - !> - !> @param [in] prog OpenCL program object obtained from - !> get_program - !> @return the kernel object - !> - function get_kernel(prog, kernel_name) result(kernel) - integer(c_intptr_t), target, intent(in) :: prog - character(len=*), intent(in) :: kernel_name - integer(c_intptr_t), target :: kernel - ! Locals - integer :: irec, i - character(len=1, kind=c_char), target :: c_kernel_name(1:1024) - - irec = len(trim(kernel_name)) - do i=1, irec - c_kernel_name(i) = kernel_name(i:i) - enddo - c_kernel_name(irec+1) = C_NULL_CHAR - - kernel = clCreateKernel(prog, C_LOC(c_kernel_name), irec) - call check_status('clCreateKernel', irec) - - end function get_kernel - - !=================================================== - - subroutine release_kernel(kern) - integer(c_intptr_t), target, intent(inout) :: kern - integer(c_int32_t) :: ierr - - ierr = clReleaseKernel(kern) - call check_status('clReleaseKernel', ierr) - - end subroutine release_kernel - - !=================================================== - - !> Set-up the specified number of OpenCL command queues for the specified - !! context and device. - subroutine init_cmd_queues(nqueues, queues, context, device) - !> The number of command queues to create - integer, intent(in) :: nqueues - integer(c_intptr_t), target, intent(inout) :: queues(nqueues) - integer(c_intptr_t), intent(in) :: device - integer(c_intptr_t), intent(in) :: context - ! Locals - integer :: i - integer(c_int32_t) :: ierr - - do i=1, nqueues - queues(i) = clCreateCommandQueue(context, device, & - CL_QUEUE_PROFILING_ENABLE, ierr) - call check_status('clCreateCommandQueue', ierr) - end do - - end subroutine init_cmd_queues - - !=================================================== - - subroutine release_cmd_queues(nqueues, queues) - integer, intent(in) :: nqueues - integer(c_intptr_t), target, intent(inout) :: queues(nqueues) - ! Locals - integer :: iq - integer(c_int32_t) :: ierr - - do iq=1, nqueues - ierr=clReleaseCommandQueue(queues(iq)) - call check_status('clReleaseCommandQueue', ierr) - end do - - end subroutine release_cmd_queues - - !=================================================== - - !> Create a buffer in the supplied OpenCL context - function create_buffer(context, access, nbytes) result(buffer) - integer(c_intptr_t), intent(in) :: context - integer(c_int64_t), intent(in) :: access - integer(c_size_t), intent(in) :: nbytes - integer(c_intptr_t), target :: buffer - ! Locals - integer(c_int32_t) :: ierr - - buffer = clCreateBuffer(context, access, & - nbytes, C_NULL_PTR, ierr) - call check_status('clCreateBuffer', ierr) - - end function create_buffer - - !===================================================== - - !> Read a buffer (containing 64-bit floats) from an OpenCL device. Call - !! blocks until read is complete. - subroutine read_buffer(queue, device_ptr, local_array, nelem) - use ocl_params_mod, only: wp - integer(c_intptr_t), intent(in) :: queue, device_ptr - real(kind=wp), target, intent(in) :: local_array - integer(8), intent(in) :: nelem - ! Locals - integer(c_int32_t) :: ierr - integer(c_intptr_t), target :: event - integer(8) :: nbytes - - nbytes = nelem * 8_8 - ierr = clEnqueueReadBuffer(queue, device_ptr, CL_TRUE, 0_8, & - nbytes, C_LOC(local_array), & - 0, C_NULL_PTR, C_LOC(event)) - call check_status('clEnqueueReadBuffer', ierr) - - !> \todo implement an asynchronous read so we don't have to wait - !! for each one to complete. - ierr = clWaitForEvents(1, C_LOC(event)) - call check_status('clWaitForEvents', ierr) - - end subroutine read_buffer - - !===================================================== - - !> Check the return code of an OpenCL API cal - subroutine check_status(text, ierr) - implicit none - character(len=*), intent(in) :: text - integer, intent(in) :: ierr - - logical, parameter :: verbose = .TRUE. - - if(ierr /= CL_SUCCESS)then - write(*,'("Hit error: ",(A),": ",(A))') text, OCL_GetErrorString(ierr) - stop - end if - if(verbose)then - write(*,'("Called ",(A)," OK")') text - end if - end subroutine check_status - -function OCL_GetErrorString(error) - implicit none - character(len=64) :: OCL_GetErrorString - integer, intent(in) :: error - select case(error) - - case (CL_SUCCESS) - OCL_GetErrorString = "CL_SUCCESS" - case (CL_DEVICE_NOT_FOUND) - OCL_GetErrorString = "CL_DEVICE_NOT_FOUND" - case (CL_DEVICE_NOT_AVAILABLE) - OCL_GetErrorString = "CL_DEVICE_NOT_AVAILABLE" - case (CL_COMPILER_NOT_AVAILABLE) - OCL_GetErrorString = "CL_COMPILER_NOT_AVAILABLE" - case (CL_MEM_OBJECT_ALLOCATION_FAILURE) - OCL_GetErrorString = "CL_MEM_OBJECT_ALLOCATION_FAILURE" - case (CL_OUT_OF_RESOURCES) - OCL_GetErrorString = "CL_OUT_OF_RESOURCES" - case (CL_OUT_OF_HOST_MEMORY) - OCL_GetErrorString = "CL_OUT_OF_HOST_MEMORY" - case (CL_PROFILING_INFO_NOT_AVAILABLE) - OCL_GetErrorString = "CL_PROFILING_INFO_NOT_AVAILABLE" - case (CL_MEM_COPY_OVERLAP) - OCL_GetErrorString = "CL_MEM_COPY_OVERLAP" - case (CL_IMAGE_FORMAT_MISMATCH) - OCL_GetErrorString = "CL_IMAGE_FORMAT_MISMATCH" - case (CL_IMAGE_FORMAT_NOT_SUPPORTED) - OCL_GetErrorString = "CL_IMAGE_FORMAT_NOT_SUPPORTED" - case (CL_BUILD_PROGRAM_FAILURE) - OCL_GetErrorString = "CL_BUILD_PROGRAM_FAILURE" - case (CL_MAP_FAILURE) - OCL_GetErrorString = "CL_MAP_FAILURE" - case (CL_INVALID_VALUE) - OCL_GetErrorString = "CL_INVALID_VALUE" - case (CL_INVALID_DEVICE_TYPE) - OCL_GetErrorString = "CL_INVALID_DEVICE_TYPE" - case (CL_INVALID_PLATFORM) - OCL_GetErrorString = "CL_INVALID_PLATFORM" - case (CL_INVALID_DEVICE) - OCL_GetErrorString = "CL_INVALID_DEVICE" - case (CL_INVALID_CONTEXT) - OCL_GetErrorString = "CL_INVALID_CONTEXT" - case (CL_INVALID_QUEUE_PROPERTIES) - OCL_GetErrorString = "CL_INVALID_QUEUE_PROPERTIES" - case (CL_INVALID_COMMAND_QUEUE) - OCL_GetErrorString = "CL_INVALID_COMMAND_QUEUE" - case (CL_INVALID_HOST_PTR) - OCL_GetErrorString = "CL_INVALID_HOST_PTR" - case (CL_INVALID_MEM_OBJECT) - OCL_GetErrorString = "CL_INVALID_MEM_OBJECT" - case (CL_INVALID_IMAGE_FORMAT_DESCRIPTOR) - OCL_GetErrorString = "CL_INVALID_IMAGE_FORMAT_DESCRIPTOR" - case (CL_INVALID_IMAGE_SIZE) - OCL_GetErrorString = "CL_INVALID_IMAGE_SIZE" - case (CL_INVALID_SAMPLER) - OCL_GetErrorString = "CL_INVALID_SAMPLER" - case (CL_INVALID_BINARY) - OCL_GetErrorString = "CL_INVALID_BINARY" - case (CL_INVALID_BUILD_OPTIONS) - OCL_GetErrorString = "CL_INVALID_BUILD_OPTIONS" - case (CL_INVALID_PROGRAM) - OCL_GetErrorString = "CL_INVALID_PROGRAM" - case (CL_INVALID_PROGRAM_EXECUTABLE) - OCL_GetErrorString = "CL_INVALID_PROGRAM_EXECUTABLE" - case (CL_INVALID_KERNEL_NAME) - OCL_GetErrorString = "CL_INVALID_KERNEL_NAME" - case (CL_INVALID_KERNEL_DEFINITION) - OCL_GetErrorString = "CL_INVALID_KERNEL_DEFINITION" - case (CL_INVALID_KERNEL) - OCL_GetErrorString = "CL_INVALID_KERNEL" - case (CL_INVALID_ARG_INDEX) - OCL_GetErrorString = "CL_INVALID_ARG_INDEX" - case (CL_INVALID_ARG_VALUE) - OCL_GetErrorString = "CL_INVALID_ARG_VALUE" - case (CL_INVALID_ARG_SIZE) - OCL_GetErrorString = "CL_INVALID_ARG_SIZE" - case (CL_INVALID_KERNEL_ARGS) - OCL_GetErrorString = "CL_INVALID_KERNEL_ARGS" - case (CL_INVALID_WORK_DIMENSION) - OCL_GetErrorString = "CL_INVALID_WORK_DIMENSION" - case (CL_INVALID_WORK_GROUP_SIZE) - OCL_GetErrorString = "CL_INVALID_WORK_GROUP_SIZE" - case (CL_INVALID_WORK_ITEM_SIZE) - OCL_GetErrorString = "CL_INVALID_WORK_ITEM_SIZE" - case (CL_INVALID_GLOBAL_OFFSET) - OCL_GetErrorString = "CL_INVALID_GLOBAL_OFFSET" - case (CL_INVALID_EVENT_WAIT_LIST) - OCL_GetErrorString = "CL_INVALID_EVENT_WAIT_LIST" - case (CL_INVALID_EVENT) - OCL_GetErrorString = "CL_INVALID_EVENT" - case (CL_INVALID_OPERATION) - OCL_GetErrorString = "CL_INVALID_OPERATION" - case (CL_INVALID_GL_OBJECT) - OCL_GetErrorString = "CL_INVALID_GL_OBJECT" - case (CL_INVALID_BUFFER_SIZE) - OCL_GetErrorString = "CL_INVALID_BUFFER_SIZE" - case (CL_INVALID_MIP_LEVEL) - OCL_GetErrorString = "CL_INVALID_MIP_LEVEL" - case(CL_INVALID_GLOBAL_WORK_SIZE) - OCL_GetErrorString = "CL_INVALID_GLOBAL_WORK_SIZE" - case default - OCL_GetErrorString = "unknown error code" - end select - end function OCL_GetErrorString - -end module ocl_utils_mod From 3ade38fd95b8f76959fb2fd1a4d24f810f82d483 Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Thu, 20 Sep 2018 14:23:05 +0100 Subject: [PATCH 36/60] #216 use namespace manager for kernel name [skip ci] --- src/psyclone/gocean1p0.py | 33 ++++++++++++++++++++++----------- src/psyclone/psyGen.py | 5 ++++- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/src/psyclone/gocean1p0.py b/src/psyclone/gocean1p0.py index bfb1f2a43b..c3056084c1 100644 --- a/src/psyclone/gocean1p0.py +++ b/src/psyclone/gocean1p0.py @@ -50,7 +50,7 @@ from psyclone.parse import Descriptor, KernelType, ParseError from psyclone.psyGen import PSy, Invokes, Invoke, Schedule, \ Loop, Kern, Arguments, Argument, KernelArgument, ACCDataDirective, \ - GenerationError, args_filter + GenerationError, args_filter, NameSpaceFactory import psyclone.expression as expr # The different grid-point types that a field can live on @@ -135,9 +135,9 @@ class GOInvokes(Invokes): ''' The GOcean specific invokes class. This passes the GOcean specific invoke class to the base class so it creates the one we require. - :param alg_calls: list of the Invoke calls discovered in the Algorithm - layer - :type alg_calls: list of :py:class:`psyclone.TBD` + :param alg_calls: The Invoke calls discovered in the Algorithm layer. + :type alg_calls: OrderedDict of :py:class:`psyclone.parse.InvokeCall` \ + objects. ''' def __init__(self, alg_calls): if False: # pylint: disable=using-constant-test @@ -251,6 +251,9 @@ def gen_code(self, parent): # Generate the code body of this subroutine self.schedule.gen_code(invoke_sub) + # If we're generating an OpenCL routine then the arguments must + # have the target attribute as we pass pointers to them in to + # the OpenCL run-time. if self.schedule.opencl: target = True else: @@ -725,6 +728,8 @@ def __init__(self): self._children = [] self._name = "" self._index_offset = "" + # Get a reference to the namespace manager + self._name_space_manager = NameSpaceFactory().create() def load(self, call, parent=None): ''' Populate the state of this GOKern object ''' @@ -818,21 +823,28 @@ def gen_code(self, parent): def gen_ocl(self, parent): ''' + Generates code for the OpenCL invocation of this kernel. + :param parent: Parent node in the f2pygen AST to which to add content. :type parent: :py:class:`psyclone.f2pygen.SubroutineGen` ''' from psyclone.f2pygen import CallGen, DeclGen, AssignGen, CommentGen, \ IfThenGen, UseGen - + # Create the array used to specify the iteration space of the kernel garg = self.find_grid_access() + glob_size = self._name_space_manager.create_name( + root_name="globalsize", context="PSyVars", + label="globalsize") parent.add(DeclGen(parent, datatype="integer", target=True, - kind="c_size_t", entity_decls=["globalsize(2)"])) + kind="c_size_t", entity_decls=[glob_size + "(2)"])) parent.add(AssignGen( - parent, lhs="globalsize", + parent, lhs=glob_size, rhs="(/{0}%grid%nx, {0}%grid%ny/)".format(garg.name))) - kernel = "kernel_" + self._name # TODO use namespace manager - + base = "kernel_" + self._name + kernel = self._name_space_manager.create_name(root_name=base, + context="PSyVars", + label=base) parent.add(UseGen(parent, name="fortcl", only=True, funcnames=["create_rw_buffer"])) # Ensure fields are on device TODO this belongs somewhere else! @@ -908,9 +920,8 @@ def gen_ocl(self, parent): cnull = "C_NULL_PTR" cmd_queue = "cmd_queues(1)" # TODO use namespace manager - gsize = "globalsize" # TODO use namespace manager args = ", ".join([cmd_queue, kernel, "2", cnull, - "C_LOC({0})".format(gsize), + "C_LOC({0})".format(glob_size), cnull, "0", cnull, cnull]) parent.add( AssignGen(parent, lhs="ierr", diff --git a/src/psyclone/psyGen.py b/src/psyclone/psyGen.py index 78d026540c..2ff7c3ba0f 100644 --- a/src/psyclone/psyGen.py +++ b/src/psyclone/psyGen.py @@ -1392,6 +1392,7 @@ def __init__(self, KernFactory, BuiltInFactory, alg_calls=[]): Node.__init__(self, children=sequence) self._invoke = None self._opencl = False # Whether or not to generate OpenCL + self._name_space_manager = NameSpaceFactory().create() def view(self, indent=0): ''' @@ -1467,7 +1468,9 @@ def gen_code(self, parent): # Kernel pointers kernels = self.walk(self._children, Call) for kern in kernels: - kernel = "kernel_" + kern.name # TODO use namespace manager + base = "kernel_" + kern.name + kernel = self._name_space_manager.create_name( + root_name=base, context="PSyVars", label=base) parent.add( DeclGen(parent, datatype="integer", kind="c_intptr_t", save=True, target=True, entity_decls=[kernel])) From 070e2d5c94075fe2664ab913996f4e1701b7dea5 Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Thu, 20 Sep 2018 15:11:33 +0100 Subject: [PATCH 37/60] #216 WIP using namespace manager for vars in generated code [skip ci] --- src/psyclone/gocean1p0.py | 57 ++++++++++++++++++++++++--------------- src/psyclone/psyGen.py | 29 +++++++++++++------- 2 files changed, 54 insertions(+), 32 deletions(-) diff --git a/src/psyclone/gocean1p0.py b/src/psyclone/gocean1p0.py index c3056084c1..c67817c53b 100644 --- a/src/psyclone/gocean1p0.py +++ b/src/psyclone/gocean1p0.py @@ -863,41 +863,53 @@ def gen_ocl(self, parent): condition = device_buff + " == 0" host_buff = "{0}%grid%{1}".format(garg.name, arg.name) + nbytes = self._name_space_manager.create_name( + root_name="size_in_bytes", context="PSyVars", + label="size_in_bytes") + wevent = self._name_space_manager.create_name( + root_name="write_event", context="PSyVars", + label="write_event") ifthen = IfThenGen(parent, condition) parent.add(ifthen) parent.add(DeclGen(parent, datatype="integer", kind="c_size_t", - entity_decls=["size_in_bytes"])) + entity_decls=[nbytes])) parent.add(DeclGen(parent, datatype="integer", kind="c_intptr_t", target=True, - entity_decls=["write_event"])) + entity_decls=[wevent])) # Use c_sizeof() on first element of array to be copied over in # order to cope with the fact that some grid properties are # integer. size_expr = ("int({0}%grid%nx*{0}%grid%ny, 8)*" "c_sizeof({1}(1,1))".format(garg.name, host_buff)) - ifthen.add(AssignGen(ifthen, lhs="size_in_bytes", - rhs=size_expr)) + ifthen.add(AssignGen(ifthen, lhs=nbytes, rhs=size_expr)) ifthen.add(CommentGen(ifthen, " Create buffer on device")) + # Get the name of the list of command queues (set in + # psyGen.Schedule) + qlist = self._name_space_manager.create_name( + root_name="cmd_queues", context="PSyVars", + label="cmd_queues") + flag = self._name_space_manager.create_name( + root_name="ierr", context="PSyVars", label="ierr") ifthen.add(AssignGen( ifthen, lhs=device_buff, - rhs="create_rw_buffer(size_in_bytes)")) + rhs="create_rw_buffer(" + nbytes + ")")) ifthen.add(AssignGen( - ifthen, lhs="ierr", - rhs="clEnqueueWriteBuffer(cmd_queues(1), {0}, " - "CL_TRUE, 0_8, size_in_bytes, C_LOC({1}), " - "0, C_NULL_PTR, C_LOC(write_event))".format(device_buff, - host_buff))) + ifthen, lhs=flag, + rhs="clEnqueueWriteBuffer({0}(1), {1}, CL_TRUE, " + "0_8, {2}, C_LOC({3}), 0, C_NULL_PTR, " + "C_LOC({4}))".format(qlist, device_buff, + nbytes, host_buff, wevent))) if arg.type == "field": - ifthen.add(AssignGen(ifthen, - lhs="{0}%data_on_device".format(arg.name), - rhs=".true.")) + ifthen.add(AssignGen( + ifthen, lhs="{0}%data_on_device".format(arg.name), + rhs=".true.")) # Ensure data copies have finished parent.add(CommentGen(parent, " Block until data copies have finished")) - parent.add(AssignGen(parent, lhs="ierr", - rhs="clFinish(cmd_queues(1))")) + parent.add(AssignGen(parent, lhs=flag, + rhs="clFinish(" + qlist + "(1))")) # Then we set the kernel arguments arguments = [kernel, garg.name+"%grid%nx"] # TODO this argument-list generation duplicates that in @@ -918,13 +930,13 @@ def gen_ocl(self, parent): # Then we call clEnqueueNDRangeKernel parent.add(CommentGen(parent, " Launch the kernel")) cnull = "C_NULL_PTR" - cmd_queue = "cmd_queues(1)" # TODO use namespace manager + cmd_queue = qlist + "(1)" args = ", ".join([cmd_queue, kernel, "2", cnull, "C_LOC({0})".format(glob_size), cnull, "0", cnull, cnull]) parent.add( - AssignGen(parent, lhs="ierr", + AssignGen(parent, lhs=flag, rhs="clEnqueueNDRangeKernel({0})".format(args))) parent.add(CommentGen(parent, "")) @@ -944,9 +956,10 @@ def gen_arg_setter_code(self, parent): from psyclone.f2pygen import SubroutineGen, UseGen, DeclGen, \ AssignGen, CommentGen # TODO take care with literal arguments - # TODO use name-space manager for name of kernel-object arg - arguments = ["kernel_obj", "nx"] + \ - [arg.name for arg in self._arguments.args] + kobj = self._name_space_manager.create_name( + root_name="kernel_obj", context="ArgSetter", + label="kernel_obj") + arguments = [kobj, "nx"] + [arg.name for arg in self._arguments.args] sub = SubroutineGen(parent, name=self.name+"_set_args", args=arguments) parent.add(sub) @@ -960,7 +973,7 @@ def gen_arg_setter_code(self, parent): sub.add(DeclGen(sub, datatype="integer", target=True, entity_decls=["nx"])) sub.add(DeclGen(sub, datatype="integer", kind="c_intptr_t", - target=True, entity_decls=["kernel_obj"])) + target=True, entity_decls=[kobj])) # Arrays (grid properties and fields) args = args_filter(self._arguments.args, @@ -993,7 +1006,7 @@ def gen_arg_setter_code(self, parent): sub.add(AssignGen( sub, lhs="ierr", rhs="clSetKernelArg({0}, {1}, C_SIZEOF({2}), C_LOC({2}))". - format("kernel_obj", index, "nx"))) + format(kobj, index, "nx"))) # Now all of the 'standard' kernel arguments for arg in self.arguments.args: index += 1 diff --git a/src/psyclone/psyGen.py b/src/psyclone/psyGen.py index 2ff7c3ba0f..02f7d07a39 100644 --- a/src/psyclone/psyGen.py +++ b/src/psyclone/psyGen.py @@ -1444,26 +1444,35 @@ def gen_code(self, parent): "get_cmd_queues", "get_kernel_by_name"])) # Command queues + nqueues = self._name_space_manager.create_name( + root_name="num_cmd_queues", context="PSyVars", + label="num_cmd_queues") + qlist = self._name_space_manager.create_name( + root_name="cmd_queues", context="PSyVars", label="cmd_queues") + first = self._name_space_manager.create_name( + root_name="first_time", context="PSyVars", label="first_time") + flag = self._name_space_manager.create_name( + root_name="ierr", context="PSyVars", label="ierr") parent.add(DeclGen(parent, datatype="integer", save=True, - entity_decls=["num_cmd_queues"])) + entity_decls=[nqueues])) parent.add(DeclGen(parent, datatype="integer", save=True, pointer=True, kind="c_intptr_t", - entity_decls=["cmd_queues(:)"])) + entity_decls=[qlist + "(:)"])) parent.add(DeclGen(parent, datatype="integer", - entity_decls=["ierr"])) + entity_decls=[flag])) parent.add(DeclGen(parent, datatype="logical", save=True, - entity_decls=["first_time"], + entity_decls=[first], initial_values=[".true."])) - if_first = IfThenGen(parent, "first_time") + if_first = IfThenGen(parent, first) parent.add(if_first) - if_first.add(AssignGen(if_first, lhs="first_time", rhs=".false.")) + if_first.add(AssignGen(if_first, lhs=first, rhs=".false.")) if_first.add(CommentGen(if_first, " Ensure OpenCL run-time is initialised " "for this PSy-layer module")) if_first.add(CallGen(if_first, "psy_init")) - if_first.add(AssignGen(if_first, lhs="num_cmd_queues", + if_first.add(AssignGen(if_first, lhs=nqueues, rhs="get_num_cmd_queues()")) - if_first.add(AssignGen(if_first, lhs="cmd_queues", pointer=True, + if_first.add(AssignGen(if_first, lhs=qlist, pointer=True, rhs="get_cmd_queues()")) # Kernel pointers kernels = self.walk(self._children, Call) @@ -1489,8 +1498,8 @@ def gen_code(self, parent): # BUG this assumes only the first command queue is used parent.add(CommentGen(parent, " Block until all kernels have finished")) - parent.add(AssignGen(parent, lhs="ierr", - rhs="clFinish(cmd_queues(1))")) + parent.add(AssignGen(parent, lhs=flag, + rhs="clFinish(" + qlist + "(1))")) @property def opencl(self): From 651fd975b63111fe9d0691b8d3fd657276daaff3 Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Thu, 20 Sep 2018 22:04:45 +0100 Subject: [PATCH 38/60] #216 add xfailing test for const scalar kernel args --- src/psyclone/tests/gocean1p0_opencl_test.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/psyclone/tests/gocean1p0_opencl_test.py b/src/psyclone/tests/gocean1p0_opencl_test.py index 60b3eb1ca6..6e65e61fbd 100644 --- a/src/psyclone/tests/gocean1p0_opencl_test.py +++ b/src/psyclone/tests/gocean1p0_opencl_test.py @@ -42,6 +42,7 @@ from psyclone.parse import parse from psyclone.psyGen import PSyFactory from psyclone.generator import GenerationError, ParseError +from psyclone.transformations import OCLTrans from psyclone_test_utils import get_invoke API = "gocean1.0" @@ -52,7 +53,6 @@ def test_use_stmts(): module use statements ''' psy, _ = get_invoke("single_invoke.f90", API, idx=0) sched = psy.invokes.invoke_list[0].schedule - from psyclone.transformations import OCLTrans otrans = OCLTrans() otrans.apply(sched) generated_code = str(psy.gen).lower() @@ -73,7 +73,6 @@ def test_psy_init(): OpenCL environment ''' psy, _ = get_invoke("single_invoke.f90", API, idx=0) sched = psy.invokes.invoke_list[0].schedule - from psyclone.transformations import OCLTrans otrans = OCLTrans() otrans.apply(sched) generated_code = str(psy.gen) @@ -139,7 +138,6 @@ def test_set_kern_float_arg(): argument. ''' psy, _ = get_invoke("single_invoke_scalar_float_arg.f90", API, idx=0) sched = psy.invokes.invoke_list[0].schedule - from psyclone.transformations import OCLTrans otrans = OCLTrans() otrans.apply(sched) generated_code = str(psy.gen) @@ -167,3 +165,18 @@ def test_set_kern_float_arg(): CALL check_status('clSetKernelArg: arg 3 of bc_ssh_code', ierr) END SUBROUTINE bc_ssh_code_set_args''' assert expected in generated_code + + +@pytest.mark.xfail(reason="Fail to handle kernel arguments passed by value") +def test_set_arg_const_scalar(): # pylint:disable=invalid-name + ''' Check that using a const scalar as parameter works when setting + kernel arguments. ''' + psy, _ = get_invoke("test00.1_invoke_kernel_using_const_scalar.f90", + API, idx=0) + sched = psy.invokes.invoke_list[0].schedule + otrans = OCLTrans() + otrans.apply(sched) + generated = str(psy.gen) + print(generated) + assert "clSetKernelArg(kernel_obj, 1, C_SIZEOF(0), C_LOC(0))" not in \ + generated From d8633a48e1a94d90ae5d4f8981940e4d38495647 Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Fri, 21 Sep 2018 10:04:01 +0100 Subject: [PATCH 39/60] #216 add code and test that we reject passing scalars by value --- src/psyclone/tests/gocean1p0_opencl_test.py | 45 ++++++++++--------- src/psyclone/transformations.py | 49 ++++++++++++++++++--- 2 files changed, 66 insertions(+), 28 deletions(-) diff --git a/src/psyclone/tests/gocean1p0_opencl_test.py b/src/psyclone/tests/gocean1p0_opencl_test.py index 6e65e61fbd..e8f3aea1d9 100644 --- a/src/psyclone/tests/gocean1p0_opencl_test.py +++ b/src/psyclone/tests/gocean1p0_opencl_test.py @@ -50,7 +50,7 @@ def test_use_stmts(): ''' Test that generating code for OpenCL results in the correct - module use statements ''' + module use statements. ''' psy, _ = get_invoke("single_invoke.f90", API, idx=0) sched = psy.invokes.invoke_list[0].schedule otrans = OCLTrans() @@ -67,10 +67,9 @@ def test_use_stmts(): assert "if (first_time) then" in generated_code -@pytest.mark.xfail(reason="DeclGen does not support character") def test_psy_init(): ''' Check that we create a psy_init() routine that sets-up the - OpenCL environment ''' + OpenCL environment. ''' psy, _ = get_invoke("single_invoke.f90", API, idx=0) sched = psy.invokes.invoke_list[0].schedule otrans = OCLTrans() @@ -79,22 +78,27 @@ def test_psy_init(): print(generated_code) expected = '''\ SUBROUTINE psy_init() - USE ocl_env_mod, ONLY: ocl_env_init, add_kernels - CHARACTER, LEN(40) :: kernel_names(1) - ! Initialise the OpenCL environment/device - CALL ocl_env_init - ! The kernels this PSy layer module requires - kernel_names(1) = "compute_cu_code" - ! Create the OpenCL kernel objects. Expects to find all of the compiled - ! kernels in PSYCLONE_KERNELS_FILE. - CALL add_kernels(1, kernel_names) + USE fortcl, ONLY: ocl_env_init, add_kernels + CHARACTER(LEN=30) kernel_names(1) + LOGICAL, save :: initialised=.False. + ! Check to make sure we only execute this routine once + IF (.not. initialised) THEN + initialised = .True. + ! Initialise the OpenCL environment/device + CALL ocl_env_init + ! The kernels this PSy layer module requires + kernel_names(1) = "compute_cu_code" + ! Create the OpenCL kernel objects. Expects to find all of the compiled + ! kernels in PSYCLONE_KERNELS_FILE. + CALL add_kernels(1, kernel_names) + END IF END SUBROUTINE psy_init ''' assert expected in generated_code def test_set_kern_args(): - ''' Check that we generate the necessary code to set kernel arguments ''' + ''' Check that we generate the necessary code to set kernel arguments. ''' psy, _ = get_invoke("single_invoke_two_kernels.f90", API, idx=0) sched = psy.invokes.invoke_list[0].schedule from psyclone.transformations import OCLTrans @@ -167,16 +171,15 @@ def test_set_kern_float_arg(): assert expected in generated_code -@pytest.mark.xfail(reason="Fail to handle kernel arguments passed by value") def test_set_arg_const_scalar(): # pylint:disable=invalid-name - ''' Check that using a const scalar as parameter works when setting - kernel arguments. ''' + ''' Check that an invoke that passes a scalar kernel argument by + value is rejected. (We haven't yet implemented the necessary code for + setting the value of such an argument in OpenCL.) ''' psy, _ = get_invoke("test00.1_invoke_kernel_using_const_scalar.f90", API, idx=0) sched = psy.invokes.invoke_list[0].schedule otrans = OCLTrans() - otrans.apply(sched) - generated = str(psy.gen) - print(generated) - assert "clSetKernelArg(kernel_obj, 1, C_SIZEOF(0), C_LOC(0))" not in \ - generated + with pytest.raises(NotImplementedError) as err: + otrans.apply(sched) + assert ("Cannot generate OpenCL for Invokes that contain kernels with " + "arguments passed by value" in str(err)) diff --git a/src/psyclone/transformations.py b/src/psyclone/transformations.py index 0e3d7e6f99..3b7dedcab3 100644 --- a/src/psyclone/transformations.py +++ b/src/psyclone/transformations.py @@ -2120,20 +2120,55 @@ def name(self): return "OCLTrans" def apply(self, sched, opencl=True): - from psyclone.psyGen import Schedule - if not isinstance(sched, Schedule): - raise TransformationError( - "Error in OCLTrans: the supplied node must be a (sub-class " - "of) Schedule but got {0}".format(type(sched))) - + ''' + Apply the OpenCL transformation to the supplied Schedule. This + causes PSyclone to generate an OpenCL version of the corresponding + PSy-layer routine. + :param sched: Schedule to transform. + :type sched: :py:class:`psyclone.psyGen.Schedule` + :param bool opencl: whether or not to enable OpenCL generation. + ''' + if opencl: + self._validate(sched) # create a memento of the schedule and the proposed transformation from psyclone.undoredo import Memento keep = Memento(sched, self, [sched]) sched.opencl = opencl - return sched, keep + def _validate(self, sched): + ''' + Checks that the supplied Schedule is valid and that an OpenCL + version of it can be generated. + + :param sched: Schedule to check. + :type sched: :py:class:`psyclone.psyGen.Schedule` + :raises TransformationError: if the Schedule is not for the GOcean1.0 \ + API. + :raises NotImplementedError: if any of the kernels have arguments \ + passed by value. + ''' + from psyclone.psyGen import Schedule, args_filter + from psyclone.gocean1p0 import GOSchedule + if isinstance(sched, Schedule): + if not isinstance(sched, GOSchedule): + raise TransformationError( + "OpenCL generation is currently only supported for the " + "GOcean API but got a Schedule of type: '{0}'". + format(type(sched))) + else: + raise TransformationError( + "Error in OCLTrans: the supplied node must be a (sub-class " + "of) Schedule but got {0}".format(type(sched))) + # Now we need to check the arguments of all the kernels + args = args_filter(sched.args, arg_types=["scalar"], is_literal=True) + for arg in args: + if arg.is_literal: + raise NotImplementedError( + "Cannot generate OpenCL for Invokes that contain " + "kernels with arguments passed by value") + class ProfileRegionTrans(RegionTrans): ''' Create a profile region around a list of statements. For From 335f75a47ca3f285b14043e8ce64db93ac03a2c6 Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Mon, 1 Oct 2018 11:27:49 +0100 Subject: [PATCH 40/60] #216 create new gen_data_on_ocl_device() routine --- src/psyclone/gocean1p0.py | 148 ++++++++++++++++++++------------------ 1 file changed, 77 insertions(+), 71 deletions(-) diff --git a/src/psyclone/gocean1p0.py b/src/psyclone/gocean1p0.py index c67817c53b..9f63f197be 100644 --- a/src/psyclone/gocean1p0.py +++ b/src/psyclone/gocean1p0.py @@ -815,7 +815,6 @@ def gen_code(self, parent): raise GenerationError("Kernel {0}, argument {1} has " "unrecognised type: {2}". format(self._name, arg.name, arg.type)) - parent.add(CallGen(parent, self._name, arguments)) if not self.module_inline: parent.add(UseGen(parent, name=self._module_name, only=True, @@ -833,8 +832,7 @@ def gen_ocl(self, parent): # Create the array used to specify the iteration space of the kernel garg = self.find_grid_access() glob_size = self._name_space_manager.create_name( - root_name="globalsize", context="PSyVars", - label="globalsize") + root_name="globalsize", context="PSyVars", label="globalsize") parent.add(DeclGen(parent, datatype="integer", target=True, kind="c_size_t", entity_decls=[glob_size + "(2)"])) parent.add(AssignGen( @@ -845,75 +843,10 @@ def gen_ocl(self, parent): kernel = self._name_space_manager.create_name(root_name=base, context="PSyVars", label=base) - parent.add(UseGen(parent, name="fortcl", only=True, - funcnames=["create_rw_buffer"])) - # Ensure fields are on device TODO this belongs somewhere else! - parent.add(CommentGen(parent, - " Ensure field data is on device")) - for arg in self._arguments.args: - if arg.type == "field" or arg.type == "grid_property": - - if arg.type == "field": - condition = ".NOT. {0}%data_on_device".format(arg.name) - device_buff = "{0}%device_ptr".format(arg.name) - host_buff = "{0}%data".format(arg.name) - else: - device_buff = "{0}%grid%{1}_device".format(garg.name, - arg.name) - condition = device_buff + " == 0" - host_buff = "{0}%grid%{1}".format(garg.name, arg.name) - - nbytes = self._name_space_manager.create_name( - root_name="size_in_bytes", context="PSyVars", - label="size_in_bytes") - wevent = self._name_space_manager.create_name( - root_name="write_event", context="PSyVars", - label="write_event") - ifthen = IfThenGen(parent, condition) - parent.add(ifthen) - parent.add(DeclGen(parent, datatype="integer", kind="c_size_t", - entity_decls=[nbytes])) - parent.add(DeclGen(parent, datatype="integer", - kind="c_intptr_t", target=True, - entity_decls=[wevent])) - # Use c_sizeof() on first element of array to be copied over in - # order to cope with the fact that some grid properties are - # integer. - size_expr = ("int({0}%grid%nx*{0}%grid%ny, 8)*" - "c_sizeof({1}(1,1))".format(garg.name, host_buff)) - ifthen.add(AssignGen(ifthen, lhs=nbytes, rhs=size_expr)) - ifthen.add(CommentGen(ifthen, " Create buffer on device")) - # Get the name of the list of command queues (set in - # psyGen.Schedule) - qlist = self._name_space_manager.create_name( - root_name="cmd_queues", context="PSyVars", - label="cmd_queues") - flag = self._name_space_manager.create_name( - root_name="ierr", context="PSyVars", label="ierr") - - ifthen.add(AssignGen( - ifthen, lhs=device_buff, - rhs="create_rw_buffer(" + nbytes + ")")) - ifthen.add(AssignGen( - ifthen, lhs=flag, - rhs="clEnqueueWriteBuffer({0}(1), {1}, CL_TRUE, " - "0_8, {2}, C_LOC({3}), 0, C_NULL_PTR, " - "C_LOC({4}))".format(qlist, device_buff, - nbytes, host_buff, wevent))) - if arg.type == "field": - ifthen.add(AssignGen( - ifthen, lhs="{0}%data_on_device".format(arg.name), - rhs=".true.")) - - # Ensure data copies have finished - parent.add(CommentGen(parent, - " Block until data copies have finished")) - parent.add(AssignGen(parent, lhs=flag, - rhs="clFinish(" + qlist + "(1))")) + # Generate code to ensure data is on device + self.gen_data_on_ocl_device() # Then we set the kernel arguments arguments = [kernel, garg.name+"%grid%nx"] - # TODO this argument-list generation duplicates that in - # GOKern.gen_code(). We need to re-factor ala dynamo0p3.ArgOrdering. for arg in self._arguments.args: if arg.type == "scalar": arguments.append(arg.name) @@ -955,7 +888,8 @@ def gen_arg_setter_code(self, parent): ''' from psyclone.f2pygen import SubroutineGen, UseGen, DeclGen, \ AssignGen, CommentGen - # TODO take care with literal arguments + # TODO take care with literal arguments. Currently these are + # checked for and rejected by the OpenCL transformation. kobj = self._name_space_manager.create_name( root_name="kernel_obj", context="ArgSetter", label="kernel_obj") @@ -1012,6 +946,78 @@ def gen_arg_setter_code(self, parent): index += 1 arg.set_kernel_arg(sub, index, self.name) + def gen_data_on_ocl_device(self): + # Ensure the fields required by this kernel are on device. We must + # create the buffers for them if they're not. + parent.add(UseGen(parent, name="fortcl", only=True, + funcnames=["create_rw_buffer"])) + parent.add(CommentGen(parent, " Ensure field data is on device")) + for arg in self._arguments.args: + if arg.type == "field" or arg.type == "grid_property": + + if arg.type == "field": + # fields have a 'data_on_device' property for keeping + # track of whether they are on the device + condition = ".NOT. {0}%data_on_device".format(arg.name) + device_buff = "{0}%device_ptr".format(arg.name) + host_buff = "{0}%data".format(arg.name) + else: + # grid properties do not have such an attribute (because + # they are just arrays) so we check whether the device + # pointer is NULL. + device_buff = "{0}%grid%{1}_device".format(garg.name, + arg.name) + condition = device_buff + " == 0" + host_buff = "{0}%grid%{1}".format(garg.name, arg.name) + # Name of variable to hold no. of bytes of storage required + nbytes = self._name_space_manager.create_name( + root_name="size_in_bytes", context="PSyVars", + label="size_in_bytes") + # Variable to hold write event returned by OpenCL runtime + wevent = self._name_space_manager.create_name( + root_name="write_event", context="PSyVars", + label="write_event") + ifthen = IfThenGen(parent, condition) + parent.add(ifthen) + parent.add(DeclGen(parent, datatype="integer", kind="c_size_t", + entity_decls=[nbytes])) + parent.add(DeclGen(parent, datatype="integer", + kind="c_intptr_t", target=True, + entity_decls=[wevent])) + # Use c_sizeof() on first element of array to be copied over in + # order to cope with the fact that some grid properties are + # integer. + size_expr = ("int({0}%grid%nx*{0}%grid%ny, 8)*" + "c_sizeof({1}(1,1))".format(garg.name, host_buff)) + ifthen.add(AssignGen(ifthen, lhs=nbytes, rhs=size_expr)) + ifthen.add(CommentGen(ifthen, " Create buffer on device")) + # Get the name of the list of command queues (set in + # psyGen.Schedule) + qlist = self._name_space_manager.create_name( + root_name="cmd_queues", context="PSyVars", + label="cmd_queues") + flag = self._name_space_manager.create_name( + root_name="ierr", context="PSyVars", label="ierr") + + ifthen.add(AssignGen(ifthen, lhs=device_buff, + rhs="create_rw_buffer(" + nbytes + ")")) + ifthen.add( + AssignGen(ifthen, lhs=flag, + rhs="clEnqueueWriteBuffer({0}(1), {1}, CL_TRUE, " + "0_8, {2}, C_LOC({3}), 0, C_NULL_PTR, " + "C_LOC({4}))".format(qlist, device_buff, + nbytes, host_buff, wevent))) + if arg.type == "field": + ifthen.add(AssignGen( + ifthen, lhs="{0}%data_on_device".format(arg.name), + rhs=".true.")) + + # Ensure data copies have finished + parent.add(CommentGen(parent, + " Block until data copies have finished")) + parent.add(AssignGen(parent, lhs=flag, + rhs="clFinish(" + qlist + "(1))")) + class GOKernelArguments(Arguments): '''Provides information about GOcean kernel-call arguments From d70011449018eaff79bb6ab207469854ea47fb16 Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Fri, 2 Nov 2018 15:03:08 +0000 Subject: [PATCH 41/60] #216 fix errors due to break-out of gen_data_on_ocl_device() [skip ci] --- src/psyclone/gocean1p0.py | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/src/psyclone/gocean1p0.py b/src/psyclone/gocean1p0.py index 9f63f197be..586df0d565 100644 --- a/src/psyclone/gocean1p0.py +++ b/src/psyclone/gocean1p0.py @@ -844,7 +844,8 @@ def gen_ocl(self, parent): context="PSyVars", label=base) # Generate code to ensure data is on device - self.gen_data_on_ocl_device() + self.gen_data_on_ocl_device(parent) + # Then we set the kernel arguments arguments = [kernel, garg.name+"%grid%nx"] for arg in self._arguments.args: @@ -860,6 +861,13 @@ def gen_ocl(self, parent): parent.add(CallGen( parent, "{0}_set_args".format(self.name), arguments)) + # Get the name of the list of command queues (set in + # psyGen.Schedule) + qlist = self._name_space_manager.create_name( + root_name="cmd_queues", context="PSyVars", label="cmd_queues") + flag = self._name_space_manager.create_name( + root_name="ierr", context="PSyVars", label="ierr") + # Then we call clEnqueueNDRangeKernel parent.add(CommentGen(parent, " Launch the kernel")) cnull = "C_NULL_PTR" @@ -868,9 +876,8 @@ def gen_ocl(self, parent): args = ", ".join([cmd_queue, kernel, "2", cnull, "C_LOC({0})".format(glob_size), cnull, "0", cnull, cnull]) - parent.add( - AssignGen(parent, lhs=flag, - rhs="clEnqueueNDRangeKernel({0})".format(args))) + parent.add(AssignGen(parent, lhs=flag, + rhs="clEnqueueNDRangeKernel({0})".format(args))) parent.add(CommentGen(parent, "")) @property @@ -946,7 +953,16 @@ def gen_arg_setter_code(self, parent): index += 1 arg.set_kernel_arg(sub, index, self.name) - def gen_data_on_ocl_device(self): + def gen_data_on_ocl_device(self, parent): + ''' + Generate code to create data buffers on OpenCL device. + + :param parent: Parent subroutine in f2pygen AST of generated code. + :type parent: :py:class:`psyclone.f2pygen.SubroutineGen` + ''' + from psyclone.f2pygen import UseGen, CommentGen, IfThenGen, DeclGen, \ + AssignGen + grid_arg = self.find_grid_access() # Ensure the fields required by this kernel are on device. We must # create the buffers for them if they're not. parent.add(UseGen(parent, name="fortcl", only=True, @@ -965,10 +981,10 @@ def gen_data_on_ocl_device(self): # grid properties do not have such an attribute (because # they are just arrays) so we check whether the device # pointer is NULL. - device_buff = "{0}%grid%{1}_device".format(garg.name, + device_buff = "{0}%grid%{1}_device".format(grid_arg.name, arg.name) condition = device_buff + " == 0" - host_buff = "{0}%grid%{1}".format(garg.name, arg.name) + host_buff = "{0}%grid%{1}".format(grid_arg.name, arg.name) # Name of variable to hold no. of bytes of storage required nbytes = self._name_space_manager.create_name( root_name="size_in_bytes", context="PSyVars", @@ -987,8 +1003,8 @@ def gen_data_on_ocl_device(self): # Use c_sizeof() on first element of array to be copied over in # order to cope with the fact that some grid properties are # integer. - size_expr = ("int({0}%grid%nx*{0}%grid%ny, 8)*" - "c_sizeof({1}(1,1))".format(garg.name, host_buff)) + size_expr = ("int({0}%grid%nx*{0}%grid%ny, 8)*c_sizeof(" + "{1}(1,1))".format(grid_arg.name, host_buff)) ifthen.add(AssignGen(ifthen, lhs=nbytes, rhs=size_expr)) ifthen.add(CommentGen(ifthen, " Create buffer on device")) # Get the name of the list of command queues (set in From e2a7626ba6f24e2cbf086df8fe30d15123935ae7 Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Fri, 2 Nov 2018 16:35:20 +0000 Subject: [PATCH 42/60] #216 tidy opencl_test for pylint --- src/psyclone/psyGen.py | 2 +- src/psyclone/tests/gocean1p0_opencl_test.py | 44 ++++++++++----------- 2 files changed, 21 insertions(+), 25 deletions(-) diff --git a/src/psyclone/psyGen.py b/src/psyclone/psyGen.py index 5bbeed2b20..ade7dc5b68 100644 --- a/src/psyclone/psyGen.py +++ b/src/psyclone/psyGen.py @@ -416,7 +416,7 @@ def gen_ocl_init(self, parent, kernels): rhs='"{0}"'.format(kern))) ifthen.add(CommentGen(ifthen, " Create the OpenCL kernel objects. Expects " - "to find all of the compiled ")) + "to find all of the compiled")) ifthen.add(CommentGen(ifthen, " kernels in PSYCLONE_KERNELS_FILE.")) ifthen.add(CallGen(ifthen, "add_kernels", [nkernstr, "kernel_names"])) diff --git a/src/psyclone/tests/gocean1p0_opencl_test.py b/src/psyclone/tests/gocean1p0_opencl_test.py index e8f3aea1d9..71f65259fd 100644 --- a/src/psyclone/tests/gocean1p0_opencl_test.py +++ b/src/psyclone/tests/gocean1p0_opencl_test.py @@ -32,16 +32,12 @@ # POSSIBILITY OF SUCH DAMAGE. # ---------------------------------------------------------------------------- # Author A. R. Porter, STFC Daresbury Lab -from __future__ import print_function, absolute_import '''Tests for OpenCL PSy-layer code generation that are specific to the GOcean 1.0 API.''' -import os +from __future__ import print_function, absolute_import import pytest -from psyclone.parse import parse -from psyclone.psyGen import PSyFactory -from psyclone.generator import GenerationError, ParseError from psyclone.transformations import OCLTrans from psyclone_test_utils import get_invoke @@ -76,24 +72,25 @@ def test_psy_init(): otrans.apply(sched) generated_code = str(psy.gen) print(generated_code) - expected = '''\ - SUBROUTINE psy_init() - USE fortcl, ONLY: ocl_env_init, add_kernels - CHARACTER(LEN=30) kernel_names(1) - LOGICAL, save :: initialised=.False. - ! Check to make sure we only execute this routine once - IF (.not. initialised) THEN - initialised = .True. - ! Initialise the OpenCL environment/device - CALL ocl_env_init - ! The kernels this PSy layer module requires - kernel_names(1) = "compute_cu_code" - ! Create the OpenCL kernel objects. Expects to find all of the compiled - ! kernels in PSYCLONE_KERNELS_FILE. - CALL add_kernels(1, kernel_names) - END IF - END SUBROUTINE psy_init -''' + expected = ( + " SUBROUTINE psy_init()\n" + " USE fortcl, ONLY: ocl_env_init, add_kernels\n" + " CHARACTER(LEN=30) kernel_names(1)\n" + " LOGICAL, save :: initialised=.False.\n" + " ! Check to make sure we only execute this routine once\n" + " IF (.not. initialised) THEN\n" + " initialised = .True.\n" + " ! Initialise the OpenCL environment/device\n" + " CALL ocl_env_init\n" + " ! The kernels this PSy layer module requires\n" + " kernel_names(1) = \"compute_cu_code\"\n" + " ! Create the OpenCL kernel objects. Expects to find all of " + "the compiled\n" + " ! kernels in PSYCLONE_KERNELS_FILE.\n" + " CALL add_kernels(1, kernel_names)\n" + " END IF \n" + " END SUBROUTINE psy_init\n") + assert expected in generated_code @@ -101,7 +98,6 @@ def test_set_kern_args(): ''' Check that we generate the necessary code to set kernel arguments. ''' psy, _ = get_invoke("single_invoke_two_kernels.f90", API, idx=0) sched = psy.invokes.invoke_list[0].schedule - from psyclone.transformations import OCLTrans otrans = OCLTrans() otrans.apply(sched) generated_code = str(psy.gen) From d018160ca02e3a4452601074f7a91eb6e29c9234 Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Fri, 2 Nov 2018 16:35:59 +0000 Subject: [PATCH 43/60] #216 add test that OCLTrans() reject non-GOcean schedules --- .../tests/dynamo0p3_transformations_test.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/psyclone/tests/dynamo0p3_transformations_test.py b/src/psyclone/tests/dynamo0p3_transformations_test.py index 38cfc22966..7c8d207d22 100644 --- a/src/psyclone/tests/dynamo0p3_transformations_test.py +++ b/src/psyclone/tests/dynamo0p3_transformations_test.py @@ -6503,3 +6503,20 @@ def test_no_acc(): _ = acclt.apply(sched.children[0]) assert ("OpenACC loop transformations are currently only supported for " "the gocean 1.0 API" in str(err)) + + +def test_no_ocl(): + ''' Check that attempting to apply an OpenCL transformation to a Dynamo + Schedule raises the expected error. ''' + from psyclone.transformations import OCLTrans + _, info = parse(os.path.join(os.path.dirname(os.path.abspath(__file__)), + "test_files", "dynamo0p3", + "1_single_invoke.f90"), + api=TEST_API) + psy = PSyFactory(TEST_API, distributed_memory=False).create(info) + sched = psy.invokes.get('invoke_0_testkern_type').schedule + trans = OCLTrans() + with pytest.raises(TransformationError) as err: + _ = trans.apply(sched) + assert ("OpenCL generation is currently only supported for the GOcean " + "API but got a Schedule of type:" in str(err)) From d38168e394c5c4b7e732ca52bb8c8dc6e5a4c183 Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Fri, 2 Nov 2018 17:01:19 +0000 Subject: [PATCH 44/60] #216 tidy docstrings in gocean1p0 and psyGen --- src/psyclone/gocean1p0.py | 7 +++---- src/psyclone/psyGen.py | 40 +++++++++++++++++++++++++-------------- 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/src/psyclone/gocean1p0.py b/src/psyclone/gocean1p0.py index fcfb0b5cfe..19cb347ccf 100644 --- a/src/psyclone/gocean1p0.py +++ b/src/psyclone/gocean1p0.py @@ -1025,11 +1025,10 @@ def gen_arg_setter_code(self, parent): ''' from psyclone.f2pygen import SubroutineGen, UseGen, DeclGen, \ AssignGen, CommentGen - # TODO take care with literal arguments. Currently these are - # checked for and rejected by the OpenCL transformation. + # Currently literal arguments are checked for and rejected by + # the OpenCL transformation. kobj = self._name_space_manager.create_name( - root_name="kernel_obj", context="ArgSetter", - label="kernel_obj") + root_name="kernel_obj", context="ArgSetter", label="kernel_obj") arguments = [kobj, "nx"] + [arg.name for arg in self._arguments.args] sub = SubroutineGen(parent, name=self.name+"_set_args", args=arguments) diff --git a/src/psyclone/psyGen.py b/src/psyclone/psyGen.py index ade7dc5b68..a5268810f1 100644 --- a/src/psyclone/psyGen.py +++ b/src/psyclone/psyGen.py @@ -273,12 +273,10 @@ class PSy(object): function :func:`parse.parse` as its input and stores this in a way suitable for optimisation and code generation. - :param FileInfo invoke_info: An object containing the required - invocation information for code - optimisation and generation. Produced + :param FileInfo invoke_info: An object containing the required \ + invocation information for code \ + optimisation and generation. Produced \ by the function :func:`parse.parse`. - :param bool opencl: #TODO - For example: >>> import psyclone @@ -378,9 +376,18 @@ def gen_code(self, parent): def gen_ocl_init(self, parent, kernels): ''' + Generates a subroutine to initialise the OpenCL environment and + construct the list of OpenCL kernel objects used by this PSy layer. + + :param parent: the node in the f2pygen AST representing the module \ + that will contain the generated subroutine. + :type parent: :py:class:`psyclone.f2pygen.ModuleGen` + :param kernels: List of kernel names called by the PSy layer. + :type kernels: list of str ''' from psyclone.f2pygen import SubroutineGen, DeclGen, AssignGen, \ CallGen, UseGen, CommentGen, CharDeclGen, IfThenGen + sub = SubroutineGen(parent, "psy_init") parent.add(sub) sub.add(UseGen(sub, name="fortcl", only=True, @@ -1509,7 +1516,10 @@ def opencl(self): @opencl.setter def opencl(self, value): ''' - :param bool value: whether or not to generate OpenCL for this schedule + Setter for whether or not to generate the OpenCL version of this + schedule. + + :param bool value: whether or not to generate OpenCL. ''' if not isinstance(value, bool): raise ValueError("Schedule.opencl must be a bool but got {0}". @@ -3085,8 +3095,8 @@ def gen_arg_setter_code(self, parent): Creates a Fortran routine to set the arguments of the OpenCL version of this kernel. - :param parent: Parent node of the set-kernel-arguments routine - :type parent: :py:class:`psyclone.f2pygen.moduleGen` + :param parent: Parent node of the set-kernel-arguments routine. + :type parent: :py:class:`psyclone.f2pygen.ModuleGen` ''' raise NotImplementedError("gen_arg_setter_code must be implemented " "by sub-class.") @@ -3095,11 +3105,11 @@ def incremented_arg(self, mapping={}): ''' Returns the argument that has INC access. Raises a FieldNotFoundError if none is found. - :param mapping: dictionary of access types (here INC) associated + :param mapping: dictionary of access types (here INC) associated \ with arguments with their metadata strings as keys :type mapping: dict - :return: a Fortran argument name - :rtype: string + :return: a Fortran argument name. + :rtype: str :raises FieldNotFoundError: if none is found. ''' @@ -3362,18 +3372,20 @@ def set_kernel_arg(self, parent, index, kname): Generate the code to set this argument for an OpenCL kernel. :param parent: the node in the Schedule to which to add the code. - :param int index: the (zero-based) index of this argument in the + :type parent: :py:class:`psyclone.f2pygen.SubroutineGen` + :param int index: the (zero-based) index of this argument in the \ list of kernel arguments. + :param str kname: the name of the OpenCL kernel. ''' from psyclone.f2pygen import AssignGen, CallGen + # TODO fix hardwired names of "ierr" and "kernel_obj" parent.add(AssignGen( parent, lhs="ierr", rhs="clSetKernelArg({0}, {1}, C_SIZEOF({2}), C_LOC({2}))". format("kernel_obj", index, self.name))) parent.add(CallGen( parent, "check_status", - ["'clSetKernelArg: arg {0} of {1}'".format(index, - kname), + ["'clSetKernelArg: arg {0} of {1}'".format(index, kname), "ierr"])) def backward_dependence(self): From 9887fed2b3420ec0b7750963b9ff970abb1679d0 Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Fri, 21 Dec 2018 09:51:09 +0000 Subject: [PATCH 45/60] #216 add OpenCL eg [skip ci] --- examples/gocean/eg3/README | 59 ++++++++++++ examples/gocean/eg3/alg.f90 | 47 ++++++++++ examples/gocean/eg3/compute_cu_mod.f90 | 120 +++++++++++++++++++++++++ examples/gocean/eg3/compute_cv_mod.f90 | 116 ++++++++++++++++++++++++ examples/gocean/eg3/compute_h_mod.f90 | 117 ++++++++++++++++++++++++ examples/gocean/eg3/compute_z_mod.f90 | 101 +++++++++++++++++++++ examples/gocean/eg3/ocl_trans.py | 61 +++++++++++++ 7 files changed, 621 insertions(+) create mode 100644 examples/gocean/eg3/README create mode 100644 examples/gocean/eg3/alg.f90 create mode 100644 examples/gocean/eg3/compute_cu_mod.f90 create mode 100644 examples/gocean/eg3/compute_cv_mod.f90 create mode 100644 examples/gocean/eg3/compute_h_mod.f90 create mode 100644 examples/gocean/eg3/compute_z_mod.f90 create mode 100644 examples/gocean/eg3/ocl_trans.py diff --git a/examples/gocean/eg3/README b/examples/gocean/eg3/README new file mode 100644 index 0000000000..198eaa0b72 --- /dev/null +++ b/examples/gocean/eg3/README @@ -0,0 +1,59 @@ +# ----------------------------------------------------------------------------- +# BSD 3-Clause License +# +# Copyright (c) 2018, Science and Technology Facilities Council +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# * Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +#------------------------------------------------------------------------------ +# Author A. R. Porter, STFC Daresbury Lab + +The directory containing this file contains an example of the use of +PSyclone to generate OpenCL driver code with the GOcean 1.0 API. + +In order to use PSyclone you must first install it, ideally with pip. +See ../../../README.md for more details. + +PSyclone can be run in the directory containing this file by +executing, e.g.:: + + psyclone -api "gocean1.0" alg.f90 + +This will generate 'vanilla' PSy-layer code which is output to stdout. + +In order to generate an OpenCL PSy layer instead, PSyclone must be +provided with a transformation script:: + + psyclone -api "gocean1.0" -s ./ocl_trans.py alg.f90 + +where ocl_trans.py simply applies the psyclone.transformations.OCLTrans +transformation to the Schedule of the Invoke. + +Currently the (Fortran) kernels called by the Invoke must be manually +translated into OpenCL. This step will be automated in a future +release of PSyclone. diff --git a/examples/gocean/eg3/alg.f90 b/examples/gocean/eg3/alg.f90 new file mode 100644 index 0000000000..b60ae5839a --- /dev/null +++ b/examples/gocean/eg3/alg.f90 @@ -0,0 +1,47 @@ +program simple + + use grid_mod + use field_mod + use compute_cu_mod, only: compute_cu + use compute_cv_mod, only: compute_cv + use compute_z_mod, only: compute_z + use compute_h_mod, only: compute_h + implicit none + + type(grid_type), target :: model_grid + + type(r2d_field) :: p_fld + type(r2d_field) :: u_fld, v_fld + !> Mass flux in x and y directions + type(r2d_field) :: cu_fld, cv_fld + !> Potential vorticity + type(r2d_field) :: z_fld + !> Surface height + type(r2d_field) :: h_fld + + !> Loop counter for time-stepping loop + INTEGER :: ncycle + + model_grid = grid_type(ARAKAWA_C, & + (/BC_PERIODIC,BC_PERIODIC,BC_NONE/), & + OFFSET_SW) + + ! Create fields on this grid + p_fld = r2d_field(model_grid, T_POINTS) + u_fld = r2d_field(model_grid, U_POINTS) + v_fld = r2d_field(model_grid, V_POINTS) + cu_fld = r2d_field(model_grid, U_POINTS) + cv_fld = r2d_field(model_grid, V_POINTS) + z_fld = r2d_field(model_grid, F_POINTS) + h_fld = r2d_field(model_grid, T_POINTS) + + DO ncycle=1,itmax + + call invoke( compute_cu(CU_fld, p_fld, u_fld), & + compute_cv(CV_fld, p_fld, v_fld), & + compute_z(z_fld, p_fld, u_fld, v_fld), & + compute_h(h_fld, p_fld, u_fld, v_fld) ) + + end do + +end program simple diff --git a/examples/gocean/eg3/compute_cu_mod.f90 b/examples/gocean/eg3/compute_cu_mod.f90 new file mode 100644 index 0000000000..9078a98e72 --- /dev/null +++ b/examples/gocean/eg3/compute_cu_mod.f90 @@ -0,0 +1,120 @@ +!> \brief Compute the mass flux in the x direction, cu +!! \detail Given the current pressure and velocity fields, +!! computes the mass flux in the x direction. +module compute_cu_mod + use kind_params_mod + use kernel_mod + use argument_mod + use field_mod + use grid_mod + implicit none + + private + + public invoke_compute_cu + public compute_cu, compute_cu_code + + type, extends(kernel_type) :: compute_cu + type(arg), dimension(3) :: meta_args = & + (/ go_arg(GO_WRITE, GO_CU, GO_POINTWISE), & ! cu + go_arg(GO_READ, GO_CT, GO_POINTWISE), & ! p + go_arg(GO_READ, GO_CU, GO_POINTWISE) & ! u + /) + !> This kernel writes only to internal points of the + !! simulation domain. + integer :: ITERATES_OVER = GO_INTERNAL_PTS + + !> Although the staggering of variables used in an Arakawa + !! C grid is well defined, the way in which they are indexed is + !! an implementation choice. This can be thought of as choosing + !! which grid-point types have the same (i,j) index as a T + !! point. This kernel assumes that the U,V and F points that + !! share the same index as a given T point are those immediately + !! to the South and West of it. + integer :: index_offset = GO_OFFSET_SW + + contains + procedure, nopass :: code => compute_cu_code + end type compute_cu + +contains + + !=================================================== + + !> Manual implementation of the code needed to invoke + !! compute_cu_code(). + subroutine invoke_compute_cu(cufld, pfld, ufld) + implicit none + type(r2d_field), intent(inout) :: cufld + type(r2d_field), intent(in) :: pfld, ufld + ! Locals + integer :: I, J + + ! Note that we do not loop over the full extent of the field. + ! Fields are allocated with extents (M+1,N+1). + ! Presumably the extra row and column are needed for periodic BCs. + ! We are updating a quantity on CU. + ! This loop writes to cu(2:M+1,1:N) so this looks like + ! (using x to indicate a location that is written): + ! + ! i=1 i=M + ! o o o o + ! o x x x j=N + ! o x x x + ! o x x x j=1 + + ! Quantity CU is mass flux in x direction. + + ! Original code looked like: + ! + ! DO J=1,N + ! DO I=1,M + ! CU(I+1,J) = .5*(P(I+1,J)+P(I,J))*U(I+1,J) + ! END DO + ! END DO + + ! cu(i,j) depends upon: + ! p(i-1,j), p(i,j) : CT + ! => lateral CT neighbours of the CU pt being updated + ! u(i,j) : CU + ! => the horiz. vel. component at the CU pt being updated + + ! vi-1j+1--fij+1---vij+1---fi+1j+1 + ! | | | | + ! | | | | + ! Ti-1j----uij-----Tij-----ui+1j + ! | | | | + ! | | | | + ! vi-1j----fij-----vij-----fi+1j + ! | | | | + ! | | | | + ! Ti-1j-1--uij-1---Tij-1---ui+1j-1 + ! + + do J=cufld%internal%ystart, cufld%internal%ystop + do I=cufld%internal%xstart, cufld%internal%xstop + + call compute_cu_code(i, j, cufld%data, pfld%data, ufld%data) + end do + end do + + end subroutine invoke_compute_cu + + !=================================================== + + !> Compute the mass flux in the x direction at point (i,j) + subroutine compute_cu_code(i, j, cu, p, u) + implicit none + integer, intent(in) :: I, J + real(wp), intent(out), dimension(:,:) :: cu + real(wp), intent(in), dimension(:,:) :: p, u + + + CU(I,J) = 0.5d0*(P(i,J)+P(I-1,J))*U(I,J) + + !write (*,"('CU calc: ',I3,1x,I3,3(1x,E24.16))") & + ! i, j, p(i,j), p(i-1,j), u(i,j) + + end subroutine compute_cu_code + +end module compute_cu_mod diff --git a/examples/gocean/eg3/compute_cv_mod.f90 b/examples/gocean/eg3/compute_cv_mod.f90 new file mode 100644 index 0000000000..abd2ac9cb8 --- /dev/null +++ b/examples/gocean/eg3/compute_cv_mod.f90 @@ -0,0 +1,116 @@ +!> \brief Compute the mass flux in the y direction, cv +!! \detail Given the current pressure and velocity fields, +!! computes the mass flux in the y direction. +module compute_cv_mod + use kind_params_mod + use kernel_mod + use argument_mod + use grid_mod + use field_mod + implicit none + + private + + public invoke_compute_cv + public compute_cv, compute_cv_code + + type, extends(kernel_type) :: compute_cv + type(go_arg), dimension(3) :: meta_args = & + (/ go_arg(GO_WRITE, GO_CV, GO_POINTWISE), & ! cv + go_arg(GO_READ, GO_CT, GO_POINTWISE), & ! p + go_arg(GO_READ, GO_CV, GO_POINTWISE) & ! v + /) + !> This kernel writes only to internal points of the + !! simulation domain. + integer :: ITERATES_OVER = GO_INTERNAL_PTS + + !> Although the staggering of variables used in an Arakawa + !! C grid is well defined, the way in which they are indexed is + !! an implementation choice. This can be thought of as choosing + !! which grid-point types have the same (i,j) index as a T + !! point. This kernel assumes that the U,V and F points that + !! share the same index as a given T point are those immediately + !! to the South and West of it. + integer :: index_offset = GO_OFFSET_SW + + contains + procedure, nopass :: code => compute_cv_code + end type compute_cv + +contains + + !=================================================== + + !> Manual implementation of the code needed to invoke + !! compute_cv_code(). + subroutine invoke_compute_cv(cvfld, pfld, vfld) + implicit none + type(r2d_field), intent(inout) :: cvfld + type(r2d_field), intent(in) :: pfld, vfld + ! Locals + integer :: I, J + + ! Note that we do not loop over the full extent of the field. + ! Fields are allocated with extents (M+1,N+1). + ! Presumably the extra row and column are needed for periodic BCs. + ! We are updating a quantity on CV. + ! This loop writes to cv(1:M,2:N+1) so this looks like + ! (using x to indicate a location that is written): + ! + ! i=1 i=M + ! x x x o + ! x x x o j=N + ! x x x o + ! o o o o j=1 + + ! Quantity CV is mass flux in y direction. + + ! Original code looked like: + ! + ! DO J=1,N + ! DO I=1,M + ! CV(I,J+1) = .5*(P(I,J+1)+P(I,J))*V(I,J+1) + ! END DO + ! END DO + + ! cv(i,j) depends upon: + ! p(i,j-1), p(i,j) : CT + ! => vertical CT neighbours of the CV pt being updated + ! v(i,j) : CV + ! => the velocity component at the CV pt being updated + + ! vi-1j+1--fij+1---vij+1---fi+1j+1 + ! | | | | + ! | | | | + ! Ti-1j----uij-----Tij-----ui+1j + ! | | | | + ! | | | | + ! vi-1j----fij-----vij-----fi+1j + ! | | | | + ! | | | | + ! Ti-1j-1--uij-1---Tij-1---ui+1j-1 + ! + + do J=cvfld%internal%ystart, cvfld%internal%ystop + do I=cvfld%internal%xstart, cvfld%internal%xstop + + call compute_cv_code(i, j, cvfld%data, pfld%data, vfld%data) + end do + end do + + end subroutine invoke_compute_cv + + !=================================================== + + !> Compute the mass flux in the y direction at point (i,j) + subroutine compute_cv_code(i, j, cv, p, v) + implicit none + integer, intent(in) :: I, J + real(go_wp), intent(out), dimension(:,:) :: cv + real(go_wp), intent(in), dimension(:,:) :: p, v + + CV(I,J) = .5d0*(P(I,J)+P(I,J-1))*V(I,J) + + end subroutine compute_cv_code + +end module compute_cv_mod diff --git a/examples/gocean/eg3/compute_h_mod.f90 b/examples/gocean/eg3/compute_h_mod.f90 new file mode 100644 index 0000000000..2a0c74c6ce --- /dev/null +++ b/examples/gocean/eg3/compute_h_mod.f90 @@ -0,0 +1,117 @@ +module compute_h_mod + use kind_params_mod + use kernel_mod + use argument_mod + use grid_mod + use field_mod + implicit none + + private + + public invoke_compute_h + public compute_h, compute_h_code + + type, extends(kernel_type) :: compute_h + type(go_arg), dimension(4) :: meta_args = & + (/ go_arg(GO_WRITE, GO_CT, GO_POINTWISE), & ! h + go_arg(GO_READ, GO_CT, GO_POINTWISE), & ! p + go_arg(GO_READ, GO_CU, GO_POINTWISE), & ! u + go_arg(GO_READ, GO_CV, GO_POINTWISE) & ! v + /) + !> This kernel writes only to internal points of the + !! simulation domain. + integer :: ITERATES_OVER = GO_INTERNAL_PTS + + !> Although the staggering of variables used in an Arakawa + !! C grid is well defined, the way in which they are indexed is + !! an implementation choice. This can be thought of as choosing + !! which grid-point types have the same (i,j) index as a T + !! point. This kernel assumes that the U,V and F points that + !! share the same index as a given T point are those immediately + !! to the South and West of it. + integer :: index_offset = GO_OFFSET_SW + + contains + procedure, nopass :: code => compute_h_code + end type compute_h + +contains + + !=================================================== + + subroutine invoke_compute_h(hfld, pfld, ufld, vfld) + implicit none + type(r2d_field), intent(inout) :: hfld + type(r2d_field), intent(in) :: pfld, ufld,vfld + ! Locals + integer :: I, J + + ! Note that we do not loop over the full extent of the field. + ! Fields are allocated with extents (M+1,N+1). + ! Presumably the extra row and column are needed for periodic BCs. + ! We are updating a quantity on CT. + ! This loop writes to h(1:M,1:N) so this looks like + ! (using x to indicate a location that is written): + ! + ! i=1 i=M + ! o o o o + ! x x x o j=N + ! x x x o + ! x x x o j=1 + + ! Quantity H is defined as: + ! H = P + 0.5(_x + _y) + ! where _x indicates average over field d in x direction. + + ! Original code looked like: + ! + ! DO J=1,N + ! DO I=1,M + ! H(I,J) = P(I,J)+.25*(U(I+1,J)*U(I+1,J)+U(I,J)*U(I,J) & + ! +V(I,J+1)*V(I,J+1)+V(I,J)*V(I,J)) + ! END DO + ! END DO + + ! h(i,j) depends upon: + ! p(i,j) : CT + ! u(i,j), u(i+1,j) : CU + ! => lateral CU neighbours of the CT pt being updated + ! v(i,j), v(i,j+1) : CV + ! => vertical CV neighbours of the CT pt being updated + + ! x-------vij+1---fi+1j+1 + ! | | | + ! | | | + ! uij-----Tij-----ui+1j + ! | | | + ! | | | + ! fij-----vij-----fi+1j + ! | | | + ! | | | + ! uij-1- -Tij-1---ui+1j-1 + ! + + DO J=hfld%internal%ystart, hfld%internal%ystop, 1 + DO I=hfld%internal%xstart, hfld%internal%xstop, 1 + + CALL compute_h_code(i, j, hfld%data, & + pfld%data, ufld%data, vfld%data) + END DO + END DO + + end subroutine invoke_compute_h + + !=================================================== + + SUBROUTINE compute_h_code(i, j, h, p, u, v) + IMPLICIT none + integer, intent(in) :: I, J + REAL(wp), INTENT(out), DIMENSION(:,:) :: h + REAL(wp), INTENT(in), DIMENSION(:,:) :: p, u, v + + H(I,J) = P(I,J)+.25d0*(U(I+1,J)*U(I+1,J)+U(I,J)*U(I,J) + & + V(I,J+1)*V(I,J+1)+V(I,J)*V(I,J)) + + END SUBROUTINE compute_h_code + +END MODULE compute_h_mod diff --git a/examples/gocean/eg3/compute_z_mod.f90 b/examples/gocean/eg3/compute_z_mod.f90 new file mode 100644 index 0000000000..4cd72e319a --- /dev/null +++ b/examples/gocean/eg3/compute_z_mod.f90 @@ -0,0 +1,101 @@ +!> \brief Compute the potential vorticity, z +!! \detail Given the current pressure and velocity fields, +!! computes the potential voriticity. +module compute_z_mod + use kind_params_mod + use kernel_mod + use argument_mod + use grid_mod + use field_mod + implicit none + + private + + public invoke_compute_z + public compute_z, compute_z_code + + type, extends(kernel_type) :: compute_z + type(go_arg), dimension(6) :: meta_args = & + (/ go_arg(GO_WRITE, GO_CF, GO_POINTWISE), & ! z + go_arg(GO_READ, GO_CT, GO_POINTWISE), & ! p + go_arg(GO_READ, GO_CU, GO_POINTWISE), & ! u + go_arg(GO_READ, GO_CV, GO_POINTWISE), & ! v + go_arg(GO_READ, GO_GRID_DX_CONST), & ! dx + go_arg(GO_READ, GO_GRID_DY_CONST) & ! dy + /) + !> This kernel operates on fields that live on an + !! orthogonal, regular grid. + integer :: GRID_TYPE = GO_ORTHOGONAL_REGULAR + + !> This kernel writes only to internal points of the + !! simulation domain. + integer :: ITERATES_OVER = GO_INTERNAL_PTS + + !> Although the staggering of variables used in an Arakawa + !! C grid is well defined, the way in which they are indexed is + !! an implementation choice. This can be thought of as choosing + !! which grid-point types have the same (i,j) index as a T + !! point. This kernel assumes that the U,V and F points that + !! share the same index as a given T point are those immediately + !! to the South and West of it. + integer :: index_offset = GO_OFFSET_SW + + contains + procedure, nopass :: code => compute_z_code + end type compute_z + +contains + + !=================================================== + + !> Manual implementation of the code needed to invoke + !! compute_z_code(). + subroutine invoke_compute_z(zfld, pfld, ufld, vfld) + implicit none + type(r2d_field), intent(inout) :: zfld + type(r2d_field), intent(in) :: pfld, ufld, vfld + ! Locals + integer :: I, J + real(go_wp) :: dx, dy + + dx = zfld%grid%dx + dy = zfld%grid%dy + + do J=zfld%internal%ystart, zfld%internal%ystop, 1 + do I=zfld%internal%xstart, zfld%internal%xstop, 1 + + call compute_z_code(i, j, & + zfld%data, & + pfld%data, & + ufld%data, & + vfld%data, & + dx, dy) + + end do + end do + + end subroutine invoke_compute_z + + !=================================================== + + !> Compute the potential vorticity on the grid point (i,j) + subroutine compute_z_code(i, j, z, p, u, v, dx, dy) + implicit none + integer, intent(in) :: I, J + real(go_wp), intent(in) :: dx, dy + real(go_wp), intent(inout), dimension(:,:) :: z + real(go_wp), intent(in), dimension(:,:) :: p, u, v + + ! Original code looked like: + ! DO J=1,N + ! DO I=1,M + ! Z(I+1,J+1) =(FSDX*(V(I+1,J+1)-V(I,J+1))-FSDY*(U(I+1,J+1) & + ! -U(I+1,J)))/(P(I,J)+P(I+1,J)+P(I+1,J+1)+P(I,J+1)) + + Z(I,J) =( (4.0d0/dx)*( V(I,J)-V(I-1,J))- & + (4.0d0/dy)*( U(I,J)-U(I,J-1)) ) / & + (P(I-1,J-1)+P(I,J-1)+ P(I,J)+P(I-1,J)) + + end subroutine compute_z_code + +end module compute_z_mod diff --git a/examples/gocean/eg3/ocl_trans.py b/examples/gocean/eg3/ocl_trans.py new file mode 100644 index 0000000000..395b9639e6 --- /dev/null +++ b/examples/gocean/eg3/ocl_trans.py @@ -0,0 +1,61 @@ +# ----------------------------------------------------------------------------- +# BSD 3-Clause License +# +# Copyright (c) 2018, Science and Technology Facilities Council +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# * Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# ----------------------------------------------------------------------------- +# Author: A. R. Porter, STFC Daresbury Lab + +''' Module providing a transformation script that converts the Schedule of + the first Invoke to use OpenCL. ''' + + +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 + + # Get the Schedule associated with the first Invoke + invoke = psy.invokes.invoke_list[0] + sched = invoke.schedule + + # Transform the Schedule + cltrans = OCLTrans() + cltrans.apply(sched) + + return psy From 13b25cb3288eab7624b537ca5de72cac4d106a55 Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Fri, 21 Dec 2018 10:00:59 +0000 Subject: [PATCH 46/60] #216 update copyright info in fortran files [skip ci] --- examples/gocean/eg3/alg.f90 | 43 ++++++++++-- examples/gocean/eg3/compute_cu_mod.f90 | 96 +++++++++---------------- examples/gocean/eg3/compute_cv_mod.f90 | 97 +++++++++----------------- examples/gocean/eg3/compute_h_mod.f90 | 97 +++++++++----------------- examples/gocean/eg3/compute_z_mod.f90 | 63 +++++++++-------- 5 files changed, 169 insertions(+), 227 deletions(-) diff --git a/examples/gocean/eg3/alg.f90 b/examples/gocean/eg3/alg.f90 index b60ae5839a..80810c8e22 100644 --- a/examples/gocean/eg3/alg.f90 +++ b/examples/gocean/eg3/alg.f90 @@ -1,3 +1,38 @@ +! ----------------------------------------------------------------------------- +! BSD 3-Clause License +! +! Copyright (c) 2018, Science and Technology Facilities Council. +! All rights reserved. +! +! Redistribution and use in source and binary forms, with or without +! modification, are permitted provided that the following conditions are met: +! +! * Redistributions of source code must retain the above copyright notice, this +! list of conditions and the following disclaimer. +! +! * Redistributions in binary form must reproduce the above copyright notice, +! this list of conditions and the following disclaimer in the documentation +! and/or other materials provided with the distribution. +! +! * Neither the name of the copyright holder nor the names of its +! contributors may be used to endorse or promote products derived from +! this software without specific prior written permission. +! +! THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +! AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +! IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +! DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +! FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +! DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +! SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +! CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +! OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +! OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +! ----------------------------------------------------------------------------- +! Author: A. R. Porter, STFC Daresbury Lab. + +! A simple, single Invoke example to demonstrate the generation of an +! OpenCL driver PSy layer. program simple use grid_mod @@ -12,15 +47,11 @@ program simple type(r2d_field) :: p_fld type(r2d_field) :: u_fld, v_fld - !> Mass flux in x and y directions type(r2d_field) :: cu_fld, cv_fld - !> Potential vorticity type(r2d_field) :: z_fld - !> Surface height type(r2d_field) :: h_fld - !> Loop counter for time-stepping loop - INTEGER :: ncycle + integer :: ncycle model_grid = grid_type(ARAKAWA_C, & (/BC_PERIODIC,BC_PERIODIC,BC_NONE/), & @@ -35,7 +66,7 @@ program simple z_fld = r2d_field(model_grid, F_POINTS) h_fld = r2d_field(model_grid, T_POINTS) - DO ncycle=1,itmax + do ncycle=1,itmax call invoke( compute_cu(CU_fld, p_fld, u_fld), & compute_cv(CV_fld, p_fld, v_fld), & diff --git a/examples/gocean/eg3/compute_cu_mod.f90 b/examples/gocean/eg3/compute_cu_mod.f90 index 9078a98e72..bbf35cde3e 100644 --- a/examples/gocean/eg3/compute_cu_mod.f90 +++ b/examples/gocean/eg3/compute_cu_mod.f90 @@ -1,3 +1,36 @@ +! ----------------------------------------------------------------------------- +! BSD 3-Clause License +! +! Copyright (c) 2018, Science and Technology Facilities Council. +! All rights reserved. +! +! Redistribution and use in source and binary forms, with or without +! modification, are permitted provided that the following conditions are met: +! +! * Redistributions of source code must retain the above copyright notice, this +! list of conditions and the following disclaimer. +! +! * Redistributions in binary form must reproduce the above copyright notice, +! this list of conditions and the following disclaimer in the documentation +! and/or other materials provided with the distribution. +! +! * Neither the name of the copyright holder nor the names of its +! contributors may be used to endorse or promote products derived from +! this software without specific prior written permission. +! +! THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +! AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +! IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +! DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +! FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +! DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +! SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +! CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +! OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +! OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +! ----------------------------------------------------------------------------- +! Author: A. R. Porter, STFC Daresbury Lab. + !> \brief Compute the mass flux in the x direction, cu !! \detail Given the current pressure and velocity fields, !! computes the mass flux in the x direction. @@ -39,69 +72,6 @@ module compute_cu_mod contains - !=================================================== - - !> Manual implementation of the code needed to invoke - !! compute_cu_code(). - subroutine invoke_compute_cu(cufld, pfld, ufld) - implicit none - type(r2d_field), intent(inout) :: cufld - type(r2d_field), intent(in) :: pfld, ufld - ! Locals - integer :: I, J - - ! Note that we do not loop over the full extent of the field. - ! Fields are allocated with extents (M+1,N+1). - ! Presumably the extra row and column are needed for periodic BCs. - ! We are updating a quantity on CU. - ! This loop writes to cu(2:M+1,1:N) so this looks like - ! (using x to indicate a location that is written): - ! - ! i=1 i=M - ! o o o o - ! o x x x j=N - ! o x x x - ! o x x x j=1 - - ! Quantity CU is mass flux in x direction. - - ! Original code looked like: - ! - ! DO J=1,N - ! DO I=1,M - ! CU(I+1,J) = .5*(P(I+1,J)+P(I,J))*U(I+1,J) - ! END DO - ! END DO - - ! cu(i,j) depends upon: - ! p(i-1,j), p(i,j) : CT - ! => lateral CT neighbours of the CU pt being updated - ! u(i,j) : CU - ! => the horiz. vel. component at the CU pt being updated - - ! vi-1j+1--fij+1---vij+1---fi+1j+1 - ! | | | | - ! | | | | - ! Ti-1j----uij-----Tij-----ui+1j - ! | | | | - ! | | | | - ! vi-1j----fij-----vij-----fi+1j - ! | | | | - ! | | | | - ! Ti-1j-1--uij-1---Tij-1---ui+1j-1 - ! - - do J=cufld%internal%ystart, cufld%internal%ystop - do I=cufld%internal%xstart, cufld%internal%xstop - - call compute_cu_code(i, j, cufld%data, pfld%data, ufld%data) - end do - end do - - end subroutine invoke_compute_cu - - !=================================================== - !> Compute the mass flux in the x direction at point (i,j) subroutine compute_cu_code(i, j, cu, p, u) implicit none diff --git a/examples/gocean/eg3/compute_cv_mod.f90 b/examples/gocean/eg3/compute_cv_mod.f90 index abd2ac9cb8..b43972a14e 100644 --- a/examples/gocean/eg3/compute_cv_mod.f90 +++ b/examples/gocean/eg3/compute_cv_mod.f90 @@ -1,3 +1,36 @@ +! ----------------------------------------------------------------------------- +! BSD 3-Clause License +! +! Copyright (c) 2018, Science and Technology Facilities Council. +! All rights reserved. +! +! Redistribution and use in source and binary forms, with or without +! modification, are permitted provided that the following conditions are met: +! +! * Redistributions of source code must retain the above copyright notice, this +! list of conditions and the following disclaimer. +! +! * Redistributions in binary form must reproduce the above copyright notice, +! this list of conditions and the following disclaimer in the documentation +! and/or other materials provided with the distribution. +! +! * Neither the name of the copyright holder nor the names of its +! contributors may be used to endorse or promote products derived from +! this software without specific prior written permission. +! +! THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +! AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +! IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +! DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +! FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +! DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +! SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +! CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +! OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +! OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +! ----------------------------------------------------------------------------- +! Author: A. R. Porter, STFC Daresbury Lab. + !> \brief Compute the mass flux in the y direction, cv !! \detail Given the current pressure and velocity fields, !! computes the mass flux in the y direction. @@ -11,7 +44,6 @@ module compute_cv_mod private - public invoke_compute_cv public compute_cv, compute_cv_code type, extends(kernel_type) :: compute_cv @@ -39,69 +71,6 @@ module compute_cv_mod contains - !=================================================== - - !> Manual implementation of the code needed to invoke - !! compute_cv_code(). - subroutine invoke_compute_cv(cvfld, pfld, vfld) - implicit none - type(r2d_field), intent(inout) :: cvfld - type(r2d_field), intent(in) :: pfld, vfld - ! Locals - integer :: I, J - - ! Note that we do not loop over the full extent of the field. - ! Fields are allocated with extents (M+1,N+1). - ! Presumably the extra row and column are needed for periodic BCs. - ! We are updating a quantity on CV. - ! This loop writes to cv(1:M,2:N+1) so this looks like - ! (using x to indicate a location that is written): - ! - ! i=1 i=M - ! x x x o - ! x x x o j=N - ! x x x o - ! o o o o j=1 - - ! Quantity CV is mass flux in y direction. - - ! Original code looked like: - ! - ! DO J=1,N - ! DO I=1,M - ! CV(I,J+1) = .5*(P(I,J+1)+P(I,J))*V(I,J+1) - ! END DO - ! END DO - - ! cv(i,j) depends upon: - ! p(i,j-1), p(i,j) : CT - ! => vertical CT neighbours of the CV pt being updated - ! v(i,j) : CV - ! => the velocity component at the CV pt being updated - - ! vi-1j+1--fij+1---vij+1---fi+1j+1 - ! | | | | - ! | | | | - ! Ti-1j----uij-----Tij-----ui+1j - ! | | | | - ! | | | | - ! vi-1j----fij-----vij-----fi+1j - ! | | | | - ! | | | | - ! Ti-1j-1--uij-1---Tij-1---ui+1j-1 - ! - - do J=cvfld%internal%ystart, cvfld%internal%ystop - do I=cvfld%internal%xstart, cvfld%internal%xstop - - call compute_cv_code(i, j, cvfld%data, pfld%data, vfld%data) - end do - end do - - end subroutine invoke_compute_cv - - !=================================================== - !> Compute the mass flux in the y direction at point (i,j) subroutine compute_cv_code(i, j, cv, p, v) implicit none diff --git a/examples/gocean/eg3/compute_h_mod.f90 b/examples/gocean/eg3/compute_h_mod.f90 index 2a0c74c6ce..eafb1356ca 100644 --- a/examples/gocean/eg3/compute_h_mod.f90 +++ b/examples/gocean/eg3/compute_h_mod.f90 @@ -1,3 +1,36 @@ +! ----------------------------------------------------------------------------- +! BSD 3-Clause License +! +! Copyright (c) 2018, Science and Technology Facilities Council. +! All rights reserved. +! +! Redistribution and use in source and binary forms, with or without +! modification, are permitted provided that the following conditions are met: +! +! * Redistributions of source code must retain the above copyright notice, this +! list of conditions and the following disclaimer. +! +! * Redistributions in binary form must reproduce the above copyright notice, +! this list of conditions and the following disclaimer in the documentation +! and/or other materials provided with the distribution. +! +! * Neither the name of the copyright holder nor the names of its +! contributors may be used to endorse or promote products derived from +! this software without specific prior written permission. +! +! THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +! AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +! IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +! DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +! FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +! DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +! SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +! CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +! OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +! OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +! ----------------------------------------------------------------------------- +! Author: A. R. Porter, STFC Daresbury Lab. + module compute_h_mod use kind_params_mod use kernel_mod @@ -39,70 +72,6 @@ module compute_h_mod !=================================================== - subroutine invoke_compute_h(hfld, pfld, ufld, vfld) - implicit none - type(r2d_field), intent(inout) :: hfld - type(r2d_field), intent(in) :: pfld, ufld,vfld - ! Locals - integer :: I, J - - ! Note that we do not loop over the full extent of the field. - ! Fields are allocated with extents (M+1,N+1). - ! Presumably the extra row and column are needed for periodic BCs. - ! We are updating a quantity on CT. - ! This loop writes to h(1:M,1:N) so this looks like - ! (using x to indicate a location that is written): - ! - ! i=1 i=M - ! o o o o - ! x x x o j=N - ! x x x o - ! x x x o j=1 - - ! Quantity H is defined as: - ! H = P + 0.5(_x + _y) - ! where _x indicates average over field d in x direction. - - ! Original code looked like: - ! - ! DO J=1,N - ! DO I=1,M - ! H(I,J) = P(I,J)+.25*(U(I+1,J)*U(I+1,J)+U(I,J)*U(I,J) & - ! +V(I,J+1)*V(I,J+1)+V(I,J)*V(I,J)) - ! END DO - ! END DO - - ! h(i,j) depends upon: - ! p(i,j) : CT - ! u(i,j), u(i+1,j) : CU - ! => lateral CU neighbours of the CT pt being updated - ! v(i,j), v(i,j+1) : CV - ! => vertical CV neighbours of the CT pt being updated - - ! x-------vij+1---fi+1j+1 - ! | | | - ! | | | - ! uij-----Tij-----ui+1j - ! | | | - ! | | | - ! fij-----vij-----fi+1j - ! | | | - ! | | | - ! uij-1- -Tij-1---ui+1j-1 - ! - - DO J=hfld%internal%ystart, hfld%internal%ystop, 1 - DO I=hfld%internal%xstart, hfld%internal%xstop, 1 - - CALL compute_h_code(i, j, hfld%data, & - pfld%data, ufld%data, vfld%data) - END DO - END DO - - end subroutine invoke_compute_h - - !=================================================== - SUBROUTINE compute_h_code(i, j, h, p, u, v) IMPLICIT none integer, intent(in) :: I, J diff --git a/examples/gocean/eg3/compute_z_mod.f90 b/examples/gocean/eg3/compute_z_mod.f90 index 4cd72e319a..f46e952522 100644 --- a/examples/gocean/eg3/compute_z_mod.f90 +++ b/examples/gocean/eg3/compute_z_mod.f90 @@ -1,3 +1,36 @@ +! ----------------------------------------------------------------------------- +! BSD 3-Clause License +! +! Copyright (c) 2018, Science and Technology Facilities Council. +! All rights reserved. +! +! Redistribution and use in source and binary forms, with or without +! modification, are permitted provided that the following conditions are met: +! +! * Redistributions of source code must retain the above copyright notice, this +! list of conditions and the following disclaimer. +! +! * Redistributions in binary form must reproduce the above copyright notice, +! this list of conditions and the following disclaimer in the documentation +! and/or other materials provided with the distribution. +! +! * Neither the name of the copyright holder nor the names of its +! contributors may be used to endorse or promote products derived from +! this software without specific prior written permission. +! +! THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +! AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +! IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +! DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +! FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +! DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +! SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +! CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +! OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +! OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +! ----------------------------------------------------------------------------- +! Author: A. R. Porter, STFC Daresbury Lab. + !> \brief Compute the potential vorticity, z !! \detail Given the current pressure and velocity fields, !! computes the potential voriticity. @@ -48,36 +81,6 @@ module compute_z_mod !=================================================== - !> Manual implementation of the code needed to invoke - !! compute_z_code(). - subroutine invoke_compute_z(zfld, pfld, ufld, vfld) - implicit none - type(r2d_field), intent(inout) :: zfld - type(r2d_field), intent(in) :: pfld, ufld, vfld - ! Locals - integer :: I, J - real(go_wp) :: dx, dy - - dx = zfld%grid%dx - dy = zfld%grid%dy - - do J=zfld%internal%ystart, zfld%internal%ystop, 1 - do I=zfld%internal%xstart, zfld%internal%xstop, 1 - - call compute_z_code(i, j, & - zfld%data, & - pfld%data, & - ufld%data, & - vfld%data, & - dx, dy) - - end do - end do - - end subroutine invoke_compute_z - - !=================================================== - !> Compute the potential vorticity on the grid point (i,j) subroutine compute_z_code(i, j, z, p, u, v, dx, dy) implicit none From 72dd82fa52e845fdd1b37a0a883f5b5c9110ae23 Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Fri, 21 Dec 2018 10:09:43 +0000 Subject: [PATCH 47/60] #216 fix test failures due to addition of go_ suffix on master --- src/psyclone/gocean1p0.py | 6 +++--- src/psyclone/tests/gocean1p0_opencl_test.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/psyclone/gocean1p0.py b/src/psyclone/gocean1p0.py index 0550930266..ebafd2b80a 100644 --- a/src/psyclone/gocean1p0.py +++ b/src/psyclone/gocean1p0.py @@ -212,7 +212,7 @@ def unique_args_arrays(self): @property def unique_args_rscalars(self): ''' find unique arguments that are scalars of type real (defined - as those that are r_scalar 'space'. ''' + as those that are go_r_scalar 'space'. ''' result = [] for call in self._schedule.calls(): for arg in args_filter(call.arguments.args, arg_types=["scalar"], @@ -1060,9 +1060,9 @@ def gen_arg_setter_code(self, parent): arg_types=["scalar"], is_literal=False) for arg in args: - if arg.space.lower() == "r_scalar": + if arg.space.lower() == "go_r_scalar": sub.add(DeclGen( - sub, datatype="REAL", intent="in", kind="wp", + sub, datatype="REAL", intent="in", kind="go_wp", target=True, entity_decls=[arg.name])) else: sub.add(DeclGen(sub, datatype="INTEGER", intent="in", diff --git a/src/psyclone/tests/gocean1p0_opencl_test.py b/src/psyclone/tests/gocean1p0_opencl_test.py index 71f65259fd..3efefecc27 100644 --- a/src/psyclone/tests/gocean1p0_opencl_test.py +++ b/src/psyclone/tests/gocean1p0_opencl_test.py @@ -147,7 +147,7 @@ def test_set_kern_float_arg(): USE clfortran, ONLY: clSetKernelArg USE iso_c_binding, ONLY: c_sizeof, c_loc, c_intptr_t USE ocl_utils_mod, ONLY: check_status - REAL(KIND=wp), intent(in), target :: a_scalar + REAL(KIND=go_wp), intent(in), target :: a_scalar INTEGER ierr INTEGER(KIND=c_intptr_t), target :: ssh_fld, tmask INTEGER(KIND=c_intptr_t), target :: kernel_obj From d89f8d34ee35144a879c668ebb8cee39b11b5ff2 Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Fri, 21 Dec 2018 11:14:34 +0000 Subject: [PATCH 48/60] #216 update user-guide with new OCLTrans() [skip ci] --- doc/transformations.rst | 19 +++++++++++------ src/psyclone/psyGen.py | 37 +++++++++++++++++---------------- src/psyclone/transformations.py | 10 +++++++-- 3 files changed, 40 insertions(+), 26 deletions(-) diff --git a/doc/transformations.rst b/doc/transformations.rst index f6525a0c40..a91d60274d 100644 --- a/doc/transformations.rst +++ b/doc/transformations.rst @@ -35,9 +35,9 @@ The generic transformations currently available are listed in alphabetical order below (a number of these have specialisations which can be found in the API-specific sections). -.. note:: PSyclone currently only supports OpenACC transformations - for the GOcean 1.0 API. Attempts to apply these - transformations to (members of) Schedules from other +.. note:: PSyclone currently only supports OpenACC and OpenCL + transformations for the GOcean 1.0 API. Attempts to apply + these transformations to (members of) Schedules from other APIs will be rejected. #### @@ -87,9 +87,9 @@ can be found in the API-specific sections). #### -.. autoclass:: psyclone.transformations.ProfileRegionTrans - :members: - :noindex: +.. autoclass:: psyclone.transformations.OCLTrans + :members: + :noindex: #### @@ -119,6 +119,13 @@ can be found in the API-specific sections). :ref:`MoveTrans ` transformation may be used for this. +#### + +.. autoclass:: psyclone.transformations.ProfileRegionTrans + :members: + :noindex: + + Kernels ------- diff --git a/src/psyclone/psyGen.py b/src/psyclone/psyGen.py index cf7bb5603d..a34365860e 100644 --- a/src/psyclone/psyGen.py +++ b/src/psyclone/psyGen.py @@ -285,24 +285,25 @@ def create(self, invoke_info): class PSy(object): ''' - Base class to help manage and generate PSy code for a single - algorithm file. Takes the invocation information output from the - function :func:`parse.parse` as its input and stores this in a - way suitable for optimisation and code generation. - - :param FileInfo invoke_info: An object containing the required \ - invocation information for code \ - optimisation and generation. Produced \ - by the function :func:`parse.parse`. - For example: - - >>> import psyclone - >>> from psyclone.parse import parse - >>> ast, info = parse("argspec.F90") - >>> from psyclone.psyGen import PSyFactory - >>> api = "..." - >>> psy = PSyFactory(api).create(info) - >>> print(psy.gen) + Base class to help manage and generate PSy code for a single + algorithm file. Takes the invocation information output from the + function :func:`parse.parse` as its input and stores this in a + way suitable for optimisation and code generation. + + :param FileInfo invoke_info: An object containing the required \ + invocation information for code \ + optimisation and generation. Produced \ + by the function :func:`parse.parse`. + + For example: + + >>> import psyclone + >>> from psyclone.parse import parse + >>> ast, info = parse("argspec.F90") + >>> from psyclone.psyGen import PSyFactory + >>> api = "..." + >>> psy = PSyFactory(api).create(info) + >>> print(psy.gen) ''' def __init__(self, invoke_info): diff --git a/src/psyclone/transformations.py b/src/psyclone/transformations.py index 44cd7c2723..523bbe155c 100644 --- a/src/psyclone/transformations.py +++ b/src/psyclone/transformations.py @@ -2136,6 +2136,7 @@ class OCLTrans(Transformation): >>> >>> ocl_trans = OCLTrans() >>> new_sched, _ = ocl_trans.apply(schedule) + ''' @property def name(self): @@ -2146,17 +2147,22 @@ def apply(self, sched, opencl=True): ''' Apply the OpenCL transformation to the supplied Schedule. This causes PSyclone to generate an OpenCL version of the corresponding - PSy-layer routine. + PSy-layer routine. The generated code makes use of the FortCL + library (https://github.com/stfc/FortCL) in order to manage the + OpenCL device directly from Fortran. + :param sched: Schedule to transform. :type sched: :py:class:`psyclone.psyGen.Schedule` :param bool opencl: whether or not to enable OpenCL generation. + ''' if opencl: self._validate(sched) # create a memento of the schedule and the proposed transformation from psyclone.undoredo import Memento keep = Memento(sched, self, [sched]) - + # All we have to do here is set the flag in the Schedule. When this + # flag is True PSyclone produces OpenCL at code-generation time. sched.opencl = opencl return sched, keep From 0268b0e8da7cd92f81eecdfe9e136617c5e3b9c3 Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Fri, 21 Dec 2018 12:38:09 +0000 Subject: [PATCH 49/60] #216 add doc to developers guide [skip ci] --- doc/developers.rst | 131 ++++++++++++++++++++++++++++++++++++++++ doc/references.bib | 7 +++ doc/transformations.rst | 3 + 3 files changed, 141 insertions(+) diff --git a/doc/developers.rst b/doc/developers.rst index 5efa833520..dcd6317397 100644 --- a/doc/developers.rst +++ b/doc/developers.rst @@ -1399,3 +1399,134 @@ Of course, a given field may already be on the device (and have been updated) due to a previous Invoke. In this case, the fact that the OpenACC run-time does not copy over the now out-dated host version of the field is essential for correctness. + +.. _opencl_dev: + +OpenCL Support +############## + +PSyclone is able to generate an OpenCL :cite:`opencl` version of +PSy-layer code for the GOcean 1.0 API. Such code may then be executed +on devices such as GPUs and FPGAs (Field-Programmable Gate +Arrays). Since OpenCL code is very different to that which PSyclone +normally generates, its creation is handled by ``gen_ocl`` methods +instead of the normal ``gen_code``. Which of these to use is +determined by the value of the ``Schedule.opencl`` flag. In turn, +this is set at a user level by the ``transformations.OCLTrans`` +transformation. + +The PSyKAl model of calling kernels for pre-determined iteration +spaces is a natural fit to OpenCL's concept of an +``NDRangeKernel``. However, the kernels themselves must be created or +loaded at runtime, their arguments explicitly set and any arrays +copied to the compute device. All of this 'boilerplate' code is +generated by PSyclone. In order to minimise the changes required, the +generated code is still Fortran and makes use of the FortCL library +(https://github.com/stfc/FortCL) to access OpenCL functionality. We +could of course generate the PSy layer in C instead but this would +require further extension of PSyclone. + +Consider the following invoke:: + + call invoke( compute_cu(CU_fld, p_fld, u_fld) ) + +When creating the OpenCL PSy layer for this invoke, PSyclone creates +three subroutines instead of the usual one. The first, ``psy_init`` +is responsible for ensuring that a valid kernel object is created +for each kernel called by the invoke, e.g.:: + + use fortcl, only: ocl_env_init, add_kernels + ... + ! Initialise the OpenCL environment/device + CALL ocl_env_init + ! The kernels this PSy layer module requires + kernel_names(1) = "compute_cu_code" + ! Create the OpenCL kernel objects. Expects to find all of the + ! compiled kernels in PSYCLONE_KERNELS_FILE. + CALL add_kernels(1, kernel_names) + +As indicated in the comment, the ``FortCL::add_kernels`` routine +expects to find all kernels in a pre-compiled file pointed to by the +PSYCLONE_KERNELS_FILE environment variable. (A pre-compiled file is +used instead of run-time kernel compilation in order to support +execution on FPGAs.) + +The second routine created by PSyclone sets the kernel arguments, e.g.:: + + SUBROUTINE compute_cu_code_set_args(kernel_obj, nx, cu_fld, p_fld, u_fld) + USE clfortran, ONLY: clSetKernelArg + USE iso_c_binding, ONLY: c_sizeof, c_loc, c_intptr_t + ... + INTEGER(KIND=c_intptr_t), target :: cu_fld, p_fld, u_fld + INTEGER(KIND=c_intptr_t), target :: kernel_obj + INTEGER, target :: nx + ! Set the arguments for the compute_cu_code OpenCL Kernel + ierr = clSetKernelArg(kernel_obj, 0, C_SIZEOF(nx), C_LOC(nx)) + ierr = clSetKernelArg(kernel_obj, 1, C_SIZEOF(cu_fld), C_LOC(cu_fld)) + ... + END SUBROUTINE compute_cu_code_set_args + +The third routine generated is the ususal psy-layer routine that is +responsible for calling all of the kernels. However, it must now also +call ``psy_init``, create buffers on the compute device (if they are +not already present) and copy data over:: + + SUBROUTINE invoke_compute_cu(...) + ... + IF (first_time) THEN + first_time = .false. + CALL psy_init + num_cmd_queues = get_num_cmd_queues() + cmd_queues => get_cmd_queues() + kernel_compute_cu_code = get_kernel_by_name("compute_cu_code") + END IF + globalsize = (/p_fld%grid%nx, p_fld%grid%ny/) + ! Ensure field data is on device + IF (.NOT. cu_fld%data_on_device) THEN + size_in_bytes = int(p_fld%grid%nx*p_fld%grid%ny, 8)* & + c_sizeof(cu_fld%data(1,1)) + ! Create buffer on device + cu_fld%device_ptr = create_rw_buffer(size_in_bytes) + ierr = clEnqueueWriteBuffer(cmd_queues(1), cu_fld%device_ptr, & + CL_TRUE, 0_8, size_in_bytes, & + C_LOC(cu_fld%data), 0, C_NULL_PTR, & + C_LOC(write_event)) + cu_fld%data_on_device = .true. + END IF + ... + +Note that we use the ``data_on_device`` member of the field derived +type (implement in github.com/stfc/dl_esm_inf) to keep track of +whether a given field has been copied to the compute device. Once all +of this setup is done, the kernel itself is launched by calling +``clEnqueueNDRangeKernel``:: + + ierr = clEnqueueNDRangeKernel(cmd_queues(1), kernel_compute_cu_code, & + 2, C_NULL_PTR, C_LOC(globalsize), & + C_NULL_PTR, 0, C_NULL_PTR, C_NULL_PTR) + +Limitations +=========== + +Currently PSyclone can only generate the OpenCL version of the PSy +layer. Execution of the resulting code requires that the kernels +themselves be converted from Fortran to OpenCL (a dialect of C) and at +present this must be done manually. Since all data accessed by an +OpenCL kernel must be passed as an argument, this conversion must also +convert any accesses to module data into routine arguments. +Work is in progress to support kernel transformation and this will be +made available in a future PSyclone release. + +In OpenCL, all tasks to be performed (whether copying data or kernel +execution) are associated with a command queue. Tasks submitted to +different command queues may then be executed concurrently, +potentially giving greater performance. The OpenCL PSy code currently +generated by PSyclone makes use of just one command queue but again, +this could be extended in the future. + +Since PSyclone knows nothing about the I/O performed by a model, the +task of ensuring that the correct data is written out by a model +(including when doing halo exchanges for distributed memory) is left +to the dl_esm_inf library since that has the information on whether +field data is local or on a remote compute device. + diff --git a/doc/references.bib b/doc/references.bib index 8f749ecc16..2f19804ca2 100644 --- a/doc/references.bib +++ b/doc/references.bib @@ -14,3 +14,10 @@ @manual{nemo_code_conv url = "https://forge.ipsl.jussieu.fr/nemo/attachment/wiki/Literature/NEMO_coding.conv_v3.pdf", year = "2013" } + +@manual{opencl, + title = "The OpenCL 2.2 Reference Guide", + version = "2.2", + url = "https://www.khronos.org/files/opencl22-reference-guide.pdf", + year = "2018" +} \ No newline at end of file diff --git a/doc/transformations.rst b/doc/transformations.rst index a91d60274d..ae03423ac0 100644 --- a/doc/transformations.rst +++ b/doc/transformations.rst @@ -91,6 +91,9 @@ can be found in the API-specific sections). :members: :noindex: +.. note:: OpenCL support is still under development. See + :ref:`opencl_dev` for more details. + #### .. autoclass:: psyclone.transformations.OMPLoopTrans From a5022253609cd5897c4349ec023c4c8bd9687ff5 Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Fri, 21 Dec 2018 14:07:21 +0000 Subject: [PATCH 50/60] #216 use namespace manager for arg-setter routine variables --- src/psyclone/gocean1p0.py | 29 ++++++++++++++++++++--------- src/psyclone/psyGen.py | 15 ++++++++++----- 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/src/psyclone/gocean1p0.py b/src/psyclone/gocean1p0.py index ebafd2b80a..cb64497ee4 100644 --- a/src/psyclone/gocean1p0.py +++ b/src/psyclone/gocean1p0.py @@ -991,8 +991,10 @@ def gen_ocl(self, parent): # for grid properties in "grid-prop-name_device" which # is a bit hacky but works for now. arguments.append(garg.name+"%grid%"+arg.name+"_device") - parent.add(CallGen( - parent, "{0}_set_args".format(self.name), arguments)) + sub_name = self._name_space_manager.create_name( + root_name=self.name+"_set_args", context=self.name+"ArgSetter", + label=self.name+"_set_args") + parent.add(CallGen(parent, sub_name, arguments)) # Get the name of the list of command queues (set in # psyGen.Schedule) @@ -1032,9 +1034,14 @@ def gen_arg_setter_code(self, parent): # the OpenCL transformation. kobj = self._name_space_manager.create_name( root_name="kernel_obj", context="ArgSetter", label="kernel_obj") - arguments = [kobj, "nx"] + [arg.name for arg in self._arguments.args] - sub = SubroutineGen(parent, name=self.name+"_set_args", - args=arguments) + nx_name = self._name_space_manager.create_name( + root_name="nx", context="ArgSetter", label="nx") + args = [kobj, nx_name] + [arg.name for arg in self._arguments.args] + + sub_name = self._name_space_manager.create_name( + root_name=self.name+"_set_args", context=self.name+"ArgSetter", + label=self.name+"_set_args") + sub = SubroutineGen(parent, name=sub_name, args=args) parent.add(sub) sub.add(UseGen(sub, name="ocl_utils_mod", only=True, funcnames=["check_status"])) @@ -1044,7 +1051,7 @@ def gen_arg_setter_code(self, parent): funcnames=["clSetKernelArg"])) # Declare arguments sub.add(DeclGen(sub, datatype="integer", target=True, - entity_decls=["nx"])) + entity_decls=[nx_name])) sub.add(DeclGen(sub, datatype="integer", kind="c_intptr_t", target=True, entity_decls=[kobj])) @@ -1069,7 +1076,9 @@ def gen_arg_setter_code(self, parent): target=True, entity_decls=[arg.name])) # Declare local variables - sub.add(DeclGen(sub, datatype="integer", entity_decls=["ierr"])) + err_name = self._name_space_manager.create_name( + root_name="ierr", context="PSyVars", label="ierr") + sub.add(DeclGen(sub, datatype="integer", entity_decls=[err_name])) sub.add(CommentGen( sub, " Set the arguments for the {0} OpenCL Kernel".format(self.name))) @@ -1077,9 +1086,9 @@ def gen_arg_setter_code(self, parent): # a kernel index = 0 sub.add(AssignGen( - sub, lhs="ierr", + sub, lhs=err_name, rhs="clSetKernelArg({0}, {1}, C_SIZEOF({2}), C_LOC({2}))". - format(kobj, index, "nx"))) + format(kobj, index, nx_name))) # Now all of the 'standard' kernel arguments for arg in self.arguments.args: index += 1 @@ -1320,6 +1329,8 @@ def __init__(self, arg): # This object always represents an argument that is a grid_property self._type = "grid_property" + # Access to the name-space manager + self._name_space_manager = NameSpaceFactory().create() @property def name(self): diff --git a/src/psyclone/psyGen.py b/src/psyclone/psyGen.py index a34365860e..6edb9a91ed 100644 --- a/src/psyclone/psyGen.py +++ b/src/psyclone/psyGen.py @@ -3592,6 +3592,8 @@ def __init__(self, call, arg_info, access): self._form = arg_info.form self._is_literal = arg_info.is_literal() self._access = access + self._name_space_manager = NameSpaceFactory().create() + if self._orig_name is None: # this is an infrastructure call literal argument. Therefore # we do not want an argument (_text=None) but we do want to @@ -3599,7 +3601,6 @@ def __init__(self, call, arg_info, access): self._name = arg_info.text self._text = None else: - self._name_space_manager = NameSpaceFactory().create() # Use our namespace manager to create a unique name unless # the context and label match in which case return the # previous name. @@ -3679,15 +3680,19 @@ def set_kernel_arg(self, parent, index, kname): :param str kname: the name of the OpenCL kernel. ''' from psyclone.f2pygen import AssignGen, CallGen - # TODO fix hardwired names of "ierr" and "kernel_obj" + # Look up variable names from name-space manager + err_name = self._name_space_manager.create_name( + root_name="ierr", context="PSyVars", label="ierr") + kobj = self._name_space_manager.create_name( + root_name="kernel_obj", context="ArgSetter", label="kernel_obj") parent.add(AssignGen( - parent, lhs="ierr", + parent, lhs=err_name, rhs="clSetKernelArg({0}, {1}, C_SIZEOF({2}), C_LOC({2}))". - format("kernel_obj", index, self.name))) + format(kobj, index, self.name))) parent.add(CallGen( parent, "check_status", ["'clSetKernelArg: arg {0} of {1}'".format(index, kname), - "ierr"])) + err_name])) def backward_dependence(self): '''Returns the preceding argument that this argument has a direct From 1ed37a5221c486cc300f79330e817a2c5d190a60 Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Fri, 21 Dec 2018 14:15:36 +0000 Subject: [PATCH 51/60] #216 make cmd-queue assumption more explicit [skip ci] --- src/psyclone/psyGen.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/psyclone/psyGen.py b/src/psyclone/psyGen.py index 6edb9a91ed..41925a41f8 100644 --- a/src/psyclone/psyGen.py +++ b/src/psyclone/psyGen.py @@ -1531,7 +1531,8 @@ def gen_code(self, parent): # Ensure we block at the end of the invoke to ensure all # kernels have completed before we return. # TODO can we lift this restriction? - # BUG this assumes only the first command queue is used + # This code ASSUMES only the first command queue is used for + # executing kernels. parent.add(CommentGen(parent, " Block until all kernels have finished")) parent.add(AssignGen(parent, lhs=flag, From fc6a8f613c28c4df32db16e6563036e7604d46df Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Wed, 16 Jan 2019 13:42:04 +0000 Subject: [PATCH 52/60] #216 tidy-up docstrings and set Memento --- src/psyclone/gocean1p0.py | 7 ++++--- src/psyclone/psyGen.py | 1 - src/psyclone/transformations.py | 7 +++++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/psyclone/gocean1p0.py b/src/psyclone/gocean1p0.py index cb64497ee4..16f2c65523 100644 --- a/src/psyclone/gocean1p0.py +++ b/src/psyclone/gocean1p0.py @@ -987,9 +987,10 @@ def gen_ocl(self, parent): elif arg.type == "field": arguments.append(arg.name + "%device_ptr") elif arg.type == "grid_property": - # TODO dl_esm_inf stores the pointers to device memory - # for grid properties in "grid-prop-name_device" which - # is a bit hacky but works for now. + # TODO (dl_esm_inf/#18) the dl_esm_inf library stores + # the pointers to device memory for grid properties in + # "_device" which is a bit hacky but + # works for now. arguments.append(garg.name+"%grid%"+arg.name+"_device") sub_name = self._name_space_manager.create_name( root_name=self.name+"_set_args", context=self.name+"ArgSetter", diff --git a/src/psyclone/psyGen.py b/src/psyclone/psyGen.py index 41925a41f8..b7a75d18c7 100644 --- a/src/psyclone/psyGen.py +++ b/src/psyclone/psyGen.py @@ -1530,7 +1530,6 @@ def gen_code(self, parent): if self.opencl: # Ensure we block at the end of the invoke to ensure all # kernels have completed before we return. - # TODO can we lift this restriction? # This code ASSUMES only the first command queue is used for # executing kernels. parent.add(CommentGen(parent, diff --git a/src/psyclone/transformations.py b/src/psyclone/transformations.py index 523bbe155c..46a207d7a8 100644 --- a/src/psyclone/transformations.py +++ b/src/psyclone/transformations.py @@ -2140,7 +2140,10 @@ class OCLTrans(Transformation): ''' @property def name(self): - '''Returns the name of this transformation as a string.''' + ''' + :returns: the name of this transformation. + :rtype: str + ''' return "OCLTrans" def apply(self, sched, opencl=True): @@ -2160,7 +2163,7 @@ def apply(self, sched, opencl=True): self._validate(sched) # create a memento of the schedule and the proposed transformation from psyclone.undoredo import Memento - keep = Memento(sched, self, [sched]) + keep = Memento(sched, self, [sched, opencl]) # All we have to do here is set the flag in the Schedule. When this # flag is True PSyclone produces OpenCL at code-generation time. sched.opencl = opencl From 35049bd6817b6be292b6a4e6ab3332e7962fafa7 Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Fri, 25 Jan 2019 10:05:11 +0000 Subject: [PATCH 53/60] #216 improve doc strings following review [skip ci] --- src/psyclone/gocean1p0.py | 31 ++++++++++++----- src/psyclone/psyGen.py | 72 ++++++++++++++++++++++----------------- 2 files changed, 62 insertions(+), 41 deletions(-) diff --git a/src/psyclone/gocean1p0.py b/src/psyclone/gocean1p0.py index 4585b93554..dfebf28b7f 100644 --- a/src/psyclone/gocean1p0.py +++ b/src/psyclone/gocean1p0.py @@ -194,9 +194,10 @@ class GOInvoke(Invoke): def __init__(self, alg_invocation, idx): '''Constructor for the GOcean-specific invoke class. + :param alg_invocation: Node in the AST describing the invoke call. :type alg_invocation: :py:class:`psyclone.parse.InvokeCall` - :param int idx: The position of the invoke in the list of invokes + :param int idx: The position of the invoke in the list of invokes \ contained in the Algorithm. ''' @@ -242,10 +243,15 @@ def unique_args_iscalars(self): return result def gen_code(self, parent): - ''' Generates GOcean specific invocation code (the subroutine called - by the associated invoke call in the algorithm layer). This - consists of the PSy invocation subroutine and the declaration of - its arguments.''' + ''' + Generates GOcean specific invocation code (the subroutine called + by the associated invoke call in the algorithm layer). This + consists of the PSy invocation subroutine and the declaration of + its arguments. + + :param parent: the node in the generated AST to which to add content. + :type parent: :py:class:`psyclone.f2pygen.ModuleGen` + ''' from psyclone.f2pygen import SubroutineGen, DeclGen, TypeDeclGen, \ CommentGen, AssignGen # create the subroutine @@ -911,6 +917,7 @@ def gen_code(self, parent): :param parent: parent node in the f2pygen AST being created. :type parent: :py:class:`psyclone.f2pygen.LoopGen` + :raises GenerationError: if the kernel requires a grid property but \ does not have any field arguments. :raises GenerationError: if it encounters a kernel argument of \ @@ -1386,12 +1393,18 @@ def function_space(self): class GOKernelGridArgument(Argument): - ''' Describes arguments that supply grid properties to a kernel. - These arguments are provided by the PSy layer rather than in - the Algorithm layer. ''' + ''' + Describes arguments that supply grid properties to a kernel. + These arguments are provided by the PSy layer rather than in + the Algorithm layer. - def __init__(self, arg): + :param arg: the meta-data entry describing the required grid property. + :type arg: :py:class:`psyclone.gocean1p0.GO1p0Descriptor` + + :raises GenerationError: if the grid property is not recognised. + ''' + def __init__(self, arg): if arg.grid_prop in GRID_PROPERTY_DICT: self._name = GRID_PROPERTY_DICT[arg.grid_prop] else: diff --git a/src/psyclone/psyGen.py b/src/psyclone/psyGen.py index 0de3ff42cc..8b829f6189 100644 --- a/src/psyclone/psyGen.py +++ b/src/psyclone/psyGen.py @@ -1374,22 +1374,42 @@ def update(self): class Schedule(Node): + ''' + Stores schedule information for an invocation call. Schedules can be + optimised using transformations. - ''' Stores schedule information for an invocation call. Schedules can be - optimised using transformations. + >>> from parse import parse + >>> ast, info = parse("algorithm.f90") + >>> from psyGen import PSyFactory + >>> api = "..." + >>> psy = PSyFactory(api).create(info) + >>> invokes = psy.invokes + >>> invokes.names + >>> invoke = invokes.get("name") + >>> schedule = invoke.schedule + >>> schedule.view() - >>> from parse import parse - >>> ast, info = parse("algorithm.f90") - >>> from psyGen import PSyFactory - >>> api = "..." - >>> psy = PSyFactory(api).create(info) - >>> invokes = psy.invokes - >>> invokes.names - >>> invoke = invokes.get("name") - >>> schedule = invoke.schedule - >>> schedule.view() + :param type KernFactory: the sub-class-specific Kernel factory. + :param type BuiltInFactory: the sub-class-specific factory for built-ins. + :param alg_calls: list of kernel-calls in the schedule. + :type alg_calls: list of :py:class:`psyclone.parse.KernelCall` ''' + def __init__(self, KernFactory, BuiltInFactory, alg_calls=[]): + # we need to separate calls into loops (an iteration space really) + # and calls so that we can perform optimisations separately on the + # two entities. + sequence = [] + from psyclone.parse import BuiltInCall + for call in alg_calls: + if isinstance(call, BuiltInCall): + sequence.append(BuiltInFactory.create(call, parent=self)) + else: + sequence.append(KernFactory.create(call, parent=self)) + Node.__init__(self, children=sequence) + self._invoke = None + self._opencl = False # Whether or not to generate OpenCL + self._name_space_manager = NameSpaceFactory().create() @property def dag_name(self): @@ -1414,23 +1434,6 @@ def invoke(self): def invoke(self, my_invoke): self._invoke = my_invoke - def __init__(self, KernFactory, BuiltInFactory, alg_calls=[]): - - # we need to separate calls into loops (an iteration space really) - # and calls so that we can perform optimisations separately on the - # two entities. - sequence = [] - from psyclone.parse import BuiltInCall - for call in alg_calls: - if isinstance(call, BuiltInCall): - sequence.append(BuiltInFactory.create(call, parent=self)) - else: - sequence.append(KernFactory.create(call, parent=self)) - Node.__init__(self, children=sequence) - self._invoke = None - self._opencl = False # Whether or not to generate OpenCL - self._name_space_manager = NameSpaceFactory().create() - def view(self, indent=0): ''' Print a text representation of this node to stdout and then @@ -1541,8 +1544,7 @@ def gen_code(self, parent): @property def opencl(self): ''' - Whether or not we are generating OpenCL for this Schedule. - + :return: Whether or not we are generating OpenCL for this Schedule. :rtype: bool ''' return self._opencl @@ -2853,7 +2855,13 @@ def args_filter(self, arg_types=None, arg_accesses=None, unique=False): return all_args def gen_code(self, parent): - '''Generate the fortran Loop and any associated code ''' + ''' + Generate the Fortran Loop and any associated code. + + :param parent: the node in the f2pygen AST to which to add content. + :type parent: :py:class:`psyclone.f2pygen.SubroutineGen` + + ''' if not self.is_openmp_parallel(): calls = self.reductions() zero_reduction_variables(calls, parent) From 2553e8e5b70517bbb8ba258d01cd6bbe51a7d0b7 Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Fri, 25 Jan 2019 10:42:31 +0000 Subject: [PATCH 54/60] #216 ensure GOKern.gen_code calls rename_and_write to fix tests --- src/psyclone/gocean1p0.py | 18 +++++++++++++----- .../tests/kernel_transformation_test.py | 13 ++++++++++++- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/psyclone/gocean1p0.py b/src/psyclone/gocean1p0.py index dfebf28b7f..8c0a4532d2 100644 --- a/src/psyclone/gocean1p0.py +++ b/src/psyclone/gocean1p0.py @@ -858,10 +858,13 @@ def create(call, parent=None): class GOKern(Kern): - ''' Stores information about GOcean Kernels as specified by the Kernel - metadata. Uses this information to generate appropriate PSy layer - code for the Kernel instance. Specialises the gen_code method to - create the appropriate GOcean specific kernel call. ''' + ''' + Stores information about GOcean Kernels as specified by the Kernel + metadata. Uses this information to generate appropriate PSy layer + code for the Kernel instance. Specialises the gen_code method to + create the appropriate GOcean specific kernel call. + + ''' def __init__(self): ''' Create an empty GOKern object. The object is given state via the load method ''' @@ -913,7 +916,8 @@ def find_grid_access(self): def gen_code(self, parent): ''' Generates GOcean v1.0 specific psy code for a call to the - kernel instance. + kernel instance. Also ensures that the kernel is written to file + if it has been transformed. :param parent: parent node in the f2pygen AST being created. :type parent: :py:class:`psyclone.f2pygen.LoopGen` @@ -925,6 +929,10 @@ def gen_code(self, parent): ''' from psyclone.f2pygen import CallGen, UseGen + # If the kernel has been transformed then we rename it. If it + # is *not* being module inlined then we also write it to file. + self.rename_and_write() + if self.root.opencl: # OpenCL is completely different so has its own gen method. self.gen_ocl(parent) diff --git a/src/psyclone/tests/kernel_transformation_test.py b/src/psyclone/tests/kernel_transformation_test.py index 8cb006892f..9a50606e33 100644 --- a/src/psyclone/tests/kernel_transformation_test.py +++ b/src/psyclone/tests/kernel_transformation_test.py @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2018, Science and Technology Facilities Council. +# Copyright (c) 2018-19, Science and Technology Facilities Council. # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -47,6 +47,16 @@ from psyclone.configuration import Config +def setup_module(): + ''' + This setup routine ensures that and pre-exisiting Config object is + wiped when this module is first entered and the teardown function below + guarantees it for subsequent tests. (Necessary when running tests in + parallel.) + ''' + Config._instance = None + + def teardown_function(): ''' This function is called automatically after every test in this file. It ensures that any existing configuration object is deleted. ''' @@ -131,6 +141,7 @@ def test_new_kernel_file(tmpdir, monkeypatch): # Ensure kernel-output directory is uninitialised config = Config.get() monkeypatch.setattr(config, "_kernel_output_dir", "") + monkeypatch.setattr(config, "_kernel_naming", "multiple") # Change to temp dir (so kernel written there) _ = tmpdir.chdir() psy, invoke = get_invoke("nemolite2d_alg_mod.f90", api="gocean1.0", idx=0) From d886b8342a46806acb29ec3ecd4c48486f0f42ad Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Fri, 25 Jan 2019 11:17:56 +0000 Subject: [PATCH 55/60] #216 update developers and transformation docs [skip ci] --- doc/developers.rst | 7 ++++++- doc/transformations.rst | 26 ++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/doc/developers.rst b/doc/developers.rst index 3726022ca0..708db4a704 100644 --- a/doc/developers.rst +++ b/doc/developers.rst @@ -1586,7 +1586,7 @@ not already present) and copy data over:: ... Note that we use the ``data_on_device`` member of the field derived -type (implement in github.com/stfc/dl_esm_inf) to keep track of +type (implemented in github.com/stfc/dl_esm_inf) to keep track of whether a given field has been copied to the compute device. Once all of this setup is done, the kernel itself is launched by calling ``clEnqueueNDRangeKernel``:: @@ -1614,6 +1614,11 @@ potentially giving greater performance. The OpenCL PSy code currently generated by PSyclone makes use of just one command queue but again, this could be extended in the future. +The current implementation only supports the conversion of a whole +Invoke to use OpenCL. In the future we may refine this functionality +so that it may be applied to just a subset of kernels within an +Invoke. + Since PSyclone knows nothing about the I/O performed by a model, the task of ensuring that the correct data is written out by a model (including when doing halo exchanges for distributed memory) is left diff --git a/doc/transformations.rst b/doc/transformations.rst index eeca135ada..1cbe144aa7 100644 --- a/doc/transformations.rst +++ b/doc/transformations.rst @@ -396,3 +396,29 @@ region for a set of nodes that includes halo swaps or global sums will produce an error. In such cases it may be possible to re-order the nodes in the Schedule using the :ref:`MoveTrans ` transformation. + +OpenCL +------ + +In common with OpenMP, the conversion of the generated code to use +OpenCL is performed by a transformation (``OCLTrans`` - see the +:ref:`sec_transformations_available` Section above). Currently this +transformation is only supported for the GOcean1.0 API and is applied +to the whole Schedule of an Invoke. This means that all kernels in +that Invoke will be executed on the OpenCL device. At present the +``OCLTrans`` transformation only alters the generated PSy-layer code. It +is currently the user's responsibility to convert the actual kernel code +from Fortran into OpenCL. Work is underway to extend PSyclone in +order to perform this translation automatically. + +The OpenCL code generated by PSyclone is still Fortran and makes use +of the FortCL library (https://github.com/stfc/FortCL) to access +OpenCL functionality. It also relies upon the OpenCL support provided +by the dl_esm_inf library (https://github.com/stfc/dl_esm_inf). + +The introduction of OpenCL code generation in PSyclone has been +largely motivated by the need to target Field Programmable Gate Array +(FPGA) accelerator devices. It is not currently designed to target the other +compute devices that OpenCL supports (such as GPUs and multi-core CPUs) but +this is a potentially fruitful area for future work. + From 266a2b76a0eeded710ab2dec87b908bbb7e1af46 Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Fri, 25 Jan 2019 11:49:56 +0000 Subject: [PATCH 56/60] #216 tidy for pylint --- src/psyclone/gocean1p0.py | 37 ++++++++++++++------------------- src/psyclone/psyGen.py | 13 ++++++------ src/psyclone/transformations.py | 6 +++--- 3 files changed, 26 insertions(+), 30 deletions(-) diff --git a/src/psyclone/gocean1p0.py b/src/psyclone/gocean1p0.py index 8c0a4532d2..022f510aa3 100644 --- a/src/psyclone/gocean1p0.py +++ b/src/psyclone/gocean1p0.py @@ -184,23 +184,22 @@ def __init__(self, alg_calls): class GOInvoke(Invoke): - ''' The GOcean specific invoke class. This passes the GOcean specific - schedule class to the base class so it creates the one we require. - A set of GOcean infrastructure reserved names are also passed to - ensure that there are no name clashes. Also overrides the gen_code - method so that we generate GOcean specific invocation code and - provides three methods which separate arguments that are arrays from - arguments that are {integer, real} scalars. ''' + ''' + The GOcean specific invoke class. This passes the GOcean specific + schedule class to the base class so it creates the one we require. + A set of GOcean infrastructure reserved names are also passed to + ensure that there are no name clashes. Also overrides the gen_code + method so that we generate GOcean specific invocation code and + provides three methods which separate arguments that are arrays from + arguments that are {integer, real} scalars. + + :param alg_invocation: Node in the AST describing the invoke call. + :type alg_invocation: :py:class:`psyclone.parse.InvokeCall` + :param int idx: The position of the invoke in the list of invokes \ + contained in the Algorithm. + ''' def __init__(self, alg_invocation, idx): - '''Constructor for the GOcean-specific invoke class. - - :param alg_invocation: Node in the AST describing the invoke call. - :type alg_invocation: :py:class:`psyclone.parse.InvokeCall` - :param int idx: The position of the invoke in the list of invokes \ - contained in the Algorithm. - ''' - if False: # pylint: disable=using-constant-test self._schedule = GOSchedule(None) # for pyreverse Invoke.__init__(self, alg_invocation, idx, GOSchedule) @@ -272,10 +271,7 @@ def gen_code(self, parent): # If we're generating an OpenCL routine then the arguments must # have the target attribute as we pass pointers to them in to # the OpenCL run-time. - if self.schedule.opencl: - target = True - else: - target = False + target = bool(self.schedule.opencl) # add the subroutine argument declarations for fields if self.unique_args_arrays: @@ -980,8 +976,7 @@ def gen_ocl(self, parent): :param parent: Parent node in the f2pygen AST to which to add content. :type parent: :py:class:`psyclone.f2pygen.SubroutineGen` ''' - from psyclone.f2pygen import CallGen, DeclGen, AssignGen, CommentGen, \ - IfThenGen, UseGen + from psyclone.f2pygen import CallGen, DeclGen, AssignGen, CommentGen # Create the array used to specify the iteration space of the kernel garg = self.find_grid_access() glob_size = self._name_space_manager.create_name( diff --git a/src/psyclone/psyGen.py b/src/psyclone/psyGen.py index 8b829f6189..7dce82cea9 100644 --- a/src/psyclone/psyGen.py +++ b/src/psyclone/psyGen.py @@ -1395,17 +1395,18 @@ class Schedule(Node): :type alg_calls: list of :py:class:`psyclone.parse.KernelCall` ''' - def __init__(self, KernFactory, BuiltInFactory, alg_calls=[]): + def __init__(self, KernFactory, BuiltInFactory, alg_calls=None): # we need to separate calls into loops (an iteration space really) # and calls so that we can perform optimisations separately on the # two entities. sequence = [] from psyclone.parse import BuiltInCall - for call in alg_calls: - if isinstance(call, BuiltInCall): - sequence.append(BuiltInFactory.create(call, parent=self)) - else: - sequence.append(KernFactory.create(call, parent=self)) + if alg_calls: + for call in alg_calls: + if isinstance(call, BuiltInCall): + sequence.append(BuiltInFactory.create(call, parent=self)) + else: + sequence.append(KernFactory.create(call, parent=self)) Node.__init__(self, children=sequence) self._invoke = None self._opencl = False # Whether or not to generate OpenCL diff --git a/src/psyclone/transformations.py b/src/psyclone/transformations.py index 04edb77e30..e992b658a5 100644 --- a/src/psyclone/transformations.py +++ b/src/psyclone/transformations.py @@ -2074,7 +2074,7 @@ def _validate(self, node_outer): # pylint: disable=no-self-use "an instance of '{1}." .format(node_outer, type(node_outer))) - if len(node_outer.children) == 0: + if not node_outer.children: raise TransformationError("Error in GOLoopSwap transformation. " "Supplied node '{0}' must be the outer " "loop of a loop nest and must have one " @@ -2482,8 +2482,8 @@ def apply(self, sched): "not a Schedule") schedule = sched # Check that we don't already have a data region - data_dir = schedule.walk(schedule.children, AccDataDir) - if len(data_dir) != 0: + data_directives = schedule.walk(schedule.children, AccDataDir) + if data_directives: raise TransformationError("Schedule already has an OpenACC " "data region - cannot add another.") # create a memento of the schedule and the proposed From 24822c1444982515ccaffd5c9c7e84ad6d515f33 Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Wed, 30 Jan 2019 12:12:23 +0000 Subject: [PATCH 57/60] #216 tidy for pylint and improve docstrings --- src/psyclone/gocean1p0.py | 16 ++++++++++++---- src/psyclone/psyGen.py | 12 ++++++++---- src/psyclone/tests/gocean1p0_opencl_test.py | 7 ++----- 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/psyclone/gocean1p0.py b/src/psyclone/gocean1p0.py index 022f510aa3..71bc072846 100644 --- a/src/psyclone/gocean1p0.py +++ b/src/psyclone/gocean1p0.py @@ -217,8 +217,12 @@ def unique_args_arrays(self): @property def unique_args_rscalars(self): - ''' find unique arguments that are scalars of type real (defined - as those that are go_r_scalar 'space'. ''' + ''' + :returns: the unique arguments that are scalars of type real \ + (defined as those that are go_r_scalar 'space'). + :rtype: list of str. + + ''' result = [] for call in self._schedule.calls(): for arg in args_filter(call.arguments.args, arg_types=["scalar"], @@ -230,8 +234,12 @@ def unique_args_rscalars(self): @property def unique_args_iscalars(self): - ''' find unique arguments that are scalars of type integer (defined - as those that are i_scalar 'space'). ''' + ''' + :returns: the unique arguments that are scalars of type integer \ + (defined as those that are i_scalar 'space'). + :rtype: list of str. + + ''' result = [] for call in self._schedule.calls(): for arg in args_filter(call.arguments.args, arg_types=["scalar"], diff --git a/src/psyclone/psyGen.py b/src/psyclone/psyGen.py index 7dce82cea9..e7eef1bf5b 100644 --- a/src/psyclone/psyGen.py +++ b/src/psyclone/psyGen.py @@ -393,7 +393,8 @@ def gen_code(self, parent): # each kernel called from the PSy layer self.gen_ocl_init(parent, opencl_kernels) - def gen_ocl_init(self, parent, kernels): + @staticmethod + def gen_ocl_init(parent, kernels): ''' Generates a subroutine to initialise the OpenCL environment and construct the list of OpenCL kernel objects used by this PSy layer. @@ -1389,9 +1390,12 @@ class Schedule(Node): >>> schedule = invoke.schedule >>> schedule.view() - :param type KernFactory: the sub-class-specific Kernel factory. - :param type BuiltInFactory: the sub-class-specific factory for built-ins. - :param alg_calls: list of kernel-calls in the schedule. + :param type KernFactory: class instance of the factory to use when \ + creating Kernels. e.g. :py:class:`psyclone.dynamo0p3.DynKernCallFactory`. + :param type BuiltInFactory: class instance of the factory to use when \ + creating built-ins. e.g. \ + :py:class:`psyclone.dynamo0p3_builtins.DynBuiltInCallFactory`. + :param alg_calls: list of Kernel calls in the schedule. :type alg_calls: list of :py:class:`psyclone.parse.KernelCall` ''' diff --git a/src/psyclone/tests/gocean1p0_opencl_test.py b/src/psyclone/tests/gocean1p0_opencl_test.py index 3efefecc27..7e7747b5d8 100644 --- a/src/psyclone/tests/gocean1p0_opencl_test.py +++ b/src/psyclone/tests/gocean1p0_opencl_test.py @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2018, Science and Technology Facilities Council +# Copyright (c) 2018-2019, Science and Technology Facilities Council. # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -52,7 +52,6 @@ def test_use_stmts(): otrans = OCLTrans() otrans.apply(sched) generated_code = str(psy.gen).lower() - print(generated_code) expected = '''\ subroutine invoke_0_compute_cu(cu_fld, p_fld, u_fld) use fortcl, only: create_rw_buffer @@ -71,7 +70,6 @@ def test_psy_init(): otrans = OCLTrans() otrans.apply(sched) generated_code = str(psy.gen) - print(generated_code) expected = ( " SUBROUTINE psy_init()\n" " USE fortcl, ONLY: ocl_env_init, add_kernels\n" @@ -141,7 +139,6 @@ def test_set_kern_float_arg(): otrans = OCLTrans() otrans.apply(sched) generated_code = str(psy.gen) - print(generated_code) expected = '''\ SUBROUTINE bc_ssh_code_set_args(kernel_obj, nx, a_scalar, ssh_fld, tmask) USE clfortran, ONLY: clSetKernelArg @@ -167,7 +164,7 @@ def test_set_kern_float_arg(): assert expected in generated_code -def test_set_arg_const_scalar(): # pylint:disable=invalid-name +def test_set_arg_const_scalar(): ''' Check that an invoke that passes a scalar kernel argument by value is rejected. (We haven't yet implemented the necessary code for setting the value of such an argument in OpenCL.) ''' From fa098895fc8ff07beba9494f620202471918d1f0 Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Thu, 31 Jan 2019 09:52:44 +0000 Subject: [PATCH 58/60] #216 rm duplicated GOKern.find_grid_access() method --- src/psyclone/gocean1p0.py | 25 +++---------------------- 1 file changed, 3 insertions(+), 22 deletions(-) diff --git a/src/psyclone/gocean1p0.py b/src/psyclone/gocean1p0.py index 71bc072846..07dc98c168 100644 --- a/src/psyclone/gocean1p0.py +++ b/src/psyclone/gocean1p0.py @@ -898,25 +898,6 @@ def local_vars(self): ''' return [] - def find_grid_access(self): - '''Determine the best kernel argument from which to get properties of - the grid. For this, an argument must be a field (i.e. not - a scalar) and must be supplied by the algorithm layer - (i.e. not a grid property). If possible it should also be - a field that is read-only as otherwise compilers can get - confused about data dependencies and refuse to SIMD - vectorise. - - ''' - for access in ["go_read", "go_readwrite", "go_write"]: - for arg in self._arguments.args: - if arg.type == "field" and arg.access.lower() == access: - return arg - # We failed to find any kernel argument which could be used - # to access the grid properties. This will only be a problem - # if the kernel requires a grid-property argument. - return None - def gen_code(self, parent): ''' Generates GOcean v1.0 specific psy code for a call to the @@ -944,7 +925,7 @@ def gen_code(self, parent): # Before we do anything else, go through the arguments and # determine the best one from which to obtain the grid properties. - grid_arg = self.find_grid_access() + grid_arg = self._arguments.find_grid_access() # A GOcean 1.0 kernel always requires the [i,j] indices of the # grid-point that is to be updated @@ -986,7 +967,7 @@ def gen_ocl(self, parent): ''' from psyclone.f2pygen import CallGen, DeclGen, AssignGen, CommentGen # Create the array used to specify the iteration space of the kernel - garg = self.find_grid_access() + garg = self._arguments.find_grid_access() glob_size = self._name_space_manager.create_name( root_name="globalsize", context="PSyVars", label="globalsize") parent.add(DeclGen(parent, datatype="integer", target=True, @@ -1127,7 +1108,7 @@ def gen_data_on_ocl_device(self, parent): ''' from psyclone.f2pygen import UseGen, CommentGen, IfThenGen, DeclGen, \ AssignGen - grid_arg = self.find_grid_access() + grid_arg = self._arguments.find_grid_access() # Ensure the fields required by this kernel are on device. We must # create the buffers for them if they're not. parent.add(UseGen(parent, name="fortcl", only=True, From a5ec76f1efa2fd8b9becba35f819011702f160ff Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Thu, 31 Jan 2019 10:06:19 +0000 Subject: [PATCH 59/60] #216 use raw_arg_list() method in GOKern.gen_code() --- src/psyclone/gocean1p0.py | 35 ++++------------------------------- 1 file changed, 4 insertions(+), 31 deletions(-) diff --git a/src/psyclone/gocean1p0.py b/src/psyclone/gocean1p0.py index 07dc98c168..996e462992 100644 --- a/src/psyclone/gocean1p0.py +++ b/src/psyclone/gocean1p0.py @@ -923,36 +923,7 @@ def gen_code(self, parent): self.gen_ocl(parent) return - # Before we do anything else, go through the arguments and - # determine the best one from which to obtain the grid properties. - grid_arg = self._arguments.find_grid_access() - - # A GOcean 1.0 kernel always requires the [i,j] indices of the - # grid-point that is to be updated - arguments = ["i", "j"] - for arg in self._arguments.args: - - if arg.type == "scalar": - # Scalar arguments require no de-referencing - arguments.append(arg.name) - elif arg.type == "field": - # Field objects are Fortran derived-types - arguments.append(arg.name + "%data") - elif arg.type == "grid_property": - # Argument is a property of the grid which we can access via - # the grid member of any field object. - # We use the most suitable field as chosen above. - if grid_arg is None: - raise GenerationError( - "Error: kernel {0} requires grid property {1} but " - "does not have any arguments that are fields". - format(self._name, arg.name)) - else: - arguments.append(grid_arg.name+"%grid%"+arg.name) - else: - raise GenerationError("Kernel {0}, argument {1} has " - "unrecognised type: {2}". - format(self._name, arg.name, arg.type)) + arguments = self._arguments.raw_arg_list() parent.add(CallGen(parent, self._name, arguments)) if not self.module_inline: parent.add(UseGen(parent, name=self._module_name, only=True, @@ -1213,12 +1184,14 @@ def __init__(self, call, parent_call): "field"])) self._dofs = [] - def raw_arg_list(self, parent=None): + def raw_arg_list(self): ''' :returns: a list of all of the actual arguments to the \ kernel call. :rtype: list of str + :raises GenerationError: if the kernel requires a grid property \ + but has no field arguments. :raises InternalError: if we encounter a kernel argument with an \ unrecognised type. ''' From 60e4cc8b47b2a26d16f8a03979e5b8f5f1475164 Mon Sep 17 00:00:00 2001 From: Rupert Ford Date: Thu, 31 Jan 2019 12:01:05 +0000 Subject: [PATCH 60/60] pr #216. Updating changelog and documentation pdf ready for merge to master. --- changelog | 3 +++ psyclone.pdf | Bin 1243230 -> 1265400 bytes 2 files changed, 3 insertions(+) diff --git a/changelog b/changelog index 4b9c2f66e7..e6f785d6de 100644 --- a/changelog +++ b/changelog @@ -1,3 +1,6 @@ + 7) #174 and PR #216. Adds the ability to generate an OpenCL PSy + layer in the gocean1.0 API (including an OpenCL transformation). + 6) #201 and PR #224. Add ability to write transformed kernels to file. Also ensures that kernel and psy-layer names match, allows an output directory to be specified and supports different output diff --git a/psyclone.pdf b/psyclone.pdf index 4539780094b87f328fa2c37400f95da61c0f5c0f..75ed1073e9a341e4066f27b22fee9da4eb0dc2d8 100644 GIT binary patch delta 246238 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[$%EaB+ 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 delta 228211 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