Mercurial > hg > truffle
annotate src/share/vm/gc_implementation/g1/heapRegionRemSet.cpp @ 889:15c5903cf9e1
6865703: G1: Parallelize hot card cache cleanup
Summary: Have the GC worker threads clear the hot card cache in parallel by having each worker thread claim a chunk of the card cache and process the cards in that chunk. The size of the chunks that each thread will claim is determined at VM initialization from the size of the card cache and the number of worker threads.
Reviewed-by: jmasa, tonyp
author | johnc |
---|---|
date | Mon, 03 Aug 2009 12:59:30 -0700 |
parents | df6caf649ff7 |
children | 2c79770d1f6e |
rev | line source |
---|---|
342 | 1 /* |
579 | 2 * Copyright 2001-2009 Sun Microsystems, Inc. All Rights Reserved. |
342 | 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
4 * | |
5 * This code is free software; you can redistribute it and/or modify it | |
6 * under the terms of the GNU General Public License version 2 only, as | |
7 * published by the Free Software Foundation. | |
8 * | |
9 * This code is distributed in the hope that it will be useful, but WITHOUT | |
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
12 * version 2 for more details (a copy is included in the LICENSE file that | |
13 * accompanied this code). | |
14 * | |
15 * You should have received a copy of the GNU General Public License version | |
16 * 2 along with this work; if not, write to the Free Software Foundation, | |
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
18 * | |
19 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, | |
20 * CA 95054 USA or visit www.sun.com if you need additional information or | |
21 * have any questions. | |
22 * | |
23 */ | |
24 | |
25 #include "incls/_precompiled.incl" | |
26 #include "incls/_heapRegionRemSet.cpp.incl" | |
27 | |
28 #define HRRS_VERBOSE 0 | |
29 | |
30 #define PRT_COUNT_OCCUPIED 1 | |
31 | |
32 // OtherRegionsTable | |
33 | |
34 class PerRegionTable: public CHeapObj { | |
35 friend class OtherRegionsTable; | |
36 friend class HeapRegionRemSetIterator; | |
37 | |
38 HeapRegion* _hr; | |
39 BitMap _bm; | |
40 #if PRT_COUNT_OCCUPIED | |
41 jint _occupied; | |
42 #endif | |
43 PerRegionTable* _next_free; | |
44 | |
45 PerRegionTable* next_free() { return _next_free; } | |
46 void set_next_free(PerRegionTable* prt) { _next_free = prt; } | |
47 | |
48 | |
49 static PerRegionTable* _free_list; | |
50 | |
51 #ifdef _MSC_VER | |
52 // For some reason even though the classes are marked as friend they are unable | |
53 // to access CardsPerRegion when private/protected. Only the windows c++ compiler | |
54 // says this Sun CC and linux gcc don't have a problem with access when private | |
55 | |
56 public: | |
57 | |
58 #endif // _MSC_VER | |
59 | |
60 enum SomePrivateConstants { | |
61 CardsPerRegion = HeapRegion::GrainBytes >> CardTableModRefBS::card_shift | |
62 }; | |
63 | |
64 protected: | |
65 // We need access in order to union things into the base table. | |
66 BitMap* bm() { return &_bm; } | |
67 | |
545 | 68 #if PRT_COUNT_OCCUPIED |
342 | 69 void recount_occupied() { |
70 _occupied = (jint) bm()->count_one_bits(); | |
71 } | |
545 | 72 #endif |
342 | 73 |
74 PerRegionTable(HeapRegion* hr) : | |
75 _hr(hr), | |
76 #if PRT_COUNT_OCCUPIED | |
77 _occupied(0), | |
78 #endif | |
79 _bm(CardsPerRegion, false /* in-resource-area */) | |
80 {} | |
81 | |
82 static void free(PerRegionTable* prt) { | |
83 while (true) { | |
84 PerRegionTable* fl = _free_list; | |
85 prt->set_next_free(fl); | |
86 PerRegionTable* res = | |
87 (PerRegionTable*) | |
88 Atomic::cmpxchg_ptr(prt, &_free_list, fl); | |
89 if (res == fl) return; | |
90 } | |
91 ShouldNotReachHere(); | |
92 } | |
93 | |
94 static PerRegionTable* alloc(HeapRegion* hr) { | |
95 PerRegionTable* fl = _free_list; | |
96 while (fl != NULL) { | |
97 PerRegionTable* nxt = fl->next_free(); | |
98 PerRegionTable* res = | |
99 (PerRegionTable*) | |
100 Atomic::cmpxchg_ptr(nxt, &_free_list, fl); | |
101 if (res == fl) { | |
102 fl->init(hr); | |
103 return fl; | |
104 } else { | |
105 fl = _free_list; | |
106 } | |
107 } | |
108 assert(fl == NULL, "Loop condition."); | |
109 return new PerRegionTable(hr); | |
110 } | |
111 | |
807
d44bdab1c03d
6843694: G1: assert(index < _vs.committed_size(),"bad index"), g1BlockOffsetTable.inline.hpp:55
johnc
parents:
795
diff
changeset
|
112 void add_card_work(CardIdx_t from_card, bool par) { |
342 | 113 if (!_bm.at(from_card)) { |
114 if (par) { | |
115 if (_bm.par_at_put(from_card, 1)) { | |
116 #if PRT_COUNT_OCCUPIED | |
117 Atomic::inc(&_occupied); | |
118 #endif | |
119 } | |
120 } else { | |
121 _bm.at_put(from_card, 1); | |
122 #if PRT_COUNT_OCCUPIED | |
123 _occupied++; | |
124 #endif | |
125 } | |
126 } | |
127 } | |
128 | |
845
df6caf649ff7
6700789: G1: Enable use of compressed oops with G1 heaps
ysr
parents:
807
diff
changeset
|
129 void add_reference_work(OopOrNarrowOopStar from, bool par) { |
342 | 130 // Must make this robust in case "from" is not in "_hr", because of |
131 // concurrency. | |
132 | |
133 #if HRRS_VERBOSE | |
134 gclog_or_tty->print_cr(" PRT::Add_reference_work(" PTR_FORMAT "->" PTR_FORMAT").", | |
135 from, *from); | |
136 #endif | |
137 | |
138 HeapRegion* loc_hr = hr(); | |
139 // If the test below fails, then this table was reused concurrently | |
140 // with this operation. This is OK, since the old table was coarsened, | |
141 // and adding a bit to the new table is never incorrect. | |
142 if (loc_hr->is_in_reserved(from)) { | |
143 size_t hw_offset = pointer_delta((HeapWord*)from, loc_hr->bottom()); | |
807
d44bdab1c03d
6843694: G1: assert(index < _vs.committed_size(),"bad index"), g1BlockOffsetTable.inline.hpp:55
johnc
parents:
795
diff
changeset
|
144 CardIdx_t from_card = (CardIdx_t) |
d44bdab1c03d
6843694: G1: assert(index < _vs.committed_size(),"bad index"), g1BlockOffsetTable.inline.hpp:55
johnc
parents:
795
diff
changeset
|
145 hw_offset >> (CardTableModRefBS::card_shift - LogHeapWordSize); |
342 | 146 |
807
d44bdab1c03d
6843694: G1: assert(index < _vs.committed_size(),"bad index"), g1BlockOffsetTable.inline.hpp:55
johnc
parents:
795
diff
changeset
|
147 assert(0 <= from_card && from_card < CardsPerRegion, "Must be in range."); |
d44bdab1c03d
6843694: G1: assert(index < _vs.committed_size(),"bad index"), g1BlockOffsetTable.inline.hpp:55
johnc
parents:
795
diff
changeset
|
148 add_card_work(from_card, par); |
342 | 149 } |
150 } | |
151 | |
152 public: | |
153 | |
154 HeapRegion* hr() const { return _hr; } | |
155 | |
156 #if PRT_COUNT_OCCUPIED | |
157 jint occupied() const { | |
158 // Overkill, but if we ever need it... | |
159 // guarantee(_occupied == _bm.count_one_bits(), "Check"); | |
160 return _occupied; | |
161 } | |
162 #else | |
163 jint occupied() const { | |
164 return _bm.count_one_bits(); | |
165 } | |
166 #endif | |
167 | |
168 void init(HeapRegion* hr) { | |
169 _hr = hr; | |
170 #if PRT_COUNT_OCCUPIED | |
171 _occupied = 0; | |
172 #endif | |
173 _bm.clear(); | |
174 } | |
175 | |
845
df6caf649ff7
6700789: G1: Enable use of compressed oops with G1 heaps
ysr
parents:
807
diff
changeset
|
176 void add_reference(OopOrNarrowOopStar from) { |
342 | 177 add_reference_work(from, /*parallel*/ true); |
178 } | |
179 | |
845
df6caf649ff7
6700789: G1: Enable use of compressed oops with G1 heaps
ysr
parents:
807
diff
changeset
|
180 void seq_add_reference(OopOrNarrowOopStar from) { |
342 | 181 add_reference_work(from, /*parallel*/ false); |
182 } | |
183 | |
184 void scrub(CardTableModRefBS* ctbs, BitMap* card_bm) { | |
185 HeapWord* hr_bot = hr()->bottom(); | |
489
2494ab195856
6653214: MemoryPoolMXBean.setUsageThreshold() does not support large heap sizes.
swamyv
parents:
342
diff
changeset
|
186 size_t hr_first_card_index = ctbs->index_for(hr_bot); |
342 | 187 bm()->set_intersection_at_offset(*card_bm, hr_first_card_index); |
188 #if PRT_COUNT_OCCUPIED | |
189 recount_occupied(); | |
190 #endif | |
191 } | |
192 | |
807
d44bdab1c03d
6843694: G1: assert(index < _vs.committed_size(),"bad index"), g1BlockOffsetTable.inline.hpp:55
johnc
parents:
795
diff
changeset
|
193 void add_card(CardIdx_t from_card_index) { |
342 | 194 add_card_work(from_card_index, /*parallel*/ true); |
195 } | |
196 | |
807
d44bdab1c03d
6843694: G1: assert(index < _vs.committed_size(),"bad index"), g1BlockOffsetTable.inline.hpp:55
johnc
parents:
795
diff
changeset
|
197 void seq_add_card(CardIdx_t from_card_index) { |
342 | 198 add_card_work(from_card_index, /*parallel*/ false); |
199 } | |
200 | |
201 // (Destructively) union the bitmap of the current table into the given | |
202 // bitmap (which is assumed to be of the same size.) | |
203 void union_bitmap_into(BitMap* bm) { | |
204 bm->set_union(_bm); | |
205 } | |
206 | |
207 // Mem size in bytes. | |
208 size_t mem_size() const { | |
209 return sizeof(this) + _bm.size_in_words() * HeapWordSize; | |
210 } | |
211 | |
212 static size_t fl_mem_size() { | |
213 PerRegionTable* cur = _free_list; | |
214 size_t res = 0; | |
215 while (cur != NULL) { | |
216 res += sizeof(PerRegionTable); | |
217 cur = cur->next_free(); | |
218 } | |
219 return res; | |
220 } | |
221 | |
222 // Requires "from" to be in "hr()". | |
845
df6caf649ff7
6700789: G1: Enable use of compressed oops with G1 heaps
ysr
parents:
807
diff
changeset
|
223 bool contains_reference(OopOrNarrowOopStar from) const { |
342 | 224 assert(hr()->is_in_reserved(from), "Precondition."); |
225 size_t card_ind = pointer_delta(from, hr()->bottom(), | |
226 CardTableModRefBS::card_size); | |
227 return _bm.at(card_ind); | |
228 } | |
229 }; | |
230 | |
231 PerRegionTable* PerRegionTable::_free_list = NULL; | |
232 | |
233 | |
234 #define COUNT_PAR_EXPANDS 0 | |
235 | |
236 #if COUNT_PAR_EXPANDS | |
237 static jint n_par_expands = 0; | |
238 static jint n_par_contracts = 0; | |
239 static jint par_expand_list_len = 0; | |
240 static jint max_par_expand_list_len = 0; | |
241 | |
242 static void print_par_expand() { | |
243 Atomic::inc(&n_par_expands); | |
244 Atomic::inc(&par_expand_list_len); | |
245 if (par_expand_list_len > max_par_expand_list_len) { | |
246 max_par_expand_list_len = par_expand_list_len; | |
247 } | |
248 if ((n_par_expands % 10) == 0) { | |
249 gclog_or_tty->print_cr("\n\n%d par expands: %d contracts, " | |
250 "len = %d, max_len = %d\n.", | |
251 n_par_expands, n_par_contracts, par_expand_list_len, | |
252 max_par_expand_list_len); | |
253 } | |
254 } | |
255 #endif | |
256 | |
257 class PosParPRT: public PerRegionTable { | |
258 PerRegionTable** _par_tables; | |
259 | |
260 enum SomePrivateConstants { | |
261 ReserveParTableExpansion = 1 | |
262 }; | |
263 | |
264 void par_expand() { | |
265 int n = HeapRegionRemSet::num_par_rem_sets()-1; | |
266 if (n <= 0) return; | |
267 if (_par_tables == NULL) { | |
268 PerRegionTable* res = | |
269 (PerRegionTable*) | |
270 Atomic::cmpxchg_ptr((PerRegionTable*)ReserveParTableExpansion, | |
271 &_par_tables, NULL); | |
272 if (res != NULL) return; | |
273 // Otherwise, we reserved the right to do the expansion. | |
274 | |
275 PerRegionTable** ptables = NEW_C_HEAP_ARRAY(PerRegionTable*, n); | |
276 for (int i = 0; i < n; i++) { | |
277 PerRegionTable* ptable = PerRegionTable::alloc(hr()); | |
278 ptables[i] = ptable; | |
279 } | |
280 // Here we do not need an atomic. | |
281 _par_tables = ptables; | |
282 #if COUNT_PAR_EXPANDS | |
283 print_par_expand(); | |
284 #endif | |
285 // We must put this table on the expanded list. | |
286 PosParPRT* exp_head = _par_expanded_list; | |
287 while (true) { | |
288 set_next_par_expanded(exp_head); | |
289 PosParPRT* res = | |
290 (PosParPRT*) | |
291 Atomic::cmpxchg_ptr(this, &_par_expanded_list, exp_head); | |
292 if (res == exp_head) return; | |
293 // Otherwise. | |
294 exp_head = res; | |
295 } | |
296 ShouldNotReachHere(); | |
297 } | |
298 } | |
299 | |
300 void par_contract() { | |
301 assert(_par_tables != NULL, "Precondition."); | |
302 int n = HeapRegionRemSet::num_par_rem_sets()-1; | |
303 for (int i = 0; i < n; i++) { | |
304 _par_tables[i]->union_bitmap_into(bm()); | |
305 PerRegionTable::free(_par_tables[i]); | |
306 _par_tables[i] = NULL; | |
307 } | |
308 #if PRT_COUNT_OCCUPIED | |
309 // We must recount the "occupied." | |
310 recount_occupied(); | |
311 #endif | |
312 FREE_C_HEAP_ARRAY(PerRegionTable*, _par_tables); | |
313 _par_tables = NULL; | |
314 #if COUNT_PAR_EXPANDS | |
315 Atomic::inc(&n_par_contracts); | |
316 Atomic::dec(&par_expand_list_len); | |
317 #endif | |
318 } | |
319 | |
320 static PerRegionTable** _par_table_fl; | |
321 | |
322 PosParPRT* _next; | |
323 | |
324 static PosParPRT* _free_list; | |
325 | |
326 PerRegionTable** par_tables() const { | |
327 assert(uintptr_t(NULL) == 0, "Assumption."); | |
328 if (uintptr_t(_par_tables) <= ReserveParTableExpansion) | |
329 return NULL; | |
330 else | |
331 return _par_tables; | |
332 } | |
333 | |
334 PosParPRT* _next_par_expanded; | |
335 PosParPRT* next_par_expanded() { return _next_par_expanded; } | |
336 void set_next_par_expanded(PosParPRT* ppprt) { _next_par_expanded = ppprt; } | |
337 static PosParPRT* _par_expanded_list; | |
338 | |
339 public: | |
340 | |
341 PosParPRT(HeapRegion* hr) : PerRegionTable(hr), _par_tables(NULL) {} | |
342 | |
343 jint occupied() const { | |
344 jint res = PerRegionTable::occupied(); | |
345 if (par_tables() != NULL) { | |
346 for (int i = 0; i < HeapRegionRemSet::num_par_rem_sets()-1; i++) { | |
347 res += par_tables()[i]->occupied(); | |
348 } | |
349 } | |
350 return res; | |
351 } | |
352 | |
353 void init(HeapRegion* hr) { | |
354 PerRegionTable::init(hr); | |
355 _next = NULL; | |
356 if (par_tables() != NULL) { | |
357 for (int i = 0; i < HeapRegionRemSet::num_par_rem_sets()-1; i++) { | |
358 par_tables()[i]->init(hr); | |
359 } | |
360 } | |
361 } | |
362 | |
363 static void free(PosParPRT* prt) { | |
364 while (true) { | |
365 PosParPRT* fl = _free_list; | |
366 prt->set_next(fl); | |
367 PosParPRT* res = | |
368 (PosParPRT*) | |
369 Atomic::cmpxchg_ptr(prt, &_free_list, fl); | |
370 if (res == fl) return; | |
371 } | |
372 ShouldNotReachHere(); | |
373 } | |
374 | |
375 static PosParPRT* alloc(HeapRegion* hr) { | |
376 PosParPRT* fl = _free_list; | |
377 while (fl != NULL) { | |
378 PosParPRT* nxt = fl->next(); | |
379 PosParPRT* res = | |
380 (PosParPRT*) | |
381 Atomic::cmpxchg_ptr(nxt, &_free_list, fl); | |
382 if (res == fl) { | |
383 fl->init(hr); | |
384 return fl; | |
385 } else { | |
386 fl = _free_list; | |
387 } | |
388 } | |
389 assert(fl == NULL, "Loop condition."); | |
390 return new PosParPRT(hr); | |
391 } | |
392 | |
393 PosParPRT* next() const { return _next; } | |
394 void set_next(PosParPRT* nxt) { _next = nxt; } | |
395 PosParPRT** next_addr() { return &_next; } | |
396 | |
845
df6caf649ff7
6700789: G1: Enable use of compressed oops with G1 heaps
ysr
parents:
807
diff
changeset
|
397 void add_reference(OopOrNarrowOopStar from, int tid) { |
342 | 398 // Expand if necessary. |
399 PerRegionTable** pt = par_tables(); | |
400 if (par_tables() == NULL && tid > 0 && hr()->is_gc_alloc_region()) { | |
401 par_expand(); | |
402 pt = par_tables(); | |
403 } | |
404 if (pt != NULL) { | |
405 // We always have to assume that mods to table 0 are in parallel, | |
406 // because of the claiming scheme in parallel expansion. A thread | |
407 // with tid != 0 that finds the table to be NULL, but doesn't succeed | |
408 // in claiming the right of expanding it, will end up in the else | |
409 // clause of the above if test. That thread could be delayed, and a | |
410 // thread 0 add reference could see the table expanded, and come | |
411 // here. Both threads would be adding in parallel. But we get to | |
412 // not use atomics for tids > 0. | |
413 if (tid == 0) { | |
414 PerRegionTable::add_reference(from); | |
415 } else { | |
416 pt[tid-1]->seq_add_reference(from); | |
417 } | |
418 } else { | |
419 // Not expanded -- add to the base table. | |
420 PerRegionTable::add_reference(from); | |
421 } | |
422 } | |
423 | |
424 void scrub(CardTableModRefBS* ctbs, BitMap* card_bm) { | |
425 assert(_par_tables == NULL, "Precondition"); | |
426 PerRegionTable::scrub(ctbs, card_bm); | |
427 } | |
428 | |
429 size_t mem_size() const { | |
430 size_t res = | |
431 PerRegionTable::mem_size() + sizeof(this) - sizeof(PerRegionTable); | |
432 if (_par_tables != NULL) { | |
433 for (int i = 0; i < HeapRegionRemSet::num_par_rem_sets()-1; i++) { | |
434 res += _par_tables[i]->mem_size(); | |
435 } | |
436 } | |
437 return res; | |
438 } | |
439 | |
440 static size_t fl_mem_size() { | |
441 PosParPRT* cur = _free_list; | |
442 size_t res = 0; | |
443 while (cur != NULL) { | |
444 res += sizeof(PosParPRT); | |
445 cur = cur->next(); | |
446 } | |
447 return res; | |
448 } | |
449 | |
845
df6caf649ff7
6700789: G1: Enable use of compressed oops with G1 heaps
ysr
parents:
807
diff
changeset
|
450 bool contains_reference(OopOrNarrowOopStar from) const { |
342 | 451 if (PerRegionTable::contains_reference(from)) return true; |
452 if (_par_tables != NULL) { | |
453 for (int i = 0; i < HeapRegionRemSet::num_par_rem_sets()-1; i++) { | |
454 if (_par_tables[i]->contains_reference(from)) return true; | |
455 } | |
456 } | |
457 return false; | |
458 } | |
459 | |
460 static void par_contract_all(); | |
461 | |
462 }; | |
463 | |
464 void PosParPRT::par_contract_all() { | |
465 PosParPRT* hd = _par_expanded_list; | |
466 while (hd != NULL) { | |
467 PosParPRT* nxt = hd->next_par_expanded(); | |
468 PosParPRT* res = | |
469 (PosParPRT*) | |
470 Atomic::cmpxchg_ptr(nxt, &_par_expanded_list, hd); | |
471 if (res == hd) { | |
472 // We claimed the right to contract this table. | |
473 hd->set_next_par_expanded(NULL); | |
474 hd->par_contract(); | |
475 hd = _par_expanded_list; | |
476 } else { | |
477 hd = res; | |
478 } | |
479 } | |
480 } | |
481 | |
482 PosParPRT* PosParPRT::_free_list = NULL; | |
483 PosParPRT* PosParPRT::_par_expanded_list = NULL; | |
484 | |
485 jint OtherRegionsTable::_cache_probes = 0; | |
486 jint OtherRegionsTable::_cache_hits = 0; | |
487 | |
488 size_t OtherRegionsTable::_max_fine_entries = 0; | |
489 size_t OtherRegionsTable::_mod_max_fine_entries_mask = 0; | |
490 #if SAMPLE_FOR_EVICTION | |
491 size_t OtherRegionsTable::_fine_eviction_stride = 0; | |
492 size_t OtherRegionsTable::_fine_eviction_sample_size = 0; | |
493 #endif | |
494 | |
495 OtherRegionsTable::OtherRegionsTable(HeapRegion* hr) : | |
496 _g1h(G1CollectedHeap::heap()), | |
497 _m(Mutex::leaf, "An OtherRegionsTable lock", true), | |
498 _hr(hr), | |
499 _coarse_map(G1CollectedHeap::heap()->max_regions(), | |
500 false /* in-resource-area */), | |
501 _fine_grain_regions(NULL), | |
502 _n_fine_entries(0), _n_coarse_entries(0), | |
503 #if SAMPLE_FOR_EVICTION | |
504 _fine_eviction_start(0), | |
505 #endif | |
506 _sparse_table(hr) | |
507 { | |
508 typedef PosParPRT* PosParPRTPtr; | |
509 if (_max_fine_entries == 0) { | |
510 assert(_mod_max_fine_entries_mask == 0, "Both or none."); | |
645
c3a720eefe82
6816308: Changes to allow builds with latest Windows SDK 6.1 on 64bit Windows 2003
kvn
parents:
545
diff
changeset
|
511 _max_fine_entries = (size_t)(1 << G1LogRSRegionEntries); |
342 | 512 _mod_max_fine_entries_mask = _max_fine_entries - 1; |
513 #if SAMPLE_FOR_EVICTION | |
514 assert(_fine_eviction_sample_size == 0 | |
515 && _fine_eviction_stride == 0, "All init at same time."); | |
516 _fine_eviction_sample_size = MAX2((size_t)4, (size_t)G1LogRSRegionEntries); | |
517 _fine_eviction_stride = _max_fine_entries / _fine_eviction_sample_size; | |
518 #endif | |
519 } | |
520 _fine_grain_regions = new PosParPRTPtr[_max_fine_entries]; | |
521 if (_fine_grain_regions == NULL) | |
522 vm_exit_out_of_memory(sizeof(void*)*_max_fine_entries, | |
523 "Failed to allocate _fine_grain_entries."); | |
524 for (size_t i = 0; i < _max_fine_entries; i++) { | |
525 _fine_grain_regions[i] = NULL; | |
526 } | |
527 } | |
528 | |
529 int** OtherRegionsTable::_from_card_cache = NULL; | |
530 size_t OtherRegionsTable::_from_card_cache_max_regions = 0; | |
531 size_t OtherRegionsTable::_from_card_cache_mem_size = 0; | |
532 | |
533 void OtherRegionsTable::init_from_card_cache(size_t max_regions) { | |
534 _from_card_cache_max_regions = max_regions; | |
535 | |
536 int n_par_rs = HeapRegionRemSet::num_par_rem_sets(); | |
537 _from_card_cache = NEW_C_HEAP_ARRAY(int*, n_par_rs); | |
538 for (int i = 0; i < n_par_rs; i++) { | |
539 _from_card_cache[i] = NEW_C_HEAP_ARRAY(int, max_regions); | |
540 for (size_t j = 0; j < max_regions; j++) { | |
541 _from_card_cache[i][j] = -1; // An invalid value. | |
542 } | |
543 } | |
544 _from_card_cache_mem_size = n_par_rs * max_regions * sizeof(int); | |
545 } | |
546 | |
547 void OtherRegionsTable::shrink_from_card_cache(size_t new_n_regs) { | |
548 for (int i = 0; i < HeapRegionRemSet::num_par_rem_sets(); i++) { | |
549 assert(new_n_regs <= _from_card_cache_max_regions, "Must be within max."); | |
550 for (size_t j = new_n_regs; j < _from_card_cache_max_regions; j++) { | |
551 _from_card_cache[i][j] = -1; // An invalid value. | |
552 } | |
553 } | |
554 } | |
555 | |
556 #ifndef PRODUCT | |
557 void OtherRegionsTable::print_from_card_cache() { | |
558 for (int i = 0; i < HeapRegionRemSet::num_par_rem_sets(); i++) { | |
559 for (size_t j = 0; j < _from_card_cache_max_regions; j++) { | |
560 gclog_or_tty->print_cr("_from_card_cache[%d][%d] = %d.", | |
561 i, j, _from_card_cache[i][j]); | |
562 } | |
563 } | |
564 } | |
565 #endif | |
566 | |
845
df6caf649ff7
6700789: G1: Enable use of compressed oops with G1 heaps
ysr
parents:
807
diff
changeset
|
567 void OtherRegionsTable::add_reference(OopOrNarrowOopStar from, int tid) { |
342 | 568 size_t cur_hrs_ind = hr()->hrs_index(); |
569 | |
570 #if HRRS_VERBOSE | |
571 gclog_or_tty->print_cr("ORT::add_reference_work(" PTR_FORMAT "->" PTR_FORMAT ").", | |
845
df6caf649ff7
6700789: G1: Enable use of compressed oops with G1 heaps
ysr
parents:
807
diff
changeset
|
572 from, |
df6caf649ff7
6700789: G1: Enable use of compressed oops with G1 heaps
ysr
parents:
807
diff
changeset
|
573 UseCompressedOops |
df6caf649ff7
6700789: G1: Enable use of compressed oops with G1 heaps
ysr
parents:
807
diff
changeset
|
574 ? oopDesc::load_decode_heap_oop((narrowOop*)from) |
df6caf649ff7
6700789: G1: Enable use of compressed oops with G1 heaps
ysr
parents:
807
diff
changeset
|
575 : oopDesc::load_decode_heap_oop((oop*)from)); |
342 | 576 #endif |
577 | |
578 int from_card = (int)(uintptr_t(from) >> CardTableModRefBS::card_shift); | |
579 | |
580 #if HRRS_VERBOSE | |
581 gclog_or_tty->print_cr("Table for [" PTR_FORMAT "...): card %d (cache = %d)", | |
582 hr()->bottom(), from_card, | |
583 _from_card_cache[tid][cur_hrs_ind]); | |
584 #endif | |
585 | |
586 #define COUNT_CACHE 0 | |
587 #if COUNT_CACHE | |
588 jint p = Atomic::add(1, &_cache_probes); | |
589 if ((p % 10000) == 0) { | |
590 jint hits = _cache_hits; | |
591 gclog_or_tty->print_cr("%d/%d = %5.2f%% RS cache hits.", | |
592 _cache_hits, p, 100.0* (float)hits/(float)p); | |
593 } | |
594 #endif | |
595 if (from_card == _from_card_cache[tid][cur_hrs_ind]) { | |
596 #if HRRS_VERBOSE | |
597 gclog_or_tty->print_cr(" from-card cache hit."); | |
598 #endif | |
599 #if COUNT_CACHE | |
600 Atomic::inc(&_cache_hits); | |
601 #endif | |
602 assert(contains_reference(from), "We just added it!"); | |
603 return; | |
604 } else { | |
605 _from_card_cache[tid][cur_hrs_ind] = from_card; | |
606 } | |
607 | |
608 // Note that this may be a continued H region. | |
609 HeapRegion* from_hr = _g1h->heap_region_containing_raw(from); | |
807
d44bdab1c03d
6843694: G1: assert(index < _vs.committed_size(),"bad index"), g1BlockOffsetTable.inline.hpp:55
johnc
parents:
795
diff
changeset
|
610 RegionIdx_t from_hrs_ind = (RegionIdx_t) from_hr->hrs_index(); |
342 | 611 |
612 // If the region is already coarsened, return. | |
613 if (_coarse_map.at(from_hrs_ind)) { | |
614 #if HRRS_VERBOSE | |
615 gclog_or_tty->print_cr(" coarse map hit."); | |
616 #endif | |
617 assert(contains_reference(from), "We just added it!"); | |
618 return; | |
619 } | |
620 | |
621 // Otherwise find a per-region table to add it to. | |
622 size_t ind = from_hrs_ind & _mod_max_fine_entries_mask; | |
623 PosParPRT* prt = find_region_table(ind, from_hr); | |
624 if (prt == NULL) { | |
625 MutexLockerEx x(&_m, Mutex::_no_safepoint_check_flag); | |
626 // Confirm that it's really not there... | |
627 prt = find_region_table(ind, from_hr); | |
628 if (prt == NULL) { | |
629 | |
630 uintptr_t from_hr_bot_card_index = | |
631 uintptr_t(from_hr->bottom()) | |
632 >> CardTableModRefBS::card_shift; | |
807
d44bdab1c03d
6843694: G1: assert(index < _vs.committed_size(),"bad index"), g1BlockOffsetTable.inline.hpp:55
johnc
parents:
795
diff
changeset
|
633 CardIdx_t card_index = from_card - from_hr_bot_card_index; |
342 | 634 assert(0 <= card_index && card_index < PosParPRT::CardsPerRegion, |
635 "Must be in range."); | |
636 if (G1HRRSUseSparseTable && | |
807
d44bdab1c03d
6843694: G1: assert(index < _vs.committed_size(),"bad index"), g1BlockOffsetTable.inline.hpp:55
johnc
parents:
795
diff
changeset
|
637 _sparse_table.add_card(from_hrs_ind, card_index)) { |
342 | 638 if (G1RecordHRRSOops) { |
639 HeapRegionRemSet::record(hr(), from); | |
640 #if HRRS_VERBOSE | |
641 gclog_or_tty->print(" Added card " PTR_FORMAT " to region " | |
642 "[" PTR_FORMAT "...) for ref " PTR_FORMAT ".\n", | |
643 align_size_down(uintptr_t(from), | |
644 CardTableModRefBS::card_size), | |
645 hr()->bottom(), from); | |
646 #endif | |
647 } | |
648 #if HRRS_VERBOSE | |
649 gclog_or_tty->print_cr(" added card to sparse table."); | |
650 #endif | |
651 assert(contains_reference_locked(from), "We just added it!"); | |
652 return; | |
653 } else { | |
654 #if HRRS_VERBOSE | |
655 gclog_or_tty->print_cr(" [tid %d] sparse table entry " | |
656 "overflow(f: %d, t: %d)", | |
657 tid, from_hrs_ind, cur_hrs_ind); | |
658 #endif | |
659 } | |
660 | |
661 // Otherwise, transfer from sparse to fine-grain. | |
807
d44bdab1c03d
6843694: G1: assert(index < _vs.committed_size(),"bad index"), g1BlockOffsetTable.inline.hpp:55
johnc
parents:
795
diff
changeset
|
662 CardIdx_t cards[SparsePRTEntry::CardsPerEntry]; |
342 | 663 if (G1HRRSUseSparseTable) { |
807
d44bdab1c03d
6843694: G1: assert(index < _vs.committed_size(),"bad index"), g1BlockOffsetTable.inline.hpp:55
johnc
parents:
795
diff
changeset
|
664 bool res = _sparse_table.get_cards(from_hrs_ind, &cards[0]); |
342 | 665 assert(res, "There should have been an entry"); |
666 } | |
667 | |
668 if (_n_fine_entries == _max_fine_entries) { | |
669 prt = delete_region_table(); | |
670 } else { | |
671 prt = PosParPRT::alloc(from_hr); | |
672 } | |
673 prt->init(from_hr); | |
674 // Record the outgoing pointer in the from_region's outgoing bitmap. | |
675 from_hr->rem_set()->add_outgoing_reference(hr()); | |
676 | |
677 PosParPRT* first_prt = _fine_grain_regions[ind]; | |
678 prt->set_next(first_prt); // XXX Maybe move to init? | |
679 _fine_grain_regions[ind] = prt; | |
680 _n_fine_entries++; | |
681 | |
682 // Add in the cards from the sparse table. | |
683 if (G1HRRSUseSparseTable) { | |
684 for (int i = 0; i < SparsePRTEntry::CardsPerEntry; i++) { | |
807
d44bdab1c03d
6843694: G1: assert(index < _vs.committed_size(),"bad index"), g1BlockOffsetTable.inline.hpp:55
johnc
parents:
795
diff
changeset
|
685 CardIdx_t c = cards[i]; |
342 | 686 if (c != SparsePRTEntry::NullEntry) { |
687 prt->add_card(c); | |
688 } | |
689 } | |
690 // Now we can delete the sparse entry. | |
807
d44bdab1c03d
6843694: G1: assert(index < _vs.committed_size(),"bad index"), g1BlockOffsetTable.inline.hpp:55
johnc
parents:
795
diff
changeset
|
691 bool res = _sparse_table.delete_entry(from_hrs_ind); |
342 | 692 assert(res, "It should have been there."); |
693 } | |
694 } | |
695 assert(prt != NULL && prt->hr() == from_hr, "consequence"); | |
696 } | |
697 // Note that we can't assert "prt->hr() == from_hr", because of the | |
698 // possibility of concurrent reuse. But see head comment of | |
699 // OtherRegionsTable for why this is OK. | |
700 assert(prt != NULL, "Inv"); | |
701 | |
702 prt->add_reference(from, tid); | |
703 if (G1RecordHRRSOops) { | |
704 HeapRegionRemSet::record(hr(), from); | |
705 #if HRRS_VERBOSE | |
706 gclog_or_tty->print("Added card " PTR_FORMAT " to region " | |
707 "[" PTR_FORMAT "...) for ref " PTR_FORMAT ".\n", | |
708 align_size_down(uintptr_t(from), | |
709 CardTableModRefBS::card_size), | |
710 hr()->bottom(), from); | |
711 #endif | |
712 } | |
713 assert(contains_reference(from), "We just added it!"); | |
714 } | |
715 | |
716 PosParPRT* | |
717 OtherRegionsTable::find_region_table(size_t ind, HeapRegion* hr) const { | |
718 assert(0 <= ind && ind < _max_fine_entries, "Preconditions."); | |
719 PosParPRT* prt = _fine_grain_regions[ind]; | |
720 while (prt != NULL && prt->hr() != hr) { | |
721 prt = prt->next(); | |
722 } | |
723 // Loop postcondition is the method postcondition. | |
724 return prt; | |
725 } | |
726 | |
727 | |
728 #define DRT_CENSUS 0 | |
729 | |
730 #if DRT_CENSUS | |
731 static const int HistoSize = 6; | |
732 static int global_histo[HistoSize] = { 0, 0, 0, 0, 0, 0 }; | |
733 static int coarsenings = 0; | |
734 static int occ_sum = 0; | |
735 #endif | |
736 | |
737 jint OtherRegionsTable::_n_coarsenings = 0; | |
738 | |
739 PosParPRT* OtherRegionsTable::delete_region_table() { | |
740 #if DRT_CENSUS | |
741 int histo[HistoSize] = { 0, 0, 0, 0, 0, 0 }; | |
742 const int histo_limits[] = { 1, 4, 16, 64, 256, 2048 }; | |
743 #endif | |
744 | |
745 assert(_m.owned_by_self(), "Precondition"); | |
746 assert(_n_fine_entries == _max_fine_entries, "Precondition"); | |
747 PosParPRT* max = NULL; | |
748 jint max_occ = 0; | |
749 PosParPRT** max_prev; | |
750 size_t max_ind; | |
751 | |
752 #if SAMPLE_FOR_EVICTION | |
753 size_t i = _fine_eviction_start; | |
754 for (size_t k = 0; k < _fine_eviction_sample_size; k++) { | |
755 size_t ii = i; | |
756 // Make sure we get a non-NULL sample. | |
757 while (_fine_grain_regions[ii] == NULL) { | |
758 ii++; | |
759 if (ii == _max_fine_entries) ii = 0; | |
760 guarantee(ii != i, "We must find one."); | |
761 } | |
762 PosParPRT** prev = &_fine_grain_regions[ii]; | |
763 PosParPRT* cur = *prev; | |
764 while (cur != NULL) { | |
765 jint cur_occ = cur->occupied(); | |
766 if (max == NULL || cur_occ > max_occ) { | |
767 max = cur; | |
768 max_prev = prev; | |
769 max_ind = i; | |
770 max_occ = cur_occ; | |
771 } | |
772 prev = cur->next_addr(); | |
773 cur = cur->next(); | |
774 } | |
775 i = i + _fine_eviction_stride; | |
776 if (i >= _n_fine_entries) i = i - _n_fine_entries; | |
777 } | |
778 _fine_eviction_start++; | |
779 if (_fine_eviction_start >= _n_fine_entries) | |
780 _fine_eviction_start -= _n_fine_entries; | |
781 #else | |
782 for (int i = 0; i < _max_fine_entries; i++) { | |
783 PosParPRT** prev = &_fine_grain_regions[i]; | |
784 PosParPRT* cur = *prev; | |
785 while (cur != NULL) { | |
786 jint cur_occ = cur->occupied(); | |
787 #if DRT_CENSUS | |
788 for (int k = 0; k < HistoSize; k++) { | |
789 if (cur_occ <= histo_limits[k]) { | |
790 histo[k]++; global_histo[k]++; break; | |
791 } | |
792 } | |
793 #endif | |
794 if (max == NULL || cur_occ > max_occ) { | |
795 max = cur; | |
796 max_prev = prev; | |
797 max_ind = i; | |
798 max_occ = cur_occ; | |
799 } | |
800 prev = cur->next_addr(); | |
801 cur = cur->next(); | |
802 } | |
803 } | |
804 #endif | |
805 // XXX | |
806 guarantee(max != NULL, "Since _n_fine_entries > 0"); | |
807 #if DRT_CENSUS | |
808 gclog_or_tty->print_cr("In a coarsening: histo of occs:"); | |
809 for (int k = 0; k < HistoSize; k++) { | |
810 gclog_or_tty->print_cr(" <= %4d: %5d.", histo_limits[k], histo[k]); | |
811 } | |
812 coarsenings++; | |
813 occ_sum += max_occ; | |
814 if ((coarsenings % 100) == 0) { | |
815 gclog_or_tty->print_cr("\ncoarsenings = %d; global summary:", coarsenings); | |
816 for (int k = 0; k < HistoSize; k++) { | |
817 gclog_or_tty->print_cr(" <= %4d: %5d.", histo_limits[k], global_histo[k]); | |
818 } | |
819 gclog_or_tty->print_cr("Avg occ of deleted region = %6.2f.", | |
820 (float)occ_sum/(float)coarsenings); | |
821 } | |
822 #endif | |
823 | |
824 // Set the corresponding coarse bit. | |
825 int max_hrs_index = max->hr()->hrs_index(); | |
826 if (!_coarse_map.at(max_hrs_index)) { | |
827 _coarse_map.at_put(max_hrs_index, true); | |
828 _n_coarse_entries++; | |
829 #if 0 | |
830 gclog_or_tty->print("Coarsened entry in region [" PTR_FORMAT "...] " | |
831 "for region [" PTR_FORMAT "...] (%d coarse entries).\n", | |
832 hr()->bottom(), | |
833 max->hr()->bottom(), | |
834 _n_coarse_entries); | |
835 #endif | |
836 } | |
837 | |
838 // Unsplice. | |
839 *max_prev = max->next(); | |
840 Atomic::inc(&_n_coarsenings); | |
841 _n_fine_entries--; | |
842 return max; | |
843 } | |
844 | |
845 | |
846 // At present, this must be called stop-world single-threaded. | |
847 void OtherRegionsTable::scrub(CardTableModRefBS* ctbs, | |
848 BitMap* region_bm, BitMap* card_bm) { | |
849 // First eliminated garbage regions from the coarse map. | |
850 if (G1RSScrubVerbose) | |
851 gclog_or_tty->print_cr("Scrubbing region %d:", hr()->hrs_index()); | |
852 | |
853 assert(_coarse_map.size() == region_bm->size(), "Precondition"); | |
854 if (G1RSScrubVerbose) | |
855 gclog_or_tty->print(" Coarse map: before = %d...", _n_coarse_entries); | |
856 _coarse_map.set_intersection(*region_bm); | |
857 _n_coarse_entries = _coarse_map.count_one_bits(); | |
858 if (G1RSScrubVerbose) | |
859 gclog_or_tty->print_cr(" after = %d.", _n_coarse_entries); | |
860 | |
861 // Now do the fine-grained maps. | |
862 for (size_t i = 0; i < _max_fine_entries; i++) { | |
863 PosParPRT* cur = _fine_grain_regions[i]; | |
864 PosParPRT** prev = &_fine_grain_regions[i]; | |
865 while (cur != NULL) { | |
866 PosParPRT* nxt = cur->next(); | |
867 // If the entire region is dead, eliminate. | |
868 if (G1RSScrubVerbose) | |
869 gclog_or_tty->print_cr(" For other region %d:", cur->hr()->hrs_index()); | |
870 if (!region_bm->at(cur->hr()->hrs_index())) { | |
871 *prev = nxt; | |
872 cur->set_next(NULL); | |
873 _n_fine_entries--; | |
874 if (G1RSScrubVerbose) | |
875 gclog_or_tty->print_cr(" deleted via region map."); | |
876 PosParPRT::free(cur); | |
877 } else { | |
878 // Do fine-grain elimination. | |
879 if (G1RSScrubVerbose) | |
880 gclog_or_tty->print(" occ: before = %4d.", cur->occupied()); | |
881 cur->scrub(ctbs, card_bm); | |
882 if (G1RSScrubVerbose) | |
883 gclog_or_tty->print_cr(" after = %4d.", cur->occupied()); | |
884 // Did that empty the table completely? | |
885 if (cur->occupied() == 0) { | |
886 *prev = nxt; | |
887 cur->set_next(NULL); | |
888 _n_fine_entries--; | |
889 PosParPRT::free(cur); | |
890 } else { | |
891 prev = cur->next_addr(); | |
892 } | |
893 } | |
894 cur = nxt; | |
895 } | |
896 } | |
897 // Since we may have deleted a from_card_cache entry from the RS, clear | |
898 // the FCC. | |
899 clear_fcc(); | |
900 } | |
901 | |
902 | |
903 size_t OtherRegionsTable::occupied() const { | |
904 // Cast away const in this case. | |
905 MutexLockerEx x((Mutex*)&_m, Mutex::_no_safepoint_check_flag); | |
906 size_t sum = occ_fine(); | |
907 sum += occ_sparse(); | |
908 sum += occ_coarse(); | |
909 return sum; | |
910 } | |
911 | |
912 size_t OtherRegionsTable::occ_fine() const { | |
913 size_t sum = 0; | |
914 for (size_t i = 0; i < _max_fine_entries; i++) { | |
915 PosParPRT* cur = _fine_grain_regions[i]; | |
916 while (cur != NULL) { | |
917 sum += cur->occupied(); | |
918 cur = cur->next(); | |
919 } | |
920 } | |
921 return sum; | |
922 } | |
923 | |
924 size_t OtherRegionsTable::occ_coarse() const { | |
925 return (_n_coarse_entries * PosParPRT::CardsPerRegion); | |
926 } | |
927 | |
928 size_t OtherRegionsTable::occ_sparse() const { | |
929 return _sparse_table.occupied(); | |
930 } | |
931 | |
932 size_t OtherRegionsTable::mem_size() const { | |
933 // Cast away const in this case. | |
934 MutexLockerEx x((Mutex*)&_m, Mutex::_no_safepoint_check_flag); | |
935 size_t sum = 0; | |
936 for (size_t i = 0; i < _max_fine_entries; i++) { | |
937 PosParPRT* cur = _fine_grain_regions[i]; | |
938 while (cur != NULL) { | |
939 sum += cur->mem_size(); | |
940 cur = cur->next(); | |
941 } | |
942 } | |
943 sum += (sizeof(PosParPRT*) * _max_fine_entries); | |
944 sum += (_coarse_map.size_in_words() * HeapWordSize); | |
945 sum += (_sparse_table.mem_size()); | |
946 sum += sizeof(*this) - sizeof(_sparse_table); // Avoid double counting above. | |
947 return sum; | |
948 } | |
949 | |
950 size_t OtherRegionsTable::static_mem_size() { | |
951 return _from_card_cache_mem_size; | |
952 } | |
953 | |
954 size_t OtherRegionsTable::fl_mem_size() { | |
955 return PerRegionTable::fl_mem_size() + PosParPRT::fl_mem_size(); | |
956 } | |
957 | |
958 void OtherRegionsTable::clear_fcc() { | |
959 for (int i = 0; i < HeapRegionRemSet::num_par_rem_sets(); i++) { | |
960 _from_card_cache[i][hr()->hrs_index()] = -1; | |
961 } | |
962 } | |
963 | |
964 void OtherRegionsTable::clear() { | |
965 MutexLockerEx x(&_m, Mutex::_no_safepoint_check_flag); | |
966 for (size_t i = 0; i < _max_fine_entries; i++) { | |
967 PosParPRT* cur = _fine_grain_regions[i]; | |
968 while (cur != NULL) { | |
969 PosParPRT* nxt = cur->next(); | |
970 PosParPRT::free(cur); | |
971 cur = nxt; | |
972 } | |
973 _fine_grain_regions[i] = NULL; | |
974 } | |
975 _sparse_table.clear(); | |
976 _coarse_map.clear(); | |
977 _n_fine_entries = 0; | |
978 _n_coarse_entries = 0; | |
979 | |
980 clear_fcc(); | |
981 } | |
982 | |
983 void OtherRegionsTable::clear_incoming_entry(HeapRegion* from_hr) { | |
984 MutexLockerEx x(&_m, Mutex::_no_safepoint_check_flag); | |
985 size_t hrs_ind = (size_t)from_hr->hrs_index(); | |
986 size_t ind = hrs_ind & _mod_max_fine_entries_mask; | |
987 if (del_single_region_table(ind, from_hr)) { | |
988 assert(!_coarse_map.at(hrs_ind), "Inv"); | |
989 } else { | |
990 _coarse_map.par_at_put(hrs_ind, 0); | |
991 } | |
992 // Check to see if any of the fcc entries come from here. | |
993 int hr_ind = hr()->hrs_index(); | |
994 for (int tid = 0; tid < HeapRegionRemSet::num_par_rem_sets(); tid++) { | |
995 int fcc_ent = _from_card_cache[tid][hr_ind]; | |
996 if (fcc_ent != -1) { | |
997 HeapWord* card_addr = (HeapWord*) | |
998 (uintptr_t(fcc_ent) << CardTableModRefBS::card_shift); | |
999 if (hr()->is_in_reserved(card_addr)) { | |
1000 // Clear the from card cache. | |
1001 _from_card_cache[tid][hr_ind] = -1; | |
1002 } | |
1003 } | |
1004 } | |
1005 } | |
1006 | |
1007 bool OtherRegionsTable::del_single_region_table(size_t ind, | |
1008 HeapRegion* hr) { | |
1009 assert(0 <= ind && ind < _max_fine_entries, "Preconditions."); | |
1010 PosParPRT** prev_addr = &_fine_grain_regions[ind]; | |
1011 PosParPRT* prt = *prev_addr; | |
1012 while (prt != NULL && prt->hr() != hr) { | |
1013 prev_addr = prt->next_addr(); | |
1014 prt = prt->next(); | |
1015 } | |
1016 if (prt != NULL) { | |
1017 assert(prt->hr() == hr, "Loop postcondition."); | |
1018 *prev_addr = prt->next(); | |
1019 PosParPRT::free(prt); | |
1020 _n_fine_entries--; | |
1021 return true; | |
1022 } else { | |
1023 return false; | |
1024 } | |
1025 } | |
1026 | |
845
df6caf649ff7
6700789: G1: Enable use of compressed oops with G1 heaps
ysr
parents:
807
diff
changeset
|
1027 bool OtherRegionsTable::contains_reference(OopOrNarrowOopStar from) const { |
342 | 1028 // Cast away const in this case. |
1029 MutexLockerEx x((Mutex*)&_m, Mutex::_no_safepoint_check_flag); | |
1030 return contains_reference_locked(from); | |
1031 } | |
1032 | |
845
df6caf649ff7
6700789: G1: Enable use of compressed oops with G1 heaps
ysr
parents:
807
diff
changeset
|
1033 bool OtherRegionsTable::contains_reference_locked(OopOrNarrowOopStar from) const { |
342 | 1034 HeapRegion* hr = _g1h->heap_region_containing_raw(from); |
1035 if (hr == NULL) return false; | |
807
d44bdab1c03d
6843694: G1: assert(index < _vs.committed_size(),"bad index"), g1BlockOffsetTable.inline.hpp:55
johnc
parents:
795
diff
changeset
|
1036 RegionIdx_t hr_ind = (RegionIdx_t) hr->hrs_index(); |
342 | 1037 // Is this region in the coarse map? |
1038 if (_coarse_map.at(hr_ind)) return true; | |
1039 | |
1040 PosParPRT* prt = find_region_table(hr_ind & _mod_max_fine_entries_mask, | |
1041 hr); | |
1042 if (prt != NULL) { | |
1043 return prt->contains_reference(from); | |
1044 | |
1045 } else { | |
1046 uintptr_t from_card = | |
1047 (uintptr_t(from) >> CardTableModRefBS::card_shift); | |
1048 uintptr_t hr_bot_card_index = | |
1049 uintptr_t(hr->bottom()) >> CardTableModRefBS::card_shift; | |
1050 assert(from_card >= hr_bot_card_index, "Inv"); | |
807
d44bdab1c03d
6843694: G1: assert(index < _vs.committed_size(),"bad index"), g1BlockOffsetTable.inline.hpp:55
johnc
parents:
795
diff
changeset
|
1051 CardIdx_t card_index = from_card - hr_bot_card_index; |
d44bdab1c03d
6843694: G1: assert(index < _vs.committed_size(),"bad index"), g1BlockOffsetTable.inline.hpp:55
johnc
parents:
795
diff
changeset
|
1052 assert(0 <= card_index && card_index < PosParPRT::CardsPerRegion, "Must be in range."); |
d44bdab1c03d
6843694: G1: assert(index < _vs.committed_size(),"bad index"), g1BlockOffsetTable.inline.hpp:55
johnc
parents:
795
diff
changeset
|
1053 return _sparse_table.contains_card(hr_ind, card_index); |
342 | 1054 } |
1055 | |
1056 | |
1057 } | |
1058 | |
795
215f81b4d9b3
6841831: G1: assert(contains_reference(from),"We just added it!") fires
iveresov
parents:
794
diff
changeset
|
1059 // Determines how many threads can add records to an rset in parallel. |
215f81b4d9b3
6841831: G1: assert(contains_reference(from),"We just added it!") fires
iveresov
parents:
794
diff
changeset
|
1060 // This can be done by either mutator threads together with the |
215f81b4d9b3
6841831: G1: assert(contains_reference(from),"We just added it!") fires
iveresov
parents:
794
diff
changeset
|
1061 // concurrent refinement threads or GC threads. |
342 | 1062 int HeapRegionRemSet::num_par_rem_sets() { |
795
215f81b4d9b3
6841831: G1: assert(contains_reference(from),"We just added it!") fires
iveresov
parents:
794
diff
changeset
|
1063 return (int)MAX2(DirtyCardQueueSet::num_par_ids() + ConcurrentG1Refine::thread_num(), ParallelGCThreads); |
342 | 1064 } |
1065 | |
1066 HeapRegionRemSet::HeapRegionRemSet(G1BlockOffsetSharedArray* bosa, | |
1067 HeapRegion* hr) | |
1068 : _bosa(bosa), _other_regions(hr), | |
1069 _outgoing_region_map(G1CollectedHeap::heap()->max_regions(), | |
1070 false /* in-resource-area */), | |
1071 _iter_state(Unclaimed) | |
1072 {} | |
1073 | |
1074 | |
1075 void HeapRegionRemSet::init_for_par_iteration() { | |
1076 _iter_state = Unclaimed; | |
1077 } | |
1078 | |
1079 bool HeapRegionRemSet::claim_iter() { | |
1080 if (_iter_state != Unclaimed) return false; | |
1081 jint res = Atomic::cmpxchg(Claimed, (jint*)(&_iter_state), Unclaimed); | |
1082 return (res == Unclaimed); | |
1083 } | |
1084 | |
1085 void HeapRegionRemSet::set_iter_complete() { | |
1086 _iter_state = Complete; | |
1087 } | |
1088 | |
1089 bool HeapRegionRemSet::iter_is_complete() { | |
1090 return _iter_state == Complete; | |
1091 } | |
1092 | |
1093 | |
1094 void HeapRegionRemSet::init_iterator(HeapRegionRemSetIterator* iter) const { | |
1095 iter->initialize(this); | |
1096 } | |
1097 | |
1098 #ifndef PRODUCT | |
1099 void HeapRegionRemSet::print() const { | |
1100 HeapRegionRemSetIterator iter; | |
1101 init_iterator(&iter); | |
1102 size_t card_index; | |
1103 while (iter.has_next(card_index)) { | |
1104 HeapWord* card_start = | |
1105 G1CollectedHeap::heap()->bot_shared()->address_for_index(card_index); | |
1106 gclog_or_tty->print_cr(" Card " PTR_FORMAT ".", card_start); | |
1107 } | |
1108 // XXX | |
1109 if (iter.n_yielded() != occupied()) { | |
1110 gclog_or_tty->print_cr("Yielded disagrees with occupied:"); | |
1111 gclog_or_tty->print_cr(" %6d yielded (%6d coarse, %6d fine).", | |
1112 iter.n_yielded(), | |
1113 iter.n_yielded_coarse(), iter.n_yielded_fine()); | |
1114 gclog_or_tty->print_cr(" %6d occ (%6d coarse, %6d fine).", | |
1115 occupied(), occ_coarse(), occ_fine()); | |
1116 } | |
1117 guarantee(iter.n_yielded() == occupied(), | |
1118 "We should have yielded all the represented cards."); | |
1119 } | |
1120 #endif | |
1121 | |
1122 void HeapRegionRemSet::cleanup() { | |
1123 SparsePRT::cleanup_all(); | |
1124 } | |
1125 | |
1126 void HeapRegionRemSet::par_cleanup() { | |
1127 PosParPRT::par_contract_all(); | |
1128 } | |
1129 | |
1130 void HeapRegionRemSet::add_outgoing_reference(HeapRegion* to_hr) { | |
1131 _outgoing_region_map.par_at_put(to_hr->hrs_index(), 1); | |
1132 } | |
1133 | |
1134 void HeapRegionRemSet::clear() { | |
1135 clear_outgoing_entries(); | |
1136 _outgoing_region_map.clear(); | |
1137 _other_regions.clear(); | |
1138 assert(occupied() == 0, "Should be clear."); | |
1139 } | |
1140 | |
1141 void HeapRegionRemSet::clear_outgoing_entries() { | |
1142 G1CollectedHeap* g1h = G1CollectedHeap::heap(); | |
1143 size_t i = _outgoing_region_map.get_next_one_offset(0); | |
1144 while (i < _outgoing_region_map.size()) { | |
1145 HeapRegion* to_region = g1h->region_at(i); | |
545 | 1146 if (!to_region->in_collection_set()) { |
1147 to_region->rem_set()->clear_incoming_entry(hr()); | |
1148 } | |
342 | 1149 i = _outgoing_region_map.get_next_one_offset(i+1); |
1150 } | |
1151 } | |
1152 | |
1153 | |
1154 void HeapRegionRemSet::scrub(CardTableModRefBS* ctbs, | |
1155 BitMap* region_bm, BitMap* card_bm) { | |
1156 _other_regions.scrub(ctbs, region_bm, card_bm); | |
1157 } | |
1158 | |
1159 //-------------------- Iteration -------------------- | |
1160 | |
1161 HeapRegionRemSetIterator:: | |
1162 HeapRegionRemSetIterator() : | |
1163 _hrrs(NULL), | |
1164 _g1h(G1CollectedHeap::heap()), | |
1165 _bosa(NULL), | |
1166 _sparse_iter(size_t(G1CollectedHeap::heap()->reserved_region().start()) | |
1167 >> CardTableModRefBS::card_shift) | |
1168 {} | |
1169 | |
1170 void HeapRegionRemSetIterator::initialize(const HeapRegionRemSet* hrrs) { | |
1171 _hrrs = hrrs; | |
1172 _coarse_map = &_hrrs->_other_regions._coarse_map; | |
1173 _fine_grain_regions = _hrrs->_other_regions._fine_grain_regions; | |
1174 _bosa = _hrrs->bosa(); | |
1175 | |
1176 _is = Sparse; | |
1177 // Set these values so that we increment to the first region. | |
1178 _coarse_cur_region_index = -1; | |
1179 _coarse_cur_region_cur_card = (PosParPRT::CardsPerRegion-1);; | |
1180 | |
1181 _cur_region_cur_card = 0; | |
1182 | |
1183 _fine_array_index = -1; | |
1184 _fine_cur_prt = NULL; | |
1185 | |
1186 _n_yielded_coarse = 0; | |
1187 _n_yielded_fine = 0; | |
1188 _n_yielded_sparse = 0; | |
1189 | |
1190 _sparse_iter.init(&hrrs->_other_regions._sparse_table); | |
1191 } | |
1192 | |
1193 bool HeapRegionRemSetIterator::coarse_has_next(size_t& card_index) { | |
1194 if (_hrrs->_other_regions._n_coarse_entries == 0) return false; | |
1195 // Go to the next card. | |
1196 _coarse_cur_region_cur_card++; | |
1197 // Was the last the last card in the current region? | |
1198 if (_coarse_cur_region_cur_card == PosParPRT::CardsPerRegion) { | |
1199 // Yes: find the next region. This may leave _coarse_cur_region_index | |
1200 // Set to the last index, in which case there are no more coarse | |
1201 // regions. | |
1202 _coarse_cur_region_index = | |
1203 (int) _coarse_map->get_next_one_offset(_coarse_cur_region_index + 1); | |
1204 if ((size_t)_coarse_cur_region_index < _coarse_map->size()) { | |
1205 _coarse_cur_region_cur_card = 0; | |
1206 HeapWord* r_bot = | |
1207 _g1h->region_at(_coarse_cur_region_index)->bottom(); | |
1208 _cur_region_card_offset = _bosa->index_for(r_bot); | |
1209 } else { | |
1210 return false; | |
1211 } | |
1212 } | |
1213 // If we didn't return false above, then we can yield a card. | |
1214 card_index = _cur_region_card_offset + _coarse_cur_region_cur_card; | |
1215 return true; | |
1216 } | |
1217 | |
1218 void HeapRegionRemSetIterator::fine_find_next_non_null_prt() { | |
1219 // Otherwise, find the next bucket list in the array. | |
1220 _fine_array_index++; | |
1221 while (_fine_array_index < (int) OtherRegionsTable::_max_fine_entries) { | |
1222 _fine_cur_prt = _fine_grain_regions[_fine_array_index]; | |
1223 if (_fine_cur_prt != NULL) return; | |
1224 else _fine_array_index++; | |
1225 } | |
1226 assert(_fine_cur_prt == NULL, "Loop post"); | |
1227 } | |
1228 | |
1229 bool HeapRegionRemSetIterator::fine_has_next(size_t& card_index) { | |
1230 if (fine_has_next()) { | |
1231 _cur_region_cur_card = | |
1232 _fine_cur_prt->_bm.get_next_one_offset(_cur_region_cur_card + 1); | |
1233 } | |
1234 while (!fine_has_next()) { | |
1235 if (_cur_region_cur_card == PosParPRT::CardsPerRegion) { | |
1236 _cur_region_cur_card = 0; | |
1237 _fine_cur_prt = _fine_cur_prt->next(); | |
1238 } | |
1239 if (_fine_cur_prt == NULL) { | |
1240 fine_find_next_non_null_prt(); | |
1241 if (_fine_cur_prt == NULL) return false; | |
1242 } | |
1243 assert(_fine_cur_prt != NULL && _cur_region_cur_card == 0, | |
1244 "inv."); | |
1245 HeapWord* r_bot = | |
1246 _fine_cur_prt->hr()->bottom(); | |
1247 _cur_region_card_offset = _bosa->index_for(r_bot); | |
1248 _cur_region_cur_card = _fine_cur_prt->_bm.get_next_one_offset(0); | |
1249 } | |
1250 assert(fine_has_next(), "Or else we exited the loop via the return."); | |
1251 card_index = _cur_region_card_offset + _cur_region_cur_card; | |
1252 return true; | |
1253 } | |
1254 | |
1255 bool HeapRegionRemSetIterator::fine_has_next() { | |
1256 return | |
1257 _fine_cur_prt != NULL && | |
1258 _cur_region_cur_card < PosParPRT::CardsPerRegion; | |
1259 } | |
1260 | |
1261 bool HeapRegionRemSetIterator::has_next(size_t& card_index) { | |
1262 switch (_is) { | |
1263 case Sparse: | |
1264 if (_sparse_iter.has_next(card_index)) { | |
1265 _n_yielded_sparse++; | |
1266 return true; | |
1267 } | |
1268 // Otherwise, deliberate fall-through | |
1269 _is = Fine; | |
1270 case Fine: | |
1271 if (fine_has_next(card_index)) { | |
1272 _n_yielded_fine++; | |
1273 return true; | |
1274 } | |
1275 // Otherwise, deliberate fall-through | |
1276 _is = Coarse; | |
1277 case Coarse: | |
1278 if (coarse_has_next(card_index)) { | |
1279 _n_yielded_coarse++; | |
1280 return true; | |
1281 } | |
1282 // Otherwise... | |
1283 break; | |
1284 } | |
1285 assert(ParallelGCThreads > 1 || | |
1286 n_yielded() == _hrrs->occupied(), | |
1287 "Should have yielded all the cards in the rem set " | |
1288 "(in the non-par case)."); | |
1289 return false; | |
1290 } | |
1291 | |
1292 | |
1293 | |
845
df6caf649ff7
6700789: G1: Enable use of compressed oops with G1 heaps
ysr
parents:
807
diff
changeset
|
1294 OopOrNarrowOopStar* HeapRegionRemSet::_recorded_oops = NULL; |
df6caf649ff7
6700789: G1: Enable use of compressed oops with G1 heaps
ysr
parents:
807
diff
changeset
|
1295 HeapWord** HeapRegionRemSet::_recorded_cards = NULL; |
df6caf649ff7
6700789: G1: Enable use of compressed oops with G1 heaps
ysr
parents:
807
diff
changeset
|
1296 HeapRegion** HeapRegionRemSet::_recorded_regions = NULL; |
df6caf649ff7
6700789: G1: Enable use of compressed oops with G1 heaps
ysr
parents:
807
diff
changeset
|
1297 int HeapRegionRemSet::_n_recorded = 0; |
342 | 1298 |
1299 HeapRegionRemSet::Event* HeapRegionRemSet::_recorded_events = NULL; | |
1300 int* HeapRegionRemSet::_recorded_event_index = NULL; | |
1301 int HeapRegionRemSet::_n_recorded_events = 0; | |
1302 | |
845
df6caf649ff7
6700789: G1: Enable use of compressed oops with G1 heaps
ysr
parents:
807
diff
changeset
|
1303 void HeapRegionRemSet::record(HeapRegion* hr, OopOrNarrowOopStar f) { |
342 | 1304 if (_recorded_oops == NULL) { |
1305 assert(_n_recorded == 0 | |
1306 && _recorded_cards == NULL | |
1307 && _recorded_regions == NULL, | |
1308 "Inv"); | |
845
df6caf649ff7
6700789: G1: Enable use of compressed oops with G1 heaps
ysr
parents:
807
diff
changeset
|
1309 _recorded_oops = NEW_C_HEAP_ARRAY(OopOrNarrowOopStar, MaxRecorded); |
df6caf649ff7
6700789: G1: Enable use of compressed oops with G1 heaps
ysr
parents:
807
diff
changeset
|
1310 _recorded_cards = NEW_C_HEAP_ARRAY(HeapWord*, MaxRecorded); |
df6caf649ff7
6700789: G1: Enable use of compressed oops with G1 heaps
ysr
parents:
807
diff
changeset
|
1311 _recorded_regions = NEW_C_HEAP_ARRAY(HeapRegion*, MaxRecorded); |
342 | 1312 } |
1313 if (_n_recorded == MaxRecorded) { | |
1314 gclog_or_tty->print_cr("Filled up 'recorded' (%d).", MaxRecorded); | |
1315 } else { | |
1316 _recorded_cards[_n_recorded] = | |
1317 (HeapWord*)align_size_down(uintptr_t(f), | |
1318 CardTableModRefBS::card_size); | |
1319 _recorded_oops[_n_recorded] = f; | |
1320 _recorded_regions[_n_recorded] = hr; | |
1321 _n_recorded++; | |
1322 } | |
1323 } | |
1324 | |
1325 void HeapRegionRemSet::record_event(Event evnt) { | |
1326 if (!G1RecordHRRSEvents) return; | |
1327 | |
1328 if (_recorded_events == NULL) { | |
1329 assert(_n_recorded_events == 0 | |
1330 && _recorded_event_index == NULL, | |
1331 "Inv"); | |
1332 _recorded_events = NEW_C_HEAP_ARRAY(Event, MaxRecordedEvents); | |
1333 _recorded_event_index = NEW_C_HEAP_ARRAY(int, MaxRecordedEvents); | |
1334 } | |
1335 if (_n_recorded_events == MaxRecordedEvents) { | |
1336 gclog_or_tty->print_cr("Filled up 'recorded_events' (%d).", MaxRecordedEvents); | |
1337 } else { | |
1338 _recorded_events[_n_recorded_events] = evnt; | |
1339 _recorded_event_index[_n_recorded_events] = _n_recorded; | |
1340 _n_recorded_events++; | |
1341 } | |
1342 } | |
1343 | |
1344 void HeapRegionRemSet::print_event(outputStream* str, Event evnt) { | |
1345 switch (evnt) { | |
1346 case Event_EvacStart: | |
1347 str->print("Evac Start"); | |
1348 break; | |
1349 case Event_EvacEnd: | |
1350 str->print("Evac End"); | |
1351 break; | |
1352 case Event_RSUpdateEnd: | |
1353 str->print("RS Update End"); | |
1354 break; | |
1355 } | |
1356 } | |
1357 | |
1358 void HeapRegionRemSet::print_recorded() { | |
1359 int cur_evnt = 0; | |
1360 Event cur_evnt_kind; | |
1361 int cur_evnt_ind = 0; | |
1362 if (_n_recorded_events > 0) { | |
1363 cur_evnt_kind = _recorded_events[cur_evnt]; | |
1364 cur_evnt_ind = _recorded_event_index[cur_evnt]; | |
1365 } | |
1366 | |
1367 for (int i = 0; i < _n_recorded; i++) { | |
1368 while (cur_evnt < _n_recorded_events && i == cur_evnt_ind) { | |
1369 gclog_or_tty->print("Event: "); | |
1370 print_event(gclog_or_tty, cur_evnt_kind); | |
1371 gclog_or_tty->print_cr(""); | |
1372 cur_evnt++; | |
1373 if (cur_evnt < MaxRecordedEvents) { | |
1374 cur_evnt_kind = _recorded_events[cur_evnt]; | |
1375 cur_evnt_ind = _recorded_event_index[cur_evnt]; | |
1376 } | |
1377 } | |
1378 gclog_or_tty->print("Added card " PTR_FORMAT " to region [" PTR_FORMAT "...]" | |
1379 " for ref " PTR_FORMAT ".\n", | |
1380 _recorded_cards[i], _recorded_regions[i]->bottom(), | |
1381 _recorded_oops[i]); | |
1382 } | |
1383 } | |
1384 | |
1385 #ifndef PRODUCT | |
1386 void HeapRegionRemSet::test() { | |
1387 os::sleep(Thread::current(), (jlong)5000, false); | |
1388 G1CollectedHeap* g1h = G1CollectedHeap::heap(); | |
1389 | |
1390 // Run with "-XX:G1LogRSRegionEntries=2", so that 1 and 5 end up in same | |
1391 // hash bucket. | |
1392 HeapRegion* hr0 = g1h->region_at(0); | |
1393 HeapRegion* hr1 = g1h->region_at(1); | |
1394 HeapRegion* hr2 = g1h->region_at(5); | |
1395 HeapRegion* hr3 = g1h->region_at(6); | |
1396 HeapRegion* hr4 = g1h->region_at(7); | |
1397 HeapRegion* hr5 = g1h->region_at(8); | |
1398 | |
1399 HeapWord* hr1_start = hr1->bottom(); | |
1400 HeapWord* hr1_mid = hr1_start + HeapRegion::GrainWords/2; | |
1401 HeapWord* hr1_last = hr1->end() - 1; | |
1402 | |
1403 HeapWord* hr2_start = hr2->bottom(); | |
1404 HeapWord* hr2_mid = hr2_start + HeapRegion::GrainWords/2; | |
1405 HeapWord* hr2_last = hr2->end() - 1; | |
1406 | |
1407 HeapWord* hr3_start = hr3->bottom(); | |
1408 HeapWord* hr3_mid = hr3_start + HeapRegion::GrainWords/2; | |
1409 HeapWord* hr3_last = hr3->end() - 1; | |
1410 | |
1411 HeapRegionRemSet* hrrs = hr0->rem_set(); | |
1412 | |
1413 // Make three references from region 0x101... | |
845
df6caf649ff7
6700789: G1: Enable use of compressed oops with G1 heaps
ysr
parents:
807
diff
changeset
|
1414 hrrs->add_reference((OopOrNarrowOopStar)hr1_start); |
df6caf649ff7
6700789: G1: Enable use of compressed oops with G1 heaps
ysr
parents:
807
diff
changeset
|
1415 hrrs->add_reference((OopOrNarrowOopStar)hr1_mid); |
df6caf649ff7
6700789: G1: Enable use of compressed oops with G1 heaps
ysr
parents:
807
diff
changeset
|
1416 hrrs->add_reference((OopOrNarrowOopStar)hr1_last); |
342 | 1417 |
845
df6caf649ff7
6700789: G1: Enable use of compressed oops with G1 heaps
ysr
parents:
807
diff
changeset
|
1418 hrrs->add_reference((OopOrNarrowOopStar)hr2_start); |
df6caf649ff7
6700789: G1: Enable use of compressed oops with G1 heaps
ysr
parents:
807
diff
changeset
|
1419 hrrs->add_reference((OopOrNarrowOopStar)hr2_mid); |
df6caf649ff7
6700789: G1: Enable use of compressed oops with G1 heaps
ysr
parents:
807
diff
changeset
|
1420 hrrs->add_reference((OopOrNarrowOopStar)hr2_last); |
342 | 1421 |
845
df6caf649ff7
6700789: G1: Enable use of compressed oops with G1 heaps
ysr
parents:
807
diff
changeset
|
1422 hrrs->add_reference((OopOrNarrowOopStar)hr3_start); |
df6caf649ff7
6700789: G1: Enable use of compressed oops with G1 heaps
ysr
parents:
807
diff
changeset
|
1423 hrrs->add_reference((OopOrNarrowOopStar)hr3_mid); |
df6caf649ff7
6700789: G1: Enable use of compressed oops with G1 heaps
ysr
parents:
807
diff
changeset
|
1424 hrrs->add_reference((OopOrNarrowOopStar)hr3_last); |
342 | 1425 |
1426 // Now cause a coarsening. | |
845
df6caf649ff7
6700789: G1: Enable use of compressed oops with G1 heaps
ysr
parents:
807
diff
changeset
|
1427 hrrs->add_reference((OopOrNarrowOopStar)hr4->bottom()); |
df6caf649ff7
6700789: G1: Enable use of compressed oops with G1 heaps
ysr
parents:
807
diff
changeset
|
1428 hrrs->add_reference((OopOrNarrowOopStar)hr5->bottom()); |
342 | 1429 |
1430 // Now, does iteration yield these three? | |
1431 HeapRegionRemSetIterator iter; | |
1432 hrrs->init_iterator(&iter); | |
1433 size_t sum = 0; | |
1434 size_t card_index; | |
1435 while (iter.has_next(card_index)) { | |
1436 HeapWord* card_start = | |
1437 G1CollectedHeap::heap()->bot_shared()->address_for_index(card_index); | |
1438 gclog_or_tty->print_cr(" Card " PTR_FORMAT ".", card_start); | |
1439 sum++; | |
1440 } | |
1441 guarantee(sum == 11 - 3 + 2048, "Failure"); | |
1442 guarantee(sum == hrrs->occupied(), "Failure"); | |
1443 } | |
1444 #endif |