[pypy-commit] pypy arm-backend-2: merge 98d5562c9322 (out of line guards)

bivab noreply at buildbot.pypy.org
Fri Jul 1 14:18:32 CEST 2011


Author: David Schneider <david.schneider at picle.org>
Branch: arm-backend-2
Changeset: r45215:5f9517d7f75b
Date: 2011-06-30 16:08 +0200
http://bitbucket.org/pypy/pypy/changeset/5f9517d7f75b/

Log:	merge 98d5562c9322 (out of line guards)

diff --git a/pypy/annotation/description.py b/pypy/annotation/description.py
--- a/pypy/annotation/description.py
+++ b/pypy/annotation/description.py
@@ -3,7 +3,6 @@
 from pypy.interpreter.pycode import cpython_code_signature
 from pypy.interpreter.argument import rawshape
 from pypy.interpreter.argument import ArgErr
-from pypy.interpreter.function import Defaults
 from pypy.tool.sourcetools import valid_identifier
 from pypy.tool.pairtype import extendabletype
 
@@ -251,7 +250,7 @@
             for x in defaults:
                 defs_s.append(self.bookkeeper.immutablevalue(x))
         try:
-            inputcells = args.match_signature(signature, Defaults(defs_s))
+            inputcells = args.match_signature(signature, defs_s)
         except ArgErr, e:
             raise TypeError, "signature mismatch: %s" % e.getmsg(self.name)
         return inputcells
@@ -638,16 +637,19 @@
         return None
 
     def maybe_return_immutable_list(self, attr, s_result):
-        # hack: 'x.lst' where lst is listed in _immutable_fields_ as 'lst[*]'
+        # hack: 'x.lst' where lst is listed in _immutable_fields_ as
+        # either 'lst[*]' or 'lst?[*]'
         # should really return an immutable list as a result.  Implemented
         # by changing the result's annotation (but not, of course, doing an
         # actual copy in the rtyper).  Tested in pypy.rpython.test.test_rlist,
         # test_immutable_list_out_of_instance.
-        search = '%s[*]' % (attr,)
+        search1 = '%s[*]' % (attr,)
+        search2 = '%s?[*]' % (attr,)
         cdesc = self
         while cdesc is not None:
             if '_immutable_fields_' in cdesc.classdict:
-                if search in cdesc.classdict['_immutable_fields_'].value:
+                if (search1 in cdesc.classdict['_immutable_fields_'].value or
+                    search2 in cdesc.classdict['_immutable_fields_'].value):
                     s_result.listdef.never_resize()
                     s_copy = s_result.listdef.offspring()
                     s_copy.listdef.mark_as_immutable()
diff --git a/pypy/annotation/test/test_annrpython.py b/pypy/annotation/test/test_annrpython.py
--- a/pypy/annotation/test/test_annrpython.py
+++ b/pypy/annotation/test/test_annrpython.py
@@ -3398,6 +3398,20 @@
         s = a.build_types(f, [int])
         assert s.listdef.listitem.immutable
 
+    def test_return_immutable_list_quasiimmut_field(self):
+        class A:
+            _immutable_fields_ = 'lst?[*]'
+        def f(n):
+            a = A()
+            l1 = [n, 0]
+            l1[1] = n+1
+            a.lst = l1
+            return a.lst
+
+        a = self.RPythonAnnotator()
+        s = a.build_types(f, [int])
+        assert s.listdef.listitem.immutable
+
     def test_immutable_list_is_actually_resized(self):
         class A:
             _immutable_fields_ = 'lst[*]'
diff --git a/pypy/interpreter/argument.py b/pypy/interpreter/argument.py
--- a/pypy/interpreter/argument.py
+++ b/pypy/interpreter/argument.py
@@ -239,7 +239,7 @@
 
     ###  Parsing for function calls  ###
 
-    def _match_signature(self, w_firstarg, scope_w, signature, defaults=None,
+    def _match_signature(self, w_firstarg, scope_w, signature, defaults_w=None,
                          blindargs=0):
         """Parse args and kwargs according to the signature of a code object,
         or raise an ArgErr in case of failure.
@@ -247,20 +247,20 @@
         """
         if jit.we_are_jitted() and self._dont_jit:
             return self._match_signature_jit_opaque(w_firstarg, scope_w,
-                                                    signature, defaults,
+                                                    signature, defaults_w,
                                                     blindargs)
         return self._really_match_signature(w_firstarg, scope_w, signature,
-                                            defaults, blindargs)
+                                            defaults_w, blindargs)
 
     @jit.dont_look_inside
     def _match_signature_jit_opaque(self, w_firstarg, scope_w, signature,
-                                    defaults, blindargs):
+                                    defaults_w, blindargs):
         return self._really_match_signature(w_firstarg, scope_w, signature,
-                                            defaults, blindargs)
+                                            defaults_w, blindargs)
 
     @jit.unroll_safe
-    def _really_match_signature(self, w_firstarg, scope_w, signature, defaults=None,
-                                blindargs=0):
+    def _really_match_signature(self, w_firstarg, scope_w, signature,
+                                defaults_w=None, blindargs=0):
         #
         #   args_w = list of the normal actual parameters, wrapped
         #   kwds_w = real dictionary {'keyword': wrapped parameter}
@@ -327,7 +327,7 @@
         elif avail > co_argcount:
             raise ArgErrCount(avail, num_kwds,
                               co_argcount, has_vararg, has_kwarg,
-                              defaults, 0)
+                              defaults_w, 0)
 
         # the code assumes that keywords can potentially be large, but that
         # argnames is typically not too large
@@ -357,12 +357,13 @@
                     num_remainingkwds -= 1
         missing = 0
         if input_argcount < co_argcount:
-            def_first = co_argcount - (0 if defaults is None else defaults.getlen())
+            def_first = co_argcount - (0 if defaults_w is None else len(defaults_w))
             for i in range(input_argcount, co_argcount):
                 if scope_w[i] is not None:
-                    pass
-                elif i >= def_first:
-                    scope_w[i] = defaults.getitem(i - def_first)
+                    continue
+                defnum = i - def_first
+                if defnum >= 0:
+                    scope_w[i] = defaults_w[defnum]
                 else:
                     # error: not enough arguments.  Don't signal it immediately
                     # because it might be related to a problem with */** or
@@ -382,20 +383,20 @@
             if co_argcount == 0:
                 raise ArgErrCount(avail, num_kwds,
                               co_argcount, has_vararg, has_kwarg,
-                              defaults, missing)
+                              defaults_w, missing)
             raise ArgErrUnknownKwds(num_remainingkwds, keywords, used_keywords)
 
         if missing:
             raise ArgErrCount(avail, num_kwds,
                               co_argcount, has_vararg, has_kwarg,
-                              defaults, missing)
+                              defaults_w, missing)
 
         return co_argcount + has_vararg + has_kwarg
 
 
 
     def parse_into_scope(self, w_firstarg,
-                         scope_w, fnname, signature, defaults=None):
+                         scope_w, fnname, signature, defaults_w=None):
         """Parse args and kwargs to initialize a frame
         according to the signature of code object.
         Store the argumentvalues into scope_w.
@@ -403,29 +404,29 @@
         """
         try:
             return self._match_signature(w_firstarg,
-                                         scope_w, signature, defaults, 0)
+                                         scope_w, signature, defaults_w, 0)
         except ArgErr, e:
             raise OperationError(self.space.w_TypeError,
                                  self.space.wrap(e.getmsg(fnname)))
 
-    def _parse(self, w_firstarg, signature, defaults, blindargs=0):
+    def _parse(self, w_firstarg, signature, defaults_w, blindargs=0):
         """Parse args and kwargs according to the signature of a code object,
         or raise an ArgErr in case of failure.
         """
         scopelen = signature.scope_length()
         scope_w = [None] * scopelen
-        self._match_signature(w_firstarg, scope_w, signature, defaults,
+        self._match_signature(w_firstarg, scope_w, signature, defaults_w,
                               blindargs)
         return scope_w
 
 
     def parse_obj(self, w_firstarg,
-                  fnname, signature, defaults=None, blindargs=0):
+                  fnname, signature, defaults_w=None, blindargs=0):
         """Parse args and kwargs to initialize a frame
         according to the signature of code object.
         """
         try:
-            return self._parse(w_firstarg, signature, defaults, blindargs)
+            return self._parse(w_firstarg, signature, defaults_w, blindargs)
         except ArgErr, e:
             raise OperationError(self.space.w_TypeError,
                                  self.space.wrap(e.getmsg(fnname)))
@@ -474,23 +475,23 @@
 
 
 
-    def _match_signature(self, w_firstarg, scope_w, signature, defaults=None,
+    def _match_signature(self, w_firstarg, scope_w, signature, defaults_w=None,
                          blindargs=0):
         self.combine_if_necessary()
         # _match_signature is destructive
         return Arguments._match_signature(
                self, w_firstarg, scope_w, signature,
-               defaults, blindargs)
+               defaults_w, blindargs)
 
     def unpack(self):
         self.combine_if_necessary()
         return Arguments.unpack(self)
 
-    def match_signature(self, signature, defaults):
+    def match_signature(self, signature, defaults_w):
         """Parse args and kwargs according to the signature of a code object,
         or raise an ArgErr in case of failure.
         """
-        return self._parse(None, signature, defaults)
+        return self._parse(None, signature, defaults_w)
 
     def unmatch_signature(self, signature, data_w):
         """kind of inverse of match_signature"""
@@ -603,12 +604,12 @@
 class ArgErrCount(ArgErr):
 
     def __init__(self, got_nargs, nkwds, expected_nargs, has_vararg, has_kwarg,
-                 defaults, missing_args):
+                 defaults_w, missing_args):
         self.expected_nargs = expected_nargs
         self.has_vararg = has_vararg
         self.has_kwarg = has_kwarg
 
-        self.num_defaults = 0 if defaults is None else defaults.getlen()
+        self.num_defaults = 0 if defaults_w is None else len(defaults_w)
         self.missing_args = missing_args
         self.num_args = got_nargs
         self.num_kwds = nkwds
diff --git a/pypy/interpreter/function.py b/pypy/interpreter/function.py
--- a/pypy/interpreter/function.py
+++ b/pypy/interpreter/function.py
@@ -21,28 +21,6 @@
     assert not func.can_change_code
     return func.code
 
-class Defaults(object):
-    _immutable_fields_ = ["items[*]", "promote"]
-
-    def __init__(self, items, promote=False):
-        self.items = items
-        self.promote = promote
-
-    def getitems(self):
-        # an idea - we want to promote only items that we know won't change
-        # too often. this is the case for builtin functions and functions
-        # with known constant defaults. Otherwise we don't want to promote
-        # this so lambda a=a won't create a new trace each time it's
-        # encountered
-        if self.promote:
-            return jit.hint(self, promote=True).items
-        return self.items
-
-    def getitem(self, idx):
-        return self.getitems()[idx]
-
-    def getlen(self):
-        return len(self.getitems())
 
 class Function(Wrappable):
     """A function is a code object captured with some environment:
@@ -50,17 +28,20 @@
     and an arbitrary 'closure' passed to the code object."""
 
     can_change_code = True
+    _immutable_fields_ = ['code?',
+                          'w_func_globals?',
+                          'closure?',
+                          'defs_w?[*]']
 
     def __init__(self, space, code, w_globals=None, defs_w=[], closure=None,
-                 forcename=None, promote_defs=False):
+                 forcename=None):
         self.space = space
         self.name = forcename or code.co_name
         self.w_doc = None   # lazily read from code.getdocstring()
         self.code = code       # Code instance
         self.w_func_globals = w_globals  # the globals dictionary
         self.closure   = closure    # normally, list of Cell instances or None
-        self.defs = Defaults(defs_w, promote=promote_defs)
-        # wrapper around list of w_default's
+        self.defs_w = defs_w
         self.w_func_dict = None # filled out below if needed
         self.w_module = None
 
@@ -157,7 +138,7 @@
             return self._flat_pycall(code, nargs, frame)
         elif fast_natural_arity & Code.FLATPYCALL:
             natural_arity = fast_natural_arity & 0xff
-            if natural_arity > nargs >= natural_arity - self.defs.getlen():
+            if natural_arity > nargs >= natural_arity - len(self.defs_w):
                 assert isinstance(code, PyCode)
                 return self._flat_pycall_defaults(code, nargs, frame,
                                                   natural_arity - nargs)
@@ -190,12 +171,11 @@
             w_arg = frame.peekvalue(nargs-1-i)
             new_frame.fastlocals_w[i] = w_arg
 
-        defs = self.defs
-        ndefs = defs.getlen()
+        ndefs = len(self.defs_w)
         start = ndefs - defs_to_load
         i = nargs
         for j in xrange(start, ndefs):
-            new_frame.fastlocals_w[i] = defs.getitem(j)
+            new_frame.fastlocals_w[i] = self.defs_w[j]
             i += 1
         return new_frame.run()
 
@@ -311,7 +291,7 @@
             w(self.code),
             w_func_globals,
             w_closure,
-            nt(self.defs.getitems()),
+            nt(self.defs_w),
             w_func_dict,
             self.w_module,
         ]
@@ -346,11 +326,11 @@
         if space.is_w(w_func_dict, space.w_None):
             w_func_dict = None
         self.w_func_dict = w_func_dict
-        self.defs = Defaults(space.fixedview(w_defs))
+        self.defs_w = space.fixedview(w_defs)
         self.w_module = w_module
 
     def fget_func_defaults(self, space):
-        values_w = self.defs.getitems()
+        values_w = self.defs_w
         # the `None in values_w` check here is to ensure that interp-level
         # functions with a default of NoneNotWrapped do not get their defaults
         # exposed at applevel
@@ -360,14 +340,14 @@
 
     def fset_func_defaults(self, space, w_defaults):
         if space.is_w(w_defaults, space.w_None):
-            self.defs = Defaults([])
+            self.defs_w = []
             return
         if not space.is_true(space.isinstance(w_defaults, space.w_tuple)):
             raise OperationError( space.w_TypeError, space.wrap("func_defaults must be set to a tuple object or None") )
-        self.defs = Defaults(space.fixedview(w_defaults))
+        self.defs_w = space.fixedview(w_defaults)
 
     def fdel_func_defaults(self, space):
-        self.defs = Defaults([])
+        self.defs_w = []
 
     def fget_func_doc(self, space):
         if self.w_doc is None:
@@ -448,6 +428,7 @@
 
 class Method(Wrappable):
     """A method is a function bound to a specific instance or class."""
+    _immutable_fields_ = ['w_function', 'w_instance', 'w_class']
 
     def __init__(self, space, w_function, w_instance, w_class):
         self.space = space
@@ -631,8 +612,7 @@
     def __init__(self, func):
         assert isinstance(func, Function)
         Function.__init__(self, func.space, func.code, func.w_func_globals,
-                          func.defs.getitems(), func.closure, func.name,
-                          promote_defs=True)
+                          func.defs_w, func.closure, func.name)
         self.w_doc = func.w_doc
         self.w_func_dict = func.w_func_dict
         self.w_module = func.w_module
diff --git a/pypy/interpreter/gateway.py b/pypy/interpreter/gateway.py
--- a/pypy/interpreter/gateway.py
+++ b/pypy/interpreter/gateway.py
@@ -594,7 +594,7 @@
         space = func.space
         activation = self.activation
         scope_w = args.parse_obj(w_obj, func.name, self.sig,
-                                 func.defs, self.minargs)
+                                 func.defs_w, self.minargs)
         try:
             w_result = activation._run(space, scope_w)
         except DescrMismatch:
diff --git a/pypy/interpreter/generator.py b/pypy/interpreter/generator.py
--- a/pypy/interpreter/generator.py
+++ b/pypy/interpreter/generator.py
@@ -7,6 +7,7 @@
 
 class GeneratorIterator(Wrappable):
     "An iterator created by a generator."
+    _immutable_fields_ = ['pycode']
     
     def __init__(self, frame):
         self.space = frame.space
diff --git a/pypy/interpreter/pycode.py b/pypy/interpreter/pycode.py
--- a/pypy/interpreter/pycode.py
+++ b/pypy/interpreter/pycode.py
@@ -204,7 +204,7 @@
                                       fresh_virtualizable=True)
         args_matched = args.parse_into_scope(None, fresh_frame.fastlocals_w,
                                              func.name,
-                                             sig, func.defs)
+                                             sig, func.defs_w)
         fresh_frame.init_cells()
         return frame.run()
 
@@ -217,7 +217,7 @@
                                       fresh_virtualizable=True)
         args_matched = args.parse_into_scope(w_obj, fresh_frame.fastlocals_w,
                                              func.name,
-                                             sig, func.defs)
+                                             sig, func.defs_w)
         fresh_frame.init_cells()
         return frame.run()
 
diff --git a/pypy/interpreter/pyopcode.py b/pypy/interpreter/pyopcode.py
--- a/pypy/interpreter/pyopcode.py
+++ b/pypy/interpreter/pyopcode.py
@@ -1197,6 +1197,7 @@
                 WHY_CONTINUE,   SContinueLoop
                 WHY_YIELD       not needed
     """
+    _immutable_ = True
     def nomoreblocks(self):
         raise BytecodeCorruption("misplaced bytecode - should not return")
 
@@ -1207,6 +1208,7 @@
 class SReturnValue(SuspendedUnroller):
     """Signals a 'return' statement.
     Argument is the wrapped object to return."""
+    _immutable_ = True
     kind = 0x01
     def __init__(self, w_returnvalue):
         self.w_returnvalue = w_returnvalue
@@ -1222,6 +1224,7 @@
 class SApplicationException(SuspendedUnroller):
     """Signals an application-level exception
     (i.e. an OperationException)."""
+    _immutable_ = True
     kind = 0x02
     def __init__(self, operr):
         self.operr = operr
@@ -1236,6 +1239,7 @@
 
 class SBreakLoop(SuspendedUnroller):
     """Signals a 'break' statement."""
+    _immutable_ = True
     kind = 0x04
 
     def state_unpack_variables(self, space):
@@ -1249,6 +1253,7 @@
 class SContinueLoop(SuspendedUnroller):
     """Signals a 'continue' statement.
     Argument is the bytecode position of the beginning of the loop."""
+    _immutable_ = True
     kind = 0x08
     def __init__(self, jump_to):
         self.jump_to = jump_to
@@ -1261,10 +1266,11 @@
 
 
 class FrameBlock(object):
-
     """Abstract base class for frame blocks from the blockstack,
     used by the SETUP_XXX and POP_BLOCK opcodes."""
 
+    _immutable_ = True
+
     def __init__(self, frame, handlerposition):
         self.handlerposition = handlerposition
         self.valuestackdepth = frame.valuestackdepth
@@ -1306,6 +1312,7 @@
 class LoopBlock(FrameBlock):
     """A loop block.  Stores the end-of-loop pointer in case of 'break'."""
 
+    _immutable_ = True
     _opname = 'SETUP_LOOP'
     handling_mask = SBreakLoop.kind | SContinueLoop.kind
 
@@ -1325,6 +1332,7 @@
 class ExceptBlock(FrameBlock):
     """An try:except: block.  Stores the position of the exception handler."""
 
+    _immutable_ = True
     _opname = 'SETUP_EXCEPT'
     handling_mask = SApplicationException.kind
 
@@ -1349,6 +1357,7 @@
 class FinallyBlock(FrameBlock):
     """A try:finally: block.  Stores the position of the exception handler."""
 
+    _immutable_ = True
     _opname = 'SETUP_FINALLY'
     handling_mask = -1     # handles every kind of SuspendedUnroller
 
@@ -1376,6 +1385,8 @@
 
 class WithBlock(FinallyBlock):
 
+    _immutable_ = True
+
     def really_handle(self, frame, unroller):
         if (frame.space.full_exceptions and
             isinstance(unroller, SApplicationException)):
diff --git a/pypy/interpreter/test/test_argument.py b/pypy/interpreter/test/test_argument.py
--- a/pypy/interpreter/test/test_argument.py
+++ b/pypy/interpreter/test/test_argument.py
@@ -3,7 +3,6 @@
     ArgErr, ArgErrUnknownKwds, ArgErrMultipleValues, ArgErrCount, rawshape,
     Signature)
 from pypy.interpreter.error import OperationError
-from pypy.interpreter.function import Defaults
 
 
 class TestSignature(object):
@@ -184,7 +183,7 @@
         py.test.raises(ArgErr, args._match_signature, None, l, Signature(["a"], "*"))
         args = Arguments(space, [])
         l = [None]
-        args._match_signature(None, l, Signature(["a"]), defaults=Defaults([1]))
+        args._match_signature(None, l, Signature(["a"]), defaults_w=[1])
         assert l == [1]
         args = Arguments(space, [])
         l = [None]
@@ -232,7 +231,7 @@
                 py.test.raises(ArgErr, args._match_signature, firstarg, l, Signature(["a", "b", "c", "d", "e"], "*"))
                 l = [None, None, None, None, None]
                 args = Arguments(space, arglist, w_stararg=starargs)
-                args._match_signature(firstarg, l, Signature(["a", "b", "c", "d", "e"]), defaults=Defaults([1]))
+                args._match_signature(firstarg, l, Signature(["a", "b", "c", "d", "e"]), defaults_w=[1])
                 assert l == [4, 5, 6, 7, 1]
                 for j in range(len(values)):
                     l = [None] * (j + 1)
@@ -257,24 +256,24 @@
             assert len(keywords) == len(keywords_w)
             args = Arguments(space, [1, 2], keywords[:], keywords_w[:], w_starstararg=w_kwds)
             l = [None, None, None]
-            args._match_signature(None, l, Signature(["a", "b", "c"]), defaults=Defaults([4]))
+            args._match_signature(None, l, Signature(["a", "b", "c"]), defaults_w=[4])
             assert l == [1, 2, 3]
             args = Arguments(space, [1, 2], keywords[:], keywords_w[:], w_starstararg=w_kwds)
             l = [None, None, None, None]
-            args._match_signature(None, l, Signature(["a", "b", "b1", "c"]), defaults=Defaults([4, 5]))
+            args._match_signature(None, l, Signature(["a", "b", "b1", "c"]), defaults_w=[4, 5])
             assert l == [1, 2, 4, 3]
             args = Arguments(space, [1, 2], keywords[:], keywords_w[:], w_starstararg=w_kwds)
             l = [None, None, None, None]
-            args._match_signature(None, l, Signature(["a", "b", "c", "d"]), defaults=Defaults([4, 5]))
+            args._match_signature(None, l, Signature(["a", "b", "c", "d"]), defaults_w=[4, 5])
             assert l == [1, 2, 3, 5]
             args = Arguments(space, [1, 2], keywords[:], keywords_w[:], w_starstararg=w_kwds)
             l = [None, None, None, None]
             py.test.raises(ArgErr, args._match_signature, None, l,
-                           Signature(["c", "b", "a", "d"]), defaults=Defaults([4, 5]))
+                           Signature(["c", "b", "a", "d"]), defaults_w=[4, 5])
             args = Arguments(space, [1, 2], keywords[:], keywords_w[:], w_starstararg=w_kwds)
             l = [None, None, None, None]
             py.test.raises(ArgErr, args._match_signature, None, l,
-                           Signature(["a", "b", "c1", "d"]), defaults=Defaults([4, 5]))
+                           Signature(["a", "b", "c1", "d"]), defaults_w=[4, 5])
             args = Arguments(space, [1, 2], keywords[:], keywords_w[:], w_starstararg=w_kwds)
             l = [None, None, None]
             args._match_signature(None, l, Signature(["a", "b"], None, "**"))
@@ -355,10 +354,10 @@
         calls = []
 
         def _match_signature(w_firstarg, scope_w, signature,
-                             defaults=None, blindargs=0):
-            defaults = [] if defaults is None else defaults.getitems()
+                             defaults_w=None, blindargs=0):
+            defaults_w = [] if defaults_w is None else defaults_w
             calls.append((w_firstarg, scope_w, signature.argnames, signature.has_vararg(),
-                          signature.has_kwarg(), defaults, blindargs))
+                          signature.has_kwarg(), defaults_w, blindargs))
         args._match_signature = _match_signature
 
         scope_w = args.parse_obj(None, "foo", Signature(["a", "b"], None, None))
@@ -376,7 +375,7 @@
         calls = []
 
         scope_w = args.parse_obj(None, "foo", Signature(["a", "b"], "args", "kw"),
-                             defaults=Defaults(['x', 'y']))
+                             defaults_w=['x', 'y'])
         assert len(calls) == 1
         assert calls[0] == (None, [None, None, None, None], ["a", "b"],
                             True, True,
@@ -384,7 +383,7 @@
         calls = []
 
         scope_w = args.parse_obj("obj", "foo", Signature(["a", "b"], "args", "kw"),
-                             defaults=Defaults(['x', 'y']), blindargs=1)
+                             defaults_w=['x', 'y'], blindargs=1)
         assert len(calls) == 1
         assert calls[0] == ("obj", [None, None, None, None], ["a", "b"],
                             True, True,
@@ -413,10 +412,10 @@
         calls = []
 
         def _match_signature(w_firstarg, scope_w, signature,
-                             defaults=None, blindargs=0):
-            defaults = [] if defaults is None else defaults.getitems()
+                             defaults_w=None, blindargs=0):
+            defaults_w = [] if defaults_w is None else defaults_w
             calls.append((w_firstarg, scope_w, signature.argnames, signature.has_vararg(),
-                          signature.has_kwarg(), defaults, blindargs))
+                          signature.has_kwarg(), defaults_w, blindargs))
         args._match_signature = _match_signature
 
         scope_w = [None, None]
@@ -429,7 +428,7 @@
 
         scope_w = [None, None, None, None]
         args.parse_into_scope(None, scope_w, "foo", Signature(["a", "b"], "args", "kw"),
-                              defaults=Defaults(['x', 'y']))
+                              defaults_w=['x', 'y'])
         assert len(calls) == 1
         assert calls[0] == (None, scope_w, ["a", "b"],
                             True, True,
@@ -439,7 +438,7 @@
         scope_w = [None, None, None, None]
         args.parse_into_scope("obj", scope_w, "foo", Signature(["a", "b"],
                                                       "args", "kw"),
-                              defaults=Defaults(['x', 'y']))
+                              defaults_w=['x', 'y'])
         assert len(calls) == 1
         assert calls[0] == ("obj", scope_w, ["a", "b"],
                             True, True,
@@ -511,25 +510,25 @@
     def test_missing_args(self):
         # got_nargs, nkwds, expected_nargs, has_vararg, has_kwarg,
         # defaults_w, missing_args
-        err = ArgErrCount(1, 0, 0, False, False, Defaults([]), 0)
+        err = ArgErrCount(1, 0, 0, False, False, None, 0)
         s = err.getmsg('foo')
         assert s == "foo() takes no argument (1 given)"
-        err = ArgErrCount(0, 0, 1, False, False, Defaults([]), 1)
+        err = ArgErrCount(0, 0, 1, False, False, [], 1)
         s = err.getmsg('foo')
         assert s == "foo() takes exactly 1 argument (0 given)"
-        err = ArgErrCount(3, 0, 2, False, False, Defaults([]), 0)
+        err = ArgErrCount(3, 0, 2, False, False, [], 0)
         s = err.getmsg('foo')
         assert s == "foo() takes exactly 2 arguments (3 given)"
-        err = ArgErrCount(1, 0, 2, True, False, Defaults([]), 1)
+        err = ArgErrCount(1, 0, 2, True, False, [], 1)
         s = err.getmsg('foo')
         assert s == "foo() takes at least 2 arguments (1 given)"
-        err = ArgErrCount(3, 0, 2, True, False, Defaults(['a']), 0)
+        err = ArgErrCount(3, 0, 2, True, False, ['a'], 0)
         s = err.getmsg('foo')
         assert s == "foo() takes at most 2 arguments (3 given)"
-        err = ArgErrCount(0, 1, 2, True, False, Defaults(['a']), 1)
+        err = ArgErrCount(0, 1, 2, True, False, ['a'], 1)
         s = err.getmsg('foo')
         assert s == "foo() takes at least 1 argument (1 given)"
-        err = ArgErrCount(2, 1, 1, False, True, Defaults([]), 0)
+        err = ArgErrCount(2, 1, 1, False, True, [], 0)
         s = err.getmsg('foo')
         assert s == "foo() takes exactly 1 argument (3 given)"
 
@@ -603,7 +602,7 @@
 
         args = make_arguments_for_translation(space, [1])
         sig = Signature(['a', 'b', 'c'], None, None)
-        data = args.match_signature(sig, Defaults([2, 3]))
+        data = args.match_signature(sig, [2, 3])
         new_args = args.unmatch_signature(sig, data)
         assert args.unpack() == new_args.unpack()
 
@@ -615,25 +614,25 @@
 
         args = make_arguments_for_translation(space, [1], {'c': 3, 'b': 2})
         sig = Signature(['a', 'b', 'c'], None, None)
-        data = args.match_signature(sig, Defaults([]))
+        data = args.match_signature(sig, [])
         new_args = args.unmatch_signature(sig, data)
         assert args.unpack() == new_args.unpack()
 
         args = make_arguments_for_translation(space, [1], {'c': 5})
         sig = Signature(['a', 'b', 'c'], None, None)
-        data = args.match_signature(sig, Defaults([2, 3]))
+        data = args.match_signature(sig, [2, 3])
         new_args = args.unmatch_signature(sig, data)
         assert args.unpack() == new_args.unpack()
 
         args = make_arguments_for_translation(space, [1], {'c': 5, 'd': 7})
         sig = Signature(['a', 'b', 'c'], None, 'kw')
-        data = args.match_signature(sig, Defaults([2, 3]))
+        data = args.match_signature(sig, [2, 3])
         new_args = args.unmatch_signature(sig, data)
         assert args.unpack() == new_args.unpack()
 
         args = make_arguments_for_translation(space, [1,2,3,4,5], {'e': 5, 'd': 7})
         sig = Signature(['a', 'b', 'c'], 'r', 'kw')
-        data = args.match_signature(sig, Defaults([2, 3]))
+        data = args.match_signature(sig, [2, 3])
         new_args = args.unmatch_signature(sig, data)
         assert args.unpack() == new_args.unpack()
 
@@ -641,7 +640,7 @@
                                        w_stararg=[1],
                                        w_starstararg={'c': 5, 'd': 7})
         sig = Signature(['a', 'b', 'c'], None, 'kw')
-        data = args.match_signature(sig, Defaults([2, 3]))
+        data = args.match_signature(sig, [2, 3])
         new_args = args.unmatch_signature(sig, data)
         assert args.unpack() == new_args.unpack()
 
@@ -649,7 +648,7 @@
                                        w_stararg=[3,4,5],
                                        w_starstararg={'e': 5, 'd': 7})
         sig = Signature(['a', 'b', 'c'], 'r', 'kw')
-        data = args.match_signature(sig, Defaults([2, 3]))
+        data = args.match_signature(sig, [2, 3])
         new_args = args.unmatch_signature(sig, data)
         assert args.unpack() == new_args.unpack()
 
diff --git a/pypy/interpreter/test/test_gateway.py b/pypy/interpreter/test/test_gateway.py
--- a/pypy/interpreter/test/test_gateway.py
+++ b/pypy/interpreter/test/test_gateway.py
@@ -4,7 +4,6 @@
 from pypy.conftest import gettestobjspace
 from pypy.interpreter import gateway, argument
 from pypy.interpreter.gateway import ObjSpace, W_Root
-from pypy.interpreter.function import Defaults
 import py
 import sys
 
@@ -12,7 +11,7 @@
     def __init__(self, space, name):
         self.space = space
         self.name = name
-        self.defs = Defaults([])
+        self.defs_w = []
 
 class TestBuiltinCode:
     def test_signature(self):
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
@@ -168,6 +168,7 @@
 
 class CompiledLoop(object):
     has_been_freed = False
+    invalid = False
 
     def __init__(self):
         self.inputargs = []
@@ -944,6 +945,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/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/backend/model.py b/pypy/jit/backend/model.py
--- a/pypy/jit/backend/model.py
+++ b/pypy/jit/backend/model.py
@@ -141,6 +141,16 @@
         oldlooptoken so that from now own they will call newlooptoken."""
         raise NotImplementedError
 
+    def invalidate_loop(self, looptoken):
+        """Activate all GUARD_NOT_INVALIDATED in the loop and its attached
+        bridges.  Before this call, all GUARD_NOT_INVALIDATED do nothing;
+        after this call, they all fail.  Note that afterwards, if one such
+        guard fails often enough, it has a bridge attached to it; it is
+        possible then to re-call invalidate_loop() on the same looptoken,
+        which must invalidate all newer GUARD_NOT_INVALIDATED, but not the
+        old one that already has a bridge attached to it."""
+        raise NotImplementedError
+
     def free_loop_and_bridges(self, compiled_loop_token):
         """This method is called to free resources (machine code,
         references to resume guards, etc.) allocated by the compilation
@@ -296,6 +306,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/backend/test/runner_test.py b/pypy/jit/backend/test/runner_test.py
--- a/pypy/jit/backend/test/runner_test.py
+++ b/pypy/jit/backend/test/runner_test.py
@@ -1870,6 +1870,66 @@
         assert self.cpu.get_latest_value_int(2) == 10
         assert values == [1, 10]
 
+    def test_guard_not_invalidated(self):
+        cpu = self.cpu
+        i0 = BoxInt()
+        i1 = BoxInt()
+        faildescr = BasicFailDescr(1)
+        ops = [
+            ResOperation(rop.GUARD_NOT_INVALIDATED, [], None, descr=faildescr),
+            ResOperation(rop.FINISH, [i0], None, descr=BasicFailDescr(0))
+        ]
+        ops[0].setfailargs([i1])
+        looptoken = LoopToken()
+        self.cpu.compile_loop([i0, i1], ops, looptoken)
+
+        self.cpu.set_future_value_int(0, -42)
+        self.cpu.set_future_value_int(1, 9)
+        fail = self.cpu.execute_token(looptoken)
+        assert fail.identifier == 0
+        assert self.cpu.get_latest_value_int(0) == -42
+        print 'step 1 ok'
+        print '-'*79
+
+        # mark as failing
+        self.cpu.invalidate_loop(looptoken)
+
+        self.cpu.set_future_value_int(0, -42)
+        self.cpu.set_future_value_int(1, 9)
+        fail = self.cpu.execute_token(looptoken)
+        assert fail is faildescr
+        assert self.cpu.get_latest_value_int(0) == 9
+        print 'step 2 ok'
+        print '-'*79
+
+        # attach a bridge
+        i2 = BoxInt()
+        faildescr2 = BasicFailDescr(2)
+        ops = [
+            ResOperation(rop.GUARD_NOT_INVALIDATED, [],None, descr=faildescr2),
+            ResOperation(rop.FINISH, [i2], None, descr=BasicFailDescr(3))
+        ]
+        ops[0].setfailargs([])
+        self.cpu.compile_bridge(faildescr, [i2], ops, looptoken)
+
+        self.cpu.set_future_value_int(0, -42)
+        self.cpu.set_future_value_int(1, 9)
+        fail = self.cpu.execute_token(looptoken)
+        assert fail.identifier == 3
+        assert self.cpu.get_latest_value_int(0) == 9
+        print 'step 3 ok'
+        print '-'*79
+
+        # mark as failing again
+        self.cpu.invalidate_loop(looptoken)
+
+        self.cpu.set_future_value_int(0, -42)
+        self.cpu.set_future_value_int(1, 9)
+        fail = self.cpu.execute_token(looptoken)
+        assert fail is faildescr2
+        print 'step 4 ok'
+        print '-'*79
+
     # pure do_ / descr features
 
     def test_do_operations(self):
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,13 @@
 
 
 class GuardToken(object):
-    def __init__(self, faildescr, failargs, fail_locs, exc):
+    def __init__(self, faildescr, failargs, fail_locs, exc,
+                 is_guard_not_invalidated):
         self.faildescr = faildescr
         self.failargs = failargs
         self.fail_locs = fail_locs
         self.exc = exc
+        self.is_guard_not_invalidated = is_guard_not_invalidated
 
 DEBUG_COUNTER = lltype.Struct('DEBUG_COUNTER', ('i', lltype.Signed))
 
@@ -435,15 +437,36 @@
         # 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
+        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 not tok.is_guard_not_invalidated:
+                mc = codebuf.MachineCodeBlockWrapper()
+                mc.writeimm32(relative_target)
+                mc.copy_to_raw_memory(addr)
+            else:
+                # GUARD_NOT_INVALIDATED, record an entry in
+                # clt.invalidate_positions of the form:
+                #     (addr-in-the-code-of-the-not-yet-written-jump-target,
+                #      relative-target-to-use)
+                relpos = tok.pos_jump_offset
+                clt.invalidate_positions.append((rawstart + relpos,
+                                                 relative_target))
+                # General idea: Although no code was generated by this
+                # guard, the code might be patched with a "JMP rel32" to
+                # the guard recovery code.  This recovery code is
+                # already generated, and looks like the recovery code
+                # for any guard, even if at first it has no jump to it.
+                # So we may later write 5 bytes overriding the existing
+                # instructions; this works because a CALL instruction
+                # would also take at least 5 bytes.  If it could take
+                # less, we would run into the issue that overwriting the
+                # 5 bytes here might get a few nonsense bytes at the
+                # return address of the following CALL.
 
     def get_asmmemmgr_blocks(self, looptoken):
         clt = looptoken.compiled_loop_token
@@ -1471,6 +1494,12 @@
         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.pending_guard_tokens.append(guard_token)
+
     def genop_guard_guard_exception(self, ign_1, guard_op, guard_token,
                                     locs, resloc):
         loc = locs[0]
@@ -1569,7 +1598,9 @@
         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)
+        is_guard_not_invalidated = guard_opnum == rop.GUARD_NOT_INVALIDATED
+        return GuardToken(faildescr, failargs, fail_locs, exc,
+                          is_guard_not_invalidated)
 
     def generate_quick_failure(self, guardtok):
         """Generate the initial code for handling a failure.  We try to
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
@@ -455,6 +455,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/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
@@ -153,6 +153,17 @@
     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)
+            assert mc.get_relative_pos() == 5      # [JMP] [tgt 4 bytes]
+            mc.copy_to_raw_memory(addr - 1)
+        # positions invalidated
+        looptoken.compiled_loop_token.invalidate_positions = []
+
 class CPU386(AbstractX86CPU):
     WORD = 4
     NUM_REGS = 8
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/jit/codewriter/call.py b/pypy/jit/codewriter/call.py
--- a/pypy/jit/codewriter/call.py
+++ b/pypy/jit/codewriter/call.py
@@ -6,6 +6,7 @@
 from pypy.jit.codewriter import support
 from pypy.jit.codewriter.jitcode import JitCode
 from pypy.jit.codewriter.effectinfo import VirtualizableAnalyzer
+from pypy.jit.codewriter.effectinfo import QuasiImmutAnalyzer
 from pypy.jit.codewriter.effectinfo import effectinfo_from_writeanalyze
 from pypy.jit.codewriter.effectinfo import EffectInfo, CallInfoCollection
 from pypy.translator.simplify import get_funcobj, get_functype
@@ -30,6 +31,7 @@
             self.raise_analyzer = RaiseAnalyzer(translator)
             self.readwrite_analyzer = ReadWriteAnalyzer(translator)
             self.virtualizable_analyzer = VirtualizableAnalyzer(translator)
+            self.quasiimmut_analyzer = QuasiImmutAnalyzer(translator)
         #
         for index, jd in enumerate(jitdrivers_sd):
             jd.index = index
@@ -220,6 +222,8 @@
         if extraeffect is None:
             if self.virtualizable_analyzer.analyze(op):
                 extraeffect = EffectInfo.EF_FORCES_VIRTUAL_OR_VIRTUALIZABLE
+            elif self.quasiimmut_analyzer.analyze(op):
+                extraeffect = EffectInfo.EF_CAN_INVALIDATE
             elif loopinvariant:
                 extraeffect = EffectInfo.EF_LOOPINVARIANT
             elif pure:
@@ -237,6 +241,7 @@
         if pure or loopinvariant:
             assert effectinfo is not None
             assert extraeffect != EffectInfo.EF_FORCES_VIRTUAL_OR_VIRTUALIZABLE
+            assert extraeffect != EffectInfo.EF_CAN_INVALIDATE
         #
         return self.cpu.calldescrof(FUNC, tuple(NON_VOID_ARGS), RESULT,
                                     effectinfo)
diff --git a/pypy/jit/codewriter/codewriter.py b/pypy/jit/codewriter/codewriter.py
--- a/pypy/jit/codewriter/codewriter.py
+++ b/pypy/jit/codewriter/codewriter.py
@@ -13,13 +13,13 @@
 
 class CodeWriter(object):
     callcontrol = None    # for tests
+    debug = False
 
-    def __init__(self, cpu=None, jitdrivers_sd=[], debug=False):
+    def __init__(self, cpu=None, jitdrivers_sd=[]):
         self.cpu = cpu
         self.assembler = Assembler()
         self.callcontrol = CallControl(cpu, jitdrivers_sd)
         self._seen_files = set()
-        self.debug = debug
 
     def transform_func_to_jitcode(self, func, values, type_system='lltype'):
         """For testing."""
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
@@ -13,7 +13,8 @@
     EF_LOOPINVARIANT                   = 1 #special: call it only once per loop
     EF_CANNOT_RAISE                    = 2 #a function which cannot raise
     EF_CAN_RAISE                       = 3 #normal function (can raise)
-    EF_FORCES_VIRTUAL_OR_VIRTUALIZABLE = 4 #can raise and force virtualizables
+    EF_CAN_INVALIDATE                  = 4 #can force all GUARD_NOT_INVALIDATED
+    EF_FORCES_VIRTUAL_OR_VIRTUALIZABLE = 5 #can raise and force virtualizables
 
     # the 'oopspecindex' field is one of the following values:
     OS_NONE                     = 0    # normal case, no oopspec
@@ -100,6 +101,9 @@
         cls._cache[key] = result
         return result
 
+    def check_can_invalidate(self):
+        return self.extraeffect >= self.EF_CAN_INVALIDATE
+
     def check_forces_virtual_or_virtualizable(self):
         return self.extraeffect >= self.EF_FORCES_VIRTUAL_OR_VIRTUALIZABLE
 
@@ -175,6 +179,10 @@
         return op.opname in ('jit_force_virtualizable',
                              'jit_force_virtual')
 
+class QuasiImmutAnalyzer(BoolGraphAnalyzer):
+    def analyze_simple_operation(self, op, graphinfo):
+        return op.opname == 'jit_force_quasi_immutable'
+
 # ____________________________________________________________
 
 class CallInfoCollection(object):
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
@@ -9,6 +9,8 @@
 from pypy.jit.codewriter.effectinfo import EffectInfo
 from pypy.jit.codewriter.policy import log
 from pypy.jit.metainterp.typesystem import deref, arrayItem
+from pypy.jit.metainterp import quasiimmut
+from pypy.rpython.rclass import IR_QUASIIMMUTABLE, IR_QUASIIMMUTABLE_ARRAY
 from pypy.rlib import objectmodel
 from pypy.rlib.jit import _we_are_jitted
 from pypy.translator.simplify import get_funcobj
@@ -113,7 +115,7 @@
                     "known non-negative, or catching IndexError, or\n"
                     "not inlining at all (for tests: use listops=True).\n"
                     "Occurred in: %r" % self.graph)
-            # extra expanation: with the way things are organized in
+            # extra explanation: with the way things are organized in
             # rpython/rlist.py, the ll_getitem becomes a function call
             # that is typically meant to be inlined by the JIT, but
             # this does not work with vable arrays because
@@ -362,7 +364,7 @@
         # If the resulting op1 is still a direct_call, turn it into a
         # residual_call.
         if isinstance(op1, SpaceOperation) and op1.opname == 'direct_call':
-            op1 = self.handle_residual_call(op1 or op)
+            op1 = self.handle_residual_call(op1)
         return op1
 
     def handle_recursive_call(self, op):
@@ -563,7 +565,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)):
@@ -576,8 +579,18 @@
         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 in (IR_QUASIIMMUTABLE, IR_QUASIIMMUTABLE_ARRAY):
+            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):
         if self.is_typeptr_getset(op):
@@ -1370,6 +1383,15 @@
         return self._handle_oopspec_call(op, args, EffectInfo.OS_MATH_SQRT,
                                          EffectInfo.EF_PURE)
 
+    def rewrite_op_jit_force_quasi_immutable(self, op):
+        v_inst, c_fieldname = op.args
+        descr1 = self.cpu.fielddescrof(v_inst.concretetype.TO,
+                                       c_fieldname.value)
+        op0 = SpaceOperation('-live-', [], None)
+        op1 = SpaceOperation('jit_force_quasi_immutable', [v_inst, descr1],
+                             None)
+        return [op0, op1]
+
 # ____________________________________________________________
 
 class NotSupported(Exception):
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
@@ -969,3 +969,48 @@
     assert op1.args[3] == ListOfKind("ref", [])
     assert op1.args[4] == ListOfKind('float', [v1])
     assert op1.result == v2
+
+def test_quasi_immutable():
+    from pypy.rpython.rclass import FieldListAccessor, IR_QUASIIMMUTABLE
+    accessor = FieldListAccessor()
+    accessor.initialize(None, {'inst_x': IR_QUASIIMMUTABLE})
+    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_QUASIIMMUTABLE
+    accessor = FieldListAccessor()
+    accessor.initialize(None, {'inst_x': IR_QUASIIMMUTABLE})
+    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('jit_force_quasi_immutable',
+                            [v_x, Constant('mutate_x', lltype.Void)],
+                            varoftype(lltype.Void))
+        tr = Transformer(FakeCPU(), FakeRegularCallControl())
+        tr.graph = 'currentgraph'
+        op0, op1 = tr.rewrite_operation(op)
+        assert op0.opname == '-live-'
+        assert op1.opname == 'jit_force_quasi_immutable'
+        assert op1.args[0] == v_x
+        assert op1.args[1] == ('fielddescr', STRUCT, 'mutate_x')
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
@@ -1174,6 +1174,15 @@
     def bhimpl_setfield_raw_f(cpu, struct, fielddescr, newvalue):
         cpu.bh_setfield_raw_f(struct, fielddescr, newvalue)
 
+    @arguments("r", "d", "d")
+    def bhimpl_record_quasiimmut_field(struct, fielddescr, mutatefielddescr):
+        pass
+
+    @arguments("cpu", "r", "d")
+    def bhimpl_jit_force_quasi_immutable(cpu, struct, mutatefielddescr):
+        from pypy.jit.metainterp import quasiimmut
+        quasiimmut.do_force_quasi_immutable(cpu, struct, mutatefielddescr)
+
     @arguments("cpu", "d", returns="r")
     def bhimpl_new(cpu, descr):
         return cpu.bh_new(descr)
@@ -1299,6 +1308,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/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/jit/metainterp/executor.py b/pypy/jit/metainterp/executor.py
--- a/pypy/jit/metainterp/executor.py
+++ b/pypy/jit/metainterp/executor.py
@@ -322,6 +322,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/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/jit/metainterp/jitprof.py b/pypy/jit/metainterp/jitprof.py
--- a/pypy/jit/metainterp/jitprof.py
+++ b/pypy/jit/metainterp/jitprof.py
@@ -22,6 +22,7 @@
 ABORT_BRIDGE
 ABORT_ESCAPE
 ABORT_BAD_LOOP
+ABORT_FORCE_QUASIIMMUT
 NVIRTUALS
 NVHOLES
 NVREUSED
@@ -179,6 +180,8 @@
         self._print_intline("abort: compiling", cnt[ABORT_BRIDGE])
         self._print_intline("abort: vable escape", cnt[ABORT_ESCAPE])
         self._print_intline("abort: bad loop", cnt[ABORT_BAD_LOOP])
+        self._print_intline("abort: force quasi-immut",
+                                               cnt[ABORT_FORCE_QUASIIMMUT])
         self._print_intline("nvirtuals", cnt[NVIRTUALS])
         self._print_intline("nvholes", cnt[NVHOLES])
         self._print_intline("nvreused", cnt[NVREUSED])
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/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,8 @@
         self._lazy_setfields = []
         # cached array items:  {descr: CachedArrayItems}
         self.cached_arrayitems = {}
+        self._remove_guard_not_invalidated = False
+        self._seen_guard_not_invalidated = False
 
     def reconstruct_for_next_iteration(self, optimizer, valuemap):
         new = OptHeap()
@@ -238,6 +240,8 @@
                 effectinfo = None
             else:
                 effectinfo = op.getdescr().get_extra_info()
+            if effectinfo is None or effectinfo.check_can_invalidate():
+                self._seen_guard_not_invalidated = False
             if effectinfo is not None:
                 # XXX we can get the wrong complexity here, if the lists
                 # XXX stored on effectinfo are large
@@ -378,6 +382,46 @@
         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.force_lazy_setfield(self)
+        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
+        if self._seen_guard_not_invalidated:
+            return
+        self._seen_guard_not_invalidated = True
+        self.emit_operation(op)
+
     def propagate_forward(self, op):
         opnum = op.getopnum()
         for value, func in optimize_ops:
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/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,11 @@
         op = ResOperation(rop.SAME_AS, [op.getarg(0)], op.result)
         self.emit_operation(op)
 
+    def optimize_QUASIIMMUT_FIELD(self, op):
+        # xxx ideally we could also kill the following GUARD_NOT_INVALIDATED
+        #     but it's a bit hard to implement robustly if heap.py is also run
+        pass
+
     def propagate_forward(self, op):
         opnum = op.getopnum()
         for value, func in optimize_ops:
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/jit/metainterp/pyjitpl.py b/pypy/jit/metainterp/pyjitpl.py
--- a/pypy/jit/metainterp/pyjitpl.py
+++ b/pypy/jit/metainterp/pyjitpl.py
@@ -15,7 +15,7 @@
 from pypy.jit.metainterp.jitprof import EmptyProfiler
 from pypy.jit.metainterp.jitprof import GUARDS, RECORDED_OPS, ABORT_ESCAPE
 from pypy.jit.metainterp.jitprof import ABORT_TOO_LONG, ABORT_BRIDGE, \
-                                        ABORT_BAD_LOOP
+                                        ABORT_BAD_LOOP, ABORT_FORCE_QUASIIMMUT
 from pypy.jit.metainterp.jitexc import JitException, get_llexception
 from pypy.rlib.rarithmetic import intmask
 from pypy.rlib.objectmodel import specialize
@@ -555,6 +555,35 @@
     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)
+
+    @arguments("box", "descr", "orgpc")
+    def opimpl_jit_force_quasi_immutable(self, box, mutatefielddescr, orgpc):
+        # During tracing, a 'jit_force_quasi_immutable' usually turns into
+        # the operations that check that the content of 'mutate_xxx' is null.
+        # If it is actually not null already now, then we abort tracing.
+        # The idea is that if we use 'jit_force_quasi_immutable' on a freshly
+        # allocated object, then the GETFIELD_GC will know that the answer is
+        # null, and the guard will be removed.  So the fact that the field is
+        # quasi-immutable will have no effect, and instead it will work as a
+        # regular, probably virtual, structure.
+        mutatebox = self.execute_with_descr(rop.GETFIELD_GC,
+                                            mutatefielddescr, box)
+        if mutatebox.nonnull():
+            from pypy.jit.metainterp.quasiimmut import do_force_quasi_immutable
+            do_force_quasi_immutable(self.metainterp.cpu, box.getref_base(),
+                                     mutatefielddescr)
+            raise SwitchToBlackhole(ABORT_FORCE_QUASIIMMUT)
+        self.generate_guard(rop.GUARD_ISNULL, mutatebox, 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]
@@ -1080,6 +1109,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,
@@ -1852,6 +1883,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/quasiimmut.py b/pypy/jit/metainterp/quasiimmut.py
new file mode 100644
--- /dev/null
+++ b/pypy/jit/metainterp/quasiimmut.py
@@ -0,0 +1,121 @@
+import weakref
+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 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.
+    """
+    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
+
+def do_force_quasi_immutable(cpu, p, mutatefielddescr):
+    qmut_ref = cpu.bh_getfield_gc_r(p, mutatefielddescr)
+    if qmut_ref:
+        cpu.bh_setfield_gc_r(p, mutatefielddescr, cpu.ts.NULLREF)
+        qmut_ptr = lltype.cast_opaque_ptr(rclass.OBJECTPTR, qmut_ref)
+        qmut = cast_base_ptr_to_instance(QuasiImmut, qmut_ptr)
+        qmut.invalidate()
+
+
+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: all GUARD_NOT_INVALIDATED in these loops (and
+        # in attached bridges) must now fail.
+        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):
+    structbox = None
+
+    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):
+        assert self.structbox is not None
+        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/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 -----
@@ -476,6 +477,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/jit/metainterp/test/support.py b/pypy/jit/metainterp/test/support.py
--- a/pypy/jit/metainterp/test/support.py
+++ b/pypy/jit/metainterp/test/support.py
@@ -8,12 +8,12 @@
 from pypy.jit.metainterp import pyjitpl, history
 from pypy.jit.metainterp.warmstate import set_future_value
 from pypy.jit.codewriter.policy import JitPolicy
-from pypy.jit.codewriter import longlong
-from pypy.rlib.rfloat import isinf, isnan
+from pypy.jit.codewriter import codewriter, longlong
+from pypy.rlib.rfloat import isnan
 
 def _get_jitcodes(testself, CPUClass, func, values, type_system,
                   supports_longlong=False, **kwds):
-    from pypy.jit.codewriter import support, codewriter
+    from pypy.jit.codewriter import support
 
     class FakeJitCell:
         __compiled_merge_points = []
@@ -50,6 +50,7 @@
     stats = history.Stats()
     cpu = CPUClass(rtyper, stats, None, False)
     cw = codewriter.CodeWriter(cpu, [FakeJitDriverSD()])
+    cw.debug = True
     testself.cw = cw
     policy = JitPolicy()
     policy.set_supports_floats(True)
@@ -173,7 +174,12 @@
         kwds['type_system'] = self.type_system
         if "backendopt" not in kwds:
             kwds["backendopt"] = False
-        return ll_meta_interp(*args, **kwds)
+        old = codewriter.CodeWriter.debug
+        try:
+            codewriter.CodeWriter.debug = True
+            return ll_meta_interp(*args, **kwds)
+        finally:
+            codewriter.CodeWriter.debug = old
 
     def interp_operations(self, f, args, **kwds):
         # get the JitCodes for the function f
diff --git a/pypy/jit/metainterp/test/test_jitprof.py b/pypy/jit/metainterp/test/test_jitprof.py
--- a/pypy/jit/metainterp/test/test_jitprof.py
+++ b/pypy/jit/metainterp/test/test_jitprof.py
@@ -65,7 +65,7 @@
             ]
         assert profiler.events == expected
         assert profiler.times == [3, 2, 1, 1]
-        assert profiler.counters == [1, 2, 1, 1, 3, 3, 1, 13, 2, 0, 0, 0,
+        assert profiler.counters == [1, 2, 1, 1, 3, 3, 1, 13, 2, 0, 0, 0, 0,
                                      0, 0, 0, 0, 0]
 
     def test_simple_loop_with_call(self):
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
@@ -5718,34 +5718,121 @@
         # not obvious, because of the exception UnicodeDecodeError that
         # can be raised by ll_str2unicode()
 
-
-
-
-##class TestOOtype(OptimizeOptTest, OOtypeMixin):
-
-##    def test_instanceof(self):
-##        ops = """
-##        [i0]
-##        p0 = new_with_vtable(ConstClass(node_vtable))
-##        i1 = instanceof(p0, descr=nodesize)
-##        jump(i1)
-##        """
-##        expected = """
-##        [i0]
-##        jump(1)
-##        """
-##        self.optimize_loop(ops, expected)
-
-##    def test_instanceof_guard_class(self):
-##        ops = """
-##        [i0, p0]
-##        guard_class(p0, ConstClass(node_vtable)) []
-##        i1 = instanceof(p0, descr=nodesize)
-##        jump(i1, p0)
-##        """
-##        expected = """
-##        [i0, p0]
-##        guard_class(p0, ConstClass(node_vtable)) []
-##        jump(1, p0)
-##        """
-##        self.optimize_loop(ops, expected)
+    def test_quasi_immut(self):
+        ops = """
+        [p0, p1, i0]
+        quasiimmut_field(p0, descr=quasiimmutdescr)
+        guard_not_invalidated() []
+        i1 = getfield_gc(p0, descr=quasifielddescr)
+        escape(i1)
+        jump(p1, p0, i1)
+        """
+        expected = """
+        [p0, p1, i0]
+        i1 = getfield_gc(p0, descr=quasifielddescr)
+        escape(i1)
+        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)
+        escape(i1)
+        jump()
+        """
+        expected = """
+        []
+        guard_not_invalidated() []        
+        escape(-4247)
+        jump()
+        """
+        self.optimize_loop(ops, expected, expected)
+
+    def test_remove_extra_guards_not_invalidated(self):
+        ops = """
+        [i0]
+        guard_not_invalidated() []
+        guard_not_invalidated() []
+        i1 = int_add(i0, 1)
+        guard_not_invalidated() []
+        guard_not_invalidated() []
+        jump(i1)
+        """
+        expected = """
+        [i0]
+        guard_not_invalidated() []
+        i1 = int_add(i0, 1)
+        jump(i1)
+        """
+        self.optimize_loop(ops, expected)
+
+    def test_call_may_force_invalidated_guards(self):
+        ops = """
+        [i0]
+        guard_not_invalidated() []
+        call_may_force(i0, descr=mayforcevirtdescr)
+        guard_not_invalidated() []
+        jump(i0)
+        """
+        expected = """
+        [i0]
+        guard_not_invalidated() []
+        call_may_force(i0, descr=mayforcevirtdescr)
+        guard_not_invalidated() []
+        jump(i0)
+        """
+        self.optimize_loop(ops, expected)
+
+    def test_call_may_force_invalidated_guards_reload(self):
+        ops = """
+        [i0a, i0b]
+        quasiimmut_field(ConstPtr(myptr), descr=quasiimmutdescr)
+        guard_not_invalidated() []
+        i1 = getfield_gc(ConstPtr(myptr), descr=quasifielddescr)
+        call_may_force(i0b, descr=mayforcevirtdescr)
+        quasiimmut_field(ConstPtr(myptr), descr=quasiimmutdescr)
+        guard_not_invalidated() []
+        i2 = getfield_gc(ConstPtr(myptr), descr=quasifielddescr)
+        i3 = escape(i1)
+        i4 = escape(i2)
+        jump(i3, i4)
+        """
+        expected = """
+        [i0a, i0b]
+        guard_not_invalidated() []
+        call_may_force(i0b, descr=mayforcevirtdescr)
+        guard_not_invalidated() []
+        i3 = escape(-4247)
+        i4 = escape(-4247)
+        jump(i3, i4)
+        """
+        self.optimize_loop(ops, expected)
+
+    def test_call_may_force_invalidated_guards_virtual(self):
+        ops = """
+        [i0a, i0b]
+        p = new(descr=quasisize)
+        setfield_gc(p, 421, descr=quasifielddescr)
+        quasiimmut_field(p, descr=quasiimmutdescr)
+        guard_not_invalidated() []
+        i1 = getfield_gc(p, descr=quasifielddescr)
+        call_may_force(i0b, descr=mayforcevirtdescr)
+        quasiimmut_field(p, descr=quasiimmutdescr)
+        guard_not_invalidated() []
+        i2 = getfield_gc(p, descr=quasifielddescr)
+        i3 = escape(i1)
+        i4 = escape(i2)
+        jump(i3, i4)
+        """
+        expected = """
+        [i0a, i0b]
+        call_may_force(i0b, descr=mayforcevirtdescr)
+        i3 = escape(421)
+        i4 = escape(421)
+        jump(i3, i4)
+        """
+        self.optimize_loop(ops, expected)
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_QUASIIMMUTABLE
 
 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,20 @@
     nextdescr = cpu.fielddescrof(NODE, 'next')
     otherdescr = cpu.fielddescrof(NODE2, 'other')
 
+    accessor = FieldListAccessor()
+    accessor.initialize(None, {'inst_field': IR_QUASIIMMUTABLE})
+    QUASI = lltype.GcStruct('QUASIIMMUT', ('inst_field', lltype.Signed),
+                            ('mutate_field', rclass.OBJECTPTR),
+                            hints={'immutable_fields': accessor})
+    quasisize = cpu.sizeof(QUASI)
+    quasi = lltype.malloc(QUASI, immortal=True)
+    quasi.inst_field = -4247
+    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/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,462 @@
+
+import py
+
+from pypy.rpython.lltypesystem import lltype, llmemory, rclass
+from pypy.rpython.rclass import FieldListAccessor, IR_QUASIIMMUTABLE
+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.support 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_QUASIIMMUTABLE})
+    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(guard_not_invalidated=2, 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(guard_not_invalidated=0, 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_opt_via_virtual_1(self):
+        myjitdriver = JitDriver(greens=['foo'], reds=['x', 'total'])
+        class Foo:
+            _immutable_fields_ = ['a?']
+            def __init__(self, a):
+                self.a = a
+        class A:
+            pass
+        def f(a, x):
+            foo = Foo(a)
+            total = 0
+            while x > 0:
+                myjitdriver.jit_merge_point(foo=foo, x=x, total=total)
+                # make it a Constant after optimization only
+                a = A()
+                a.foo = foo
+                foo = a.foo
+                # read a quasi-immutable field out of it
+                total += foo.a
+                x -= 1
+            return total
+        #
+        res = self.meta_interp(f, [100, 7])
+        assert res == 700
+        self.check_loops(guard_not_invalidated=2, getfield_gc=0,
+                         everywhere=True)
+
+    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(guard_not_invalidated=0, 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(guard_not_invalidated=0, 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(guard_not_invalidated=2, 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):
+        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)
+            foo.a = 1
+            total += f(foo)
+            return total
+
+        res = self.meta_interp(main, [])
+        self.check_loop_count(7)
+        assert res == main()
+
+    def test_change_during_running(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, x):
+            if x == 5:
+                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)
+                total += foo.a
+                x -= 1
+            return total
+        #
+        assert f(100, 15) == 3009
+        res = self.meta_interp(f, [100, 15])
+        assert res == 3009
+        self.check_loops(guard_not_invalidated=2, getfield_gc=0,
+                         call_may_force=0, guard_not_forced=0)
+
+    def test_list_simple_1(self):
+        myjitdriver = JitDriver(greens=['foo'], reds=['x', 'total'])
+        class Foo:
+            _immutable_fields_ = ['lst?[*]']
+            def __init__(self, lst):
+                self.lst = lst
+        def f(a, x):
+            lst1 = [0, 0]
+            lst1[1] = a
+            foo = Foo(lst1)
+            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.lst[1]
+                x -= 1
+            return total
+        #
+        res = self.meta_interp(f, [100, 7])
+        assert res == 700
+        self.check_loops(guard_not_invalidated=2, getfield_gc=0,
+                         getarrayitem_gc=0, getarrayitem_gc_pure=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_list_length_1(self):
+        myjitdriver = JitDriver(greens=['foo'], reds=['x', 'total'])
+        class Foo:
+            _immutable_fields_ = ['lst?[*]']
+            def __init__(self, lst):
+                self.lst = lst
+        class A:
+            pass
+        def f(a, x):
+            lst1 = [0, 0]
+            lst1[1] = a
+            foo = Foo(lst1)
+            total = 0
+            while x > 0:
+                myjitdriver.jit_merge_point(foo=foo, x=x, total=total)
+                # make it a Constant after optimization only
+                a = A()
+                a.foo = foo
+                foo = a.foo
+                # read a quasi-immutable field out of it
+                total += foo.lst[1]
+                # also read the length
+                total += len(foo.lst)
+                x -= 1
+            return total
+        #
+        res = self.meta_interp(f, [100, 7])
+        assert res == 714
+        self.check_loops(guard_not_invalidated=2, getfield_gc=0,
+                         getarrayitem_gc=0, getarrayitem_gc_pure=0,
+                         arraylen_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_list_pass_around(self):
+        py.test.skip("think about a way to fix it")
+        myjitdriver = JitDriver(greens=['foo'], reds=['x', 'total'])
+        class Foo:
+            _immutable_fields_ = ['lst?[*]']
+            def __init__(self, lst):
+                self.lst = lst
+        def g(lst):
+            # here, 'lst' is statically annotated as a "modified" list,
+            # so the following doesn't generate a getarrayitem_gc_pure...
+            return lst[1]
+        def f(a, x):
+            lst1 = [0, 0]
+            g(lst1)
+            lst1[1] = a
+            foo = Foo(lst1)
+            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 += g(foo.lst)
+                x -= 1
+            return total
+        #
+        res = self.meta_interp(f, [100, 7])
+        assert res == 700
+        self.check_loops(guard_not_invalidated=2, getfield_gc=0,
+                         getarrayitem_gc=0, getarrayitem_gc_pure=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_list_change_during_running(self):
+        myjitdriver = JitDriver(greens=['foo'], reds=['x', 'total'])
+        class Foo:
+            _immutable_fields_ = ['lst?[*]']
+            def __init__(self, lst):
+                self.lst = lst
+        @dont_look_inside
+        def residual_call(foo, x):
+            if x == 5:
+                lst2 = [0, 0]
+                lst2[1] = foo.lst[1] + 1
+                foo.lst = lst2
+        def f(a, x):
+            lst1 = [0, 0]
+            lst1[1] = a
+            foo = Foo(lst1)
+            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.lst[1]
+                residual_call(foo, x)
+                total += foo.lst[1]
+                x -= 1
+            return total
+        #
+        assert f(100, 15) == 3009
+        res = self.meta_interp(f, [100, 15])
+        assert res == 3009
+        self.check_loops(guard_not_invalidated=2, getfield_gc=0,
+                         getarrayitem_gc=0, getarrayitem_gc_pure=0,
+                         call_may_force=0, guard_not_forced=0)
+
+
+class TestLLtypeGreenFieldsTests(QuasiImmutTests, LLJitMixin):
+    pass
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_IMMUTABLE_ARRAY
 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_IMMUTABLE_ARRAY, 'inst_l2' : IR_IMMUTABLE_ARRAY})
 
     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/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_IMMUTABLE_ARRAY, 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_IMMUTABLE_ARRAY:
                 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/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/jit/tl/pypyjit.py b/pypy/jit/tl/pypyjit.py
--- a/pypy/jit/tl/pypyjit.py
+++ b/pypy/jit/tl/pypyjit.py
@@ -115,6 +115,8 @@
     # print a message, and restart
     unixcheckpoint.restartable_point(auto='run')
 
+    from pypy.jit.codewriter.codewriter import CodeWriter
+    CodeWriter.debug = True
     from pypy.jit.tl.pypyjit_child import run_child, run_child_ootype
     if BACKEND == 'c':
         run_child(globals(), locals())
diff --git a/pypy/jit/tl/pypyjit_demo.py b/pypy/jit/tl/pypyjit_demo.py
--- a/pypy/jit/tl/pypyjit_demo.py
+++ b/pypy/jit/tl/pypyjit_demo.py
@@ -1,18 +1,11 @@
 
 try:
-    def g(x):
-        return x - 1
     def f(x):
-        while x:
-            x = g(x)
-    import cProfile
-    import time
-    t1 = time.time()
-    cProfile.run("f(10000000)")
-    t2 = time.time()
-    f(10000000)
-    t3 = time.time()
-    print t2 - t1, t3 - t2, (t3 - t2) / (t2 - t1)
+        i = 0
+        while i < x:
+            range(i)
+            i += 1
+    f(10000)
 except Exception, e:
     print "Exception: ", type(e)
     print e
diff --git a/pypy/jit/tool/jitoutput.py b/pypy/jit/tool/jitoutput.py
--- a/pypy/jit/tool/jitoutput.py
+++ b/pypy/jit/tool/jitoutput.py
@@ -25,6 +25,7 @@
     (('abort.compiling',), '^abort: compiling:\s+(\d+)$'),
     (('abort.vable_escape',), '^abort: vable escape:\s+(\d+)$'),
     (('abort.bad_loop',), '^abort: bad loop:\s+(\d+)$'),
+    (('abort.force_quasiimmut',), '^abort: force quasi-immut:\s+(\d+)$'),
     (('nvirtuals',), '^nvirtuals:\s+(\d+)$'),
     (('nvholes',), '^nvholes:\s+(\d+)$'),
     (('nvreused',), '^nvreused:\s+(\d+)$'),
diff --git a/pypy/jit/tool/test/test_jitoutput.py b/pypy/jit/tool/test/test_jitoutput.py
--- a/pypy/jit/tool/test/test_jitoutput.py
+++ b/pypy/jit/tool/test/test_jitoutput.py
@@ -61,6 +61,7 @@
 abort: compiling:       11
 abort: vable escape:    12
 abort: bad loop:        135
+abort: force quasi-immut: 3
 nvirtuals:              13
 nvholes:                14
 nvreused:               15
@@ -89,6 +90,7 @@
     assert info.abort.compiling == 11
     assert info.abort.vable_escape == 12
     assert info.abort.bad_loop == 135
+    assert info.abort.force_quasiimmut == 3
     assert info.nvirtuals == 13
     assert info.nvholes == 14
     assert info.nvreused == 15
diff --git a/pypy/module/pypyjit/test_pypy_c/model.py b/pypy/module/pypyjit/test_pypy_c/model.py
--- a/pypy/module/pypyjit/test_pypy_c/model.py
+++ b/pypy/module/pypyjit/test_pypy_c/model.py
@@ -128,7 +128,7 @@
             if op.name != 'debug_merge_point' or include_debug_merge_points:
                 yield op
 
-    def allops(self, include_debug_merge_points=False, opcode=None):
+    def _allops(self, include_debug_merge_points=False, opcode=None):
         opcode_name = opcode
         for chunk in self.flatten_chunks():
             opcode = chunk.getopcode()                                                          
@@ -136,6 +136,9 @@
                 for op in self._ops_for_chunk(chunk, include_debug_merge_points):
                     yield op
 
+    def allops(self, *args, **kwds):
+        return list(self._allops(*args, **kwds))
+
     def format_ops(self, id=None, **kwds):
         if id is None:
             ops = self.allops(**kwds)
@@ -146,7 +149,7 @@
     def print_ops(self, *args, **kwds):
         print self.format_ops(*args, **kwds)
 
-    def ops_by_id(self, id, include_debug_merge_points=False, opcode=None):
+    def _ops_by_id(self, id, include_debug_merge_points=False, opcode=None):
         opcode_name = opcode
         target_opcodes = self.ids[id]
         for chunk in self.flatten_chunks():
@@ -156,6 +159,9 @@
                 for op in self._ops_for_chunk(chunk, include_debug_merge_points):
                     yield op
 
+    def ops_by_id(self, *args, **kwds):
+        return list(self._ops_by_id(*args, **kwds))
+
     def match(self, expected_src, **kwds):
         ops = list(self.allops())
         matcher = OpMatcher(ops, src=self.format_ops())
@@ -167,6 +173,7 @@
         return matcher.match(expected_src)
 
 class InvalidMatch(Exception):
+    opindex = None
 
     def __init__(self, message, frame):
         Exception.__init__(self, message)
@@ -326,31 +333,39 @@
         """
         iter_exp_ops = iter(expected_ops)
         iter_ops = iter(self.ops)
-        for exp_op in iter_exp_ops:
-            if exp_op == '...':
-                # loop until we find an operation which matches
-                try:
-                    exp_op = iter_exp_ops.next()
-                except StopIteration:
-                    # the ... is the last line in the expected_ops, so we just
-                    # return because it matches everything until the end
-                    return
-                op = self.match_until(exp_op, iter_ops)
-            else:
-                while True:
-                    op = self._next_op(iter_ops)
-                    if op.name not in ignore_ops:
-                        break
-            self.match_op(op, exp_op)
+        for opindex, exp_op in enumerate(iter_exp_ops):
+            try:
+                if exp_op == '...':
+                    # loop until we find an operation which matches
+                    try:
+                        exp_op = iter_exp_ops.next()
+                    except StopIteration:
+                        # the ... is the last line in the expected_ops, so we just
+                        # return because it matches everything until the end
+                        return
+                    op = self.match_until(exp_op, iter_ops)
+                else:
+                    while True:
+                        op = self._next_op(iter_ops)
+                        if op.name not in ignore_ops:
+                            break
+                self.match_op(op, exp_op)
+            except InvalidMatch, e:
+                e.opindex = opindex
+                raise
         #
         # make sure we exhausted iter_ops
         self._next_op(iter_ops, assert_raises=True)
 
     def match(self, expected_src, ignore_ops=[]):
-        def format(src):
+        def format(src, opindex=None):
             if src is None:
                 return ''
-            return py.code.Source(src).deindent().indent()
+            text = str(py.code.Source(src).deindent().indent())
+            lines = text.splitlines(True)
+            if opindex is not None and 0 <= opindex < len(lines):
+                lines[opindex] = lines[opindex].rstrip() + '\t<=====\n'
+            return ''.join(lines)
         #
         expected_src = self.preprocess_expected_src(expected_src)
         expected_ops = self.parse_ops(expected_src)
@@ -366,7 +381,7 @@
             print
             print "Ignore ops:", ignore_ops
             print "Got:"
-            print format(self.src)
+            print format(self.src, e.opindex)
             print
             print "Expected:"
             print format(expected_src)
diff --git a/pypy/module/pypyjit/test_pypy_c/test_model.py b/pypy/module/pypyjit/test_pypy_c/test_model.py
--- a/pypy/module/pypyjit/test_pypy_c/test_model.py
+++ b/pypy/module/pypyjit/test_pypy_c/test_model.py
@@ -468,11 +468,13 @@
         log = self.run(f)
         loop, = log.loops_by_id('ntohs')
         assert loop.match_by_id('ntohs', """
+            guard_not_invalidated(descr=...)
             p12 = call(ConstClass(ntohs), 1, descr=...)
             guard_no_exception(descr=...)
         """)
         #
         assert not loop.match_by_id('ntohs', """
+            guard_not_invalidated(descr=...)
             p12 = call(ConstClass(foobar), 1, descr=...)
             guard_no_exception(descr=...)
         """)
diff --git a/pypy/module/pypyjit/test_pypy_c/test_pypy_c_new.py b/pypy/module/pypyjit/test_pypy_c/test_pypy_c_new.py
--- a/pypy/module/pypyjit/test_pypy_c/test_pypy_c_new.py
+++ b/pypy/module/pypyjit/test_pypy_c/test_pypy_c_new.py
@@ -221,7 +221,7 @@
         entry_bridge, = log.loops_by_filename(self.filepath, is_entry_bridge=True)
         ops = entry_bridge.ops_by_id('meth1', opcode='LOOKUP_METHOD')
         assert log.opnames(ops) == ['guard_value', 'getfield_gc', 'guard_value',
-                                    'getfield_gc', 'guard_value']
+                                    'guard_not_invalidated']
         # the second LOOKUP_METHOD is folded away
         assert list(entry_bridge.ops_by_id('meth2', opcode='LOOKUP_METHOD')) == []
         #
@@ -231,14 +231,15 @@
         assert loop.match("""
             i15 = int_lt(i6, i9)
             guard_true(i15, descr=<Guard3>)
+            guard_not_invalidated(descr=<Guard4>)
             i16 = force_token()
             i17 = int_add_ovf(i10, i6)
-            guard_no_overflow(descr=<Guard4>)
+            guard_no_overflow(descr=<Guard5>)
             i18 = force_token()
             i19 = int_add_ovf(i10, i17)
-            guard_no_overflow(descr=<Guard5>)
+            guard_no_overflow(descr=<Guard6>)
             --TICK--
-            jump(p0, p1, p2, p3, p4, p5, i19, p7, i17, i9, i10, p11, p12, p13, p14, descr=<Loop0>)
+            jump(p0, p1, p2, p3, p4, p5, i19, p7, i17, i9, i10, p11, p12, p13, descr=<Loop0>)
         """)
 
     def test_static_classmethod_call(self):
@@ -264,17 +265,17 @@
         assert loop.match("""
             i14 = int_lt(i6, i9)
             guard_true(i14, descr=<Guard3>)
+            guard_not_invalidated(descr=<Guard4>)
             i15 = force_token()
             i17 = int_add_ovf(i8, 1)
-            guard_no_overflow(descr=<Guard4>)
+            guard_no_overflow(descr=<Guard5>)
             i18 = force_token()
             i20 = int_sub(i17, 1)
             --TICK--
-            jump(p0, p1, p2, p3, p4, p5, i20, p7, i17, i9, p10, p11, p12, p13, descr=<Loop0>)
+            jump(p0, p1, p2, p3, p4, p5, i20, p7, i17, i9, p10, p11, p12, descr=<Loop0>)
         """)
 
     def test_default_and_kw(self):
-        py.test.skip("Wait until we have saner defaults strat")
         def main(n):
             def f(i, j=1):
                 return i + j
@@ -415,8 +416,9 @@
         assert loop.match("""
             i7 = int_lt(i5, i6)
             guard_true(i7, descr=<Guard3>)
+            guard_not_invalidated(descr=<Guard4>)
             i9 = int_add_ovf(i5, 2)
-            guard_no_overflow(descr=<Guard4>)
+            guard_no_overflow(descr=<Guard5>)
             --TICK--
             jump(p0, p1, p2, p3, p4, i9, i6, descr=<Loop0>)
         """)
@@ -439,10 +441,11 @@
         assert loop.match("""
             i9 = int_lt(i5, i6)
             guard_true(i9, descr=<Guard3>)
+            guard_not_invalidated(descr=<Guard4>)
             i10 = int_add_ovf(i5, i7)
-            guard_no_overflow(descr=<Guard4>)
+            guard_no_overflow(descr=<Guard5>)
             --TICK--
-            jump(p0, p1, p2, p3, p4, i10, i6, i7, p8, descr=<Loop0>)
+            jump(p0, p1, p2, p3, p4, i10, i6, p7, i7, p8, descr=<Loop0>)
         """)
 
     def test_mixed_type_loop(self):
@@ -506,15 +509,15 @@
             i20 = int_add(i11, 1)
             i21 = force_token()
             setfield_gc(p4, i20, descr=<.* .*W_AbstractSeqIterObject.inst_index .*>)
+            guard_not_invalidated(descr=<Guard4>)
             i23 = int_lt(i18, 0)
-            guard_false(i23, descr=<Guard4>)
+            guard_false(i23, descr=<Guard5>)
             i25 = int_ge(i18, i9)
-            guard_false(i25, descr=<Guard5>)
-            i26 = int_mul(i18, i10)
-            i27 = int_add_ovf(i7, i26)
-            guard_no_overflow(descr=<Guard6>)
+            guard_false(i25, descr=<Guard6>)
+            i27 = int_add_ovf(i7, i18)
+            guard_no_overflow(descr=<Guard7>)
             --TICK--
-            jump(p0, p1, p2, p3, p4, p5, p6, i27, i18, i9, i10, i20, i12, p13, i14, i15, descr=<Loop0>)
+            jump(..., descr=<Loop0>)
         """)
 
     def test_exception_inside_loop_1(self):
@@ -533,11 +536,12 @@
         assert loop.match("""
         i5 = int_is_true(i3)
         guard_true(i5, descr=<Guard3>)
+        guard_not_invalidated(descr=<Guard4>)
         --EXC-TICK--
         i12 = int_sub_ovf(i3, 1)
-        guard_no_overflow(descr=<Guard5>)
+        guard_no_overflow(descr=<Guard6>)
         --TICK--
-        jump(p0, p1, p2, i12, descr=<Loop0>)
+        jump(..., descr=<Loop0>)
         """)
 
     def test_exception_inside_loop_2(self):
@@ -580,10 +584,11 @@
         assert loop.match("""
             i7 = int_lt(i4, i5)
             guard_true(i7, descr=<Guard3>)
+            guard_not_invalidated(descr=<Guard4>)
             --EXC-TICK--
             i14 = int_add(i4, 1)
             --TICK--
-            jump(p0, p1, p2, p3, i14, i5, descr=<Loop0>)
+            jump(..., descr=<Loop0>)
         """)
 
     def test_chain_of_guards(self):
@@ -685,10 +690,11 @@
         assert loop.match_by_id('import', """
             p11 = getfield_gc(ConstPtr(ptr10), descr=<GcPtrFieldDescr pypy.objspace.std.celldict.ModuleCell.inst_w_value 8>)
             guard_value(p11, ConstPtr(ptr12), descr=<Guard4>)
+            guard_not_invalidated(descr=<Guard5>)
             p14 = getfield_gc(ConstPtr(ptr13), descr=<GcPtrFieldDescr pypy.objspace.std.celldict.ModuleCell.inst_w_value 8>)
             p16 = getfield_gc(ConstPtr(ptr15), descr=<GcPtrFieldDescr pypy.objspace.std.celldict.ModuleCell.inst_w_value 8>)
-            guard_value(p14, ConstPtr(ptr17), descr=<Guard5>)
-            guard_isnull(p16, descr=<Guard6>)
+            guard_value(p14, ConstPtr(ptr17), descr=<Guard6>)
+            guard_isnull(p16, descr=<Guard7>)
         """)
 
     def test_import_fast_path(self, tmpdir):
@@ -1109,7 +1115,7 @@
         # -------------------------------
         entry_bridge, = log.loops_by_filename(self.filepath, is_entry_bridge=True)
         ops = entry_bridge.ops_by_id('mutate', opcode='LOAD_ATTR')
-        assert log.opnames(ops) == ['guard_value', 'getfield_gc', 'guard_value',
+        assert log.opnames(ops) == ['guard_value', 'guard_not_invalidated',
                                     'getfield_gc', 'guard_nonnull_class']
         # the STORE_ATTR is folded away
         assert list(entry_bridge.ops_by_id('meth1', opcode='STORE_ATTR')) == []
@@ -1121,6 +1127,7 @@
             i8 = getfield_gc_pure(p5, descr=<SignedFieldDescr .*W_IntObject.inst_intval.*>)
             i9 = int_lt(i8, i7)
             guard_true(i9, descr=.*)
+            guard_not_invalidated(descr=.*)
             i11 = int_add(i8, 1)
             i12 = force_token()
             --TICK--
diff --git a/pypy/objspace/std/boolobject.py b/pypy/objspace/std/boolobject.py
--- a/pypy/objspace/std/boolobject.py
+++ b/pypy/objspace/std/boolobject.py
@@ -5,8 +5,7 @@
 
 class W_BoolObject(W_Object):
     from pypy.objspace.std.booltype import bool_typedef as typedef
-
-    _immutable_ = True
+    _immutable_fields_ = ['boolval']
 
     def __init__(w_self, boolval):
         w_self.boolval = not not boolval
diff --git a/pypy/objspace/std/bytearrayobject.py b/pypy/objspace/std/bytearrayobject.py
--- a/pypy/objspace/std/bytearrayobject.py
+++ b/pypy/objspace/std/bytearrayobject.py
@@ -22,7 +22,6 @@
 from pypy.interpreter import gateway
 from pypy.interpreter.argument import Signature
 from pypy.interpreter.buffer import RWBuffer
-from pypy.interpreter.function import Defaults
 from pypy.objspace.std.bytearraytype import (
     makebytearraydata_w, getbytevalue,
     new_bytearray
@@ -43,7 +42,7 @@
 registerimplementation(W_BytearrayObject)
 
 init_signature = Signature(['source', 'encoding', 'errors'], None, None)
-init_defaults = Defaults([None, None, None])
+init_defaults = [None, None, None]
 
 def init__Bytearray(space, w_bytearray, __args__):
     # this is on the silly side
diff --git a/pypy/objspace/std/dictmultiobject.py b/pypy/objspace/std/dictmultiobject.py
--- a/pypy/objspace/std/dictmultiobject.py
+++ b/pypy/objspace/std/dictmultiobject.py
@@ -4,7 +4,6 @@
 from pypy.interpreter import gateway
 from pypy.interpreter.argument import Signature
 from pypy.interpreter.error import OperationError, operationerrfmt
-from pypy.interpreter.function import Defaults
 from pypy.module.__builtin__.__init__ import BUILTIN_TO_INDEX, OPTIMIZED_BUILTINS
 
 from pypy.rlib.objectmodel import r_dict, we_are_translated
@@ -617,7 +616,7 @@
 
 
 init_signature = Signature(['seq_or_map'], None, 'kwargs')
-init_defaults = Defaults([None])
+init_defaults = [None]
 
 def update1(space, w_dict, w_data):
     if space.findattr(w_data, space.wrap("keys")) is None:
diff --git a/pypy/objspace/std/fake.py b/pypy/objspace/std/fake.py
--- a/pypy/objspace/std/fake.py
+++ b/pypy/objspace/std/fake.py
@@ -144,7 +144,7 @@
         frame = func.space.createframe(self, func.w_func_globals,
                                         func.closure)
         sig = self.signature()
-        scope_w = args.parse_obj(None, func.name, sig, func.defs.getitems())
+        scope_w = args.parse_obj(None, func.name, sig, func.defs_w)
         frame.setfastscope(scope_w)
         return frame.run()
 
diff --git a/pypy/objspace/std/listobject.py b/pypy/objspace/std/listobject.py
--- a/pypy/objspace/std/listobject.py
+++ b/pypy/objspace/std/listobject.py
@@ -8,7 +8,6 @@
 
 from pypy.objspace.std import slicetype
 from pypy.interpreter import gateway, baseobjspace
-from pypy.interpreter.function import Defaults
 from pypy.rlib.listsort import TimSort
 from pypy.interpreter.argument import Signature
 
@@ -33,7 +32,7 @@
 
 
 init_signature = Signature(['sequence'], None, None)
-init_defaults = Defaults([None])
+init_defaults = [None]
 
 def init__List(space, w_list, __args__):
     from pypy.objspace.std.tupleobject import W_TupleObject
diff --git a/pypy/objspace/std/noneobject.py b/pypy/objspace/std/noneobject.py
--- a/pypy/objspace/std/noneobject.py
+++ b/pypy/objspace/std/noneobject.py
@@ -9,7 +9,6 @@
 
 class W_NoneObject(W_Object):
     from pypy.objspace.std.nonetype import none_typedef as typedef
-    _immutable_ = True
 
     def unwrap(w_self, space):
         return None
diff --git a/pypy/objspace/std/setobject.py b/pypy/objspace/std/setobject.py
--- a/pypy/objspace/std/setobject.py
+++ b/pypy/objspace/std/setobject.py
@@ -5,7 +5,6 @@
 from pypy.interpreter.error import OperationError
 from pypy.interpreter import gateway
 from pypy.interpreter.argument import Signature
-from pypy.interpreter.function import Defaults
 from pypy.objspace.std.settype import set_typedef as settypedef
 from pypy.objspace.std.frozensettype import frozenset_typedef as frozensettypedef
 
@@ -625,7 +624,7 @@
 cmp__Frozenset_frozensettypedef = cmp__Set_settypedef
 
 init_signature = Signature(['some_iterable'], None, None)
-init_defaults = Defaults([None])
+init_defaults = [None]
 def init__Set(space, w_set, __args__):
     w_iterable, = __args__.parse_obj(
             None, 'set',
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
@@ -88,13 +88,14 @@
 
     _immutable_fields_ = ["flag_heaptype",
                           "flag_cpytype",
-                          #  flag_abstract is not immutable
+                          "flag_abstract?",
                           'needsdel',
                           'weakrefable',
                           'hasdict',
                           'nslots',
                           'instancetypedef',
                           'terminator',
+                          '_version_tag?',
                           ]
 
     # for config.objspace.std.getattributeshortcut
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/rpython/callparse.py b/pypy/rpython/callparse.py
--- a/pypy/rpython/callparse.py
+++ b/pypy/rpython/callparse.py
@@ -1,5 +1,4 @@
 from pypy.interpreter.argument import ArgumentsForTranslation, ArgErr
-from pypy.interpreter.function import Defaults
 from pypy.annotation import model as annmodel
 from pypy.rpython import rtuple
 from pypy.rpython.error import TyperError
@@ -53,7 +52,7 @@
         for x in graph.defaults:
             defs_h.append(ConstHolder(x))
     try:
-        holders = arguments.match_signature(signature, Defaults(defs_h))
+        holders = arguments.match_signature(signature, defs_h)
     except ArgErr, e:
         raise TyperError, "signature mismatch: %s" % e.getmsg(graph.name)
 
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)
@@ -1231,7 +1240,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/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/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/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/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/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/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,52 @@
     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_IMMUTABLE_ARRAY, rclass.IR_QUASIIMMUTABLE,
+                 rclass.IR_QUASIIMMUTABLE_ARRAY]:
+        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_IMMUTABLE_ARRAY, rclass.IR_QUASIIMMUTABLE,
+                 rclass.IR_QUASIIMMUTABLE_ARRAY]:
+        #
+        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_IMMUTABLE_ARRAY]:
+            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/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/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/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/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,21 @@
     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_IMMUTABLE_ARRAY      = ImmutableRanking('immutable_array', True)
+IR_QUASIIMMUTABLE       = ImmutableRanking('quasiimmutable', False)
+IR_QUASIIMMUTABLE_ARRAY = ImmutableRanking('quasiimmutable_array', False)
+
 class ImmutableConflictError(Exception):
     """Raised when the _immutable_ or _immutable_fields_ hints are
     not consistent across a class hierarchy."""
@@ -155,7 +173,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 +186,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 +220,38 @@
         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('?[*]'):   # a quasi-immutable field pointing to
+                name = name[:-4]        # an immutable array
+                rank = IR_QUASIIMMUTABLE_ARRAY
+            elif name.endswith('[*]'):    # for virtualizables' lists
                 name = name[:-3]
-                suffix = '[*]'
-            else:
-                suffix = ''
+                rank = IR_IMMUTABLE_ARRAY
+            elif name.endswith('?'):    # a quasi-immutable field
+                name = name[:-1]
+                rank = IR_QUASIIMMUTABLE
+            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 +272,32 @@
                         "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):
+        search1 = fieldname + '?'
+        search2 = fieldname + '?[*]'
+        rbase = self
+        while rbase.classdef is not None:
+            if (search1 in rbase.immutable_field_set or
+                search2 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/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/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/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_IMMUTABLE_ARRAY
+from pypy.rpython.rclass import IR_QUASIIMMUTABLE, IR_QUASIIMMUTABLE_ARRAY
 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_IMMUTABLE_ARRAY} or \
+               accessor.fields == {"ox": IR_IMMUTABLE,
+                                   "oy": IR_IMMUTABLE_ARRAY} # 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,58 @@
         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_QUASIIMMUTABLE} or \
+               accessor.fields == {"ox": IR_IMMUTABLE,
+                                   "oy": IR_IMMUTABLE,
+                                   "oa": IR_QUASIIMMUTABLE,
+                                   "ob": IR_QUASIIMMUTABLE} # 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']
+
+    def test_quasi_immutable_array(self):
+        from pypy.jit.metainterp.typesystem import deref
+        class A(object):
+            _immutable_fields_ = ['c?[*]']
+        class B(A):
+            pass
+        def f():
+            a = A()
+            a.c = [3, 4, 5]
+            return A()
+        t, typer, graph = self.gengraph(f, [])
+        A_TYPE = deref(graph.getreturnvar().concretetype)
+        accessor = A_TYPE._hints["immutable_fields"]
+        assert accessor.fields == {"inst_c": IR_QUASIIMMUTABLE_ARRAY} or \
+               accessor.fields == {"oc": IR_QUASIIMMUTABLE_ARRAY} # 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_c']
+
 
 class TestLLtype(BaseTestRclass, LLRtypeMixin):
 
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_IMMUTABLE_ARRAY
 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_IMMUTABLE_ARRAY}
         #
         def fn2(n):
             Base().base1 = 42
diff --git a/pypy/translator/geninterplevel.py b/pypy/translator/geninterplevel.py
--- a/pypy/translator/geninterplevel.py
+++ b/pypy/translator/geninterplevel.py
@@ -71,7 +71,7 @@
 log = py.log.Producer("geninterp")
 py.log.setconsumer("geninterp", ansi_log)
 
-GI_VERSION = '1.2.8'  # bump this for substantial changes
+GI_VERSION = '1.2.9'  # bump this for substantial changes
 # ____________________________________________________________
 
 try:
@@ -1175,8 +1175,7 @@
             pass
         defaultsname = self.uniquename('default')
         self._defaults_cache[key] = defaultsname
-        self.initcode.append("from pypy.interpreter.function import Defaults")
-        self.initcode.append("%s = Defaults([%s])" % (defaultsname, ', '.join(names)))
+        self.initcode.append("%s = [%s]" % (defaultsname, ', '.join(names)))
         return defaultsname
 
     def gen_rpyfunction(self, func):


More information about the pypy-commit mailing list