Skip to content

Commit 0c34dbc

Browse files
committed
Dict views mostly working
1 parent 4e8a46d commit 0c34dbc

File tree

3 files changed

+135
-20
lines changed

3 files changed

+135
-20
lines changed

Lib/test/test_regions/test_dict.py

Lines changed: 103 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,42 @@
11
import unittest
2+
import sys
23
from regions import Region, is_local
34
from immutable import freeze, isfrozen
45

5-
class BaseTestRegionDict(unittest.TestCase):
6+
class TestRegionDict(unittest.TestCase):
7+
def check_view_ref(self, create_view):
8+
# Setup
9+
r = Region()
10+
r.dict = {}
11+
base_lrc = r._lrc
12+
13+
# Pre-condition
14+
self.assertGreater(base_lrc, 0)
15+
16+
# Action: Creating a view
17+
view = create_view(r.dict)
18+
self.assertEqual(r._lrc, base_lrc + 1)
19+
20+
# Action: Check mapping of view
21+
mapping = view.mapping
22+
self.assertEqual(r._lrc, base_lrc + 2)
23+
24+
# Clearing references could decrease the LRC
25+
mapping = None
26+
self.assertEqual(r._lrc, base_lrc + 1)
27+
view = None
28+
self.assertEqual(r._lrc, base_lrc)
29+
30+
def test_new_dict_refs_from_item_view(self):
31+
self.check_view_ref(lambda dict: dict.items())
32+
33+
def test_new_dict_refs_from_keys_view(self):
34+
self.check_view_ref(lambda dict: dict.keys())
35+
36+
def test_new_dict_refs_from_values_view(self):
37+
self.check_view_ref(lambda dict: dict.values())
38+
39+
class BaseTestRegionDictKeys(unittest.TestCase):
640
__test__ = False
741

842
def check_dict_assign(self, dict, key):
@@ -103,8 +137,39 @@ class SomeObject:
103137
# Post-condition
104138
self.assertEqual(r._lrc, lrc + 1)
105139

140+
def check_loop_lrc_change(self, region, iter_src, loop_lrc_effect, iter_lrc_cost = 1):
141+
# Check loop iterations change the LRC
142+
lrc = region._lrc
143+
i = 0
144+
for v in iter_src:
145+
self.assertEqual(region._lrc, lrc + iter_lrc_cost + loop_lrc_effect,
146+
f"Fail in iteration: {i} base LRC {lrc} + {iter_lrc_cost} for iter")
147+
# Reassigning v should reset the LRC
148+
v = None
149+
self.assertEqual(region._lrc, lrc + iter_lrc_cost,
150+
f"LRC didn't reset in iteration: {i} base LRC {lrc} + {iter_lrc_cost} for iter")
151+
i += 1
152+
153+
# Check LRC is back to pre-loop levels
154+
self.assertEqual(region._lrc, lrc)
155+
156+
def check_dict_view(self, key1, key2, create_view, loop_lrc_effect, iter_lrc_cost = 1):
157+
class SomeObject:
158+
pass
159+
freeze(SomeObject())
160+
161+
# Setup
162+
r = Region()
163+
r.dict = {}
164+
r.dict[key1] = SomeObject()
165+
r.dict[key2] = SomeObject()
166+
167+
# Create the view
168+
view = create_view(r.dict)
169+
170+
self.check_loop_lrc_change(r, view, loop_lrc_effect, iter_lrc_cost)
106171

107-
class TestRegionDictUnicodeKeys(BaseTestRegionDict):
172+
class TestRegionDictUnicodeKeys(BaseTestRegionDictKeys):
108173
def test_wb_insert_empty(self):
109174
self.check_dict_assign({}, "some-key")
110175

@@ -138,7 +203,24 @@ def test_wb_get_default(self):
138203
def test_wb_copy(self):
139204
self.check_dict_item_access("another key", lambda dict, key: dict.copy())
140205

141-
class TestRegionDictObjectKeys(BaseTestRegionDict):
206+
def test_wb_keys_view(self):
207+
self.check_dict_view("K1", "K2", lambda d: d.keys(), loop_lrc_effect=0)
208+
209+
def test_wb_values_view(self):
210+
self.check_dict_view("K1", "K2", lambda d: d.values(), loop_lrc_effect=1)
211+
212+
@unittest.expectedFailure # FIXME(regions): xFrednet: Broken until WBs in tuples have been added
213+
def test_wb_items_view(self):
214+
self.check_dict_view("K1", "K2", lambda d: d.items(), loop_lrc_effect=1)
215+
216+
def test_wb_iter_dict(self):
217+
self.check_dict_view("K1", "K2", lambda d: d, loop_lrc_effect=0)
218+
219+
def test_wb_iter_dict_reversed(self):
220+
# The iterator doesn't effect the LRC, since we create it with the `reversed`
221+
self.check_dict_view("K1", "K2", lambda d: reversed(d), loop_lrc_effect=0, iter_lrc_cost=0)
222+
223+
class TestRegionDictObjectKeys(BaseTestRegionDictKeys):
142224
class Key:
143225
pass
144226

@@ -182,11 +264,26 @@ def test_wb_copy(self):
182264
# LRC increase of 2: 1x for the key 1x for the item
183265
self.check_dict_item_access(self.Key(), lambda dict, key: dict.copy(), lrc_offset = 2)
184266

185-
@unittest.expectedFailure # Broken until WBs in lists have been added
267+
@unittest.expectedFailure # FIXME(regions): xFrednet: Broken until WBs in lists have been added
186268
def test_wb_key_list(self):
187269
self.check_dict_item_access(self.Key(), lambda dict, key: list(dict))
188270

271+
def test_wb_keys_view(self):
272+
self.check_dict_view(self.Key(), self.Key(), lambda d: d.keys(), 1)
273+
274+
def test_wb_values_view(self):
275+
self.check_dict_view(self.Key(), self.Key(), lambda d: d.values(), 1)
189276

277+
@unittest.expectedFailure # FIXME(regions): xFrednet: Broken until WBs in tuples have been added
278+
def test_wb_items_view(self):
279+
self.check_dict_view(self.Key(), self.Key(), lambda d: d.items(), 2)
280+
281+
def test_wb_iter_dict(self):
282+
self.check_dict_view(self.Key(), self.Key(), lambda d: d, loop_lrc_effect=1)
283+
284+
def test_wb_iter_reversed_dict(self):
285+
# The iterator doesn't effect the LRC, since we create it with the `reversed`
286+
self.check_dict_view(self.Key(), self.Key(), lambda d: reversed(d), loop_lrc_effect=1, iter_lrc_cost=0)
190287

191288
# TODO: Construction: dict(one=1, two=2, three=3)
192289
# TODO: Construction: {'one': 1, 'two': 2, 'three': 3}
@@ -195,18 +292,16 @@ def test_wb_key_list(self):
195292
# TODO: Construction: dict({'three': 3, 'one': 1, 'two': 2})
196293
# TODO: Construction: dict({'one': 1, 'three': 3}, two=2)
197294

198-
295+
# FIXME(regions): xFrednet: Set operations on views, like `&`, `^` and `|`
296+
# are currently not tested and probably don't work.
199297

200298
# TODO: classmethod fromkeys(iterable, value=None, /)
201-
# TODO: dict.items()
202-
# TODO: dict.keys()
203299
# TODO: dict.pop(key)
204300
# TODO: dict.pop(key, default)
205301
# TODO: dict.popitem(key)
206302
# TODO: reversed(dict)
207303
# TODO: dict.setdefault(key)
208304
# TODO: dict.setdefault(key, default)
209305
# TODO: dict.update(???)
210-
# TODO: dict.values()
211306
# TODO: dict1 | dict2
212307
# TODO: dict1 |= dict2

Objects/descrobject.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1189,7 +1189,7 @@ mappingproxy_dealloc(PyObject *self)
11891189
{
11901190
mappingproxyobject *pp = (mappingproxyobject *)self;
11911191
_PyObject_GC_UNTRACK(pp);
1192-
Py_DECREF(pp->mapping);
1192+
PyRegion_CLEAR(pp, pp->mapping);
11931193
PyObject_GC_Del(pp);
11941194
}
11951195

Objects/dictobject.c

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5365,6 +5365,10 @@ dictiter_new(PyDictObject *dict, PyTypeObject *itertype)
53655365
if (di == NULL) {
53665366
return NULL;
53675367
}
5368+
if (PyRegion_AddRef(di, dict)) {
5369+
Py_DECREF(dict);
5370+
return NULL;
5371+
}
53685372
di->di_dict = (PyDictObject*)Py_NewRef(dict);
53695373
used = FT_ATOMIC_LOAD_SSIZE_RELAXED(dict->ma_used);
53705374
di->di_used = used;
@@ -5403,8 +5407,8 @@ dictiter_dealloc(PyObject *self)
54035407
dictiterobject *di = (dictiterobject *)self;
54045408
/* bpo-31095: UnTrack is needed before calling any callbacks */
54055409
_PyObject_GC_UNTRACK(di);
5406-
Py_XDECREF(di->di_dict);
5407-
Py_XDECREF(di->di_result);
5410+
PyRegion_CLEAR(di, di->di_dict);
5411+
PyRegion_CLEAR(di, di->di_result);
54085412
PyObject_GC_Del(di);
54095413
}
54105414

@@ -5510,10 +5514,11 @@ dictiter_iternextkey_lock_held(PyDictObject *d, PyObject *self)
55105514
}
55115515
di->di_pos = i+1;
55125516
di->len--;
5513-
return Py_NewRef(key);
5517+
return PyRegion_NewRef(key);
55145518

55155519
fail:
55165520
di->di_dict = NULL;
5521+
PyRegion_RemoveLocalRef(d);
55175522
Py_DECREF(d);
55185523
return NULL;
55195524
}
@@ -5633,10 +5638,11 @@ dictiter_iternextvalue_lock_held(PyDictObject *d, PyObject *self)
56335638
}
56345639
di->di_pos = i+1;
56355640
di->len--;
5636-
return Py_NewRef(value);
5641+
return PyRegion_NewRef(value);
56375642

56385643
fail:
56395644
di->di_dict = NULL;
5645+
PyRegion_RemoveLocalRef(d);
56405646
Py_DECREF(d);
56415647
return NULL;
56425648
}
@@ -5760,15 +5766,16 @@ dictiter_iternextitem_lock_held(PyDictObject *d, PyObject *self,
57605766
di->di_pos = i+1;
57615767
di->len--;
57625768
if (out_key != NULL) {
5763-
*out_key = Py_NewRef(key);
5769+
*out_key = PyRegion_NewRef(key);
57645770
}
57655771
if (out_value != NULL) {
5766-
*out_value = Py_NewRef(value);
5772+
*out_value = PyRegion_NewRef(value);
57675773
}
57685774
return 0;
57695775

57705776
fail:
57715777
di->di_dict = NULL;
5778+
PyRegion_RemoveLocalRef(d);
57725779
Py_DECREF(d);
57735780
return -1;
57745781
}
@@ -5927,6 +5934,7 @@ static bool
59275934
acquire_iter_result(PyObject *result)
59285935
{
59295936
if (has_unique_reference(result)) {
5937+
PyRegion_AddLocalRef(result);
59305938
Py_INCREF(result);
59315939
return true;
59325940
}
@@ -5950,11 +5958,13 @@ dictiter_iternextitem(PyObject *self)
59505958

59515959
#endif
59525960
PyObject *result = di->di_result;
5953-
if (acquire_iter_result(result)) {
5961+
if (acquire_iter_result(result) && PyRegion_IsLocal(result)) {
59545962
PyObject *oldkey = PyTuple_GET_ITEM(result, 0);
59555963
PyObject *oldvalue = PyTuple_GET_ITEM(result, 1);
59565964
PyTuple_SET_ITEM(result, 0, key);
59575965
PyTuple_SET_ITEM(result, 1, value);
5966+
PyRegion_RemoveLocalRef(oldkey);
5967+
PyRegion_RemoveLocalRef(oldvalue);
59585968
Py_DECREF(oldkey);
59595969
Py_DECREF(oldvalue);
59605970
// bpo-42536: The GC may have untracked this result tuple. Since we're
@@ -6065,19 +6075,25 @@ dictreviter_iter_lock_held(PyDictObject *d, PyObject *self)
60656075
di->len--;
60666076

60676077
if (Py_IS_TYPE(di, &PyDictRevIterKey_Type)) {
6068-
return Py_NewRef(key);
6078+
return PyRegion_NewRef(key);
60696079
}
60706080
else if (Py_IS_TYPE(di, &PyDictRevIterValue_Type)) {
6071-
return Py_NewRef(value);
6081+
return PyRegion_NewRef(value);
60726082
}
60736083
else if (Py_IS_TYPE(di, &PyDictRevIterItem_Type)) {
60746084
result = di->di_result;
6075-
if (Py_REFCNT(result) == 1) {
6085+
if (Py_REFCNT(result) == 1 && PyRegion_IsLocal(result)) {
60766086
PyObject *oldkey = PyTuple_GET_ITEM(result, 0);
60776087
PyObject *oldvalue = PyTuple_GET_ITEM(result, 1);
6088+
if (PyRegion_AddRefs(result, key, value)) {
6089+
Py_DECREF(result);
6090+
return NULL;
6091+
}
60786092
PyTuple_SET_ITEM(result, 0, Py_NewRef(key));
60796093
PyTuple_SET_ITEM(result, 1, Py_NewRef(value));
60806094
Py_INCREF(result);
6095+
PyRegion_RemoveLocalRef(oldkey);
6096+
PyRegion_RemoveLocalRef(oldvalue);
60816097
Py_DECREF(oldkey);
60826098
Py_DECREF(oldvalue);
60836099
// bpo-42536: The GC may have untracked this result tuple. Since
@@ -6089,6 +6105,10 @@ dictreviter_iter_lock_held(PyDictObject *d, PyObject *self)
60896105
if (result == NULL) {
60906106
return NULL;
60916107
}
6108+
if (PyRegion_AddRefs(result, key, value)) {
6109+
Py_DECREF(result);
6110+
return NULL;
6111+
}
60926112
PyTuple_SET_ITEM(result, 0, Py_NewRef(key));
60936113
PyTuple_SET_ITEM(result, 1, Py_NewRef(value));
60946114
}
@@ -6199,7 +6219,7 @@ dictview_dealloc(PyObject *self)
61996219
_PyDictViewObject *dv = (_PyDictViewObject *)self;
62006220
/* bpo-31095: UnTrack is needed before calling any callbacks */
62016221
_PyObject_GC_UNTRACK(dv);
6202-
Py_XDECREF(dv->dv_dict);
6222+
PyRegion_CLEAR(dv, dv->dv_dict);
62036223
PyObject_GC_Del(dv);
62046224
}
62056225

0 commit comments

Comments
 (0)