/* * 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@ */ /* AutoZone.h Copyright (c) 2004-2009 Apple Inc. All rights reserved. */ #pragma once #ifndef __AUTO_ZONE_CORE__ #define __AUTO_ZONE_CORE__ #include "auto_zone.h" #include "auto_impl_utilities.h" #include "auto_weak.h" #include "AutoBitmap.h" #include "AutoConfiguration.h" #include "AutoDefs.h" #include "AutoLarge.h" #include "AutoLock.h" #include "AutoAdmin.h" #include "AutoRegion.h" #include "AutoStatistics.h" #include "AutoSubzone.h" #include "AutoThread.h" #include #include #define USE_DISPATCH_QUEUE 1 #if USE_DISPATCH_QUEUE #include #endif namespace Auto { // // Forward declarations. // class Monitor; // // TrackedPageAllocator -- Tracks the number of pages in use in a PointerArray by calling Statistics::add_admin() with all // requested allocation and deallocation sizes. // class TrackedPageAllocator { private: Statistics &_stats; public: TrackedPageAllocator(Statistics &st) : _stats(st) {} TrackedPageAllocator(const TrackedPageAllocator &allocator) : _stats(allocator._stats) {} inline void *allocate_memory(usword_t size) { _stats.add_admin(size); return Auto::allocate_memory(size); } inline void deallocate_memory(void *address, usword_t size) { _stats.add_admin(-(intptr_t)size); Auto::deallocate_memory(address, size); } inline void uncommit_memory(void *address, usword_t size) { Auto::uncommit_memory(address, size); } inline void copy_memory(void *dest, void *source, usword_t size) { Auto::copy_memory(dest, source, size); } }; typedef PointerArray PointerList; template class EnliveningHelper : public BarrierType { Thread &_thread; public: EnliveningHelper(Thread &thread) : BarrierType(thread.enlivening_queue().needs_enlivening()), _thread(thread) {} void enliven_block(void *block) { _thread.enlivening_queue().add(block); } }; //----- ScanStack -----// class ScanStack { private: void ** _address; // base address of stack void ** _end; // stack last + 1 void ** _cursor; // top of stack + 1 void ** _highwater; // largest stack required public: // // Constructor // ScanStack() : _address(NULL) , _end(NULL) , _cursor(NULL) , _highwater(NULL) {} // // set_range // // Set the stack base address and end. // inline void set_range(Range range) { set_range(range.address(), range.end()); } inline void set_range(void *address, void *end) { _address = (void **)address; _end = (void **)end; _cursor = (void **)address; _highwater = (void **)address; } inline void set_range(void *address, usword_t size) { set_range(address, displace(address, size)); } // // reset // // Resets the stack back to initial state. // void reset() { _cursor = _address; _highwater = _address; } // // is_allocated // // Returns true if a stack has been allocated. // inline bool is_allocated() const { return _address != NULL; } // // is_empty // // Returns true if a stack contains no elements. // inline bool is_empty() const { return _cursor == _address; } // // is_overflow // // Returns true if a stack overflow condition has occurred. // inline bool is_overflow() const { return _cursor == _end; } // // push // // Push the block onto the scan stack. // inline void push(void *block) { // if overflow then lock at overflow if (!is_overflow()) { *_cursor++ = block; if (_highwater < _cursor) _highwater = _cursor; } } // // top // // Return the top block in the stack. // inline void *top() { // If non empty return TOS (if overflow then lock at overflow) if (!is_empty() && !is_overflow()) return _cursor[-1]; return NULL; } // // pop // // Return the next block to scan. // inline void *pop() { // If non empty return TOS (if overflow then lock at overflow) if (!is_empty() && !is_overflow()) return *--_cursor; return NULL; } // // max items // // return the maximum number of items on the stack since last reset // inline unsigned long max() { return _highwater - _address; } }; typedef std::vector > RangeVector; class ObjectAssocationHashMap : public PtrPtrMap, public AuxAllocated {}; // Reduce space usage for each association. typedef __gnu_cxx::hash_map > AssocationsHashMap; //----- Zone -----// enum State { idle, scanning, enlivening, finalizing, reclaiming }; class Zone { friend class MemoryScanner; #define INVALID_THREAD_KEY_VALUE ((Thread *)-1) public: malloc_zone_t basic_zone; bool multithreaded; // if set, will run collector on dedicated thread volatile int32_t collector_disable_count; // counter for external disable-collector API volatile int32_t collector_collecting; // internal guard to prevent recursion and/or several threads attempting gc at once uint32_t collection_count; // collection control auto_collection_control_t control; // statistics auto_lock_t stats_lock; // only affects fields below; only a write lock; read access may not be accurate, as we lock statistics independently of the main data structures auto_statistics_t stats; // weak references unsigned num_weak_refs; unsigned max_weak_refs; struct weak_entry_t *weak_refs_table; spin_lock_t weak_refs_table_lock; #if USE_DISPATCH_QUEUE dispatch_queue_t collection_queue; dispatch_block_t collection_block; #else pthread_t collection_thread; pthread_cond_t collection_requested; #endif pthread_mutex_t collection_mutex; volatile uint32_t collection_requested_mode; pthread_cond_t collection_status; volatile uint32_t collection_status_state; private: // // Shared information // // watch out for static initialization static volatile int32_t _zone_count; // used to generate _zone_id static Zone *_first_zone; // for debugging // // thread management // Thread *_registered_threads; // linked list of registered threads pthread_key_t _registered_threads_key; // pthread key for looking up Thread instance for this zone pthread_mutex_t _registered_threads_mutex; // protects _registered_threads and _enlivening_enabled bool _enlivening_enabled; // tracks whether new threads should be initialized with enlivening on bool _enlivening_complete; // tracks whether or not enlivening has been performed on this collection cycle. pthread_mutex_t _mark_bits_mutex; // protects the per-Region and Large block mark bits. // // memory management // Bitmap _in_subzone; // indicates which allocations are used for subzone region Bitmap _in_large; // indicates which allocations are used for large blocks Large *_large_list; // doubly linked list of large allocations spin_lock_t _large_lock; // protects _large_list, _in_large, and large block refcounts PtrHashSet _roots; // hash set of registered roots (globals) spin_lock_t _roots_lock; // protects _roots RangeVector _datasegments; // registered data segments. spin_lock_t _datasegments_lock; // protects _datasegments PtrHashSet _zombies; // hash set of zombies spin_lock_t _zombies_lock; // protects _zombies Region *_region_list; // singly linked list of subzone regions spin_lock_t _region_lock; // protects _region_list PtrIntHashMap _retains; // STL hash map of retain counts spin_lock_t _retains_lock; // protects _retains bool _repair_write_barrier; // true if write barrier needs to be repaired after full collection. ScanStack _scan_stack; // stack used suring scanning Range _coverage; // range of managed memory spin_lock_t _coverage_lock; // protects _coverage TrackedPageAllocator _page_allocator; // Statistics tracking page allocator. Statistics _stats; // statistics for this zone int32_t _allocation_threshold; // byte allocation counter (reset after each collection). PointerList _garbage_list; // vm_map allocated pages to hold the garbage list. AssocationsHashMap _associations; // associative references object -> ObjectAssocationHashMap*. spin_lock_t _associations_lock; // protects _associations bool _scanning_associations; // tells volatile enum State _state; // the state of the collector #if UseArena void *_arena; // the actual 32G space (region low, larges high) void *_large_start; // half-way into arena + size of bitmaps needed for region Bitmap _large_bits; // bitmap of top half - tracks quanta used for large blocks spin_lock_t _large_bits_lock; // protects _large_bits #endif Admin _small_admin; // small quantum admin Admin _medium_admin; // medium quantum admin // // thread safe Large deallocation routines. // void deallocate_large(void *block); // // allocate_region // // Allocate and initialize a new subzone region. // Region *allocate_region(); // // allocate_large // // Allocates a large block from the universal pool (directly from vm_memory.) // void *allocate_large(Thread &thread, usword_t &size, const unsigned layout, bool clear, bool refcount_is_one); // // find_large // // Find a large block in this zone. // inline Large *find_large(void *block) { return Large::large(block); } // // deallocate_small_medium // // Release memory allocated for a small block // void deallocate_small_medium(void *block); public: // // raw memory allocation // #if UseArena // set our one region up void *arena_allocate_region(usword_t newsize); #endif // on 32-bit w/o arena, goes directly to vm system // w/arena, allocate from the top of the arena void *arena_allocate_large(usword_t size); // // raw memory deallocation // void arena_deallocate(void *, size_t size); // // admin_offset // // Return the number of bytes to the beginning of the first admin data item. // static inline const usword_t admin_offset() { return align(sizeof(Zone), page_size); } // // bytes_needed // // Calculate the number of bytes needed for zone data // static inline const usword_t bytes_needed() { usword_t in_subzone_size = Bitmap::bytes_needed(subzone_quantum_max); usword_t in_large_size = Bitmap::bytes_needed(allocate_quantum_large_max); #if UseArena usword_t arena_size = Bitmap::bytes_needed(allocate_quantum_large_max); #else usword_t arena_size = 0; #endif return admin_offset() + in_subzone_size + in_large_size + arena_size; } // // allocator // inline void *operator new(const size_t size) { #if DEBUG // allocate zone data void *allocation_address = allocate_guarded_memory(bytes_needed()); #else void *allocation_address = allocate_memory(bytes_needed()); #endif if (!allocation_address) error("Can not allocate zone"); return allocation_address; } // // deallocator // inline void operator delete(void *zone) { #if DEBUG // release zone data if (zone) deallocate_guarded_memory(zone, bytes_needed()); #else if (zone) deallocate_memory(zone, bytes_needed()); #endif } // // setup_shared // // Initialize information used by all zones. // static void setup_shared(); // // allocate_thread_key // // attempt to allocate a static pthread key for use when creating a new zone // returns the new key, or 0 if no keys are available. // static pthread_key_t allocate_thread_key(); // // Constructors // Zone(pthread_key_t thread_registration_key); // // Destructor // ~Zone(); // // zone // // Returns the lowest index zone - for debugging purposes only (no locks.) // static inline Zone *zone() { return _first_zone; } // // Accessors // inline Admin *small_admin() { return &_small_admin; } inline Admin *medium_admin() { return &_medium_admin; } inline Thread *threads() { return _registered_threads; } inline pthread_mutex_t *threads_mutex() { return &_registered_threads_mutex; } inline Region *region_list() { return _region_list; } inline Large *large_list() { return _large_list; } inline spin_lock_t *large_lock() { return &_large_lock; } inline Statistics &statistics() { return _stats; } inline Range &coverage() { return _coverage; } inline TrackedPageAllocator &page_allocator() { return _page_allocator; } inline PointerList &garbage_list() { return _garbage_list; } inline Thread *current_thread_direct() { if (_pthread_has_direct_tsd()) { #define CASE_FOR_DIRECT_KEY(key) case key: return (Thread *)_pthread_getspecific_direct(key) switch (_registered_threads_key) { CASE_FOR_DIRECT_KEY(__PTK_FRAMEWORK_GC_KEY0); CASE_FOR_DIRECT_KEY(__PTK_FRAMEWORK_GC_KEY1); CASE_FOR_DIRECT_KEY(__PTK_FRAMEWORK_GC_KEY2); CASE_FOR_DIRECT_KEY(__PTK_FRAMEWORK_GC_KEY3); CASE_FOR_DIRECT_KEY(__PTK_FRAMEWORK_GC_KEY4); CASE_FOR_DIRECT_KEY(__PTK_FRAMEWORK_GC_KEY5); CASE_FOR_DIRECT_KEY(__PTK_FRAMEWORK_GC_KEY6); CASE_FOR_DIRECT_KEY(__PTK_FRAMEWORK_GC_KEY7); CASE_FOR_DIRECT_KEY(__PTK_FRAMEWORK_GC_KEY8); CASE_FOR_DIRECT_KEY(__PTK_FRAMEWORK_GC_KEY9); default: return NULL; } } else { return (Thread *)pthread_getspecific(_registered_threads_key); } } // // current_thread // // If the calling thread is registered with the collector, returns the registered Thread object. // If the calling thread is not registered, returns NULL. // inline Thread *current_thread() { Thread *thread = current_thread_direct(); if (__builtin_expect(thread == INVALID_THREAD_KEY_VALUE, 0)) { // If we see this then it means some pthread destructor ran after the // zone's destructor and tried to look up a Thread object (tried to perform a GC operation). // The collector's destructor needs to run last. We treat this as a fatal error so we will notice immediately. // Investigate as a pthreads bug in the ordering of static (Apple internal) pthread keys. auto_fatal("Zone::current_thread(): pthread looked up after unregister. Pthreads static key destructor ordering issue?\n"); } return thread; } // // registered_thread // // Returns the Thread object for the calling thread. // If the calling thread is not registered, it is registered implicitly, and if warn_if_unregistered is true an error message is logged. // inline Thread ®istered_thread() { Thread *thread = current_thread(); if (!thread) { auto_error(this, "GC operation on unregistered thread. Thread registered implicitly. Break on auto_zone_thread_registration_error() to debug.\n", NULL); auto_zone_thread_registration_error(); return register_thread(); } return *thread; } // // destroy_registered_thread // // Pthread key destructor. The collector has a critical dependency on the ordering of pthread destructors. // This destructor must run after any other code which might possibly call into the collector. // We have arranged with pthreads to have our well known key destructor called after any dynamic keys and // any static (Apple internal) keys that might call into the collector (Foundation, CF). On the last iteration // (PTHREAD_DESTRUCTOR_ITERATIONS) we unregister the thread. // Note that this implementation is non-portable due to our agreement with pthreads. // static void destroy_registered_thread(void *key_value); inline ScanStack &scan_stack() { return _scan_stack; } inline void set_state(enum State ns) { _state = ns; } inline bool is_state(enum State ns) { return _state == ns; } inline spin_lock_t *associations_lock() { return &_associations_lock; } inline AssocationsHashMap &assocations() { return _associations; } #if UseArena inline void * arena() { return _arena; } #else inline void * arena() { return (void *)0; } #endif inline bool threshold_reached() const { return _allocation_threshold <= 0; } // threshold triggers GC as it crosses below zero inline void reset_threshold() { _allocation_threshold = control.collection_threshold; } inline void adjust_threshold_counter(usword_t n) { _allocation_threshold -= n; } // // subzone_index // // Returns a subzone index for an arbitrary pointer. Note that this is relative to absolute memory. subzone_index in // Region is relative memory. // static inline const usword_t subzone_index(void *address) { return (((usword_t)address & mask(arena_size_log2)) >> subzone_quantum_log2); } // // subzone_count // // Returns a number of subzone quantum for a given size. // static inline const usword_t subzone_count(const size_t size) { return partition2(size, subzone_quantum_log2); } // // activate_subzone // // Marks the subzone as being active. // inline void activate_subzone(Subzone *subzone) { _in_subzone.set_bit_atomic(subzone_index(subzone)); } // // address_in_arena // // Given arbitrary address, is it in the arena of GC allocated memory // inline bool address_in_arena(void *address) const { #if UseArena //return (((usword_t)address) >> arena_size_log2) == (((usword_t)_arena) >> arena_size_log2); return ((usword_t)address & ~mask(arena_size_log2)) == (usword_t)_arena; #else return true; #endif } // // in_subzone_memory // // Returns true if address is in auto managed memory. // inline const bool in_subzone_memory(void *address) const { return address_in_arena(address) && (bool)_in_subzone.bit(subzone_index(address)); } // // in_large_memory // // Returns true if address is in auto managed memory. Since side data is smaller than a large quantum we'll not // concern ourselves with rounding. // inline const bool in_large_memory(void *address) const { return address_in_arena(address) && (bool)_in_large.bit(Large::quantum_index(address)); } // // in_zone_memory // // Returns true in address is in auto managed memory. // inline const bool in_zone_memory(void *address) const { return in_subzone_memory(address) || in_large_memory(address); } // // good_block_size // // Return a block size which maximizes memory usage (no slop.) // static inline const usword_t good_block_size(usword_t size) { if (size <= allocate_quantum_large) return align2(size, allocate_quantum_medium_log2); return align2(size, allocate_quantum_small_log2); } // // is_block // // Determines if the specfied address is a block in this zone. // inline bool is_block(void *address) { return _coverage.in_range(address) && block_is_start(address); } // // block_allocate // // Allocate a block of memory from the zone. layout indicates whether the block is an // object or not and whether it is scanned or not. // void *block_allocate(Thread &thread, const size_t size, const unsigned layout, const bool clear, bool refcount_is_one); // // block_deallocate // // Release a block of memory from the zone, lazily while scanning. // void block_deallocate(void *block); // // block_is_start // // Return if the arbitrary address is the start of a block. // Broken down because of high frequency of use. // inline bool block_is_start(void *address) { if (in_subzone_memory(address)) { return Subzone::subzone(address)->block_is_start(address); } else if (in_large_memory(address)) { return Large::is_start(address); } return false; } // // block_start_large // // Return the start of a large block. // void *block_start_large(void *address); // // block_start // // Return the base block address of an arbitrary address. // Broken down because of high frequency of use. // void *block_start(void *address); // // block_size // // Return the size of a specified block. // usword_t block_size(void *address); // // block_layout // // Return the layout of a block. // int block_layout(void *address); // // block_set_layout // // Set the layout of a block. // void block_set_layout(void *address, int layout); // // get_refcount_small_medium // // Return the refcount of a candidate small/medium block. // int get_refcount_small_medium(Subzone *subzone, void *address); private: // // inc_refcount_small_medium // // Increments the refcount of a small/medium block, returning the new value. // Requires subzone->admin()->lock() to be held, to protect side data. // int inc_refcount_small_medium(Subzone *subzone, void *block); // // dec_refcount_small_medium // // Decrements the refcount of a small/medium block, returning the new value. // Requires subzone->admin()->lock() to be held, to protect side data. // int dec_refcount_small_medium(Subzone *subzone, void *block); // // close_locks // // acquires all locks for critical sections whose behavior changes during scanning // enlivening_lock is and must already be held; all other critical sections must // order their locks with enlivening_lock acquired first // inline void close_locks() { // acquire all locks for sections that have predicated enlivening work // (These locks are in an arbitary order) spin_lock(_small_admin.lock()); spin_lock(_medium_admin.lock()); // Eventually we'll acquire these as well as we reintroduce ConditionBarrier //spin_lock(&_retains_lock); // retain/release //spin_lock(&weak_refs_table_lock); // weak references //spin_lock(&_associations_lock); // associative references //spin_lock(&_roots_lock); // global roots } inline void open_locks() { //spin_unlock(&_roots_lock); //spin_unlock(&_associations_lock); //spin_unlock(&weak_refs_table_lock); //spin_unlock(&_retains_lock); spin_unlock(_medium_admin.lock()); spin_unlock(_small_admin.lock()); } public: // // is_locked // // Called by debuggers, with all other threads suspended, to determine if any locks are held that might cause a deadlock from this thread. // bool is_locked(); // // add_subzone // // when out of subzones, add another one, allocating region if necessary // return false if region can't be allocated // bool add_subzone(Admin *admin); // // block_refcount // // Returns the reference count of the specified block. // int block_refcount(void *block); // // block_increment_refcount // // Increment the reference count of the specified block. // int block_increment_refcount(void *block); // // block_decrement_refcount // // Decrement the reference count of the specified block. // int block_decrement_refcount(void *block); // // block_refcount_and_layout // // Accesses the reference count and layout of the specified block. // void block_refcount_and_layout(void *block, int *refcount, int *layout); // // is_new // // Returns true if the known-to-be-a-block was recently created. // inline bool is_new(void *block) { if (in_subzone_memory(block)) { return Subzone::subzone(block)->is_new(block); } else if (in_large_memory(block) && Large::is_start(block)) { return Large::is_new(block); } return false; } // // is_local // // Returns true if the known-to-be-a-block is a thread local node. // inline bool is_local(void *block) { if (in_subzone_memory(block)) { return Subzone::subzone(block)->is_thread_local(block); } return false; } // // block_is_garbage // // Returns true if the specified block is flagged as garbage. Only valid // during finalization. // inline bool block_is_garbage(void *block) { if (in_subzone_memory(block)) { Subzone *subzone = Subzone::subzone(block); return subzone->block_is_start(block) && subzone->is_garbage(block); } else if (in_large_memory(block) && Large::is_start(block)) { return Large::large(block)->is_garbage(); } return false; } // // block_is_marked // // if the block is already marked, say so. // Blocks are marked only by the collector thread in an unsynchronized way, so this may return false // if the collector's memmory write hasn't propogated to the caller's cpu. inline bool block_is_marked(void *block) { if (in_subzone_memory(block)) { Subzone *subzone = Subzone::subzone(block); return subzone->block_is_start(block) && subzone->is_marked(block); } else if (in_large_memory(block) && Large::is_start(block)) { return Large::large(block)->is_marked(); } return false; } // // associations_should_be_marked // // Predicate to test whether a block's associations should be marked. // inline bool associations_should_be_marked(void *address) { if (in_subzone_memory(address)) { Subzone *subzone = Subzone::subzone(address); return subzone->block_is_start(address) && subzone->is_marked(address); } else if (in_large_memory(address) && Large::is_start(address)) { return Large::large(address)->is_marked(); } // Treat non-block pointers as unconditionally live. return true; } // // set_associative_ref // // Creates an association between a given block, a unique pointer-sized key, and a pointer value. // void set_associative_ref(void *block, void *key, void *value); // // get_associative_ref // // Returns the associated pointer value for a given block and key. // void *get_associative_ref(void *block, void *key); // // erase_associations_internal // // Assuming association lock held, do the dissassociation dance // void erase_associations_internal(void *block); // // erase_assocations // // Removes all associations for a given block. Used to // clear associations for explicitly deallocated blocks. // When the collector frees blocks, it uses a different code // path, to minimize locking overhead. See free_garbage(). // void erase_associations(void *block); // // erase_associations_in_range // // Called by remove_datasegment() below, when a data segment is unloaded // to automatically break associations referenced by global objects (@string constants). // void erase_associations_in_range(const Range &r); // // scan_assocations // // Called by MemoryScanner classes to enliven associative references. // void scan_associations(MemoryScanner &scanner); // // pend_associations // // Called during associative reference scanning to scan a newly discovered blocks // associations. // void pend_associations(void *block, MemoryScanner &scanner); // // add_root // // Adds the address as a known root. // Performs the assignment in a race-safe way. // Escapes thread-local value if necessary. // inline void add_root(void *root, void *value) { Thread &thread = registered_thread(); thread.block_escaped(this, NULL, value); EnliveningHelper barrier(thread); SpinLock lock(&_roots_lock); if (_roots.find(root) == _roots.end()) { _roots.insert(root); } // whether new or old, make sure it gets scanned // if new, well, that's obvious, but if old the scanner may already have scanned // this root and we'll never see this value otherwise if (barrier && !block_is_marked(value)) barrier.enliven_block(value); *(void **)root = value; } // // add_root_no_barrier // // Adds the address as a known root. // inline void add_root_no_barrier(void *root) { #if DEBUG // this currently fires if somebody uses the wrong version of objc_atomicCompareAndSwap* //if (in_subzone_memory(root)) __builtin_trap(); #endif SpinLock lock(&_roots_lock); if (_roots.find(root) == _roots.end()) { _roots.insert(root); } } // copy_roots // // Takes a snapshot of the registered roots during scanning. // inline void copy_roots(PointerList &list) { SpinLock lock(&_roots_lock); usword_t count = _roots.size(); list.clear_count(); list.grow(count); list.set_count(count); std::copy(_roots.begin(), _roots.end(), (void**)list.buffer()); } // copy_roots // // Takes a snapshot of the registered roots during scanning. // inline void copy_roots(PtrVector &list) { SpinLock lock(&_roots_lock); usword_t count = _roots.size(); list.resize(count); std::copy(_roots.begin(), _roots.end(), list.begin()); } // remove_root // // Removes the address from the known roots. // inline void remove_root(void *root) { SpinLock lock(&_roots_lock); PtrHashSet::iterator iter = _roots.find(root); if (iter != _roots.end()) { _roots.erase(iter); } } // // is_root // // Returns whether or not the address has been registered. // inline bool is_root(void *address) { SpinLock lock(&_roots_lock); PtrHashSet::iterator iter = _roots.find(address); return (iter != _roots.end()); } // // RangeLess // // Compares two ranges, returning true IFF r1 is left of r2 on the number line. // Returns false if the ranges overlap in any way. // struct RangeLess { bool operator()(const Range &r1, const Range &r2) const { return (r1.address() < r2.address()) && (r1.end() <= r2.address()); // overlapping ranges will always return false. } }; // // add_datasegment // // Adds the given data segment address range to a list of known data segments, which is searched by is_global_address(). // inline void add_datasegment(const Range &r) { SpinLock lock(&_datasegments_lock); RangeVector::iterator i = std::lower_bound(_datasegments.begin(), _datasegments.end(), r, RangeLess()); _datasegments.insert(i, r); } // // RangeExcludes // // Returns false if the address lies outside the given range. // struct RangeExcludes { Range _range; RangeExcludes(const Range &r) : _range(r) {} bool operator()(void *address) { return !_range.in_range(address); } }; // // RootRemover // // Used by remove_datasegment() below, removes an address from the // root table. Simply an artifact for use with std::for_each(). // struct RootRemover { PtrHashSet &_roots; RootRemover(PtrHashSet &roots) : _roots(roots) {} void operator()(void *address) { PtrHashSet::iterator iter = _roots.find(address); if (iter != _roots.end()) _roots.erase(iter); } }; // // remove_datasegment // // Removes the given data segment address range from the list of known address ranges. // inline void remove_datasegment(const Range &r) { { SpinLock lock(&_datasegments_lock); // could use std::lower_bound(), or std::equal_range() to speed this up, since they use binary search to find the range. // _datasegments.erase(std::remove(_datasegments.begin(), _datasegments.end(), r, _datasegments.end())); RangeVector::iterator i = std::lower_bound(_datasegments.begin(), _datasegments.end(), r, RangeLess()); if (i != _datasegments.end()) _datasegments.erase(i); } { // When a bundle gets unloaded, scour the roots table to make sure no stale roots are left behind. SpinLock lock(&_roots_lock); PtrVector rootsToRemove; std::remove_copy_if(_roots.begin(), _roots.end(), std::back_inserter(rootsToRemove), RangeExcludes(r)); std::for_each(rootsToRemove.begin(), rootsToRemove.end(), RootRemover(_roots)); } erase_associations_in_range(r); weak_unregister_data_segment(this, r.address(), r.size()); } inline void add_datasegment(void *address, size_t size) { add_datasegment(Range(address, size)); } inline void remove_datasegment(void *address, size_t size) { remove_datasegment(Range(address, size)); } // // is_global_address // // Binary searches the registered data segment address ranges to determine whether the address could be referring to // a global variable. // inline bool is_global_address(void *address) { SpinLock lock(&_datasegments_lock); return std::binary_search(_datasegments.begin(), _datasegments.end(), Range(address, sizeof(void*)), RangeLess()); } #if DEBUG // // DATASEGMENT REGISTRATION UNIT TEST // struct RangePrinter { void operator() (const Range &r) { printf("{ address = %p, end = %p }\n", r.address(), r.end()); } }; inline void print_datasegments() { SpinLock lock(&_datasegments_lock); std::for_each(_datasegments.begin(), _datasegments.end(), RangePrinter()); } void test_datasegments() { Range r1((void*)0x1000, 512), r2((void*)0xA000, 512); add_datasegment(r1); add_datasegment(r2); print_datasegments(); Range r3(r1), r4(r2); r3.adjust(r1.size()), r4.adjust(-r2.size()); add_datasegment(r3); add_datasegment(r4); print_datasegments(); assert(is_global_address(r1.address())); assert(is_global_address(displace(r1.address(), 0x10))); assert(is_global_address(displace(r1.end(), -sizeof(void*)))); assert(is_global_address(displace(r2.address(), 0xA0))); assert(is_global_address(displace(r3.address(), 0x30))); assert(is_global_address(displace(r4.address(), 0x40))); remove_datasegment(r2); print_datasegments(); assert(!is_global_address(displace(r2.address(), 0xA0))); remove_datasegment(r1); assert(!is_global_address(displace(r1.address(), 0x10))); print_datasegments(); remove_datasegment(r3); remove_datasegment(r4); print_datasegments(); } #endif // // erase_weak // // unregisters any weak references contained within known AUTO_OBJECT // inline void erase_weak(void *ptr) { if (control.weak_layout_for_address) { const unsigned char* weak_layout = control.weak_layout_for_address((auto_zone_t*)zone, ptr); if (weak_layout) weak_unregister_with_layout(this, (void**)ptr, weak_layout); } } // // add_zombie // // Adds address to the zombie set. // inline void add_zombie(void *address) { SpinLock lock(&_zombies_lock); if (_zombies.find(address) == _zombies.end()) { _zombies.insert(address); } } // // is_zombie // // Returns whether or not the address is in the zombie set. // inline bool is_zombie(void *address) { SpinLock lock(&_zombies_lock); PtrHashSet::iterator iter = _zombies.find(address); return (iter != _zombies.end()); } // // clear_zombies // inline void clear_zombies() { SpinLock lock(&_zombies_lock); _zombies.clear(); } // // set_write_barrier // // Set the write barrier byte corresponding to the specified address. // If scanning is going on then the value is marked pending. // If address is within an allocated block the value is set there and will return true. // bool set_write_barrier(Thread &thread, void *address, void *value); // // set_write_barrier_range // // Set the write barrier bytes corresponding to the specified address & length. // Returns if the address is within an allocated block (and barrier set) // bool set_write_barrier_range(void *address, const usword_t size); // // set_write_barrier // // Set the write barrier byte corresponding to the specified address. // Returns if the address is within an allocated block (and barrier set) // bool set_write_barrier(void *address); // // mark_write_barriers_untouched // // iterate through all the write barriers and mark the live cards as provisionally untouched. // void mark_write_barriers_untouched(); // // clear_untouched_write_barriers // // iterate through all the write barriers and clear all the cards still marked as untouched. // void clear_untouched_write_barriers(); // // clear_all_write_barriers // // iterate through all the write barriers and clear all the marks. // void clear_all_write_barriers(); // // reset_all_marks // // Clears the mark flags on all blocks // void reset_all_marks(); // // reset_all_marks_and_pending // // Clears the mark and ending flags on all blocks // void reset_all_marks_and_pending(); // // statistics // // Returns the statistics for this zone. // void statistics(Statistics &stats); // // // scan_stack_push_block // // Push the block onto the scan stack. // void scan_stack_push_block(void *block) { _scan_stack.push(block); } // // scan_stack_push_range // // Push the range onto the scan stack. // void scan_stack_push_range(Range &range) { _scan_stack.push(range.end()); _scan_stack.push(displace(range.address(), 1)); } // // scan_stack_push_write_barrier // // Pushes a pointer to a WriteBarrier* that was in effect when a range was pushed. // void scan_stack_push_write_barrier(WriteBarrier *wb) { _scan_stack.push(displace(wb, 3)); } // // scan_stack_is_empty // // Returns true if the stack is empty. // bool scan_stack_is_empty() { return _scan_stack.is_empty() || _scan_stack.is_overflow(); } // // scan_stack_is_range // // Returns true if the top of the scan stack is a range. // bool scan_stack_is_range() { void *pointer = _scan_stack.top(); return (uintptr_t(pointer) & 0x3) == 0x1; } bool scan_stack_is_write_barrier() { void *pointer = _scan_stack.top(); return (uintptr_t(pointer) & 0x3) == 0x3; } // // scan_stack_pop_block // // Return the next block to scan. // void *scan_stack_pop_block() { return _scan_stack.pop(); } // // scan_stack_pop_range // // Return the next block to scan. // Range scan_stack_pop_range() { void *block1 = _scan_stack.pop(); void *block2 = _scan_stack.pop(); return Range(displace(block1, -1), block2); } WriteBarrier *scan_stack_pop_write_barrier() { void *pointer = _scan_stack.pop(); return (WriteBarrier*) displace(pointer, -3); } // // scan_stack_max // // Return the maximum words used in this scan // unsigned long int scan_stack_max() { return _scan_stack.max(); } inline void set_repair_write_barrier(bool repair) { _repair_write_barrier = repair; } inline bool repair_write_barrier() const { return _repair_write_barrier; } // // set_needs_enlivening // // Inform all known threads that scanning is about to commence, thus blocks will need to be // enlivened to make sure they aren't missed during concurrent scanning. // void set_needs_enlivening(); // // enlivening_barrier // // Called by Collector::scan_barrier() to enliven all blocks that // would otherwise be missed by concurrent scanning. // void enlivening_barrier(MemoryScanner &scanner); // // clear_needs_enlivening // // Unblocks threads that may be spinning waiting for enlivening to finish. // void clear_needs_enlivening(); // // collect_begin // // Indicate the beginning of the collection period. New blocks allocated during the time will // automatically marked and not treated as garbage. // inline void collect_begin() { } // // collect_end // // Indicate the end of the collection period. New blocks will no longer be marked automatically and will // be collected normally. // inline void collect_end() { _garbage_list.uncommit(); _small_admin.purge_free_space(); _medium_admin.purge_free_space(); } // // block_collector // // Called to lock the global mark bits and thread lists. // Returns true if successful. // bool block_collector(); // // unblock_collector // // Called to unlock the global mark bits and thread lists. // void unblock_collector(); // // collect // // Performs the collection process. // void collect(bool is_partial, void *current_stack_bottom, auto_date_t *scan_end); // // scavenge_blocks // // Constructs a list of all blocks that are to be garbaged // void scavenge_blocks(); // // invalidate_garbage // // Given an array of garbage, do callouts for finalization // void invalidate_garbage(const size_t garbage_count, const vm_address_t *garbage); // // handle_overretained_garbage // // called when we detect a garbage block has been over retained during finalization // logs a (fatal, based on the setting) resurrection error // void handle_overretained_garbage(void *block, int rc); // // free_garbage // // Given an array of garbage, free it en masse // size_t free_garbage(boolean_t generational, const size_t garbage_count, vm_address_t *garbage, size_t &blocks_freed, size_t &bytes_freed); // // release_pages // // Release any pages that are not in use. // void release_pages() { } // // recycle_threads // // Searches for unbound threads, queueing them for deletion. // void recycle_threads(); // // register_thread // // Add the current thread as a thread to be scanned during gc. // Thread ®ister_thread(); // // unregister_thread // // deprecated // void unregister_thread(); private: Thread *firstScannableThread(); Thread *nextScannableThread(Thread *thread); public: // // scan_registered_threads // // Safely enumerates the registered threads, ensuring that their stacks // remain valid during the call to the scanner block. // typedef void (^thread_scanner_t) (Thread *thread); void scan_registered_threads(thread_scanner_t scanner); void scan_registered_threads(void (*scanner) (Thread *, void *), void *arg) { scan_registered_threads(^(Thread *thread) { scanner(thread, arg); }); } // // suspend_all_registered_threads // // Suspend all registered threads. Provided for heap snapshots. // Acquires _registered_threads_lock so that no new threads can enter the system. // void suspend_all_registered_threads(); // // resume_all_registered_threads // // Resumes all suspended registered threads. Only used by the monitor for heap snapshots. // Relinquishes the _registered_threads_lock. // void resume_all_registered_threads(); // // weak references. // unsigned has_weak_references() { return (num_weak_refs != 0); } // // layout_map_for_block. // // Used for precise (non-conservative) block scanning. // const unsigned char *layout_map_for_block(void *block) { // FIXME: for speed, change this to a hard coded offset from the block's word0 field. return control.layout_for_address ? control.layout_for_address((auto_zone_t *)this, block) : NULL; } // // weak_layout_map_for_block. // // Used for conservative block with weak references scanning. // const unsigned char *weak_layout_map_for_block(void *block) { // FIXME: for speed, change this to a hard coded offset from the block's word0 field. return control.weak_layout_for_address ? control.weak_layout_for_address((auto_zone_t *)this, block) : NULL; } // // print_all_blocks // // Prints all allocated blocks. // void print_all_blocks(); // // print block // // Print the details of a block // void print_block(void *block); void print_block(void *block, const char *tag); // // malloc_statistics // // computes the necessary malloc statistics // void malloc_statistics(malloc_statistics_t *stats); // // dump // // call blocks with everything needed to recreate heap // blocks are called in the order given // void dump( auto_zone_stack_dump stack_dump, auto_zone_register_dump register_dump, auto_zone_node_dump thread_local_node_dump, auto_zone_root_dump root_dump, auto_zone_node_dump global_node_dump, auto_zone_weak_dump weak_dump_entry ); }; }; #endif // __AUTO_ZONE_CORE__