Reference Guide  2.5.0
opencl.py
1 # -----------------------------------------------------------------------------
2 # BSD 3-Clause License
3 #
4 # Copyright (c) 2019-2024, Science and Technology Facilities Council
5 # All rights reserved.
6 #
7 # Redistribution and use in source and binary forms, with or without
8 # modification, are permitted provided that the following conditions are met:
9 #
10 # * Redistributions of source code must retain the above copyright notice, this
11 # list of conditions and the following disclaimer.
12 #
13 # * Redistributions in binary form must reproduce the above copyright notice,
14 # this list of conditions and the following disclaimer in the documentation
15 # and/or other materials provided with the distribution.
16 #
17 # * Neither the name of the copyright holder nor the names of its
18 # contributors may be used to endorse or promote products derived from
19 # this software without specific prior written permission.
20 #
21 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25 # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32 # POSSIBILITY OF SUCH DAMAGE.
33 # -----------------------------------------------------------------------------
34 # Author S. Siso, STFC Daresbury Lab.
35 # Modified A. R. Porter, R. W. Ford and N. Nobre, STFC Daresbury Lab.
36 
37 '''OpenCL PSyIR backend. Extends the C PSyIR back-end to generate
38 OpenCL code from PSyIR nodes.
39 
40 '''
41 
42 from psyclone.psyir.backend.visitor import VisitorError
43 from psyclone.psyir.backend.c import CWriter
44 from psyclone.psyir.nodes import Literal
45 from psyclone.psyir.symbols import ScalarType, ArrayType
46 
47 
49  '''Implements a PSyIR-to-OpenCL back-end for the PSyIR AST. This writer
50  produces OpenCL code conforming to Version 1.2 of the specification
51  (https://www.khronos.org/registry/OpenCL/specs/opencl-1.2.pdf).
52 
53  :param bool skip_nodes: if skip_nodes is False then an exception \
54  is raised if a visitor method for a PSyIR node has not been \
55  implemented, otherwise the visitor silently continues. This is an \
56  optional argument which defaults to False.
57  :param str indent_string: specifies what to use for indentation. This \
58  is an optional argument that defaults to two spaces.
59  :param int initial_indent_depth: specifies how much indentation to \
60  start with. This is an optional argument that defaults to 0.
61  :param int kernel_local_size: uses the given local_size when generating \
62  OpenCL kernels.
63 
64  :raises TypeError: if kernel_local_size is not an integer.
65  :raises ValueError: if kernel_local_size is not positive.
66 
67  '''
68  def __init__(self, skip_nodes=False, indent_string=" ",
69  initial_indent_depth=0, kernels_local_size=1):
70 
71  super(OpenCLWriter, self).__init__(
72  skip_nodes, indent_string, initial_indent_depth)
73 
74  if not isinstance(kernels_local_size, int):
75  raise TypeError(
76  f"kernel_local_size should be an integer but found "
77  f"'{type(kernels_local_size).__name__}'.")
78 
79  if kernels_local_size < 1:
80  raise ValueError(
81  f"kernel_local_size should be a positive integer but found "
82  f"{kernels_local_size}.")
83 
84  self._kernels_local_size_kernels_local_size = kernels_local_size
85 
86  def gen_id_variable(self, symbol, dimension_index):
87  '''
88  Generate the declaration and initialisation of a variable identifying
89  an OpenCL work-item. IDs are initialised by the OpenCL function:
90  `size_t get_global_id(uint dimindx)`
91 
92  :param symbol: The symbol instance.
93  :type symbol: :py:class:`psyclone.psyir.symbols.DataSymbol`
94  :param int dimension_index: Dimension which the given symbol will \
95  iterate on.
96 
97  :returns: OpenCL declaration of an OpenCL id variable.
98  :rtype: str
99 
100  :raises VisitorError: if symbol is not a scalar integer
101  '''
102  if (not isinstance(symbol.datatype, ScalarType) or
103  symbol.datatype.intrinsic != ScalarType.Intrinsic.INTEGER):
104  raise VisitorError(
105  f"OpenCL work-item identifiers must be scalar integer symbols "
106  f"but found {symbol}.")
107 
108  code = ""
109  code += self._nindent_nindent + "int " + symbol.name
110  code += " = get_global_id(" + str(dimension_index) + ");\n"
111  return code
112 
113  def gen_declaration(self, symbol):
114  '''
115  Generate the declaration of an OpenCL variable. This can be either
116  a local variable or a routine argument, so no indention or punctuation
117  is generated by this method.
118 
119  Memory buffers reside in specific levels of the memory hierarchy,
120  and pointers are annotated with the region qualifiers __global,
121  __local, __constant, and __private, reflecting this.
122  At the moment all memory buffers (PSyIR arrays) are allocated at
123  the global address space.
124 
125  :param symbol: The symbol instance.
126  :type symbol: :py:class:`psyclone.psyir.symbols.DataSymbol`
127 
128  :returns: The OpenCL declaration of the given of the symbol.
129  :rtype: str
130 
131  :raises VisitorError: if an array is encountered that does not have \
132  a lower bound of 1 for all of its dimensions.
133  '''
134  prefix = ""
135  if symbol.shape:
136  for dim in symbol.shape:
137  if not isinstance(dim, ArrayType.ArrayBounds):
138  continue
139  if (not isinstance(dim.lower, Literal) or
140  dim.lower.value != "1"):
141  raise VisitorError(
142  f"The OpenCL backend only supports arrays with a lower"
143  f" bound of 1 in each dimension. However, array "
144  f"'{symbol.name}' has a lower bound of "
145  f"'{self._visit(dim.lower)}' for dimension "
146  f"{symbol.shape.index(dim)}")
147  prefix += "__global "
148  return prefix + super(OpenCLWriter, self).gen_declaration(symbol)
149 
150  def gen_array_length_variables(self, symbol, symtab=None):
151  '''
152  Generate length variable declaration and initialisation for each array
153  dimension of the given symbol. By convention they are named:
154  <name>LEN<DIM>, and initialised using the function:
155  `size_t get_global_size(uint dimindx)`.
156 
157 
158  :param symbol: The symbol instance.
159  :type symbol: :py:class:`psyclone.psyir.symbols.DataSymbol`
160  :param symtab: The symbol table from the given symbol context to \
161  check for name clashes.
162  :type symtab: :py:class:`psyclone.psyir.symbols.SymbolTable`
163 
164  :return: OpenCL declaration and initialisation of length variables.
165  :rtype: str
166 
167  :raises VisitorError: if the array length variable name clashes \
168  with another symbol name.
169  '''
170 
171  code = ""
172  dimensions = len(symbol.shape)
173  for dim in range(1, dimensions + 1):
174  code += self._nindent_nindent + "int "
175  varname = symbol.name + "LEN" + str(dim)
176 
177  # Check there is no clash with other variables
178  if symtab and varname in symtab:
179  raise VisitorError(
180  f"Unable to declare the variable '{varname}' to store the "
181  f"length of '{symbol.name}' because the Symbol Table "
182  f"already contains a symbol with the same name.")
183 
184  code += varname + " = get_global_size("
185  code += str(dim - 1) + ");\n"
186  return code
187 
188  def kernelschedule_node(self, node):
189  '''This method is called when a KernelSchedule instance is found in
190  the PSyIR tree.
191 
192  :param node: A KernelSchedule PSyIR node.
193  :type node: :py:class:`psyclone.psyir.nodes.KernelSchedule`
194 
195  :returns: The OpenCL code as a string.
196  :rtype: str
197 
198  :raises VisitorError: if a non-precision symbol is found with a \
199  UnresolvedInterface.
200  '''
201  # OpenCL implementation assumptions:
202  # - All array have the same size and it is given by the
203  # global_work_size argument to clEnqueueNDRangeKernel.
204  # - Assumes no dependencies among kernels called concurrently.
205  # - All real variables are 64-bit
206 
207  # TODO: At the moment, the method caller is responsible to ensure
208  # these assumptions. KernelSchedule access to the kernel
209  # meta-arguments could be used to check them and also improve the
210  # generated code. (Issue #288)
211 
212  symtab = node.symbol_table
213  data_args = symtab.data_arguments
214 
215  # Check that we know where everything in the symbol table
216  # comes from. TODO #592 ultimately precision symbols should
217  # be included in this check too as we will need to be able to
218  # map from them to the equivalent OpenCL type.
219  unresolved_datasymbols = list(set(symtab.unresolved_datasymbols) -
220  set(symtab.precision_datasymbols))
221  if unresolved_datasymbols:
222  symbols_txt = ", ".join(
223  [f"'{sym.name}'" for sym in unresolved_datasymbols])
224  raise VisitorError(
225  f"Cannot generate OpenCL because the symbol table contains "
226  f"unresolved data entries (i.e. that have no defined "
227  f"Interface) which are not used purely to define the "
228  f"precision of other symbols: {symbols_txt}")
229 
230  # Start OpenCL kernel definition
231  code = self._nindent_nindent
232  if self._kernels_local_size_kernels_local_size != 1:
233  code += f"__attribute__((reqd_work_group_size("\
234  f"{self._kernels_local_size}, 1, 1)))\n"
235  code += "__kernel void " + node.name + "(\n"
236  self._depth_depth += 1
237  arguments = []
238 
239  # Declare kernel arguments
240  for symbol in data_args:
241  arguments.append(self._nindent_nindent + self.gen_declarationgen_declarationgen_declaration(symbol))
242  code += ",\n".join(arguments) + "\n"
243  code += self._nindent_nindent + "){\n"
244 
245  # Declare local variables.
246  for symbol in symtab.automatic_datasymbols:
247  code += self.gen_local_variablegen_local_variable(symbol)
248 
249  # Declare array length
250  for symbol in data_args:
251  code += self.gen_array_length_variablesgen_array_length_variables(symbol, symtab)
252 
253  # Declare iteration indices
254  for index, symbol in enumerate(symtab.iteration_indices):
255  code += self.gen_id_variablegen_id_variable(symbol, index)
256 
257  # Generate kernel body
258  for child in node.children:
259  code += self._visit_visit(child)
260 
261  # Close kernel definition
262  self._depth_depth -= 1
263  code += self._nindent_nindent + "}\n\n"
264 
265  return code
def gen_local_variable(self, symbol)
Definition: c.py:153
def gen_declaration(self, symbol)
Definition: c.py:121
def gen_id_variable(self, symbol, dimension_index)
Definition: opencl.py:86
def gen_array_length_variables(self, symbol, symtab=None)
Definition: opencl.py:150