1/*
2 * Copyright (c) 2011 Apple Inc. All rights reserved.
3 *
4 * @APPLE_APACHE_LICENSE_HEADER_START@
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 *     http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 *
18 * @APPLE_APACHE_LICENSE_HEADER_END@
19 */
20//
21//  BlockLifetime.m
22//  Copyright (c) 2008-2011 Apple Inc. All rights reserved.
23//
24
25#import "Configuration.h"
26#import "WhiteBoxTest.h"
27
28/*
29 BlockLifetime implements the following test scenario:
30 Allocate a test block.
31 Perform full collection.
32 Verify test block was scanned exactly once.
33 Verify test block remained local.
34 Assign test block to a global variable.
35 Verify test block became global.
36 Clear global variable, assign test block to ivar.
37 Loop:
38 run generational collection
39 verify block is scanned exactly once and age decrements by 1
40 until test block age becomes 0.
41 Run a full collection (to clear write barriers).
42 Verify block is scanned exactly once.
43 Run a generational collection.
44 Verify block *not* scanned.
45 Clear test block ivar.
46 Run generational collection.
47 Verify block *not* scanned and *not* collected.
48 Run a full collection.
49 Verify block was collected.
50 */
51
52@interface BlockLifetime : WhiteBoxTest {
53    BOOL _becameGlobal;
54    BOOL _scanned;
55    BOOL _collected;
56    BOOL _shouldCollect;
57    uint32_t _age;
58    id _testBlock;
59    vm_address_t _disguisedTestBlock;
60}
61
62@end
63
64@interface BlockLifetimeTestObject : NSObject
65{
66    @public
67    id anObject;
68}
69@end
70@implementation BlockLifetimeTestObject
71@end
72
73@interface BlockLifetime(internal)
74- (void)globalTransition;
75- (void)ageLoop;
76- (void)clearBarriers;
77- (void)generationalScan;
78- (void)verifyGenerationalScan;
79- (void)uncollectedCheck;
80- (void)collectedCheck;
81@end
82
83
84@implementation BlockLifetime
85
86static id _globalTestBlock = nil;
87
88- (void)allocLocalBlock
89{
90    _disguisedTestBlock = [self disguise:[BlockLifetimeTestObject new]];
91    [self testThreadStackBuffer][0] = (id)[self undisguise:_disguisedTestBlock];
92}
93
94- (void)makeTestBlockGlobal
95{
96    _globalTestBlock = (id)[self undisguise:_disguisedTestBlock];
97    [self testThreadStackBuffer][0] = nil;
98}
99
100// First step: allocate a new object and request a full collection.
101// The new object should be thread local at this point.
102- (void)performTest
103{
104    [self performSelectorOnTestThread:@selector(allocLocalBlock)];
105
106    // Run a full collection
107    [self requestFullCollectionWithCompletionCallback:^{ [self globalTransition]; }];
108}
109
110// defeat the write barrier card optimization by touching the object
111- (void)touchObject
112{
113    BlockLifetimeTestObject *object = (BlockLifetimeTestObject *)[self undisguise:_disguisedTestBlock];
114    object->anObject = self;
115    object->anObject = nil;
116}
117
118// verify the block got scanned, flag an error if it did not
119#define verifyScanned() \
120do { if (!_scanned) { [self fail:[NSString stringWithFormat:@"block was not scanned as expected in %@", NSStringFromSelector(_cmd)]]; [self testFinished]; return; } _scanned = NO; } while(0)
121
122
123// Second step: transition the block to global
124- (void)globalTransition
125{
126    verifyScanned();
127
128    // verify the block isn't global already
129    if (_becameGlobal) {
130        [self fail:@"block became global prematurely"];
131        [self testFinished];
132        return;
133    }
134
135    // make the block go global and verify that we saw the transition
136    // it's possible we never saw the transition because it went global before we started watching
137    // that situation probably merits investigation
138    [self performSelectorOnTestThread:@selector(makeTestBlockGlobal)];
139
140    if (!_becameGlobal) {
141        [self fail:@"block failed to become global"];
142        [self testFinished];
143        return;
144    }
145
146    // now the only reference to the test block is in _globalTestBlock
147
148    // do generational collections until the block becomes old
149    [self touchObject];
150    [self requestGenerationalCollectionWithCompletionCallback:^{ [self ageLoop]; }];
151}
152
153
154// This method runs generational collections until the block reaches age 0
155- (void)ageLoop
156{
157    SEL next;
158    verifyScanned();
159
160    if (_age != 0) {
161        // set up for the next iteration
162        [self touchObject];
163        [self requestGenerationalCollectionWithCompletionCallback:^{ [self ageLoop]; }];
164    } else {
165        // We are done iterating, the block is now age 0.
166        // Now we want to verify that the block does *NOT* get scanned during a generational collection.
167        // First must run a full collection to clear write barrier cards.
168        [self requestFullCollectionWithCompletionCallback:^{ [self clearBarriers]; }];
169    }
170}
171
172
173// The full collection does not clear write barrier cards. Run a generational to clear them.
174- (void)clearBarriers
175{
176    verifyScanned();
177    [self requestGenerationalCollectionWithCompletionCallback:^{ [self generationalScan]; }];
178}
179
180
181// Now the object is old and should have the write barrier cards cleared.
182// Verify that a generational scan does *not* scan the block.
183- (void)generationalScan
184{
185    verifyScanned();
186    [self requestGenerationalCollectionWithCompletionCallback:^{ [self verifyGenerationalScan]; }];
187}
188
189
190// The last scan was generational, the block is old, and write barriers were cleared. Verify the block was *not* scanned.
191- (void)verifyGenerationalScan
192{
193    if (_scanned) {
194        [self fail:@"age 0 block scanned during generational collection"];
195        [self testFinished];
196        return;
197    }
198
199    // Now just verify that the block gets collected
200    _globalTestBlock = nil;
201    _shouldCollect = YES;
202
203    // Run a generational and verify the old block was not collected.
204    [self requestGenerationalCollectionWithCompletionCallback:^{ [self uncollectedCheck]; }];
205}
206
207
208// We expect that the old garbage block would not be collected by a generational collection
209- (void)uncollectedCheck
210{
211    if (_collected) {
212        [self fail:@"old block collected by generational collection"];
213        [self testFinished];
214        return;
215    }
216
217    // now run a full collection
218    [self requestFullCollectionWithCompletionCallback:^{ [self collectedCheck]; }];
219}
220
221
222// A full collection should have collected the garbage block.
223- (void)collectedCheck
224{
225    if (!_collected)
226        [self fail:@"block not collected after full collection"];
227    else
228        [self passed];
229    [self testFinished];
230}
231
232
233
234// Watch for our test block becoming global.
235- (void)blockBecameGlobal:(void *)block withAge:(uint32_t)age
236{
237    if (block == [self undisguise:_disguisedTestBlock]) {
238        _becameGlobal = YES;
239        _age = age;
240    }
241    [super blockBecameGlobal:block withAge:age];
242}
243
244
245// Monitor the aging of our test block
246- (void)blockMatured:(void *)block newAge:(uint32_t)age
247{
248    if (block == [self undisguise:_disguisedTestBlock]) {
249        if (age != _age - 1)
250            [self fail:@"Age decrease != 1 after mature"];
251        _age = age;
252    }
253    [super blockMatured:block newAge:age];
254}
255
256
257// Check if the test block is in the garbage list
258- (void)endHeapScanWithGarbage:(void **)garbage_list count:(size_t)count
259{
260    if ([self block:[self undisguise:_disguisedTestBlock] isInList:garbage_list count:count]) {
261        if (!_shouldCollect)
262            [self fail:@"block was collected prematurely"];
263        _collected = YES;
264    }
265    [super endHeapScanWithGarbage:garbage_list count:count];
266}
267
268
269// Monitor when our test block is scanned, and verify it never gets scanned twice in the same collection.
270- (void)scanBlock:(void *)block endAddress:(void *)end
271{
272    if (block == [self undisguise:_disguisedTestBlock]) {
273        if (_scanned) {
274            [self fail:@"block scanned twice"];
275        }
276        _scanned = YES;
277    }
278    [super scanBlock:block endAddress:end];
279}
280
281- (void)scanBlock:(void *)block endAddress:(void *)end withLayout:(const unsigned char *)map
282{
283    [self scanBlock:block endAddress:end];
284    [super scanBlock:block endAddress:end withLayout:map];
285}
286
287@end
288