From 6f0658417391e7e840f55fe45c697fd4a5ee9bef Mon Sep 17 00:00:00 2001 From: "jq.yang@berkeley.edu" Date: Sat, 17 Oct 2020 01:04:59 -0400 Subject: [PATCH 01/10] add Context manager and default context under threading --- islpy/__init__.py | 88 +++++++++++++++++++++++++++++++++++++++-------- test/test_isl.py | 43 ++++++++++++++++++++--- 2 files changed, 111 insertions(+), 20 deletions(-) diff --git a/islpy/__init__.py b/islpy/__init__.py index c998af9..df7feee 100644 --- a/islpy/__init__.py +++ b/islpy/__init__.py @@ -20,6 +20,8 @@ THE SOFTWARE. """ +import sys + import islpy._isl as _isl from islpy.version import VERSION, VERSION_TEXT # noqa import six @@ -145,14 +147,65 @@ EXPR_CLASSES = tuple(cls for cls in ALL_CLASSES if "Aff" in cls.__name__ or "Polynomial" in cls.__name__) -DEFAULT_CONTEXT = Context() +def _module_property(func): + """Decorator to turn module functions into properties. + Function names must be prefixed with an underscore.""" + module = sys.modules[func.__module__] -def _get_default_context(): - """A callable to get the default context for the benefit of Python's - ``__reduce__`` protocol. - """ - return DEFAULT_CONTEXT + def base_getattr(name): + raise AttributeError( + f"module '{module.__name__}' has no attribute '{name}'") + + old_getattr = getattr(module, '__getattr__', base_getattr) + + def new_getattr(name): + if f'_{name}' == func.__name__: + return func() + else: + return old_getattr(name) + + module.__getattr__ = new_getattr + return func + + +import threading + + +_thread_local_storage = threading.local() + + +def _check_init_default_context(): + if not hasattr(_thread_local_storage, "islpy_default_contexts"): + _thread_local_storage.islpy_default_contexts = [Context()] + + +def get_default_context(): + """Get or create the default context under current thread.""" + _check_init_default_context() + return _thread_local_storage.islpy_default_contexts[-1] + + +import contextlib + + +@contextlib.contextmanager +def push_context(ctx=None): + if ctx is None: + ctx = Context() + _check_init_default_context() + _thread_local_storage.islpy_default_contexts.append(ctx) + yield ctx + _thread_local_storage.islpy_default_contexts.pop() + + +@_module_property +def _DEFAULT_CONTEXT(): + from warnings import warn + warn("Use of islpy.DEFAULT_CONTEXT is deprecated and will be removed in the future. " + "Please use `islpy.get_default_context()` instead. ", FutureWarning, + stacklevel=3) + return get_default_context() def _read_from_str_wrapper(cls, context, s): @@ -168,10 +221,14 @@ def _add_functionality(): # {{{ Context def context_reduce(self): - if self._wraps_same_instance_as(DEFAULT_CONTEXT): - return (_get_default_context, ()) - else: - return (Context, ()) + return (get_default_context, ()) + + def context_copy(self): + return self + + def context_deepcopy(self, memo): + del memo + return self def context_eq(self, other): return isinstance(other, Context) and self._wraps_same_instance_as(other) @@ -180,9 +237,10 @@ def context_ne(self, other): return not self.__eq__(other) Context.__reduce__ = context_reduce + Context.__copy__ = context_copy + Context.__deepcopy__ = context_deepcopy Context.__eq__ = context_eq Context.__ne__ = context_ne - # }}} # {{{ generic initialization, pickling @@ -197,7 +255,7 @@ def obj_new(cls, s=None, context=None): return cls._prev_new(cls) if context is None: - context = DEFAULT_CONTEXT + context = get_default_context() result = cls.read_from_str(context, s) return result @@ -473,7 +531,7 @@ def obj_get_coefficients_by_name(self, dimtype=None, dim_to_name=None): def id_new(cls, name, user=None, context=None): if context is None: - context = DEFAULT_CONTEXT + context = get_default_context() result = cls.alloc(context, name, user) result._made_from_python = True @@ -777,7 +835,7 @@ def expr_like_floordiv(self, other): def val_new(cls, src, context=None): if context is None: - context = DEFAULT_CONTEXT + context = get_default_context() if isinstance(src, six.string_types): result = cls.read_from_str(context, src) @@ -1274,7 +1332,7 @@ def make_zero_and_vars(set_vars, params=[], ctx=None): ) """ if ctx is None: - ctx = DEFAULT_CONTEXT + ctx = get_default_context() if isinstance(set_vars, str): set_vars = [s.strip() for s in set_vars.split(",")] diff --git a/test/test_isl.py b/test/test_isl.py index 0b6002d..ae6705d 100644 --- a/test/test_isl.py +++ b/test/test_isl.py @@ -189,11 +189,11 @@ def cb_print_for(printer, options, node): printer = printer.print_str("Callback For") return printer - opts = isl.AstPrintOptions.alloc(isl.DEFAULT_CONTEXT) + opts = isl.AstPrintOptions.alloc(isl.get_default_context()) opts, cb_print_user_handle = opts.set_print_user(cb_print_user) opts, cb_print_for_handle = opts.set_print_for(cb_print_for) - printer = isl.Printer.to_str(isl.DEFAULT_CONTEXT) + printer = isl.Printer.to_str(isl.get_default_context()) printer = printer.set_output_format(isl.format.C) printer.print_str("// Start\n") printer = ast.print_(printer, opts) @@ -248,7 +248,7 @@ def isl_ast_codegen(S): # noqa: N803 m = isl.Map.identity(m.get_space()) m = isl.Map.from_domain(S) ast = b.ast_from_schedule(m) - p = isl.Printer.to_str(isl.DEFAULT_CONTEXT) + p = isl.Printer.to_str(isl.get_default_context()) p = p.set_output_format(isl.format.C) p.flush() p = p.print_ast_node(ast) @@ -362,8 +362,41 @@ def test_bound(): def test_copy_context(): ctx = isl.Context() import copy - assert not ctx._wraps_same_instance_as(copy.copy(ctx)) - assert not isl.DEFAULT_CONTEXT._wraps_same_instance_as(copy.copy(ctx)) + assert ctx._wraps_same_instance_as(copy.copy(ctx)) + assert ctx == copy.copy(ctx) + assert not isl.get_default_context()._wraps_same_instance_as(copy.copy(ctx)) + + +def test_context_manager(): + import copy + import pickle + + def transfer_copy(obj): + return pickle.loads(pickle.dumps(obj)) + + b1 = isl.BasicSet("{ [0] : }") + old_dctx = isl.get_default_context() + assert b1.get_ctx() == old_dctx + + with isl.push_context() as dctx: + assert dctx == isl.get_default_context() + assert not old_dctx._wraps_same_instance_as(dctx) + b2 = isl.BasicSet("{ [0] : }") + assert b2.get_ctx() == dctx + # Under context manager always use `dctx` + assert transfer_copy(b2).get_ctx() == transfer_copy(b1).get_ctx() == dctx + + # Check for proper exit + assert old_dctx == isl.get_default_context() + + # Check for nested context + with isl.push_context() as c1: + with isl.push_context() as c2: + assert c1 != c2 + with isl.push_context() as c3: + assert c2 != c3 + # Check for proper exit + assert old_dctx == isl.get_default_context() def test_ast_node_list_free(): From b0be76744835b092991b42597457b552ff2b6ebf Mon Sep 17 00:00:00 2001 From: "jq.yang@berkeley.edu" Date: Sat, 17 Oct 2020 14:43:36 -0400 Subject: [PATCH 02/10] fix flake8 --- islpy/__init__.py | 12 +++++++----- test/test_isl.py | 1 - 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/islpy/__init__.py b/islpy/__init__.py index df7feee..126c378 100644 --- a/islpy/__init__.py +++ b/islpy/__init__.py @@ -157,10 +157,10 @@ def base_getattr(name): raise AttributeError( f"module '{module.__name__}' has no attribute '{name}'") - old_getattr = getattr(module, '__getattr__', base_getattr) + old_getattr = getattr(module, "__getattr__", base_getattr) def new_getattr(name): - if f'_{name}' == func.__name__: + if f"_{name}" == func.__name__: return func() else: return old_getattr(name) @@ -200,10 +200,12 @@ def push_context(ctx=None): @_module_property -def _DEFAULT_CONTEXT(): +def _DEFAULT_CONTEXT(): # noqa: N802 from warnings import warn - warn("Use of islpy.DEFAULT_CONTEXT is deprecated and will be removed in the future. " - "Please use `islpy.get_default_context()` instead. ", FutureWarning, + warn("Use of islpy.DEFAULT_CONTEXT is deprecated " + "and will be removed in the future." + " Please use `islpy.get_default_context()` instead. ", + FutureWarning, stacklevel=3) return get_default_context() diff --git a/test/test_isl.py b/test/test_isl.py index ae6705d..a2c065f 100644 --- a/test/test_isl.py +++ b/test/test_isl.py @@ -368,7 +368,6 @@ def test_copy_context(): def test_context_manager(): - import copy import pickle def transfer_copy(obj): From cc95c56a436152290d6229b14c481dbcae0be271 Mon Sep 17 00:00:00 2001 From: Cambridge Yang Date: Sun, 18 Oct 2020 20:21:33 -0400 Subject: [PATCH 03/10] Update islpy/__init__.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix future warning date of DEFAULT_CONTEXT Co-authored-by: Andreas Klöckner --- islpy/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/islpy/__init__.py b/islpy/__init__.py index 126c378..03d0495 100644 --- a/islpy/__init__.py +++ b/islpy/__init__.py @@ -203,7 +203,7 @@ def push_context(ctx=None): def _DEFAULT_CONTEXT(): # noqa: N802 from warnings import warn warn("Use of islpy.DEFAULT_CONTEXT is deprecated " - "and will be removed in the future." + "and will be removed in 2022." " Please use `islpy.get_default_context()` instead. ", FutureWarning, stacklevel=3) From 345142f1a2351d62890006fca0d0b3777525f9ef Mon Sep 17 00:00:00 2001 From: Cambridge Yang Date: Mon, 19 Oct 2020 02:14:07 -0400 Subject: [PATCH 04/10] add documentation and fixes comments --- doc/ref_fundamental.rst | 5 +++++ doc/reference.rst | 17 ++++++++++++++--- islpy/__init__.py | 42 ++++++++++++++++++++++++++++++++++++++++- test/test_isl.py | 7 +++++++ 4 files changed, 67 insertions(+), 4 deletions(-) diff --git a/doc/ref_fundamental.rst b/doc/ref_fundamental.rst index f769771..0df8096 100644 --- a/doc/ref_fundamental.rst +++ b/doc/ref_fundamental.rst @@ -6,6 +6,11 @@ Reference: Basic Building Blocks Context ------- +.. note:: this class implements Python's ``__copy__`` and ``__deepcopy__`` + protocals. Copying of objects of this class always returns the identity. + +.. seealso:: :ref:`sec-context-management` + .. autoclass:: Context() :members: diff --git a/doc/reference.rst b/doc/reference.rst index 37d6f47..58b3bea 100644 --- a/doc/reference.rst +++ b/doc/reference.rst @@ -88,15 +88,26 @@ Lifetime Helpers function call to which they're passed. These callback return a callback handle that must be kept alive until the callback is no longer needed. -Global Data -^^^^^^^^^^^ +.. _sec-context-management: -.. data:: DEFAULT_CONTEXT +Context Management +^^^^^^^^^^^^^^^^^^ + +.. autofunction:: get_default_context + +.. autofunction:: push_context +.. data:: DEFAULT_CONTEXT + ISL objects being unpickled or initialized from strings will be instantiated within this :class:`Context`. + .. seealso:: :func:`get_default_context` + .. versionadded:: 2015.2 + .. deprecated:: TODO_VERSION + + Symbolic Constants ^^^^^^^^^^^^^^^^^^ diff --git a/islpy/__init__.py b/islpy/__init__.py index 03d0495..5d91ed8 100644 --- a/islpy/__init__.py +++ b/islpy/__init__.py @@ -181,16 +181,52 @@ def _check_init_default_context(): def get_default_context(): - """Get or create the default context under current thread.""" + """Get or create the default context under current thread. + + :return: the current default :class:`Context` + + .. versionadded:: TODO_VERSION + """ _check_init_default_context() return _thread_local_storage.islpy_default_contexts[-1] +def _get_default_context(): + from warnings import warn + warn("It appears that you might be deserializing an islpy.Context" + "that was serialized by a previous version of islpy." + "If so, this is discouraged and please consider to re-serialize" + "the Context with the newer version to avoid possible inconsistencies.", + UserWarning) + return get_default_context() + + import contextlib @contextlib.contextmanager def push_context(ctx=None): + """Context manager to push new default :class:`Context` + + :param ctx: an optional explicit context that is pushed to + the stack of default :class:`Context` s + + .. versionadded:: TODO_VERSION + + :mod:`islpy` internally maintains a stack of default :class:`Context` s + for each Python thread. + By default, each stack is initialized with a base default :class:`Context`. + ISL objects being unpickled or initialized from strings will be + instantiated within the top :class:`Context` of the stack of + the executing thread. + + Usage example:: + + with islpy.push_context() as dctx: + s = islpy.Set("{[0]: }") + assert s.get_ctx() == dctx + + """ if ctx is None: ctx = Context() _check_init_default_context() @@ -210,6 +246,10 @@ def _DEFAULT_CONTEXT(): # noqa: N802 return get_default_context() +if sys.version_info < (3, 7): + DEFAULT_CONTEXT = get_default_context() + + def _read_from_str_wrapper(cls, context, s): """A callable to reconstitute instances from strings for the benefit of Python's ``__reduce__`` protocol. diff --git a/test/test_isl.py b/test/test_isl.py index a2c065f..62e2aea 100644 --- a/test/test_isl.py +++ b/test/test_isl.py @@ -398,6 +398,13 @@ def transfer_copy(obj): assert old_dctx == isl.get_default_context() +def test_deprecated_default_context(): + import warnings + with warnings.catch_warnings(): + dctx = isl.DEFAULT_CONTEXT + assert dctx == isl.get_default_context() + + def test_ast_node_list_free(): # from https://github.com/inducer/islpy/issues/21 # by Cambridge Yang From f9083b7b2939184474e027611c8de53155cf5311 Mon Sep 17 00:00:00 2001 From: Cambridge Yang Date: Mon, 19 Oct 2020 13:04:58 -0400 Subject: [PATCH 05/10] Update doc/ref_fundamental.rst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Andreas Klöckner --- doc/ref_fundamental.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ref_fundamental.rst b/doc/ref_fundamental.rst index 0df8096..21939bf 100644 --- a/doc/ref_fundamental.rst +++ b/doc/ref_fundamental.rst @@ -7,7 +7,7 @@ Context ------- .. note:: this class implements Python's ``__copy__`` and ``__deepcopy__`` - protocals. Copying of objects of this class always returns the identity. + protocols. Each of these returns the context being 'copied' identically. .. seealso:: :ref:`sec-context-management` From 113df5fe7d2033ed97d8629d6710b1af7aa51004 Mon Sep 17 00:00:00 2001 From: Cambridge Yang Date: Mon, 19 Oct 2020 13:05:32 -0400 Subject: [PATCH 06/10] Update doc/reference.rst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Andreas Klöckner --- doc/reference.rst | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/doc/reference.rst b/doc/reference.rst index 58b3bea..3a7b5e7 100644 --- a/doc/reference.rst +++ b/doc/reference.rst @@ -97,17 +97,6 @@ Context Management .. autofunction:: push_context -.. data:: DEFAULT_CONTEXT - - ISL objects being unpickled or initialized from strings will be instantiated - within this :class:`Context`. - - .. seealso:: :func:`get_default_context` - - .. versionadded:: 2015.2 - .. deprecated:: TODO_VERSION - - Symbolic Constants ^^^^^^^^^^^^^^^^^^ From 74a0da7867c61ff8bbeeccb8d267a79b6e2694a3 Mon Sep 17 00:00:00 2001 From: Cambridge Yang Date: Mon, 19 Oct 2020 13:05:41 -0400 Subject: [PATCH 07/10] Update islpy/__init__.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Andreas Klöckner --- islpy/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/islpy/__init__.py b/islpy/__init__.py index 5d91ed8..23d8e98 100644 --- a/islpy/__init__.py +++ b/islpy/__init__.py @@ -185,7 +185,7 @@ def get_default_context(): :return: the current default :class:`Context` - .. versionadded:: TODO_VERSION + .. versionadded:: 2020.3 """ _check_init_default_context() return _thread_local_storage.islpy_default_contexts[-1] From ac4f449a808f95ed5be45de5c26e9c0064361ced Mon Sep 17 00:00:00 2001 From: Cambridge Yang Date: Mon, 19 Oct 2020 13:10:32 -0400 Subject: [PATCH 08/10] Update islpy/__init__.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Andreas Klöckner --- islpy/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/islpy/__init__.py b/islpy/__init__.py index 23d8e98..7f9377d 100644 --- a/islpy/__init__.py +++ b/islpy/__init__.py @@ -211,7 +211,7 @@ def push_context(ctx=None): :param ctx: an optional explicit context that is pushed to the stack of default :class:`Context` s - .. versionadded:: TODO_VERSION + .. versionadded:: 2020.3 :mod:`islpy` internally maintains a stack of default :class:`Context` s for each Python thread. From 96a5af2c7daf1de6294be45fd65d8d12954d1c9a Mon Sep 17 00:00:00 2001 From: Cambridge Yang Date: Mon, 19 Oct 2020 13:12:03 -0400 Subject: [PATCH 09/10] update docs --- doc/ref_fundamental.rst | 3 +++ islpy/__init__.py | 4 +--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/doc/ref_fundamental.rst b/doc/ref_fundamental.rst index 21939bf..f9c0dda 100644 --- a/doc/ref_fundamental.rst +++ b/doc/ref_fundamental.rst @@ -9,6 +9,9 @@ Context .. note:: this class implements Python's ``__copy__`` and ``__deepcopy__`` protocols. Each of these returns the context being 'copied' identically. +.. note:: during an unpickle operation, the current default :class:`Context` + is always used. + .. seealso:: :ref:`sec-context-management` .. autoclass:: Context() diff --git a/islpy/__init__.py b/islpy/__init__.py index 23d8e98..b4a4bc2 100644 --- a/islpy/__init__.py +++ b/islpy/__init__.py @@ -21,6 +21,7 @@ """ import sys +import contextlib import islpy._isl as _isl from islpy.version import VERSION, VERSION_TEXT # noqa @@ -201,9 +202,6 @@ def _get_default_context(): return get_default_context() -import contextlib - - @contextlib.contextmanager def push_context(ctx=None): """Context manager to push new default :class:`Context` From 71da6cdb41c4a17d8466fe06e0766eca272cdde7 Mon Sep 17 00:00:00 2001 From: Cambridge Yang Date: Mon, 19 Oct 2020 13:22:55 -0400 Subject: [PATCH 10/10] update docs --- doc/ref_fundamental.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ref_fundamental.rst b/doc/ref_fundamental.rst index f9c0dda..63eefb3 100644 --- a/doc/ref_fundamental.rst +++ b/doc/ref_fundamental.rst @@ -9,7 +9,7 @@ Context .. note:: this class implements Python's ``__copy__`` and ``__deepcopy__`` protocols. Each of these returns the context being 'copied' identically. -.. note:: during an unpickle operation, the current default :class:`Context` +.. note:: during an pickle operation, the current default :class:`Context` is always used. .. seealso:: :ref:`sec-context-management`