@@ -16,106 +16,7 @@ private:
1616 const (void )* stackBottom;
1717 const (void * )[][] roots;
1818
19- // // Cache for Slab Allocs
20- struct SmallAllocCache {
21- private :
22- alias Entry = void * ;
23- enum BinCount = ClassCount.Small;
24- enum CacheEntries = 4 ;
25- enum BinEntries = CacheEntries * 2 ;
26- enum LowWaterMark = 1 ;
27- enum HighWaterMark = CacheEntries;
28- enum Ceiling = BinEntries - 1 ;
29-
30- struct CacheBin {
31- private :
32- uint top = 0 ;
33- uint bottom = 0 ;
34- Entry[BinEntries] entries;
35-
36- void push (Entry ptr) {
37- assert (entryCount < CacheEntries, " No room to push() !" );
38- assert (top < Ceiling, " No room to push()!" );
39-
40- entries[++ top] = ptr;
41- }
42-
43- Entry pop () {
44- assert (entryCount > 0 , " No entry to pop() !" );
45-
46- auto entry = entries[bottom];
47- assert (entry ! is null , " Pop() got a null!" );
48-
49- entries[bottom++ ] = null ;
50- return entry;
51- }
52-
53- @property
54- uint entryCount () {
55- assert (top >= bottom, " Top is below bottom!" );
56-
57- return top - bottom;
58- }
59-
60- public :
61- @property
62- bool needRefill () {
63- return entryCount <= LowWaterMark;
64- }
65-
66- @property
67- bool needFlush () {
68- return entryCount >= HighWaterMark;
69- }
70-
71- void refill () {}
72-
73- void flush () {}
74-
75- Entry alloc () {
76- if (needRefill) {
77- refill();
78- assert (! needRefill, " Cache did not refill!" );
79- }
80-
81- return pop ();
82- }
83-
84- void free (Entry ptr) {
85- if (needFlush) {
86- flush();
87- assert (! needFlush, " Cache did not flush!" );
88- }
89-
90- push(ptr);
91- }
92- }
93-
94- CacheBin[2 ][BinCount] _bins;
95-
96- ref CacheBin getBin (size_t sizeClass, bool containsPointers) {
97- assert (isSmallSize(sizeClass), " Invalid size class!" );
98-
99- return _bins[containsPointers][sizeClass];
100- }
101-
102- public :
103- Entry alloc (size_t size, bool containsPointers) {
104- auto sizeClass = getSizeClass(size);
105- assert (isSmallSize(sizeClass), " Invalid size class!" );
106-
107- return getBin (sizeClass, containsPointers).alloc();
108- }
109-
110- void free (PageDescriptor pd, void * ptr) {
111- auto sizeClass = pd.sizeClass;
112- assert (isSmallSize(sizeClass), " Invalid size class!" );
113-
114- getBin(sizeClass, pd.containsPointers).free(ptr);
115- }
116- }
117-
118- SmallAllocCache _smallAllocCache;
19+ SmallAllocCache[2 ] smallAllocCache;
11920
12021public :
12122 void * alloc (size_t size, bool containsPointers) {
@@ -127,29 +28,34 @@ public:
12728
12829 auto arena = chooseArena(containsPointers);
12930 return isSmallSize (size)
130- ? arena.allocSmall( emap, size)
31+ ? smallAllocCache[containsPointers].alloc(arena, emap, size)
13132 : arena.allocLarge(emap, size, false );
13233 }
13334
13435 void * allocAppendable (size_t size, bool containsPointers,
13536 Finalizer finalizer = null ) {
136- auto asize = getAllocSize(alignUp(size, 2 * Quantum));
37+ if (! isAllocatableSize(size)) {
38+ return null ;
39+ }
40+
41+ auto reservedBytes = finalizer is null ? 0 : PointerSize;
42+ auto asize = getAllocSize(alignUp(size + reservedBytes, 2 * Quantum));
13743 assert (sizeClassSupportsMetadata(getSizeClass(asize)),
13844 " allocAppendable got size class without metadata support!" );
13945
14046 initializeExtentMap();
14147
14248 auto arena = chooseArena(containsPointers);
14349 if (isSmallSize(asize)) {
144- auto ptr = arena.allocSmall(emap, asize);
50+ auto ptr =
51+ smallAllocCache[containsPointers].alloc(arena, emap, asize);
14552 auto pd = getPageDescriptor(ptr);
14653 auto si = SlabAllocInfo(pd, ptr);
147- si.setUsedCapacity(size);
148- assert (finalizer is null , " finalizer not yet supported for slab!" );
54+ si.initializeMetadata(finalizer, size);
14955 return ptr;
15056 }
15157
152- auto ptr = arena.allocLarge(emap, asize , false );
58+ auto ptr = arena.allocLarge(emap, size , false );
15359 auto pd = getPageDescriptor(ptr);
15460 auto e = pd.extent;
15561 e.setUsedCapacity(size);
@@ -169,7 +75,7 @@ public:
16975 return arena.allocLarge(emap, size, true );
17076 }
17177
172- auto ret = arena.allocSmall( emap, size);
78+ auto ret = smallAllocCache[containsPointers].alloc(arena, emap, size);
17379 memset(ret, 0 , size);
17480 return ret;
17581 }
@@ -180,6 +86,15 @@ public:
18086 }
18187
18288 auto pd = getPageDescriptor(ptr);
89+
90+ if (pd.isSlab()) {
91+ auto si = SlabAllocInfo(pd, ptr);
92+ si.clearMetadata();
93+ smallAllocCache[pd.containsPointers]
94+ .intern(pd.arena, emap, pd, ptr);
95+ return ;
96+ }
97+
18398 pd.arena.free(emap, pd, ptr);
18499 }
185100
@@ -189,10 +104,25 @@ public:
189104 }
190105
191106 auto pd = getPageDescriptor(ptr);
192-
193107 auto e = pd.extent;
194- // Slab is not yet supported
195- if (e ! is null && ! pd.isSlab() && e.finalizer ! is null ) {
108+
109+ if (pd.isSlab()) {
110+ auto si = SlabAllocInfo(pd, ptr);
111+ auto finalizer = si.finalizer;
112+ if (finalizer ! is null ) {
113+ assert (cast (void * ) si.address == ptr,
114+ " destroy() was invoked on an interior pointer!" );
115+
116+ finalizer(ptr, si.usedCapacity);
117+ }
118+
119+ si.clearMetadata();
120+ smallAllocCache[pd.containsPointers]
121+ .intern(pd.arena, emap, pd, ptr);
122+ return ;
123+ }
124+
125+ if (e.finalizer ! is null ) {
196126 e.finalizer(ptr, e.usedCapacity);
197127 }
198128
@@ -222,8 +152,9 @@ public:
222152 auto oldSizeClass = pd.sizeClass;
223153 if (samePointerness && newSizeClass == oldSizeClass) {
224154 auto si = SlabAllocInfo(pd, ptr);
225- si.setUsedCapacity(size);
226- return ptr;
155+ if (! si.allowsMetadata || si.setUsedCapacity(size)) {
156+ return ptr;
157+ }
227158 }
228159
229160 if (newSizeClass > oldSizeClass) {
@@ -253,7 +184,7 @@ public:
253184 }
254185
255186 memcpy(newPtr, ptr, copySize);
256- pd.arena. free(emap, pd, ptr);
187+ free(ptr);
257188
258189 return newPtr;
259190 }
@@ -464,6 +395,112 @@ private:
464395 import d.gc.arena;
465396 return Arena.getOrInitialize((cpuid << 1 ) | containsPointers);
466397 }
398+
399+ /**
400+ * GC Allocation Cache.
401+ */
402+
403+ struct SmallAllocCache {
404+ private :
405+ enum CacheEntries = 4 ;
406+
407+ CacheBin[ClassCount.Small] _bins;
408+
409+ public :
410+ import d.gc.arena;
411+
412+ void * alloc (shared (Arena)* arena, ref shared (ExtentMap)* _emap,
413+ size_t size) {
414+ auto sc = getSizeClass(size);
415+ assert (isSmallSizeClass(sc), " Invalid size class!" );
416+
417+ if (_bins[sc].empty) {
418+ // TODO: bulk alloc!!!
419+ foreach (i; 0 .. CacheEntries) {
420+ auto entry = arena.allocSmall(_emap, size);
421+ _bins[sc].insert(entry);
422+ }
423+ }
424+
425+ return _bins[sc].dequeue();
426+ }
427+
428+ void intern (shared (Arena)* arena, ref shared (ExtentMap)* _emap,
429+ PageDescriptor pd, void * ptr) {
430+ auto sc = pd.sizeClass;
431+ assert (isSmallSizeClass(sc), " Size class is not small!" );
432+
433+ if (_bins[sc].full) {
434+ // TODO: sort by quality and evict the 'worst' !!!
435+ auto evictedEntry = _bins[sc].pop();
436+ arena.free(_emap, pd, evictedEntry);
437+ }
438+
439+ _bins[sc].push(ptr);
440+ }
441+
442+ private :
443+ struct CacheBin {
444+ private :
445+ enum BinEntries = CacheEntries + 1 ;
446+
447+ uint _head = 0 ;
448+ uint _tail = 0 ;
449+ uint _count = 0 ;
450+ void * [BinEntries] entries;
451+
452+ public :
453+ void insert (void * ptr) {
454+ assert (ptr ! is null , " Tried to insert() null!" );
455+ assert (_count < BinEntries, " Cannot insert()!" );
456+
457+ entries[_tail] = ptr;
458+ _tail = (_tail + 1 ) % BinEntries;
459+ _count++ ;
460+ }
461+
462+ void * dequeue () {
463+ assert (_count > 0 , " Nothing to dequeue()!" );
464+
465+ auto entry = entries[_head];
466+ _head = (_head + 1 ) % BinEntries;
467+ _count-- ;
468+
469+ assert (entry ! is null , " dequeue() got null!" );
470+ return entry;
471+ }
472+
473+ void push (void * ptr) {
474+ assert (ptr ! is null , " Tried to push() null!" );
475+ assert (_count < BinEntries, " Cannot push()!" );
476+
477+ entries[_head] = ptr;
478+ _head = (_head - 1 ) % BinEntries;
479+ _count++ ;
480+ }
481+
482+ void * pop () {
483+ assert (_count > 0 , " Nothing to pop()!" );
484+
485+ auto entry = entries[_tail];
486+ _tail = (_tail - 1 ) % BinEntries;
487+ _count-- ;
488+
489+ assert (entry ! is null , " pop() got null!" );
490+ return entry;
491+ }
492+
493+ @property
494+ bool empty () {
495+ return _count == 0 ;
496+ }
497+
498+ @property
499+ bool full () {
500+ return _count == CacheEntries;
501+ }
502+ }
503+ }
467504}
468505
469506private :
@@ -527,6 +564,16 @@ unittest makeRange {
527564 checkRange(ptr[1 .. 8 ], 8 , 8 );
528565}
529566
567+ unittest nonAllocatableSizes {
568+ // Prohibited sizes of allocations
569+ assert (threadCache.alloc(0 , false ) == null );
570+ assert (threadCache.alloc(MaxAllocationSize + 1 , false ) == null );
571+ assert (threadCache.calloc(0 , false ) == null );
572+ assert (threadCache.calloc(MaxAllocationSize + 1 , false ) == null );
573+ assert (threadCache.allocAppendable(0 , false ) == null );
574+ assert (threadCache.allocAppendable(MaxAllocationSize + 1 , false ) == null );
575+ }
576+
530577unittest getCapacity {
531578 // Non-appendable size class 6 (56 bytes)
532579 auto nonAppendable = threadCache.alloc(50 , false );
@@ -921,4 +968,33 @@ unittest finalization {
921968 auto oldDestroyCount = destroyCount;
922969 threadCache.destroy (s1);
923970 assert (destroyCount == oldDestroyCount);
971+
972+ // Finalizers for small allocs:
973+ auto s2 = threadCache.allocAppendable(45 , false , &destruct);
974+ assert (threadCache.getCapacity(s2[0 .. 45 ]) == 56 );
975+ assert (! threadCache.extend(s2[0 .. 45 ], 12 ));
976+ assert (threadCache.extend(s2[0 .. 45 ], 11 ));
977+ assert (threadCache.getCapacity(s2[0 .. 56 ]) == 56 );
978+ threadCache.destroy (s2);
979+ assert (lastKilledAddress == s2);
980+ assert (lastKilledUsedCapacity == 56 );
981+
982+ // Behaviour of realloc() on small allocs with finalizers:
983+ auto s3 = threadCache.allocAppendable(70 , false , &destruct);
984+ assert (threadCache.getCapacity(s3[0 .. 70 ]) == 72 );
985+ auto s4 = threadCache.realloc(s3, 70 , false );
986+ assert (s3 == s4);
987+
988+ // This is in the same size class, but will not work in-place
989+ // given as finalizer occupies final 8 of the 80 bytes in the slot:
990+ auto s5 = threadCache.realloc(s4, 75 , false );
991+ assert (s5 != s4);
992+
993+ // So we end up with a new alloc, without metadata:
994+ assert (threadCache.getCapacity(s5[0 .. 80 ]) == 80 );
995+
996+ // And the finalizer has been discarded:
997+ oldDestroyCount = destroyCount;
998+ threadCache.destroy (s5);
999+ assert (destroyCount == oldDestroyCount);
9241000}
0 commit comments