1 /*
2 Copyright (C) 2005, 2006 MySQL AB
3 All rights reserved. Use is subject to license terms.
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License, version 2.0,
7 as published by the Free Software Foundation.
8
9 This program is also distributed with certain software (including
10 but not limited to OpenSSL) that is licensed under separate terms,
11 as designated in a particular file or component or in included license
12 documentation. The authors of MySQL hereby grant you an additional
13 permission to link the program and your derivative works with the
14 separately licensed software that they have included with MySQL.
15
16 This program is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 GNU General Public License, version 2.0, for more details.
20
21 You should have received a copy of the GNU General Public License
22 along with this program; if not, write to the Free Software
23 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
24 */
25
26 #ifndef LINEAR_POOL_HPP
27 #define LINEAR_POOL_HPP
28
29 #include <Bitmask.hpp>
30 #include "SuperPool.hpp"
31
32 /*
33 * LinearPool - indexed record pool
34 *
35 * LinearPool implements a pool where each record has a 0-based index.
36 * Any index value (up to 2^32-1) is allowed. Normal efficient usage is
37 * to assign index values in sequence and to re-use any values which
38 * have become free. This is default seize/release behaviour.
39 *
40 * LinearPool has 2 internal RecordPool instances:
41 *
42 * (a) record pool of T (the template argument class)
43 * (b) record pool of "maps" (array of Uint32)
44 *
45 * The maps translate an index into an i-value in (a). Each map has
46 * a level. Level 0 maps point to i-values. Level N+1 maps point to
47 * level N maps. There is a unique "root map" at top.
48 *
49 * This works exactly like numbers in a given base. Each map has base
50 * size entries. For implementation convenience the base must be power
51 * of 2 between 2^1 and 2^15. It is given by its log2 value (1-15).
52 *
53 * A position in a map is also called a "digit".
54 *
55 * There is a doubly linked list of available maps (some free entries)
56 * on each level. There is a doubly linked freelist within each map.
57 * There is also a bitmask of used entries in each map.
58 *
59 * Level 0 free entry has space for one record. Level N free entry
60 * implies space for base^N records. The implied levels are created and
61 * removed on demand. Empty maps are usually removed.
62 *
63 * Default base is 256 (log2 = 8) which requires maximum 4 levels or
64 * digits (similar to ip address).
65 *
66 * TODO
67 *
68 * - move most of the inline code to LinearPool.cpp
69 * - optimize for common case
70 * - add optimized 2-level implementation (?)
71 */
72
73 #include "SuperPool.hpp"
74
75 template <class T, Uint32 LogBase = 8>
76 class LinearPool {
77 typedef SuperPool::PtrI PtrI;
78
79 // Base.
80 STATIC_CONST( Base = 1 << LogBase );
81
82 // Digit mask.
83 STATIC_CONST( DigitMask = Base - 1 );
84
85 // Max possible levels (0 to max root level).
86 STATIC_CONST( MaxLevels = (32 + LogBase - 1) / LogBase );
87
88 // Number of words in map used bit mask.
89 STATIC_CONST( BitmaskSize = (Base + 31) / 32 );
90
91 // Map.
92 struct Map {
93 Uint32 m_level;
94 Uint32 m_occup; // number of used entries
95 Uint32 m_firstfree; // position of first free entry
96 PtrI m_parent; // parent map
97 Uint32 m_index; // from root to here
98 PtrI m_nextavail;
99 PtrI m_prevavail;
100 Uint32 m_bitmask[BitmaskSize];
101 PtrI m_entry[Base];
102 };
103
104 public:
105
106 // Constructor.
107 LinearPool(GroupPool& gp);
108
109 // Destructor.
110 ~LinearPool();
111
112 // Update pointer ptr.p according to index value ptr.i.
113 void getPtr(Ptr<T>& ptr);
114
115 // Allocate record from the pool. Reuses free index if possible.
116 bool seize(Ptr<T>& ptr);
117
118 // Allocate given index. Like seize but returns -1 if in use.
119 int seize_index(Ptr<T>& ptr, Uint32 index);
120
121 // Return record to the pool.
122 void release(Ptr<T>& ptr);
123
124 // Return number of used records (may require 1 page scan).
125 Uint32 count();
126
127 // Verify (debugging).
128 void verify();
129
130 private:
131
132 // Given index find the bottom map.
133 void get_map(Ptr<Map>& map_ptr, Uint32 index);
134
135 // Add new root map and increase level
136 bool add_root();
137
138 // Add new non-root map.
139 bool add_map(Ptr<Map>& map_ptr, Ptr<Map> parent_ptr, Uint32 digit);
140
141 // Subroutine to initialize map free lists.
142 void init_free(Ptr<Map> map_ptr);
143
144 // Add entry at given free position.
145 void add_entry(Ptr<Map> map_ptr, Uint32 digit, PtrI ptr_i);
146
147 // Remove entry and map if it becomes empty.
148 void remove_entry(Ptr<Map> map_ptr, Uint32 digit);
149
150 // Remove map and all parents which become empty.
151 void remove_map(Ptr<Map> map_ptr);
152
153 // Add map to available list.
154 void add_avail(Ptr<Map> map_ptr);
155
156 // Remove map from available list.
157 void remove_avail(Ptr<Map> map_ptr);
158
159 // Verify available lists
160 void verify_avail();
161
162 // Verify map (recursive).
163 void verify_map(Ptr<Map> map_ptr, Uint32 level, Uint32* count);
164
165 RecordPool<T> m_records;
166 RecordPool<Map> m_maps;
167 Uint32 m_levels; // 0 means empty pool
168 PtrI m_root;
169 PtrI m_avail[MaxLevels];
170 };
171
172 template <class T, Uint32 LogBase>
173 inline
LinearPool(GroupPool & gp)174 LinearPool<T, LogBase>::LinearPool(GroupPool& gp) :
175 m_records(gp),
176 m_maps(gp),
177 m_levels(0),
178 m_root(RNIL)
179 {
180 Uint32 n;
181 for (n = 0; n < MaxLevels; n++)
182 m_avail[n] = RNIL;
183 }
184
185 template <class T, Uint32 LogBase>
186 inline
~LinearPool()187 LinearPool<T, LogBase>::~LinearPool()
188 {
189 }
190
191 template <class T, Uint32 LogBase>
192 inline void
getPtr(Ptr<T> & ptr)193 LinearPool<T, LogBase>::getPtr(Ptr<T>& ptr)
194 {
195 Uint32 index = ptr.i;
196 // get level 0 map
197 Ptr<Map> map_ptr;
198 get_map(map_ptr, index);
199 // get record
200 Ptr<T> rec_ptr;
201 Uint32 digit = index & DigitMask;
202 assert(BitmaskImpl::get(BitmaskSize, map_ptr.p->m_bitmask, digit));
203 rec_ptr.i = map_ptr.p->m_entry[digit];
204 m_records.getPtr(rec_ptr);
205 ptr.p = rec_ptr.p;
206 }
207
208 template <class T, Uint32 LogBase>
209 inline bool
seize(Ptr<T> & ptr)210 LinearPool<T, LogBase>::seize(Ptr<T>& ptr)
211 {
212 // look for free list on some level
213 Ptr<Map> map_ptr;
214 map_ptr.i = RNIL;
215 Uint32 n = 0;
216 while (n < m_levels) {
217 if ((map_ptr.i = m_avail[n]) != RNIL)
218 break;
219 n++;
220 }
221 if (map_ptr.i == RNIL) {
222 // add new level with available maps
223 if (! add_root())
224 return false;
225 assert(n < m_levels);
226 map_ptr.i = m_avail[n];
227 }
228 m_maps.getPtr(map_ptr);
229 // walk down creating missing levels and using an entry on each
230 Uint32 digit;
231 Ptr<Map> new_ptr;
232 new_ptr.i = RNIL;
233 while (true) {
234 digit = map_ptr.p->m_firstfree;
235 if (n == 0)
236 break;
237 Ptr<Map> child_ptr;
238 if (! add_map(child_ptr, map_ptr, digit)) {
239 if (new_ptr.i != RNIL)
240 remove_map(new_ptr);
241 return false;
242 }
243 new_ptr = child_ptr;
244 map_ptr = child_ptr;
245 n--;
246 }
247 // now on level 0
248 assert(map_ptr.p->m_level == 0);
249 Ptr<T> rec_ptr;
250 if (! m_records.seize(rec_ptr)) {
251 if (new_ptr.i != RNIL)
252 remove_map(new_ptr);
253 return false;
254 }
255 add_entry(map_ptr, digit, rec_ptr.i);
256 ptr.i = digit + (map_ptr.p->m_index << LogBase);
257 ptr.p = rec_ptr.p;
258 return true;
259 }
260
261 template <class T, Uint32 LogBase>
262 inline int
seize_index(Ptr<T> & ptr,Uint32 index)263 LinearPool<T, LogBase>::seize_index(Ptr<T>& ptr, Uint32 index)
264 {
265 // extract all digits at least up to current root level
266 Uint32 digits[MaxLevels];
267 Uint32 n = 0;
268 Uint32 tmp = index;
269 do {
270 digits[n] = tmp & DigitMask;
271 tmp >>= LogBase;
272 } while (++n < m_levels || tmp != 0);
273 // add any new root levels
274 while (n > m_levels) {
275 if (! add_root())
276 return false;
277 }
278 // start from root
279 Ptr<Map> map_ptr;
280 map_ptr.i = m_root;
281 m_maps.getPtr(map_ptr);
282 // walk down creating or re-using existing levels
283 Uint32 digit;
284 bool used;
285 Ptr<Map> new_ptr;
286 new_ptr.i = RNIL;
287 while (true) {
288 digit = digits[--n];
289 used = BitmaskImpl::get(BitmaskSize, map_ptr.p->m_bitmask, digit);
290 if (n == 0)
291 break;
292 if (used) {
293 map_ptr.i = map_ptr.p->m_entry[digit];
294 m_maps.getPtr(map_ptr);
295 } else {
296 Ptr<Map> child_ptr;
297 if (! add_map(child_ptr, map_ptr, digit)) {
298 if (new_ptr.i != RNIL)
299 remove_map(new_ptr);
300 }
301 new_ptr = child_ptr;
302 map_ptr = child_ptr;
303 }
304 }
305 // now at level 0
306 assert(map_ptr.p->m_level == 0);
307 Ptr<T> rec_ptr;
308 if (used || ! m_records.seize(rec_ptr)) {
309 if (new_ptr.i != RNIL)
310 remove_map(new_ptr);
311 return used ? -1 : false;
312 }
313 add_entry(map_ptr, digit, rec_ptr.i);
314 assert(index == digit + (map_ptr.p->m_index << LogBase));
315 ptr.i = index;
316 ptr.p = rec_ptr.p;
317 return true;
318 }
319
320 template <class T, Uint32 LogBase>
321 inline void
release(Ptr<T> & ptr)322 LinearPool<T, LogBase>::release(Ptr<T>& ptr)
323 {
324 Uint32 index = ptr.i;
325 // get level 0 map
326 Ptr<Map> map_ptr;
327 get_map(map_ptr, index);
328 // release record
329 Ptr<T> rec_ptr;
330 Uint32 digit = index & DigitMask;
331 rec_ptr.i = map_ptr.p->m_entry[digit];
332 m_records.release(rec_ptr);
333 // remove entry
334 remove_entry(map_ptr, digit);
335 // null pointer
336 ptr.i = RNIL;
337 ptr.p = 0;
338 }
339
340 template <class T, Uint32 LogBase>
341 inline Uint32
count()342 LinearPool<T, LogBase>::count()
343 {
344 SuperPool& sp = m_records.m_superPool;
345 Uint32 count1 = sp.getRecUseCount(m_records.m_recInfo);
346 return count1;
347 }
348
349 template <class T, Uint32 LogBase>
350 inline void
verify()351 LinearPool<T, LogBase>::verify()
352 {
353 verify_avail();
354 if (m_root == RNIL) {
355 assert(m_levels == 0);
356 return;
357 }
358 assert(m_levels != 0);
359 Ptr<Map> map_ptr;
360 map_ptr.i = m_root;
361 m_maps.getPtr(map_ptr);
362 Uint32 count1 = count();
363 Uint32 count2 = 0;
364 verify_map(map_ptr, m_levels - 1, &count2);
365 assert(count1 == count2);
366 }
367
368 // private methods
369
370 template <class T, Uint32 LogBase>
371 inline void
get_map(Ptr<Map> & map_ptr,Uint32 index)372 LinearPool<T, LogBase>::get_map(Ptr<Map>& map_ptr, Uint32 index)
373 {
374 // root map must exist
375 Ptr<Map> tmp_ptr;
376 tmp_ptr.i = m_root;
377 m_maps.getPtr(tmp_ptr);
378 assert(tmp_ptr.p->m_level + 1 == m_levels);
379 // extract index digits up to current root level
380 Uint32 digits[MaxLevels];
381 Uint32 n = 0;
382 do {
383 digits[n] = index & DigitMask;
384 index >>= LogBase;
385 } while (++n < m_levels);
386 assert(index == 0);
387 // walk down indirect levels
388 while (--n > 0) {
389 tmp_ptr.i = tmp_ptr.p->m_entry[digits[n]];
390 m_maps.getPtr(tmp_ptr);
391 }
392 // level 0 map
393 assert(tmp_ptr.p->m_level == 0);
394 map_ptr = tmp_ptr;
395 }
396
397 template <class T, Uint32 LogBase>
398 inline bool
add_root()399 LinearPool<T, LogBase>::add_root()
400 {
401 // new root
402 Ptr<Map> map_ptr;
403 if (! m_maps.seize(map_ptr))
404 return false;
405 Uint32 n = m_levels++;
406 assert(n < MaxLevels);
407 // set up
408 map_ptr.p->m_level = n;
409 map_ptr.p->m_parent = RNIL;
410 map_ptr.p->m_index = 0;
411 init_free(map_ptr);
412 // on level > 0 digit 0 points to old root
413 if (n > 0) {
414 Ptr<Map> old_ptr;
415 old_ptr.i = m_root;
416 m_maps.getPtr(old_ptr);
417 assert(old_ptr.p->m_parent == RNIL);
418 old_ptr.p->m_parent = map_ptr.i;
419 add_entry(map_ptr, 0, old_ptr.i);
420 }
421 // set new root
422 m_root = map_ptr.i;
423 return true;
424 }
425
426 template <class T, Uint32 LogBase>
427 inline bool
add_map(Ptr<Map> & map_ptr,Ptr<Map> parent_ptr,Uint32 digit)428 LinearPool<T, LogBase>::add_map(Ptr<Map>& map_ptr, Ptr<Map> parent_ptr, Uint32 digit)
429 {
430 if (! m_maps.seize(map_ptr))
431 return false;
432 assert(parent_ptr.p->m_level != 0);
433 // set up
434 map_ptr.p->m_level = parent_ptr.p->m_level - 1;
435 map_ptr.p->m_parent = parent_ptr.i;
436 map_ptr.p->m_index = digit + (parent_ptr.p->m_index << LogBase);
437 init_free(map_ptr);
438 add_entry(parent_ptr, digit, map_ptr.i);
439 return true;
440 }
441
442 template <class T, Uint32 LogBase>
443 inline void
init_free(Ptr<Map> map_ptr)444 LinearPool<T, LogBase>::init_free(Ptr<Map> map_ptr)
445 {
446 map_ptr.p->m_occup = 0;
447 map_ptr.p->m_firstfree = 0;
448 // freelist
449 Uint32 j;
450 Uint16 back = ZNIL;
451 for (j = 0; j < Base - 1; j++) {
452 map_ptr.p->m_entry[j] = back | ((j + 1) << 16);
453 back = j;
454 }
455 map_ptr.p->m_entry[j] = back | (ZNIL << 16);
456 // bitmask
457 BitmaskImpl::clear(BitmaskSize, map_ptr.p->m_bitmask);
458 // add to available
459 add_avail(map_ptr);
460 }
461
462 template <class T, Uint32 LogBase>
463 inline void
add_entry(Ptr<Map> map_ptr,Uint32 digit,PtrI ptr_i)464 LinearPool<T, LogBase>::add_entry(Ptr<Map> map_ptr, Uint32 digit, PtrI ptr_i)
465 {
466 assert(map_ptr.p->m_occup < Base && digit < Base);
467 assert(! BitmaskImpl::get(BitmaskSize, map_ptr.p->m_bitmask, digit));
468 // unlink from freelist
469 Uint32 val = map_ptr.p->m_entry[digit];
470 Uint16 back = val & ZNIL;
471 Uint16 forw = val >> 16;
472 if (back != ZNIL) {
473 assert(back < Base);
474 map_ptr.p->m_entry[back] &= ZNIL;
475 map_ptr.p->m_entry[back] |= (forw << 16);
476 }
477 if (forw != ZNIL) {
478 assert(forw < Base);
479 map_ptr.p->m_entry[forw] &= (ZNIL << 16);
480 map_ptr.p->m_entry[forw] |= back;
481 }
482 if (back == ZNIL) {
483 map_ptr.p->m_firstfree = forw;
484 }
485 // set new value
486 map_ptr.p->m_entry[digit] = ptr_i;
487 map_ptr.p->m_occup++;
488 BitmaskImpl::set(BitmaskSize, map_ptr.p->m_bitmask, digit);
489 if (map_ptr.p->m_occup == Base)
490 remove_avail(map_ptr);
491 }
492
493 template <class T, Uint32 LogBase>
494 inline void
remove_entry(Ptr<Map> map_ptr,Uint32 digit)495 LinearPool<T, LogBase>::remove_entry(Ptr<Map> map_ptr, Uint32 digit)
496 {
497 assert(map_ptr.p->m_occup != 0 && digit < Base);
498 assert(BitmaskImpl::get(BitmaskSize, map_ptr.p->m_bitmask, digit));
499 // add to freelist
500 Uint32 firstfree = map_ptr.p->m_firstfree;
501 map_ptr.p->m_entry[digit] = ZNIL | (firstfree << 16);
502 if (firstfree != ZNIL) {
503 assert(firstfree < Base);
504 map_ptr.p->m_entry[firstfree] &= (ZNIL << 16);
505 map_ptr.p->m_entry[firstfree] |= digit;
506 }
507 map_ptr.p->m_firstfree = digit;
508 map_ptr.p->m_occup--;
509 BitmaskImpl::clear(BitmaskSize, map_ptr.p->m_bitmask, digit);
510 if (map_ptr.p->m_occup + 1 == Base)
511 add_avail(map_ptr);
512 else if (map_ptr.p->m_occup == 0)
513 remove_map(map_ptr);
514 }
515
516 template <class T, Uint32 LogBase>
517 inline void
remove_map(Ptr<Map> map_ptr)518 LinearPool<T, LogBase>::remove_map(Ptr<Map> map_ptr)
519 {
520 assert(map_ptr.p->m_occup == 0);
521 remove_avail(map_ptr);
522 Ptr<Map> parent_ptr;
523 parent_ptr.i = map_ptr.p->m_parent;
524 Uint32 digit = map_ptr.p->m_index & DigitMask;
525 PtrI map_ptr_i = map_ptr.i;
526 m_maps.release(map_ptr);
527 if (m_root == map_ptr_i) {
528 assert(parent_ptr.i == RNIL);
529 Uint32 used = count();
530 assert(used == 0);
531 m_root = RNIL;
532 m_levels = 0;
533 }
534 if (parent_ptr.i != RNIL) {
535 m_maps.getPtr(parent_ptr);
536 // remove child entry (recursive)
537 remove_entry(parent_ptr, digit);
538 }
539 }
540
541 template <class T, Uint32 LogBase>
542 inline void
add_avail(Ptr<Map> map_ptr)543 LinearPool<T, LogBase>::add_avail(Ptr<Map> map_ptr)
544 {
545 Uint32 n = map_ptr.p->m_level;
546 assert(n < m_levels);
547 map_ptr.p->m_nextavail = m_avail[n];
548 if (map_ptr.p->m_nextavail != RNIL) {
549 Ptr<Map> next_ptr;
550 next_ptr.i = map_ptr.p->m_nextavail;
551 m_maps.getPtr(next_ptr);
552 next_ptr.p->m_prevavail = map_ptr.i;
553 }
554 map_ptr.p->m_prevavail = RNIL;
555 m_avail[n] = map_ptr.i;
556 }
557
558 template <class T, Uint32 LogBase>
559 inline void
remove_avail(Ptr<Map> map_ptr)560 LinearPool<T, LogBase>::remove_avail(Ptr<Map> map_ptr)
561 {
562 Uint32 n = map_ptr.p->m_level;
563 assert(n < m_levels);
564 if (map_ptr.p->m_nextavail != RNIL) {
565 Ptr<Map> next_ptr;
566 next_ptr.i = map_ptr.p->m_nextavail;
567 m_maps.getPtr(next_ptr);
568 next_ptr.p->m_prevavail = map_ptr.p->m_prevavail;
569 }
570 if (map_ptr.p->m_prevavail != RNIL) {
571 Ptr<Map> prev_ptr;
572 prev_ptr.i = map_ptr.p->m_prevavail;
573 m_maps.getPtr(prev_ptr);
574 prev_ptr.p->m_nextavail = map_ptr.p->m_nextavail;
575 }
576 if (map_ptr.p->m_prevavail == RNIL) {
577 m_avail[n] = map_ptr.p->m_nextavail;
578 }
579 map_ptr.p->m_nextavail = RNIL;
580 map_ptr.p->m_prevavail = RNIL;
581 }
582
583 template <class T, Uint32 LogBase>
584 inline void
verify_avail()585 LinearPool<T, LogBase>::verify_avail()
586 {
587 // check available lists
588 for (Uint32 n = 0; n < MaxLevels; n++) {
589 Ptr<Map> map_ptr;
590 map_ptr.i = m_avail[n];
591 Uint32 back = RNIL;
592 while (map_ptr.i != RNIL) {
593 m_maps.getPtr(map_ptr);
594 assert(map_ptr.p->m_occup < Base);
595 assert(back == map_ptr.p->m_prevavail);
596 back = map_ptr.i;
597 map_ptr.i = map_ptr.p->m_nextavail;
598 }
599 }
600 }
601
602 template <class T, Uint32 LogBase>
603 inline void
verify_map(Ptr<Map> map_ptr,Uint32 level,Uint32 * count)604 LinearPool<T, LogBase>::verify_map(Ptr<Map> map_ptr, Uint32 level, Uint32* count)
605 {
606 assert(level < MaxLevels);
607 assert(map_ptr.p->m_level == level);
608 // check freelist
609 {
610 Uint32 nused = BitmaskImpl::count(BitmaskSize, map_ptr.p->m_bitmask);
611 assert(nused <= Base);
612 assert(map_ptr.p->m_occup == nused);
613 Uint32 nfree = 0;
614 Uint32 j = map_ptr.p->m_firstfree;
615 Uint16 back = ZNIL;
616 while (j != ZNIL) {
617 assert(j < Base);
618 assert(! BitmaskImpl::get(BitmaskSize, map_ptr.p->m_bitmask, j));
619 Uint32 val = map_ptr.p->m_entry[j];
620 assert(back == (val & ZNIL));
621 back = j;
622 j = (val >> 16);
623 nfree++;
624 }
625 assert(nused + nfree == Base);
626 }
627 // check entries
628 {
629 for (Uint32 j = 0; j < Base; j++) {
630 bool free = ! BitmaskImpl::get(BitmaskSize, map_ptr.p->m_bitmask, j);
631 if (free)
632 continue;
633 if (level != 0) {
634 Ptr<Map> child_ptr;
635 child_ptr.i = map_ptr.p->m_entry[j];
636 m_maps.getPtr(child_ptr);
637 assert(child_ptr.p->m_parent == map_ptr.i);
638 assert(child_ptr.p->m_index == j + (map_ptr.p->m_index << LogBase));
639 verify_map(child_ptr, level - 1, count);
640 } else {
641 Ptr<T> rec_ptr;
642 rec_ptr.i = map_ptr.p->m_entry[j];
643 m_records.getPtr(rec_ptr);
644 (*count)++;
645 }
646 }
647 }
648 // check membership on available list
649 {
650 Ptr<Map> avail_ptr;
651 avail_ptr.i = m_avail[map_ptr.p->m_level];
652 bool found = false;
653 while (avail_ptr.i != RNIL) {
654 if (avail_ptr.i == map_ptr.i) {
655 found = true;
656 break;
657 }
658 m_maps.getPtr(avail_ptr);
659 avail_ptr.i = avail_ptr.p->m_nextavail;
660 }
661 assert(found == (map_ptr.p->m_occup < Base));
662 }
663 }
664
665 #endif
666