@@ -76,3 +76,163 @@ def test_cell_replace_same_bridge(self):
7676 # since this writes replaces the previous owning reference
7777 c .cell_contents = child
7878
79+ # It's not possible to test the existance of all bytecodes. The dis only
80+ # shows the unoptimized version of the bytecode. For testing specilized or
81+ # optimized it's therefore not possible to check for the presence of that
82+ # bytecode in the `dis`. Instead we can only check for the effects of the
83+ # bytecodes.
84+ #
85+ # During test development it's also possible to verify that the expected
86+ # bytecode is triggered by setting a breakpoint in it. Note that it's also
87+ # run during the startup of the runtime and by other tests, so make sure
88+ # the test in question actually started, when the breakpoint is triggered.
89+ class TestBytecodeWriteBarrier (unittest .TestCase ):
90+ # Define a class with __slots__
91+ class ClassWithSlots :
92+ # Use `__slots__` to get the `STORE_ATTR_SLOT` bytecode
93+ __slots__ = ('slot' ,)
94+
95+ def __init__ (self ):
96+ self .slot = None
97+
98+ class ClassWithAttr :
99+ def __init__ (self ):
100+ self .attr = None
101+
102+ def setUp (self ):
103+ # This freezes A and super and meta types of A namely `type` and `object`
104+ makeimmutable (self .ClassWithSlots )
105+ makeimmutable (self .ClassWithAttr )
106+
107+ def test_delete_deref (self ):
108+ r = Region ()
109+ r .field = {}
110+ x = r .field
111+
112+ # Make sure the region knows about local reference from x
113+ self .assertFalse (r .try_close ())
114+
115+ def del_x ():
116+ nonlocal x
117+ del x # This triggers the DELETE_DEREF opcode
118+
119+ # Create the function and disassemble to confirm DELETE_DEREF is present
120+ bytecode = dis .Bytecode (del_x )
121+ self .assertIn ("DELETE_DEREF" , [instr .opname for instr in bytecode ])
122+
123+ self .assertTrue (r .is_open ())
124+
125+ # Call the function forcing the deletion of x which should
126+ # close the region.
127+ del_x ()
128+
129+ self .assertFalse (r .is_open ())
130+
131+ # This tests that references stored by `STORE_ATTR_SLOT` are known
132+ # by the write barrier
133+ def test_store_attr_slot_add_reference (self ):
134+ def set_value (obj , val ):
135+ obj .slot = val # This triggers the STORE_ATTR_SLOT opcode
136+
137+ # Setup a region and a class with slots
138+ r = Region ()
139+ r .slots = self .ClassWithSlots ()
140+ self .assertTrue (r .owns_object (r .slots ))
141+
142+ # Run `set_value` multiple times to optimize it
143+ set_value (r .slots , None )
144+ set_value (r .slots , {})
145+
146+ # Create a new local object
147+ new_object = {}
148+ new_object ["data" ] = {}
149+
150+ # Store the object in slots
151+ set_value (r .slots , new_object )
152+
153+ # Verify the region ownership
154+ self .assertTrue (r .owns_object (new_object ))
155+ self .assertTrue (r .owns_object (new_object ["data" ]))
156+
157+ # This tests that references removed by `STORE_ATTR_SLOT` are known
158+ # by the write barrier
159+ def test_store_attr_slot_remove_reference (self ):
160+ def set_value (obj , val ):
161+ obj .slot = val # This triggers the STORE_ATTR_SLOT opcode
162+
163+ # Setup a region and a class with slots
164+ r = Region ()
165+ r .data = {}
166+ slots = self .ClassWithSlots ()
167+
168+ # Run `set_value` multiple times to optimize it
169+ set_value (slots , None )
170+ set_value (slots , {})
171+
172+ # Create local reference into the region
173+ set_value (slots , r .data )
174+
175+ # Make sure the region knows about the reference from the slot
176+ self .assertFalse (r .try_close ())
177+
178+ # Clear the reference from slots
179+ set_value (slots , None )
180+
181+ # Check that the region was closed.
182+ self .assertFalse (r .is_open ())
183+
184+ # This tests that references stored by `STORE_ATTR_INSTANCE_VALUE` are known
185+ # by the write barrier
186+ def test_store_attr_instance_value_add_reference (self ):
187+ def set_value (obj , val ):
188+ obj .attr = val # This triggers the STORE_ATTR_INSTANCE_VALUE opcode
189+
190+ # Setup a region and a class with attributes
191+ r = Region ()
192+ r .attr = self .ClassWithAttr ()
193+ self .assertTrue (r .owns_object (r .attr ))
194+
195+ # Run `set_value` multiple times to optimize it
196+ set_value (r .attr , None )
197+ set_value (r .attr , {})
198+
199+ # Create a new local object
200+ new_object = {}
201+ new_object ["data" ] = {}
202+
203+ # Store the object in attributes
204+ set_value (r .attr , new_object )
205+
206+ # Verify the region ownership
207+ self .assertTrue (r .owns_object (new_object ))
208+ self .assertTrue (r .owns_object (new_object ["data" ]))
209+
210+ # This tests that references removed by `STORE_ATTR_INSTANCE_VALUE` are known
211+ # by the write barrier
212+ def test_store_attr_instance_value_remove_reference (self ):
213+ def set_value (obj , val ):
214+ obj .attr = val # This triggers the STORE_ATTR_INSTANCE_VALUE opcode
215+
216+ # Setup a region and a class with attributes
217+ r = Region ()
218+ r .data = {}
219+ attr = self .ClassWithAttr ()
220+
221+ # Run `set_value` multiple times to optimize it
222+ set_value (attr , None )
223+ set_value (attr , {})
224+
225+ # Create local reference into the region
226+ set_value (attr , r .data )
227+
228+ # Make sure the region knows about the reference from the slot
229+ self .assertFalse (r .try_close ())
230+
231+ # Clear the reference from attributes
232+ set_value (attr , None )
233+
234+ # Check that the region was closed.
235+ self .assertFalse (r .is_open ())
236+
237+ if __name__ == "__main__" :
238+ unittest .main ()
0 commit comments