[pypy-svn] r17455 - in pypy/dist/pypy/rpython: . test

arigo at codespeak.net arigo at codespeak.net
Sun Sep 11 13:32:58 CEST 2005


Author: arigo
Date: Sun Sep 11 13:32:56 2005
New Revision: 17455

Modified:
   pypy/dist/pypy/rpython/llinterp.py
   pypy/dist/pypy/rpython/objectmodel.py
   pypy/dist/pypy/rpython/rbuiltin.py
   pypy/dist/pypy/rpython/rdict.py
   pypy/dist/pypy/rpython/rpbc.py
   pypy/dist/pypy/rpython/test/test_objectmodel.py
   pypy/dist/pypy/rpython/test/test_rconstantdict.py
Log:
Progress on test_rtype_r_dict, but not passing yet.

* the r_dict becomes a GcStruct with two new fields, fnkeyeq and fnkeyhash,
  that are function pointers (which usually means just Voids, if the functions
  in question are constant).
* we pass a test about a constant prebuilt r_dict (thanks pedronis for hints).
* ll_dict_lookup() is slightly verbose now :-(
* fix in FunctionsPBCRepr.convert_const(), which should return None for Voids.
* llinterp prints more infos about the LLExceptions it got -- at least the
  exception's class name.
* the _r_dictkey now caches the hash -- needed for rdict.convert_const().



Modified: pypy/dist/pypy/rpython/llinterp.py
==============================================================================
--- pypy/dist/pypy/rpython/llinterp.py	(original)
+++ pypy/dist/pypy/rpython/llinterp.py	Sun Sep 11 13:32:56 2005
@@ -39,6 +39,11 @@
         llframe = LLFrame(graph, args, self)
         try:
             return llframe.eval()
+        except LLException, e:
+            etype, evalue = e.args
+            print "LLEXCEPTION:", etype.name
+            self.print_traceback()
+            raise
         except Exception, e:
             print "AN ERROR OCCURED:", e
             self.print_traceback()

Modified: pypy/dist/pypy/rpython/objectmodel.py
==============================================================================
--- pypy/dist/pypy/rpython/objectmodel.py	(original)
+++ pypy/dist/pypy/rpython/objectmodel.py	Sun Sep 11 13:32:56 2005
@@ -101,24 +101,20 @@
     def clear(self):
         self._dict.clear()
 
-    def key_eq(self, key1, key2):
-        "Called to compare two keys.  Can be overridden in subclasses."
-        return key1 == key2
-
-    def key_hash(self, key):
-        "Called to compute the hash of a key.  Can be overridden in subclasses."
-        return hash(key)
-
     def __repr__(self):
         "Representation for debugging purposes."
         return 'r_dict(%r)' % (dict(self.items()),)
 
+    def __hash__(self):
+        raise TypeError("cannot hash r_dict instances")
+
 
 class _r_dictkey(object):
-    __slots__ = ['dic', 'key']
+    __slots__ = ['dic', 'key', 'hash']
     def __init__(self, dic, key):
         self.dic = dic
         self.key = key
+        self.hash = dic.key_hash(key)
     def __eq__(self, other):
         if not isinstance(other, _r_dictkey):
             return NotImplemented
@@ -128,4 +124,4 @@
             return NotImplemented
         return not self.dic.key_eq(self.key, other.key)
     def __hash__(self):
-        return self.dic.key_hash(self.key)
+        return self.hash

Modified: pypy/dist/pypy/rpython/rbuiltin.py
==============================================================================
--- pypy/dist/pypy/rpython/rbuiltin.py	(original)
+++ pypy/dist/pypy/rpython/rbuiltin.py	Sun Sep 11 13:32:56 2005
@@ -9,6 +9,7 @@
 from pypy.rpython.robject import pyobj_repr
 from pypy.rpython.rfloat import float_repr, FloatRepr
 from pypy.rpython.rbool import bool_repr
+from pypy.rpython.rdict import rtype_r_dict
 from pypy.rpython import rclass
 from pypy.tool import sourcetools
 
@@ -154,6 +155,8 @@
 
 #def rtype_builtin_xrange(hop): see rrange.py
 
+#def rtype_r_dict(hop): see rdict.py
+
 def rtype_intmask(hop):
     vlist = hop.inputargs(lltype.Signed)
     return vlist[0]
@@ -305,6 +308,7 @@
 BUILTIN_TYPER[lltype.runtime_type_info] = rtype_runtime_type_info
 BUILTIN_TYPER[rarithmetic.intmask] = rtype_intmask
 BUILTIN_TYPER[rarithmetic.r_uint] = rtype_r_uint
+BUILTIN_TYPER[objectmodel.r_dict] = rtype_r_dict
 BUILTIN_TYPER[objectmodel.instantiate] = rtype_instantiate
 BUILTIN_TYPER[objectmodel.we_are_translated] = rtype_we_are_translated
 

Modified: pypy/dist/pypy/rpython/rdict.py
==============================================================================
--- pypy/dist/pypy/rpython/rdict.py	(original)
+++ pypy/dist/pypy/rpython/rdict.py	Sun Sep 11 13:32:56 2005
@@ -3,8 +3,10 @@
 from pypy.objspace.flow.model import Constant
 from pypy.rpython import rmodel, lltype
 from pypy.rpython.rarithmetic import r_uint
+from pypy.rpython.objectmodel import hlinvoke
 from pypy.rpython import rlist
 from pypy.rpython import robject
+from pypy.rpython import objectmodel
 
 # ____________________________________________________________
 #
@@ -17,7 +19,7 @@
 #      the array should be inlined and num_pristine_entries is not needed.
 #
 #    struct dictentry {
-#        DICTSTR key;
+#        DICTKEY key;
 #        bool valid;      # to mark if the entry is filled
 #        bool everused;   # to mark if the entry is or has ever been filled
 #        DICTVALUE value;
@@ -26,7 +28,9 @@
 #    struct dicttable {
 #        int num_items;
 #        int num_pristine_entries;  # never used entries
-#        Array *entries; 
+#        Array *entries;
+#        (Function DICTKEY, DICTKEY -> bool) *fnkeyeq;
+#        (Function DICTKEY -> int) *fnkeyhash;
 #    }
 #
 #
@@ -41,17 +45,24 @@
             s_value.__class__ is annmodel.SomeObject and s_value.knowntype == object):
             return robject.pyobj_repr
         else:
+            if dictkey.custom_eq_hash:
+                custom_eq_hash = lambda: (rtyper.getrepr(dictkey.s_rdict_eqfn),
+                                          rtyper.getrepr(dictkey.s_rdict_hashfn))
+            else:
+                custom_eq_hash = None
             return DictRepr(lambda: rtyper.getrepr(s_key),
                             lambda: rtyper.getrepr(s_value),
                             dictkey,
-                            dictvalue)
+                            dictvalue,
+                            custom_eq_hash)
 
     def rtyper_makekey(self):
         return (self.__class__, self.dictdef.dictkey, self.dictdef.dictvalue)
 
 class DictRepr(rmodel.Repr):
 
-    def __init__(self, key_repr, value_repr, dictkey=None, dictvalue=None):
+    def __init__(self, key_repr, value_repr, dictkey=None, dictvalue=None,
+                 custom_eq_hash=None):
         self.DICT = lltype.GcForwardReference()
         self.lowleveltype = lltype.Ptr(self.DICT)
         if not isinstance(key_repr, rmodel.Repr):  # not computed yet, done by setup()
@@ -67,6 +78,8 @@
         self.dictkey = dictkey
         self.dictvalue = dictvalue
         self.dict_cache = {}
+        self.custom_eq_hash = custom_eq_hash is not None
+        self._custom_eq_hash_repr = custom_eq_hash
         # setup() needs to be called to finish this initialization
 
     def _setup_repr(self):
@@ -83,11 +96,15 @@
                                 ("everused", lltype.Bool),
                                 ("value", self.DICTVALUE))
             self.DICTENTRYARRAY = lltype.GcArray(self.DICTENTRY)
-            self.DICT.become(lltype.GcStruct("dicttable",
-                                ("num_items", lltype.Signed), 
+            fields =          [ ("num_items", lltype.Signed),
                                 ("num_pristine_entries", lltype.Signed), 
-                                ("entries", lltype.Ptr(self.DICTENTRYARRAY))))
-        if 'll_keyhash' not in self.__dict__:
+                                ("entries", lltype.Ptr(self.DICTENTRYARRAY)) ]
+            if self.custom_eq_hash:
+                self.r_rdict_eqfn, self.r_rdict_hashfn = self._custom_eq_hash_repr()
+                fields.extend([ ("fnkeyeq", self.r_rdict_eqfn.lowleveltype),
+                                ("fnkeyhash", self.r_rdict_hashfn.lowleveltype) ])
+            self.DICT.become(lltype.GcStruct("dicttable", *fields))
+        if 'll_keyhash' not in self.__dict__ and not self.custom_eq_hash:
             # figure out which functions must be used to hash and compare keys
             self.ll_keyeq   = self.key_repr.get_ll_eq_function()   # can be None
             self.ll_keyhash = self.key_repr.get_ll_hash_function()
@@ -97,22 +114,49 @@
         #dictobj = getattr(dictobj, '__self__', dictobj) 
         if dictobj is None:
             return nullptr(self.DICT)
-        if not isinstance(dictobj, dict):
+        if not isinstance(dictobj, (dict, objectmodel.r_dict)):
             raise TyperError("expected a dict: %r" % (dictobj,))
         try:
             key = Constant(dictobj)
             return self.dict_cache[key]
         except KeyError:
             self.setup()
-            l_dict = ll_newdict(self.lowleveltype)
-            self.dict_cache[key] = l_dict 
-            r_key = self.key_repr
-            r_value = self.value_repr
-            for dictkey, dictvalue in dictobj.items():
-                llkey = r_key.convert_const(dictkey)
-                llvalue = r_value.convert_const(dictvalue)
-                ll_dict_setitem(l_dict, llkey, llvalue, self)
-            return l_dict
+            if isinstance(dictobj, objectmodel.r_dict):
+                l_eqfn   = self.r_rdict_eqfn  .convert_const(dictobj.key_eq)
+                l_hashfn = self.r_rdict_hashfn.convert_const(dictobj.key_hash)
+                l_dict = ll_newdict_custom_eq_hash(l_eqfn, l_hashfn, self)
+                # a dummy object with ll_keyeq and ll_keyhash methods to
+                # pass to ll_dict_setitem()
+                class Dummy:
+                    custom_eq_hash = False
+                    def ll_keyeq(self, key1, key2):
+                        # theory: ll_dict_lookup() will only see new items,
+                        # which are never equal to any existing one
+                        return False
+                    def ll_keyhash(self, key):
+                        return self.currenthash
+
+                self.dict_cache[key] = l_dict 
+                r_key = self.key_repr
+                r_value = self.value_repr
+                for dictkeycontainer, dictvalue in dictobj._dict.items():
+                    dummy = Dummy()
+                    dummy.currenthash = dictkeycontainer.hash
+                    llkey = r_key.convert_const(dictkeycontainer.key)
+                    llvalue = r_value.convert_const(dictvalue)
+                    ll_dict_setitem(l_dict, llkey, llvalue, dummy)
+                return l_dict
+
+            else:
+                l_dict = ll_newdict(self)
+                self.dict_cache[key] = l_dict 
+                r_key = self.key_repr
+                r_value = self.value_repr
+                for dictkey, dictvalue in dictobj.items():
+                    llkey = r_key.convert_const(dictkey)
+                    llvalue = r_value.convert_const(dictvalue)
+                    ll_dict_setitem(l_dict, llkey, llvalue, self)
+                return l_dict
 
     def rtype_len(self, hop):
         v_dict, = hop.inputargs(self)
@@ -134,8 +178,9 @@
 
     def rtype_method_copy(self, hop):
         v_dict, = hop.inputargs(self)
+        crepr = hop.inputconst(lltype.Void, self)
         hop.exception_cannot_occur()
-        return hop.gendirectcall(ll_copy, v_dict)
+        return hop.gendirectcall(ll_copy, v_dict, crepr)
 
     def rtype_method_update(self, hop):
         v_dic1, v_dic2 = hop.inputargs(self, self)
@@ -301,7 +346,10 @@
 PERTURB_SHIFT = 5
 
 def ll_dict_lookup(d, key, dictrepr):
-    hash = dictrepr.ll_keyhash(key)
+    if dictrepr.custom_eq_hash:
+        hash = hlinvoke(dictrepr.r_rdict_hashfn, d.fnkeyhash, key)
+    else:
+        hash = dictrepr.ll_keyhash(key)
     entries = d.entries
     mask = len(entries) - 1
     i = r_uint(hash & mask) 
@@ -313,7 +361,11 @@
     if entry.valid:
         if entry.key == key:
             return entry   # found the entry
-        if dictrepr.ll_keyeq is not None and dictrepr.ll_keyeq(entry.key, key):
+        if dictrepr.custom_eq_hash:
+            res = hlinvoke(dictrepr.r_rdict_eqfn, d.fnkeyeq, entry.key, key)
+        else:
+            res = dictrepr.ll_keyeq is not None and dictrepr.ll_keyeq(entry.key, key)
+        if res:
             return entry   # found the entry
         freeslot = lltype.nullptr(lltype.typeOf(entry).TO)
     elif entry.everused:
@@ -332,7 +384,11 @@
         elif entry.valid:
             if entry.key == key:
                 return entry
-            if dictrepr.ll_keyeq is not None and dictrepr.ll_keyeq(entry.key, key):
+            if dictrepr.custom_eq_hash:
+                res = hlinvoke(dictrepr.r_rdict_eqfn, d.fnkeyeq, entry.key, key)
+            else:
+                res = dictrepr.ll_keyeq is not None and dictrepr.ll_keyeq(entry.key, key)
+            if res:
                 return entry
         elif not freeslot:
             freeslot = entry 
@@ -341,22 +397,52 @@
 # ____________________________________________________________
 #
 #  Irregular operations.
+
 DICT_INITSIZE = 8
 
-def ll_newdict(DICTPTR):
-    d = lltype.malloc(DICTPTR.TO)
-    d.entries = lltype.malloc(DICTPTR.TO.entries.TO, DICT_INITSIZE)
+def ll_newdict(dictrepr):
+    assert not dictrepr.custom_eq_hash     # use ll_newdict_custom_eq_hash() instead
+    d = lltype.malloc(dictrepr.DICT)
+    d.entries = lltype.malloc(dictrepr.DICTENTRYARRAY, DICT_INITSIZE)
+    d.num_items = 0  # but still be explicit
+    d.num_pristine_entries = DICT_INITSIZE
+    return d
+
+def ll_newdict_custom_eq_hash(eqfn, hashfn, dictrepr):
+    assert dictrepr.custom_eq_hash
+    d = lltype.malloc(dictrepr.DICT)
+    d.entries = lltype.malloc(dictrepr.DICTENTRYARRAY, DICT_INITSIZE)
     d.num_items = 0  # but still be explicit
-    d.num_pristine_entries = DICT_INITSIZE 
+    d.num_pristine_entries = DICT_INITSIZE
+    d.fnkeyeq = eqfn
+    d.fnkeyhash = hashfn
     return d
 
+def ll_copy_extra_data(targetdict, sourcedict, dictrepr):
+    if dictrepr.custom_eq_hash:
+        targetdict.fnkeyeq   = sourcedict.fnkeyeq
+        targetdict.fnkeyhash = sourcedict.fnkeyhash
+
 def rtype_newdict(hop):
+    hop.inputargs()    # no arguments expected
     r_dict = hop.r_result
     if r_dict == robject.pyobj_repr: # special case: SomeObject: SomeObject dicts!
         cdict = hop.inputconst(robject.pyobj_repr, dict)
         return hop.genop('simple_call', [cdict], resulttype = robject.pyobj_repr)
-    c1 = hop.inputconst(lltype.Void, r_dict.lowleveltype)
-    v_result = hop.gendirectcall(ll_newdict, c1)
+    crepr = hop.inputconst(lltype.Void, r_dict)
+    v_result = hop.gendirectcall(ll_newdict, crepr)
+    return v_result
+
+def rtype_r_dict(hop):
+    r_dict = hop.r_result
+    if not r_dict.custom_eq_hash:
+        raise TyperError("r_dict() call does not return an r_dict instance")
+    v_eqfn, v_hashfn = hop.inputargs(r_dict.r_rdict_eqfn,
+                                     r_dict.r_rdict_hashfn)
+    crepr = hop.inputconst(lltype.Void, r_dict)
+    hop.exception_cannot_occur()
+    v_result = hop.gendirectcall(ll_newdict_custom_eq_hash,
+                                 v_eqfn, v_hashfn, crepr)
     return v_result
 
 # ____________________________________________________________
@@ -423,15 +509,15 @@
     else: 
         return default
 
-def ll_copy(dict):
-    DICTPTR = lltype.typeOf(dict)
-    d = lltype.malloc(DICTPTR.TO)
-    d.entries = lltype.malloc(DICTPTR.TO.entries.TO, len(dict.entries))
+def ll_copy(dict, dictrepr):
+    dictsize = len(dict.entries)
+    d = lltype.malloc(dictrepr.DICT)
+    d.entries = lltype.malloc(dictrepr.DICTENTRYARRAY, dictsize)
     d.num_items = dict.num_items
     d.num_pristine_entries = dict.num_pristine_entries
+    ll_copy_extra_data(d, dict, dictrepr)
     i = 0
-    dictlen = len(d.entries)
-    while i < dictlen:
+    while i < dictsize:
         d_entry = d.entries[i]
         entry = dict.entries[i]
         d_entry.key = entry.key

Modified: pypy/dist/pypy/rpython/rpbc.py
==============================================================================
--- pypy/dist/pypy/rpython/rpbc.py	(original)
+++ pypy/dist/pypy/rpython/rpbc.py	Sun Sep 11 13:32:56 2005
@@ -402,8 +402,11 @@
         if value not in self.function_signatures():
             raise TyperError("%r not in %r" % (value,
                                                self.s_pbc.prebuiltinstances))
-        f, rinputs, rresult = self.function_signatures()[value]
-        return f
+        if self.lowleveltype == Void:
+            return None
+        else:
+            f, rinputs, rresult = self.function_signatures()[value]
+            return f
 
     def rtype_simple_call(self, hop):
         f, rinputs, rresult = self.function_signatures().itervalues().next()

Modified: pypy/dist/pypy/rpython/test/test_objectmodel.py
==============================================================================
--- pypy/dist/pypy/rpython/test/test_objectmodel.py	(original)
+++ pypy/dist/pypy/rpython/test/test_objectmodel.py	Sun Sep 11 13:32:56 2005
@@ -28,7 +28,8 @@
         assert False, "should have raised"
     assert len(d) == 1
     assert 'oops' not in d
-    assert list(d) == ['hello']
+    x, = d
+    assert x == 'hello'
     assert d.get('hola', -1) == 42
     assert d.get('salut', -1) == -1
     d1 = d.copy()
@@ -38,9 +39,12 @@
     d.update(d1)
     assert d.values() == [42]
     assert d.items() == [('hello', 42)]
-    assert list(d.iterkeys()) == ['hello']
-    assert list(d.itervalues()) == [42]
-    assert list(d.iteritems()) == [('hello', 42)]
+    x, = d.iterkeys()
+    assert x == 'hello'
+    x, = d.itervalues()
+    assert x == 42
+    x, = d.iteritems()
+    assert x == ('hello', 42)
     d.clear()
     assert d.keys() == []
     return True   # for the tests below

Modified: pypy/dist/pypy/rpython/test/test_rconstantdict.py
==============================================================================
--- pypy/dist/pypy/rpython/test/test_rconstantdict.py	(original)
+++ pypy/dist/pypy/rpython/test/test_rconstantdict.py	Sun Sep 11 13:32:56 2005
@@ -1,5 +1,6 @@
 import py
-from pypy.rpython.test.test_llinterp import interpret 
+from pypy.rpython.test.test_llinterp import interpret
+from pypy.rpython.objectmodel import r_dict
 
 def test_constant_int_dict(): 
     d = {1: 2, 2: 3, 3: 4} 
@@ -36,3 +37,19 @@
     assert res == 123
     res = interpret(func, [63])
     assert res == 321
+
+def test_constant_r_dict():
+    def strange_key_eq(key1, key2):
+        return key1[0] == key2[0]   # only the 1st character is relevant
+    def strange_key_hash(key):
+        return ord(key[0])
+
+    d = r_dict(strange_key_eq, strange_key_hash)
+    d['hello'] = 42
+    d['world'] = 43
+    def func(i):
+        return d[chr(i)]
+    res = interpret(func, [ord('h')])
+    assert res == 42
+    res = interpret(func, [ord('w')])
+    assert res == 43



More information about the Pypy-commit mailing list