[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