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