Skip to content

Commit bb27aa7

Browse files
authored
Merge pull request #5029 from somzzz/issue_16564
fix issue 16564 - KRRegion.empty sometimes returns Ternary.no
2 parents fc37e0f + 8a69b10 commit bb27aa7

File tree

1 file changed

+110
-3
lines changed

1 file changed

+110
-3
lines changed

std/experimental/allocator/building_blocks/kernighan_ritchie.d

Lines changed: 110 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,13 +117,21 @@ struct KRRegion(ParentAllocator = NullAllocator)
117117
{
118118
assert(right);
119119
auto p = payload;
120-
return p.ptr + p.length == right;
120+
return p.ptr < right && right < p.ptr + p.length + Node.sizeof;
121121
}
122122

123-
bool coalesce()
123+
bool coalesce(void* memoryEnd = null)
124124
{
125+
// Coalesce the last node before the memory end with any possible gap
126+
if (memoryEnd
127+
&& memoryEnd < payload.ptr + payload.length + Node.sizeof)
128+
{
129+
size += memoryEnd - (payload.ptr + payload.length);
130+
return true;
131+
}
132+
125133
if (!adjacent(next)) return false;
126-
size += next.size;
134+
size = (cast(ubyte*) next + next.size) - cast(ubyte*) &this;
127135
next = next.next;
128136
return true;
129137
}
@@ -137,6 +145,7 @@ struct KRRegion(ParentAllocator = NullAllocator)
137145
if (size < bytes) return typeof(return)();
138146
assert(size >= bytes);
139147
immutable leftover = size - bytes;
148+
140149
if (leftover >= Node.sizeof)
141150
{
142151
// There's room for another node
@@ -146,6 +155,7 @@ struct KRRegion(ParentAllocator = NullAllocator)
146155
assert(next);
147156
return tuple(payload, newNode);
148157
}
158+
149159
// No slack space, just return next node
150160
return tuple(payload, next == &this ? null : next);
151161
}
@@ -400,6 +410,7 @@ struct KRRegion(ParentAllocator = NullAllocator)
400410
}
401411
return result[0 .. n];
402412
}
413+
403414
// Not enough memory, switch to freelist mode and fall through
404415
switchToFreeList;
405416
}
@@ -417,6 +428,7 @@ struct KRRegion(ParentAllocator = NullAllocator)
417428
pnode.next = k[1];
418429
return k[0][0 .. n];
419430
}
431+
420432
pnode = pnode.next;
421433
if (pnode == root) break;
422434
}
@@ -443,6 +455,7 @@ struct KRRegion(ParentAllocator = NullAllocator)
443455
// coalesce at this time. Instead, do it lazily during allocation.
444456
auto n = cast(Node*) b.ptr;
445457
n.size = goodAllocSize(b.length);
458+
auto memoryEnd = payload.ptr + payload.length;
446459

447460
if (regionMode)
448461
{
@@ -458,6 +471,10 @@ struct KRRegion(ParentAllocator = NullAllocator)
458471
// What a sight for sore eyes
459472
root = n;
460473
root.next = root;
474+
475+
// If the first block freed is the last one allocated,
476+
// maybe there's a gap after it.
477+
root.coalesce(memoryEnd);
461478
return true;
462479
}
463480

@@ -486,8 +503,10 @@ struct KRRegion(ParentAllocator = NullAllocator)
486503
else if (pnode < n)
487504
{
488505
// Insert at the end of the list
506+
// Add any possible gap at the end of n to the length of n
489507
n.next = pnode.next;
490508
pnode.next = n;
509+
n.coalesce(memoryEnd);
491510
pnode.coalesce;
492511
root = pnode;
493512
return true;
@@ -771,3 +790,91 @@ unittest
771790
p = alloc.allocateAll();
772791
assert(p.length == 1024 * 1024);
773792
}
793+
794+
unittest
795+
{
796+
import std.experimental.allocator.building_blocks;
797+
import std.random;
798+
import std.typecons : Ternary;
799+
800+
// Both sequences must work on either system
801+
802+
// A sequence of allocs which generates the error described in issue 16564
803+
// that is a gap at the end of buf from the perspective of the allocator
804+
805+
// for 64 bit systems (leftover balance = 8 bytes < 16)
806+
int[] sizes64 = [18904, 2008, 74904, 224, 111904, 1904, 52288, 8];
807+
808+
// for 32 bit systems (leftover balance < 8)
809+
int[] sizes32 = [81412, 107068, 49892, 23768];
810+
811+
812+
void test(int[] sizes)
813+
{
814+
ubyte[256 * 1024] buf;
815+
auto a = KRRegion!()(buf);
816+
817+
void[][] bufs;
818+
819+
foreach (size; sizes)
820+
{
821+
bufs ~= a.allocate(size);
822+
}
823+
824+
foreach (b; bufs.randomCover)
825+
{
826+
a.deallocate(b);
827+
}
828+
829+
assert(a.empty == Ternary.yes);
830+
}
831+
832+
test(sizes64);
833+
test(sizes32);
834+
}
835+
836+
unittest
837+
{
838+
import std.experimental.allocator.building_blocks;
839+
import std.random;
840+
import std.typecons : Ternary;
841+
842+
// For 64 bits, we allocate in multiples of 8, but the minimum alloc size is 16.
843+
// This can create gaps.
844+
// This test is an example of such a case. The gap is formed between the block
845+
// allocated for the second value in sizes and the third. There is also a gap
846+
// at the very end. (total lost 2 * word)
847+
848+
int[] sizes64 = [2008, 18904, 74904, 224, 111904, 1904, 52288, 8];
849+
int[] sizes32 = [81412, 107068, 49892, 23768];
850+
851+
int word64 = 8;
852+
int word32 = 4;
853+
854+
void test(int[] sizes, int word)
855+
{
856+
ubyte[256 * 1024] buf;
857+
auto a = KRRegion!()(buf);
858+
859+
void[][] bufs;
860+
861+
foreach (size; sizes)
862+
{
863+
bufs ~= a.allocate(size);
864+
}
865+
866+
a.deallocate(bufs[1]);
867+
bufs ~= a.allocate(sizes[1] - word);
868+
869+
a.deallocate(bufs[0]);
870+
foreach (i; 2..bufs.length)
871+
{
872+
a.deallocate(bufs[i]);
873+
}
874+
875+
assert(a.empty == Ternary.yes);
876+
}
877+
878+
test(sizes64, word64);
879+
test(sizes32, word32);
880+
}

0 commit comments

Comments
 (0)