1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
|
////////////////////////////////////////////////////////////////////////////////
// The Loki Library
// Copyright (c) 2001 by Andrei Alexandrescu
// This code accompanies the book:
// Alexandrescu, Andrei. "Modern C++ Design: Generic Programming and Design
// Patterns Applied". Copyright (c) 2001. Addison-Wesley.
// Permission to use, copy, modify, distribute and sell this software for any
// purpose is hereby granted without fee, provided that the above copyright
// notice appear in all copies and that both that copyright notice and this
// permission notice appear in supporting documentation.
// The author or Addison-Wesley Longman make no representations about the
// suitability of this software for any purpose. It is provided "as is"
// without express or implied warranty.
////////////////////////////////////////////////////////////////////////////////
// $Id: SmallObj.cpp 823 2007-05-08 10:48:40Z lfittl $
#include "SmallObj.h"
#include <cassert>
#include <climits>
#include <vector>
#include <bitset>
//#define DO_EXTRA_LOKI_TESTS
//#define USE_NEW_TO_ALLOCATE
//#define LOKI_CHECK_FOR_CORRUPTION
#ifdef DO_EXTRA_LOKI_TESTS
#include <iostream>
#endif
namespace Loki
{
/** @struct Chunk
@ingroup SmallObjectGroupInternal
Contains info about each allocated Chunk - which is a collection of
contiguous blocks. Each block is the same size, as specified by the
FixedAllocator. The number of blocks in a Chunk depends upon page size.
This is a POD-style struct with value-semantics. All functions and data
are private so that they can not be changed by anything other than the
FixedAllocator which owns the Chunk.
@par Minimal Interface
For the sake of runtime efficiency, no constructor, destructor, or
copy-assignment operator is defined. The inline functions made by the
compiler should be sufficient, and perhaps faster than hand-crafted
functions. The lack of these functions allows vector to create and copy
Chunks as needed without overhead. The Init and Release functions do
what the default constructor and destructor would do. A Chunk is not in
a usable state after it is constructed and before calling Init. Nor is
a Chunk usable after Release is called, but before the destructor.
@par Efficiency
Down near the lowest level of the allocator, runtime efficiencies trump
almost all other considerations. Each function does the minimum required
of it. All functions should execute in constant time to prevent higher-
level code from unwittingly using a version of Shlemiel the Painter's
Algorithm.
@par Stealth Indexes
The first char of each empty block contains the index of the next empty
block. These stealth indexes form a singly-linked list within the blocks.
A Chunk is corrupt if this singly-linked list has a loop or is shorter
than blocksAvailable_. Much of the allocator's time and space efficiency
comes from how these stealth indexes are implemented.
*/
class Chunk
{
private:
friend class FixedAllocator;
/** Initializes a just-constructed Chunk.
@param blockSize Number of bytes per block.
@param blocks Number of blocks per Chunk.
@return True for success, false for failure.
*/
bool Init( std::size_t blockSize, unsigned char blocks );
/** Allocate a block within the Chunk. Complexity is always O(1), and
this will never throw. Does not actually "allocate" by calling
malloc, new, or any other function, but merely adjusts some internal
indexes to indicate an already allocated block is no longer available.
@return Pointer to block within Chunk.
*/
void * Allocate( std::size_t blockSize );
/** Deallocate a block within the Chunk. Complexity is always O(1), and
this will never throw. For efficiency, this assumes the address is
within the block and aligned along the correct byte boundary. An
assertion checks the alignment, and a call to HasBlock is done from
within VicinityFind. Does not actually "deallocate" by calling free,
delete, or other function, but merely adjusts some internal indexes to
indicate a block is now available.
*/
void Deallocate( void * p, std::size_t blockSize );
/** Resets the Chunk back to pristine values. The available count is
set back to zero, and the first available index is set to the zeroth
block. The stealth indexes inside each block are set to point to the
next block. This assumes the Chunk's data was already using Init.
*/
void Reset( std::size_t blockSize, unsigned char blocks );
/// Releases the allocated block of memory.
void Release();
/** Determines if the Chunk has been corrupted.
@param numBlocks Total # of blocks in the Chunk.
@param blockSize # of bytes in each block.
@param checkIndexes True if caller wants to check indexes of available
blocks for corruption. If false, then caller wants to skip some
tests tests just to run faster. (Debug version does more checks, but
release version runs faster.)
@return True if Chunk is corrupt.
*/
bool IsCorrupt( unsigned char numBlocks, std::size_t blockSize,
bool checkIndexes ) const;
/** Determines if block is available.
@param p Address of block managed by Chunk.
@param numBlocks Total # of blocks in the Chunk.
@param blockSize # of bytes in each block.
@return True if block is available, else false if allocated.
*/
bool IsBlockAvailable( void * p, unsigned char numBlocks,
std::size_t blockSize ) const;
/// Returns true if block at address P is inside this Chunk.
inline bool HasBlock( void * p, std::size_t chunkLength ) const
{
unsigned char * pc = static_cast< unsigned char * >( p );
return ( pData_ <= pc ) && ( pc < pData_ + chunkLength );
}
inline bool HasAvailable( unsigned char numBlocks ) const
{ return ( blocksAvailable_ == numBlocks ); }
inline bool IsFilled( void ) const
{ return ( 0 == blocksAvailable_ ); }
/// Pointer to array of allocated blocks.
unsigned char * pData_;
/// Index of first empty block.
unsigned char firstAvailableBlock_;
/// Count of empty blocks.
unsigned char blocksAvailable_;
};
/** @class FixedAllocator
@ingroup SmallObjectGroupInternal
Offers services for allocating fixed-sized objects. It has a container
of "containers" of fixed-size blocks. The outer container has all the
Chunks. The inner container is a Chunk which owns some blocks.
@par Class Level Invariants
- There is always either zero or one Chunk which is empty.
- If this has no empty Chunk, then emptyChunk_ is NULL.
- If this has an empty Chunk, then emptyChunk_ points to it.
- If the Chunk container is empty, then deallocChunk_ and allocChunk_
are NULL.
- If the Chunk container is not-empty, then deallocChunk_ and allocChunk_
are either NULL or point to Chunks within the container.
- allocChunk_ will often point to the last Chunk in the container since
it was likely allocated most recently, and therefore likely to have an
available block.
*/
class FixedAllocator
{
private:
/** Deallocates the block at address p, and then handles the internal
bookkeeping needed to maintain class invariants. This assumes that
deallocChunk_ points to the correct chunk.
*/
void DoDeallocate( void * p );
/** Creates an empty Chunk and adds it to the end of the ChunkList.
All calls to the lower-level memory allocation functions occur inside
this function, and so the only try-catch block is inside here.
@return true for success, false for failure.
*/
bool MakeNewChunk( void );
/** Finds the Chunk which owns the block at address p. It starts at
deallocChunk_ and searches in both forwards and backwards directions
from there until it finds the Chunk which owns p. This algorithm
should find the Chunk quickly if it is deallocChunk_ or is close to it
in the Chunks container. This goes both forwards and backwards since
that works well for both same-order and opposite-order deallocations.
(Same-order = objects are deallocated in the same order in which they
were allocated. Opposite order = objects are deallocated in a last to
first order. Complexity is O(C) where C is count of all Chunks. This
never throws.
@return Pointer to Chunk that owns p, or NULL if no owner found.
*/
Chunk * VicinityFind( void * p ) const;
/// Not implemented.
FixedAllocator(const FixedAllocator&);
/// Not implemented.
FixedAllocator& operator=(const FixedAllocator&);
/// Type of container used to hold Chunks.
typedef std::vector< Chunk > Chunks;
/// Iterator through container of Chunks.
typedef Chunks::iterator ChunkIter;
/// Iterator through const container of Chunks.
typedef Chunks::const_iterator ChunkCIter;
/// Fewest # of objects managed by a Chunk.
static unsigned char MinObjectsPerChunk_;
/// Most # of objects managed by a Chunk - never exceeds UCHAR_MAX.
static unsigned char MaxObjectsPerChunk_;
/// Number of bytes in a single block within a Chunk.
std::size_t blockSize_;
/// Number of blocks managed by each Chunk.
unsigned char numBlocks_;
/// Container of Chunks.
Chunks chunks_;
/// Pointer to Chunk used for last or next allocation.
Chunk * allocChunk_;
/// Pointer to Chunk used for last or next deallocation.
Chunk * deallocChunk_;
/// Pointer to the only empty Chunk if there is one, else NULL.
Chunk * emptyChunk_;
public:
/// Create a FixedAllocator which manages blocks of 'blockSize' size.
FixedAllocator();
/// Destroy the FixedAllocator and release all its Chunks.
~FixedAllocator();
/// Initializes a FixedAllocator by calculating # of blocks per Chunk.
void Initialize( std::size_t blockSize, std::size_t pageSize );
/** Returns pointer to allocated memory block of fixed size - or NULL
if it failed to allocate.
*/
void * Allocate( void );
/** Deallocate a memory block previously allocated with Allocate. If
the block is not owned by this FixedAllocator, it returns false so
that SmallObjAllocator can call the default deallocator. If the
block was found, this returns true.
*/
bool Deallocate( void * p, Chunk * hint );
/// Returns block size with which the FixedAllocator was initialized.
inline std::size_t BlockSize() const { return blockSize_; }
/** Releases the memory used by the empty Chunk. This will take
constant time under any situation.
@return True if empty chunk found and released, false if none empty.
*/
bool TrimEmptyChunk( void );
/** Releases unused spots from ChunkList. This takes constant time
with respect to # of Chunks, but actual time depends on underlying
memory allocator.
@return False if no unused spots, true if some found and released.
*/
bool TrimChunkList( void );
/** Returns count of empty Chunks held by this allocator. Complexity
is O(C) where C is the total number of Chunks - empty or used.
*/
std::size_t CountEmptyChunks( void ) const;
/** Determines if FixedAllocator is corrupt. Checks data members to
see if any have erroneous values, or violate class invariants. It
also checks if any Chunk is corrupt. Complexity is O(C) where C is
the number of Chunks. If any data is corrupt, this will return true
in release mode, or assert in debug mode.
*/
bool IsCorrupt( void ) const;
/** Returns true if the block at address p is within a Chunk owned by
this FixedAllocator. Complexity is O(C) where C is the total number
of Chunks - empty or used.
*/
const Chunk * HasBlock( void * p ) const;
inline Chunk * HasBlock( void * p )
{
return const_cast< Chunk * >(
const_cast< const FixedAllocator * >( this )->HasBlock( p ) );
}
};
unsigned char FixedAllocator::MinObjectsPerChunk_ = 8;
unsigned char FixedAllocator::MaxObjectsPerChunk_ = UCHAR_MAX;
// Chunk::Init ----------------------------------------------------------------
bool Chunk::Init( std::size_t blockSize, unsigned char blocks )
{
assert(blockSize > 0);
assert(blocks > 0);
// Overflow check
const std::size_t allocSize = blockSize * blocks;
assert( allocSize / blockSize == blocks);
#ifdef USE_NEW_TO_ALLOCATE
// If this new operator fails, it will throw, and the exception will get
// caught one layer up.
pData_ = static_cast< unsigned char * >( ::operator new ( allocSize ) );
#else
// malloc can't throw, so its only way to indicate an error is to return
// a NULL pointer, so we have to check for that.
pData_ = static_cast< unsigned char * >( ::std::malloc( allocSize ) );
if ( NULL == pData_ ) return false;
#endif
Reset( blockSize, blocks );
return true;
}
// Chunk::Reset ---------------------------------------------------------------
void Chunk::Reset(std::size_t blockSize, unsigned char blocks)
{
assert(blockSize > 0);
assert(blocks > 0);
// Overflow check
assert((blockSize * blocks) / blockSize == blocks);
firstAvailableBlock_ = 0;
blocksAvailable_ = blocks;
unsigned char i = 0;
for ( unsigned char * p = pData_; i != blocks; p += blockSize )
{
*p = ++i;
}
}
// Chunk::Release -------------------------------------------------------------
void Chunk::Release()
{
assert( NULL != pData_ );
#ifdef USE_NEW_TO_ALLOCATE
::operator delete ( pData_ );
#else
::std::free( static_cast< void * >( pData_ ) );
#endif
}
// Chunk::Allocate ------------------------------------------------------------
void* Chunk::Allocate(std::size_t blockSize)
{
if ( IsFilled() ) return NULL;
assert((firstAvailableBlock_ * blockSize) / blockSize ==
firstAvailableBlock_);
unsigned char * pResult = pData_ + (firstAvailableBlock_ * blockSize);
firstAvailableBlock_ = *pResult;
--blocksAvailable_;
return pResult;
}
// Chunk::Deallocate ----------------------------------------------------------
void Chunk::Deallocate(void* p, std::size_t blockSize)
{
assert(p >= pData_);
unsigned char* toRelease = static_cast<unsigned char*>(p);
// Alignment check
assert((toRelease - pData_) % blockSize == 0);
unsigned char index = static_cast< unsigned char >(
( toRelease - pData_ ) / blockSize);
#if defined(DEBUG) || defined(_DEBUG)
// Check if block was already deleted. Attempting to delete the same
// block more than once causes Chunk's linked-list of stealth indexes to
// become corrupt. And causes count of blocksAvailable_ to be wrong.
if ( 0 < blocksAvailable_ )
assert( firstAvailableBlock_ != index );
#endif
*toRelease = firstAvailableBlock_;
firstAvailableBlock_ = index;
// Truncation check
assert(firstAvailableBlock_ == (toRelease - pData_) / blockSize);
++blocksAvailable_;
}
// Chunk::IsCorrupt -----------------------------------------------------------
bool Chunk::IsCorrupt( unsigned char numBlocks, std::size_t blockSize,
bool checkIndexes ) const
{
if ( numBlocks < blocksAvailable_ )
{
// Contents at this Chunk corrupted. This might mean something has
// overwritten memory owned by the Chunks container.
assert( false );
return true;
}
if ( IsFilled() )
// Useless to do further corruption checks if all blocks allocated.
return false;
unsigned char index = firstAvailableBlock_;
if ( numBlocks <= index )
{
// Contents at this Chunk corrupted. This might mean something has
// overwritten memory owned by the Chunks container.
assert( false );
return true;
}
if ( !checkIndexes )
// Caller chose to skip more complex corruption tests.
return false;
/* If the bit at index was set in foundBlocks, then the stealth index was
found on the linked-list.
*/
std::bitset< UCHAR_MAX > foundBlocks;
unsigned char * nextBlock = NULL;
/* The loop goes along singly linked-list of stealth indexes and makes sure
that each index is within bounds (0 <= index < numBlocks) and that the
index was not already found while traversing the linked-list. The linked-
list should have exactly blocksAvailable_ nodes, so the for loop will not
check more than blocksAvailable_. This loop can't check inside allocated
blocks for corruption since such blocks are not within the linked-list.
Contents of allocated blocks are not changed by Chunk.
Here are the types of corrupted link-lists which can be verified. The
corrupt index is shown with asterisks in each example.
Type 1: Index is too big.
numBlocks == 64
blocksAvailable_ == 7
firstAvailableBlock_ -> 17 -> 29 -> *101*
There should be no indexes which are equal to or larger than the total
number of blocks. Such an index would refer to a block beyond the
Chunk's allocated domain.
Type 2: Index is repeated.
numBlocks == 64
blocksAvailable_ == 5
firstAvailableBlock_ -> 17 -> 29 -> 53 -> *17* -> 29 -> 53 ...
No index should be repeated within the linked-list since that would
indicate the presence of a loop in the linked-list.
*/
for ( unsigned char cc = 0; ; )
{
nextBlock = pData_ + ( index * blockSize );
foundBlocks.set( index, true );
++cc;
if ( cc >= blocksAvailable_ )
// Successfully counted off number of nodes in linked-list.
break;
index = *nextBlock;
if ( numBlocks <= index )
{
/* This catches Type 1 corruptions as shown in above comments.
This implies that a block was corrupted due to a stray pointer
or an operation on a nearby block overran the size of the block.
*/
assert( false );
return true;
}
if ( foundBlocks.test( index ) )
{
/* This catches Type 2 corruptions as shown in above comments.
This implies that a block was corrupted due to a stray pointer
or an operation on a nearby block overran the size of the block.
Or perhaps the program tried to delete a block more than once.
*/
assert( false );
return true;
}
}
if ( foundBlocks.count() != blocksAvailable_ )
{
/* This implies that the singly-linked-list of stealth indexes was
corrupted. Ideally, this should have been detected within the loop.
*/
assert( false );
return true;
}
return false;
}
// Chunk::IsBlockAvailable ----------------------------------------------------
bool Chunk::IsBlockAvailable( void * p, unsigned char numBlocks,
std::size_t blockSize ) const
{
(void) numBlocks;
if ( IsFilled() )
return false;
unsigned char * place = static_cast< unsigned char * >( p );
// Alignment check
assert( ( place - pData_ ) % blockSize == 0 );
unsigned char blockIndex = static_cast< unsigned char >(
( place - pData_ ) / blockSize );
unsigned char index = firstAvailableBlock_;
assert( numBlocks > index );
if ( index == blockIndex )
return true;
/* If the bit at index was set in foundBlocks, then the stealth index was
found on the linked-list.
*/
std::bitset< UCHAR_MAX > foundBlocks;
unsigned char * nextBlock = NULL;
for ( unsigned char cc = 0; ; )
{
nextBlock = pData_ + ( index * blockSize );
foundBlocks.set( index, true );
++cc;
if ( cc >= blocksAvailable_ )
// Successfully counted off number of nodes in linked-list.
break;
index = *nextBlock;
if ( index == blockIndex )
return true;
assert( numBlocks > index );
assert( !foundBlocks.test( index ) );
}
return false;
}
// FixedAllocator::FixedAllocator ---------------------------------------------
FixedAllocator::FixedAllocator()
: blockSize_( 0 )
, numBlocks_( 0 )
, chunks_( 0 )
, allocChunk_( NULL )
, deallocChunk_( NULL )
, emptyChunk_( NULL )
{
}
// FixedAllocator::~FixedAllocator --------------------------------------------
FixedAllocator::~FixedAllocator()
{
#ifdef DO_EXTRA_LOKI_TESTS
TrimEmptyChunk();
assert( chunks_.empty() && "Memory leak detected!" );
#endif
for ( ChunkIter i( chunks_.begin() ); i != chunks_.end(); ++i )
i->Release();
}
// FixedAllocator::Initialize -------------------------------------------------
void FixedAllocator::Initialize( std::size_t blockSize, std::size_t pageSize )
{
assert( blockSize > 0 );
assert( pageSize >= blockSize );
blockSize_ = blockSize;
std::size_t numBlocks = pageSize / blockSize;
if ( numBlocks > MaxObjectsPerChunk_ ) numBlocks = MaxObjectsPerChunk_;
else if ( numBlocks < MinObjectsPerChunk_ ) numBlocks = MinObjectsPerChunk_;
numBlocks_ = static_cast<unsigned char>(numBlocks);
assert(numBlocks_ == numBlocks);
}
// FixedAllocator::CountEmptyChunks -------------------------------------------
std::size_t FixedAllocator::CountEmptyChunks( void ) const
{
#ifdef DO_EXTRA_LOKI_TESTS
// This code is only used for specialized tests of the allocator.
// It is #ifdef-ed so that its O(C) complexity does not overwhelm the
// functions which call it.
std::size_t count = 0;
for ( ChunkCIter it( chunks_.begin() ); it != chunks_.end(); ++it )
{
const Chunk & chunk = *it;
if ( chunk.HasAvailable( numBlocks_ ) )
++count;
}
return count;
#else
return ( NULL == emptyChunk_ ) ? 0 : 1;
#endif
}
// FixedAllocator::IsCorrupt --------------------------------------------------
bool FixedAllocator::IsCorrupt( void ) const
{
const bool isEmpty = chunks_.empty();
ChunkCIter start( chunks_.begin() );
ChunkCIter last( chunks_.end() );
const size_t emptyChunkCount = CountEmptyChunks();
if ( isEmpty )
{
if ( start != last )
{
assert( false );
return true;
}
if ( 0 < emptyChunkCount )
{
assert( false );
return true;
}
if ( NULL != deallocChunk_ )
{
assert( false );
return true;
}
if ( NULL != allocChunk_ )
{
assert( false );
return true;
}
if ( NULL != emptyChunk_ )
{
assert( false );
return true;
}
}
else
{
const Chunk * front = &chunks_.front();
const Chunk * back = &chunks_.back();
if ( start >= last )
{
assert( false );
return true;
}
if ( back < deallocChunk_ )
{
assert( false );
return true;
}
if ( back < allocChunk_ )
{
assert( false );
return true;
}
if ( front > deallocChunk_ )
{
assert( false );
return true;
}
if ( front > allocChunk_ )
{
assert( false );
return true;
}
switch ( emptyChunkCount )
{
case 0:
if ( emptyChunk_ != NULL )
{
assert( false );
return true;
}
break;
case 1:
if ( emptyChunk_ == NULL )
{
assert( false );
return true;
}
if ( back < emptyChunk_ )
{
assert( false );
return true;
}
if ( front > emptyChunk_ )
{
assert( false );
return true;
}
if ( !emptyChunk_->HasAvailable( numBlocks_ ) )
{
// This may imply somebody tried to delete a block twice.
assert( false );
return true;
}
break;
default:
assert( false );
return true;
}
for ( ChunkCIter it( start ); it != last; ++it )
{
const Chunk & chunk = *it;
if ( chunk.IsCorrupt( numBlocks_, blockSize_, true ) )
return true;
}
}
return false;
}
// FixedAllocator::HasBlock ---------------------------------------------------
const Chunk * FixedAllocator::HasBlock( void * p ) const
{
const std::size_t chunkLength = numBlocks_ * blockSize_;
for ( ChunkCIter it( chunks_.begin() ); it != chunks_.end(); ++it )
{
const Chunk & chunk = *it;
if ( chunk.HasBlock( p, chunkLength ) )
return &chunk;
}
return NULL;
}
// FixedAllocator::TrimEmptyChunk ---------------------------------------------
bool FixedAllocator::TrimEmptyChunk( void )
{
// prove either emptyChunk_ points nowhere, or points to a truly empty Chunk.
assert( ( NULL == emptyChunk_ ) || ( emptyChunk_->HasAvailable( numBlocks_ ) ) );
if ( NULL == emptyChunk_ ) return false;
// If emptyChunk_ points to valid Chunk, then chunk list is not empty.
assert( !chunks_.empty() );
// And there should be exactly 1 empty Chunk.
assert( 1 == CountEmptyChunks() );
Chunk * lastChunk = &chunks_.back();
if ( lastChunk != emptyChunk_ )
std::swap( *emptyChunk_, *lastChunk );
assert( lastChunk->HasAvailable( numBlocks_ ) );
lastChunk->Release();
chunks_.pop_back();
if ( chunks_.empty() )
{
allocChunk_ = NULL;
deallocChunk_ = NULL;
}
else
{
if ( deallocChunk_ == emptyChunk_ )
{
deallocChunk_ = &chunks_.front();
assert( deallocChunk_->blocksAvailable_ < numBlocks_ );
}
if ( allocChunk_ == emptyChunk_ )
{
allocChunk_ = &chunks_.back();
assert( allocChunk_->blocksAvailable_ < numBlocks_ );
}
}
emptyChunk_ = NULL;
assert( 0 == CountEmptyChunks() );
return true;
}
// FixedAllocator::TrimChunkList ----------------------------------------------
bool FixedAllocator::TrimChunkList( void )
{
if ( chunks_.empty() )
{
assert( NULL == allocChunk_ );
assert( NULL == deallocChunk_ );
}
if ( chunks_.size() == chunks_.capacity() )
return false;
// Use the "make-a-temp-and-swap" trick to remove excess capacity.
Chunks( chunks_ ).swap( chunks_ );
return true;
}
// FixedAllocator::MakeNewChunk -----------------------------------------------
bool FixedAllocator::MakeNewChunk( void )
{
bool allocated = false;
try
{
std::size_t size = chunks_.size();
// Calling chunks_.reserve *before* creating and initializing the new
// Chunk means that nothing is leaked by this function in case an
// exception is thrown from reserve.
if ( chunks_.capacity() == size )
{
if ( 0 == size ) size = 4;
chunks_.reserve( size * 2 );
}
Chunk newChunk;
allocated = newChunk.Init( blockSize_, numBlocks_ );
if ( allocated )
chunks_.push_back( newChunk );
}
catch ( ... )
{
allocated = false;
}
if ( !allocated ) return false;
allocChunk_ = &chunks_.back();
deallocChunk_ = &chunks_.front();
return true;
}
// FixedAllocator::Allocate ---------------------------------------------------
void * FixedAllocator::Allocate( void )
{
// prove either emptyChunk_ points nowhere, or points to a truly empty Chunk.
assert( ( NULL == emptyChunk_ ) || ( emptyChunk_->HasAvailable( numBlocks_ ) ) );
assert( CountEmptyChunks() < 2 );
if ( ( NULL == allocChunk_ ) || allocChunk_->IsFilled() )
{
if ( NULL != emptyChunk_ )
{
allocChunk_ = emptyChunk_;
emptyChunk_ = NULL;
}
else
{
for ( ChunkIter i( chunks_.begin() ); ; ++i )
{
if ( chunks_.end() == i )
{
if ( !MakeNewChunk() )
return NULL;
break;
}
if ( !i->IsFilled() )
{
allocChunk_ = &*i;
break;
}
}
}
}
else if ( allocChunk_ == emptyChunk_)
// detach emptyChunk_ from allocChunk_, because after
// calling allocChunk_->Allocate(blockSize_); the chunk
// is no longer empty.
emptyChunk_ = NULL;
assert( allocChunk_ != NULL );
assert( !allocChunk_->IsFilled() );
void * place = allocChunk_->Allocate( blockSize_ );
// prove either emptyChunk_ points nowhere, or points to a truly empty Chunk.
assert( ( NULL == emptyChunk_ ) || ( emptyChunk_->HasAvailable( numBlocks_ ) ) );
assert( CountEmptyChunks() < 2 );
#ifdef LOKI_CHECK_FOR_CORRUPTION
if ( allocChunk_->IsCorrupt( numBlocks_, blockSize_, true ) )
{
assert( false );
return NULL;
}
#endif
return place;
}
// FixedAllocator::Deallocate -------------------------------------------------
bool FixedAllocator::Deallocate( void * p, Chunk * hint )
{
assert(!chunks_.empty());
assert(&chunks_.front() <= deallocChunk_);
assert(&chunks_.back() >= deallocChunk_);
assert( &chunks_.front() <= allocChunk_ );
assert( &chunks_.back() >= allocChunk_ );
assert( CountEmptyChunks() < 2 );
Chunk * foundChunk = ( NULL == hint ) ? VicinityFind( p ) : hint;
if ( NULL == foundChunk )
return false;
assert( foundChunk->HasBlock( p, numBlocks_ * blockSize_ ) );
#ifdef LOKI_CHECK_FOR_CORRUPTION
if ( foundChunk->IsCorrupt( numBlocks_, blockSize_, true ) )
{
assert( false );
return false;
}
if ( foundChunk->IsBlockAvailable( p, numBlocks_, blockSize_ ) )
{
assert( false );
return false;
}
#endif
deallocChunk_ = foundChunk;
DoDeallocate(p);
assert( CountEmptyChunks() < 2 );
return true;
}
// FixedAllocator::VicinityFind -----------------------------------------------
Chunk * FixedAllocator::VicinityFind( void * p ) const
{
if ( chunks_.empty() ) return NULL;
assert(deallocChunk_);
const std::size_t chunkLength = numBlocks_ * blockSize_;
Chunk * lo = deallocChunk_;
Chunk * hi = deallocChunk_ + 1;
const Chunk * loBound = &chunks_.front();
const Chunk * hiBound = &chunks_.back() + 1;
// Special case: deallocChunk_ is the last in the array
if (hi == hiBound) hi = NULL;
for (;;)
{
if (lo)
{
if ( lo->HasBlock( p, chunkLength ) ) return lo;
if ( lo == loBound )
{
lo = NULL;
if ( NULL == hi ) break;
}
else --lo;
}
if (hi)
{
if ( hi->HasBlock( p, chunkLength ) ) return hi;
if ( ++hi == hiBound )
{
hi = NULL;
if ( NULL == lo ) break;
}
}
}
return NULL;
}
// FixedAllocator::DoDeallocate -----------------------------------------------
void FixedAllocator::DoDeallocate(void* p)
{
// Show that deallocChunk_ really owns the block at address p.
assert( deallocChunk_->HasBlock( p, numBlocks_ * blockSize_ ) );
// Either of the next two assertions may fail if somebody tries to
// delete the same block twice.
assert( emptyChunk_ != deallocChunk_ );
assert( !deallocChunk_->HasAvailable( numBlocks_ ) );
// prove either emptyChunk_ points nowhere, or points to a truly empty Chunk.
assert( ( NULL == emptyChunk_ ) || ( emptyChunk_->HasAvailable( numBlocks_ ) ) );
// call into the chunk, will adjust the inner list but won't release memory
deallocChunk_->Deallocate(p, blockSize_);
if ( deallocChunk_->HasAvailable( numBlocks_ ) )
{
assert( emptyChunk_ != deallocChunk_ );
// deallocChunk_ is empty, but a Chunk is only released if there are 2
// empty chunks. Since emptyChunk_ may only point to a previously
// cleared Chunk, if it points to something else besides deallocChunk_,
// then FixedAllocator currently has 2 empty Chunks.
if ( NULL != emptyChunk_ )
{
// If last Chunk is empty, just change what deallocChunk_
// points to, and release the last. Otherwise, swap an empty
// Chunk with the last, and then release it.
Chunk * lastChunk = &chunks_.back();
if ( lastChunk == deallocChunk_ )
deallocChunk_ = emptyChunk_;
else if ( lastChunk != emptyChunk_ )
std::swap( *emptyChunk_, *lastChunk );
assert( lastChunk->HasAvailable( numBlocks_ ) );
lastChunk->Release();
chunks_.pop_back();
if ( ( allocChunk_ == lastChunk ) || allocChunk_->IsFilled() )
allocChunk_ = deallocChunk_;
}
emptyChunk_ = deallocChunk_;
}
// prove either emptyChunk_ points nowhere, or points to a truly empty Chunk.
assert( ( NULL == emptyChunk_ ) || ( emptyChunk_->HasAvailable( numBlocks_ ) ) );
}
// GetOffset ------------------------------------------------------------------
/// @ingroup SmallObjectGroupInternal
/// Calculates index into array where a FixedAllocator of numBytes is located.
inline std::size_t GetOffset( std::size_t numBytes, std::size_t alignment )
{
const std::size_t alignExtra = alignment-1;
return ( numBytes + alignExtra ) / alignment;
}
// DefaultAllocator -----------------------------------------------------------
/** @ingroup SmallObjectGroupInternal
Calls the default allocator when SmallObjAllocator decides not to handle a
request. SmallObjAllocator calls this if the number of bytes is bigger than
the size which can be handled by any FixedAllocator.
@param numBytes number of bytes
@param doThrow True if this function should throw an exception, or false if it
should indicate failure by returning a NULL pointer.
*/
void * DefaultAllocator( std::size_t numBytes, bool doThrow )
{
#ifdef USE_NEW_TO_ALLOCATE
return doThrow ? ::operator new( numBytes ) :
::operator new( numBytes, std::nothrow_t() );
#else
void * p = ::std::malloc( numBytes );
if ( doThrow && ( NULL == p ) )
throw std::bad_alloc();
return p;
#endif
}
// DefaultDeallocator ---------------------------------------------------------
/** @ingroup SmallObjectGroupInternal
Calls default deallocator when SmallObjAllocator decides not to handle a
request. The default deallocator could be the global delete operator or the
free function. The free function is the preferred default deallocator since
it matches malloc which is the preferred default allocator. SmallObjAllocator
will call this if an address was not found among any of its own blocks.
*/
void DefaultDeallocator( void * p )
{
#ifdef USE_NEW_TO_ALLOCATE
::operator delete( p );
#else
::std::free( p );
#endif
}
// SmallObjAllocator::SmallObjAllocator ---------------------------------------
SmallObjAllocator::SmallObjAllocator( std::size_t pageSize,
std::size_t maxObjectSize, std::size_t objectAlignSize ) :
pool_( NULL ),
maxSmallObjectSize_( maxObjectSize ),
objectAlignSize_( objectAlignSize )
{
#ifdef DO_EXTRA_LOKI_TESTS
std::cout << "SmallObjAllocator " << this << std::endl;
#endif
assert( 0 != objectAlignSize );
const std::size_t allocCount = GetOffset( maxObjectSize, objectAlignSize );
pool_ = new FixedAllocator[ allocCount ];
for ( std::size_t i = 0; i < allocCount; ++i )
pool_[ i ].Initialize( ( i+1 ) * objectAlignSize, pageSize );
}
// SmallObjAllocator::~SmallObjAllocator --------------------------------------
SmallObjAllocator::~SmallObjAllocator( void )
{
#ifdef DO_EXTRA_LOKI_TESTS
std::cout << "~SmallObjAllocator " << this << std::endl;
#endif
delete [] pool_;
}
// SmallObjAllocator::TrimExcessMemory ----------------------------------------
bool SmallObjAllocator::TrimExcessMemory( void )
{
bool found = false;
const std::size_t allocCount = GetOffset( GetMaxObjectSize(), GetAlignment() );
std::size_t i = 0;
for ( ; i < allocCount; ++i )
{
if ( pool_[ i ].TrimEmptyChunk() )
found = true;
}
for ( i = 0; i < allocCount; ++i )
{
if ( pool_[ i ].TrimChunkList() )
found = true;
}
return found;
}
// SmallObjAllocator::Allocate ------------------------------------------------
void * SmallObjAllocator::Allocate( std::size_t numBytes, bool doThrow )
{
if ( numBytes > GetMaxObjectSize() )
return DefaultAllocator( numBytes, doThrow );
assert( NULL != pool_ );
if ( 0 == numBytes ) numBytes = 1;
const std::size_t index = GetOffset( numBytes, GetAlignment() ) - 1;
const std::size_t allocCount = GetOffset( GetMaxObjectSize(), GetAlignment() );
(void) allocCount;
assert( index < allocCount );
FixedAllocator & allocator = pool_[ index ];
assert( allocator.BlockSize() >= numBytes );
assert( allocator.BlockSize() < numBytes + GetAlignment() );
void * place = allocator.Allocate();
if ( ( NULL == place ) && TrimExcessMemory() )
place = allocator.Allocate();
if ( ( NULL == place ) && doThrow )
{
#ifdef _MSC_VER
throw std::bad_alloc( "could not allocate small object" );
#else
// GCC did not like a literal string passed to std::bad_alloc.
// so just throw the default-constructed exception.
throw std::bad_alloc();
#endif
}
return place;
}
// SmallObjAllocator::Deallocate ----------------------------------------------
void SmallObjAllocator::Deallocate( void * p, std::size_t numBytes )
{
if ( NULL == p ) return;
if ( numBytes > GetMaxObjectSize() )
{
DefaultDeallocator( p );
return;
}
assert( NULL != pool_ );
if ( 0 == numBytes ) numBytes = 1;
const std::size_t index = GetOffset( numBytes, GetAlignment() ) - 1;
const std::size_t allocCount = GetOffset( GetMaxObjectSize(), GetAlignment() );
(void) allocCount;
assert( index < allocCount );
FixedAllocator & allocator = pool_[ index ];
assert( allocator.BlockSize() >= numBytes );
assert( allocator.BlockSize() < numBytes + GetAlignment() );
const bool found = allocator.Deallocate( p, NULL );
(void) found;
assert( found );
}
// SmallObjAllocator::Deallocate ----------------------------------------------
void SmallObjAllocator::Deallocate( void * p )
{
if ( NULL == p ) return;
assert( NULL != pool_ );
FixedAllocator * pAllocator = NULL;
const std::size_t allocCount = GetOffset( GetMaxObjectSize(), GetAlignment() );
Chunk * chunk = NULL;
for ( std::size_t ii = 0; ii < allocCount; ++ii )
{
chunk = pool_[ ii ].HasBlock( p );
if ( NULL != chunk )
{
pAllocator = &pool_[ ii ];
break;
}
}
if ( NULL == pAllocator )
{
DefaultDeallocator( p );
return;
}
assert( NULL != chunk );
const bool found = pAllocator->Deallocate( p, chunk );
(void) found;
assert( found );
}
// SmallObjAllocator::IsCorrupt -----------------------------------------------
bool SmallObjAllocator::IsCorrupt( void ) const
{
if ( NULL == pool_ )
{
assert( false );
return true;
}
if ( 0 == GetAlignment() )
{
assert( false );
return true;
}
if ( 0 == GetMaxObjectSize() )
{
assert( false );
return true;
}
const std::size_t allocCount = GetOffset( GetMaxObjectSize(), GetAlignment() );
for ( std::size_t ii = 0; ii < allocCount; ++ii )
{
if ( pool_[ ii ].IsCorrupt() )
return true;
}
return false;
}
} // end namespace Loki
|