Author: Armin Rigo <[email protected]>
Branch:
Changeset: r67828:b5cd6e18901d
Date: 2013-11-04 12:52 +0100
http://bitbucket.org/pypy/pypy/changeset/b5cd6e18901d/
Log: Add a test that dictionaries are type-erased correctly. This fails
with rordereddict because of the LookupFamily. Managed to fix it by
killing LookupFamily and replacing it with regular specialization.
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
@@ -4,6 +4,7 @@
from rpython.rtyper.rdict import AbstractDictRepr, AbstractDictIteratorRepr
from rpython.rtyper.lltypesystem import lltype, llmemory, rffi
from rpython.rlib import objectmodel, jit, rgc
+from rpython.rlib.objectmodel import specialize
from rpython.rlib.debug import ll_assert
from rpython.rlib.rarithmetic import r_uint, intmask
from rpython.rtyper import rmodel
@@ -44,20 +45,19 @@
DICT = lltype.typeOf(d).TO
fun = d.lookup_function_no
if fun == FUNC_BYTE:
- return DICT.lookup_family.byte_lookup_function(d, key, hash, flag)
+ return ll_dict_lookup(d, key, hash, flag, TYPE_BYTE)
elif fun == FUNC_SHORT:
- return DICT.lookup_family.short_lookup_function(d, key, hash, flag)
+ return ll_dict_lookup(d, key, hash, flag, TYPE_SHORT)
elif IS_64BIT and fun == FUNC_INT:
- return DICT.lookup_family.int_lookup_function(d, key, hash, flag)
+ return ll_dict_lookup(d, key, hash, flag, TYPE_INT)
elif fun == FUNC_LONG:
- return DICT.lookup_family.long_lookup_function(d, key, hash, flag)
+ return ll_dict_lookup(d, key, hash, flag, TYPE_LONG)
assert False
def get_ll_dict(DICTKEY, DICTVALUE, get_custom_eq_hash=None, DICT=None,
ll_fasthash_function=None, ll_hash_function=None,
ll_eq_function=None, method_cache={},
- dummykeyobj=None, dummyvalueobj=None, rtyper=None,
- setup_lookup_funcs=True):
+ dummykeyobj=None, dummyvalueobj=None, rtyper=None):
# get the actual DICT type. if DICT is None, it's created, otherwise
# forward reference is becoming DICT
if DICT is None:
@@ -147,52 +147,10 @@
adtmeths['lookup_function'] =
lltype.staticAdtMethod(ll_call_lookup_function)
adtmeths['allocate'] = lltype.typeMethod(_ll_malloc_dict)
- family = LookupFamily()
- adtmeths['lookup_family'] = family
-
DICT.become(lltype.GcStruct("dicttable", adtmeths=adtmeths,
*fields))
-
- family.empty_array = DICTENTRYARRAY.allocate(0)
- if setup_lookup_funcs:
- _setup_lookup_funcs(DICT, rtyper, family)
return DICT
-def _setup_lookup_funcs(DICT, rtyper, family):
- DICTKEY = DICT.entries.TO.OF.key
- LOOKUP_FUNC = lltype.Ptr(lltype.FuncType([lltype.Ptr(DICT), DICTKEY,
- lltype.Signed, lltype.Signed],
- lltype.Signed))
-
-
- STORECLEAN_FUNC = lltype.Ptr(lltype.FuncType([lltype.Ptr(DICT),
- lltype.Signed,
- lltype.Signed],
- lltype.Void))
-
- for name, T in [('byte', rffi.UCHAR),
- ('short', rffi.USHORT),
- ('int', rffi.UINT),
- ('long', lltype.Unsigned)]:
- if name == 'int' and not IS_64BIT:
- continue
- lookupfn, storecleanfn = new_lookup_functions(LOOKUP_FUNC,
- STORECLEAN_FUNC, T=T,
- rtyper=rtyper)
- setattr(family, '%s_lookup_function' % name, lookupfn)
- setattr(family, '%s_insert_clean_function' % name, storecleanfn)
-
-def llhelper_or_compile(rtyper, FUNCPTR, ll_func):
- # the check is for pseudo rtyper from tests
- if rtyper is None or not hasattr(rtyper, 'annotate_helper_fn'):
- return llhelper(FUNCPTR, ll_func)
- else:
- return rtyper.annotate_helper_fn(ll_func, FUNCPTR.TO.ARGS)
-
-class LookupFamily:
- def _freeze_(self):
- return True
-
class OrderedDictRepr(AbstractDictRepr):
@@ -249,17 +207,9 @@
s_key)
kwd['dummyvalueobj'] = self.value_repr.get_ll_dummyval_obj(
self.rtyper, s_value)
-
- kwd['setup_lookup_funcs'] = False
get_ll_dict(DICTKEY, DICTVALUE, DICT=self.DICT,
rtyper=self.rtyper, **kwd)
- def _setup_repr_final(self):
- if not self.finalized:
- family = self.lowleveltype.TO.lookup_family
- _setup_lookup_funcs(self.lowleveltype.TO, self.rtyper, family)
- self.finalized = True
-
def convert_const(self, dictobj):
from rpython.rtyper.lltypesystem import llmemory
@@ -451,6 +401,10 @@
FUNC_BYTE, FUNC_SHORT, FUNC_INT, FUNC_LONG = range(4)
else:
FUNC_BYTE, FUNC_SHORT, FUNC_LONG = range(3)
+TYPE_BYTE = rffi.UCHAR
+TYPE_SHORT = rffi.USHORT
+TYPE_INT = rffi.UINT
+TYPE_LONG = lltype.Unsigned
def ll_malloc_indexes_and_choose_lookup(d, n):
if n <= 256:
@@ -477,13 +431,13 @@
def ll_call_insert_clean_function(d, hash, i):
DICT = lltype.typeOf(d).TO
if d.lookup_function_no == FUNC_BYTE:
- DICT.lookup_family.byte_insert_clean_function(d, hash, i)
+ ll_dict_store_clean(d, hash, i, TYPE_BYTE)
elif d.lookup_function_no == FUNC_SHORT:
- DICT.lookup_family.short_insert_clean_function(d, hash, i)
+ ll_dict_store_clean(d, hash, i, TYPE_SHORT)
elif IS_64BIT and d.lookup_function_no == FUNC_INT:
- DICT.lookup_family.int_insert_clean_function(d, hash, i)
+ ll_dict_store_clean(d, hash, i, TYPE_INT)
elif d.lookup_function_no == FUNC_LONG:
- DICT.lookup_family.long_insert_clean_function(d, hash, i)
+ ll_dict_store_clean(d, hash, i, TYPE_LONG)
else:
assert False
@@ -738,31 +692,83 @@
FLAG_DELETE = 2
FLAG_DELETE_TRY_HARD = 3
-def new_lookup_functions(LOOKUP_FUNC, STORECLEAN_FUNC, T, rtyper=None):
- INDEXES = lltype.Ptr(lltype.GcArray(T))
[email protected]()
+def _ll_ptr_to_array_of(T):
+ return lltype.Ptr(lltype.GcArray(T))
- def ll_kill_something(d):
- i = 0
- indexes = lltype.cast_opaque_ptr(INDEXES, d.indexes)
- while True:
- index = rffi.cast(lltype.Signed, indexes[i])
- if index >= VALID_OFFSET:
+def ll_kill_something(d, T):
+ INDEXES = _ll_ptr_to_array_of(T)
+ i = 0
+ indexes = lltype.cast_opaque_ptr(INDEXES, d.indexes)
+ while True:
+ index = rffi.cast(lltype.Signed, indexes[i])
+ if index >= VALID_OFFSET:
+ indexes[i] = rffi.cast(T, DELETED)
+ return index
+ i += 1
+
[email protected]_inside_iff(lambda d, key, hash, store_flag, T:
+ jit.isvirtual(d) and jit.isconstant(key))
+def ll_dict_lookup(d, key, hash, store_flag, T):
+ INDEXES = _ll_ptr_to_array_of(T)
+ entries = d.entries
+ indexes = lltype.cast_opaque_ptr(INDEXES, d.indexes)
+ mask = len(indexes) - 1
+ i = r_uint(hash & mask)
+ # do the first try before any looping
+ ENTRIES = lltype.typeOf(entries).TO
+ direct_compare = not hasattr(ENTRIES, 'no_direct_compare')
+ index = rffi.cast(lltype.Signed, indexes[intmask(i)])
+ if index >= VALID_OFFSET:
+ checkingkey = entries[index - VALID_OFFSET].key
+ if direct_compare and checkingkey == key:
+ if store_flag == FLAG_DELETE:
indexes[i] = rffi.cast(T, DELETED)
- return index
- i += 1
+ return index - VALID_OFFSET # found the entry
+ if d.keyeq is not None and entries.hash(index - VALID_OFFSET) == hash:
+ # correct hash, maybe the key is e.g. a different pointer to
+ # an equal object
+ found = d.keyeq(checkingkey, key)
+ #llop.debug_print(lltype.Void, "comparing keys",
ll_debugrepr(checkingkey), ll_debugrepr(key), found)
+ if d.paranoia:
+ if (entries != d.entries or
lltype.cast_opaque_ptr(llmemory.GCREF, indexes) != d.indexes or
+ not entries.valid(index - VALID_OFFSET) or
+ entries[index - VALID_OFFSET].key != checkingkey):
+ # the compare did major nasty stuff to the dict: start over
+ return ll_dict_lookup(d, key, hash, store_flag, T)
+ if found:
+ if store_flag == FLAG_DELETE:
+ indexes[i] = rffi.cast(T, DELETED)
+ return index - VALID_OFFSET
+ deletedslot = -1
+ elif index == DELETED:
+ deletedslot = intmask(i)
+ else:
+ # pristine entry -- lookup failed
+ if store_flag == FLAG_STORE:
+ indexes[i] = rffi.cast(T, d.num_used_items + VALID_OFFSET)
+ elif d.paranoia and store_flag == FLAG_DELETE_TRY_HARD:
+ return ll_kill_something(d, T)
+ return -1
- @jit.look_inside_iff(lambda d, key, hash, store_flag:
- jit.isvirtual(d) and jit.isconstant(key))
- def ll_dict_lookup(d, key, hash, store_flag):
- entries = d.entries
- indexes = lltype.cast_opaque_ptr(INDEXES, d.indexes)
- mask = len(indexes) - 1
- i = r_uint(hash & mask)
- # do the first try before any looping
- ENTRIES = lltype.typeOf(entries).TO
- direct_compare = not hasattr(ENTRIES, 'no_direct_compare')
+ # In the loop, a deleted entry (everused and not valid) is by far
+ # (factor of 100s) the least likely outcome, so test for that last.
+ perturb = r_uint(hash)
+ while 1:
+ # compute the next index using unsigned arithmetic
+ i = (i << 2) + i + perturb + 1
+ i = i & mask
index = rffi.cast(lltype.Signed, indexes[intmask(i)])
- if index >= VALID_OFFSET:
+ if index == FREE:
+ if store_flag == FLAG_STORE:
+ if deletedslot == -1:
+ deletedslot = intmask(i)
+ indexes[deletedslot] = rffi.cast(T, d.num_used_items +
+ VALID_OFFSET)
+ elif d.paranoia and store_flag == FLAG_DELETE_TRY_HARD:
+ return ll_kill_something(d, T)
+ return -1
+ elif index >= VALID_OFFSET:
checkingkey = entries[index - VALID_OFFSET].key
if direct_compare and checkingkey == key:
if store_flag == FLAG_DELETE:
@@ -772,85 +778,34 @@
# correct hash, maybe the key is e.g. a different pointer to
# an equal object
found = d.keyeq(checkingkey, key)
- #llop.debug_print(lltype.Void, "comparing keys",
ll_debugrepr(checkingkey), ll_debugrepr(key), found)
if d.paranoia:
if (entries != d.entries or
lltype.cast_opaque_ptr(llmemory.GCREF, indexes) != d.indexes or
not entries.valid(index - VALID_OFFSET) or
entries[index - VALID_OFFSET].key != checkingkey):
# the compare did major nasty stuff to the dict: start
over
- return ll_dict_lookup(d, key, hash, store_flag)
+ return ll_dict_lookup(d, key, hash, store_flag, T)
if found:
if store_flag == FLAG_DELETE:
indexes[i] = rffi.cast(T, DELETED)
return index - VALID_OFFSET
- deletedslot = -1
- elif index == DELETED:
+ elif deletedslot == -1:
deletedslot = intmask(i)
- else:
- # pristine entry -- lookup failed
- if store_flag == FLAG_STORE:
- indexes[i] = rffi.cast(T, d.num_used_items + VALID_OFFSET)
- elif d.paranoia and store_flag == FLAG_DELETE_TRY_HARD:
- return ll_kill_something(d)
- return -1
+ perturb >>= PERTURB_SHIFT
- # In the loop, a deleted entry (everused and not valid) is by far
- # (factor of 100s) the least likely outcome, so test for that last.
- perturb = r_uint(hash)
- while 1:
- # compute the next index using unsigned arithmetic
- i = (i << 2) + i + perturb + 1
- i = i & mask
- index = rffi.cast(lltype.Signed, indexes[intmask(i)])
- if index == FREE:
- if store_flag == FLAG_STORE:
- if deletedslot == -1:
- deletedslot = intmask(i)
- indexes[deletedslot] = rffi.cast(T, d.num_used_items +
- VALID_OFFSET)
- elif d.paranoia and store_flag == FLAG_DELETE_TRY_HARD:
- return ll_kill_something(d)
- return -1
- elif index >= VALID_OFFSET:
- checkingkey = entries[index - VALID_OFFSET].key
- if direct_compare and checkingkey == key:
- if store_flag == FLAG_DELETE:
- indexes[i] = rffi.cast(T, DELETED)
- return index - VALID_OFFSET # found the entry
- if d.keyeq is not None and entries.hash(index - VALID_OFFSET)
== hash:
- # correct hash, maybe the key is e.g. a different pointer
to
- # an equal object
- found = d.keyeq(checkingkey, key)
- if d.paranoia:
- if (entries != d.entries or
lltype.cast_opaque_ptr(llmemory.GCREF, indexes) != d.indexes or
- not entries.valid(index - VALID_OFFSET) or
- entries[index - VALID_OFFSET].key != checkingkey):
- # the compare did major nasty stuff to the dict:
start over
- return ll_dict_lookup(d, key, hash, store_flag)
- if found:
- if store_flag == FLAG_DELETE:
- indexes[i] = rffi.cast(T, DELETED)
- return index - VALID_OFFSET
- elif deletedslot == -1:
- deletedslot = intmask(i)
- perturb >>= PERTURB_SHIFT
-
- def ll_dict_store_clean(d, hash, index):
- # a simplified version of ll_dict_lookup() which assumes that the
- # key is new, and the dictionary doesn't contain deleted entries.
- # It only finds the next free slot for the given hash.
- indexes = lltype.cast_opaque_ptr(INDEXES, d.indexes)
- mask = len(indexes) - 1
- i = r_uint(hash & mask)
- perturb = r_uint(hash)
- while rffi.cast(lltype.Signed, indexes[i]) != 0:
- i = (i << 2) + i + perturb + 1
- i = i & mask
- perturb >>= PERTURB_SHIFT
- indexes[i] = rffi.cast(T, index + VALID_OFFSET)
-
- return (llhelper_or_compile(rtyper, LOOKUP_FUNC, ll_dict_lookup),
- llhelper_or_compile(rtyper, STORECLEAN_FUNC, ll_dict_store_clean))
+def ll_dict_store_clean(d, hash, index, T):
+ # a simplified version of ll_dict_lookup() which assumes that the
+ # key is new, and the dictionary doesn't contain deleted entries.
+ # It only finds the next free slot for the given hash.
+ INDEXES = _ll_ptr_to_array_of(T)
+ indexes = lltype.cast_opaque_ptr(INDEXES, d.indexes)
+ mask = len(indexes) - 1
+ i = r_uint(hash & mask)
+ perturb = r_uint(hash)
+ while rffi.cast(lltype.Signed, indexes[i]) != 0:
+ i = (i << 2) + i + perturb + 1
+ i = i & mask
+ perturb >>= PERTURB_SHIFT
+ indexes[i] = rffi.cast(T, index + VALID_OFFSET)
# ____________________________________________________________
#
@@ -858,9 +813,15 @@
DICT_INITSIZE = 8
+
[email protected]()
+def _ll_empty_array(DICT):
+ """Memo function: cache a single prebuilt allocated empty array."""
+ return DICT.entries.TO.allocate(0)
+
def ll_newdict(DICT):
d = DICT.allocate()
- d.entries = DICT.lookup_family.empty_array
+ d.entries = _ll_empty_array(DICT)
ll_malloc_indexes_and_choose_lookup(d, DICT_INITSIZE)
d.num_items = 0
d.num_used_items = 0
@@ -1030,7 +991,7 @@
return
DICT = lltype.typeOf(d).TO
old_entries = d.entries
- d.entries = DICT.lookup_family.empty_array
+ d.entries = _ll_empty_array(DICT)
ll_malloc_indexes_and_choose_lookup(d, DICT_INITSIZE)
d.num_items = 0
d.num_used_items = 0
diff --git a/rpython/rtyper/test/test_rdict.py
b/rpython/rtyper/test/test_rdict.py
--- a/rpython/rtyper/test/test_rdict.py
+++ b/rpython/rtyper/test/test_rdict.py
@@ -856,12 +856,28 @@
res = self.interpret(func, [42])
assert res == 42
+ def test_externalvsinternal(self):
+ class A: pass
+ class B: pass
+ class C: pass
+ class D: pass
+ def func():
+ d1 = self.newdict(); d1[A()] = B()
+ d2 = self.newdict2(); d2[C()] = D()
+ return (d1, d2)
+ res = self.interpret(func, [])
+ assert lltype.typeOf(res.item0) == lltype.typeOf(res.item1)
+
class TestRDict(BaseTestRDict):
@staticmethod
def newdict():
return {}
+ @staticmethod
+ def newdict2():
+ return {}
+
def test_two_dicts_with_different_value_types(self):
def func(i):
d1 = {}
diff --git a/rpython/rtyper/test/test_rordereddict.py
b/rpython/rtyper/test/test_rordereddict.py
--- a/rpython/rtyper/test/test_rordereddict.py
+++ b/rpython/rtyper/test/test_rordereddict.py
@@ -261,6 +261,10 @@
def newdict():
return OrderedDict()
+ @staticmethod
+ def newdict2():
+ return OrderedDict()
+
def test_two_dicts_with_different_value_types(self):
def func(i):
d1 = OrderedDict()
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit