/* * Copyright (c) 2009 Apple Inc. All rights reserved. * * @APPLE_APACHE_LICENSE_HEADER_START@ * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * @APPLE_APACHE_LICENSE_HEADER_END@ */ // // BlockLifetime.m // auto // // Created by Josh Behnke on 5/19/08. // Copyright 2008 Apple Inc. All rights reserved. // #import "BlockLifetime.h" #import "AutoConfiguration.h" @interface BlockLifetimeTestObject : NSObject { @public id anObject; } @end @implementation BlockLifetimeTestObject @end @implementation BlockLifetime static id _globalTestBlock = nil; // First step: allocate a new object and request a full collection. // The new object should be thread local at this point. - (void)startTest { // We need our test object to live in a space in memory that has its own write barrier card. // We try to accomplish this by allocating a bunch of objects that we then throw away. // BlockLifetimeTestObject *first = [BlockLifetimeTestObject new]; // BlockLifetimeTestObject *current = first; // for (int i=0; i<Auto::write_barrier_quantum / sizeof(BlockLifetimeTestObject) * 10; i++) { // current->anObject = [BlockLifetimeTestObject new]; // current = (BlockLifetimeTestObject *)current->anObject; // } // Create the test block and assign it directly to _disguisedBlock. // This enables all the probes that look for the test block. _disguisedTestBlock = [self disguise:[BlockLifetimeTestObject new]]; // We store the test block on the stack. STACK_POINTER_TYPE *stackPointers = [self stackPointers]; stackPointers[0] = (id)[self undisguise:_disguisedTestBlock]; // Run a full collection [self requestFullCollection]; _testThreadSynchronizer = [self setNextTestSelector:@selector(globalTransition)]; } // defeat the write barrier card optimization by touching the object - (void)touchObject { BlockLifetimeTestObject *object = (BlockLifetimeTestObject *)[self undisguise:_disguisedTestBlock]; object->anObject = self; object->anObject = nil; } // verify the block got scanned, flag an error if it did not - (void)verifyScanned { if (!_scanned) [self fail:"block was not scanned as expected"]; _scanned = NO; } // Second step: transition the block to global - (void)globalTransition { [self verifyScanned]; // verify the block isn't global already if (_becameGlobal) [self fail:"block became global prematurely"]; // make the block go global and verify that we saw the transition // it's possible we never saw the transition because it went global before we started watching // that situation probably merits investigation _globalTestBlock = (id)[self undisguise:_disguisedTestBlock]; if (!_becameGlobal) [self fail:"block failed to become global"]; // now switch the block's reference off the stack and into an ivar STACK_POINTER_TYPE *stackPointers = [self stackPointers]; stackPointers[0] = nil; // now the only reference is in _globalTestBlock // do generational collections until the block becomes old [self touchObject]; [self requestGenerationalCollection]; _testThreadSynchronizer = [self setNextTestSelector:@selector(ageLoop)]; } // This method runs generational collections until the block reaches age 0 - (void)ageLoop { SEL next; [self verifyScanned]; if (_age != 0) { // set up for the next iteration next = _cmd; [self touchObject]; [self requestGenerationalCollection]; } else { // We are done iterating, the block is now age 0. // Now we want to verify that the block does *NOT* get scanned during a generational collection. // First must run a full collection to clear write barrier cards. next = @selector(clearBarriers); [self requestFullCollection]; } _testThreadSynchronizer = [self setNextTestSelector:next]; } // The full collection does not clear write barrier cards. Run a generational to clear them. - (void)clearBarriers { [self verifyScanned]; [self requestGenerationalCollection]; _testThreadSynchronizer = [self setNextTestSelector:@selector(generationalScan)]; } // Now the object is old and should have the write barrier cards cleared. // Verify that a generational scan does *not* scan the block. - (void)generationalScan { [self verifyScanned]; [self requestGenerationalCollection]; _testThreadSynchronizer = [self setNextTestSelector:@selector(verifyGenerationalScan)]; } // The last scan was generational, the block is old, and write barriers were cleared. Verify the block was *not* scanned. - (void)verifyGenerationalScan { if (_scanned) [self fail:"age 0 block scanned during generational collection"]; // Now just verify that the block gets collected _globalTestBlock = nil; _shouldCollect = YES; // Run a generational and verify the old block was not collected. [self requestGenerationalCollection]; _testThreadSynchronizer = [self setNextTestSelector:@selector(uncollectedCheck)]; } // We expect that the old garbage block would not be collected by a generational collection - (void)uncollectedCheck { if (_collected) [self fail:"old block collected by generational collection"]; // now run a full collection [self requestFullCollection]; _testThreadSynchronizer = [self setNextTestSelector:@selector(collectedCheck)]; } // A full collection should have collected the garbage block. - (void)collectedCheck { if (!_collected) [self fail:"block not collected after full collection"]; // done with test } // In this test we always want to wake up the test thread when a collection completes. - (void)collectionComplete { [_testThreadSynchronizer signal]; [super heapCollectionComplete]; } // Watch for our test block becoming global. - (void)blockBecameGlobal:(void *)block withAge:(uint32_t)age { if (block == [self undisguise:_disguisedTestBlock]) { _becameGlobal = YES; _age = age; } [super blockBecameGlobal:block withAge:age]; } // Monitor the aging of our test block - (void)blockMatured:(void *)block newAge:(uint32_t)age { if (block == [self undisguise:_disguisedTestBlock]) { if (age != _age - 1) [self fail:"Age decrease != 1 after mature"]; _age = age; } [super blockMatured:block newAge:age]; } // Check if the test block is in the garbage list - (void)endHeapScanWithGarbage:(void **)garbage_list count:(size_t)count { if ([self block:[self undisguise:_disguisedTestBlock] isInList:garbage_list count:count]) { if (!_shouldCollect) [self fail:"block was collected prematurely"]; _collected = YES; } [super endHeapScanWithGarbage:garbage_list count:count]; } // Monitor when our test block is scanned, and verify it never gets scanned twice in the same collection. - (void)scanBlock:(void *)block endAddress:(void *)end { if (block == [self undisguise:_disguisedTestBlock]) { if (_scanned) { [self fail:"block scanned twice"]; } _scanned = YES; } [super scanBlock:block endAddress:end]; } - (void)scanBlock:(void *)block endAddress:(void *)end withLayout:(const unsigned char *)map { [self scanBlock:block endAddress:end]; [super scanBlock:block endAddress:end withLayout:map]; } - (void)scanBlock:(void *)block endAddress:(void *)end withWeakLayout:(const unsigned char *)map { [self scanBlock:block endAddress:end]; [super scanBlock:block endAddress:end withWeakLayout:map]; } @end