From 3e0393fde566fbf6d57795695aaf3a0df74c49bb Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Tue, 22 Jan 2019 01:01:49 -0600 Subject: [PATCH 1/4] Revive gitlab MR 297 by @mattwala --- doc/ref_kernel.rst | 2 ++ doc/tutorial.rst | 2 +- loopy/__init__.py | 4 +++ loopy/kernel/array.py | 5 +-- loopy/kernel/creation.py | 28 +++++++-------- loopy/kernel/data.py | 5 +-- loopy/kernel/instruction.py | 58 +++++++++++++++--------------- loopy/symbolic.py | 5 +-- loopy/tools.py | 72 +++++++++++++++++++++++++++++++++++++ test/test_misc.py | 40 +++++++++++++++++++++ 10 files changed, 170 insertions(+), 51 deletions(-) diff --git a/doc/ref_kernel.rst b/doc/ref_kernel.rst index 896388d29..460633109 100644 --- a/doc/ref_kernel.rst +++ b/doc/ref_kernel.rst @@ -553,6 +553,8 @@ Helper values .. autoclass:: UniqueName +.. autoclass:: Optional + .. }}} Libraries: Extending and Interfacing with External Functionality diff --git a/doc/tutorial.rst b/doc/tutorial.rst index 397f34a98..3c85060da 100644 --- a/doc/tutorial.rst +++ b/doc/tutorial.rst @@ -1869,7 +1869,7 @@ Now to make things more interesting, we'll create a kernel with barriers: ... e[i,j,k] = c[i,j,k+1]+c[i,j,k-1] ... """ ... ], [ - ... lp.TemporaryVariable("c", lp.auto, shape=(50, 10, 99)), + ... lp.TemporaryVariable("c", dtype=None, shape=(50, 10, 99)), ... "..." ... ]) >>> knl = lp.add_and_infer_dtypes(knl, dict(a=np.int32)) diff --git a/loopy/__init__.py b/loopy/__init__.py index f50ce237c..d69a57bf1 100644 --- a/loopy/__init__.py +++ b/loopy/__init__.py @@ -149,6 +149,8 @@ from loopy.target.pyopencl import PyOpenCLTarget from loopy.target.ispc import ISPCTarget from loopy.target.numba import NumbaTarget, NumbaCudaTarget +from loopy.tools import Optional + __all__ = [ "TaggedVariable", "Reduction", "LinearSubscript", "TypeCast", @@ -275,6 +277,8 @@ __all__ = [ "NumbaTarget", "NumbaCudaTarget", "ASTBuilderBase", + "Optional", + # {{{ from this file "register_preamble_generators", diff --git a/loopy/kernel/array.py b/loopy/kernel/array.py index bae9d7d1f..3588f38af 100644 --- a/loopy/kernel/array.py +++ b/loopy/kernel/array.py @@ -693,8 +693,9 @@ class ArrayBase(ImmutableRecord): if dtype is lp.auto: from warnings import warn - warn("Argument/temporary data type should be None if unspecified, " - "not auto. This usage will be disallowed in 2018.", + warn("Argument/temporary data type for '%s' should be None if " + "unspecified, not auto. This usage will be disallowed in 2018." + % name, DeprecationWarning, stacklevel=2) dtype = None diff --git a/loopy/kernel/creation.py b/loopy/kernel/creation.py index c42db3482..2175b5f36 100644 --- a/loopy/kernel/creation.py +++ b/loopy/kernel/creation.py @@ -28,7 +28,7 @@ THE SOFTWARE. import numpy as np from pymbolic.mapper import CSECachingMapperMixin -from loopy.tools import intern_frozenset_of_ids +from loopy.tools import intern_frozenset_of_ids, Optional from loopy.symbolic import IdentityMapper, WalkMapper from loopy.kernel.data import ( InstructionBase, @@ -454,7 +454,6 @@ def parse_insn(groups, insn_options): and *inames_to_dup* is None or a list of tuples `(old, new)`. """ - import loopy as lp from loopy.symbolic import parse if "lhs" in groups: @@ -486,14 +485,11 @@ def parse_insn(groups, insn_options): for lhs_i in lhs: if isinstance(lhs_i, TypeAnnotation): - if lhs_i.type is None: - temp_var_types.append(lp.auto) - else: - temp_var_types.append(lhs_i.type) - + assert isinstance(lhs_i.type, Optional) + temp_var_types.append(lhs_i.type) lhs_i = lhs_i.child else: - temp_var_types.append(None) + temp_var_types.append(Optional()) inner_lhs_i = lhs_i if isinstance(inner_lhs_i, Lookup): @@ -1138,9 +1134,9 @@ class ArgumentGuesser: def make_new_arg(self, arg_name): arg_name = arg_name.strip() + import loopy as lp from loopy.kernel.data import ValueArg, ArrayArg, AddressSpace - import loopy as lp if arg_name in self.all_params: return ValueArg(arg_name) @@ -1191,7 +1187,7 @@ class ArgumentGuesser: for assignee_var_name, temp_var_type in zip( insn.assignee_var_names(), insn.temp_var_types): - if temp_var_type is not None: + if temp_var_type.has_value: temp_var_names.add(assignee_var_name) # }}} @@ -1431,7 +1427,7 @@ def create_temporaries(knl, default_order): insn.assignee_var_names(), insn.temp_var_types): - if temp_var_type is None: + if not temp_var_type.has_value: continue if assignee_name in new_temp_vars: @@ -1446,17 +1442,17 @@ def create_temporaries(knl, default_order): new_temp_vars[assignee_name] = lp.TemporaryVariable( name=assignee_name, - dtype=temp_var_type, + dtype=temp_var_type.value, address_space=lp.auto, base_indices=lp.auto, shape=lp.auto, order=default_order, target=knl.target) - if isinstance(insn, Assignment): - insn = insn.copy(temp_var_type=None) - else: - insn = insn.copy(temp_var_types=None) + if isinstance(insn, Assignment): + insn = insn.copy(temp_var_type=Optional()) + else: + insn = insn.copy(temp_var_types=(Optional(),) * len(insn.assignees)) new_insns.append(insn) diff --git a/loopy/kernel/data.py b/loopy/kernel/data.py index 7877f8b93..8103029dc 100644 --- a/loopy/kernel/data.py +++ b/loopy/kernel/data.py @@ -330,8 +330,9 @@ class KernelArgument(ImmutableRecord): import loopy as lp if dtype is lp.auto: - warn("Argument/temporary data type should be None if unspecified, " - "not auto. This usage will be disallowed in 2018.", + warn("Argument/temporary data type for '%s' should be None if " + "unspecified, not auto. This usage will be disallowed in 2018." + % kwargs["name"], DeprecationWarning, stacklevel=2) dtype = None diff --git a/loopy/kernel/instruction.py b/loopy/kernel/instruction.py index e9c7bde9f..d7784eabf 100644 --- a/loopy/kernel/instruction.py +++ b/loopy/kernel/instruction.py @@ -25,6 +25,7 @@ THE SOFTWARE. from six.moves import intern from pytools import ImmutableRecord, memoize_method from loopy.diagnostic import LoopyError +from loopy.tools import Optional from warnings import warn @@ -838,8 +839,9 @@ class Assignment(MultiAssignmentBase): .. attribute:: temp_var_type - if not *None*, a type that will be assigned to the new temporary variable - created from the assignee + A :class:`loopy.Optional`. If not empty, contains the type that + will be assigned to the new temporary variable created from the + assignment. .. attribute:: atomicity @@ -893,7 +895,7 @@ class Assignment(MultiAssignmentBase): within_inames_is_final=None, within_inames=None, boostable=None, boostable_into=None, tags=None, - temp_var_type=None, atomicity=(), + temp_var_type=Optional(), atomicity=(), priority=0, predicates=frozenset(), insn_deps=None, insn_deps_is_final=None, forced_iname_deps=None, forced_iname_deps_is_final=None): @@ -1006,8 +1008,9 @@ class CallInstruction(MultiAssignmentBase): .. attribute:: temp_var_types - if not *None*, a type that will be assigned to the new temporary variable - created from the assignee + A tuple of `:class:loopy.Optional`. If an entry is not empty, it + contains the type that will be assigned to the new temporary variable + created from the assigment. .. automethod:: __init__ """ @@ -1079,7 +1082,7 @@ class CallInstruction(MultiAssignmentBase): self.expression = expression if temp_var_types is None: - self.temp_var_types = (None,) * len(self.assignees) + self.temp_var_types = (Optional(),) * len(self.assignees) else: self.temp_var_types = temp_var_types @@ -1127,34 +1130,33 @@ class CallInstruction(MultiAssignmentBase): def make_assignment(assignees, expression, temp_var_types=None, **kwargs): - if len(assignees) > 1 or len(assignees) == 0: - atomicity = kwargs.pop("atomicity", ()) - if atomicity: - raise LoopyError("atomic operations with more than one " - "left-hand side not supported") + if temp_var_types is None: + temp_var_types = (Optional(),) * len(assignees) - from pymbolic.primitives import Call - from loopy.symbolic import Reduction - if not isinstance(expression, (Call, Reduction)): - raise LoopyError("right-hand side in multiple assignment must be " - "function call or reduction, got: '%s'" % expression) - - return CallInstruction( - assignees=assignees, - expression=expression, - temp_var_types=temp_var_types, - **kwargs) - - else: + if len(assignees) == 1: return Assignment( assignee=assignees[0], expression=expression, - temp_var_type=( - temp_var_types[0] - if temp_var_types is not None - else None), + temp_var_type=temp_var_types[0], **kwargs) + atomicity = kwargs.pop("atomicity", ()) + if atomicity: + raise LoopyError("atomic operations with more than one " + "left-hand side not supported") + + from pymbolic.primitives import Call + from loopy.symbolic import Reduction + if not isinstance(expression, (Call, Reduction)): + raise LoopyError("right-hand side in multiple assignment must be " + "function call or reduction, got: '%s'" % expression) + + return CallInstruction( + assignees=assignees, + expression=expression, + temp_var_types=temp_var_types, + **kwargs) + # {{{ c instruction diff --git a/loopy/symbolic.py b/loopy/symbolic.py index f4d46854b..ae0c8999e 100644 --- a/loopy/symbolic.py +++ b/loopy/symbolic.py @@ -1131,14 +1131,15 @@ class LoopyParser(ParserBase): def parse_prefix(self, pstate): from pymbolic.parser import _PREC_UNARY, _less, _greater, _identifier + import loopy as lp if pstate.is_next(_less): pstate.advance() if pstate.is_next(_greater): - typename = None + typename = lp.Optional(None) pstate.advance() else: pstate.expect(_identifier) - typename = pstate.next_str() + typename = lp.Optional(pstate.next_str()) pstate.advance() pstate.expect(_greater) pstate.advance() diff --git a/loopy/tools.py b/loopy/tools.py index 7e9a89214..470921e31 100644 --- a/loopy/tools.py +++ b/loopy/tools.py @@ -587,6 +587,78 @@ class LazilyUnpicklingListWithEqAndPersistentHashing(LazilyUnpicklingList): # }}} +# {{{ optional object + +class _no_value(object): # noqa + pass + + +class Optional(object): + """A wrapper for an optionally present object. + + .. attribute:: has_value + + *True* if and only if this object contains a value. + + .. attribute:: value + + The value, if present. + """ + + __slots__ = ("has_value", "_value") + + def __init__(self, value=_no_value): + self.has_value = value is not _no_value + if self.has_value: + self._value = value + + def __str__(self): + if not self.has_value: + return "Optional()" + return "Optional(%s)" % self._value + + def __repr__(self): + if not self.has_value: + return "Optional()" + return "Optional(%r)" % self._value + + def __getstate__(self): + if not self.has_value: + return _no_value + + return (self._value,) + + def __setstate__(self, state): + if state is _no_value: + self.has_value = False + return + + self.has_value = True + self._value, = state + + def __eq__(self, other): + if not self.has_value: + return not other.has_value + + return self.value == other.value if other.has_value else False + + def __neq__(self, other): + return not self.__eq__(other) + + @property + def value(self): + if not self.has_value: + raise AttributeError("optional value not present") + return self._value + + def update_persistent_hash(self, key_hash, key_builder): + key_builder.rec( + key_hash, + (self._value,) if self.has_value else ()) + +# }}} + + def unpickles_equally(obj): from six.moves.cPickle import loads, dumps return loads(dumps(obj)) == obj diff --git a/test/test_misc.py b/test/test_misc.py index 05df0317a..7a834a6f5 100644 --- a/test/test_misc.py +++ b/test/test_misc.py @@ -287,6 +287,46 @@ def test_LazilyUnpicklingListWithEqAndPersistentHashing(): # }}} +def test_Optional(): # noqa + from loopy import Optional + + # {{{ test API + + opt = Optional() + assert not opt.has_value + with pytest.raises(AttributeError): + opt.value + + opt = Optional(1) + assert opt.has_value + assert 1 == opt.value + + assert Optional(1) == Optional(1) + assert Optional(1) != Optional(2) + assert Optional() == Optional() + assert Optional() != Optional(1) + + # }}} + + # {{{ test pickling + + import pickle + + assert not pickle.loads(pickle.dumps(Optional())).has_value + assert pickle.loads(pickle.dumps(Optional(1))).value == 1 + + # }}} + + # {{{ test key builder + + from loopy.tools import LoopyKeyBuilder + kb = LoopyKeyBuilder() + kb(Optional()) + kb(Optional(None)) + + # }}} + + if __name__ == "__main__": if len(sys.argv) > 1: exec(sys.argv[1]) -- GitLab From 7f440875c8ea448eecaa15d5a30690b1bed91a65 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Tue, 22 Jan 2019 10:27:26 -0600 Subject: [PATCH 2/4] Catch lp.auto as argument to temp_var_type --- loopy/kernel/instruction.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/loopy/kernel/instruction.py b/loopy/kernel/instruction.py index d7784eabf..543d8e637 100644 --- a/loopy/kernel/instruction.py +++ b/loopy/kernel/instruction.py @@ -932,6 +932,14 @@ class Assignment(MultiAssignmentBase): self.assignee = assignee self.expression = expression + + import loopy as lp + if temp_var_type is lp.auto: + warn("temp_var_type should be Optional(None) if " + "unspecified, not auto. This usage will be disallowed soon.", + DeprecationWarning, stacklevel=2) + temp_var_type = lp.Optional(None) + self.temp_var_type = temp_var_type self.atomicity = atomicity @@ -1084,7 +1092,17 @@ class CallInstruction(MultiAssignmentBase): if temp_var_types is None: self.temp_var_types = (Optional(),) * len(self.assignees) else: - self.temp_var_types = temp_var_types + import loopy as lp + processed_temp_var_types = [] + for temp_var_type in temp_var_types: + if temp_var_type is lp.auto: + warn("temp_var_type should be Optional(None) if " + "unspecified, not auto. This usage will be disallowed soon.", + DeprecationWarning, stacklevel=2) + temp_var_type = lp.Optional(None) + processed_temp_var_types.append(temp_var_type) + + self.temp_var_types = tuple(processed_temp_var_types) # {{{ implement InstructionBase interface -- GitLab From e057bdbc79b2563f4fe93f738c96de942c41f8a4 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Wed, 23 Jan 2019 14:37:59 -0600 Subject: [PATCH 3/4] Fix line length --- loopy/kernel/instruction.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/loopy/kernel/instruction.py b/loopy/kernel/instruction.py index 543d8e637..6b0fa64db 100644 --- a/loopy/kernel/instruction.py +++ b/loopy/kernel/instruction.py @@ -1097,7 +1097,8 @@ class CallInstruction(MultiAssignmentBase): for temp_var_type in temp_var_types: if temp_var_type is lp.auto: warn("temp_var_type should be Optional(None) if " - "unspecified, not auto. This usage will be disallowed soon.", + "unspecified, not auto. " + "This usage will be disallowed soon.", DeprecationWarning, stacklevel=2) temp_var_type = lp.Optional(None) processed_temp_var_types.append(temp_var_type) -- GitLab From 26ccb262eab73b9a6648f0568ebf525e48b3a293 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Wed, 23 Jan 2019 15:20:56 -0600 Subject: [PATCH 4/4] Fix the case that temp_var_type is supplied as a dtype --- loopy/kernel/instruction.py | 47 +++++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/loopy/kernel/instruction.py b/loopy/kernel/instruction.py index 6b0fa64db..bfc06cff5 100644 --- a/loopy/kernel/instruction.py +++ b/loopy/kernel/instruction.py @@ -933,14 +933,7 @@ class Assignment(MultiAssignmentBase): self.assignee = assignee self.expression = expression - import loopy as lp - if temp_var_type is lp.auto: - warn("temp_var_type should be Optional(None) if " - "unspecified, not auto. This usage will be disallowed soon.", - DeprecationWarning, stacklevel=2) - temp_var_type = lp.Optional(None) - - self.temp_var_type = temp_var_type + self.temp_var_type = _check_and_fix_temp_var_type(temp_var_type) self.atomicity = atomicity # {{{ implement InstructionBase interface @@ -1092,18 +1085,8 @@ class CallInstruction(MultiAssignmentBase): if temp_var_types is None: self.temp_var_types = (Optional(),) * len(self.assignees) else: - import loopy as lp - processed_temp_var_types = [] - for temp_var_type in temp_var_types: - if temp_var_type is lp.auto: - warn("temp_var_type should be Optional(None) if " - "unspecified, not auto. " - "This usage will be disallowed soon.", - DeprecationWarning, stacklevel=2) - temp_var_type = lp.Optional(None) - processed_temp_var_types.append(temp_var_type) - - self.temp_var_types = tuple(processed_temp_var_types) + self.temp_var_types = tuple( + _check_and_fix_temp_var_type(tvt) for tvt in temp_var_types) # {{{ implement InstructionBase interface @@ -1512,4 +1495,28 @@ def _get_insn_hash_key(insn): # }}} +# {{{ _check_and_fix_temp_var_type + +def _check_and_fix_temp_var_type(temp_var_type): + """Check temp_var_type for deprecated usage, and convert to the right value. + """ + + if temp_var_type is not None: + import loopy as lp + if temp_var_type is lp.auto: + warn("temp_var_type should be Optional(None) if " + "unspecified, not auto. This usage will be disallowed soon.", + DeprecationWarning, stacklevel=3) + temp_var_type = lp.Optional(None) + elif not isinstance(temp_var_type, lp.Optional): + warn("temp_var_type should be None or an instance of Optional. " + "Other values for temp_var_type will be disallowed soon.", + DeprecationWarning, stacklevel=3) + temp_var_type = lp.Optional(temp_var_type) + + return temp_var_type + +# }}} + + # vim: foldmethod=marker -- GitLab