aboutsummaryrefslogtreecommitdiff
blob: 3f3a9229a38a538050691cb8a24699e97dab7cef (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
from rpython.flowspace.model import Constant
from rpython.rtyper.lltypesystem import lltype, llmemory, rdict
from rpython.rtyper.lltypesystem.llmemory import weakref_create, weakref_deref
from rpython.rtyper import rclass
from rpython.rtyper.rclass import getinstancerepr
from rpython.rtyper.rmodel import Repr
from rpython.rlib.rweakref import RWeakKeyDictionary
from rpython.rlib import jit
from rpython.rlib.objectmodel import compute_identity_hash


# Warning: this implementation of RWeakKeyDictionary is not exactly
# leaking, but can keep around some values for a long time, even after
# the corresponding keys were freed.  They will be eventually freed if
# you continue to manipulate the dictionary.  Avoid to use this if the
# values are objects that might keep alive tons of memory.


class WeakKeyDictRepr(Repr):
    def __init__(self, rtyper):
        self.rtyper = rtyper
        self.lowleveltype = lltype.Ptr(WEAKDICT)
        self.dict_cache = {}

    def convert_const(self, weakdict):
        if not isinstance(weakdict, RWeakKeyDictionary):
            raise TyperError("expected an RWeakKeyDictionary: %r" % (
                weakdict,))
        try:
            key = Constant(weakdict)
            return self.dict_cache[key]
        except KeyError:
            self.setup()
            if weakdict.length() != 0:
                raise TyperError("got a non-empty prebuilt RWeakKeyDictionary")
            l_dict = ll_new_weakdict()
            self.dict_cache[key] = l_dict
            return l_dict

    def rtype_method_get(self, hop):
        r_object = getinstancerepr(self.rtyper, None)
        v_d, v_key = hop.inputargs(self, r_object)
        hop.exception_cannot_occur()
        v_result = hop.gendirectcall(ll_get, v_d, v_key)
        v_result = hop.genop("cast_pointer", [v_result],
                             resulttype=hop.r_result.lowleveltype)
        return v_result

    def rtype_method_set(self, hop):
        r_object = getinstancerepr(self.rtyper, None)
        v_d, v_key, v_value = hop.inputargs(self, r_object, r_object)
        hop.exception_cannot_occur()
        if hop.args_s[2].is_constant() and hop.args_s[2].const is None:
            hop.gendirectcall(ll_set_null, v_d, v_key)
        else:
            hop.gendirectcall(ll_set, v_d, v_key, v_value)

    def rtype_method_length(self, hop):
        v_d, = hop.inputargs(self)
        hop.exception_cannot_occur()
        return hop.gendirectcall(ll_length, v_d)


def specialize_make_weakdict(hop):
    hop.exception_cannot_occur()
    v_d = hop.gendirectcall(ll_new_weakdict)
    return v_d

# ____________________________________________________________


NULLVALUE = lltype.nullptr(rclass.OBJECTPTR.TO)
WEAKDICTENTRY = lltype.Struct("weakdictentry",
                              ("key", llmemory.WeakRefPtr),
                              ("value", rclass.OBJECTPTR),
                              ("f_hash", lltype.Signed))

def ll_debugrepr(x):
    if x:
        h = compute_identity_hash(x)
    else:
        h = 0
    return '<%x>' % (h,)

def ll_valid(entries, i):
    key = entries[i].key
    if not key:
        return False
    elif weakref_deref(rclass.OBJECTPTR, key):
        return True
    else:
        # The entry might be a dead weakref still holding a strong
        # reference to the value; for this case, we clear the old
        # value from the entry, if any.
        entries[i].value = NULLVALUE
        return False

def ll_everused(entries, i):
    return bool(entries[i].key)

entrymeths = {
    'allocate': lltype.typeMethod(rdict._ll_malloc_entries),
    'delete': rdict._ll_free_entries,
    'valid': ll_valid,
    'everused': ll_everused,
    'hash': rdict.ll_hash_from_cache,
    'no_direct_compare': True,
    }
WEAKDICTENTRYARRAY = lltype.GcArray(WEAKDICTENTRY,
                                    adtmeths=entrymeths,
                                    hints={'weakarray': 'key'})
# NB. the 'hints' is not used so far ^^^

@jit.dont_look_inside
def ll_new_weakdict():
    d = lltype.malloc(WEAKDICT)
    d.entries = WEAKDICT.entries.TO.allocate(rdict.DICT_INITSIZE)
    d.num_items = 0
    d.resize_counter = rdict.DICT_INITSIZE * 2
    return d

@jit.dont_look_inside
def ll_get(d, llkey):
    hash = compute_identity_hash(llkey)
    i = rdict.ll_dict_lookup(d, llkey, hash) & rdict.MASK
    #llop.debug_print(lltype.Void, i, 'get', hex(hash),
    #                 ll_debugrepr(d.entries[i].key),
    #                 ll_debugrepr(d.entries[i].value))
    # NB. ll_valid() above was just called at least on entry i, so if
    # it is an invalid entry with a dead weakref, the value was reset
    # to NULLVALUE.
    return d.entries[i].value

@jit.dont_look_inside
def ll_set(d, llkey, llvalue):
    if llvalue:
        ll_set_nonnull(d, llkey, llvalue)
    else:
        ll_set_null(d, llkey)

@jit.dont_look_inside
def ll_set_nonnull(d, llkey, llvalue):
    hash = compute_identity_hash(llkey)
    keyref = weakref_create(llkey)    # GC effects here, before the rest
    i = rdict.ll_dict_lookup(d, llkey, hash) & rdict.MASK
    everused = d.entries.everused(i)
    d.entries[i].key = keyref
    d.entries[i].value = llvalue
    d.entries[i].f_hash = hash
    #llop.debug_print(lltype.Void, i, 'stored', hex(hash),
    #                 ll_debugrepr(llkey),
    #                 ll_debugrepr(llvalue))
    if not everused:
        d.resize_counter -= 3
        if d.resize_counter <= 0:
            #llop.debug_print(lltype.Void, 'RESIZE')
            ll_weakdict_resize(d)

@jit.dont_look_inside
def ll_set_null(d, llkey):
    hash = compute_identity_hash(llkey)
    i = rdict.ll_dict_lookup(d, llkey, hash) & rdict.MASK
    if d.entries.everused(i):
        # If the entry was ever used, clean up its key and value.
        # We don't store a NULL value, but a dead weakref, because
        # the entry must still be marked as everused().
        d.entries[i].key = llmemory.dead_wref
        d.entries[i].value = NULLVALUE
        #llop.debug_print(lltype.Void, i, 'zero')

def ll_update_num_items(d):
    entries = d.entries
    num_items = 0
    for i in range(len(entries)):
        if entries.valid(i):
            num_items += 1
    d.num_items = num_items

def ll_weakdict_resize(d):
    # first set num_items to its correct, up-to-date value
    ll_update_num_items(d)
    rdict.ll_dict_resize(d)

def ll_keyeq(d, weakkey1, realkey2):
    # only called by ll_dict_lookup() with the first arg coming from an
    # entry.key, and the 2nd arg being the argument to ll_dict_lookup().
    if not weakkey1:
        assert bool(realkey2)
        return False
    return weakref_deref(rclass.OBJECTPTR, weakkey1) == realkey2

@jit.dont_look_inside
def ll_length(d):
    # xxx slow, but it's only for debugging
    ll_update_num_items(d)
    #llop.debug_print(lltype.Void, 'length:', d.num_items)
    return d.num_items

dictmeths = {
    'll_get': ll_get,
    'll_set': ll_set,
    'keyeq': ll_keyeq,
    'paranoia': False,
    }

WEAKDICT = lltype.GcStruct("weakkeydict",
                           ("num_items", lltype.Signed),
                           ("resize_counter", lltype.Signed),
                           ("entries", lltype.Ptr(WEAKDICTENTRYARRAY)),
                           adtmeths=dictmeths)