[pypy-commit] pypy guard-compatible: in progress: support for quasi-immutables as the second argument of

cfbolz pypy.commits at gmail.com
Fri Mar 18 13:03:50 EDT 2016


Author: Carl Friedrich Bolz <cfbolz at gmx.de>
Branch: guard-compatible
Changeset: r83137:c572a1cedff3
Date: 2016-03-16 13:43 +0100
http://bitbucket.org/pypy/pypy/changeset/c572a1cedff3/

Log:	in progress: support for quasi-immutables as the second argument of
	@elidable_compatible functions. This is needed for supporting the
	pattern elidable_func(x, x.version) where x.version is a quasi-
	immutable field.

diff --git a/rpython/jit/metainterp/compatible.py b/rpython/jit/metainterp/compatible.py
--- a/rpython/jit/metainterp/compatible.py
+++ b/rpython/jit/metainterp/compatible.py
@@ -1,5 +1,6 @@
 from rpython.jit.metainterp.history import newconst
 from rpython.jit.codewriter import longlong
+from rpython.jit.metainterp.resoperation import rop
 
 def do_call(cpu, argboxes, descr):
     from rpython.jit.metainterp.history import INT, REF, FLOAT, VOID
@@ -51,25 +52,122 @@
     """ A collections of conditions that an object needs to fulfil. """
     def __init__(self, ptr):
         self.known_valid = ptr
-        self.pure_call_conditions = []
+        self.conditions = []
+        self.last_quasi_immut_field_op = None
 
-    def record_pure_call(self, op, res):
-        self.pure_call_conditions.append((op, res))
+    def record_condition(self, cond, res, optimizer):
+        cond.activate(res, optimizer)
+        self.conditions.append(cond)
 
-    def check_compat(self, cpu, ref):
+    def register_quasi_immut_field(self, op):
+        self.last_quasi_immut_field_op = op
+
+    def check_compat(self, cpu, ref, loop_token):
+        for cond in self.conditions:
+            if not cond.check(cpu, ref):
+                return False
+        # need to tell all conditions, in case a quasi-immut needs to be registered
+        for cond in self.conditions:
+            cond.activate_secondary(ref, loop_token)
+        return True
+
+    def prepare_const_arg_call(self, op):
+        from rpython.jit.metainterp.quasiimmut import QuasiImmutDescr
+        copied_op = op.copy()
+        copied_op.setarg(1, self.known_valid)
+        if op.numargs() == 2:
+            return copied_op, PureCallCondition(op)
+        arg2 = copied_op.getarg(2)
+        # really simple-minded pattern matching
+        # the order of things is like this:
+        # GUARD_COMPATIBLE(x)
+        # QUASIIMMUT_FIELD(x)
+        # y = GETFIELD_GC(x, f)
+        # z = CALL_PURE(x, y, ...)
+        # we want to discover this (and so far precisely this) situation and
+        # make it possible for the GUARD_COMPATIBLE to still remove the call,
+        # even though the second argument is not constant
+        if arg2.getopnum() != rop.GETFIELD_GC_R:
+            return None, None
+        if not self.last_quasi_immut_field_op:
+            return None, None
+        qmutdescr = self.last_quasi_immut_field_op.getdescr()
+        assert isinstance(qmutdescr, QuasiImmutDescr)
+        fielddescr = qmutdescr.fielddescr # XXX
+        same_arg = self.last_quasi_immut_field_op.getarg(0) is arg2.getarg(0)
+        if arg2.getdescr() is not fielddescr or not same_arg:
+            return None, None
+        if not qmutdescr.is_still_valid_for(self.known_valid):
+            return None, None
+        copied_op.setarg(2, qmutdescr.constantfieldbox)
+        self.last_quasi_immut_field_op = None
+        return copied_op, QuasiimmutGetfieldAndPureCallCondition(op, qmutdescr)
+
+class Condition(object):
+    def check(self, cpu, ref):
+        raise NotImplementedError
+
+    def activate(self, ref, optimizer):
+        self.res = ref
+
+    def activate_secondary(self, ref, loop_token):
+        pass
+
+
+class PureCallCondition(Condition):
+    def __init__(self, op):
+        self.op = op
+
+    def check(self, cpu, ref):
         from rpython.rlib.debug import debug_print, debug_start, debug_stop
-        for op, correct_res in self.pure_call_conditions:
-            calldescr = op.getdescr()
-            # change exactly the first argument
-            arglist = op.getarglist()
-            arglist[1] = newconst(ref)
-            try:
-                res = do_call(cpu, arglist, calldescr)
-            except Exception:
-                debug_start("jit-guard-compatible")
-                debug_print("call to elidable_compatible function raised")
-                debug_stop("jit-guard-compatible")
-                return False
-            if not res.same_constant(correct_res):
-                return False
+        calldescr = self.op.getdescr()
+        # change exactly the first argument
+        arglist = self.op.getarglist()
+        arglist[1] = newconst(ref)
+        try:
+            res = do_call(cpu, arglist, calldescr)
+        except Exception:
+            debug_start("jit-guard-compatible")
+            debug_print("call to elidable_compatible function raised")
+            debug_stop("jit-guard-compatible")
+            return False
+        if not res.same_constant(self.res):
+            return False
         return True
+
+
+class QuasiimmutGetfieldAndPureCallCondition(PureCallCondition):
+    def __init__(self, op, qmutdescr):
+        self.op = op
+        self.qmutdescr = qmutdescr
+
+    def activate(self, ref, optimizer):
+        # record the quasi-immutable
+        optimizer.record_quasi_immutable_dep(self.qmutdescr.qmut)
+        Condition.activate(self, ref, optimizer)
+
+    def activate_secondary(self, ref, loop_token):
+        from rpython.jit.metainterp.quasiimmut import get_current_qmut_instance
+        # need to register the loop for invalidation as well!
+        qmut = get_current_qmut_instance(loop_token.cpu, ref,
+                                         self.qmutdescr.mutatefielddescr)
+        qmut.register_loop_token(loop_token.loop_token_wref)
+
+    def check(self, cpu, ref):
+        from rpython.rlib.debug import debug_print, debug_start, debug_stop
+        calldescr = self.op.getdescr()
+        # change exactly the first argument
+        arglist = self.op.getarglist()
+        arglist[1] = newconst(ref)
+        arglist[2] = self.qmutdescr._get_fieldvalue(ref)
+        try:
+            res = do_call(cpu, arglist, calldescr)
+        except Exception:
+            debug_start("jit-guard-compatible")
+            debug_print("call to elidable_compatible function raised")
+            debug_stop("jit-guard-compatible")
+            return False
+        if not res.same_constant(self.res):
+            return False
+        return True
+
diff --git a/rpython/jit/metainterp/compile.py b/rpython/jit/metainterp/compile.py
--- a/rpython/jit/metainterp/compile.py
+++ b/rpython/jit/metainterp/compile.py
@@ -1086,6 +1086,7 @@
     fulfil need to be attached to this descr by optimizeopt. """
 
     def __init__(self):
+        # XXX think about what is being kept alive here
         self._compatibility_conditions = None
 
     def handle_fail(self, deadframe, metainterp_sd, jitdriver_sd):
@@ -1094,18 +1095,21 @@
         assert typetag == self.TY_REF # for now
         refval = metainterp_sd.cpu.get_value_direct(deadframe, 'r', index)
         if self.is_compatible(metainterp_sd.cpu, refval):
+            print "~~~~~~~~~~~~~~~~~~~ compatible! growing switch", self
             from rpython.jit.metainterp.blackhole import resume_in_blackhole
             metainterp_sd.cpu.grow_guard_compatible_switch(
                 self.rd_loop_token, self, refval)
             resume_in_blackhole(metainterp_sd, jitdriver_sd, self, deadframe)
         else:
+            print "~~~~~~~~~~~~~~~~~~~ not compatible!", self
             # a real failure
             return ResumeGuardDescr.handle_fail(self, deadframe, metainterp_sd, jitdriver_sd)
 
     def is_compatible(self, cpu, ref):
         const = history.newconst(ref)
         if self._compatibility_conditions:
-            if self._compatibility_conditions.check_compat(cpu, ref):
+            if self._compatibility_conditions.check_compat(
+                    cpu, ref, self.rd_loop_token):
                 return True
             return False
         return True # no conditions, everything works
diff --git a/rpython/jit/metainterp/optimizeopt/heap.py b/rpython/jit/metainterp/optimizeopt/heap.py
--- a/rpython/jit/metainterp/optimizeopt/heap.py
+++ b/rpython/jit/metainterp/optimizeopt/heap.py
@@ -635,7 +635,18 @@
         # registered.
         structvalue = self.ensure_ptr_info_arg0(op)
         if not structvalue.is_constant():
-            self._remove_guard_not_invalidated = True
+            ccond = structvalue._compatibility_conditions
+            if ccond:
+                # the object is subject to a guard_compatible. We cannot remove
+                # the getfield_gc on the object, since it's not constant.
+                # However, if the quasi-immutable field is passed to a pure
+                # function call, we can treat it as constant then
+                ccond.register_quasi_immut_field(op)
+                # don't remove the guard_not_invalidated, the guard_compatible
+                # needs it
+                self._remove_guard_not_invalidated = False
+            else:
+                self._remove_guard_not_invalidated = True
             return    # not a constant at all; ignore QUASIIMMUT_FIELD
         #
         from rpython.jit.metainterp.quasiimmut import QuasiImmutDescr
@@ -648,9 +659,7 @@
                 self.get_box_replacement(op.getarg(0))):
             raise InvalidLoop('quasi immutable field changed during tracing')
         # record as an out-of-line guard
-        if self.optimizer.quasi_immutable_deps is None:
-            self.optimizer.quasi_immutable_deps = {}
-        self.optimizer.quasi_immutable_deps[qmutdescr.qmut] = None
+        self.optimizer.record_quasi_immutable_dep(qmutdescr.qmut)
         self._remove_guard_not_invalidated = False
 
     def optimize_GUARD_NOT_INVALIDATED(self, op):
diff --git a/rpython/jit/metainterp/optimizeopt/optimizer.py b/rpython/jit/metainterp/optimizeopt/optimizer.py
--- a/rpython/jit/metainterp/optimizeopt/optimizer.py
+++ b/rpython/jit/metainterp/optimizeopt/optimizer.py
@@ -268,6 +268,11 @@
         self.set_optimizations(optimizations)
         self.setup()
 
+    def record_quasi_immutable_dep(self, qmut):
+        if self.quasi_immutable_deps is None:
+            self.quasi_immutable_deps = {}
+        self.quasi_immutable_deps[qmut] = None
+
     def init_inparg_dict_from(self, lst):
         self.inparg_dict = {}
         for box in lst:
@@ -457,10 +462,13 @@
         if arg0.is_constant():
             return info.ConstPtrInfo(arg0)
         opinfo = arg0.get_forwarded()
+        ccond = None
         if isinstance(opinfo, info.AbstractVirtualPtrInfo):
             return opinfo
         elif opinfo is not None:
             last_guard_pos = opinfo.get_last_guard_pos()
+            if isinstance(opinfo, info.PtrInfo):
+                ccond = opinfo._compatibility_conditions
         else:
             last_guard_pos = -1
         assert opinfo is None or opinfo.__class__ is info.NonNullPtrInfo
@@ -473,6 +481,7 @@
             else:
                 opinfo = info.StructPtrInfo(parent_descr)
             opinfo.init_fields(parent_descr, descr.get_index())
+            opinfo._compatibility_conditions = ccond
         elif (op.is_getarrayitem() or op.getopnum() == rop.SETARRAYITEM_GC or
               op.getopnum() == rop.ARRAYLEN_GC):
             opinfo = info.ArrayPtrInfo(op.getdescr())
diff --git a/rpython/jit/metainterp/optimizeopt/pure.py b/rpython/jit/metainterp/optimizeopt/pure.py
--- a/rpython/jit/metainterp/optimizeopt/pure.py
+++ b/rpython/jit/metainterp/optimizeopt/pure.py
@@ -142,14 +142,14 @@
                     ccond = info._compatibility_conditions
                     if ccond:
                         # it's subject to guard_compatible
-                        copied_op = op.copy()
-                        copied_op.setarg(1, ccond.known_valid)
-                        result = self._can_optimize_call_pure(copied_op)
-                        if result is not None:
-                            self.make_constant(op, result)
-                            self.last_emitted_operation = REMOVED
-                            ccond.record_pure_call(copied_op, result)
-                            return
+                        copied_op, cond = ccond.prepare_const_arg_call(op)
+                        if copied_op:
+                            result = self._can_optimize_call_pure(copied_op)
+                            if result is not None:
+                                self.make_constant(op, result)
+                                self.last_emitted_operation = REMOVED
+                                ccond.record_condition(cond, result, self.optimizer)
+                                return
 
         # Step 1: check if all arguments are constant
         for arg in op.getarglist():
diff --git a/rpython/jit/metainterp/quasiimmut.py b/rpython/jit/metainterp/quasiimmut.py
--- a/rpython/jit/metainterp/quasiimmut.py
+++ b/rpython/jit/metainterp/quasiimmut.py
@@ -129,6 +129,9 @@
 
     def get_current_constant_fieldvalue(self):
         struct = self.struct
+        return self._get_fieldvalue(struct)
+
+    def _get_fieldvalue(self, struct):
         fielddescr = self.fielddescr
         if self.fielddescr.is_pointer_field():
             return ConstPtr(self.cpu.bh_getfield_gc_r(struct, fielddescr))
diff --git a/rpython/jit/metainterp/test/test_compatible.py b/rpython/jit/metainterp/test/test_compatible.py
--- a/rpython/jit/metainterp/test/test_compatible.py
+++ b/rpython/jit/metainterp/test/test_compatible.py
@@ -76,3 +76,79 @@
 
         self.meta_interp(main, [])
         # XXX check number of bridges
+
+
+    def test_quasi_immutable(self):
+        from rpython.rlib.objectmodel import we_are_translated
+        class C(object):
+            _immutable_fields_ = ['version?']
+
+        class Version(object):
+            def __init__(self, cls):
+                self.cls = cls
+        p1 = C()
+        p1.version = Version(p1)
+        p1.x = 1
+        p2 = C()
+        p2.version = Version(p2)
+        p2.x = 1
+        p3 = C()
+        p3.version = Version(p3)
+        p3.x = 3
+
+        driver = jit.JitDriver(greens = [], reds = ['n', 'x'])
+
+        class Counter(object):
+            pass
+
+        c = Counter()
+        c.count = 0
+        @jit.elidable_compatible()
+        def g(cls, v):
+            if we_are_translated():
+                c.count += 1
+            return cls.x
+
+        def f(n, x):
+            res = 0
+            while n > 0:
+                driver.can_enter_jit(n=n, x=x)
+                driver.jit_merge_point(n=n, x=x)
+                x = jit.hint(x, promote_compatible=True)
+                res = g(x, x.version)
+                n -= res
+            return res
+
+        def main(x):
+            res = f(100, p1)
+            assert res == 1
+            res = f(100, p2)
+            assert res == 1
+            res = f(100, p3)
+            assert res == 3
+            # invalidate p1 or p2
+            if x:
+                p1.x = 2
+                p1.version = Version(p1)
+                res = f(100, p1)
+                assert res == 2
+                p1.x = 1
+                p1.version = Version(p1)
+            else:
+                p2.x = 2
+                p2.version = Version(p2)
+                res = f(100, p2)
+                assert res == 2
+                p2.x = 1
+                p2.version = Version(p2)
+            return c.count
+        main(True)
+        main(False)
+
+        x = self.meta_interp(main, [True])
+        assert x < 30
+
+        x = self.meta_interp(main, [False])
+        assert x < 30
+        # XXX check number of bridges
+


More information about the pypy-commit mailing list