[pypy-svn] pypy default: Merge out-of-line-guards-2. This branch provides a way to specify a field
fijal
commits-noreply at bitbucket.org
Sun Apr 17 01:44:06 CEST 2011
Author: Maciej Fijalkowski <fijall at gmail.com>
Branch:
Changeset: r43414:124ebb7828dd
Date: 2011-04-17 01:43 +0200
http://bitbucket.org/pypy/pypy/changeset/124ebb7828dd/
Log: Merge out-of-line-guards-2. This branch provides a way to specify a
field that doesn't change very often by saying
_immutable_fields_ = ['a?']
which means when it changes, a new assembler will be compiled.
diff --git a/pypy/rpython/ootypesystem/rclass.py b/pypy/rpython/ootypesystem/rclass.py
--- a/pypy/rpython/ootypesystem/rclass.py
+++ b/pypy/rpython/ootypesystem/rclass.py
@@ -262,6 +262,10 @@
self.rbase = getinstancerepr(self.rtyper, self.classdef.basedef)
self.rbase.setup()
+ for name, attrdef in selfattrs.iteritems():
+ if not attrdef.readonly and self.is_quasi_immutable(name):
+ ootype.addFields(self.lowleveltype, {'mutable_'+name: OBJECT})
+
classattributes = {}
baseInstance = self.lowleveltype._superclass
classrepr = getclassrepr(self.rtyper, self.classdef)
@@ -476,11 +480,9 @@
mangled_name = mangle(attr, self.rtyper.getconfig())
cname = inputconst(ootype.Void, mangled_name)
self.hook_access_field(vinst, cname, llops, flags)
+ self.hook_setfield(vinst, attr, llops)
llops.genop('oosetfield', [vinst, cname, vvalue])
- def hook_access_field(self, vinst, cname, llops, flags):
- pass # for virtualizables; see rvirtualizable2.py
-
def rtype_is_true(self, hop):
vinst, = hop.inputargs(self)
return hop.genop('oononnull', [vinst], resulttype=ootype.Bool)
diff --git a/pypy/jit/backend/model.py b/pypy/jit/backend/model.py
--- a/pypy/jit/backend/model.py
+++ b/pypy/jit/backend/model.py
@@ -291,6 +291,7 @@
# that belong to this loop or to a bridge attached to it.
# Filled by the frontend calling record_faildescr_index().
self.faildescr_indices = []
+ self.invalidate_positions = []
debug_start("jit-mem-looptoken-alloc")
debug_print("allocating Loop #", self.number)
debug_stop("jit-mem-looptoken-alloc")
diff --git a/pypy/jit/metainterp/history.py b/pypy/jit/metainterp/history.py
--- a/pypy/jit/metainterp/history.py
+++ b/pypy/jit/metainterp/history.py
@@ -791,6 +791,7 @@
operations = None
token = None
call_pure_results = None
+ quasi_immutable_deps = None
def __init__(self, name):
self.name = name
diff --git a/pypy/rpython/lltypesystem/lltype.py b/pypy/rpython/lltypesystem/lltype.py
--- a/pypy/rpython/lltypesystem/lltype.py
+++ b/pypy/rpython/lltypesystem/lltype.py
@@ -341,13 +341,14 @@
return _struct(self, n, initialization='example')
def _immutable_field(self, field):
+ if self._hints.get('immutable'):
+ return True
if 'immutable_fields' in self._hints:
try:
- s = self._hints['immutable_fields'].fields[field]
- return s or True
+ return self._hints['immutable_fields'].fields[field]
except KeyError:
pass
- return self._hints.get('immutable', False)
+ return False
class RttiStruct(Struct):
_runtime_type_info = None
@@ -1029,6 +1030,8 @@
return None # null pointer
if type(p._obj0) is int:
return p # a pointer obtained by cast_int_to_ptr
+ if getattr(p._obj0, '_carry_around_for_tests', False):
+ return p # a pointer obtained by cast_instance_to_base_ptr
container = obj._normalizedcontainer()
if type(container) is int:
# this must be an opaque ptr originating from an integer
@@ -1881,8 +1884,8 @@
if self.__class__ is not other.__class__:
return NotImplemented
if hasattr(self, 'container') and hasattr(other, 'container'):
- obj1 = self.container._normalizedcontainer()
- obj2 = other.container._normalizedcontainer()
+ obj1 = self._normalizedcontainer()
+ obj2 = other._normalizedcontainer()
return obj1 == obj2
else:
return self is other
@@ -1906,6 +1909,8 @@
# an integer, cast to a ptr, cast to an opaque
if type(self.container) is int:
return self.container
+ if getattr(self.container, '_carry_around_for_tests', False):
+ return self.container
return self.container._normalizedcontainer()
else:
return _parentable._normalizedcontainer(self)
diff --git a/pypy/rpython/lltypesystem/ll2ctypes.py b/pypy/rpython/lltypesystem/ll2ctypes.py
--- a/pypy/rpython/lltypesystem/ll2ctypes.py
+++ b/pypy/rpython/lltypesystem/ll2ctypes.py
@@ -578,6 +578,7 @@
_all_callbacks_results = []
_int2obj = {}
_callback_exc_info = None
+_opaque_objs = [None]
def get_rtyper():
llinterp = LLInterpreter.current_interpreter
@@ -616,6 +617,10 @@
T = lltype.Ptr(lltype.typeOf(container))
# otherwise it came from integer and we want a c_void_p with
# the same valu
+ if getattr(container, 'llopaque', None):
+ no = len(_opaque_objs)
+ _opaque_objs.append(container)
+ return no * 2 + 1
else:
container = llobj._obj
if isinstance(T.TO, lltype.FuncType):
@@ -764,10 +769,14 @@
if isinstance(T, lltype.Typedef):
T = T.OF
if isinstance(T, lltype.Ptr):
- if not cobj or not ctypes.cast(cobj, ctypes.c_void_p).value: # NULL pointer
+ ptrval = ctypes.cast(cobj, ctypes.c_void_p).value
+ if not cobj or not ptrval: # NULL pointer
# CFunctionType.__nonzero__ is broken before Python 2.6
return lltype.nullptr(T.TO)
if isinstance(T.TO, lltype.Struct):
+ if ptrval & 1: # a tagged pointer
+ gcref = _opaque_objs[ptrval // 2].hide()
+ return lltype.cast_opaque_ptr(T, gcref)
REAL_TYPE = T.TO
if T.TO._arrayfld is not None:
carray = getattr(cobj.contents, T.TO._arrayfld)
@@ -1228,7 +1237,9 @@
return not self == other
def _cast_to_ptr(self, PTRTYPE):
- return force_cast(PTRTYPE, self.intval)
+ if self.intval & 1:
+ return _opaque_objs[self.intval // 2]
+ return force_cast(PTRTYPE, self.intval)
## def _cast_to_int(self):
## return self.intval
diff --git a/pypy/jit/metainterp/test/test_quasiimmut.py b/pypy/jit/metainterp/test/test_quasiimmut.py
new file mode 100644
--- /dev/null
+++ b/pypy/jit/metainterp/test/test_quasiimmut.py
@@ -0,0 +1,266 @@
+
+import py
+
+from pypy.rpython.lltypesystem import lltype, llmemory, rclass
+from pypy.rpython.rclass import FieldListAccessor, IR_QUASI_IMMUTABLE
+from pypy.jit.metainterp import typesystem
+from pypy.jit.metainterp.quasiimmut import QuasiImmut
+from pypy.jit.metainterp.quasiimmut import get_current_qmut_instance
+from pypy.jit.metainterp.test.test_basic import LLJitMixin
+from pypy.jit.codewriter.policy import StopAtXPolicy
+from pypy.rlib.jit import JitDriver, dont_look_inside
+
+
+def test_get_current_qmut_instance():
+ accessor = FieldListAccessor()
+ accessor.initialize(None, {'inst_x': IR_QUASI_IMMUTABLE})
+ STRUCT = lltype.GcStruct('Foo', ('inst_x', lltype.Signed),
+ ('mutate_x', rclass.OBJECTPTR),
+ hints={'immutable_fields': accessor})
+ foo = lltype.malloc(STRUCT, zero=True)
+ foo.inst_x = 42
+ assert not foo.mutate_x
+
+ class FakeCPU:
+ ts = typesystem.llhelper
+
+ def bh_getfield_gc_r(self, gcref, fielddescr):
+ assert fielddescr == mutatefielddescr
+ foo = lltype.cast_opaque_ptr(lltype.Ptr(STRUCT), gcref)
+ result = foo.mutate_x
+ return lltype.cast_opaque_ptr(llmemory.GCREF, result)
+
+ def bh_setfield_gc_r(self, gcref, fielddescr, newvalue_gcref):
+ assert fielddescr == mutatefielddescr
+ foo = lltype.cast_opaque_ptr(lltype.Ptr(STRUCT), gcref)
+ newvalue = lltype.cast_opaque_ptr(rclass.OBJECTPTR, newvalue_gcref)
+ foo.mutate_x = newvalue
+
+ cpu = FakeCPU()
+ mutatefielddescr = ('fielddescr', STRUCT, 'mutate_x')
+
+ foo_gcref = lltype.cast_opaque_ptr(llmemory.GCREF, foo)
+ qmut1 = get_current_qmut_instance(cpu, foo_gcref, mutatefielddescr)
+ assert isinstance(qmut1, QuasiImmut)
+ qmut2 = get_current_qmut_instance(cpu, foo_gcref, mutatefielddescr)
+ assert qmut1 is qmut2
+
+
+class QuasiImmutTests(object):
+
+ def test_simple_1(self):
+ myjitdriver = JitDriver(greens=['foo'], reds=['x', 'total'])
+ class Foo:
+ _immutable_fields_ = ['a?']
+ def __init__(self, a):
+ self.a = a
+ def f(a, x):
+ foo = Foo(a)
+ total = 0
+ while x > 0:
+ myjitdriver.jit_merge_point(foo=foo, x=x, total=total)
+ # read a quasi-immutable field out of a Constant
+ total += foo.a
+ x -= 1
+ return total
+ #
+ res = self.meta_interp(f, [100, 7])
+ assert res == 700
+ self.check_loops(getfield_gc=0, everywhere=True)
+ #
+ from pypy.jit.metainterp.warmspot import get_stats
+ loops = get_stats().loops
+ for loop in loops:
+ assert len(loop.quasi_immutable_deps) == 1
+ assert isinstance(loop.quasi_immutable_deps.keys()[0], QuasiImmut)
+
+ def test_nonopt_1(self):
+ myjitdriver = JitDriver(greens=[], reds=['x', 'total', 'lst'])
+ class Foo:
+ _immutable_fields_ = ['a?']
+ def __init__(self, a):
+ self.a = a
+ def setup(x):
+ return [Foo(100 + i) for i in range(x)]
+ def f(a, x):
+ lst = setup(x)
+ total = 0
+ while x > 0:
+ myjitdriver.jit_merge_point(lst=lst, x=x, total=total)
+ # read a quasi-immutable field out of a variable
+ x -= 1
+ total += lst[x].a
+ return total
+ #
+ assert f(100, 7) == 721
+ res = self.meta_interp(f, [100, 7])
+ assert res == 721
+ self.check_loops(getfield_gc=1)
+ #
+ from pypy.jit.metainterp.warmspot import get_stats
+ loops = get_stats().loops
+ for loop in loops:
+ assert loop.quasi_immutable_deps is None
+
+ def test_change_during_tracing_1(self):
+ myjitdriver = JitDriver(greens=['foo'], reds=['x', 'total'])
+ class Foo:
+ _immutable_fields_ = ['a?']
+ def __init__(self, a):
+ self.a = a
+ @dont_look_inside
+ def residual_call(foo):
+ foo.a += 1
+ def f(a, x):
+ foo = Foo(a)
+ total = 0
+ while x > 0:
+ myjitdriver.jit_merge_point(foo=foo, x=x, total=total)
+ # read a quasi-immutable field out of a Constant
+ total += foo.a
+ residual_call(foo)
+ x -= 1
+ return total
+ #
+ assert f(100, 7) == 721
+ res = self.meta_interp(f, [100, 7])
+ assert res == 721
+ self.check_loops(getfield_gc=1)
+
+ def test_change_during_tracing_2(self):
+ myjitdriver = JitDriver(greens=['foo'], reds=['x', 'total'])
+ class Foo:
+ _immutable_fields_ = ['a?']
+ def __init__(self, a):
+ self.a = a
+ @dont_look_inside
+ def residual_call(foo, difference):
+ foo.a += difference
+ def f(a, x):
+ foo = Foo(a)
+ total = 0
+ while x > 0:
+ myjitdriver.jit_merge_point(foo=foo, x=x, total=total)
+ # read a quasi-immutable field out of a Constant
+ total += foo.a
+ residual_call(foo, +1)
+ residual_call(foo, -1)
+ x -= 1
+ return total
+ #
+ assert f(100, 7) == 700
+ res = self.meta_interp(f, [100, 7])
+ assert res == 700
+ self.check_loops(getfield_gc=1)
+
+ def test_change_invalidate_reentering(self):
+ myjitdriver = JitDriver(greens=['foo'], reds=['x', 'total'])
+ class Foo:
+ _immutable_fields_ = ['a?']
+ def __init__(self, a):
+ self.a = a
+ def f(foo, x):
+ total = 0
+ while x > 0:
+ myjitdriver.jit_merge_point(foo=foo, x=x, total=total)
+ # read a quasi-immutable field out of a Constant
+ total += foo.a
+ x -= 1
+ return total
+ def g(a, x):
+ foo = Foo(a)
+ res1 = f(foo, x)
+ foo.a += 1 # invalidation, while the jit is not running
+ res2 = f(foo, x) # should still mark the loop as invalid
+ return res1 * 1000 + res2
+ #
+ assert g(100, 7) == 700707
+ res = self.meta_interp(g, [100, 7])
+ assert res == 700707
+ self.check_loops(getfield_gc=0)
+
+ def test_invalidate_while_running(self):
+ jitdriver = JitDriver(greens=['foo'], reds=['i', 'total'])
+
+ class Foo(object):
+ _immutable_fields_ = ['a?']
+ def __init__(self, a):
+ self.a = a
+
+ def external(foo, v):
+ if v:
+ foo.a = 2
+
+ def f(foo):
+ i = 0
+ total = 0
+ while i < 10:
+ jitdriver.jit_merge_point(i=i, foo=foo, total=total)
+ external(foo, i > 7)
+ i += 1
+ total += foo.a
+ return total
+
+ def g():
+ return f(Foo(1))
+
+ assert self.meta_interp(g, [], policy=StopAtXPolicy(external)) == g()
+
+ def test_invalidate_by_setfield(self):
+ py.test.skip("Not implemented")
+ jitdriver = JitDriver(greens=['bc', 'foo'], reds=['i', 'total'])
+
+ class Foo(object):
+ _immutable_fields_ = ['a?']
+ def __init__(self, a):
+ self.a = a
+
+ def f(foo, bc):
+ i = 0
+ total = 0
+ while i < 10:
+ jitdriver.jit_merge_point(bc=bc, i=i, foo=foo, total=total)
+ if bc == 0:
+ f(foo, 1)
+ if bc == 1:
+ foo.a = int(i > 5)
+ i += 1
+ total += foo.a
+ return total
+
+ def g():
+ return f(Foo(1), 0)
+
+ assert self.meta_interp(g, []) == g()
+
+ def test_invalidate_bridge(self):
+ jitdriver = JitDriver(greens=['foo'], reds=['i', 'total'])
+
+ class Foo(object):
+ _immutable_fields_ = ['a?']
+
+ def f(foo):
+ i = 0
+ total = 0
+ while i < 10:
+ jitdriver.jit_merge_point(i=i, total=total, foo=foo)
+ if i > 5:
+ total += foo.a
+ else:
+ total += 2*foo.a
+ i += 1
+ return total
+
+ def main():
+ foo = Foo()
+ foo.a = 1
+ total = f(foo)
+ foo.a = 2
+ total += f(foo)
+ return total
+
+ res = self.meta_interp(main, [])
+ assert res == main()
+
+class TestLLtypeGreenFieldsTests(QuasiImmutTests, LLJitMixin):
+ pass
diff --git a/pypy/jit/metainterp/quasiimmut.py b/pypy/jit/metainterp/quasiimmut.py
new file mode 100644
--- /dev/null
+++ b/pypy/jit/metainterp/quasiimmut.py
@@ -0,0 +1,116 @@
+import weakref
+from pypy.rpython.rclass import IR_QUASI_IMMUTABLE
+from pypy.rpython.lltypesystem import lltype, rclass
+from pypy.rpython.annlowlevel import cast_base_ptr_to_instance
+from pypy.jit.metainterp.history import AbstractDescr
+
+
+def is_quasi_immutable(STRUCT, fieldname):
+ imm_fields = STRUCT._hints.get('immutable_fields')
+ return (imm_fields is not None and
+ imm_fields.fields.get(fieldname) is IR_QUASI_IMMUTABLE)
+
+def get_mutate_field_name(fieldname):
+ if fieldname.startswith('inst_'): # lltype
+ return 'mutate_' + fieldname[5:]
+ elif fieldname.startswith('o'): # ootype
+ return 'mutate_' + fieldname[1:]
+ else:
+ raise AssertionError(fieldname)
+
+def get_current_qmut_instance(cpu, gcref, mutatefielddescr):
+ """Returns the current QuasiImmut instance in the field,
+ possibly creating one.
+ """
+ # XXX this is broken on x86
+ qmut_gcref = cpu.bh_getfield_gc_r(gcref, mutatefielddescr)
+ if qmut_gcref:
+ qmut = QuasiImmut.show(cpu, qmut_gcref)
+ else:
+ qmut = QuasiImmut(cpu)
+ cpu.bh_setfield_gc_r(gcref, mutatefielddescr, qmut.hide())
+ return qmut
+
+def make_invalidation_function(STRUCT, mutatefieldname):
+ #
+ def _invalidate_now(p):
+ qmut_ptr = getattr(p, mutatefieldname)
+ setattr(p, mutatefieldname, lltype.nullptr(rclass.OBJECT))
+ qmut = cast_base_ptr_to_instance(QuasiImmut, qmut_ptr)
+ qmut.invalidate()
+ _invalidate_now._dont_inline_ = True
+ #
+ def invalidation(p):
+ if getattr(p, mutatefieldname):
+ _invalidate_now(p)
+ #
+ return invalidation
+
+
+class QuasiImmut(object):
+ llopaque = True
+
+ def __init__(self, cpu):
+ self.cpu = cpu
+ # list of weakrefs to the LoopTokens that must be invalidated if
+ # this value ever changes
+ self.looptokens_wrefs = []
+ self.compress_limit = 30
+
+ def hide(self):
+ qmut_ptr = self.cpu.ts.cast_instance_to_base_ref(self)
+ return self.cpu.ts.cast_to_ref(qmut_ptr)
+
+ @staticmethod
+ def show(cpu, qmut_gcref):
+ qmut_ptr = cpu.ts.cast_to_baseclass(qmut_gcref)
+ return cast_base_ptr_to_instance(QuasiImmut, qmut_ptr)
+
+ def register_loop_token(self, wref_looptoken):
+ if len(self.looptokens_wrefs) > self.compress_limit:
+ self.compress_looptokens_list()
+ self.looptokens_wrefs.append(wref_looptoken)
+
+ def compress_looptokens_list(self):
+ self.looptokens_wrefs = [wref for wref in self.looptokens_wrefs
+ if wref() is not None]
+ self.compress_limit = (len(self.looptokens_wrefs) + 15) * 2
+
+ def invalidate(self):
+ # When this is called, all the loops that we record become
+ # invalid and must not be called again, nor returned to.
+ wrefs = self.looptokens_wrefs
+ self.looptokens_wrefs = []
+ for wref in wrefs:
+ looptoken = wref()
+ if looptoken is not None:
+ self.cpu.invalidate_loop(looptoken)
+
+
+class QuasiImmutDescr(AbstractDescr):
+ def __init__(self, cpu, structbox, fielddescr, mutatefielddescr):
+ self.cpu = cpu
+ self.structbox = structbox
+ self.fielddescr = fielddescr
+ self.mutatefielddescr = mutatefielddescr
+ gcref = structbox.getref_base()
+ self.qmut = get_current_qmut_instance(cpu, gcref, mutatefielddescr)
+ self.constantfieldbox = self.get_current_constant_fieldvalue()
+
+ def get_current_constant_fieldvalue(self):
+ from pypy.jit.metainterp import executor
+ from pypy.jit.metainterp.resoperation import rop
+ fieldbox = executor.execute(self.cpu, None, rop.GETFIELD_GC,
+ self.fielddescr, self.structbox)
+ return fieldbox.constbox()
+
+ def is_still_valid(self):
+ cpu = self.cpu
+ gcref = self.structbox.getref_base()
+ qmut = get_current_qmut_instance(cpu, gcref, self.mutatefielddescr)
+ if qmut is not self.qmut:
+ return False
+ else:
+ currentbox = self.get_current_constant_fieldvalue()
+ assert self.constantfieldbox.same_constant(currentbox)
+ return True
diff --git a/pypy/jit/metainterp/test/test_virtualizable.py b/pypy/jit/metainterp/test/test_virtualizable.py
--- a/pypy/jit/metainterp/test/test_virtualizable.py
+++ b/pypy/jit/metainterp/test/test_virtualizable.py
@@ -2,6 +2,7 @@
from pypy.rpython.extregistry import ExtRegistryEntry
from pypy.rpython.lltypesystem import lltype, lloperation, rclass, llmemory
from pypy.rpython.annlowlevel import llhelper
+from pypy.rpython.rclass import IR_IMMUTABLE, IR_ARRAY_IMMUTABLE
from pypy.jit.codewriter.policy import StopAtXPolicy
from pypy.jit.codewriter import heaptracker
from pypy.rlib.jit import JitDriver, hint, dont_look_inside
@@ -45,7 +46,7 @@
('inst_node', lltype.Ptr(LLtypeMixin.NODE)),
hints = {'virtualizable2_accessor': FieldListAccessor()})
XY._hints['virtualizable2_accessor'].initialize(
- XY, {'inst_x' : "", 'inst_node' : ""})
+ XY, {'inst_x' : IR_IMMUTABLE, 'inst_node' : IR_IMMUTABLE})
xy_vtable = lltype.malloc(rclass.OBJECT_VTABLE, immortal=True)
heaptracker.set_testing_vtable_for_gcstruct(XY, xy_vtable, 'XY')
@@ -210,7 +211,8 @@
('inst_l2', lltype.Ptr(lltype.GcArray(lltype.Signed))),
hints = {'virtualizable2_accessor': FieldListAccessor()})
XY2._hints['virtualizable2_accessor'].initialize(
- XY2, {'inst_x' : "", 'inst_l1' : "[*]", 'inst_l2' : "[*]"})
+ XY2, {'inst_x' : IR_IMMUTABLE,
+ 'inst_l1' : IR_ARRAY_IMMUTABLE, 'inst_l2' : IR_ARRAY_IMMUTABLE})
xy2_vtable = lltype.malloc(rclass.OBJECT_VTABLE, immortal=True)
heaptracker.set_testing_vtable_for_gcstruct(XY2, xy2_vtable, 'XY2')
diff --git a/pypy/jit/metainterp/optimizeopt/__init__.py b/pypy/jit/metainterp/optimizeopt/__init__.py
--- a/pypy/jit/metainterp/optimizeopt/__init__.py
+++ b/pypy/jit/metainterp/optimizeopt/__init__.py
@@ -41,7 +41,8 @@
# during preamble but to keep it during the loop
optimizations.append(o)
- if 'rewrite' not in enable_opts or 'virtualize' not in enable_opts:
+ if ('rewrite' not in enable_opts or 'virtualize' not in enable_opts
+ or 'heap' not in enable_opts):
optimizations.append(OptSimplify())
if inline_short_preamble:
diff --git a/pypy/jit/codewriter/jtransform.py b/pypy/jit/codewriter/jtransform.py
--- a/pypy/jit/codewriter/jtransform.py
+++ b/pypy/jit/codewriter/jtransform.py
@@ -7,8 +7,9 @@
from pypy.jit.codewriter.flatten import ListOfKind, IndirectCallTargets
from pypy.jit.codewriter import support, heaptracker, longlong
from pypy.jit.codewriter.effectinfo import EffectInfo
-from pypy.jit.codewriter.policy import log
+from pypy.jit.codewriter.policy import log, check_skip_operation
from pypy.jit.metainterp.typesystem import deref, arrayItem
+from pypy.jit.metainterp import quasiimmut
from pypy.rlib import objectmodel
from pypy.rlib.jit import _we_are_jitted
from pypy.translator.simplify import get_funcobj
@@ -561,7 +562,8 @@
arraydescr)
return []
# check for _immutable_fields_ hints
- if v_inst.concretetype.TO._immutable_field(c_fieldname.value):
+ immut = v_inst.concretetype.TO._immutable_field(c_fieldname.value)
+ if immut:
if (self.callcontrol is not None and
self.callcontrol.could_be_green_field(v_inst.concretetype.TO,
c_fieldname.value)):
@@ -574,10 +576,21 @@
descr = self.cpu.fielddescrof(v_inst.concretetype.TO,
c_fieldname.value)
kind = getkind(RESULT)[0]
- return SpaceOperation('getfield_%s_%s%s' % (argname, kind, pure),
- [v_inst, descr], op.result)
+ op1 = SpaceOperation('getfield_%s_%s%s' % (argname, kind, pure),
+ [v_inst, descr], op.result)
+ #
+ if immut is quasiimmut.IR_QUASI_IMMUTABLE:
+ descr1 = self.cpu.fielddescrof(
+ v_inst.concretetype.TO,
+ quasiimmut.get_mutate_field_name(c_fieldname.value))
+ op1 = [SpaceOperation('-live-', [], None),
+ SpaceOperation('record_quasiimmut_field',
+ [v_inst, descr, descr1], None),
+ op1]
+ return op1
def rewrite_op_setfield(self, op):
+ check_skip_operation(op) # just to check it doesn't raise
if self.is_typeptr_getset(op):
# ignore the operation completely -- instead, it's done by 'new'
return
diff --git a/pypy/jit/metainterp/optimizeopt/optimizer.py b/pypy/jit/metainterp/optimizeopt/optimizer.py
--- a/pypy/jit/metainterp/optimizeopt/optimizer.py
+++ b/pypy/jit/metainterp/optimizeopt/optimizer.py
@@ -257,6 +257,7 @@
self.pendingfields = []
self.posponedop = None
self.exception_might_have_happened = False
+ self.quasi_immutable_deps = None
self.newoperations = []
if loop is not None:
self.call_pure_results = loop.call_pure_results
@@ -309,6 +310,7 @@
new.pure_operations = self.pure_operations
new.producer = self.producer
assert self.posponedop is None
+ new.quasi_immutable_deps = self.quasi_immutable_deps
return new
@@ -410,6 +412,7 @@
self.first_optimization.propagate_forward(op)
self.i += 1
self.loop.operations = self.newoperations
+ self.loop.quasi_immutable_deps = self.quasi_immutable_deps
# accumulate counters
self.resumedata_memo.update_counters(self.metainterp_sd.profiler)
diff --git a/pypy/jit/metainterp/test/test_optimizeopt.py b/pypy/jit/metainterp/test/test_optimizeopt.py
--- a/pypy/jit/metainterp/test/test_optimizeopt.py
+++ b/pypy/jit/metainterp/test/test_optimizeopt.py
@@ -5717,8 +5717,35 @@
# not obvious, because of the exception UnicodeDecodeError that
# can be raised by ll_str2unicode()
-
-
+ def test_quasi_immut(self):
+ ops = """
+ [p0, p1, i0]
+ quasiimmut_field(p0, descr=quasiimmutdescr)
+ guard_not_invalidated() []
+ i1 = getfield_gc(p0, descr=quasifielddescr)
+ jump(p1, p0, i1)
+ """
+ expected = """
+ [p0, p1, i0]
+ i1 = getfield_gc(p0, descr=quasifielddescr)
+ jump(p1, p0, i1)
+ """
+ self.optimize_loop(ops, expected)
+
+ def test_quasi_immut_2(self):
+ ops = """
+ []
+ quasiimmut_field(ConstPtr(myptr), descr=quasiimmutdescr)
+ guard_not_invalidated() []
+ i1 = getfield_gc(ConstPtr(myptr), descr=quasifielddescr)
+ jump()
+ """
+ expected = """
+ []
+ guard_not_invalidated() []
+ jump()
+ """
+ self.optimize_loop(ops, expected, expected)
##class TestOOtype(OptimizeOptTest, OOtypeMixin):
diff --git a/pypy/rpython/test/test_annlowlevel.py b/pypy/rpython/test/test_annlowlevel.py
--- a/pypy/rpython/test/test_annlowlevel.py
+++ b/pypy/rpython/test/test_annlowlevel.py
@@ -4,9 +4,12 @@
from pypy.rpython.test.tool import BaseRtypingTest, LLRtypeMixin, OORtypeMixin
from pypy.rpython.lltypesystem.rstr import mallocstr, mallocunicode
+from pypy.rpython.lltypesystem import lltype
from pypy.rpython.ootypesystem import ootype
from pypy.rpython.annlowlevel import hlstr, llstr, oostr
from pypy.rpython.annlowlevel import hlunicode, llunicode
+from pypy.rpython import annlowlevel
+
class TestLLType(BaseRtypingTest, LLRtypeMixin):
def test_hlstr(self):
@@ -53,6 +56,15 @@
res = self.interpret(f, [self.unicode_to_ll(u"abc")])
assert res == 3
+ def test_cast_instance_to_base_ptr(self):
+ class X(object):
+ pass
+ x = X()
+ ptr = annlowlevel.cast_instance_to_base_ptr(x)
+ assert lltype.typeOf(ptr) == annlowlevel.base_ptr_lltype()
+ y = annlowlevel.cast_base_ptr_to_instance(X, ptr)
+ assert y is x
+
class TestOOType(BaseRtypingTest, OORtypeMixin):
def test_hlstr(self):
@@ -71,3 +83,12 @@
res = self.interpret(f, [self.string_to_ll("abc")])
assert res == 3
+
+ def test_cast_instance_to_base_obj(self):
+ class X(object):
+ pass
+ x = X()
+ obj = annlowlevel.cast_instance_to_base_obj(x)
+ assert lltype.typeOf(obj) == annlowlevel.base_obj_ootype()
+ y = annlowlevel.cast_base_ptr_to_instance(X, obj)
+ assert y is x
diff --git a/pypy/jit/backend/llgraph/runner.py b/pypy/jit/backend/llgraph/runner.py
--- a/pypy/jit/backend/llgraph/runner.py
+++ b/pypy/jit/backend/llgraph/runner.py
@@ -286,6 +286,10 @@
raise ValueError("CALL_ASSEMBLER not supported")
llimpl.redirect_call_assembler(self, oldlooptoken, newlooptoken)
+ def invalidate_loop(self, looptoken):
+ for loop in looptoken.compiled_loop_token.loop_and_bridges:
+ loop._obj.externalobj.invalid = True
+
# ----------
def sizeof(self, S):
diff --git a/pypy/jit/metainterp/test/test_optimizeutil.py b/pypy/jit/metainterp/test/test_optimizeutil.py
--- a/pypy/jit/metainterp/test/test_optimizeutil.py
+++ b/pypy/jit/metainterp/test/test_optimizeutil.py
@@ -3,6 +3,7 @@
from pypy.rpython.lltypesystem import lltype, llmemory, rclass, rstr
from pypy.rpython.ootypesystem import ootype
from pypy.rpython.lltypesystem.rclass import OBJECT, OBJECT_VTABLE
+from pypy.rpython.rclass import FieldListAccessor, IR_QUASI_IMMUTABLE
from pypy.jit.backend.llgraph import runner
from pypy.jit.metainterp.history import (BoxInt, BoxPtr, ConstInt, ConstPtr,
@@ -12,6 +13,7 @@
from pypy.jit.codewriter.effectinfo import EffectInfo
from pypy.jit.codewriter.heaptracker import register_known_gctype, adr2int
from pypy.jit.tool.oparser import parse
+from pypy.jit.metainterp.quasiimmut import QuasiImmutDescr
def test_sort_descrs():
class PseudoDescr(AbstractDescr):
@@ -62,6 +64,18 @@
nextdescr = cpu.fielddescrof(NODE, 'next')
otherdescr = cpu.fielddescrof(NODE2, 'other')
+ accessor = FieldListAccessor()
+ accessor.initialize(None, {'inst_field': IR_QUASI_IMMUTABLE})
+ QUASI = lltype.GcStruct('QUASIIMMUT', ('inst_field', lltype.Signed),
+ ('mutate_field', rclass.OBJECTPTR),
+ hints={'immutable_fields': accessor})
+ quasi = lltype.malloc(QUASI, immortal=True)
+ quasifielddescr = cpu.fielddescrof(QUASI, 'inst_field')
+ quasibox = BoxPtr(lltype.cast_opaque_ptr(llmemory.GCREF, quasi))
+ quasiimmutdescr = QuasiImmutDescr(cpu, quasibox,
+ quasifielddescr,
+ cpu.fielddescrof(QUASI, 'mutate_field'))
+
NODEOBJ = lltype.GcStruct('NODEOBJ', ('parent', OBJECT),
('ref', lltype.Ptr(OBJECT)))
nodeobj = lltype.malloc(NODEOBJ)
diff --git a/pypy/jit/metainterp/virtualizable.py b/pypy/jit/metainterp/virtualizable.py
--- a/pypy/jit/metainterp/virtualizable.py
+++ b/pypy/jit/metainterp/virtualizable.py
@@ -1,6 +1,7 @@
from pypy.rpython.lltypesystem import lltype, llmemory
from pypy.rpython.ootypesystem import ootype
from pypy.rpython.annlowlevel import cast_base_ptr_to_instance
+from pypy.rpython.rclass import IR_ARRAY_IMMUTABLE, IR_IMMUTABLE
from pypy.rpython import rvirtualizable2
from pypy.rlib.objectmodel import we_are_translated
from pypy.rlib.unroll import unrolling_iterable
@@ -10,7 +11,7 @@
from pypy.jit.metainterp.warmstate import wrap, unwrap
from pypy.rlib.objectmodel import specialize
-class VirtualizableInfo:
+class VirtualizableInfo(object):
TOKEN_NONE = 0 # must be 0 -- see also x86.call_assembler
TOKEN_TRACING_RESCALL = -1
@@ -33,11 +34,13 @@
all_fields = accessor.fields
static_fields = []
array_fields = []
- for name, suffix in all_fields.iteritems():
- if suffix == '[*]':
+ for name, tp in all_fields.iteritems():
+ if tp == IR_ARRAY_IMMUTABLE:
array_fields.append(name)
+ elif tp == IR_IMMUTABLE:
+ static_fields.append(name)
else:
- static_fields.append(name)
+ raise Exception("unknown type: %s" % tp)
self.static_fields = static_fields
self.array_fields = array_fields
#
diff --git a/pypy/jit/backend/x86/regalloc.py b/pypy/jit/backend/x86/regalloc.py
--- a/pypy/jit/backend/x86/regalloc.py
+++ b/pypy/jit/backend/x86/regalloc.py
@@ -492,6 +492,8 @@
def consider_guard_no_exception(self, op):
self.perform_guard(op, [], None)
+ consider_guard_not_invalidated = consider_guard_no_exception
+
def consider_guard_exception(self, op):
loc = self.rm.make_sure_var_in_reg(op.getarg(0))
box = TempBox()
diff --git a/pypy/rpython/lltypesystem/test/test_ll2ctypes.py b/pypy/rpython/lltypesystem/test/test_ll2ctypes.py
--- a/pypy/rpython/lltypesystem/test/test_ll2ctypes.py
+++ b/pypy/rpython/lltypesystem/test/test_ll2ctypes.py
@@ -1293,6 +1293,28 @@
rffi.cast(SP, p).x = 0
lltype.free(chunk, flavor='raw')
+ def test_opaque_tagged_pointers(self):
+ from pypy.rpython.annlowlevel import cast_base_ptr_to_instance
+ from pypy.rpython.annlowlevel import cast_instance_to_base_ptr
+ from pypy.rpython.lltypesystem import rclass
+
+ class Opaque(object):
+ llopaque = True
+
+ def hide(self):
+ ptr = cast_instance_to_base_ptr(self)
+ return lltype.cast_opaque_ptr(llmemory.GCREF, ptr)
+
+ @staticmethod
+ def show(gcref):
+ ptr = lltype.cast_opaque_ptr(lltype.Ptr(rclass.OBJECT), gcref)
+ return cast_base_ptr_to_instance(Opaque, ptr)
+
+ opaque = Opaque()
+ round = ctypes2lltype(llmemory.GCREF, lltype2ctypes(opaque.hide()))
+ assert Opaque.show(round) is opaque
+
+
class TestPlatform(object):
def test_lib_on_libpaths(self):
from pypy.translator.platform import platform
diff --git a/pypy/jit/metainterp/optimizeopt/heap.py b/pypy/jit/metainterp/optimizeopt/heap.py
--- a/pypy/jit/metainterp/optimizeopt/heap.py
+++ b/pypy/jit/metainterp/optimizeopt/heap.py
@@ -119,6 +119,7 @@
self._lazy_setfields = []
# cached array items: {descr: CachedArrayItems}
self.cached_arrayitems = {}
+ self._remove_guard_not_invalidated = False
def reconstruct_for_next_iteration(self, optimizer, valuemap):
new = OptHeap()
@@ -378,6 +379,43 @@
self.cache_arrayitem_value(op.getdescr(), value, indexvalue, fieldvalue,
write=True)
+ def optimize_QUASIIMMUT_FIELD(self, op):
+ # Pattern: QUASIIMMUT_FIELD(s, descr=QuasiImmutDescr)
+ # x = GETFIELD_GC(s, descr='inst_x')
+ # If 's' is a constant (after optimizations), then we make 's.inst_x'
+ # a constant too, and we rely on the rest of the optimizations to
+ # constant-fold the following getfield_gc.
+ structvalue = self.getvalue(op.getarg(0))
+ if not structvalue.is_constant():
+ self._remove_guard_not_invalidated = True
+ return # not a constant at all; ignore QUASIIMMUT_FIELD
+ #
+ from pypy.jit.metainterp.quasiimmut import QuasiImmutDescr
+ qmutdescr = op.getdescr()
+ assert isinstance(qmutdescr, QuasiImmutDescr)
+ # check that the value is still correct; it could have changed
+ # already between the tracing and now. In this case, we are
+ # simply ignoring the QUASIIMMUT_FIELD hint and compiling it
+ # as a regular getfield.
+ if not qmutdescr.is_still_valid():
+ self._remove_guard_not_invalidated = True
+ return
+ # 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
+ # perform the replacement in the list of operations
+ fieldvalue = self.getvalue(qmutdescr.constantfieldbox)
+ cf = self.field_cache(qmutdescr.fielddescr)
+ cf.remember_field_value(structvalue, fieldvalue)
+ self._remove_guard_not_invalidated = False
+
+ def optimize_GUARD_NOT_INVALIDATED(self, op):
+ if self._remove_guard_not_invalidated:
+ return
+ self._remove_guard_not_invalidated = False
+ self.emit_operation(op)
+
def propagate_forward(self, op):
opnum = op.getopnum()
for value, func in optimize_ops:
diff --git a/pypy/jit/metainterp/executor.py b/pypy/jit/metainterp/executor.py
--- a/pypy/jit/metainterp/executor.py
+++ b/pypy/jit/metainterp/executor.py
@@ -312,6 +312,7 @@
rop.DEBUG_MERGE_POINT,
rop.JIT_DEBUG,
rop.SETARRAYITEM_RAW,
+ rop.QUASIIMMUT_FIELD,
): # list of opcodes never executed by pyjitpl
continue
raise AssertionError("missing %r" % (key,))
diff --git a/pypy/rpython/rclass.py b/pypy/rpython/rclass.py
--- a/pypy/rpython/rclass.py
+++ b/pypy/rpython/rclass.py
@@ -3,7 +3,8 @@
#from pypy.annotation.classdef import isclassdef
from pypy.annotation import description
from pypy.rpython.error import TyperError
-from pypy.rpython.rmodel import Repr, getgcflavor
+from pypy.rpython.rmodel import Repr, getgcflavor, inputconst
+from pypy.rpython.lltypesystem.lltype import Void
class FieldListAccessor(object):
@@ -12,6 +13,8 @@
assert type(fields) is dict
self.TYPE = TYPE
self.fields = fields
+ for x in fields.itervalues():
+ assert isinstance(x, ImmutableRanking)
def __repr__(self):
return '<FieldListAccessor for %s>' % getattr(self, 'TYPE', '?')
@@ -19,6 +22,20 @@
def _freeze_(self):
return True
+class ImmutableRanking(object):
+ def __init__(self, name, is_immutable):
+ self.name = name
+ self.is_immutable = is_immutable
+ def __nonzero__(self):
+ return self.is_immutable
+ def __repr__(self):
+ return '<%s>' % self.name
+
+IR_MUTABLE = ImmutableRanking('mutable', False)
+IR_IMMUTABLE = ImmutableRanking('immutable', True)
+IR_ARRAY_IMMUTABLE = ImmutableRanking('array_immutable', True)
+IR_QUASI_IMMUTABLE = ImmutableRanking('quasi_immutable', False)
+
class ImmutableConflictError(Exception):
"""Raised when the _immutable_ or _immutable_fields_ hints are
not consistent across a class hierarchy."""
@@ -155,7 +172,8 @@
self.classdef = classdef
def _setup_repr(self):
- pass
+ if self.classdef is None:
+ self.immutable_field_set = set()
def _check_for_immutable_hints(self, hints):
loc = self.classdef.classdesc.lookup('_immutable_')
@@ -167,13 +185,13 @@
self.classdef,))
hints = hints.copy()
hints['immutable'] = True
- self.immutable_field_list = [] # unless overwritten below
+ self.immutable_field_set = set() # unless overwritten below
if self.classdef.classdesc.lookup('_immutable_fields_') is not None:
hints = hints.copy()
immutable_fields = self.classdef.classdesc.classdict.get(
'_immutable_fields_')
if immutable_fields is not None:
- self.immutable_field_list = immutable_fields.value
+ self.immutable_field_set = set(immutable_fields.value)
accessor = FieldListAccessor()
hints['immutable_fields'] = accessor
return hints
@@ -201,33 +219,35 @@
if "immutable_fields" in hints:
accessor = hints["immutable_fields"]
if not hasattr(accessor, 'fields'):
- immutable_fields = []
+ immutable_fields = set()
rbase = self
while rbase.classdef is not None:
- immutable_fields += rbase.immutable_field_list
+ immutable_fields.update(rbase.immutable_field_set)
rbase = rbase.rbase
self._parse_field_list(immutable_fields, accessor)
def _parse_field_list(self, fields, accessor):
- with_suffix = {}
+ ranking = {}
for name in fields:
- if name.endswith('[*]'):
+ if name.endswith('[*]'): # for virtualizables' lists
name = name[:-3]
- suffix = '[*]'
- else:
- suffix = ''
+ rank = IR_ARRAY_IMMUTABLE
+ elif name.endswith('?'): # a quasi-immutable field
+ name = name[:-1]
+ rank = IR_QUASI_IMMUTABLE
+ else: # a regular immutable/green field
+ rank = IR_IMMUTABLE
try:
mangled_name, r = self._get_field(name)
except KeyError:
continue
- with_suffix[mangled_name] = suffix
- accessor.initialize(self.object_type, with_suffix)
- return with_suffix
+ ranking[mangled_name] = rank
+ accessor.initialize(self.object_type, ranking)
+ return ranking
def _check_for_immutable_conflicts(self):
# check for conflicts, i.e. a field that is defined normally as
# mutable in some parent class but that is now declared immutable
- from pypy.rpython.lltypesystem.lltype import Void
is_self_immutable = "immutable" in self.object_type._hints
base = self
while base.classdef is not None:
@@ -248,12 +268,30 @@
"class %r has _immutable_=True, but parent class %r "
"defines (at least) the mutable field %r" % (
self, base, fieldname))
- if fieldname in self.immutable_field_list:
+ if (fieldname in self.immutable_field_set or
+ (fieldname + '?') in self.immutable_field_set):
raise ImmutableConflictError(
"field %r is defined mutable in class %r, but "
"listed in _immutable_fields_ in subclass %r" % (
fieldname, base, self))
+ def hook_access_field(self, vinst, cname, llops, flags):
+ pass # for virtualizables; see rvirtualizable2.py
+
+ def hook_setfield(self, vinst, fieldname, llops):
+ if self.is_quasi_immutable(fieldname):
+ c_fieldname = inputconst(Void, 'mutate_' + fieldname)
+ llops.genop('jit_force_quasi_immutable', [vinst, c_fieldname])
+
+ def is_quasi_immutable(self, fieldname):
+ search = fieldname + '?'
+ rbase = self
+ while rbase.classdef is not None:
+ if search in rbase.immutable_field_set:
+ return True
+ rbase = rbase.rbase
+ return False
+
def new_instance(self, llops, classcallhop=None):
raise NotImplementedError
diff --git a/pypy/jit/codewriter/policy.py b/pypy/jit/codewriter/policy.py
--- a/pypy/jit/codewriter/policy.py
+++ b/pypy/jit/codewriter/policy.py
@@ -1,5 +1,5 @@
from pypy.translator.simplify import get_funcobj
-from pypy.jit.metainterp import history
+from pypy.jit.metainterp import history, quasiimmut
from pypy.rpython.lltypesystem import lltype, rclass
from pypy.tool.udir import udir
@@ -85,12 +85,20 @@
getkind(v.concretetype, supports_floats, supports_longlong)
v = op.result
getkind(v.concretetype, supports_floats, supports_longlong)
+ check_skip_operation(op)
except NotImplementedError, e:
log.WARNING('%s, ignoring graph' % (e,))
log.WARNING(' %s' % (graph,))
return True
return False
+def check_skip_operation(op):
+ if op.opname == 'setfield':
+ if quasiimmut.is_quasi_immutable(op.args[0].concretetype.TO,
+ op.args[1].value):
+ raise NotImplementedError("write to quasi-immutable field %r"
+ % (op.args[1].value,))
+
# ____________________________________________________________
class StopAtXPolicy(JitPolicy):
diff --git a/pypy/jit/backend/llgraph/llimpl.py b/pypy/jit/backend/llgraph/llimpl.py
--- a/pypy/jit/backend/llgraph/llimpl.py
+++ b/pypy/jit/backend/llgraph/llimpl.py
@@ -167,6 +167,7 @@
class CompiledLoop(object):
has_been_freed = False
+ invalid = False
def __init__(self):
self.inputargs = []
@@ -933,6 +934,9 @@
if forced:
raise GuardFailed
+ def op_guard_not_invalidated(self, descr):
+ if self.loop.invalid:
+ raise GuardFailed
class OOFrame(Frame):
diff --git a/pypy/jit/codewriter/test/test_jtransform.py b/pypy/jit/codewriter/test/test_jtransform.py
--- a/pypy/jit/codewriter/test/test_jtransform.py
+++ b/pypy/jit/codewriter/test/test_jtransform.py
@@ -947,3 +947,43 @@
assert op1.args[1] == 'calldescr-%d' % effectinfo.EffectInfo.OS_ARRAYCOPY
assert op1.args[2] == ListOfKind('int', [v3, v4, v5])
assert op1.args[3] == ListOfKind('ref', [v1, v2])
+
+def test_quasi_immutable():
+ from pypy.rpython.rclass import FieldListAccessor, IR_QUASI_IMMUTABLE
+ accessor = FieldListAccessor()
+ accessor.initialize(None, {'inst_x': IR_QUASI_IMMUTABLE})
+ v2 = varoftype(lltype.Signed)
+ STRUCT = lltype.GcStruct('struct', ('inst_x', lltype.Signed),
+ ('mutate_x', rclass.OBJECTPTR),
+ hints={'immutable_fields': accessor})
+ for v_x in [const(lltype.malloc(STRUCT)), varoftype(lltype.Ptr(STRUCT))]:
+ op = SpaceOperation('getfield', [v_x, Constant('inst_x', lltype.Void)],
+ v2)
+ tr = Transformer(FakeCPU())
+ [_, op1, op2] = tr.rewrite_operation(op)
+ assert op1.opname == 'record_quasiimmut_field'
+ assert len(op1.args) == 3
+ assert op1.args[0] == v_x
+ assert op1.args[1] == ('fielddescr', STRUCT, 'inst_x')
+ assert op1.args[2] == ('fielddescr', STRUCT, 'mutate_x')
+ assert op1.result is None
+ assert op2.opname == 'getfield_gc_i'
+ assert len(op2.args) == 2
+ assert op2.args[0] == v_x
+ assert op2.args[1] == ('fielddescr', STRUCT, 'inst_x')
+ assert op2.result is op.result
+
+def test_quasi_immutable_setfield():
+ from pypy.rpython.rclass import FieldListAccessor, IR_QUASI_IMMUTABLE
+ accessor = FieldListAccessor()
+ accessor.initialize(None, {'inst_x': IR_QUASI_IMMUTABLE})
+ v1 = varoftype(lltype.Signed)
+ STRUCT = lltype.GcStruct('struct', ('inst_x', lltype.Signed),
+ ('mutate_x', rclass.OBJECTPTR),
+ hints={'immutable_fields': accessor})
+ for v_x in [const(lltype.malloc(STRUCT)), varoftype(lltype.Ptr(STRUCT))]:
+ op = SpaceOperation('setfield',
+ [v_x, Constant('inst_x', lltype.Void), v1],
+ varoftype(lltype.Void))
+ tr = Transformer(FakeCPU())
+ raises(NotImplementedError, tr.rewrite_operation, op)
diff --git a/pypy/jit/codewriter/effectinfo.py b/pypy/jit/codewriter/effectinfo.py
--- a/pypy/jit/codewriter/effectinfo.py
+++ b/pypy/jit/codewriter/effectinfo.py
@@ -171,7 +171,8 @@
class VirtualizableAnalyzer(BoolGraphAnalyzer):
def analyze_simple_operation(self, op, graphinfo):
return op.opname in ('jit_force_virtualizable',
- 'jit_force_virtual')
+ 'jit_force_virtual',
+ 'jit_force_quasi_immutable')
# ____________________________________________________________
diff --git a/pypy/jit/metainterp/pyjitpl.py b/pypy/jit/metainterp/pyjitpl.py
--- a/pypy/jit/metainterp/pyjitpl.py
+++ b/pypy/jit/metainterp/pyjitpl.py
@@ -555,6 +555,16 @@
opimpl_setfield_raw_r = _opimpl_setfield_raw_any
opimpl_setfield_raw_f = _opimpl_setfield_raw_any
+ @arguments("box", "descr", "descr", "orgpc")
+ def opimpl_record_quasiimmut_field(self, box, fielddescr,
+ mutatefielddescr, orgpc):
+ from pypy.jit.metainterp.quasiimmut import QuasiImmutDescr
+ cpu = self.metainterp.cpu
+ descr = QuasiImmutDescr(cpu, box, fielddescr, mutatefielddescr)
+ self.metainterp.history.record(rop.QUASIIMMUT_FIELD, [box],
+ None, descr=descr)
+ self.generate_guard(rop.GUARD_NOT_INVALIDATED, resumepc=orgpc)
+
def _nonstandard_virtualizable(self, pc, box):
# returns True if 'box' is actually not the "standard" virtualizable
# that is stored in metainterp.virtualizable_boxes[-1]
@@ -1076,6 +1086,8 @@
if opnum == rop.GUARD_NOT_FORCED:
resumedescr = compile.ResumeGuardForcedDescr(metainterp_sd,
metainterp.jitdriver_sd)
+ elif opnum == rop.GUARD_NOT_INVALIDATED:
+ resumedescr = compile.ResumeGuardNotInvalidated()
else:
resumedescr = compile.ResumeGuardDescr()
guard_op = metainterp.history.record(opnum, moreargs, None,
@@ -1848,6 +1860,9 @@
self.handle_possible_exception()
except ChangeFrame:
pass
+ elif opnum == rop.GUARD_NOT_INVALIDATED:
+ pass # XXX we want to do something special in resume descr,
+ # but not now
elif opnum == rop.GUARD_NO_OVERFLOW: # an overflow now detected
self.execute_raised(OverflowError(), constant=True)
try:
diff --git a/pypy/jit/metainterp/compile.py b/pypy/jit/metainterp/compile.py
--- a/pypy/jit/metainterp/compile.py
+++ b/pypy/jit/metainterp/compile.py
@@ -76,6 +76,11 @@
op.setdescr(None) # clear reference, mostly for tests
if not we_are_translated():
op._jumptarget_number = descr.number
+ # record this looptoken on the QuasiImmut used in the code
+ if loop.quasi_immutable_deps is not None:
+ for qmut in loop.quasi_immutable_deps:
+ qmut.register_loop_token(wref)
+ # XXX maybe we should clear the dictionary here
# mostly for tests: make sure we don't keep a reference to the LoopToken
loop.token = None
if not we_are_translated():
@@ -396,6 +401,12 @@
self.copy_all_attributes_into(res)
return res
+class ResumeGuardNotInvalidated(ResumeGuardDescr):
+ def _clone_if_mutable(self):
+ res = ResumeGuardNotInvalidated()
+ self.copy_all_attributes_into(res)
+ return res
+
class ResumeAtPositionDescr(ResumeGuardDescr):
def _clone_if_mutable(self):
res = ResumeAtPositionDescr()
diff --git a/pypy/rpython/lltypesystem/test/test_lloperation.py b/pypy/rpython/lltypesystem/test/test_lloperation.py
--- a/pypy/rpython/lltypesystem/test/test_lloperation.py
+++ b/pypy/rpython/lltypesystem/test/test_lloperation.py
@@ -54,6 +54,7 @@
def test_is_pure():
from pypy.objspace.flow.model import Variable, Constant
+ from pypy.rpython import rclass
assert llop.bool_not.is_pure([Variable()])
assert llop.debug_assert.is_pure([Variable()])
assert not llop.int_add_ovf.is_pure([Variable(), Variable()])
@@ -85,38 +86,50 @@
assert llop.getarrayitem.is_pure([v_a2, Variable()])
assert llop.getarraysize.is_pure([v_a2])
#
- accessor = rclass.FieldListAccessor()
- S3 = lltype.GcStruct('S', ('x', lltype.Signed), ('y', lltype.Signed),
- hints={'immutable_fields': accessor})
- accessor.initialize(S3, {'x': ''})
- v_s3 = Variable()
- v_s3.concretetype = lltype.Ptr(S3)
- assert not llop.setfield.is_pure([v_s3, Constant('x'), Variable()])
- assert not llop.setfield.is_pure([v_s3, Constant('y'), Variable()])
- assert llop.getfield.is_pure([v_s3, Constant('x')])
- assert not llop.getfield.is_pure([v_s3, Constant('y')])
+ for kind in [rclass.IR_MUTABLE, rclass.IR_IMMUTABLE,
+ rclass.IR_ARRAY_IMMUTABLE, rclass.IR_QUASI_IMMUTABLE]:
+ accessor = rclass.FieldListAccessor()
+ S3 = lltype.GcStruct('S', ('x', lltype.Signed), ('y', lltype.Signed),
+ hints={'immutable_fields': accessor})
+ accessor.initialize(S3, {'x': kind})
+ v_s3 = Variable()
+ v_s3.concretetype = lltype.Ptr(S3)
+ assert not llop.setfield.is_pure([v_s3, Constant('x'), Variable()])
+ assert not llop.setfield.is_pure([v_s3, Constant('y'), Variable()])
+ assert llop.getfield.is_pure([v_s3, Constant('x')]) is kind
+ assert not llop.getfield.is_pure([v_s3, Constant('y')])
def test_getfield_pure():
S1 = lltype.GcStruct('S', ('x', lltype.Signed), ('y', lltype.Signed))
S2 = lltype.GcStruct('S', ('x', lltype.Signed), ('y', lltype.Signed),
hints={'immutable': True})
accessor = rclass.FieldListAccessor()
- S3 = lltype.GcStruct('S', ('x', lltype.Signed), ('y', lltype.Signed),
- hints={'immutable_fields': accessor})
- accessor.initialize(S3, {'x': ''})
#
s1 = lltype.malloc(S1); s1.x = 45
py.test.raises(TypeError, llop.getfield, lltype.Signed, s1, 'x')
s2 = lltype.malloc(S2); s2.x = 45
assert llop.getfield(lltype.Signed, s2, 'x') == 45
- s3 = lltype.malloc(S3); s3.x = 46; s3.y = 47
- assert llop.getfield(lltype.Signed, s3, 'x') == 46
- py.test.raises(TypeError, llop.getfield, lltype.Signed, s3, 'y')
#
py.test.raises(TypeError, llop.getinteriorfield, lltype.Signed, s1, 'x')
assert llop.getinteriorfield(lltype.Signed, s2, 'x') == 45
- assert llop.getinteriorfield(lltype.Signed, s3, 'x') == 46
- py.test.raises(TypeError, llop.getinteriorfield, lltype.Signed, s3, 'y')
+ #
+ for kind in [rclass.IR_MUTABLE, rclass.IR_IMMUTABLE,
+ rclass.IR_ARRAY_IMMUTABLE, rclass.IR_QUASI_IMMUTABLE]:
+ #
+ S3 = lltype.GcStruct('S', ('x', lltype.Signed), ('y', lltype.Signed),
+ hints={'immutable_fields': accessor})
+ accessor.initialize(S3, {'x': kind})
+ s3 = lltype.malloc(S3); s3.x = 46; s3.y = 47
+ if kind in [rclass.IR_IMMUTABLE, rclass.IR_ARRAY_IMMUTABLE]:
+ assert llop.getfield(lltype.Signed, s3, 'x') == 46
+ assert llop.getinteriorfield(lltype.Signed, s3, 'x') == 46
+ else:
+ py.test.raises(TypeError, llop.getfield, lltype.Signed, s3, 'x')
+ py.test.raises(TypeError, llop.getinteriorfield,
+ lltype.Signed, s3, 'x')
+ py.test.raises(TypeError, llop.getfield, lltype.Signed, s3, 'y')
+ py.test.raises(TypeError, llop.getinteriorfield,
+ lltype.Signed, s3, 'y')
# ___________________________________________________________________________
# This tests that the LLInterpreter and the LL_OPERATIONS tables are in sync.
diff --git a/pypy/jit/backend/x86/runner.py b/pypy/jit/backend/x86/runner.py
--- a/pypy/jit/backend/x86/runner.py
+++ b/pypy/jit/backend/x86/runner.py
@@ -145,6 +145,14 @@
def redirect_call_assembler(self, oldlooptoken, newlooptoken):
self.assembler.redirect_call_assembler(oldlooptoken, newlooptoken)
+ def invalidate_loop(self, looptoken):
+ from pypy.jit.backend.x86 import codebuf
+
+ for addr, tgt in looptoken.compiled_loop_token.invalidate_positions:
+ mc = codebuf.MachineCodeBlockWrapper()
+ mc.JMP_l(tgt)
+ mc.copy_to_raw_memory(addr - 1)
+
class CPU386(AbstractX86CPU):
WORD = 4
NUM_REGS = 8
diff --git a/pypy/rpython/lltypesystem/opimpl.py b/pypy/rpython/lltypesystem/opimpl.py
--- a/pypy/rpython/lltypesystem/opimpl.py
+++ b/pypy/rpython/lltypesystem/opimpl.py
@@ -525,6 +525,9 @@
def op_jit_force_virtual(x):
return x
+def op_jit_force_quasi_immutable(*args):
+ pass
+
def op_get_group_member(TYPE, grpptr, memberoffset):
from pypy.rpython.lltypesystem import llgroup
assert isinstance(memberoffset, llgroup.GroupMemberOffset)
diff --git a/pypy/rpython/test/test_rvirtualizable2.py b/pypy/rpython/test/test_rvirtualizable2.py
--- a/pypy/rpython/test/test_rvirtualizable2.py
+++ b/pypy/rpython/test/test_rvirtualizable2.py
@@ -5,6 +5,7 @@
from pypy.rlib.jit import hint
from pypy.objspace.flow.model import summary
from pypy.rpython.llinterp import LLInterpreter
+from pypy.rpython.rclass import IR_IMMUTABLE, IR_ARRAY_IMMUTABLE
from pypy import conftest
@@ -116,8 +117,8 @@
TYPE = self.gettype(v_inst)
accessor = TYPE._hints['virtualizable2_accessor']
assert accessor.TYPE == TYPE
- assert accessor.fields == {self.prefix + 'v1' : "",
- self.prefix + 'v2': "[*]"}
+ assert accessor.fields == {self.prefix + 'v1': IR_IMMUTABLE,
+ self.prefix + 'v2': IR_ARRAY_IMMUTABLE}
#
def fn2(n):
Base().base1 = 42
diff --git a/pypy/jit/metainterp/blackhole.py b/pypy/jit/metainterp/blackhole.py
--- a/pypy/jit/metainterp/blackhole.py
+++ b/pypy/jit/metainterp/blackhole.py
@@ -1166,6 +1166,11 @@
def bhimpl_setfield_raw_f(cpu, struct, fielddescr, newvalue):
cpu.bh_setfield_raw_f(struct, fielddescr, newvalue)
+ @arguments("cpu", "r", "d", "d")
+ def bhimpl_record_quasiimmut_field(self, struct, fielddescr,
+ mutatefielddescr):
+ pass
+
@arguments("cpu", "d", returns="r")
def bhimpl_new(cpu, descr):
return cpu.bh_new(descr)
@@ -1287,6 +1292,8 @@
# We get here because it used to overflow, but now it no longer
# does.
pass
+ elif opnum == rop.GUARD_NOT_INVALIDATED:
+ pass
else:
from pypy.jit.metainterp.resoperation import opname
raise NotImplementedError(opname[opnum])
diff --git a/pypy/rpython/lltypesystem/lloperation.py b/pypy/rpython/lltypesystem/lloperation.py
--- a/pypy/rpython/lltypesystem/lloperation.py
+++ b/pypy/rpython/lltypesystem/lloperation.py
@@ -433,6 +433,7 @@
'jit_marker': LLOp(),
'jit_force_virtualizable':LLOp(canrun=True),
'jit_force_virtual': LLOp(canrun=True),
+ 'jit_force_quasi_immutable': LLOp(canrun=True),
'get_exception_addr': LLOp(),
'get_exc_value_addr': LLOp(),
'do_malloc_fixedsize_clear':LLOp(canraise=(MemoryError,),canunwindgc=True),
diff --git a/pypy/jit/backend/x86/test/test_quasiimmut.py b/pypy/jit/backend/x86/test/test_quasiimmut.py
new file mode 100644
--- /dev/null
+++ b/pypy/jit/backend/x86/test/test_quasiimmut.py
@@ -0,0 +1,9 @@
+
+import py
+from pypy.jit.backend.x86.test.test_basic import Jit386Mixin
+from pypy.jit.metainterp.test import test_quasiimmut
+
+class TestLoopSpec(Jit386Mixin, test_quasiimmut.QuasiImmutTests):
+ # for the individual tests see
+ # ====> ../../../metainterp/test/test_loop.py
+ pass
diff --git a/pypy/rpython/rvirtualizable2.py b/pypy/rpython/rvirtualizable2.py
--- a/pypy/rpython/rvirtualizable2.py
+++ b/pypy/rpython/rvirtualizable2.py
@@ -50,7 +50,7 @@
def hook_access_field(self, vinst, cname, llops, flags):
#if not flags.get('access_directly'):
- if cname.value in self.my_redirected_fields:
+ if self.my_redirected_fields.get(cname.value):
cflags = inputconst(lltype.Void, flags)
llops.genop('jit_force_virtualizable', [vinst, cname, cflags])
diff --git a/pypy/jit/metainterp/resoperation.py b/pypy/jit/metainterp/resoperation.py
--- a/pypy/jit/metainterp/resoperation.py
+++ b/pypy/jit/metainterp/resoperation.py
@@ -380,6 +380,7 @@
'GUARD_NO_OVERFLOW/0d',
'GUARD_OVERFLOW/0d',
'GUARD_NOT_FORCED/0d',
+ 'GUARD_NOT_INVALIDATED/0d',
'_GUARD_LAST', # ----- end of guard operations -----
'_NOSIDEEFFECT_FIRST', # ----- start of no_side_effect operations -----
@@ -475,6 +476,7 @@
'VIRTUAL_REF_FINISH/2', # removed before it's passed to the backend
'COPYSTRCONTENT/5', # src, dst, srcstart, dststart, length
'COPYUNICODECONTENT/5',
+ 'QUASIIMMUT_FIELD/1d', # [objptr], descr=SlowMutateDescr
'_CANRAISE_FIRST', # ----- start of can_raise operations -----
'_CALL_FIRST',
diff --git a/pypy/rpython/lltypesystem/test/test_lltype.py b/pypy/rpython/lltypesystem/test/test_lltype.py
--- a/pypy/rpython/lltypesystem/test/test_lltype.py
+++ b/pypy/rpython/lltypesystem/test/test_lltype.py
@@ -794,15 +794,8 @@
def __init__(self, fields):
self.fields = fields
S = GcStruct('S', ('x', lltype.Signed),
- hints={'immutable_fields': FieldListAccessor({'x':''})})
- assert S._immutable_field('x') == True
- #
- class FieldListAccessor(object):
- def __init__(self, fields):
- self.fields = fields
- S = GcStruct('S', ('x', lltype.Signed),
- hints={'immutable_fields': FieldListAccessor({'x':'[*]'})})
- assert S._immutable_field('x') == '[*]'
+ hints={'immutable_fields': FieldListAccessor({'x': 1234})})
+ assert S._immutable_field('x') == 1234
def test_typedef():
T = Typedef(Signed, 'T')
diff --git a/pypy/jit/metainterp/optimizeopt/unroll.py b/pypy/jit/metainterp/optimizeopt/unroll.py
--- a/pypy/jit/metainterp/optimizeopt/unroll.py
+++ b/pypy/jit/metainterp/optimizeopt/unroll.py
@@ -267,6 +267,8 @@
virtual_state = modifier.get_virtual_state(jump_args)
loop.preamble.operations = self.optimizer.newoperations
+ loop.preamble.quasi_immutable_deps = (
+ self.optimizer.quasi_immutable_deps)
self.optimizer = self.optimizer.reconstruct_for_next_iteration()
inputargs = self.inline(self.cloned_operations,
loop.inputargs, jump_args)
@@ -276,6 +278,7 @@
loop.preamble.operations.append(jmp)
loop.operations = self.optimizer.newoperations
+ loop.quasi_immutable_deps = self.optimizer.quasi_immutable_deps
start_resumedescr = loop.preamble.start_resumedescr.clone_if_mutable()
assert isinstance(start_resumedescr, ResumeGuardDescr)
diff --git a/pypy/objspace/std/typeobject.py b/pypy/objspace/std/typeobject.py
--- a/pypy/objspace/std/typeobject.py
+++ b/pypy/objspace/std/typeobject.py
@@ -85,6 +85,7 @@
'nslots',
'instancetypedef',
'terminator',
+ '_version_tag?',
]
# for config.objspace.std.getattributeshortcut
diff --git a/pypy/jit/metainterp/warmspot.py b/pypy/jit/metainterp/warmspot.py
--- a/pypy/jit/metainterp/warmspot.py
+++ b/pypy/jit/metainterp/warmspot.py
@@ -131,6 +131,16 @@
def find_set_param(graphs):
return _find_jit_marker(graphs, 'set_param')
+def find_force_quasi_immutable(graphs):
+ results = []
+ for graph in graphs:
+ for block in graph.iterblocks():
+ for i in range(len(block.operations)):
+ op = block.operations[i]
+ if op.opname == 'jit_force_quasi_immutable':
+ results.append((graph, block, i))
+ return results
+
def get_stats():
return pyjitpl._warmrunnerdesc.stats
@@ -187,6 +197,7 @@
self.rewrite_can_enter_jits()
self.rewrite_set_param()
self.rewrite_force_virtual(vrefinfo)
+ self.rewrite_force_quasi_immutable()
self.add_finish()
self.metainterp_sd.finish_setup(self.codewriter)
@@ -842,6 +853,28 @@
all_graphs = self.translator.graphs
vrefinfo.replace_force_virtual_with_call(all_graphs)
+ def replace_force_quasiimmut_with_direct_call(self, op):
+ ARG = op.args[0].concretetype
+ mutatefieldname = op.args[1].value
+ key = (ARG, mutatefieldname)
+ if key in self._cache_force_quasiimmed_funcs:
+ cptr = self._cache_force_quasiimmed_funcs[key]
+ else:
+ from pypy.jit.metainterp import quasiimmut
+ func = quasiimmut.make_invalidation_function(ARG, mutatefieldname)
+ FUNC = lltype.Ptr(lltype.FuncType([ARG], lltype.Void))
+ llptr = self.helper_func(FUNC, func)
+ cptr = Constant(llptr, FUNC)
+ self._cache_force_quasiimmed_funcs[key] = cptr
+ op.opname = 'direct_call'
+ op.args = [cptr, op.args[0]]
+
+ def rewrite_force_quasi_immutable(self):
+ self._cache_force_quasiimmed_funcs = {}
+ graphs = self.translator.graphs
+ for graph, block, i in find_force_quasi_immutable(graphs):
+ self.replace_force_quasiimmut_with_direct_call(block.operations[i])
+
# ____________________________________________________________
def execute_token(self, loop_token):
diff --git a/pypy/rpython/lltypesystem/rclass.py b/pypy/rpython/lltypesystem/rclass.py
--- a/pypy/rpython/lltypesystem/rclass.py
+++ b/pypy/rpython/lltypesystem/rclass.py
@@ -322,6 +322,7 @@
# before they are fully built, to avoid strange bugs in case
# of recursion where other code would uses these
# partially-initialized dicts.
+ AbstractInstanceRepr._setup_repr(self)
self.rclass = getclassrepr(self.rtyper, self.classdef)
fields = {}
allinstancefields = {}
@@ -370,6 +371,11 @@
kwds = {}
if self.gcflavor == 'gc':
kwds['rtti'] = True
+
+ for name, attrdef in attrs:
+ if not attrdef.readonly and self.is_quasi_immutable(name):
+ llfields.append(('mutate_' + name, OBJECTPTR))
+
object_type = MkStruct(self.classdef.name,
('super', self.rbase.object_type),
hints=hints,
@@ -488,6 +494,7 @@
if force_cast:
vinst = llops.genop('cast_pointer', [vinst], resulttype=self)
self.hook_access_field(vinst, cname, llops, flags)
+ self.hook_setfield(vinst, attr, llops)
llops.genop('setfield', [vinst, cname, vvalue])
else:
if self.classdef is None:
@@ -495,9 +502,6 @@
self.rbase.setfield(vinst, attr, vvalue, llops, force_cast=True,
flags=flags)
- def hook_access_field(self, vinst, cname, llops, flags):
- pass # for virtualizables; see rvirtualizable2.py
-
def new_instance(self, llops, classcallhop=None):
"""Build a new instance, without calling __init__."""
flavor = self.gcflavor
diff --git a/pypy/rpython/annlowlevel.py b/pypy/rpython/annlowlevel.py
--- a/pypy/rpython/annlowlevel.py
+++ b/pypy/rpython/annlowlevel.py
@@ -480,7 +480,26 @@
# ____________________________________________________________
def cast_object_to_ptr(PTR, object):
- raise NotImplementedError("cast_object_to_ptr")
+ """NOT_RPYTHON: hack. The object may be disguised as a PTR now.
+ Limited to casting a given object to a single type.
+ """
+ if isinstance(PTR, lltype.Ptr):
+ TO = PTR.TO
+ else:
+ TO = PTR
+ if not hasattr(object, '_carry_around_for_tests'):
+ assert not hasattr(object, '_TYPE')
+ object._carry_around_for_tests = True
+ object._TYPE = TO
+ else:
+ assert object._TYPE == TO
+ #
+ if isinstance(PTR, lltype.Ptr):
+ return lltype._ptr(PTR, object, True)
+ elif isinstance(PTR, ootype.Instance):
+ return object
+ else:
+ raise NotImplementedError("cast_object_to_ptr(%r, ...)" % PTR)
def cast_instance_to_base_ptr(instance):
return cast_object_to_ptr(base_ptr_lltype(), instance)
@@ -535,7 +554,13 @@
# ____________________________________________________________
def cast_base_ptr_to_instance(Class, ptr):
- raise NotImplementedError("cast_base_ptr_to_instance")
+ """NOT_RPYTHON: hack. Reverse the hacking done in cast_object_to_ptr()."""
+ if isinstance(lltype.typeOf(ptr), lltype.Ptr):
+ ptr = ptr._as_obj()
+ if not isinstance(ptr, Class):
+ raise NotImplementedError("cast_base_ptr_to_instance: casting %r to %r"
+ % (ptr, Class))
+ return ptr
class CastBasePtrToInstanceEntry(extregistry.ExtRegistryEntry):
_about_ = cast_base_ptr_to_instance
diff --git a/pypy/jit/metainterp/optimizeopt/simplify.py b/pypy/jit/metainterp/optimizeopt/simplify.py
--- a/pypy/jit/metainterp/optimizeopt/simplify.py
+++ b/pypy/jit/metainterp/optimizeopt/simplify.py
@@ -20,6 +20,9 @@
op = ResOperation(rop.SAME_AS, [op.getarg(0)], op.result)
self.emit_operation(op)
+ def optimize_QUASIIMMUT_FIELD(self, op):
+ pass
+
def propagate_forward(self, op):
opnum = op.getopnum()
for value, func in optimize_ops:
diff --git a/pypy/rpython/ootypesystem/ootype.py b/pypy/rpython/ootypesystem/ootype.py
--- a/pypy/rpython/ootypesystem/ootype.py
+++ b/pypy/rpython/ootypesystem/ootype.py
@@ -268,13 +268,14 @@
return self._superclass._get_fields_with_default() + self._fields_with_default
def _immutable_field(self, field):
+ if self._hints.get('immutable'):
+ return True
if 'immutable_fields' in self._hints:
try:
- s = self._hints['immutable_fields'].fields[field]
- return s or True
+ return self._hints['immutable_fields'].fields[field]
except KeyError:
pass
- return self._hints.get('immutable', False)
+ return False
class SpecializableType(OOType):
diff --git a/pypy/rpython/test/test_rclass.py b/pypy/rpython/test/test_rclass.py
--- a/pypy/rpython/test/test_rclass.py
+++ b/pypy/rpython/test/test_rclass.py
@@ -5,6 +5,8 @@
from pypy.rpython.ootypesystem import ootype
from pypy.rlib.rarithmetic import intmask, r_longlong
from pypy.rpython.test.tool import BaseRtypingTest, LLRtypeMixin, OORtypeMixin
+from pypy.rpython.rclass import IR_IMMUTABLE, IR_ARRAY_IMMUTABLE
+from pypy.rpython.rclass import IR_QUASI_IMMUTABLE
from pypy.objspace.flow.model import summary
class EmptyBase(object):
@@ -746,8 +748,10 @@
t, typer, graph = self.gengraph(f, [])
A_TYPE = deref(graph.getreturnvar().concretetype)
accessor = A_TYPE._hints["immutable_fields"]
- assert accessor.fields == {"inst_x" : "", "inst_y" : "[*]"} or \
- accessor.fields == {"ox" : "", "oy" : "[*]"} # for ootype
+ assert accessor.fields == {"inst_x": IR_IMMUTABLE,
+ "inst_y": IR_ARRAY_IMMUTABLE} or \
+ accessor.fields == {"ox": IR_IMMUTABLE,
+ "oy": IR_ARRAY_IMMUTABLE} # for ootype
def test_immutable_fields_subclass_1(self):
from pypy.jit.metainterp.typesystem import deref
@@ -765,8 +769,8 @@
t, typer, graph = self.gengraph(f, [])
B_TYPE = deref(graph.getreturnvar().concretetype)
accessor = B_TYPE._hints["immutable_fields"]
- assert accessor.fields == {"inst_x" : ""} or \
- accessor.fields == {"ox" : ""} # for ootype
+ assert accessor.fields == {"inst_x": IR_IMMUTABLE} or \
+ accessor.fields == {"ox": IR_IMMUTABLE} # for ootype
def test_immutable_fields_subclass_2(self):
from pypy.jit.metainterp.typesystem import deref
@@ -785,8 +789,10 @@
t, typer, graph = self.gengraph(f, [])
B_TYPE = deref(graph.getreturnvar().concretetype)
accessor = B_TYPE._hints["immutable_fields"]
- assert accessor.fields == {"inst_x" : "", "inst_y" : ""} or \
- accessor.fields == {"ox" : "", "oy" : ""} # for ootype
+ assert accessor.fields == {"inst_x": IR_IMMUTABLE,
+ "inst_y": IR_IMMUTABLE} or \
+ accessor.fields == {"ox": IR_IMMUTABLE,
+ "oy": IR_IMMUTABLE} # for ootype
def test_immutable_fields_only_in_subclass(self):
from pypy.jit.metainterp.typesystem import deref
@@ -804,8 +810,8 @@
t, typer, graph = self.gengraph(f, [])
B_TYPE = deref(graph.getreturnvar().concretetype)
accessor = B_TYPE._hints["immutable_fields"]
- assert accessor.fields == {"inst_y" : ""} or \
- accessor.fields == {"oy" : ""} # for ootype
+ assert accessor.fields == {"inst_y": IR_IMMUTABLE} or \
+ accessor.fields == {"oy": IR_IMMUTABLE} # for ootype
def test_immutable_forbidden_inheritance_1(self):
from pypy.rpython.rclass import ImmutableConflictError
@@ -849,8 +855,8 @@
except AttributeError:
A_TYPE = B_TYPE._superclass # for ootype
accessor = A_TYPE._hints["immutable_fields"]
- assert accessor.fields == {"inst_v" : ""} or \
- accessor.fields == {"ov" : ""} # for ootype
+ assert accessor.fields == {"inst_v": IR_IMMUTABLE} or \
+ accessor.fields == {"ov": IR_IMMUTABLE} # for ootype
def test_immutable_subclass_1(self):
from pypy.rpython.rclass import ImmutableConflictError
@@ -895,6 +901,37 @@
B_TYPE = deref(graph.getreturnvar().concretetype)
assert B_TYPE._hints["immutable"]
+ def test_quasi_immutable(self):
+ from pypy.jit.metainterp.typesystem import deref
+ class A(object):
+ _immutable_fields_ = ['x', 'y', 'a?', 'b?']
+ class B(A):
+ pass
+ def f():
+ a = A()
+ a.x = 42
+ a.a = 142
+ b = B()
+ b.x = 43
+ b.y = 41
+ b.a = 44
+ b.b = 45
+ return B()
+ t, typer, graph = self.gengraph(f, [])
+ B_TYPE = deref(graph.getreturnvar().concretetype)
+ accessor = B_TYPE._hints["immutable_fields"]
+ assert accessor.fields == {"inst_y": IR_IMMUTABLE,
+ "inst_b": IR_QUASI_IMMUTABLE} or \
+ accessor.fields == {"ox": IR_IMMUTABLE,
+ "oy": IR_IMMUTABLE,
+ "oa": IR_QUASI_IMMUTABLE,
+ "ob": IR_QUASI_IMMUTABLE} # for ootype
+ found = []
+ for op in graph.startblock.operations:
+ if op.opname == 'jit_force_quasi_immutable':
+ found.append(op.args[1].value)
+ assert found == ['mutate_a', 'mutate_a', 'mutate_b']
+
class TestLLtype(BaseTestRclass, LLRtypeMixin):
diff --git a/pypy/jit/backend/x86/assembler.py b/pypy/jit/backend/x86/assembler.py
--- a/pypy/jit/backend/x86/assembler.py
+++ b/pypy/jit/backend/x86/assembler.py
@@ -48,11 +48,12 @@
class GuardToken(object):
- def __init__(self, faildescr, failargs, fail_locs, exc):
+ def __init__(self, faildescr, failargs, fail_locs, exc, has_jump):
self.faildescr = faildescr
self.failargs = failargs
self.fail_locs = fail_locs
self.exc = exc
+ self.has_jump = has_jump
DEBUG_COUNTER = lltype.Struct('DEBUG_COUNTER', ('i', lltype.Signed))
@@ -133,6 +134,7 @@
def setup(self, looptoken):
assert self.memcpy_addr != 0, "setup_once() not called?"
self.current_clt = looptoken.compiled_loop_token
+ self.invalidate_positions = []
self.pending_guard_tokens = []
self.mc = codebuf.MachineCodeBlockWrapper()
if self.datablockwrapper is None:
@@ -141,6 +143,7 @@
allblocks)
def teardown(self):
+ self.invalidate_positions = None
self.pending_guard_tokens = None
self.mc = None
self.looppos = -1
@@ -435,15 +438,24 @@
# tok.faildescr._x86_adr_jump_offset to contain the raw address of
# the 4-byte target field in the JMP/Jcond instruction, and patch
# the field in question to point (initially) to the recovery stub
+ inv_counter = 0
+ clt = self.current_clt
for tok in self.pending_guard_tokens:
addr = rawstart + tok.pos_jump_offset
tok.faildescr._x86_adr_jump_offset = addr
relative_target = tok.pos_recovery_stub - (tok.pos_jump_offset + 4)
assert rx86.fits_in_32bits(relative_target)
#
- mc = codebuf.MachineCodeBlockWrapper()
- mc.writeimm32(relative_target)
- mc.copy_to_raw_memory(addr)
+ if tok.has_jump:
+ mc = codebuf.MachineCodeBlockWrapper()
+ mc.writeimm32(relative_target)
+ mc.copy_to_raw_memory(addr)
+ else:
+ # guard not invalidate, patch where it jumps
+ pos, _ = self.invalidate_positions[inv_counter]
+ clt.invalidate_positions.append((pos + rawstart,
+ relative_target))
+ inv_counter += 1
def get_asmmemmgr_blocks(self, looptoken):
clt = looptoken.compiled_loop_token
@@ -1447,6 +1459,13 @@
self.mc.CMP(heap(self.cpu.pos_exception()), imm0)
self.implement_guard(guard_token, 'NZ')
+ def genop_guard_guard_not_invalidated(self, ign_1, guard_op, guard_token,
+ locs, ign_2):
+ pos = self.mc.get_relative_pos() + 1 # after potential jmp
+ guard_token.pos_jump_offset = pos
+ self.invalidate_positions.append((pos, 0))
+ self.pending_guard_tokens.append(guard_token)
+
def genop_guard_guard_exception(self, ign_1, guard_op, guard_token,
locs, resloc):
loc = locs[0]
@@ -1545,7 +1564,8 @@
exc = (guard_opnum == rop.GUARD_EXCEPTION or
guard_opnum == rop.GUARD_NO_EXCEPTION or
guard_opnum == rop.GUARD_NOT_FORCED)
- return GuardToken(faildescr, failargs, fail_locs, exc)
+ return GuardToken(faildescr, failargs, fail_locs, exc, has_jump=
+ guard_opnum != rop.GUARD_NOT_INVALIDATED)
def generate_quick_failure(self, guardtok):
"""Generate the initial code for handling a failure. We try to
More information about the Pypy-commit
mailing list