@@ -13,9 +13,11 @@ import sdc.intrinsics;
1313
1414alias ClassTree = RBTree! (Region, classAddrRegionCmp, " rbClass" );
1515alias RangeTree = RBTree! (Region, addrRangeRegionCmp, " rbRange" );
16+ alias DirtTree = RBTree! (Region, dirtAddrRegionCmp, " rbDirt" );
1617
1718alias ClassNode = rbtree.Node! (Region, " rbClass" );
1819alias RangeNode = rbtree.Node! (Region, " rbRange" );
20+ alias DirtNode = rbtree.Node! (Region, " rbDirt" );
1921alias PHNode = heap.Node! Region;
2022
2123// Reserve memory in blocks of 1GB.
@@ -59,6 +61,9 @@ private:
5961 ClassTree regionsByClass;
6062 RangeTree regionsByRange;
6163
64+ // Dirty regions only, by dirt (in descending order)
65+ DirtTree regionsByDirt;
66+
6267 // Unused region objects.
6368 Heap! (Region, unusedRegionCmp) unusedRegions;
6469
@@ -76,10 +81,6 @@ public:
7681 void release (void * ptr, uint blocks) shared {
7782 assert (blocks > 0 , " Invalid number of blocks!" );
7883
79- // Eagerly clean the block we are returned.
80- import d.gc.memmap;
81- pages_purge(ptr, blocks * BlockSize);
82-
8384 mutex.lock();
8485 scope (exit) mutex.unlock();
8586
@@ -93,6 +94,15 @@ public:
9394 return (cast (RegionAllocator* ) &this ).computeAddressRangeImpl();
9495 }
9596
97+ uint purgeDirtyBlocks (uint blocks) shared {
98+ assert (blocks > 0 , " Invalid number of blocks!" );
99+
100+ mutex.lock();
101+ scope (exit) mutex.unlock();
102+
103+ return (cast (RegionAllocator* ) &this ).purgeDirtyBlocksImpl(blocks);
104+ }
105+
96106private :
97107 auto computeAddressRangeImpl () {
98108 assert (mutex.isHeld(), " Mutex not held!" );
@@ -116,6 +126,7 @@ private:
116126 }
117127 } else {
118128 regionsByRange.remove(r);
129+ unregisterRegionDirt(r);
119130 }
120131
121132 assert (r.address ! is null && isAligned(r.address, BlockSize),
@@ -158,6 +169,46 @@ private:
158169 registerRegion(r);
159170 }
160171
172+ void registerRegionDirt (Region* r) {
173+ assert (r ! is null , " Region is null!" );
174+
175+ if (r.dirtyBlockCount > 0 ) {
176+ regionsByDirt.insert(r);
177+ }
178+ }
179+
180+ void unregisterRegionDirt (Region* r) {
181+ assert (r ! is null , " Region is null!" );
182+
183+ if (r.dirtyBlockCount > 0 ) {
184+ regionsByDirt.remove(r);
185+ }
186+ }
187+
188+ uint purgeDirtyBlocksImpl (uint blocks) {
189+ assert (mutex.isHeld(), " Mutex not held!" );
190+
191+ blocks = min(blocks, dirtyBlockCount);
192+ auto blocksToPurge = blocks;
193+
194+ Region rr;
195+ rr.dirtyBlockCount = uint .max;
196+
197+ while (blocksToPurge > 0 ) {
198+ auto r = regionsByDirt.extractBestFit(&rr);
199+ assert (r ! is null ,
200+ " Nonzero dirty block count, but there is no dirty region!" );
201+
202+ auto regionBlocksToPurge = min(blocksToPurge, r.dirtyBlockCount);
203+ r.purgeDirtyBlocks(regionBlocksToPurge);
204+ registerRegionDirt(r);
205+ blocksToPurge -= regionBlocksToPurge;
206+ }
207+
208+ dirtyBlockCount -= blocks;
209+ return blocks;
210+ }
211+
161212 void registerRegion (Region* r) {
162213 assert (r ! is null , " Region is null!" );
163214
@@ -169,6 +220,7 @@ private:
169220 }
170221
171222 regionsByClass.remove(adjacent);
223+ unregisterRegionDirt(adjacent);
172224
173225 // Make sure we keep using the best region.
174226 bool needSwap = unusedRegionCmp(r, adjacent) < 0 ;
@@ -181,6 +233,7 @@ private:
181233
182234 regionsByClass.insert(r);
183235 regionsByRange.insert(r);
236+ registerRegionDirt(r);
184237 }
185238
186239 Region* refillAddressSpace (uint extraBlocks) {
@@ -369,6 +422,7 @@ struct Region {
369422 struct UsedLinks {
370423 ClassNode rbClass;
371424 RangeNode rbRange;
425+ DirtNode rbDirt;
372426 }
373427
374428 union Links {
@@ -466,6 +520,11 @@ public:
466520 return _links.usedLinks.rbRange;
467521 }
468522
523+ @property
524+ ref DirtNode rbDirt () {
525+ return _links.usedLinks.rbDirt;
526+ }
527+
469528 @property
470529 size_t size () const {
471530 return blockCount * BlockSize;
@@ -493,17 +552,6 @@ public:
493552 assert (address is (r.address + r.size) || r.address is (address + size),
494553 " Regions are not adjacent!" );
495554
496- auto left = address < r.address ? &this : r;
497- auto right = address < r.address ? r : &this ;
498-
499- // Dirt is at all times contiguous within a region, and starts at the bottom.
500- // Given as purging is not yet supported, this invariant always holds.
501- assert (
502- left.dirtyBlockCount == left.blockCount
503- || right.dirtyBlockCount == 0 ,
504- " Merge would place dirty blocks in front of clean blocks!"
505- );
506-
507555 // Copy the dirty bits.
508556 // FIXME: We use min to ensures we don't trip an assert
509557 // when the region is larger than 1GB.
@@ -514,6 +562,54 @@ public:
514562 return at (a, blockCount + r.blockCount,
515563 dirtyBlockCount + r.dirtyBlockCount);
516564 }
565+
566+ Region* purgeDirtyBlocks (uint blocksToPurge) {
567+ assert (blocksToPurge <= dirtyBlockCount,
568+ " Region has fewer dirty blocks than requested to purge!" );
569+ assert (blocksToPurge > 0 , " Requested to purge zero blocks!" );
570+
571+ auto startIndex = startOffset;
572+ auto endIndex = (startIndex + blockCount) % RefillBlockCount;
573+ uint index = endIndex;
574+
575+ while (blocksToPurge > 0 ) {
576+ bool roll = index <= startIndex;
577+
578+ // Find the last dirty run
579+ auto endDirtRun = dirtyBlocks.findSetBackward(index);
580+ if (endDirtRun < 0 ) {
581+ if (roll) {
582+ index = RefillBlockCount;
583+ continue ;
584+ }
585+
586+ break ;
587+ }
588+
589+ index = dirtyBlocks.findClearBackward(endDirtRun);
590+ auto startDirtRun = index + 1 ;
591+ auto runBlocksToPurge = min(blocksToPurge, endDirtRun - index);
592+ startDirtRun = endDirtRun - runBlocksToPurge + 1 ;
593+
594+ auto effectiveIndex =
595+ startDirtRun + (roll ? RefillBlockCount : 0 ) - startIndex;
596+ assert (effectiveIndex < blockCount, " Purge offset out of range!" );
597+
598+ // Purge.
599+ import d.gc.memmap;
600+ pages_purge(address + effectiveIndex * BlockSize,
601+ runBlocksToPurge * BlockSize);
602+
603+ dirtyBlocks.clearRange(startDirtRun, runBlocksToPurge);
604+ blocksToPurge -= runBlocksToPurge;
605+ dirtyBlockCount -= runBlocksToPurge;
606+ }
607+
608+ assert (blocksToPurge == 0 ,
609+ " Failed to purge all requested blocks in region!" );
610+
611+ return &this ;
612+ }
517613}
518614
519615ptrdiff_t addrRangeRegionCmp (Region* lhs, Region* rhs) {
@@ -552,6 +648,23 @@ ptrdiff_t classAddrRegionCmp(Region* lhs, Region* rhs) {
552648 return (l > r) - (l < r);
553649}
554650
651+ ptrdiff_t dirtAddrRegionCmp (Region* lhs, Region* rhs) {
652+ // Descending order of dirt
653+ auto rdirt = lhs.dirtyBlockCount;
654+ auto ldirt = rhs.dirtyBlockCount;
655+ auto dirtCmp = (ldirt > rdirt) - (ldirt < rdirt);
656+
657+ if (dirtCmp != 0 ) {
658+ return dirtCmp;
659+ }
660+
661+ // Descending order of address
662+ auto r = cast (size_t ) lhs.address;
663+ auto l = cast (size_t ) rhs.address;
664+
665+ return (l > r) - (l < r);
666+ }
667+
555668ptrdiff_t unusedRegionCmp (Region* lhs, Region* rhs) {
556669 static assert (LgAddressSpace <= 56 , " Address space too large!" );
557670
@@ -609,37 +722,70 @@ unittest trackDirtyBlocks {
609722 assert (r.countDirtyBlocksInSubRegion(0 , blocks) == dirtyBlocks);
610723 }
611724
725+ // Verify that the currently-dirtiest region has the given attributes.
726+ void verifyDirtiestRegion (void * address, uint blocks, uint dirtyBlocks) {
727+ Region rr;
728+ rr.dirtyBlockCount = uint .max;
729+
730+ auto r = ra.regionsByDirt.extractBestFit(&rr);
731+ assert (r ! is null );
732+ assert (r.address is address);
733+ assert (r.blockCount == blocks);
734+ assert (r.dirtyBlockCount == dirtyBlocks);
735+ assert (r.countDirtyBlocksInSubRegion(0 , blocks) == dirtyBlocks);
736+ ra.registerRegionDirt(r);
737+ }
738+
612739 // Initially, there are no dirty blocks.
613740 assert (regionAllocator.dirtyBlockCount == 0 );
614741
615742 // Make some dirty regions.
616- freeRun(addresses[0 .. 2 ]);
617- assert (regionAllocator.dirtyBlockCount == 2 );
618- verifyUniqueRegion(addresses[0 ], 2 , 2 , 2 );
619743 freeRun(addresses[4 .. 8 ]);
620- assert (regionAllocator.dirtyBlockCount == 6 );
744+ assert (regionAllocator.dirtyBlockCount == 4 );
621745 verifyUniqueRegion(addresses[4 ], 4 , 4 , 4 );
746+ verifyDirtiestRegion(addresses[4 ], 4 , 4 );
747+ assert (regionAllocator.purgeDirtyBlocks(2 ) == 2 );
748+ verifyUniqueRegion(addresses[4 ], 4 , 4 , 2 );
749+ verifyDirtiestRegion(addresses[4 ], 4 , 2 );
622750 freeRun(addresses[10 .. 15 ]);
623- assert (regionAllocator.dirtyBlockCount == 11 );
751+ assert (regionAllocator.dirtyBlockCount == 7 );
624752 verifyUniqueRegion(addresses[10 ], 5 , 5 , 5 );
753+ verifyDirtiestRegion(addresses[10 ], 5 , 5 );
754+ assert (regionAllocator.purgeDirtyBlocks(4 ) == 4 );
755+ verifyUniqueRegion(addresses[10 ], 5 , 5 , 1 );
756+ verifyDirtiestRegion(addresses[4 ], 4 , 2 );
757+ freeRun(addresses[0 .. 2 ]);
758+ assert (regionAllocator.dirtyBlockCount == 5 );
759+ verifyUniqueRegion(addresses[0 ], 2 , 2 , 2 );
760+ verifyDirtiestRegion(addresses[4 ], 4 , 2 );
625761
626762 // Merge regions and confirm expected effect.
627763 freeRun(addresses[8 .. 10 ]);
628- assert (regionAllocator.dirtyBlockCount == 13 );
629- verifyUniqueRegion(addresses[4 ], 10 , 11 , 11 );
764+ assert (regionAllocator.dirtyBlockCount == 7 );
765+ verifyUniqueRegion(addresses[4 ], 10 , 11 , 5 );
766+ verifyDirtiestRegion(addresses[4 ], 11 , 5 );
630767 freeRun(addresses[2 .. 4 ]);
631- assert (regionAllocator.dirtyBlockCount == 15 );
632- verifyUniqueRegion(addresses[0 ], 14 , 15 , 15 );
768+ assert (regionAllocator.dirtyBlockCount == 9 );
769+ verifyUniqueRegion(addresses[0 ], 14 , 15 , 9 );
770+ verifyDirtiestRegion(addresses[0 ], 15 , 9 );
771+ assert (regionAllocator.purgeDirtyBlocks(3 ) == 3 );
772+ assert (regionAllocator.dirtyBlockCount == 6 );
633773 freeRun(addresses[15 .. 16 ]);
634- verifyUniqueRegion(addresses[0 ], 1 , RefillBlockCount, 16 );
774+ assert (regionAllocator.dirtyBlockCount == 7 );
775+ verifyUniqueRegion(addresses[0 ], 1 , RefillBlockCount, 7 );
776+ verifyDirtiestRegion(addresses[0 ], RefillBlockCount, 7 );
635777
636778 // Test dirt behaviour in acquire and release.
637779 void * addr0;
638780 assert (regionAllocator.acquire(&addr0, 5 ));
639781 assert (addr0 is addresses[5 ]);
640- assert (regionAllocator.dirtyBlockCount == 10 );
641- verifyUniqueRegion(addresses[6 ], 1 , RefillBlockCount - 6 , 10 );
782+ assert (regionAllocator.dirtyBlockCount == 1 );
783+ verifyUniqueRegion(addresses[6 ], 1 , RefillBlockCount - 6 , 1 );
784+ verifyDirtiestRegion(addresses[6 ], RefillBlockCount - 6 , 1 );
642785 regionAllocator.release(addresses[0 ], 6 );
643- assert (regionAllocator.dirtyBlockCount == 16 );
644- verifyUniqueRegion(addresses[0 ], 1 , RefillBlockCount, 16 );
786+ assert (regionAllocator.dirtyBlockCount == 7 );
787+ verifyUniqueRegion(addresses[0 ], 1 , RefillBlockCount, 7 );
788+ verifyDirtiestRegion(addresses[0 ], RefillBlockCount, 7 );
789+ assert (regionAllocator.purgeDirtyBlocks(666 ) == 7 );
790+ assert (regionAllocator.dirtyBlockCount == 0 );
645791}
0 commit comments