Source code for loopy.options

__copyright__ = "Copyright (C) 2013 Andreas Kloeckner"

__license__ = """
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""


from pytools import ImmutableRecord
import re
import os
from warnings import warn


ALLOW_TERMINAL_COLORS = True


class _ColoramaStub:
    def __getattribute__(self, name):
        return ""


def _apply_legacy_map(lmap, kwargs):
    result = {}

    for name, val in kwargs.items():
        try:
            lmap_value = lmap[name]
        except KeyError:
            new_name = name
        else:
            if lmap_value is None:
                # ignore this
                warn("option '%s' is deprecated and was ignored" % name,
                        DeprecationWarning)
                continue

            new_name, translator = lmap_value
            if name in result:
                raise TypeError("may not pass a value for both '%s' and '%s'"
                        % (name, new_name))

            warn(f"Loopy option '{name}' is deprecated. '{new_name}' should be "
                    "used instead. The old option will stop working in 2022.",
                    DeprecationWarning)
            if translator is not None:
                val = translator(val)

        result[new_name] = val

    return result


[docs]class Options(ImmutableRecord): """ Unless otherwise specified, these options are Boolean-valued (i.e. on/off). .. rubric:: Code-generation options .. attribute:: annotate_inames When generating code for inames, annotate them with comments if it is not immediately apparent which iname is being referred to (such as for inames mapped to constants or OpenCL group/local IDs). .. attribute:: trace_assignments Generate code that uses *printf* in kernels to trace the execution of assignment instructions. .. attribute:: trace_assignment_values Like :attr:`trace_assignments`, but also trace the assigned values. .. attribute:: check_dep_resolution Whether loopy should issue an error if a dependency expression does not match any instructions in the kernel. .. rubric:: Invocation-related options .. attribute:: skip_arg_checks Do not do any checking (data type, data layout, shape, etc.) on arguments for a minor performance gain. .. versionchanged:: 2021.1 This now defaults to the same value as the ``optimize`` sub-flag from :data:`sys.flags`. This flag can be controlled (i.e. set to *True*) by running Python with the ``-O`` flag. .. attribute:: no_numpy Do not check for or accept :mod:`numpy` arrays as arguments. Defaults to *False*. .. attribute:: cl_exec_manage_array_events Within the PyOpenCL executor, respect and udpate :attr:`pyopencl.array.Array.events`. Defaults to *True*. .. attribute:: return_dict Have kernels return a :class:`dict` instead of a tuple as output. Specifically, the result of a kernel invocation with this flag is a tuple ``(evt, out_dict)``, where *out_dict* is a dictionary mapping argument names to their output values. This is helpful if arguments are inferred and argument ordering is thus implementation-defined. See :meth:`CompiledKernel.__call__`. .. attribute:: write_wrapper Print the generated Python invocation wrapper. Accepts a file name as a value. Writes to ``sys.stdout`` if none is given. .. attribute:: write_code Print the generated code. Accepts a file name or a boolean as a value. Writes to ``sys.stdout`` if set to *True*. .. attribute:: edit_code Invoke an editor (given by the environment variable ``EDITOR``) on the generated kernel code, allowing for tweaks before the code is passed on to the target for compilation. .. attribute:: build_options Options to pass to the target compiler when building the kernel. A list of strings. .. attribute:: allow_terminal_colors A :class:`bool`. Whether to allow colors in terminal output .. rubric:: Features .. attribute:: disable_global_barriers .. attribute:: enforce_variable_access_ordered If *True*, require that :func:`loopy.check.check_variable_access_ordered` passes. Required for language versions 2018.1 and above. This check helps find and eliminate unintentionally unordered access to variables. If equal to ``"no_check"``, then no check is performed. .. attribute:: enforce_array_accesses_within_bounds If *True*, require that :func:`~loopy.check.check_bounds` passes. If *False*, then :func:`~loopy.check.check_bounds` raises a warning for any out-of-bounds accesses. If equal to ``"no_check"``, then no check is performed. """ _legacy_options_map = { "cl_build_options": ("build_options", None), "write_cl": ("write_code", None), "highlight_cl": None, "highlight_wrapper": None, "disable_wrapper_highlight": None, "disable_code_highlight": None, "edit_cl": ("edit_code", None), } def __init__( # All Boolean flags in here should default to False for the # string-based interface of make_options (below) to make sense. # All defaults are further required to be False when cast to bool # for the update() functionality to work. self, **kwargs): kwargs = _apply_legacy_map(self._legacy_options_map, kwargs) try: import colorama # noqa except ImportError: allow_terminal_colors_def = False else: allow_terminal_colors_def = True allow_terminal_colors_def = ( ALLOW_TERMINAL_COLORS and allow_terminal_colors_def) import sys ImmutableRecord.__init__( self, annotate_inames=kwargs.get("annotate_inames", False), trace_assignments=kwargs.get("trace_assignments", False), trace_assignment_values=kwargs.get("trace_assignment_values", False), skip_arg_checks=kwargs.get("skip_arg_checks", sys.flags.optimize # Not considered a documented env var: Only used to test # the skip_arg_checks branch during CI, which can't use # python -O. # # Considered enabled if non-empty. or bool(os.environ.get("_LOOPY_SKIP_ARG_CHECKS"))), no_numpy=kwargs.get("no_numpy", False), cl_exec_manage_array_events=kwargs.get("no_numpy", True), return_dict=kwargs.get("return_dict", False), write_wrapper=kwargs.get("write_wrapper", False), write_code=kwargs.get("write_code", False), edit_code=kwargs.get("edit_code", False), build_options=kwargs.get("build_options", []), allow_terminal_colors=kwargs.get("allow_terminal_colors", allow_terminal_colors_def), disable_global_barriers=kwargs.get("disable_global_barriers", False), check_dep_resolution=kwargs.get("check_dep_resolution", True), enforce_variable_access_ordered=kwargs.get( "enforce_variable_access_ordered", True), enforce_array_accesses_within_bounds=kwargs.get( "enforce_array_accesses_within_bounds", True), ) # {{{ legacy compatibility @property def edit_cl(self): return self.edit_code @property def cl_build_options(self): return self.build_options @property def highlight_cl(self): return self.allow_terminal_colors @property def highlight_wrapper(self): return self.allow_terminal_colors @property def write_cl(self): return self.write_code # }}} # only used internally on new copies of Options def _update(self, other): for f in self.__class__.fields: setattr(self, f, getattr(self, f) or getattr(other, f)) def update_persistent_hash(self, key_hash, key_builder): """Custom hash computation function for use with :class:`pytools.persistent_dict.PersistentDict`. """ for field_name in sorted(self.__class__.fields): key_builder.rec(key_hash, getattr(self, field_name)) @property def _fore(self): if self.allow_terminal_colors: import colorama return colorama.Fore else: return _ColoramaStub() @property def _back(self): if self.allow_terminal_colors: import colorama return colorama.Back else: return _ColoramaStub() @property def _style(self): if self.allow_terminal_colors: import colorama return colorama.Style else: return _ColoramaStub()
KEY_VAL_RE = re.compile("^([a-zA-Z0-9]+)=(.*)$") def make_options(options_arg): if options_arg is None: return Options() elif isinstance(options_arg, str): ioptions_args = {} for key_val in options_arg.split(","): kv_match = KEY_VAL_RE.match(key_val) if kv_match is not None: key = kv_match.group(1) val = kv_match.group(2) try: val = int(val) except ValueError: pass ioptions_args[key] = val else: ioptions_args[key_val] = True return Options(**ioptions_args) elif not isinstance(options_arg, Options): return Options(**options_arg) elif isinstance(options_arg, Options): return options_arg else: raise TypeError("invalid argument to make_options")