Author: Armin Rigo <ar...@tunes.org>
Branch: cpyext-gc-support-2
Changeset: r81979:1fc97a564c99
Date: 2016-01-27 19:12 +0100
http://bitbucket.org/pypy/pypy/changeset/1fc97a564c99/

Log:    hg merge default

diff too long, truncating to 2000 out of 4461 lines

diff --git a/pypy/config/pypyoption.py b/pypy/config/pypyoption.py
--- a/pypy/config/pypyoption.py
+++ b/pypy/config/pypyoption.py
@@ -85,7 +85,8 @@
 module_dependencies = {
     '_multiprocessing': [('objspace.usemodules.time', True),
                          ('objspace.usemodules.thread', True)],
-    'cpyext': [('objspace.usemodules.array', True)],
+    'cpyext': [('objspace.usemodules.array', True),
+               ('objspace.usemodules.micronumpy', True)],
     'cppyy': [('objspace.usemodules.cpyext', True)],
     }
 module_suggests = {
diff --git a/pypy/doc/faq.rst b/pypy/doc/faq.rst
--- a/pypy/doc/faq.rst
+++ b/pypy/doc/faq.rst
@@ -54,7 +54,8 @@
 It is quite common nowadays that xyz is available on PyPI_ and
 installable with ``pip install xyz``.  The simplest solution is to `use
 virtualenv (as documented here)`_.  Then enter (activate) the virtualenv
-and type: ``pip install xyz``.
+and type: ``pip install xyz``.  If you don't know or don't want virtualenv,
+you can also install ``pip`` globally by saying ``pypy -m ensurepip``.
 
 If you get errors from the C compiler, the module is a CPython C
 Extension module using unsupported features.  `See below.`_
diff --git a/pypy/doc/whatsnew-head.rst b/pypy/doc/whatsnew-head.rst
--- a/pypy/doc/whatsnew-head.rst
+++ b/pypy/doc/whatsnew-head.rst
@@ -123,3 +123,13 @@
 .. branch: fix-cpython-ssl-tests-2.7
 
 Fix SSL tests by importing cpython's patch
+
+.. branch: remove-getfield-pure
+
+Remove pure variants of ``getfield_gc_*`` operations from the JIT. Relevant
+optimizations instead consult the field descriptor to determine the purity of
+the operation. Additionally, pure ``getfield`` operations are now handled
+entirely by `rpython/jit/metainterp/optimizeopt/heap.py` rather than
+`rpython/jit/metainterp/optimizeopt/pure.py`, which can result in better 
codegen
+for traces containing a large number of pure getfield operations.
+
diff --git a/pypy/module/cpyext/Doc_stubgen_enable.patch 
b/pypy/module/cpyext/Doc_stubgen_enable.patch
deleted file mode 100644
--- a/pypy/module/cpyext/Doc_stubgen_enable.patch
+++ /dev/null
@@ -1,27 +0,0 @@
-Index: Doc/tools/sphinx/ext/refcounting.py
-===================================================================
---- Doc/tools/sphinx/ext/refcounting.py        (Revision 79453)
-+++ Doc/tools/sphinx/ext/refcounting.py        (Arbeitskopie)
-@@ -91,6 +91,7 @@
-     if app.config.refcount_file:
-         refcounts = Refcounts.fromfile(
-             path.join(app.srcdir, app.config.refcount_file))
-+        app._refcounts = refcounts
-         app.connect('doctree-read', refcounts.add_refcount_annotations)
- 
- 
-Index: Doc/conf.py
-===================================================================
---- Doc/conf.py        (Revision 79421)
-+++ Doc/conf.py        (Arbeitskopie)
-@@ -13,8 +13,8 @@
- # General configuration
- # ---------------------
- 
--extensions = ['sphinx.ext.refcounting', 'sphinx.ext.coverage',
--              'sphinx.ext.doctest', 'pyspecific']
-+extensions = ['pypy.module.cpyext.stubgen', 'sphinx.ext.refcounting', 
'sphinx.ext.coverage',
-+              'sphinx.ext.doctest', 'pyspecific', ]
- templates_path = ['tools/sphinxext']
- 
- # General substitutions.
diff --git a/pypy/module/cpyext/api.py b/pypy/module/cpyext/api.py
--- a/pypy/module/cpyext/api.py
+++ b/pypy/module/cpyext/api.py
@@ -441,8 +441,8 @@
 TYPES = {}
 GLOBALS = { # this needs to include all prebuilt pto, otherwise segfaults occur
     '_Py_NoneStruct#': ('PyObject*', 'space.w_None'),
-    '_Py_TrueStruct#': ('PyObject*', 'space.w_True'),
-    '_Py_ZeroStruct#': ('PyObject*', 'space.w_False'),
+    '_Py_TrueStruct#': ('PyIntObject*', 'space.w_True'),
+    '_Py_ZeroStruct#': ('PyIntObject*', 'space.w_False'),
     '_Py_NotImplementedStruct#': ('PyObject*', 'space.w_NotImplemented'),
     '_Py_EllipsisObject#': ('PyObject*', 'space.w_Ellipsis'),
     'PyDateTimeAPI': ('PyDateTime_CAPI*', 'None'),
@@ -505,7 +505,9 @@
 def get_structtype_for_ctype(ctype):
     from pypy.module.cpyext.typeobjectdefs import PyTypeObjectPtr
     from pypy.module.cpyext.cdatetime import PyDateTime_CAPI
+    from pypy.module.cpyext.intobject import PyIntObject
     return {"PyObject*": PyObject, "PyTypeObject*": PyTypeObjectPtr,
+            "PyIntObject*": PyIntObject,
             "PyDateTime_CAPI*": lltype.Ptr(PyDateTime_CAPI)}[ctype]
 
 # Note: as a special case, "PyObject" is the pointer type in RPython,
@@ -846,6 +848,7 @@
     space.fromcache(State).install_dll(eci)
 
     # populate static data
+    builder = StaticObjectBuilder(space)
     for name, (typ, expr) in GLOBALS.iteritems():
         from pypy.module import cpyext    # for the eval() below
         w_obj = eval(expr)
@@ -870,7 +873,7 @@
                 assert False, "Unknown static pointer: %s %s" % (typ, name)
             ptr.value = ctypes.cast(ll2ctypes.lltype2ctypes(value),
                                     ctypes.c_void_p).value
-        elif typ in ('PyObject*', 'PyTypeObject*'):
+        elif typ in ('PyObject*', 'PyTypeObject*', 'PyIntObject*'):
             if name.startswith('PyPyExc_') or 
name.startswith('cpyexttestExc_'):
                 # we already have the pointer
                 in_dll = ll2ctypes.get_ctypes_type(PyObject).in_dll(bridge, 
name)
@@ -879,17 +882,10 @@
                 # we have a structure, get its address
                 in_dll = ll2ctypes.get_ctypes_type(PyObject.TO).in_dll(bridge, 
name)
                 py_obj = ll2ctypes.ctypes2lltype(PyObject, 
ctypes.pointer(in_dll))
-            from pypy.module.cpyext.pyobject import (
-                track_reference, get_typedescr)
-            w_type = space.type(w_obj)
-            typedescr = get_typedescr(w_type.instancetypedef)
-            py_obj.c_ob_refcnt = 1
-            py_obj.c_ob_type = rffi.cast(PyTypeObjectPtr,
-                                         make_ref(space, w_type))
-            typedescr.attach(space, py_obj, w_obj)
-            track_reference(space, py_obj, w_obj)
+            builder.prepare(py_obj, w_obj)
         else:
             assert False, "Unknown static object: %s %s" % (typ, name)
+    builder.attach_all()
 
     pypyAPI = ctypes.POINTER(ctypes.c_void_p).in_dll(bridge, 'pypyAPI')
 
@@ -906,6 +902,36 @@
     setup_init_functions(eci, translating=False)
     return modulename.new(ext='')
 
+
+class StaticObjectBuilder:
+    def __init__(self, space):
+        self.space = space
+        self.to_attach = []
+
+    def prepare(self, py_obj, w_obj):
+        from pypy.module.cpyext.pyobject import track_reference
+        py_obj.c_ob_refcnt = 1
+        track_reference(self.space, py_obj, w_obj)
+        self.to_attach.append((py_obj, w_obj))
+
+    def attach_all(self):
+        from pypy.module.cpyext.pyobject import get_typedescr, make_ref
+        from pypy.module.cpyext.typeobject import finish_type_1, finish_type_2
+        space = self.space
+        space._cpyext_type_init = []
+        for py_obj, w_obj in self.to_attach:
+            w_type = space.type(w_obj)
+            typedescr = get_typedescr(w_type.instancetypedef)
+            py_obj.c_ob_type = rffi.cast(PyTypeObjectPtr,
+                                         make_ref(space, w_type))
+            typedescr.attach(space, py_obj, w_obj)
+        cpyext_type_init = space._cpyext_type_init
+        del space._cpyext_type_init
+        for pto, w_type in cpyext_type_init:
+            finish_type_1(space, pto)
+            finish_type_2(space, pto, w_type)
+
+
 def mangle_name(prefix, name):
     if name.startswith('Py'):
         return prefix + name[2:]
@@ -1100,14 +1126,33 @@
     run_bootstrap_functions(space)
     setup_va_functions(eci)
 
+    from pypy.module import cpyext   # for eval() below
+
+    # Set up the types.  Needs a special case, because of the
+    # immediate cycle involving 'c_ob_type', and because we don't
+    # want these types to be Py_TPFLAGS_HEAPTYPE.
+    static_types = {}
+    for name, (typ, expr) in GLOBALS.items():
+        if typ == 'PyTypeObject*':
+            pto = lltype.malloc(PyTypeObject, immortal=True,
+                                zero=True, flavor='raw')
+            pto.c_ob_refcnt = 1
+            pto.c_tp_basicsize = -1
+            static_types[name] = pto
+    builder = StaticObjectBuilder(space)
+    for name, pto in static_types.items():
+        pto.c_ob_type = static_types['PyType_Type#']
+        w_type = eval(GLOBALS[name][1])
+        builder.prepare(rffi.cast(PyObject, pto), w_type)
+    builder.attach_all()
+
     # populate static data
     for name, (typ, expr) in GLOBALS.iteritems():
         name = name.replace("#", "")
         if name.startswith('PyExc_'):
             name = '_' + name
-        from pypy.module import cpyext
         w_obj = eval(expr)
-        if typ in ('PyObject*', 'PyTypeObject*'):
+        if typ in ('PyObject*', 'PyTypeObject*', 'PyIntObject*'):
             struct_ptr = make_ref(space, w_obj)
         elif typ == 'PyDateTime_CAPI*':
             continue
diff --git a/pypy/module/cpyext/dictobject.py b/pypy/module/cpyext/dictobject.py
--- a/pypy/module/cpyext/dictobject.py
+++ b/pypy/module/cpyext/dictobject.py
@@ -59,7 +59,7 @@
         return None
     return borrow_from(w_dict, w_res)
 
-@cpython_api([PyObject, rffi.CCHARP], rffi.INT_real, error=-1)
+@cpython_api([PyObject, CONST_STRING], rffi.INT_real, error=-1)
 def PyDict_DelItemString(space, w_dict, key_ptr):
     """Remove the entry in dictionary p which has a key specified by the string
     key.  Return 0 on success or -1 on failure."""
diff --git a/pypy/module/cpyext/eval.py b/pypy/module/cpyext/eval.py
--- a/pypy/module/cpyext/eval.py
+++ b/pypy/module/cpyext/eval.py
@@ -128,7 +128,7 @@
     filename = "<string>"
     return run_string(space, source, filename, start, w_globals, w_locals)
 
-@cpython_api([rffi.CCHARP, rffi.INT_real, PyObject, PyObject,
+@cpython_api([CONST_STRING, rffi.INT_real, PyObject, PyObject,
               PyCompilerFlagsPtr], PyObject)
 def PyRun_StringFlags(space, source, start, w_globals, w_locals, flagsptr):
     """Execute Python source code from str in the context specified by the
@@ -189,7 +189,7 @@
         pi[0] = space.getindex_w(w_obj, None)
     return 1
 
-@cpython_api([rffi.CCHARP, rffi.CCHARP, rffi.INT_real, PyCompilerFlagsPtr],
+@cpython_api([CONST_STRING, CONST_STRING, rffi.INT_real, PyCompilerFlagsPtr],
              PyObject)
 def Py_CompileStringFlags(space, source, filename, start, flagsptr):
     """Parse and compile the Python source code in str, returning the
diff --git a/pypy/module/cpyext/patches/Doc_stubgen_enable.patch 
b/pypy/module/cpyext/patches/Doc_stubgen_enable.patch
new file mode 100644
--- /dev/null
+++ b/pypy/module/cpyext/patches/Doc_stubgen_enable.patch
@@ -0,0 +1,27 @@
+Index: Doc/tools/sphinx/ext/refcounting.py
+===================================================================
+--- Doc/tools/sphinx/ext/refcounting.py        (Revision 79453)
++++ Doc/tools/sphinx/ext/refcounting.py        (Arbeitskopie)
+@@ -91,6 +91,7 @@
+     if app.config.refcount_file:
+         refcounts = Refcounts.fromfile(
+             path.join(app.srcdir, app.config.refcount_file))
++        app._refcounts = refcounts
+         app.connect('doctree-read', refcounts.add_refcount_annotations)
+ 
+ 
+Index: Doc/conf.py
+===================================================================
+--- Doc/conf.py        (Revision 79421)
++++ Doc/conf.py        (Arbeitskopie)
+@@ -13,8 +13,8 @@
+ # General configuration
+ # ---------------------
+ 
+-extensions = ['sphinx.ext.refcounting', 'sphinx.ext.coverage',
+-              'sphinx.ext.doctest', 'pyspecific']
++extensions = ['pypy.module.cpyext.stubgen', 'sphinx.ext.refcounting', 
'sphinx.ext.coverage',
++              'sphinx.ext.doctest', 'pyspecific', ]
+ templates_path = ['tools/sphinxext']
+ 
+ # General substitutions.
diff --git a/pypy/module/cpyext/pystrtod.py b/pypy/module/cpyext/pystrtod.py
--- a/pypy/module/cpyext/pystrtod.py
+++ b/pypy/module/cpyext/pystrtod.py
@@ -1,6 +1,6 @@
 import errno
 from pypy.interpreter.error import OperationError
-from pypy.module.cpyext.api import cpython_api
+from pypy.module.cpyext.api import cpython_api, CONST_STRING
 from pypy.module.cpyext.pyobject import PyObject
 from rpython.rlib import rdtoa
 from rpython.rlib import rfloat
@@ -22,7 +22,7 @@
     rfloat.DIST_NAN: Py_DTST_NAN
 }
 
-@cpython_api([rffi.CCHARP, rffi.CCHARPP, PyObject], rffi.DOUBLE, error=-1.0)
+@cpython_api([CONST_STRING, rffi.CCHARPP, PyObject], rffi.DOUBLE, error=-1.0)
 @jit.dont_look_inside       # direct use of _get_errno()
 def PyOS_string_to_double(space, s, endptr, w_overflow_exception):
     """Convert a string s to a double, raising a Python
diff --git a/pypy/module/cpyext/test/test_typeobject.py 
b/pypy/module/cpyext/test/test_typeobject.py
--- a/pypy/module/cpyext/test/test_typeobject.py
+++ b/pypy/module/cpyext/test/test_typeobject.py
@@ -374,6 +374,11 @@
         module = self.import_extension('foo', [
             ("test_type", "METH_O",
              '''
+                 /* "args->ob_type" is a strange way to get at 'type',
+                    which should have a different tp_getattro/tp_setattro
+                    than its tp_base, which is 'object'.
+                  */
+                  
                  if (!args->ob_type->tp_setattro)
                  {
                      PyErr_SetString(PyExc_ValueError, "missing tp_setattro");
@@ -382,8 +387,12 @@
                  if (args->ob_type->tp_setattro ==
                      args->ob_type->tp_base->tp_setattro)
                  {
-                     PyErr_SetString(PyExc_ValueError, "recursive 
tp_setattro");
-                     return NULL;
+                     /* Note that unlike CPython, in PyPy 'type.tp_setattro'
+                        is the same function as 'object.tp_setattro'.  This
+                        test used to check that it was not, but that was an
+                        artifact of the bootstrap logic only---in the final
+                        C sources I checked and they are indeed the same.
+                        So we ignore this problem here. */
                  }
                  if (!args->ob_type->tp_getattro)
                  {
diff --git a/pypy/module/cpyext/typeobject.py b/pypy/module/cpyext/typeobject.py
--- a/pypy/module/cpyext/typeobject.py
+++ b/pypy/module/cpyext/typeobject.py
@@ -146,7 +146,7 @@
             assert len(slot_names) == 2
             struct = getattr(pto, slot_names[0])
             if not struct:
-                assert not space.config.translating
+                #assert not space.config.translating
                 assert not pto.c_tp_flags & Py_TPFLAGS_HEAPTYPE
                 if slot_names[0] == 'c_tp_as_number':
                     STRUCT_TYPE = PyNumberMethods
@@ -310,36 +310,6 @@
                    realize=type_realize,
                    dealloc=type_dealloc)
 
-    # There is the obvious cycle of 'type(type) == type', but there are
-    # also several other ones, like 'tuple.tp_bases' being itself a
-    # tuple instance.  We solve the first one by creating the type
-    # "type" manually here.  For the other cycles, we fix them by delaying
-    # creation of the types here, and hoping nothing breaks by seeing
-    # uninitialized-yet types (only for a few basic types like 'type',
-    # 'tuple', 'object', 'str').
-    space._cpyext_delay_type_creation = []
-
-    py_type   = _type_alloc(space, lltype.nullptr(PyTypeObject))
-    py_type.c_ob_type = rffi.cast(PyTypeObjectPtr, py_type)
-    track_reference(space, py_type, space.w_type)
-    type_attach(space, py_type, space.w_type)
-
-    as_pyobj(space, space.w_str)
-    as_pyobj(space, space.w_tuple)
-    as_pyobj(space, space.w_object)
-
-    delayed_types = []
-    while space._cpyext_delay_type_creation:
-        (py_obj, w_type) = space._cpyext_delay_type_creation.pop()
-        _type_really_attach(space, py_obj, w_type)
-        delayed_types.append((py_obj, w_type))
-    del space._cpyext_delay_type_creation
-    for py_obj, w_type in delayed_types:
-        pto = rffi.cast(PyTypeObjectPtr, py_obj)
-        finish_type_1(space, pto)
-        finish_type_2(space, pto, w_type)
-        finish_type_3(space, pto, w_type)
-
 
 @cpython_api([PyObject], lltype.Void, external=False)
 def subtype_dealloc(space, obj):
@@ -440,16 +410,13 @@
 
 
 def type_alloc(space, w_metatype):
-    metatype = make_ref(space, w_metatype)
-    metatype = rffi.cast(PyTypeObjectPtr, metatype)
-    assert metatype
+    metatype = rffi.cast(PyTypeObjectPtr, make_ref(space, w_metatype))
     # Don't increase refcount for non-heaptypes
-    flags = rffi.cast(lltype.Signed, metatype.c_tp_flags)
-    if not flags & Py_TPFLAGS_HEAPTYPE:
-        Py_DecRef(space, w_metatype)
-    return _type_alloc(space, metatype)
+    if metatype:
+        flags = rffi.cast(lltype.Signed, metatype.c_tp_flags)
+        if not flags & Py_TPFLAGS_HEAPTYPE:
+            Py_DecRef(space, w_metatype)
 
-def _type_alloc(space, metatype):
     heaptype = lltype.malloc(PyHeapTypeObject.TO,
                              flavor='raw', zero=True)
     pto = heaptype.c_ht_type
@@ -460,7 +427,6 @@
     pto.c_tp_as_sequence = heaptype.c_as_sequence
     pto.c_tp_as_mapping = heaptype.c_as_mapping
     pto.c_tp_as_buffer = heaptype.c_as_buffer
-
     pto.c_tp_basicsize = -1 # hopefully this makes malloc bail out
     pto.c_tp_itemsize = 0
 
@@ -470,13 +436,6 @@
     """
     Fills a newly allocated PyTypeObject from an existing type.
     """
-    if hasattr(space, '_cpyext_delay_type_creation'):
-        space._cpyext_delay_type_creation.append((py_obj, w_type))
-    else:
-        _type_really_attach(space, py_obj, w_type)
-    return rffi.cast(PyTypeObjectPtr, py_obj)
-
-def _type_really_attach(space, py_obj, w_type):
     from pypy.module.cpyext.object import PyObject_Del
 
     assert isinstance(w_type, W_TypeObject)
@@ -497,15 +456,24 @@
             PyObject_Del.api_func.get_wrapper(space))
     pto.c_tp_alloc = llhelper(PyType_GenericAlloc.api_func.functype,
             PyType_GenericAlloc.api_func.get_wrapper(space))
+    if pto.c_tp_flags & Py_TPFLAGS_HEAPTYPE:
+        w_typename = space.getattr(w_type, space.wrap('__name__'))
+        heaptype = rffi.cast(PyHeapTypeObject, pto)
+        heaptype.c_ht_name = make_ref(space, w_typename)
+        from pypy.module.cpyext.stringobject import PyString_AsString
+        pto.c_tp_name = PyString_AsString(space, heaptype.c_ht_name)
+    else:
+        pto.c_tp_name = rffi.str2charp(w_type.name)
     # uninitialized fields:
     # c_tp_print, c_tp_getattr, c_tp_setattr
     # XXX implement
     # c_tp_compare and the following fields (see 
http://docs.python.org/c-api/typeobj.html )
     w_base = best_base(space, w_type.bases_w)
-    py_base = make_ref(space, w_base)
-    pto.c_tp_base = rffi.cast(PyTypeObjectPtr, py_base)
+    pto.c_tp_base = rffi.cast(PyTypeObjectPtr, make_ref(space, w_base))
 
-    if not hasattr(space, '_cpyext_delay_type_creation'):
+    if hasattr(space, '_cpyext_type_init'):
+        space._cpyext_type_init.append((pto, w_type))
+    else:
         finish_type_1(space, pto)
         finish_type_2(space, pto, w_type)
 
@@ -519,21 +487,8 @@
     if space.is_w(w_type, space.w_object):
         pto.c_tp_new = rffi.cast(newfunc, 1)
     update_all_slots(space, w_type, pto)
-
-    if not hasattr(space, '_cpyext_delay_type_creation'):
-        finish_type_3(space, pto, w_type)
-
     pto.c_tp_flags |= Py_TPFLAGS_READY
-
-def finish_type_3(space, pto, w_type):
-    if pto.c_tp_flags & Py_TPFLAGS_HEAPTYPE:
-        w_typename = space.getattr(w_type, space.wrap('__name__'))
-        heaptype = rffi.cast(PyHeapTypeObject, pto)
-        heaptype.c_ht_name = make_ref(space, w_typename)
-        from pypy.module.cpyext.stringobject import PyString_AsString
-        pto.c_tp_name = PyString_AsString(space, heaptype.c_ht_name)
-    else:
-        pto.c_tp_name = rffi.str2charp(w_type.name)
+    return pto
 
 def py_type_ready(space, pto):
     if pto.c_tp_flags & Py_TPFLAGS_READY:
diff --git a/pypy/module/pypyjit/test_pypy_c/test_call.py 
b/pypy/module/pypyjit/test_pypy_c/test_call.py
--- a/pypy/module/pypyjit/test_pypy_c/test_call.py
+++ b/pypy/module/pypyjit/test_pypy_c/test_call.py
@@ -83,9 +83,9 @@
             p38 = 
call_r(ConstClass(_ll_1_threadlocalref_get__Ptr_GcStruct_objectLlT_Signed), #, 
descr=<Callr . i EF=1 OS=5>)
             p39 = getfield_gc_r(p38, descr=<FieldP 
pypy.interpreter.executioncontext.ExecutionContext.inst_topframeref .*>)
             i40 = force_token()
-            p41 = getfield_gc_pure_r(p38, descr=<FieldP 
pypy.interpreter.executioncontext.ExecutionContext.inst_w_tracefunc .*>)
+            p41 = getfield_gc_r(p38, descr=<FieldP 
pypy.interpreter.executioncontext.ExecutionContext.inst_w_tracefunc .*>)
             guard_value(p41, ConstPtr(ptr42), descr=...)
-            i42 = getfield_gc_pure_i(p38, descr=<FieldU 
pypy.interpreter.executioncontext.ExecutionContext.inst_profilefunc .*>)
+            i42 = getfield_gc_i(p38, descr=<FieldU 
pypy.interpreter.executioncontext.ExecutionContext.inst_profilefunc .*>)
             i43 = int_is_zero(i42)
             guard_true(i43, descr=...)
             i50 = force_token()
@@ -435,21 +435,21 @@
             guard_isnull(p5, descr=...)
             guard_nonnull_class(p12, ConstClass(W_IntObject), descr=...)
             guard_value(p2, ConstPtr(ptr21), descr=...)
-            i22 = getfield_gc_pure_i(p12, descr=<FieldS 
pypy.objspace.std.intobject.W_IntObject.inst_intval .*>)
+            i22 = getfield_gc_i(p12, descr=<FieldS 
pypy.objspace.std.intobject.W_IntObject.inst_intval .*>)
             i24 = int_lt(i22, 5000)
             guard_true(i24, descr=...)
             guard_not_invalidated(descr=...)
             p29 = 
call_r(ConstClass(_ll_1_threadlocalref_get__Ptr_GcStruct_objectLlT_Signed), #, 
descr=<Callr . i EF=1 OS=5>)
             p30 = getfield_gc_r(p29, descr=<FieldP 
pypy.interpreter.executioncontext.ExecutionContext.inst_topframeref .*>)
             p31 = force_token()
-            p32 = getfield_gc_pure_r(p29, descr=<FieldP 
pypy.interpreter.executioncontext.ExecutionContext.inst_w_tracefunc .*>)
+            p32 = getfield_gc_r(p29, descr=<FieldP 
pypy.interpreter.executioncontext.ExecutionContext.inst_w_tracefunc .*>)
             guard_value(p32, ConstPtr(ptr33), descr=...)
-            i34 = getfield_gc_pure_i(p29, descr=<FieldU 
pypy.interpreter.executioncontext.ExecutionContext.inst_profilefunc .*>)
+            i34 = getfield_gc_i(p29, descr=<FieldU 
pypy.interpreter.executioncontext.ExecutionContext.inst_profilefunc .*>)
             i35 = int_is_zero(i34)
             guard_true(i35, descr=...)
             p37 = getfield_gc_r(ConstPtr(ptr36), descr=<FieldP 
pypy.interpreter.nestedscope.Cell.inst_w_value .*>)
             guard_nonnull_class(p37, ConstClass(W_IntObject), descr=...)
-            i39 = getfield_gc_pure_i(p37, descr=<FieldS 
pypy.objspace.std.intobject.W_IntObject.inst_intval .*>)
+            i39 = getfield_gc_i(p37, descr=<FieldS 
pypy.objspace.std.intobject.W_IntObject.inst_intval .*>)
             i40 = int_add_ovf(i22, i39)
             guard_no_overflow(descr=...)
             --TICK--
@@ -466,7 +466,7 @@
             """, [])
         loop, = log.loops_by_id('call')
         assert loop.match("""
-            i8 = getfield_gc_pure_i(p6, descr=<FieldS 
pypy.objspace.std.intobject.W_IntObject.inst_intval .*>)
+            i8 = getfield_gc_i(p6, descr=<FieldS 
pypy.objspace.std.intobject.W_IntObject.inst_intval .*>)
             i10 = int_lt(i8, 5000)
             guard_true(i10, descr=...)
             guard_not_invalidated?
diff --git a/pypy/module/pypyjit/test_pypy_c/test_containers.py 
b/pypy/module/pypyjit/test_pypy_c/test_containers.py
--- a/pypy/module/pypyjit/test_pypy_c/test_containers.py
+++ b/pypy/module/pypyjit/test_pypy_c/test_containers.py
@@ -84,7 +84,7 @@
             guard_no_exception(descr=...)
             p20 = new_with_vtable(descr=...)
             call_n(ConstClass(_ll_dict_setitem_lookup_done_trampoline), p13, 
p10, p20, i12, i17, descr=<Callv 0 rrrii EF=5>)
-            setfield_gc(p20, i5, descr=<FieldS .*W_IntObject.inst_intval .*>)
+            setfield_gc(p20, i5, descr=<FieldS .*W_IntObject.inst_intval .* 
pure>)
             guard_no_exception(descr=...)
             i23 = call_i(ConstClass(ll_call_lookup_function), p13, p10, i12, 
0, descr=<Calli . rrii EF=5 OS=4>)
             guard_no_exception(descr=...)
@@ -93,7 +93,7 @@
             p28 = getfield_gc_r(p13, descr=<FieldP dicttable.entries .*>)
             p29 = getinteriorfield_gc_r(p28, i23, descr=<InteriorFieldDescr 
<FieldP odictentry.value .*>>)
             guard_nonnull_class(p29, ConstClass(W_IntObject), descr=...)
-            i31 = getfield_gc_pure_i(p29, descr=<FieldS 
.*W_IntObject.inst_intval .*>)
+            i31 = getfield_gc_i(p29, descr=<FieldS .*W_IntObject.inst_intval 
.* pure>)
             i32 = int_sub_ovf(i31, i5)
             guard_no_overflow(descr=...)
             i34 = int_add_ovf(i32, 1)
diff --git a/pypy/module/pypyjit/test_pypy_c/test_micronumpy.py 
b/pypy/module/pypyjit/test_pypy_c/test_micronumpy.py
--- a/pypy/module/pypyjit/test_pypy_c/test_micronumpy.py
+++ b/pypy/module/pypyjit/test_pypy_c/test_micronumpy.py
@@ -101,13 +101,13 @@
         loop = log._filter(log.loops[0])
         assert loop.match("""
             guard_class(p1, #, descr=...)
-            p4 = getfield_gc_pure_r(p1, descr=<FieldP 
pypy.module.micronumpy.iterators.ArrayIter.inst_array \d+>)
+            p4 = getfield_gc_r(p1, descr=<FieldP 
pypy.module.micronumpy.iterators.ArrayIter.inst_array \d+ pure>)
             i5 = getfield_gc_i(p0, descr=<FieldS 
pypy.module.micronumpy.iterators.IterState.inst_offset \d+>)
-            p6 = getfield_gc_pure_r(p4, descr=<FieldP 
pypy.module.micronumpy.concrete.BaseConcreteArray.inst_dtype \d+>)
-            p7 = getfield_gc_pure_r(p6, descr=<FieldP 
pypy.module.micronumpy.descriptor.W_Dtype.inst_itemtype \d+>)
+            p6 = getfield_gc_r(p4, descr=<FieldP 
pypy.module.micronumpy.concrete.BaseConcreteArray.inst_dtype \d+ pure>)
+            p7 = getfield_gc_r(p6, descr=<FieldP 
pypy.module.micronumpy.descriptor.W_Dtype.inst_itemtype \d+ pure>)
             guard_class(p7, ConstClass(Float64), descr=...)
-            i9 = getfield_gc_pure_i(p4, descr=<FieldU 
pypy.module.micronumpy.concrete.BaseConcreteArray.inst_storage \d+>)
-            i10 = getfield_gc_pure_i(p6, descr=<FieldU 
pypy.module.micronumpy.descriptor.W_Dtype.inst_byteorder \d+>)
+            i9 = getfield_gc_i(p4, descr=<FieldU 
pypy.module.micronumpy.concrete.BaseConcreteArray.inst_storage \d+ pure>)
+            i10 = getfield_gc_i(p6, descr=<FieldU 
pypy.module.micronumpy.descriptor.W_Dtype.inst_byteorder \d+ pure>)
             i12 = int_eq(i10, 61)
             i14 = int_eq(i10, 60)
             i15 = int_or(i12, i14)
@@ -117,28 +117,28 @@
             i18 = float_ne(f16, 0.000000)
             guard_true(i18, descr=...)
             guard_nonnull_class(p2, ConstClass(W_BoolBox), descr=...)
-            i20 = getfield_gc_pure_i(p2, descr=<FieldU 
pypy.module.micronumpy.boxes.W_BoolBox.inst_value \d+>)
+            i20 = getfield_gc_i(p2, descr=<FieldU 
pypy.module.micronumpy.boxes.W_BoolBox.inst_value \d+ pure>)
             i21 = int_is_true(i20)
             guard_false(i21, descr=...)
             i22 = getfield_gc_i(p0, descr=<FieldS 
pypy.module.micronumpy.iterators.IterState.inst_index \d+>)
-            i23 = getfield_gc_pure_i(p1, descr=<FieldU 
pypy.module.micronumpy.iterators.ArrayIter.inst_track_index \d+>)
+            i23 = getfield_gc_i(p1, descr=<FieldU 
pypy.module.micronumpy.iterators.ArrayIter.inst_track_index \d+ pure>)
             guard_true(i23, descr=...)
             i25 = int_add(i22, 1)
-            p26 = getfield_gc_pure_r(p0, descr=<FieldP 
pypy.module.micronumpy.iterators.IterState.inst__indices \d+>)
-            i27 = getfield_gc_pure_i(p1, descr=<FieldS 
pypy.module.micronumpy.iterators.ArrayIter.inst_contiguous \d+>)
+            p26 = getfield_gc_r(p0, descr=<FieldP 
pypy.module.micronumpy.iterators.IterState.inst__indices \d+ pure>)
+            i27 = getfield_gc_i(p1, descr=<FieldS 
pypy.module.micronumpy.iterators.ArrayIter.inst_contiguous \d+ pure>)
             i28 = int_is_true(i27)
             guard_true(i28, descr=...)
-            i29 = getfield_gc_pure_i(p6, descr=<FieldS 
pypy.module.micronumpy.descriptor.W_Dtype.inst_elsize \d+>)
+            i29 = getfield_gc_i(p6, descr=<FieldS 
pypy.module.micronumpy.descriptor.W_Dtype.inst_elsize \d+ pure>)
             guard_value(i29, 8, descr=...)
             i30 = int_add(i5, 8)
-            i31 = getfield_gc_pure_i(p1, descr=<FieldS 
pypy.module.micronumpy.iterators.ArrayIter.inst_size \d+>)
+            i31 = getfield_gc_i(p1, descr=<FieldS 
pypy.module.micronumpy.iterators.ArrayIter.inst_size \d+ pure>)
             i32 = int_ge(i25, i31)
             guard_false(i32, descr=...)
             p34 = new_with_vtable(descr=...)
             {{{
-            setfield_gc(p34, p1, descr=<FieldP 
pypy.module.micronumpy.iterators.IterState.inst_iterator \d+>)
+            setfield_gc(p34, p1, descr=<FieldP 
pypy.module.micronumpy.iterators.IterState.inst_iterator \d+ pure>)
             setfield_gc(p34, i25, descr=<FieldS 
pypy.module.micronumpy.iterators.IterState.inst_index \d+>)
-            setfield_gc(p34, p26, descr=<FieldP 
pypy.module.micronumpy.iterators.IterState.inst__indices \d+>)
+            setfield_gc(p34, p26, descr=<FieldP 
pypy.module.micronumpy.iterators.IterState.inst__indices \d+ pure>)
             setfield_gc(p34, i30, descr=<FieldS 
pypy.module.micronumpy.iterators.IterState.inst_offset \d+>)
             }}}
             jump(..., descr=...)
diff --git a/pypy/module/pypyjit/test_pypy_c/test_min_max.py 
b/pypy/module/pypyjit/test_pypy_c/test_min_max.py
--- a/pypy/module/pypyjit/test_pypy_c/test_min_max.py
+++ b/pypy/module/pypyjit/test_pypy_c/test_min_max.py
@@ -54,7 +54,7 @@
         i19 = int_add(i11, 1)
         setfield_gc(p2, i19, descr=...)
         guard_nonnull_class(p18, ConstClass(W_IntObject), descr=...)
-        i20 = getfield_gc_pure_i(p18, descr=...)
+        i20 = getfield_gc_i(p18, descr=...)
         i21 = int_gt(i20, i14)
         guard_true(i21, descr=...)
         jump(..., descr=...)
diff --git a/pypy/module/pypyjit/test_pypy_c/test_misc.py 
b/pypy/module/pypyjit/test_pypy_c/test_misc.py
--- a/pypy/module/pypyjit/test_pypy_c/test_misc.py
+++ b/pypy/module/pypyjit/test_pypy_c/test_misc.py
@@ -113,7 +113,7 @@
             i12 = int_is_true(i4)
             guard_true(i12, descr=...)
             guard_not_invalidated(descr=...)
-            i10p = getfield_gc_pure_i(p10, descr=...)
+            i10p = getfield_gc_i(p10, descr=...)
             i10 = int_mul_ovf(2, i10p)
             guard_no_overflow(descr=...)
             i14 = int_add_ovf(i13, i10)
diff --git a/pypy/module/pypyjit/test_pypy_c/test_string.py 
b/pypy/module/pypyjit/test_pypy_c/test_string.py
--- a/pypy/module/pypyjit/test_pypy_c/test_string.py
+++ b/pypy/module/pypyjit/test_pypy_c/test_string.py
@@ -82,7 +82,7 @@
             strsetitem(p25, 0, i23)
             p93 = call_r(ConstClass(fromstr), p25, 16, descr=<Callr . ri EF=4>)
             guard_no_exception(descr=...)
-            i95 = getfield_gc_pure_i(p93, descr=<FieldS 
rpython.rlib.rbigint.rbigint.inst_size .*>)
+            i95 = getfield_gc_i(p93, descr=<FieldS 
rpython.rlib.rbigint.rbigint.inst_size .*>)
             i96 = int_gt(i95, #)
             guard_false(i96, descr=...)
             i94 = call_i(ConstClass(rbigint._toint_helper), p93, descr=<Calli 
. r EF=4>)
diff --git a/pypy/objspace/std/mapdict.py b/pypy/objspace/std/mapdict.py
--- a/pypy/objspace/std/mapdict.py
+++ b/pypy/objspace/std/mapdict.py
@@ -30,10 +30,10 @@
         assert isinstance(terminator, Terminator)
         self.terminator = terminator
 
-    def read(self, obj, selector):
-        attr = self.find_map_attr(selector)
+    def read(self, obj, name, index):
+        attr = self.find_map_attr(name, index)
         if attr is None:
-            return self.terminator._read_terminator(obj, selector)
+            return self.terminator._read_terminator(obj, name, index)
         if (
             jit.isconstant(attr.storageindex) and
             jit.isconstant(obj) and
@@ -47,39 +47,39 @@
     def _pure_mapdict_read_storage(self, obj, storageindex):
         return obj._mapdict_read_storage(storageindex)
 
-    def write(self, obj, selector, w_value):
-        attr = self.find_map_attr(selector)
+    def write(self, obj, name, index, w_value):
+        attr = self.find_map_attr(name, index)
         if attr is None:
-            return self.terminator._write_terminator(obj, selector, w_value)
+            return self.terminator._write_terminator(obj, name, index, w_value)
         if not attr.ever_mutated:
             attr.ever_mutated = True
         obj._mapdict_write_storage(attr.storageindex, w_value)
         return True
 
-    def delete(self, obj, selector):
+    def delete(self, obj, name, index):
         pass
 
-    def find_map_attr(self, selector):
+    def find_map_attr(self, name, index):
         if jit.we_are_jitted():
             # hack for the jit:
             # the _find_map_attr method is pure too, but its argument is never
             # constant, because it is always a new tuple
-            return self._find_map_attr_jit_pure(selector[0], selector[1])
+            return self._find_map_attr_jit_pure(name, index)
         else:
-            return self._find_map_attr_indirection(selector)
+            return self._find_map_attr_indirection(name, index)
 
     @jit.elidable
     def _find_map_attr_jit_pure(self, name, index):
-        return self._find_map_attr_indirection((name, index))
+        return self._find_map_attr_indirection(name, index)
 
     @jit.dont_look_inside
-    def _find_map_attr_indirection(self, selector):
+    def _find_map_attr_indirection(self, name, index):
         if (self.space.config.objspace.std.withmethodcache):
-            return self._find_map_attr_cache(selector)
-        return self._find_map_attr(selector)
+            return self._find_map_attr_cache(name, index)
+        return self._find_map_attr(name, index)
 
     @jit.dont_look_inside
-    def _find_map_attr_cache(self, selector):
+    def _find_map_attr_cache(self, name, index):
         space = self.space
         cache = space.fromcache(MapAttrCache)
         SHIFT2 = r_uint.BITS - space.config.objspace.std.methodcachesizeexp
@@ -87,31 +87,36 @@
         attrs_as_int = objectmodel.current_object_addr_as_int(self)
         # ^^^Note: see comment in typeobject.py for
         # _pure_lookup_where_with_method_cache()
-        hash_selector = objectmodel.compute_hash(selector)
+
+        # unrolled hash computation for 2-tuple
+        c1 = 0x345678
+        c2 = 1000003
+        hash_name = objectmodel.compute_hash(name)
+        hash_selector = intmask((c2 * ((c2 * c1) ^ hash_name)) ^ index)
         product = intmask(attrs_as_int * hash_selector)
         attr_hash = (r_uint(product) ^ (r_uint(product) << SHIFT1)) >> SHIFT2
         # ^^^Note2: same comment too
         cached_attr = cache.attrs[attr_hash]
         if cached_attr is self:
-            cached_selector = cache.selectors[attr_hash]
-            if cached_selector == selector:
+            cached_name = cache.names[attr_hash]
+            cached_index = cache.indexes[attr_hash]
+            if cached_name == name and cached_index == index:
                 attr = cache.cached_attrs[attr_hash]
                 if space.config.objspace.std.withmethodcachecounter:
-                    name = selector[0]
                     cache.hits[name] = cache.hits.get(name, 0) + 1
                 return attr
-        attr = self._find_map_attr(selector)
+        attr = self._find_map_attr(name, index)
         cache.attrs[attr_hash] = self
-        cache.selectors[attr_hash] = selector
+        cache.names[attr_hash] = name
+        cache.indexes[attr_hash] = index
         cache.cached_attrs[attr_hash] = attr
         if space.config.objspace.std.withmethodcachecounter:
-            name = selector[0]
             cache.misses[name] = cache.misses.get(name, 0) + 1
         return attr
 
-    def _find_map_attr(self, selector):
+    def _find_map_attr(self, name, index):
         while isinstance(self, PlainAttribute):
-            if selector == self.selector:
+            if name == self.name and index == self.index:
                 return self
             self = self.back
         return None
@@ -137,23 +142,22 @@
 
     @jit.elidable
     def _get_new_attr(self, name, index):
-        selector = name, index
         cache = self.cache_attrs
         if cache is None:
             cache = self.cache_attrs = {}
-        attr = cache.get(selector, None)
+        attr = cache.get((name, index), None)
         if attr is None:
-            attr = PlainAttribute(selector, self)
-            cache[selector] = attr
+            attr = PlainAttribute(name, index, self)
+            cache[name, index] = attr
         return attr
 
-    @jit.look_inside_iff(lambda self, obj, selector, w_value:
+    @jit.look_inside_iff(lambda self, obj, name, index, w_value:
             jit.isconstant(self) and
-            jit.isconstant(selector[0]) and
-            jit.isconstant(selector[1]))
-    def add_attr(self, obj, selector, w_value):
+            jit.isconstant(name) and
+            jit.isconstant(index))
+    def add_attr(self, obj, name, index, w_value):
         # grumble, jit needs this
-        attr = self._get_new_attr(selector[0], selector[1])
+        attr = self._get_new_attr(name, index)
         oldattr = obj._get_mapdict_map()
         if not jit.we_are_jitted():
             size_est = (oldattr._size_estimate + attr.size_estimate()
@@ -189,11 +193,11 @@
         AbstractAttribute.__init__(self, space, self)
         self.w_cls = w_cls
 
-    def _read_terminator(self, obj, selector):
+    def _read_terminator(self, obj, name, index):
         return None
 
-    def _write_terminator(self, obj, selector, w_value):
-        obj._get_mapdict_map().add_attr(obj, selector, w_value)
+    def _write_terminator(self, obj, name, index, w_value):
+        obj._get_mapdict_map().add_attr(obj, name, index, w_value)
         return True
 
     def copy(self, obj):
@@ -231,40 +235,40 @@
 
 
 class NoDictTerminator(Terminator):
-    def _write_terminator(self, obj, selector, w_value):
-        if selector[1] == DICT:
+    def _write_terminator(self, obj, name, index, w_value):
+        if index == DICT:
             return False
-        return Terminator._write_terminator(self, obj, selector, w_value)
+        return Terminator._write_terminator(self, obj, name, index, w_value)
 
 
 class DevolvedDictTerminator(Terminator):
-    def _read_terminator(self, obj, selector):
-        if selector[1] == DICT:
+    def _read_terminator(self, obj, name, index):
+        if index == DICT:
             space = self.space
             w_dict = obj.getdict(space)
-            return space.finditem_str(w_dict, selector[0])
-        return Terminator._read_terminator(self, obj, selector)
+            return space.finditem_str(w_dict, name)
+        return Terminator._read_terminator(self, obj, name, index)
 
-    def _write_terminator(self, obj, selector, w_value):
-        if selector[1] == DICT:
+    def _write_terminator(self, obj, name, index, w_value):
+        if index == DICT:
             space = self.space
             w_dict = obj.getdict(space)
-            space.setitem_str(w_dict, selector[0], w_value)
+            space.setitem_str(w_dict, name, w_value)
             return True
-        return Terminator._write_terminator(self, obj, selector, w_value)
+        return Terminator._write_terminator(self, obj, name, index, w_value)
 
-    def delete(self, obj, selector):
+    def delete(self, obj, name, index):
         from pypy.interpreter.error import OperationError
-        if selector[1] == DICT:
+        if index == DICT:
             space = self.space
             w_dict = obj.getdict(space)
             try:
-                space.delitem(w_dict, space.wrap(selector[0]))
+                space.delitem(w_dict, space.wrap(name))
             except OperationError, ex:
                 if not ex.match(space, space.w_KeyError):
                     raise
             return Terminator.copy(self, obj)
-        return Terminator.delete(self, obj, selector)
+        return Terminator.delete(self, obj, name, index)
 
     def remove_dict_entries(self, obj):
         assert 0, "should be unreachable"
@@ -276,27 +280,28 @@
         return Terminator.set_terminator(self, obj, terminator)
 
 class PlainAttribute(AbstractAttribute):
-    _immutable_fields_ = ['selector', 'storageindex', 'back', 'ever_mutated?']
+    _immutable_fields_ = ['name', 'index', 'storageindex', 'back', 
'ever_mutated?']
 
-    def __init__(self, selector, back):
+    def __init__(self, name, index, back):
         AbstractAttribute.__init__(self, back.space, back.terminator)
-        self.selector = selector
+        self.name = name
+        self.index = index
         self.storageindex = back.length()
         self.back = back
         self._size_estimate = self.length() * NUM_DIGITS_POW2
         self.ever_mutated = False
 
     def _copy_attr(self, obj, new_obj):
-        w_value = self.read(obj, self.selector)
-        new_obj._get_mapdict_map().add_attr(new_obj, self.selector, w_value)
+        w_value = self.read(obj, self.name, self.index)
+        new_obj._get_mapdict_map().add_attr(new_obj, self.name, self.index, 
w_value)
 
-    def delete(self, obj, selector):
-        if selector == self.selector:
+    def delete(self, obj, name, index):
+        if name == self.name and index == self.index:
             # ok, attribute is deleted
             if not self.ever_mutated:
                 self.ever_mutated = True
             return self.back.copy(obj)
-        new_obj = self.back.delete(obj, selector)
+        new_obj = self.back.delete(obj, name, index)
         if new_obj is not None:
             self._copy_attr(obj, new_obj)
         return new_obj
@@ -315,14 +320,14 @@
         return new_obj
 
     def search(self, attrtype):
-        if self.selector[1] == attrtype:
+        if self.index == attrtype:
             return self
         return self.back.search(attrtype)
 
     def materialize_r_dict(self, space, obj, dict_w):
         new_obj = self.back.materialize_r_dict(space, obj, dict_w)
-        if self.selector[1] == DICT:
-            w_attr = space.wrap(self.selector[0])
+        if self.index == DICT:
+            w_attr = space.wrap(self.name)
             dict_w[w_attr] = obj._mapdict_read_storage(self.storageindex)
         else:
             self._copy_attr(obj, new_obj)
@@ -330,12 +335,12 @@
 
     def remove_dict_entries(self, obj):
         new_obj = self.back.remove_dict_entries(obj)
-        if self.selector[1] != DICT:
+        if self.index != DICT:
             self._copy_attr(obj, new_obj)
         return new_obj
 
     def __repr__(self):
-        return "<PlainAttribute %s %s %r>" % (self.selector, 
self.storageindex, self.back)
+        return "<PlainAttribute %s %s %s %r>" % (self.name, self.index, 
self.storageindex, self.back)
 
 def _become(w_obj, new_obj):
     # this is like the _become method, really, but we cannot use that due to
@@ -347,8 +352,8 @@
         assert space.config.objspace.std.withmethodcache
         SIZE = 1 << space.config.objspace.std.methodcachesizeexp
         self.attrs = [None] * SIZE
-        self._empty_selector = (None, INVALID)
-        self.selectors = [self._empty_selector] * SIZE
+        self.names = [None] * SIZE
+        self.indexes = [INVALID] * SIZE
         self.cached_attrs = [None] * SIZE
         if space.config.objspace.std.withmethodcachecounter:
             self.hits = {}
@@ -357,8 +362,9 @@
     def clear(self):
         for i in range(len(self.attrs)):
             self.attrs[i] = None
-        for i in range(len(self.selectors)):
-            self.selectors[i] = self._empty_selector
+        for i in range(len(self.names)):
+            self.names[i] = None
+            self.indexes[i] = INVALID
         for i in range(len(self.cached_attrs)):
             self.cached_attrs[i] = None
 
@@ -388,20 +394,20 @@
     # objspace interface
 
     def getdictvalue(self, space, attrname):
-        return self._get_mapdict_map().read(self, (attrname, DICT))
+        return self._get_mapdict_map().read(self, attrname, DICT)
 
     def setdictvalue(self, space, attrname, w_value):
-        return self._get_mapdict_map().write(self, (attrname, DICT), w_value)
+        return self._get_mapdict_map().write(self, attrname, DICT, w_value)
 
     def deldictvalue(self, space, attrname):
-        new_obj = self._get_mapdict_map().delete(self, (attrname, DICT))
+        new_obj = self._get_mapdict_map().delete(self, attrname, DICT)
         if new_obj is None:
             return False
         self._become(new_obj)
         return True
 
     def getdict(self, space):
-        w_dict = self._get_mapdict_map().read(self, ("dict", SPECIAL))
+        w_dict = self._get_mapdict_map().read(self, "dict", SPECIAL)
         if w_dict is not None:
             assert isinstance(w_dict, W_DictMultiObject)
             return w_dict
@@ -409,7 +415,7 @@
         strategy = space.fromcache(MapDictStrategy)
         storage = strategy.erase(self)
         w_dict = W_DictObject(space, strategy, storage)
-        flag = self._get_mapdict_map().write(self, ("dict", SPECIAL), w_dict)
+        flag = self._get_mapdict_map().write(self, "dict", SPECIAL, w_dict)
         assert flag
         return w_dict
 
@@ -425,7 +431,7 @@
         # shell that continues to delegate to 'self'.
         if type(w_olddict.get_strategy()) is MapDictStrategy:
             w_olddict.get_strategy().switch_to_object_strategy(w_olddict)
-        flag = self._get_mapdict_map().write(self, ("dict", SPECIAL), w_dict)
+        flag = self._get_mapdict_map().write(self, "dict", SPECIAL, w_dict)
         assert flag
 
     def getclass(self, space):
@@ -443,16 +449,16 @@
         self._init_empty(w_subtype.terminator)
 
     def getslotvalue(self, slotindex):
-        key = ("slot", SLOTS_STARTING_FROM + slotindex)
-        return self._get_mapdict_map().read(self, key)
+        index = SLOTS_STARTING_FROM + slotindex
+        return self._get_mapdict_map().read(self, "slot", index)
 
     def setslotvalue(self, slotindex, w_value):
-        key = ("slot", SLOTS_STARTING_FROM + slotindex)
-        self._get_mapdict_map().write(self, key, w_value)
+        index = SLOTS_STARTING_FROM + slotindex
+        self._get_mapdict_map().write(self, "slot", index, w_value)
 
     def delslotvalue(self, slotindex):
-        key = ("slot", SLOTS_STARTING_FROM + slotindex)
-        new_obj = self._get_mapdict_map().delete(self, key)
+        index = SLOTS_STARTING_FROM + slotindex
+        new_obj = self._get_mapdict_map().delete(self, "slot", index)
         if new_obj is None:
             return False
         self._become(new_obj)
@@ -462,7 +468,7 @@
 
     def getweakref(self):
         from pypy.module._weakref.interp__weakref import WeakrefLifeline
-        lifeline = self._get_mapdict_map().read(self, ("weakref", SPECIAL))
+        lifeline = self._get_mapdict_map().read(self, "weakref", SPECIAL)
         if lifeline is None:
             return None
         assert isinstance(lifeline, WeakrefLifeline)
@@ -472,11 +478,11 @@
     def setweakref(self, space, weakreflifeline):
         from pypy.module._weakref.interp__weakref import WeakrefLifeline
         assert isinstance(weakreflifeline, WeakrefLifeline)
-        self._get_mapdict_map().write(self, ("weakref", SPECIAL), 
weakreflifeline)
+        self._get_mapdict_map().write(self, "weakref", SPECIAL, 
weakreflifeline)
     setweakref._cannot_really_call_random_things_ = True
 
     def delweakref(self):
-        self._get_mapdict_map().write(self, ("weakref", SPECIAL), None)
+        self._get_mapdict_map().write(self, "weakref", SPECIAL, None)
     delweakref._cannot_really_call_random_things_ = True
 
 class ObjectMixin(object):
@@ -721,7 +727,7 @@
         curr = self.unerase(w_dict.dstorage)._get_mapdict_map().search(DICT)
         if curr is None:
             raise KeyError
-        key = curr.selector[0]
+        key = curr.name
         w_value = self.getitem_str(w_dict, key)
         w_key = self.space.wrap(key)
         self.delitem(w_dict, w_key)
@@ -758,7 +764,7 @@
             curr_map = self.curr_map.search(DICT)
             if curr_map:
                 self.curr_map = curr_map.back
-                attr = curr_map.selector[0]
+                attr = curr_map.name
                 w_attr = self.space.wrap(attr)
                 return w_attr
         return None
@@ -780,7 +786,7 @@
             curr_map = self.curr_map.search(DICT)
             if curr_map:
                 self.curr_map = curr_map.back
-                attr = curr_map.selector[0]
+                attr = curr_map.name
                 return self.w_obj.getdictvalue(self.space, attr)
         return None
 
@@ -801,7 +807,7 @@
             curr_map = self.curr_map.search(DICT)
             if curr_map:
                 self.curr_map = curr_map.back
-                attr = curr_map.selector[0]
+                attr = curr_map.name
                 w_attr = self.space.wrap(attr)
                 return w_attr, self.w_obj.getdictvalue(self.space, attr)
         return None, None
@@ -884,9 +890,9 @@
             _, w_descr = w_type._pure_lookup_where_possibly_with_method_cache(
                 name, version_tag)
             #
-            selector = ("", INVALID)
+            attrname, index = ("", INVALID)
             if w_descr is None:
-                selector = (name, DICT) # common case: no such attr in the 
class
+                attrname, index = (name, DICT) # common case: no such attr in 
the class
             elif isinstance(w_descr, MutableCell):
                 pass              # we have a MutableCell in the class: give up
             elif space.is_data_descr(w_descr):
@@ -894,20 +900,21 @@
                 # (if any) has no relevance.
                 from pypy.interpreter.typedef import Member
                 if isinstance(w_descr, Member):    # it is a slot -- easy case
-                    selector = ("slot", SLOTS_STARTING_FROM + w_descr.index)
+                    attrname, index = ("slot", SLOTS_STARTING_FROM + 
w_descr.index)
             else:
                 # There is a non-data descriptor in the class.  If there is
                 # also a dict attribute, use the latter, caching its 
storageindex.
                 # If not, we loose.  We could do better in this case too,
                 # but we don't care too much; the common case of a method
                 # invocation is handled by LOOKUP_METHOD_xxx below.
-                selector = (name, DICT)
+                attrname = name
+                index = DICT
             #
-            if selector[1] != INVALID:
-                attr = map.find_map_attr(selector)
+            if index != INVALID:
+                attr = map.find_map_attr(attrname, index)
                 if attr is not None:
                     # Note that if map.terminator is a DevolvedDictTerminator,
-                    # map.find_map_attr will always return None if 
selector[1]==DICT.
+                    # map.find_map_attr will always return None if index==DICT.
                     _fill_cache(pycode, nameindex, map, version_tag, 
attr.storageindex)
                     return w_obj._mapdict_read_storage(attr.storageindex)
     if space.config.objspace.std.withmethodcachecounter:
diff --git a/pypy/objspace/std/test/test_mapdict.py 
b/pypy/objspace/std/test/test_mapdict.py
--- a/pypy/objspace/std/test/test_mapdict.py
+++ b/pypy/objspace/std/test/test_mapdict.py
@@ -34,8 +34,8 @@
 
 def test_plain_attribute():
     w_cls = "class"
-    aa = PlainAttribute(("b", DICT),
-                        PlainAttribute(("a", DICT),
+    aa = PlainAttribute("b", DICT,
+                        PlainAttribute("a", DICT,
                                        Terminator(space, w_cls)))
     assert aa.space is space
     assert aa.terminator.w_cls is w_cls
@@ -63,16 +63,16 @@
 def test_huge_chain():
     current = Terminator(space, "cls")
     for i in range(20000):
-        current = PlainAttribute((str(i), DICT), current)
-    assert current.find_map_attr(("0", DICT)).storageindex == 0
+        current = PlainAttribute(str(i), DICT, current)
+    assert current.find_map_attr("0", DICT).storageindex == 0
 
 
 def test_search():
-    aa = PlainAttribute(("b", DICT), PlainAttribute(("a", DICT), 
Terminator(None, None)))
+    aa = PlainAttribute("b", DICT, PlainAttribute("a", DICT, Terminator(None, 
None)))
     assert aa.search(DICT) is aa
     assert aa.search(SLOTS_STARTING_FROM) is None
     assert aa.search(SPECIAL) is None
-    bb = PlainAttribute(("C", SPECIAL), PlainAttribute(("A", 
SLOTS_STARTING_FROM), aa))
+    bb = PlainAttribute("C", SPECIAL, PlainAttribute("A", SLOTS_STARTING_FROM, 
aa))
     assert bb.search(DICT) is aa
     assert bb.search(SLOTS_STARTING_FROM) is bb.back
     assert bb.search(SPECIAL) is bb
@@ -320,7 +320,7 @@
 
     d = {}
     w_d = FakeDict(d)
-    flag = obj.map.write(obj, ("dict", SPECIAL), w_d)
+    flag = obj.map.write(obj, "dict", SPECIAL, w_d)
     assert flag
     materialize_r_dict(space, obj, d)
     assert d == {"a": 5, "b": 6, "c": 7}
diff --git a/rpython/jit/backend/llgraph/runner.py 
b/rpython/jit/backend/llgraph/runner.py
--- a/rpython/jit/backend/llgraph/runner.py
+++ b/rpython/jit/backend/llgraph/runner.py
@@ -152,7 +152,7 @@
         self.fieldname = fieldname
         self.FIELD = getattr(S, fieldname)
         self.index = heaptracker.get_fielddescr_index_in(S, fieldname)
-        self._is_pure = S._immutable_field(fieldname)
+        self._is_pure = S._immutable_field(fieldname) != False
 
     def is_always_pure(self):
         return self._is_pure
@@ -608,9 +608,6 @@
         p = support.cast_arg(lltype.Ptr(descr.S), p)
         return support.cast_result(descr.FIELD, getattr(p, descr.fieldname))
 
-    bh_getfield_gc_pure_i = bh_getfield_gc
-    bh_getfield_gc_pure_r = bh_getfield_gc
-    bh_getfield_gc_pure_f = bh_getfield_gc
     bh_getfield_gc_i = bh_getfield_gc
     bh_getfield_gc_r = bh_getfield_gc
     bh_getfield_gc_f = bh_getfield_gc
diff --git a/rpython/jit/backend/llsupport/descr.py 
b/rpython/jit/backend/llsupport/descr.py
--- a/rpython/jit/backend/llsupport/descr.py
+++ b/rpython/jit/backend/llsupport/descr.py
@@ -180,7 +180,8 @@
         return self.offset
 
     def repr_of_descr(self):
-        return '<Field%s %s %s>' % (self.flag, self.name, self.offset)
+        ispure = " pure" if self._is_pure else ""
+        return '<Field%s %s %s%s>' % (self.flag, self.name, self.offset, 
ispure)
 
     def get_parent_descr(self):
         return self.parent_descr
@@ -200,7 +201,7 @@
         flag = get_type_flag(FIELDTYPE)
         name = '%s.%s' % (STRUCT._name, fieldname)
         index_in_parent = heaptracker.get_fielddescr_index_in(STRUCT, 
fieldname)
-        is_pure = bool(STRUCT._immutable_field(fieldname))
+        is_pure = STRUCT._immutable_field(fieldname) != False
         fielddescr = FieldDescr(name, offset, size, flag, index_in_parent,
                                 is_pure)
         cachedict = cache.setdefault(STRUCT, {})
diff --git a/rpython/jit/backend/llsupport/rewrite.py 
b/rpython/jit/backend/llsupport/rewrite.py
--- a/rpython/jit/backend/llsupport/rewrite.py
+++ b/rpython/jit/backend/llsupport/rewrite.py
@@ -234,7 +234,6 @@
             self.emit_gc_store_or_indexed(op, ptr_box, index_box, value_box,
                                           fieldsize, itemsize, ofs)
         elif op.getopnum() in (rop.GETFIELD_GC_I, rop.GETFIELD_GC_F, 
rop.GETFIELD_GC_R,
-                               rop.GETFIELD_GC_PURE_I, rop.GETFIELD_GC_PURE_F, 
rop.GETFIELD_GC_PURE_R,
                                rop.GETFIELD_RAW_I, rop.GETFIELD_RAW_F, 
rop.GETFIELD_RAW_R):
             ofs, itemsize, sign = unpack_fielddescr(op.getdescr())
             ptr_box = op.getarg(0)
diff --git a/rpython/jit/backend/x86/assembler.py 
b/rpython/jit/backend/x86/assembler.py
--- a/rpython/jit/backend/x86/assembler.py
+++ b/rpython/jit/backend/x86/assembler.py
@@ -1477,9 +1477,6 @@
     genop_getfield_gc_f = _genop_getfield
     genop_getfield_raw_i = _genop_getfield
     genop_getfield_raw_f = _genop_getfield
-    genop_getfield_gc_pure_i = _genop_getfield
-    genop_getfield_gc_pure_r = _genop_getfield
-    genop_getfield_gc_pure_f = _genop_getfield
 
     def _genop_gc_load(self, op, arglocs, resloc):
         base_loc, ofs_loc, size_loc, sign_loc = arglocs
diff --git a/rpython/jit/metainterp/heapcache.py 
b/rpython/jit/metainterp/heapcache.py
--- a/rpython/jit/metainterp/heapcache.py
+++ b/rpython/jit/metainterp/heapcache.py
@@ -168,9 +168,6 @@
         elif (opnum != rop.GETFIELD_GC_R and
               opnum != rop.GETFIELD_GC_I and
               opnum != rop.GETFIELD_GC_F and
-              opnum != rop.GETFIELD_GC_PURE_R and
-              opnum != rop.GETFIELD_GC_PURE_I and
-              opnum != rop.GETFIELD_GC_PURE_F and
               opnum != rop.PTR_EQ and
               opnum != rop.PTR_NE and
               opnum != rop.INSTANCE_PTR_EQ and
diff --git a/rpython/jit/metainterp/history.py 
b/rpython/jit/metainterp/history.py
--- a/rpython/jit/metainterp/history.py
+++ b/rpython/jit/metainterp/history.py
@@ -816,9 +816,6 @@
         if 'getfield_gc' in check:
             assert check.pop('getfield_gc') == 0
             check['getfield_gc_i'] = check['getfield_gc_r'] = 
check['getfield_gc_f'] = 0
-        if 'getfield_gc_pure' in check:
-            assert check.pop('getfield_gc_pure') == 0
-            check['getfield_gc_pure_i'] = check['getfield_gc_pure_r'] = 
check['getfield_gc_pure_f'] = 0
         if 'getarrayitem_gc_pure' in check:
             assert check.pop('getarrayitem_gc_pure') == 0
             check['getarrayitem_gc_pure_i'] = check['getarrayitem_gc_pure_r'] 
= check['getarrayitem_gc_pure_f'] = 0
diff --git a/rpython/jit/metainterp/optimizeopt/heap.py 
b/rpython/jit/metainterp/optimizeopt/heap.py
--- a/rpython/jit/metainterp/optimizeopt/heap.py
+++ b/rpython/jit/metainterp/optimizeopt/heap.py
@@ -183,6 +183,8 @@
         return res
 
     def invalidate(self, descr):
+        if descr.is_always_pure():
+            return
         for opinfo in self.cached_infos:
             assert isinstance(opinfo, info.AbstractStructPtrInfo)
             opinfo._fields[descr.get_index()] = None
@@ -515,9 +517,14 @@
         return pendingfields
 
     def optimize_GETFIELD_GC_I(self, op):
+        descr = op.getdescr()
+        if descr.is_always_pure() and self.get_constant_box(op.getarg(0)) is 
not None:
+            resbox = self.optimizer.constant_fold(op)
+            self.optimizer.make_constant(op, resbox)
+            return
         structinfo = self.ensure_ptr_info_arg0(op)
-        cf = self.field_cache(op.getdescr())
-        field = cf.getfield_from_cache(self, structinfo, op.getdescr())
+        cf = self.field_cache(descr)
+        field = cf.getfield_from_cache(self, structinfo, descr)
         if field is not None:
             self.make_equal_to(op, field)
             return
@@ -525,23 +532,10 @@
         self.make_nonnull(op.getarg(0))
         self.emit_operation(op)
         # then remember the result of reading the field
-        structinfo.setfield(op.getdescr(), op.getarg(0), op, optheap=self, 
cf=cf)
+        structinfo.setfield(descr, op.getarg(0), op, optheap=self, cf=cf)
     optimize_GETFIELD_GC_R = optimize_GETFIELD_GC_I
     optimize_GETFIELD_GC_F = optimize_GETFIELD_GC_I
 
-    def optimize_GETFIELD_GC_PURE_I(self, op):
-        structinfo = self.ensure_ptr_info_arg0(op)
-        cf = self.field_cache(op.getdescr())
-        field = cf.getfield_from_cache(self, structinfo, op.getdescr())
-        if field is not None:
-            self.make_equal_to(op, field)
-            return
-        # default case: produce the operation
-        self.make_nonnull(op.getarg(0))
-        self.emit_operation(op)
-    optimize_GETFIELD_GC_PURE_R = optimize_GETFIELD_GC_PURE_I
-    optimize_GETFIELD_GC_PURE_F = optimize_GETFIELD_GC_PURE_I
-
     def optimize_SETFIELD_GC(self, op):
         self.setfield(op)
         #opnum = OpHelpers.getfield_pure_for_descr(op.getdescr())
@@ -631,12 +625,12 @@
 
     def optimize_QUASIIMMUT_FIELD(self, op):
         # Pattern: QUASIIMMUT_FIELD(s, descr=QuasiImmutDescr)
-        #          x = GETFIELD_GC_PURE(s, descr='inst_x')
+        #          x = GETFIELD_GC(s, descr='inst_x') # pure
         # If 's' is a constant (after optimizations) we rely on the rest of the
-        # optimizations to constant-fold the following getfield_gc_pure.
+        # optimizations to constant-fold the following pure getfield_gc.
         # in addition, we record the dependency here to make invalidation work
         # correctly.
-        # NB: emitting the GETFIELD_GC_PURE is only safe because the
+        # NB: emitting the pure GETFIELD_GC is only safe because the
         # QUASIIMMUT_FIELD is also emitted to make sure the dependency is
         # registered.
         structvalue = self.ensure_ptr_info_arg0(op)
diff --git a/rpython/jit/metainterp/optimizeopt/optimizer.py 
b/rpython/jit/metainterp/optimizeopt/optimizer.py
--- a/rpython/jit/metainterp/optimizeopt/optimizer.py
+++ b/rpython/jit/metainterp/optimizeopt/optimizer.py
@@ -10,6 +10,7 @@
 from rpython.jit.metainterp.typesystem import llhelper
 from rpython.rlib.objectmodel import specialize, we_are_translated
 from rpython.rlib.debug import debug_print
+from rpython.jit.metainterp.optimize import SpeculativeError
 
 
 
@@ -374,6 +375,7 @@
         if (box.type == 'i' and box.get_forwarded() and
             box.get_forwarded().is_constant()):
             return ConstInt(box.get_forwarded().getint())
+        return None
         #self.ensure_imported(value)
 
     def get_newoperations(self):
@@ -736,12 +738,64 @@
         self.emit_operation(op)
 
     def constant_fold(self, op):
+        self.protect_speculative_operation(op)
         argboxes = [self.get_constant_box(op.getarg(i))
                     for i in range(op.numargs())]
         return execute_nonspec_const(self.cpu, None,
                                        op.getopnum(), argboxes,
                                        op.getdescr(), op.type)
 
+    def protect_speculative_operation(self, op):
+        """When constant-folding a pure operation that reads memory from
+        a gcref, make sure that the gcref is non-null and of a valid type.
+        Otherwise, raise SpeculativeError.  This should only occur when
+        unrolling and optimizing the unrolled loop.  Note that if
+        cpu.supports_guard_gc_type is false, we can't really do this
+        check at all, but then we don't unroll in that case.
+        """
+        opnum = op.getopnum()
+        cpu = self.cpu
+
+        if OpHelpers.is_pure_getfield(opnum, op.getdescr()):
+            fielddescr = op.getdescr()
+            ref = self.get_constant_box(op.getarg(0)).getref_base()
+            cpu.protect_speculative_field(ref, fielddescr)
+            return
+
+        elif (opnum == rop.GETARRAYITEM_GC_PURE_I or
+              opnum == rop.GETARRAYITEM_GC_PURE_R or
+              opnum == rop.GETARRAYITEM_GC_PURE_F or
+              opnum == rop.ARRAYLEN_GC):
+            arraydescr = op.getdescr()
+            array = self.get_constant_box(op.getarg(0)).getref_base()
+            cpu.protect_speculative_array(array, arraydescr)
+            if opnum == rop.ARRAYLEN_GC:
+                return
+            arraylength = cpu.bh_arraylen_gc(array, arraydescr)
+
+        elif (opnum == rop.STRGETITEM or
+              opnum == rop.STRLEN):
+            string = self.get_constant_box(op.getarg(0)).getref_base()
+            cpu.protect_speculative_string(string)
+            if opnum == rop.STRLEN:
+                return
+            arraylength = cpu.bh_strlen(string)
+
+        elif (opnum == rop.UNICODEGETITEM or
+              opnum == rop.UNICODELEN):
+            unicode = self.get_constant_box(op.getarg(0)).getref_base()
+            cpu.protect_speculative_unicode(unicode)
+            if opnum == rop.UNICODELEN:
+                return
+            arraylength = cpu.bh_unicodelen(unicode)
+
+        else:
+            return
+
+        index = self.get_constant_box(op.getarg(1)).getint()
+        if not (0 <= index < arraylength):
+            raise SpeculativeError
+
     def is_virtual(self, op):
         if op.type == 'r':
             opinfo = self.getptrinfo(op)
diff --git a/rpython/jit/metainterp/optimizeopt/pure.py 
b/rpython/jit/metainterp/optimizeopt/pure.py
--- a/rpython/jit/metainterp/optimizeopt/pure.py
+++ b/rpython/jit/metainterp/optimizeopt/pure.py
@@ -94,7 +94,6 @@
                     break
             else:
                 # all constant arguments: constant-fold away
-                self.protect_speculative_operation(op)
                 resbox = self.optimizer.constant_fold(op)
                 # note that INT_xxx_OVF is not done from here, and the
                 # overflows in the INT_xxx operations are ignored
@@ -119,59 +118,6 @@
         if nextop:
             self.emit_operation(nextop)
 
-    def protect_speculative_operation(self, op):
-        """When constant-folding a pure operation that reads memory from
-        a gcref, make sure that the gcref is non-null and of a valid type.
-        Otherwise, raise SpeculativeError.  This should only occur when
-        unrolling and optimizing the unrolled loop.  Note that if
-        cpu.supports_guard_gc_type is false, we can't really do this
-        check at all, but then we don't unroll in that case.
-        """
-        opnum = op.getopnum()
-        cpu = self.optimizer.cpu
-
-        if   (opnum == rop.GETFIELD_GC_PURE_I or
-              opnum == rop.GETFIELD_GC_PURE_R or
-              opnum == rop.GETFIELD_GC_PURE_F):
-            fielddescr = op.getdescr()
-            ref = self.get_constant_box(op.getarg(0)).getref_base()
-            cpu.protect_speculative_field(ref, fielddescr)
-            return
-
-        elif (opnum == rop.GETARRAYITEM_GC_PURE_I or
-              opnum == rop.GETARRAYITEM_GC_PURE_R or
-              opnum == rop.GETARRAYITEM_GC_PURE_F or
-              opnum == rop.ARRAYLEN_GC):
-            arraydescr = op.getdescr()
-            array = self.get_constant_box(op.getarg(0)).getref_base()
-            cpu.protect_speculative_array(array, arraydescr)
-            if opnum == rop.ARRAYLEN_GC:
-                return
-            arraylength = cpu.bh_arraylen_gc(array, arraydescr)
-
-        elif (opnum == rop.STRGETITEM or
-              opnum == rop.STRLEN):
-            string = self.get_constant_box(op.getarg(0)).getref_base()
-            cpu.protect_speculative_string(string)
-            if opnum == rop.STRLEN:
-                return
-            arraylength = cpu.bh_strlen(string)
-
-        elif (opnum == rop.UNICODEGETITEM or
-              opnum == rop.UNICODELEN):
-            unicode = self.get_constant_box(op.getarg(0)).getref_base()
-            cpu.protect_speculative_unicode(unicode)
-            if opnum == rop.UNICODELEN:
-                return
-            arraylength = cpu.bh_unicodelen(unicode)
-
-        else:
-            return
-
-        index = self.get_constant_box(op.getarg(1)).getint()
-        if not (0 <= index < arraylength):
-            raise SpeculativeError
-
     def getrecentops(self, opnum):
         if rop._OVF_FIRST <= opnum <= rop._OVF_LAST:
             opnum = opnum - rop._OVF_FIRST
diff --git a/rpython/jit/metainterp/optimizeopt/test/test_dependency.py 
b/rpython/jit/metainterp/optimizeopt/test/test_dependency.py
--- a/rpython/jit/metainterp/optimizeopt/test/test_dependency.py
+++ b/rpython/jit/metainterp/optimizeopt/test/test_dependency.py
@@ -521,8 +521,8 @@
     def test_getfield(self):
         graph = self.build_dependency("""
         [p0, p1] # 0: 1,2,5
-        p2 = getfield_gc_r(p0) # 1: 3,5
-        p3 = getfield_gc_r(p0) # 2: 4
+        p2 = getfield_gc_r(p0, descr=valuedescr) # 1: 3,5
+        p3 = getfield_gc_r(p0, descr=valuedescr) # 2: 4
         guard_nonnull(p2) [p2] # 3: 4,5
         guard_nonnull(p3) [p3] # 4: 5
         jump(p0,p2) # 5:
@@ -532,10 +532,10 @@
     def test_cyclic(self):
         graph = self.build_dependency("""
         [p0, p1, p5, p6, p7, p9, p11, p12] # 0: 1,6
-        p13 = getfield_gc_r(p9) # 1: 2,5
+        p13 = getfield_gc_r(p9, descr=valuedescr) # 1: 2,5
         guard_nonnull(p13) [] # 2: 4,5
-        i14 = getfield_gc_i(p9) # 3: 5
-        p15 = getfield_gc_r(p13) # 4: 5
+        i14 = getfield_gc_i(p9, descr=valuedescr) # 3: 5
+        p15 = getfield_gc_r(p13, descr=valuedescr) # 4: 5
         guard_class(p15, 14073732) [p1, p0, p9, i14, p15, p13, p5, p6, p7] # 
5: 6
         jump(p0,p1,p5,p6,p7,p9,p11,p12) # 6:
         """)
diff --git a/rpython/jit/metainterp/optimizeopt/test/test_optimizebasic.py 
b/rpython/jit/metainterp/optimizeopt/test/test_optimizebasic.py
--- a/rpython/jit/metainterp/optimizeopt/test/test_optimizebasic.py
+++ b/rpython/jit/metainterp/optimizeopt/test/test_optimizebasic.py
@@ -955,12 +955,12 @@
         """
         self.optimize_loop(ops, expected)
 
-    def test_getfield_gc_pure_1(self):
+    def test_getfield_gc_1(self):
         ops = """
         [i]
-        p1 = new_with_vtable(descr=nodesize)
-        setfield_gc(p1, i, descr=valuedescr)
-        i1 = getfield_gc_pure_i(p1, descr=valuedescr)
+        p1 = new_with_vtable(descr=nodesize3)
+        setfield_gc(p1, i, descr=valuedescr3)
+        i1 = getfield_gc_i(p1, descr=valuedescr3)
         jump(i1)
         """
         expected = """
@@ -969,17 +969,16 @@
         """
         self.optimize_loop(ops, expected)
 
-    def test_getfield_gc_pure_2(self):
+    def test_getfield_gc_2(self):
         ops = """
         [i]
-        i1 = getfield_gc_pure_i(ConstPtr(myptr), descr=valuedescr)
+        i1 = getfield_gc_i(ConstPtr(myptr3), descr=valuedescr3)
         jump(i1)
         """
         expected = """
         [i]
-        jump(5)
-        """
-        self.node.value = 5
+        jump(7)
+        """
         self.optimize_loop(ops, expected)
 
     def test_getfield_gc_nonpure_2(self):
@@ -1343,7 +1342,7 @@
         setfield_gc(p1, i1, descr=valuedescr)
         #
         # some operations on which the above setfield_gc cannot have effect
-        i3 = getarrayitem_gc_pure_i(p3, 1, descr=arraydescr)
+        i3 = getarrayitem_gc_i(p3, 1, descr=arraydescr)
         i4 = getarrayitem_gc_i(p3, i3, descr=arraydescr)
         i5 = int_add(i3, i4)
         setarrayitem_gc(p3, 0, i5, descr=arraydescr)
@@ -1355,7 +1354,7 @@
         expected = """
         [p1, i1, i2, p3]
         #
-        i3 = getarrayitem_gc_pure_i(p3, 1, descr=arraydescr)
+        i3 = getarrayitem_gc_i(p3, 1, descr=arraydescr)
         i4 = getarrayitem_gc_i(p3, i3, descr=arraydescr)
         i5 = int_add(i3, i4)
         #
@@ -1597,7 +1596,7 @@
         ops = """
         [p1, p2]
         p3 = getarrayitem_gc_r(p1, 0, descr=arraydescr2)
-        i4 = getfield_gc_pure_i(ConstPtr(myptr), descr=valuedescr)
+        i4 = getfield_gc_i(ConstPtr(myptr3), descr=valuedescr3)
         p5 = getarrayitem_gc_r(p1, 0, descr=arraydescr2)
         escape_n(p3)
         escape_n(i4)
@@ -1608,7 +1607,7 @@
         [p1, p2]
         p3 = getarrayitem_gc_r(p1, 0, descr=arraydescr2)
         escape_n(p3)
-        escape_n(5)
+        escape_n(7)
         escape_n(p3)
         jump(p1, p2)
         """
@@ -5076,7 +5075,7 @@
         []
         quasiimmut_field(ConstPtr(quasiptr), descr=quasiimmutdescr)
         guard_not_invalidated() []
-        i0 = getfield_gc_pure_i(ConstPtr(quasiptr), descr=quasifielddescr)
+        i0 = getfield_gc_i(ConstPtr(quasiptr), descr=quasifielddescr)
         i1 = call_pure_i(123, i0, descr=nonwritedescr)
         finish(i1)
         """
@@ -5462,15 +5461,15 @@
     def test_getarrayitem_gc_pure_not_invalidated(self):
         ops = """
         [p0]
-        i1 = getarrayitem_gc_pure_i(p0, 1, descr=arraydescr)
+        i1 = getarrayitem_gc_pure_i(p0, 1, descr=arrayimmutdescr)
         escape_n(p0)
-        i2 = getarrayitem_gc_pure_i(p0, 1, descr=arraydescr)
+        i2 = getarrayitem_gc_pure_i(p0, 1, descr=arrayimmutdescr)
         escape_n(i2)
         jump(p0)
         """
         expected = """
         [p0]
-        i1 = getarrayitem_gc_pure_i(p0, 1, descr=arraydescr)
+        i1 = getarrayitem_gc_pure_i(p0, 1, descr=arrayimmutdescr)
         escape_n(p0)
         escape_n(i1)
         jump(p0)
diff --git a/rpython/jit/metainterp/optimizeopt/test/test_optimizeopt.py 
b/rpython/jit/metainterp/optimizeopt/test/test_optimizeopt.py
--- a/rpython/jit/metainterp/optimizeopt/test/test_optimizeopt.py
+++ b/rpython/jit/metainterp/optimizeopt/test/test_optimizeopt.py
@@ -1409,12 +1409,12 @@
         """
         self.optimize_loop(ops, expected)
 
-    def test_getfield_gc_pure_1(self):
+    def test_pure_getfield_gc_1(self):
         ops = """
         [i]
         p1 = new_with_vtable(descr=nodesize)
         setfield_gc(p1, i, descr=valuedescr)
-        i1 = getfield_gc_pure_i(p1, descr=valuedescr)
+        i1 = getfield_gc_i(p1, descr=valuedescr)
         jump(i1)
         """
         expected = """
@@ -1423,10 +1423,10 @@
         """
         self.optimize_loop(ops, expected)
 
-    def test_getfield_gc_pure_2(self):
+    def test_pure_getfield_gc_2(self):
         ops = """
         [i]
-        i1 = getfield_gc_pure_i(ConstPtr(myptr), descr=valuedescr)
+        i1 = getfield_gc_i(ConstPtr(myptr3), descr=valuedescr3)
         jump(i1)
         """
         expected = """
@@ -1436,20 +1436,20 @@
         self.node.value = 5
         self.optimize_loop(ops, expected)
 
-    def test_getfield_gc_pure_3(self):
+    def test_pure_getfield_gc_3(self):
         ops = """
         []
         p1 = escape_r()
-        p2 = getfield_gc_pure_r(p1, descr=nextdescr)
+        p2 = getfield_gc_r(p1, descr=nextdescr3)
         escape_n(p2)
-        p3 = getfield_gc_pure_r(p1, descr=nextdescr)
+        p3 = getfield_gc_r(p1, descr=nextdescr3)
         escape_n(p3)
         jump()
         """
         expected = """
         []
         p1 = escape_r()
-        p2 = getfield_gc_pure_r(p1, descr=nextdescr)
+        p2 = getfield_gc_r(p1, descr=nextdescr3)
         escape_n(p2)
         escape_n(p2)
         jump()
@@ -2319,7 +2319,7 @@
         setfield_gc(p1, i1, descr=valuedescr)
         #
         # some operations on which the above setfield_gc cannot have effect
-        i3 = getarrayitem_gc_pure_i(p3, 1, descr=arraydescr)
+        i3 = getarrayitem_gc_i(p3, 1, descr=arraydescr)
         i4 = getarrayitem_gc_i(p3, i3, descr=arraydescr)
         i5 = int_add(i3, i4)
         setarrayitem_gc(p3, 0, i5, descr=arraydescr)
@@ -2332,7 +2332,7 @@
         preamble = """
         [p1, i1, i2, p3]
         #
-        i3 = getarrayitem_gc_pure_i(p3, 1, descr=arraydescr)
+        i3 = getarrayitem_gc_i(p3, 1, descr=arraydescr)
         i4 = getarrayitem_gc_i(p3, i3, descr=arraydescr)
         i5 = int_add(i3, i4)
         #
@@ -2340,11 +2340,12 @@
         setfield_gc(p1, i4, descr=nextdescr)
         setarrayitem_gc(p3, 0, i5, descr=arraydescr)
         escape_n()
-        jump(p1, i1, i2, p3, i3)
-        """
-        expected = """
-        [p1, i1, i2, p3, i3]
+        jump(p1, i1, i2, p3)
+        """
+        expected = """
+        [p1, i1, i2, p3]
         #
+        i3 = getarrayitem_gc_i(p3, 1, descr=arraydescr)
         i4 = getarrayitem_gc_i(p3, i3, descr=arraydescr)
         i5 = int_add(i3, i4)
         #
@@ -2352,8 +2353,7 @@
         setfield_gc(p1, i4, descr=nextdescr)
         setarrayitem_gc(p3, 0, i5, descr=arraydescr)
         escape_n()
-        ifoo = arraylen_gc(p3, descr=arraydescr) # killed by the backend
-        jump(p1, i1, i2, p3, i3)
+        jump(p1, i1, i2, p3)
         """
         self.optimize_loop(ops, expected, preamble)
 
@@ -2669,7 +2669,7 @@
         ops = """
         [p1, p2]
         p3 = getarrayitem_gc_r(p1, 0, descr=arraydescr2)
-        i4 = getfield_gc_pure_i(ConstPtr(myptr), descr=valuedescr)
+        i4 = getfield_gc_i(ConstPtr(myptr3), descr=valuedescr3)
         p5 = getarrayitem_gc_r(p1, 0, descr=arraydescr2)
         escape_n(p3)
         escape_n(i4)
@@ -2680,7 +2680,7 @@
         [p1, p2]
         p3 = getarrayitem_gc_r(p1, 0, descr=arraydescr2)
         escape_n(p3)
-        escape_n(5)
+        escape_n(7)
         escape_n(p3)
         jump(p1, p2)
         """
@@ -3302,8 +3302,8 @@
         [p8, p11, i24]
         p26 = new(descr=ssize)
         setfield_gc(p26, i24, descr=adescr)
-        i34 = getfield_gc_pure_i(p11, descr=abisdescr)
-        i35 = getfield_gc_pure_i(p26, descr=adescr)
+        i34 = getfield_gc_i(p11, descr=abisdescr)
+        i35 = getfield_gc_i(p26, descr=adescr)
         i36 = int_add_ovf(i34, i35)
         guard_no_overflow() []
         jump(p8, p11, i35)
@@ -3330,8 +3330,8 @@
         setfield_gc(p26, i24, descr=adescr)
         i28 = int_add(i17, 1)
         setfield_gc(p8, i28, descr=valuedescr)
-        i34 = getfield_gc_pure_i(p11, descr=valuedescr3)
-        i35 = getfield_gc_pure_i(p26, descr=adescr)
+        i34 = getfield_gc_i(p11, descr=valuedescr3)
+        i35 = getfield_gc_i(p26, descr=adescr)
         guard_nonnull(p12) []
         i36 = int_add_ovf(i34, i35)
         guard_no_overflow() []
@@ -3522,14 +3522,14 @@
     def test_residual_call_does_not_invalidate_immutable_caches(self):
         ops = """
         [p1]
-        i1 = getfield_gc_pure_i(p1, descr=valuedescr3)
+        i1 = getfield_gc_i(p1, descr=valuedescr3)
         i2 = call_i(i1, descr=writevalue3descr)
-        i3 = getfield_gc_pure_i(p1, descr=valuedescr3)
+        i3 = getfield_gc_i(p1, descr=valuedescr3)
         jump(p1)
         """
         expected_preamble = """
         [p1]
-        i1 = getfield_gc_pure_i(p1, descr=valuedescr3)
+        i1 = getfield_gc_i(p1, descr=valuedescr3)
         i2 = call_i(i1, descr=writevalue3descr)
         jump(p1, i1)
         """
@@ -4878,11 +4878,11 @@
     def test_add_sub_ovf_virtual_unroll(self):
         ops = """
         [p15]
-        i886 = getfield_gc_pure_i(p15, descr=valuedescr)
+        i886 = getfield_gc_i(p15, descr=valuedescr)
         i888 = int_sub_ovf(i886, 1)
         guard_no_overflow() []
         escape_n(i888)
-        i4360 = getfield_gc_pure_i(p15, descr=valuedescr)
+        i4360 = getfield_gc_i(p15, descr=valuedescr)
         i4362 = int_add_ovf(i4360, 1)
         guard_no_overflow() []
         i4360p = int_sub_ovf(i4362, 1)
@@ -4972,18 +4972,16 @@
     def test_pure(self):
         ops = """
         [p42]
-        p53 = getfield_gc_r(ConstPtr(myptr), descr=nextdescr)
-        p59 = getfield_gc_pure_r(p53, descr=valuedescr)
+        p53 = getfield_gc_r(ConstPtr(myptr3), descr=nextdescr3)
+        p59 = getfield_gc_r(p53, descr=valuedescr3)
         i61 = call_i(1, p59, descr=nonwritedescr)
         jump(p42)
         """
         expected = """
-        [p42, p59]
-        i61 = call_i(1, p59, descr=nonwritedescr)
-        jump(p42, p59)
-
-        """
-        self.node.value = 5
+        [p42]
+        i61 = call_i(1, 7, descr=nonwritedescr)
+        jump(p42)
+        """
         self.optimize_loop(ops, expected)
 
     def test_complains_getfieldpure_setfield(self):
@@ -4992,7 +4990,7 @@
         ops = """
         [p3]
         p1 = escape_r()
-        p2 = getfield_gc_pure_r(p1, descr=nextdescr)
+        p2 = getfield_gc_r(p1, descr=nextdescr)
         setfield_gc(p1, p3, descr=nextdescr)
         jump(p3)
         """
@@ -5002,7 +5000,7 @@
         ops = """
         [p3]
         p1 = escape_r()
-        p2 = getfield_gc_pure_r(p1, descr=nextdescr)
+        p2 = getfield_gc_r(p1, descr=nextdescr3)
         setfield_gc(p1, p3, descr=otherdescr)
         escape_n(p2)
         jump(p3)
@@ -5010,7 +5008,7 @@
         expected = """
         [p3]
         p1 = escape_r()
-        p2 = getfield_gc_pure_r(p1, descr=nextdescr)
+        p2 = getfield_gc_r(p1, descr=nextdescr3)
         setfield_gc(p1, p3, descr=otherdescr)
         escape_n(p2)
         jump(p3)
@@ -5021,7 +5019,7 @@
         ops = """
         []
         p1 = escape_r()
-        p2 = getfield_gc_pure_r(p1, descr=nextdescr)
+        p2 = getfield_gc_r(p1, descr=nextdescr)
         p3 = escape_r()
         setfield_gc(p3, p1, descr=nextdescr)
         jump()
@@ -6167,14 +6165,14 @@
     def test_bug_unroll_with_immutables(self):
         ops = """
         [p0]
-        i2 = getfield_gc_pure_i(p0, descr=immut_intval)
+        i2 = getfield_gc_i(p0, descr=immut_intval)
         p1 = new_with_vtable(descr=immut_descr)
         setfield_gc(p1, 1242, descr=immut_intval)
         jump(p1)
         """
         preamble = """
         [p0]
-        i2 = getfield_gc_pure_i(p0, descr=immut_intval)
+        i2 = getfield_gc_i(p0, descr=immut_intval)
         jump()
         """
         expected = """
@@ -7229,13 +7227,13 @@
         [p0, p1, i0]
         quasiimmut_field(p0, descr=quasiimmutdescr)
         guard_not_invalidated() []
-        i1 = getfield_gc_pure_i(p0, descr=quasifielddescr)
+        i1 = getfield_gc_i(p0, descr=quasifielddescr)
         escape_n(i1)
         jump(p1, p0, i1)
         """
         expected = """
         [p0, p1, i0]
-        i1 = getfield_gc_pure_i(p0, descr=quasifielddescr)
+        i1 = getfield_gc_i(p0, descr=quasifielddescr)
         escape_n(i1)
         jump(p1, p0, i1)
         """
@@ -7246,7 +7244,7 @@
         []
         quasiimmut_field(ConstPtr(quasiptr), descr=quasiimmutdescr)
         guard_not_invalidated() []
-        i1 = getfield_gc_pure_i(ConstPtr(quasiptr), descr=quasifielddescr)
+        i1 = getfield_gc_i(ConstPtr(quasiptr), descr=quasifielddescr)
         escape_n(i1)
         jump()
         """
@@ -7298,11 +7296,11 @@
         [i0a, i0b]
         quasiimmut_field(ConstPtr(quasiptr), descr=quasiimmutdescr)
         guard_not_invalidated() []
-        i1 = getfield_gc_pure_i(ConstPtr(quasiptr), descr=quasifielddescr)
+        i1 = getfield_gc_i(ConstPtr(quasiptr), descr=quasifielddescr)
         call_may_force_n(i0b, descr=mayforcevirtdescr)
         quasiimmut_field(ConstPtr(quasiptr), descr=quasiimmutdescr)
         guard_not_invalidated() []
-        i2 = getfield_gc_pure_i(ConstPtr(quasiptr), descr=quasifielddescr)
+        i2 = getfield_gc_i(ConstPtr(quasiptr), descr=quasifielddescr)
         i3 = escape_i(i1)
         i4 = escape_i(i2)
         jump(i3, i4)
@@ -7325,11 +7323,11 @@
         setfield_gc(p, 421, descr=quasifielddescr)
         quasiimmut_field(p, descr=quasiimmutdescr)
         guard_not_invalidated() []
-        i1 = getfield_gc_pure_i(p, descr=quasifielddescr)
+        i1 = getfield_gc_i(p, descr=quasifielddescr)
         call_may_force_n(i0b, descr=mayforcevirtdescr)
         quasiimmut_field(p, descr=quasiimmutdescr)
         guard_not_invalidated() []
-        i2 = getfield_gc_pure_i(p, descr=quasifielddescr)
+        i2 = getfield_gc_i(p, descr=quasifielddescr)
         i3 = escape_i(i1)
         i4 = escape_i(i2)
         jump(i3, i4)
@@ -7568,7 +7566,7 @@
     def test_forced_virtual_pure_getfield(self):
         ops = """
         [p0]
-        p1 = getfield_gc_pure_r(p0, descr=valuedescr)
+        p1 = getfield_gc_r(p0, descr=valuedescr3)
         jump(p1)
         """
         self.optimize_loop(ops, ops)
@@ -7578,7 +7576,7 @@
         p1 = new_with_vtable(descr=nodesize3)
         setfield_gc(p1, p0, descr=valuedescr3)
         escape_n(p1)
-        p2 = getfield_gc_pure_r(p1, descr=valuedescr3)
+        p2 = getfield_gc_r(p1, descr=valuedescr3)
         escape_n(p2)
         jump(p0)
         """
@@ -7852,14 +7850,14 @@
     def test_loopinvariant_getarrayitem_gc_pure(self):
         ops = """
         [p9, i1]
-        i843 = getarrayitem_gc_pure_i(p9, i1, descr=arraydescr)
+        i843 = getarrayitem_gc_pure_i(p9, i1, descr=arrayimmutdescr)
         call_n(i843, descr=nonwritedescr)
         jump(p9, i1)
         """
         expected = """
         [p9, i1, i843]
         call_n(i843, descr=nonwritedescr)
-        ifoo = arraylen_gc(p9, descr=arraydescr)
+        ifoo = arraylen_gc(p9, descr=arrayimmutdescr)
         jump(p9, i1, i843)
         """
         self.optimize_loop(ops, expected)
@@ -7868,7 +7866,7 @@
         ops = """
         [p0]
         p1 = getfield_gc_r(p0, descr=nextdescr)
-        p2 = getarrayitem_gc_pure_r(p1, 7, descr=gcarraydescr)
+        p2 = getarrayitem_gc_r(p1, 7, descr=gcarraydescr)
         call_n(p2, descr=nonwritedescr)
         jump(p0)
         """
@@ -7883,14 +7881,14 @@
         i1 = arraylen_gc(p1, descr=gcarraydescr)
         i2 = int_ge(i1, 8)
         guard_true(i2) []
-        p2 = getarrayitem_gc_pure_r(p1, 7, descr=gcarraydescr)
-        jump(p2, p1)
-        """
-        expected = """
-        [p0, p2, p1]
+        p2 = getarrayitem_gc_r(p1, 7, descr=gcarraydescr)
+        jump(p1, p2)
+        """
+        expected = """
+        [p0, p1, p2]
         call_n(p2, descr=nonwritedescr)
         i3 = arraylen_gc(p1, descr=gcarraydescr) # Should be killed by backend
-        jump(p0, p2, p1)
+        jump(p0, p1, p2)
         """
         self.optimize_loop(ops, expected, expected_short=short)
 
@@ -8065,7 +8063,7 @@
     def test_dont_mixup_equal_boxes(self):
         ops = """
         [p8]
-        i9 = getfield_gc_pure_i(p8, descr=valuedescr)
+        i9 = getfield_gc_i(p8, descr=valuedescr3)
         i10 = int_gt(i9, 0)
         guard_true(i10) []
         i29 = int_lshift(i9, 1)
@@ -8160,9 +8158,9 @@
         py.test.skip("would be fixed by make heap optimizer aware of virtual 
setfields")
         ops = """
         [p5, p8]
-        i9 = getfield_gc_pure_i(p5, descr=valuedescr)
+        i9 = getfield_gc_i(p5, descr=valuedescr)
         call_n(i9, descr=nonwritedescr)
-        i11 = getfield_gc_pure_i(p8, descr=valuedescr)
+        i11 = getfield_gc_i(p8, descr=valuedescr)
         i13 = int_add_ovf(i11, 1)
         guard_no_overflow() []
         p22 = new_with_vtable(descr=nodesize)
@@ -8201,14 +8199,14 @@
         ops = """
         [p0]
         p10 = getfield_gc_r(ConstPtr(myptr), descr=otherdescr)
_______________________________________________
pypy-commit mailing list
pypy-commit@python.org
https://mail.python.org/mailman/listinfo/pypy-commit

Reply via email to