[pypy-commit] pypy default: hg merge keys_with_hash

arigo noreply at buildbot.pypy.org
Tue Sep 1 17:23:48 CEST 2015


Author: Armin Rigo <arigo at tunes.org>
Branch: 
Changeset: r79350:872f1108d840
Date: 2015-09-01 14:50 +0200
http://bitbucket.org/pypy/pypy/changeset/872f1108d840/

Log:	hg merge keys_with_hash

	Improve the performance of dict.update() and a bunch of methods from
	sets, by reusing the hash value stored in one dict when inspecting
	or changing another dict with that key. This is done with the help
	of new xxx_with_hash() functions in objectmodel that work for any
	RPython dict or r_dict.

diff --git a/pypy/objspace/std/celldict.py b/pypy/objspace/std/celldict.py
--- a/pypy/objspace/std/celldict.py
+++ b/pypy/objspace/std/celldict.py
@@ -3,7 +3,7 @@
 indirection is introduced to make the version tag change less often.
 """
 
-from rpython.rlib import jit, rerased
+from rpython.rlib import jit, rerased, objectmodel
 
 from pypy.interpreter.baseobjspace import W_Root
 from pypy.objspace.std.dictmultiobject import (
@@ -162,8 +162,8 @@
     def getitervalues(self, w_dict):
         return self.unerase(w_dict.dstorage).itervalues()
 
-    def getiteritems(self, w_dict):
-        return self.unerase(w_dict.dstorage).iteritems()
+    def getiteritems_with_hash(self, w_dict):
+        return objectmodel.iteritems_with_hash(self.unerase(w_dict.dstorage))
 
     wrapkey = _wrapkey
 
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
@@ -511,7 +511,7 @@
     def getitervalues(self, w_dict):
         raise NotImplementedError
 
-    def getiteritems(self, w_dict):
+    def getiteritems_with_hash(self, w_dict):
         raise NotImplementedError
 
     has_iterreversed = False
@@ -634,7 +634,7 @@
     def getitervalues(self, w_dict):
         return iter([])
 
-    def getiteritems(self, w_dict):
+    def getiteritems_with_hash(self, w_dict):
         return iter([])
 
     def getiterreversed(self, w_dict):
@@ -751,11 +751,11 @@
 
     class IterClassItems(BaseItemIterator):
         def __init__(self, space, strategy, impl):
-            self.iterator = strategy.getiteritems(impl)
+            self.iterator = strategy.getiteritems_with_hash(impl)
             BaseIteratorImplementation.__init__(self, space, strategy, impl)
 
         def next_item_entry(self):
-            for key, value in self.iterator:
+            for key, value, keyhash in self.iterator:
                 return (wrapkey(self.space, key),
                         wrapvalue(self.space, value))
             else:
@@ -793,10 +793,10 @@
         # the logic is to call prepare_dict_update() after the first setitem():
         # it gives the w_updatedict a chance to switch its strategy.
         if 1:     # (preserve indentation)
-            iteritems = self.getiteritems(w_dict)
+            iteritemsh = self.getiteritems_with_hash(w_dict)
             if not same_strategy(self, w_updatedict):
                 # Different strategy.  Try to copy one item of w_dict
-                for key, value in iteritems:
+                for key, value, keyhash in iteritemsh:
                     w_key = wrapkey(self.space, key)
                     w_value = wrapvalue(self.space, value)
                     w_updatedict.setitem(w_key, w_value)
@@ -807,7 +807,7 @@
                 w_updatedict.strategy.prepare_update(w_updatedict, count)
                 # If the strategy is still different, continue the slow way
                 if not same_strategy(self, w_updatedict):
-                    for key, value in iteritems:
+                    for key, value, keyhash in iteritemsh:
                         w_key = wrapkey(self.space, key)
                         w_value = wrapvalue(self.space, value)
                         w_updatedict.setitem(w_key, w_value)
@@ -820,8 +820,8 @@
             # wrapping/unwrapping the key.
             assert setitem_untyped is not None
             dstorage = w_updatedict.dstorage
-            for key, value in iteritems:
-                setitem_untyped(self, dstorage, key, value)
+            for key, value, keyhash in iteritemsh:
+                setitem_untyped(self, dstorage, key, value, keyhash)
 
     def same_strategy(self, w_otherdict):
         return (setitem_untyped is not None and
@@ -945,8 +945,8 @@
     def getitervalues(self, w_dict):
         return self.unerase(w_dict.dstorage).itervalues()
 
-    def getiteritems(self, w_dict):
-        return self.unerase(w_dict.dstorage).iteritems()
+    def getiteritems_with_hash(self, w_dict):
+        return objectmodel.iteritems_with_hash(self.unerase(w_dict.dstorage))
 
     def getiterreversed(self, w_dict):
         return objectmodel.reversed_dict(self.unerase(w_dict.dstorage))
@@ -955,8 +955,9 @@
         objectmodel.prepare_dict_update(self.unerase(w_dict.dstorage),
                                         num_extra)
 
-    def setitem_untyped(self, dstorage, key, w_value):
-        self.unerase(dstorage)[key] = w_value
+    def setitem_untyped(self, dstorage, key, w_value, keyhash):
+        d = self.unerase(dstorage)
+        objectmodel.setitem_with_hash(d, key, keyhash, w_value)
 
 
 class ObjectDictStrategy(AbstractTypedStrategy, DictStrategy):
diff --git a/pypy/objspace/std/dictproxyobject.py b/pypy/objspace/std/dictproxyobject.py
--- a/pypy/objspace/std/dictproxyobject.py
+++ b/pypy/objspace/std/dictproxyobject.py
@@ -1,4 +1,5 @@
 from rpython.rlib import rerased
+from rpython.rlib.objectmodel import iteritems_with_hash
 
 from pypy.interpreter.error import OperationError, oefmt
 from pypy.objspace.std.dictmultiobject import (
@@ -103,8 +104,8 @@
         return self.unerase(w_dict.dstorage).dict_w.iterkeys()
     def getitervalues(self, w_dict):
         return self.unerase(w_dict.dstorage).dict_w.itervalues()
-    def getiteritems(self, w_dict):
-        return self.unerase(w_dict.dstorage).dict_w.iteritems()
+    def getiteritems_with_hash(self, w_dict):
+        return iteritems_with_hash(self.unerase(w_dict.dstorage).dict_w)
     def wrapkey(space, key):
         return space.wrap(key)
     def wrapvalue(space, value):
diff --git a/pypy/objspace/std/kwargsdict.py b/pypy/objspace/std/kwargsdict.py
--- a/pypy/objspace/std/kwargsdict.py
+++ b/pypy/objspace/std/kwargsdict.py
@@ -3,7 +3,7 @@
 Based on two lists containing unwrapped key value pairs.
 """
 
-from rpython.rlib import jit, rerased
+from rpython.rlib import jit, rerased, objectmodel
 
 from pypy.objspace.std.dictmultiobject import (
     BytesDictStrategy, DictStrategy, EmptyDictStrategy, ObjectDictStrategy,
@@ -165,13 +165,14 @@
     def getitervalues(self, w_dict):
         return iter(self.unerase(w_dict.dstorage)[1])
 
-    def getiteritems(self, w_dict):
-        return Zip(*self.unerase(w_dict.dstorage))
+    def getiteritems_with_hash(self, w_dict):
+        keys, values_w = self.unerase(w_dict.dstorage)
+        return ZipItemsWithHash(keys, values_w)
 
     wrapkey = _wrapkey
 
 
-class Zip(object):
+class ZipItemsWithHash(object):
     def __init__(self, list1, list2):
         assert len(list1) == len(list2)
         self.list1 = list1
@@ -186,6 +187,7 @@
         if i >= len(self.list1):
             raise StopIteration
         self.i = i + 1
-        return (self.list1[i], self.list2[i])
+        key = self.list1[i]
+        return (key, self.list2[i], objectmodel.compute_hash(key))
 
 create_iterator_classes(KwargsDictStrategy)
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
@@ -8,6 +8,8 @@
 from pypy.objspace.std.unicodeobject import W_UnicodeObject
 
 from rpython.rlib.objectmodel import r_dict
+from rpython.rlib.objectmodel import iterkeys_with_hash, contains_with_hash
+from rpython.rlib.objectmodel import setitem_with_hash, delitem_with_hash
 from rpython.rlib.rarithmetic import intmask, r_uint
 from rpython.rlib import rerased, jit
 
@@ -961,12 +963,12 @@
         return self.erase(result_dict)
 
     def _difference_unwrapped(self, w_set, w_other):
-        iterator = self.unerase(w_set.sstorage).iterkeys()
+        self_dict = self.unerase(w_set.sstorage)
         other_dict = self.unerase(w_other.sstorage)
         result_dict = self.get_empty_dict()
-        for key in iterator:
-            if key not in other_dict:
-                result_dict[key] = None
+        for key, keyhash in iterkeys_with_hash(self_dict):
+            if not contains_with_hash(other_dict, key, keyhash):
+                setitem_with_hash(result_dict, key, keyhash, None)
         return self.erase(result_dict)
 
     def _difference_base(self, w_set, w_other):
@@ -989,10 +991,10 @@
         if w_set.sstorage is w_other.sstorage:
             my_dict.clear()
             return
-        iterator = self.unerase(w_other.sstorage).iterkeys()
-        for key in iterator:
+        other_dict = self.unerase(w_other.sstorage)
+        for key, keyhash in iterkeys_with_hash(other_dict):
             try:
-                del my_dict[key]
+                delitem_with_hash(my_dict, key, keyhash)
             except KeyError:
                 pass
 
@@ -1020,12 +1022,12 @@
         d_new = self.get_empty_dict()
         d_this = self.unerase(w_set.sstorage)
         d_other = self.unerase(w_other.sstorage)
-        for key in d_other.keys():
-            if not key in d_this:
-                d_new[key] = None
-        for key in d_this.keys():
-            if not key in d_other:
-                d_new[key] = None
+        for key, keyhash in iterkeys_with_hash(d_other):
+            if not contains_with_hash(d_this, key, keyhash):
+                setitem_with_hash(d_new, key, keyhash, None)
+        for key, keyhash in iterkeys_with_hash(d_this):
+            if not contains_with_hash(d_other, key, keyhash):
+                setitem_with_hash(d_new, key, keyhash, None)
 
         storage = self.erase(d_new)
         return storage
@@ -1105,9 +1107,9 @@
         result = self.get_empty_dict()
         d_this = self.unerase(w_set.sstorage)
         d_other = self.unerase(w_other.sstorage)
-        for key in d_this:
-            if key in d_other:
-                result[key] = None
+        for key, keyhash in iterkeys_with_hash(d_this):
+            if contains_with_hash(d_other, key, keyhash):
+                setitem_with_hash(result, key, keyhash, None)
         return self.erase(result)
 
     def intersect(self, w_set, w_other):
@@ -1125,9 +1127,10 @@
         w_set.sstorage = storage
 
     def _issubset_unwrapped(self, w_set, w_other):
+        d_set = self.unerase(w_set.sstorage)
         d_other = self.unerase(w_other.sstorage)
-        for item in self.unerase(w_set.sstorage):
-            if not item in d_other:
+        for key, keyhash in iterkeys_with_hash(d_set):
+            if not contains_with_hash(d_other, key, keyhash):
                 return False
         return True
 
@@ -1152,8 +1155,8 @@
     def _isdisjoint_unwrapped(self, w_set, w_other):
         d_set = self.unerase(w_set.sstorage)
         d_other = self.unerase(w_other.sstorage)
-        for key in d_set:
-            if key in d_other:
+        for key, keyhash in iterkeys_with_hash(d_set):
+            if contains_with_hash(d_other, key, keyhash):
                 return False
         return True
 
diff --git a/rpython/annotator/binaryop.py b/rpython/annotator/binaryop.py
--- a/rpython/annotator/binaryop.py
+++ b/rpython/annotator/binaryop.py
@@ -520,26 +520,32 @@
         return dic1.__class__(dic1.dictdef.union(dic2.dictdef))
 
 
+def _dict_can_only_throw_keyerror(s_dct, *ignore):
+    if s_dct.dictdef.dictkey.custom_eq_hash:
+        return None    # r_dict: can throw anything
+    return [KeyError]
+
+def _dict_can_only_throw_nothing(s_dct, *ignore):
+    if s_dct.dictdef.dictkey.custom_eq_hash:
+        return None    # r_dict: can throw anything
+    return []          # else: no possible exception
+
+
 class __extend__(pairtype(SomeDict, SomeObject)):
 
-    def _can_only_throw(dic1, *ignore):
-        if dic1.dictdef.dictkey.custom_eq_hash:
-            return None
-        return [KeyError]
-
     def getitem((dic1, obj2)):
         dic1.dictdef.generalize_key(obj2)
         return dic1.dictdef.read_value()
-    getitem.can_only_throw = _can_only_throw
+    getitem.can_only_throw = _dict_can_only_throw_keyerror
 
     def setitem((dic1, obj2), s_value):
         dic1.dictdef.generalize_key(obj2)
         dic1.dictdef.generalize_value(s_value)
-    setitem.can_only_throw = _can_only_throw
+    setitem.can_only_throw = _dict_can_only_throw_nothing
 
     def delitem((dic1, obj2)):
         dic1.dictdef.generalize_key(obj2)
-    delitem.can_only_throw = _can_only_throw
+    delitem.can_only_throw = _dict_can_only_throw_keyerror
 
 
 class __extend__(pairtype(SomeTuple, SomeInteger)):
diff --git a/rpython/annotator/unaryop.py b/rpython/annotator/unaryop.py
--- a/rpython/annotator/unaryop.py
+++ b/rpython/annotator/unaryop.py
@@ -4,6 +4,7 @@
 
 from __future__ import absolute_import
 
+from rpython.tool.pairtype import pair
 from rpython.flowspace.operation import op
 from rpython.flowspace.model import const, Constant
 from rpython.flowspace.argument import CallSpec
@@ -11,11 +12,13 @@
     SomeString, SomeChar, SomeList, SomeDict, SomeTuple, SomeImpossibleValue,
     SomeUnicodeCodePoint, SomeInstance, SomeBuiltin, SomeBuiltinMethod,
     SomeFloat, SomeIterator, SomePBC, SomeNone, SomeType, s_ImpossibleValue,
-    s_Bool, s_None, unionof, add_knowntypedata,
+    s_Bool, s_None, s_Int, unionof, add_knowntypedata,
     HarmlesslyBlocked, SomeWeakRef, SomeUnicodeString, SomeByteArray)
 from rpython.annotator.bookkeeper import getbookkeeper, immutablevalue
 from rpython.annotator import builtin
 from rpython.annotator.binaryop import _clone ## XXX where to put this?
+from rpython.annotator.binaryop import _dict_can_only_throw_keyerror
+from rpython.annotator.binaryop import _dict_can_only_throw_nothing
 from rpython.annotator.model import AnnotatorError
 from rpython.annotator.argument import simple_args, complex_args
 
@@ -364,20 +367,19 @@
         raise AnnotatorError("%s: not proven to have non-negative stop" % error)
 
 
-def _can_only_throw(s_dct, *ignore):
-    if s_dct.dictdef.dictkey.custom_eq_hash:
-        return None    # r_dict: can throw anything
-    return []          # else: no possible exception
-
- at op.contains.register(SomeDict)
-def contains_SomeDict(annotator, dct, element):
-    annotator.annotation(dct).dictdef.generalize_key(annotator.annotation(element))
-    if annotator.annotation(dct)._is_empty():
+def dict_contains(s_dct, s_element):
+    s_dct.dictdef.generalize_key(s_element)
+    if s_dct._is_empty():
         s_bool = SomeBool()
         s_bool.const = False
         return s_bool
     return s_Bool
-contains_SomeDict.can_only_throw = _can_only_throw
+
+ at op.contains.register(SomeDict)
+def contains_SomeDict(annotator, dct, element):
+    return dict_contains(annotator.annotation(dct),
+                         annotator.annotation(element))
+contains_SomeDict.can_only_throw = _dict_can_only_throw_nothing
 
 class __extend__(SomeDict):
 
@@ -401,16 +403,22 @@
             return self.dictdef.read_key()
         elif variant == 'values':
             return self.dictdef.read_value()
-        elif variant == 'items':
+        elif variant == 'items' or variant == 'items_with_hash':
             s_key   = self.dictdef.read_key()
             s_value = self.dictdef.read_value()
             if (isinstance(s_key, SomeImpossibleValue) or
                 isinstance(s_value, SomeImpossibleValue)):
                 return s_ImpossibleValue
-            else:
+            elif variant == 'items':
                 return SomeTuple((s_key, s_value))
-        else:
-            raise ValueError
+            elif variant == 'items_with_hash':
+                return SomeTuple((s_key, s_value, s_Int))
+        elif variant == 'keys_with_hash':
+            s_key = self.dictdef.read_key()
+            if isinstance(s_key, SomeImpossibleValue):
+                return s_ImpossibleValue
+            return SomeTuple((s_key, s_Int))
+        raise ValueError(variant)
 
     def method_get(self, key, dfl):
         self.dictdef.generalize_key(key)
@@ -448,6 +456,12 @@
     def method_iteritems(self):
         return SomeIterator(self, 'items')
 
+    def method_iterkeys_with_hash(self):
+        return SomeIterator(self, 'keys_with_hash')
+
+    def method_iteritems_with_hash(self):
+        return SomeIterator(self, 'items_with_hash')
+
     def method_clear(self):
         pass
 
@@ -460,6 +474,22 @@
             self.dictdef.generalize_value(s_dfl)
         return self.dictdef.read_value()
 
+    def method_contains_with_hash(self, s_key, s_hash):
+        return dict_contains(self, s_key)
+    method_contains_with_hash.can_only_throw = _dict_can_only_throw_nothing
+
+    def method_setitem_with_hash(self, s_key, s_hash, s_value):
+        pair(self, s_key).setitem(s_value)
+    method_setitem_with_hash.can_only_throw = _dict_can_only_throw_nothing
+
+    def method_getitem_with_hash(self, s_key, s_hash):
+        return pair(self, s_key).getitem()
+    method_getitem_with_hash.can_only_throw = _dict_can_only_throw_keyerror
+
+    def method_delitem_with_hash(self, s_key, s_hash):
+        pair(self, s_key).delitem()
+    method_delitem_with_hash.can_only_throw = _dict_can_only_throw_keyerror
+
 @op.contains.register(SomeString)
 @op.contains.register(SomeUnicodeString)
 def contains_String(annotator, string, char):
diff --git a/rpython/rlib/objectmodel.py b/rpython/rlib/objectmodel.py
--- a/rpython/rlib/objectmodel.py
+++ b/rpython/rlib/objectmodel.py
@@ -788,6 +788,71 @@
         d = d.keys()
     return reversed(d)
 
+def _expected_hash(d, key):
+    if isinstance(d, r_dict):
+        return d.key_hash(key)
+    else:
+        return compute_hash(key)
+
+def _iterkeys_with_hash_untranslated(d):
+    for k in d:
+        yield (k, _expected_hash(d, k))
+
+ at specialize.call_location()
+def iterkeys_with_hash(d):
+    """Iterates (key, hash) pairs without recomputing the hash."""
+    if not we_are_translated():
+        return _iterkeys_with_hash_untranslated(d)
+    return d.iterkeys_with_hash()
+
+def _iteritems_with_hash_untranslated(d):
+    for k, v in d.iteritems():
+        yield (k, v, _expected_hash(d, k))
+
+ at specialize.call_location()
+def iteritems_with_hash(d):
+    """Iterates (key, value, keyhash) triples without recomputing the hash."""
+    if not we_are_translated():
+        return _iteritems_with_hash_untranslated(d)
+    return d.iteritems_with_hash()
+
+ at specialize.call_location()
+def contains_with_hash(d, key, h):
+    """Same as 'key in d'.  The extra argument is the hash.  Use this only
+    if you got the hash just now from some other ..._with_hash() function."""
+    if not we_are_translated():
+        assert _expected_hash(d, key) == h
+        return key in d
+    return d.contains_with_hash(key, h)
+
+ at specialize.call_location()
+def setitem_with_hash(d, key, h, value):
+    """Same as 'd[key] = value'.  The extra argument is the hash.  Use this only
+    if you got the hash just now from some other ..._with_hash() function."""
+    if not we_are_translated():
+        assert _expected_hash(d, key) == h
+        d[key] = value
+        return
+    d.setitem_with_hash(key, h, value)
+
+ at specialize.call_location()
+def getitem_with_hash(d, key, h):
+    """Same as 'd[key]'.  The extra argument is the hash.  Use this only
+    if you got the hash just now from some other ..._with_hash() function."""
+    if not we_are_translated():
+        assert _expected_hash(d, key) == h
+        return d[key]
+    return d.getitem_with_hash(key, h)
+
+ at specialize.call_location()
+def delitem_with_hash(d, key, h):
+    """Same as 'del d[key]'.  The extra argument is the hash.  Use this only
+    if you got the hash just now from some other ..._with_hash() function."""
+    if not we_are_translated():
+        assert _expected_hash(d, key) == h
+        del d[key]
+        return
+    d.delitem_with_hash(key, h)
 
 # ____________________________________________________________
 
diff --git a/rpython/rlib/test/test_objectmodel.py b/rpython/rlib/test/test_objectmodel.py
--- a/rpython/rlib/test/test_objectmodel.py
+++ b/rpython/rlib/test/test_objectmodel.py
@@ -592,6 +592,97 @@
     r = interpret(f, [29])
     assert r == 1
 
+def test_iterkeys_with_hash():
+    def f(i):
+        d = {i+.0: 5, i+.5: 6}
+        total = 0
+        for k, h in iterkeys_with_hash(d):
+            total += k * h
+        total -= (i + 0.0) * compute_hash(i + 0.0)
+        total -= (i + 0.5) * compute_hash(i + 0.5)
+        return total
+
+    assert f(29) == 0.0
+    r = interpret(f, [29])
+    assert r == 0.0
+
+def test_iteritems_with_hash():
+    def f(i):
+        d = {i+.0: 5, i+.5: 6}
+        total = 0
+        for k, v, h in iteritems_with_hash(d):
+            total += k * h * v
+        total -= (i + 0.0) * compute_hash(i + 0.0) * 5
+        total -= (i + 0.5) * compute_hash(i + 0.5) * 6
+        return total
+
+    assert f(29) == 0.0
+    r = interpret(f, [29])
+    assert r == 0.0
+
+def test_contains_with_hash():
+    def f(i):
+        d = {i+.5: 5}
+        assert contains_with_hash(d, i+.5, compute_hash(i+.5))
+        assert not contains_with_hash(d, i+.3, compute_hash(i+.3))
+        return 0
+
+    f(29)
+    interpret(f, [29])
+
+def test_setitem_with_hash():
+    def f(i):
+        d = {}
+        setitem_with_hash(d, i+.5, compute_hash(i+.5), 42)
+        setitem_with_hash(d, i+.6, compute_hash(i+.6), -612)
+        return d[i+.5]
+
+    assert f(29) == 42
+    res = interpret(f, [27])
+    assert res == 42
+
+def test_getitem_with_hash():
+    def f(i):
+        d = {i+.5: 42, i+.6: -612}
+        return getitem_with_hash(d, i+.5, compute_hash(i+.5))
+
+    assert f(29) == 42
+    res = interpret(f, [27])
+    assert res == 42
+
+def test_delitem_with_hash():
+    def f(i):
+        d = {i+.5: 42, i+.6: -612}
+        delitem_with_hash(d, i+.5, compute_hash(i+.5))
+        try:
+            delitem_with_hash(d, i+.5, compute_hash(i+.5))
+        except KeyError:
+            pass
+        else:
+            raise AssertionError
+        return 0
+
+    f(29)
+    interpret(f, [27])
+
+def test_rdict_with_hash():
+    def f(i):
+        d = r_dict(strange_key_eq, strange_key_hash)
+        h = strange_key_hash("abc")
+        assert h == strange_key_hash("aXX") and strange_key_eq("abc", "aXX")
+        setitem_with_hash(d, "abc", h, i)
+        assert getitem_with_hash(d, "aXX", h) == i
+        try:
+            getitem_with_hash(d, "bYY", strange_key_hash("bYY"))
+        except KeyError:
+            pass
+        else:
+            raise AssertionError
+        return 0
+
+    assert f(29) == 0
+    interpret(f, [27])
+
 def test_import_from_mixin():
     class M:    # old-style
         def f(self): pass
diff --git a/rpython/rtyper/lltypesystem/rordereddict.py b/rpython/rtyper/lltypesystem/rordereddict.py
--- a/rpython/rtyper/lltypesystem/rordereddict.py
+++ b/rpython/rtyper/lltypesystem/rordereddict.py
@@ -335,6 +335,14 @@
         hop.exception_cannot_occur()
         return DictIteratorRepr(self, "items").newiter(hop)
 
+    def rtype_method_iterkeys_with_hash(self, hop):
+        hop.exception_cannot_occur()
+        return DictIteratorRepr(self, "keys_with_hash").newiter(hop)
+
+    def rtype_method_iteritems_with_hash(self, hop):
+        hop.exception_cannot_occur()
+        return DictIteratorRepr(self, "items_with_hash").newiter(hop)
+
     def rtype_method_clear(self, hop):
         v_dict, = hop.inputargs(self)
         hop.exception_cannot_occur()
@@ -358,6 +366,41 @@
         v_res = hop.gendirectcall(target, *v_args)
         return self.recast_value(hop.llops, v_res)
 
+    def rtype_method_contains_with_hash(self, hop):
+        v_dict, v_key, v_hash = hop.inputargs(self, self.key_repr,
+                                              lltype.Signed)
+        hop.exception_is_here()
+        return hop.gendirectcall(ll_dict_contains_with_hash,
+                                 v_dict, v_key, v_hash)
+
+    def rtype_method_setitem_with_hash(self, hop):
+        v_dict, v_key, v_hash, v_value = hop.inputargs(
+            self, self.key_repr, lltype.Signed, self.value_repr)
+        if self.custom_eq_hash:
+            hop.exception_is_here()
+        else:
+            hop.exception_cannot_occur()
+        hop.gendirectcall(ll_dict_setitem_with_hash,
+                          v_dict, v_key, v_hash, v_value)
+
+    def rtype_method_getitem_with_hash(self, hop):
+        v_dict, v_key, v_hash = hop.inputargs(
+            self, self.key_repr, lltype.Signed)
+        if not self.custom_eq_hash:
+            hop.has_implicit_exception(KeyError)  # record that we know about it
+        hop.exception_is_here()
+        v_res = hop.gendirectcall(ll_dict_getitem_with_hash,
+                                  v_dict, v_key, v_hash)
+        return self.recast_value(hop.llops, v_res)
+
+    def rtype_method_delitem_with_hash(self, hop):
+        v_dict, v_key, v_hash = hop.inputargs(
+            self, self.key_repr, lltype.Signed)
+        if not self.custom_eq_hash:
+            hop.has_implicit_exception(KeyError)  # record that we know about it
+        hop.exception_is_here()
+        hop.gendirectcall(ll_dict_delitem_with_hash, v_dict, v_key, v_hash)
+
 class __extend__(pairtype(OrderedDictRepr, rmodel.Repr)):
 
     def rtype_getitem((r_dict, r_key), hop):
@@ -373,7 +416,7 @@
         if not r_dict.custom_eq_hash:
             hop.has_implicit_exception(KeyError)   # record that we know about it
         hop.exception_is_here()
-        return hop.gendirectcall(ll_dict_delitem, v_dict, v_key)
+        hop.gendirectcall(ll_dict_delitem, v_dict, v_key)
 
     def rtype_setitem((r_dict, r_key), hop):
         v_dict, v_key, v_value = hop.inputargs(r_dict, r_dict.key_repr, r_dict.value_repr)
@@ -541,16 +584,21 @@
     return bool(d) and d.num_live_items != 0
 
 def ll_dict_getitem(d, key):
-    index = d.lookup_function(d, key, d.keyhash(key), FLAG_LOOKUP)
+    return ll_dict_getitem_with_hash(d, key, d.keyhash(key))
+
+def ll_dict_getitem_with_hash(d, key, hash):
+    index = d.lookup_function(d, key, hash, FLAG_LOOKUP)
     if index >= 0:
         return d.entries[index].value
     else:
         raise KeyError
 
 def ll_dict_setitem(d, key, value):
-    hash = d.keyhash(key)
+    ll_dict_setitem_with_hash(d, key, d.keyhash(key), value)
+
+def ll_dict_setitem_with_hash(d, key, hash, value):
     index = d.lookup_function(d, key, hash, FLAG_STORE)
-    return _ll_dict_setitem_lookup_done(d, key, value, hash, index)
+    _ll_dict_setitem_lookup_done(d, key, value, hash, index)
 
 # It may be safe to look inside always, it has a few branches though, and their
 # frequencies needs to be investigated.
@@ -731,7 +779,10 @@
 
 
 def ll_dict_delitem(d, key):
-    index = d.lookup_function(d, key, d.keyhash(key), FLAG_DELETE)
+    ll_dict_delitem_with_hash(d, key, d.keyhash(key))
+
+def ll_dict_delitem_with_hash(d, key, hash):
+    index = d.lookup_function(d, key, hash, FLAG_DELETE)
     if index < 0:
         raise KeyError
     _ll_dict_del(d, index)
@@ -1214,7 +1265,10 @@
 ll_dict_items  = _make_ll_keys_values_items('items')
 
 def ll_dict_contains(d, key):
-    i = d.lookup_function(d, key, d.keyhash(key), FLAG_LOOKUP)
+    return ll_dict_contains_with_hash(d, key, d.keyhash(key))
+
+def ll_dict_contains_with_hash(d, key, hash):
+    i = d.lookup_function(d, key, hash, FLAG_LOOKUP)
     return i >= 0
 
 def _ll_getnextitem(dic):
diff --git a/rpython/rtyper/rdict.py b/rpython/rtyper/rdict.py
--- a/rpython/rtyper/rdict.py
+++ b/rpython/rtyper/rdict.py
@@ -72,20 +72,14 @@
         return hop.gendirectcall(self.ll_dictiter, citerptr, v_dict)
 
     def rtype_next(self, hop):
-        variant = self.variant
         v_iter, = hop.inputargs(self)
         # record that we know about these two possible exceptions
         hop.has_implicit_exception(StopIteration)
         hop.has_implicit_exception(RuntimeError)
         hop.exception_is_here()
         v_index = hop.gendirectcall(self._ll_dictnext, v_iter)
-        if variant == 'items' and hop.r_result.lowleveltype != lltype.Void:
-            # this allocates the tuple for the result, directly in the function
-            # where it will be used (likely).  This will let it be removed.
-            c1 = hop.inputconst(lltype.Void, hop.r_result.lowleveltype.TO)
-            cflags = hop.inputconst(lltype.Void, {'flavor': 'gc'})
-            v_result = hop.genop('malloc', [c1, cflags],
-                                 resulttype = hop.r_result.lowleveltype)
+        #
+        # read 'iter.dict.entries'
         DICT = self.lowleveltype.TO.dict
         c_dict = hop.inputconst(lltype.Void, 'dict')
         v_dict = hop.genop('getfield', [v_iter, c_dict], resulttype=DICT)
@@ -93,32 +87,61 @@
         c_entries = hop.inputconst(lltype.Void, 'entries')
         v_entries = hop.genop('getfield', [v_dict, c_entries],
                               resulttype=ENTRIES)
-        if variant != 'values':
-            KEY = ENTRIES.TO.OF.key
-            c_key = hop.inputconst(lltype.Void, 'key')
-            v_key = hop.genop('getinteriorfield', [v_entries, v_index, c_key],
-                              resulttype=KEY)
-        if variant != 'keys' and variant != 'reversed':
-            VALUE = ENTRIES.TO.OF.value
-            c_value = hop.inputconst(lltype.Void, 'value')
-            v_value = hop.genop('getinteriorfield', [v_entries,v_index,c_value],
-                                resulttype=VALUE)
-        if variant == 'keys' or variant == 'reversed':
-            return self.r_dict.recast_key(hop.llops, v_key)
-        elif variant == 'values':
-            return self.r_dict.recast_value(hop.llops, v_value)
-        elif hop.r_result.lowleveltype == lltype.Void:
+        # call the correct variant_*() method
+        method = getattr(self, 'variant_' + self.variant)
+        return method(hop, ENTRIES, v_entries, v_index)
+
+    def get_tuple_result(self, hop, items_v):
+        # this allocates the tuple for the result, directly in the function
+        # where it will be used (likely).  This will let it be removed.
+        if hop.r_result.lowleveltype is lltype.Void:
             return hop.inputconst(lltype.Void, None)
-        else:
-            assert variant == 'items'
-            ITEM0 = v_result.concretetype.TO.item0
-            ITEM1 = v_result.concretetype.TO.item1
-            if ITEM0 != v_key.concretetype:
-                v_key = hop.genop('cast_pointer', [v_key], resulttype=ITEM0)
-            if ITEM1 != v_value.concretetype:
-                v_value = hop.genop('cast_pointer', [v_value], resulttype=ITEM1)
-            c_item0 = hop.inputconst(lltype.Void, 'item0')
-            c_item1 = hop.inputconst(lltype.Void, 'item1')
-            hop.genop('setfield', [v_result, c_item0, v_key])
-            hop.genop('setfield', [v_result, c_item1, v_value])
-            return v_result
+        c1 = hop.inputconst(lltype.Void, hop.r_result.lowleveltype.TO)
+        cflags = hop.inputconst(lltype.Void, {'flavor': 'gc'})
+        v_result = hop.genop('malloc', [c1, cflags],
+                             resulttype = hop.r_result.lowleveltype)
+        for i, v_item in enumerate(items_v):
+            ITEM = getattr(v_result.concretetype.TO, 'item%d' % i)
+            if ITEM != v_item.concretetype:
+                assert isinstance(ITEM, lltype.Ptr)
+                v_item = hop.genop('cast_pointer', [v_item], resulttype=ITEM)
+            c_item = hop.inputconst(lltype.Void, 'item%d' % i)
+            hop.genop('setfield', [v_result, c_item, v_item])
+        return v_result
+
+    def variant_keys(self, hop, ENTRIES, v_entries, v_index):
+        KEY = ENTRIES.TO.OF.key
+        c_key = hop.inputconst(lltype.Void, 'key')
+        v_key = hop.genop('getinteriorfield', [v_entries, v_index, c_key],
+                          resulttype=KEY)
+        return self.r_dict.recast_key(hop.llops, v_key)
+
+    variant_reversed = variant_keys
+
+    def variant_values(self, hop, ENTRIES, v_entries, v_index):
+        VALUE = ENTRIES.TO.OF.value
+        c_value = hop.inputconst(lltype.Void, 'value')
+        v_value = hop.genop('getinteriorfield', [v_entries,v_index,c_value],
+                            resulttype=VALUE)
+        return self.r_dict.recast_value(hop.llops, v_value)
+
+    def variant_items(self, hop, ENTRIES, v_entries, v_index):
+        v_key = self.variant_keys(hop, ENTRIES, v_entries, v_index)
+        v_value = self.variant_values(hop, ENTRIES, v_entries, v_index)
+        return self.get_tuple_result(hop, (v_key, v_value))
+
+    def variant_hashes(self, hop, ENTRIES, v_entries, v_index):
+        # there is not really a variant 'hashes', but this method is
+        # convenient for the following variants
+        return hop.gendirectcall(ENTRIES.TO.hash, v_entries, v_index)
+
+    def variant_keys_with_hash(self, hop, ENTRIES, v_entries, v_index):
+        v_key = self.variant_keys(hop, ENTRIES, v_entries, v_index)
+        v_hash = self.variant_hashes(hop, ENTRIES, v_entries, v_index)
+        return self.get_tuple_result(hop, (v_key, v_hash))
+
+    def variant_items_with_hash(self, hop, ENTRIES, v_entries, v_index):
+        v_key = self.variant_keys(hop, ENTRIES, v_entries, v_index)
+        v_value = self.variant_values(hop, ENTRIES, v_entries, v_index)
+        v_hash = self.variant_hashes(hop, ENTRIES, v_entries, v_index)
+        return self.get_tuple_result(hop, (v_key, v_value, v_hash))


More information about the pypy-commit mailing list