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    ZoneCollectionChecking
22    Copyright (c) 2010-2001 Apple Inc. All rights reserved.
23 */
24
25#include "auto_zone.h"
26#include "Zone.h"
27#include "BlockIterator.h"
28#ifdef __BLOCKS__
29#include <Block.h>
30#endif
31
32using namespace Auto;
33
34void Zone::enable_collection_checking() {
35    OSAtomicIncrement32(&_collection_checking_enabled);
36}
37
38
39void Zone::disable_collection_checking() {
40    uint32_t old_count;
41    do {
42        old_count = _collection_checking_enabled;
43    } while (old_count > 0 && !OSAtomicCompareAndSwap32(old_count, old_count-1, &_collection_checking_enabled));
44
45    if (old_count == 1) {
46        // Reset the check counts of all blocks.
47
48        for (Region *region = region_list(); region != NULL; region = region->next()) {
49            SubzoneRangeIterator iterator(region->subzone_range());
50            Subzone *sz;
51            for (sz = iterator.next(); sz != NULL; sz = iterator.next()) {
52                sz->reset_collection_checking();
53            }
54        }
55
56        SpinLock lock(&_large_lock);
57        Large *large = _large_list;
58        while (large) {
59            large->set_collection_checking_count(0);
60            large = large->next();
61        }
62    }
63}
64
65
66void Zone::track_pointer(void *pointer) {
67    assert(collection_checking_enabled());
68    if (in_subzone_memory(pointer)) {
69        Subzone *sz = Subzone::subzone(pointer);
70        usword_t q;
71        if (sz->block_is_start(pointer, &q)) {
72            if (sz->collection_checking_count(q) == 0) {
73                sz->set_collection_checking_count(q, 1);
74            }
75        }
76    } else {
77        Large *large = block_start_large(pointer);
78        if (large && large->collection_checking_count() == 0) {
79            large->set_collection_checking_count(1);
80        }
81    }
82}
83
84
85// Clears the checking count for the blocks in the garbage list.
86void Zone::clear_garbage_checking_count(void **garbage, size_t count) {
87    for (size_t i=0; i<count; i++) {
88        void *block = garbage[i];
89        if (in_subzone_memory(block)) {
90            Subzone *subzone = Subzone::subzone(block);
91            usword_t q = subzone->quantum_index_unchecked(block);
92            // most of the time the checking count is zero already, so don't dirty the page needlessly
93            if (subzone->collection_checking_count(q) > 0) {
94                subzone->set_collection_checking_count(q, 0);
95            }
96        } else {
97            Large *l = Large::large(block);
98            l->set_collection_checking_count(0);
99        }
100    }
101}
102
103
104//
105// checking_blocks_visitor
106//
107// checking_blocks_visitor searches for blocks with check counts that exceed the collection checking threshold.
108// Blocks which it finds are reported via report_uncollected_block().
109//
110class update_checking_count_visitor  {
111
112public:
113
114    inline bool visit(Zone *zone, Subzone *subzone, usword_t q) {
115        uint32_t count = subzone->collection_checking_count(q);
116        if (count > 0) {
117            subzone->set_collection_checking_count(q, count+1);
118        }
119        return true;
120    }
121
122    inline bool visit(Zone *zone, Large *large) {
123        uint32_t count = large->collection_checking_count();
124        if (count > 0) {
125            large->set_collection_checking_count(count+1);
126        }
127        return true;
128    }
129};
130
131
132void Zone::increment_check_counts() {
133    update_checking_count_visitor visitor;
134    visitAllocatedBlocks(this, visitor);
135}
136
137//
138// report_uncollected_blocks_visitor
139//
140// report_uncollected_blocks_visitor searches for blocks with check counts that exceed the collection checking threshold.
141// Blocks which it finds are reported via the callback, or just logged.
142//
143class report_uncollected_blocks_visitor  {
144    auto_zone_collection_checking_callback_t _callback;
145    Thread &_thread;
146
147public:
148
149    report_uncollected_blocks_visitor(Zone *zone, auto_zone_collection_checking_callback_t callback) : _callback(callback), _thread(zone->registered_thread()) {
150    }
151
152    ~report_uncollected_blocks_visitor() {
153    }
154
155    void report_uncollected_block(Zone *zone, void *block, int32_t count) {
156        auto_memory_type_t layout = zone->block_layout(block);
157        if (!_callback) {
158            char *name;
159            bool free_name = false;
160            if ((layout & AUTO_OBJECT) == AUTO_OBJECT) {
161                if (zone->control.name_for_address) {
162                    name = zone->control.name_for_address((auto_zone_t *)this, (vm_address_t)block, 0);
163                    free_name = true;
164                } else {
165                    name = (char *)"object";
166                }
167            } else {
168                name = (char *)"non-object block";
169            }
170            malloc_printf("%s %p was not collected after %d full collections\n", name, block, count-1);
171            if (free_name) free(name);
172        } else {
173            auto_zone_collection_checking_info info;
174            info.is_object = (layout & AUTO_OBJECT) == AUTO_OBJECT;
175            info.survived_count = count-1;
176            _callback(block, &info);
177        }
178    }
179
180    inline bool visit(Zone *zone, Subzone *subzone, usword_t q) {
181        uint32_t count = subzone->collection_checking_count(q);
182        if (count > 0) {
183            report_uncollected_block(zone, subzone->quantum_address(q), count);
184        }
185        return true;
186    }
187
188    inline bool visit(Zone *zone, Large *large) {
189        uint32_t count = large->collection_checking_count();
190        if (count > 0) {
191            report_uncollected_block(zone, large->address(), count);
192        }
193        return true;
194    }
195};
196
197void Zone::enumerate_uncollected(auto_zone_collection_checking_callback_t callback) {
198    dispatch_sync(_collection_queue, ^{
199        report_uncollected_blocks_visitor visitor(this, callback);
200        visitAllocatedBlocks(this, visitor);
201    });
202}
203