Browse Source

Use red-black trees for variable freelists.

Rika Ichinose 3 months ago
parent
commit
a8baa49c86
1 changed files with 385 additions and 182 deletions
  1. 385 182
      rtl/inc/heap.inc

+ 385 - 182
rtl/inc/heap.inc

@@ -52,14 +52,6 @@ const
   );public name 'FPC_SYSTEM_MEMORYMANAGER';
   );public name 'FPC_SYSTEM_MEMORYMANAGER';
 {$endif FPC_IN_HEAPMGR}
 {$endif FPC_IN_HEAPMGR}
 
 
-
-{ Try to find the best matching block in general freelist }
-{ define BESTMATCH}
-
-{$ifndef FPC_NO_DEFAULT_MEMORYMANAGER}
-
-{$endif HAS_MEMORYMANAGER}
-
 {*****************************************************************************
 {*****************************************************************************
                              Memory Manager
                              Memory Manager
 *****************************************************************************}
 *****************************************************************************}
@@ -177,8 +169,53 @@ end;
 {$endif FPC_HAS_FEATURE_HEAP}
 {$endif FPC_HAS_FEATURE_HEAP}
 {$endif FPC_IN_HEAPMGR}
 {$endif FPC_IN_HEAPMGR}
 
 
-{$if defined(FPC_HAS_FEATURE_HEAP) or defined(FPC_IN_HEAPMGR)}
-{$ifndef HAS_MEMORYMANAGER}
+{$if (defined(FPC_HAS_FEATURE_HEAP) or defined(FPC_IN_HEAPMGR)) and not defined(HAS_MEMORYMANAGER)}
+
+type
+
+{ Intrusive red-black tree, loosely translated from
+  https://github.com/torvalds/linux/blob/master/tools/include/linux/rbtree.h
+  https://github.com/torvalds/linux/blob/master/tools/include/linux/rbtree_augmented.h
+  https://github.com/torvalds/linux/blob/master/lib/rbtree.c }
+
+  RbTree = object
+  type
+    pNode = ^Node; ppNode = ^pNode;
+    Node = record
+      parentColor: PtrUint;
+    case uint32 of
+      0: (childs: array[0 .. 1] of pNode);
+      1: (left, right: pNode);
+    end;
+
+    { For possible future templating. Right now, hardcoded for FreeVarChunk comparisons.
+      Note that very few functions (Add and LowerBound) depend on Control, so turning the entire RbTree into a RbTree<Control> is unwise. }
+    Control = object
+      class function Less(a, b: pNode): boolean; static; inline;
+      class function KeyCompare(key: pointer; b: pNode): PtrInt; static; inline;
+    end;
+
+    procedure Add(n: pNode);
+    procedure Remove(n: pNode);
+    function LowerBound(key: pointer): pNode; { Returns smallest node ≥ key. }
+  {$ifdef DEBUG_HEAP_INC}
+    function First: pNode;
+    class function Next(n: pNode): pNode; static;
+  {$endif DEBUG_HEAP_INC}
+
+  private const
+    BlackTag = 1;
+    ParentMask = PtrUint(not BlackTag);
+
+  var
+    root: pNode;
+
+    procedure ChangeChild(old, new, parent: pNode); inline;
+    procedure RotateSetParents(old, new: pNode; color: PtrUint); inline;
+    procedure InsertColor(n: pNode);
+    function EraseAugmented(n: pNode): pNode;
+    procedure EraseColor(parent: pNode);
+  end;
 
 
 {
 {
   We use 'fixed' size chunks for small allocations,
   We use 'fixed' size chunks for small allocations,
@@ -198,7 +235,6 @@ end;
   but otherwise it will be freed to the OS.
   but otherwise it will be freed to the OS.
 }
 }
 
 
-type
   HeapInc = object
   HeapInc = object
   const
   const
     { Alignment requirement for blocks. All fixed sizes (among other things) are assumed to be divisible. }
     { Alignment requirement for blocks. All fixed sizes (among other things) are assumed to be divisible. }
@@ -224,9 +260,6 @@ type
     OSChunkVarSizeQuant = 64 * 1024;
     OSChunkVarSizeQuant = 64 * 1024;
     MaxFixedChunkSize = 256 * 1024;
     MaxFixedChunkSize = 256 * 1024;
 
 
-    { Variable freelist search strategy: -1 = exhaustive search for the best (smallest fitting) match, ≥0 = search this many after the first match. }
-    MatchEffort = {$ifdef BESTMATCH} -1 {$else} 10 {$endif};
-
     { Limit on shrinking variable chunks and keeping the tail when splitting the chunk in AllocVar / TryResizeVar. }
     { Limit on shrinking variable chunks and keeping the tail when splitting the chunk in AllocVar / TryResizeVar. }
     MinVarHeaderAndPayload = MaxFixedHeaderAndPayload * 3 div 4;
     MinVarHeaderAndPayload = MaxFixedHeaderAndPayload * 3 div 4;
 
 
@@ -358,8 +391,8 @@ type
     { Reuses the payload of variable chunks whose ch.h and UsedFlag = 0, so variable chunk payload must always fit its size. }
     { Reuses the payload of variable chunks whose ch.h and UsedFlag = 0, so variable chunk payload must always fit its size. }
     pFreeVarChunk = ^FreeVarChunk;
     pFreeVarChunk = ^FreeVarChunk;
     FreeVarChunk = record
     FreeVarChunk = record
-      prev, next: pFreeVarChunk;
       size: SizeUint; { Cached size for easier access when working with free chunks, always equals to header.ch.h and VarSizeMask. }
       size: SizeUint; { Cached size for easier access when working with free chunks, always equals to header.ch.h and VarSizeMask. }
+      rbn: RbTree.Node;
     end;
     end;
 
 
     pHugeChunk = ^HugeChunk;
     pHugeChunk = ^HugeChunk;
@@ -376,7 +409,7 @@ type
       used, maxUsed, allocated, maxAllocated: SizeUint; { “maxUsed” and “maxAllocated” include gs.hugeUsed; “used” and “allocated” don’t. }
       used, maxUsed, allocated, maxAllocated: SizeUint; { “maxUsed” and “maxAllocated” include gs.hugeUsed; “used” and “allocated” don’t. }
 
 
       varOS: pVarOSChunk;
       varOS: pVarOSChunk;
-      varFree: pFreeVarChunk;
+      varFree: RbTree;
 
 
       { OS chunks with at least 1 free chunk (including unformatted space), but not completely empty.
       { OS chunks with at least 1 free chunk (including unformatted space), but not completely empty.
         OS chunks that become completely empty are moved to freeOS, completely full are moved to fullOS. }
         OS chunks that become completely empty are moved to freeOS, completely full are moved to fullOS. }
@@ -456,6 +489,292 @@ type
     VarHeaderSize = sizeof(VarHeader);
     VarHeaderSize = sizeof(VarHeader);
     VarOSChunkDataOffset = (sizeof(VarOSChunk) + VarHeaderSize + Alignment - 1) and -Alignment - VarHeaderSize;
     VarOSChunkDataOffset = (sizeof(VarOSChunk) + VarHeaderSize + Alignment - 1) and -Alignment - VarHeaderSize;
     HugeChunkDataOffset = (sizeof(HugeChunk) + CommonHeaderSize + Alignment - 1) and -Alignment - CommonHeaderSize;
     HugeChunkDataOffset = (sizeof(HugeChunk) + CommonHeaderSize + Alignment - 1) and -Alignment - CommonHeaderSize;
+    FreeVarChunkRbnOffset = PtrUint(@FreeVarChunk(nil^).rbn);
+  end;
+
+  class function RbTree.Control.Less(a, b: pNode): boolean;
+  begin
+    result := HeapInc.pFreeVarChunk(pointer(a) - HeapInc.FreeVarChunkRbnOffset)^.size < HeapInc.pFreeVarChunk(pointer(b) - HeapInc.FreeVarChunkRbnOffset)^.size;
+  end;
+
+  class function RbTree.Control.KeyCompare(key: pointer; b: pNode): PtrInt;
+  begin
+    result := PtrInt(key) - PtrInt(HeapInc.pFreeVarChunk(pointer(b) - HeapInc.FreeVarChunkRbnOffset)^.size);
+  end;
+
+  procedure RbTree.Add(n: pNode);
+  var
+    link: ppNode;
+    parent: pNode;
+  begin
+    link := @root;
+    parent := nil;
+    if Assigned(link^) then
+      repeat
+        parent := link^;
+        if Control.Less(n, parent) then { For me, branching is much better here: Alloc/FreeVar are up to 20% faster (when compiling the compiler) than branchless. }
+        begin
+          link := @parent^.left;
+          if Assigned(link^) then continue; { This way, “continue”s jump to the beginning of the loop. Important as this code is really hot. }
+        end else
+        begin
+          link := @parent^.right;
+          if Assigned(link^) then continue;
+        end;
+        break;
+      until false;
+    n^.parentColor := PtrUint(parent);
+    n^.left := nil;
+    n^.right := nil;
+    link^ := n;
+    InsertColor(n);
+  end;
+
+  procedure RbTree.Remove(n: pNode);
+  begin
+    n := EraseAugmented(n);
+    if Assigned(n) then EraseColor(n);
+  end;
+
+  function RbTree.LowerBound(key: pointer): pNode;
+  var
+    n: pNode;
+    cmp: PtrInt;
+  begin
+    result := nil;
+    n := root;
+    if Assigned(n) then
+      repeat
+        cmp := Control.KeyCompare(key, n);
+        if cmp <= 0 then { Same as in Add: branch on the Control predicate, but avoid extra internal jumps. }
+        begin
+          result := n;
+          if cmp = 0 then exit; { Not required (and not even correct for a method named LowerBound: if there are equal items, such a method should return first of them, not any of them, better name TBD), but exits early on many equal values, which tends to happen often with memory sizes. }
+          n := n^.left;
+          if Assigned(n) then continue;
+        end else
+        begin
+          n := n^.right;
+          if Assigned(n) then continue;
+        end;
+        break;
+      until false;
+  end;
+
+{$ifdef DEBUG_HEAP_INC}
+  function RbTree.First: pNode;
+  var
+    child: pNode;
+  begin
+    result := root;
+    if not Assigned(result) then exit;
+    child := result;
+    repeat
+      result := child;
+      child := child^.left;
+    until not Assigned(child);
+  end;
+
+  class function RbTree.Next(n: pNode): pNode;
+  var
+    child: pNode;
+  begin
+    result := n^.right;
+    if Assigned(result) then
+    begin
+      while Assigned(result^.left) do result := result^.left;
+      exit;
+    end;
+    result := n;
+    repeat
+      child := result;
+      result := pointer(result^.parentColor and ParentMask);
+    until not Assigned(result) or (child = result^.left);
+  end;
+{$endif DEBUG_HEAP_INC}
+
+  procedure RbTree.ChangeChild(old, new, parent: pNode);
+  begin
+    if Assigned(parent) then
+      parent^.childs[ord(old = parent^.right)] := new
+    else
+      root := new;
+  end;
+
+  procedure RbTree.RotateSetParents(old, new: pNode; color: PtrUint);
+  var
+    oldParentColor: PtrUint;
+  begin
+    oldParentColor := old^.parentColor;
+    new^.parentColor := oldParentColor;
+    old^.parentColor := PtrUint(new) + color;
+    ChangeChild(old, new, pointer(oldParentColor and ParentMask));
+  end;
+
+  procedure RbTree.InsertColor(n: pNode);
+  var
+    parent, gparent, tmp: pNode;
+    L, R: SizeUint;
+  begin
+    parent := pNode(n^.parentColor);
+    repeat
+      if not Assigned(parent) then
+      begin
+        n^.parentColor := BlackTag;
+        break;
+      end;
+      PtrUint(gparent) := parent^.parentColor;
+      if PtrUint(gparent) and BlackTag <> 0 then
+        break;
+      { L and R are offsets of left and right child, or right and left for mirrored cases. }
+      L := PtrUint(@pNode(nil)^.childs) + SizeUint(parent = gparent^.right) * sizeof(pointer);
+      R := (2 * PtrUint(@pNode(nil)^.childs) + sizeof(pointer)) - L; { offsetof(childs, Node) + (sizeof(pointer) - (L - offsetof(childs, Node))), simplified. }
+      tmp := pPointer(pointer(gparent) + R)^;
+      if Assigned(tmp) and (tmp^.parentColor and BlackTag = 0) then
+      begin
+        tmp^.parentColor := PtrUint(gparent) + BlackTag;
+        parent^.parentColor := PtrUint(gparent) + BlackTag;
+        n := gparent;
+        parent := pointer(n^.parentColor and ParentMask);
+        n^.parentColor := PtrUint(parent);
+        continue;
+      end;
+      tmp := pPointer(pointer(parent) + R)^;
+      if n = tmp then
+      begin
+        tmp := pPointer(pointer(n) + L)^;
+        pPointer(pointer(parent) + R)^ := tmp;
+        pPointer(pointer(n) + L)^ := parent;
+        if Assigned(tmp) then
+          tmp^.parentColor := PtrUint(parent) + BlackTag;
+        parent^.parentColor := PtrUint(n);
+        parent := n;
+        tmp := pPointer(pointer(n) + R)^;
+      end;
+      pPointer(pointer(gparent) + L)^ := tmp; { = parent^.R }
+      pPointer(pointer(parent) + R)^ := gparent;
+      if Assigned(tmp) then
+        tmp^.parentColor := PtrUint(gparent) + BlackTag;
+      RotateSetParents(gparent, parent, 0);
+      break;
+    until false;
+  end;
+
+  function RbTree.EraseAugmented(n: pNode): pNode;
+  var
+    child, tmp, parent, successor, child2: pNode;
+    pc: PtrUint;
+  begin
+    result := nil;
+    child := n^.right;
+    tmp := n^.left;
+    if not Assigned(tmp) then
+    begin
+      pc := n^.parentColor;
+      parent := pointer(pc and ParentMask);
+      ChangeChild(n, child, parent);
+      if Assigned(child) then
+        child^.parentColor := pc
+      else if pc and BlackTag <> 0 then
+        result := parent;
+    end
+    else if not Assigned(child) then
+    begin
+      pc := n^.parentColor;
+      tmp^.parentColor := pc;
+      parent := pointer(pc and ParentMask);
+      ChangeChild(n, tmp, parent);
+    end else
+    begin
+      successor := child;
+      tmp := child^.left;
+      if not Assigned(tmp) then
+      begin
+        parent := successor;
+        child2 := successor^.right;
+      end else
+      begin
+        repeat
+          parent := successor;
+          successor := tmp;
+          tmp := tmp^.left;
+        until not Assigned(tmp);
+        child2 := successor^.right;
+        parent^.left := child2;
+        successor^.right := child;
+        child^.parentColor := PtrUint(successor) + child^.parentColor and BlackTag;
+      end;
+
+      tmp := n^.left;
+      successor^.left := tmp;
+      tmp^.parentColor := PtrUint(successor) + tmp^.parentColor and BlackTag;
+
+      pc := n^.parentColor;
+      tmp := pointer(pc and ParentMask);
+      ChangeChild(n, successor, tmp);
+
+      if Assigned(child2) then
+        child2^.parentColor := PtrUint(parent) + BlackTag
+      else if successor^.parentColor and BlackTag <> 0 then
+        result := parent;
+      successor^.parentColor := pc;
+    end;
+  end;
+
+  procedure RbTree.EraseColor(parent: pNode);
+  var
+    n, sibling, tmp1, tmp2: pNode;
+    L, R: SizeUint;
+  begin
+    n := nil;
+    repeat
+      L := PtrUint(@pNode(nil)^.childs) + SizeUint(n = parent^.right) * sizeof(pointer);
+      R := (2 * PtrUint(@pNode(nil)^.childs) + sizeof(pointer)) - L;
+      sibling := pPointer(pointer(parent) + R)^;
+      if sibling^.parentColor and BlackTag = 0 then
+      begin
+        tmp1 := pPointer(pointer(sibling) + L)^;
+        pPointer(pointer(parent) + R)^ := tmp1;
+        pPointer(pointer(sibling) + L)^ := parent;
+        tmp1^.parentColor := PtrUint(parent) + BlackTag;
+        RotateSetParents(parent, sibling, 0);
+        sibling := tmp1;
+      end;
+      tmp1 := pPointer(pointer(sibling) + R)^;
+      if not Assigned(tmp1) or (tmp1^.parentColor and BlackTag <> 0) then
+      begin
+        tmp2 := pPointer(pointer(sibling) + L)^;
+        if not Assigned(tmp2) or (tmp2^.parentColor and BlackTag <> 0) then
+        begin
+          sibling^.parentColor := PtrUint(parent);
+          if parent^.parentColor and BlackTag = 0 then
+            inc(parent^.parentColor, BlackTag)
+          else
+          begin
+            n := parent;
+            parent := pointer(n^.parentColor and ParentMask);
+            if Assigned(parent) then continue;
+          end;
+          break;
+        end;
+        tmp1 := pPointer(pointer(tmp2) + R)^;
+        pPointer(pointer(sibling) + L)^ := tmp1;
+        pPointer(pointer(tmp2) + R)^ := sibling;
+        pPointer(pointer(parent) + R)^ := tmp2;
+        if Assigned(tmp1) then
+          tmp1^.parentColor := PtrUint(sibling) + BlackTag;
+        tmp1 := sibling;
+        sibling := tmp2;
+      end;
+      tmp2 := pPointer(pointer(sibling) + L)^;
+      pPointer(pointer(parent) + R)^ := tmp2;
+      pPointer(pointer(sibling) + L)^ := parent;
+      tmp1^.parentColor := PtrUint(sibling) + BlackTag;
+      if Assigned(tmp2) then
+        tmp2^.parentColor := PtrUint(parent) + tmp2^.parentColor and BlackTag;
+      RotateSetParents(parent, sibling, BlackTag);
+      break;
+    until false;
   end;
   end;
 
 
   class function HeapInc.SizeMinus1ToIndex(sizeMinus1: SizeUint): SizeUint;
   class function HeapInc.SizeMinus1ToIndex(sizeMinus1: SizeUint): SizeUint;
@@ -555,7 +874,7 @@ type
   {$ifdef FPC_HAS_FEATURE_THREADING}
   {$ifdef FPC_HAS_FEATURE_THREADING}
     tf: pFreeChunk;
     tf: pFreeChunk;
   {$endif}
   {$endif}
-    vf: pFreeVarChunk;
+    vfrbn: RbTree.pNode;
     vOs: pVarOSChunk;
     vOs: pVarOSChunk;
     p: pointer;
     p: pointer;
   begin
   begin
@@ -615,14 +934,14 @@ type
         fr := fr^.next;
         fr := fr^.next;
       until not Assigned(fr);
       until not Assigned(fr);
     end;
     end;
-    vf := varFree;
-    if Assigned(vf) then
+    vfrbn := varFree.First;
+    if Assigned(vfrbn) then
     begin
     begin
       write(f, LineEnding, 'Var free:');
       write(f, LineEnding, 'Var free:');
       repeat
       repeat
-        write(f, ' ', vf^.size);
-        vf := vf^.next;
-      until not Assigned(vf);
+        write(f, ' ', pFreeVarChunk(pointer(vfrbn) - FreeVarChunkRbnOffset)^.size);
+        vfrbn := varFree.Next(vfrbn);
+      until not Assigned(vfrbn);
       writeln(f);
       writeln(f);
     end;
     end;
   {$ifdef FPC_HAS_FEATURE_THREADING}
   {$ifdef FPC_HAS_FEATURE_THREADING}
@@ -726,9 +1045,6 @@ type
     sizeIndex, usedSize: SizeUint;
     sizeIndex, usedSize: SizeUint;
     osChunk, osPrev, osNext: pFixedOSChunk;
     osChunk, osPrev, osNext: pFixedOSChunk;
     freeOsNext: pFreeOSChunk;
     freeOsNext: pFreeOSChunk;
-  {$ifdef FPC_HAS_FEATURE_THREADING}
-    chunkTs: pThreadState;
-  {$endif}
   begin
   begin
     osChunk := p - pCommonHeader(p - CommonHeaderSize)^.h shr ChunkOffsetShift;
     osChunk := p - pCommonHeader(p - CommonHeaderSize)^.h shr ChunkOffsetShift;
 
 
@@ -737,13 +1053,12 @@ type
     if osChunk^.threadState <> @self then
     if osChunk^.threadState <> @self then
     begin
     begin
       EnterCriticalSection(gs.lock);
       EnterCriticalSection(gs.lock);
-      chunkTs := osChunk^.threadState;
-      if Assigned(chunkTs) then
+      if Assigned(osChunk^.threadState) then
       begin
       begin
-        { Despite atomic Push lock must be held as otherwise target thread might end and destroy chunkTs.
+        { Despite atomic Push lock must be held as otherwise target thread might end and destroy its threadState.
           However, target thread won’t block to free p, so PushToFree instantly invalidates p. }
           However, target thread won’t block to free p, so PushToFree instantly invalidates p. }
         result := IndexToSize(pCommonHeader(p - CommonHeaderSize)^.h and SizeIndexMask) - CommonHeaderSize;
         result := IndexToSize(pCommonHeader(p - CommonHeaderSize)^.h and SizeIndexMask) - CommonHeaderSize;
-        chunkTs^.PushToFree(p);
+        osChunk^.threadState^.PushToFree(p);
         LeaveCriticalSection(gs.lock);
         LeaveCriticalSection(gs.lock);
         exit;
         exit;
       end;
       end;
@@ -879,59 +1194,23 @@ type
 
 
   function HeapInc.ThreadState.AllocVar(size: SizeUint): pointer;
   function HeapInc.ThreadState.AllocVar(size: SizeUint): pointer;
   var
   var
-    fv, fv2: pFreeVarChunk;
+    fv: pFreeVarChunk;
     osChunk, osNext: pVarOSChunk;
     osChunk, osNext: pVarOSChunk;
-    varPrev, varNext: pFreeVarChunk;
     vSize, minSize, maxSize, statv: SizeUint;
     vSize, minSize, maxSize, statv: SizeUint;
-  {$if MatchEffort >= 0} fv2Size: SizeUint; {$endif}
-  {$if MatchEffort > 1} triesLeft: uint32; {$endif}
   begin
   begin
     size := (size + (VarHeaderSize + VarSizeQuant - 1)) and SizeUint(-VarSizeQuant);
     size := (size + (VarHeaderSize + VarSizeQuant - 1)) and SizeUint(-VarSizeQuant);
   {$ifdef FPC_HAS_FEATURE_THREADING}
   {$ifdef FPC_HAS_FEATURE_THREADING}
     if Assigned(toFree) then
     if Assigned(toFree) then
       FlushToFree;
       FlushToFree;
   {$endif}
   {$endif}
-    { Seach varFree for a chunk that fits size, heuristically strive for smallest. }
-    fv := varFree;
-    while Assigned(fv) and (fv^.size < size) do
-      fv := fv^.next;
-  {$if MatchEffort <> 0}
-    if Assigned(fv) and (fv^.size > size) then { Don’t search further if the size is already exact. }
-    begin
-    {$if MatchEffort > 1} triesLeft := MatchEffort + 1; {$endif}
-      fv2 := fv;
-      repeat
-      {$if MatchEffort > 1}
-        dec(triesLeft);
-        if triesLeft = 0 then
-          break;
-      {$endif}
-        fv2 := fv2^.next;
-        if not Assigned(fv2) then
-          break;
-        fv2Size := fv2^.size;
-        if (fv2Size < size) or (fv2Size >= fv^.size) then
-          continue;
-        fv := fv2;
-      {$if MatchEffort > 1}
-        if fv2Size = size then { Check here instead of the loop condition to prevent ‘continue’ from jumping to the check. }
-          break;
-      {$endif}
-      until {$if MatchEffort = 1} true {$else} false {$endif};
-    end;
-  {$endif MatchEffort <> 0}
+
+    { Search varFree for smallest chunk ≥ size. }
+    fv := pointer(varFree.LowerBound(pointer(PtrUint(size)))); { fv is temporarily RbTree.pNode. }
 
 
     if Assigned(fv) then
     if Assigned(fv) then
     begin
     begin
-      { Remove fv from varFree. }
-      varPrev := fv^.prev;
-      varNext := fv^.next;
-      if Assigned(varPrev) then
-        varPrev^.next := varNext
-      else
-        varFree := varNext;
-      if Assigned(varNext) then
-        varNext^.prev := varPrev;
+      varFree.Remove(pointer(fv));
+      dec(pointer(fv), FreeVarChunkRbnOffset); { Now fv is fair pFreeVarChunk. }
     end else
     end else
     begin
     begin
       minSize := VarOSChunkDataOffset + size;
       minSize := VarOSChunkDataOffset + size;
@@ -961,11 +1240,11 @@ type
       pVarHeader(pointer(fv) - VarHeaderSize)^.threadState := @self;
       pVarHeader(pointer(fv) - VarHeaderSize)^.threadState := @self;
     {$endif}
     {$endif}
       vSize := SizeUint(osChunk^.size - VarOSChunkDataOffset) and SizeUint(-VarSizeQuant);
       vSize := SizeUint(osChunk^.size - VarOSChunkDataOffset) and SizeUint(-VarSizeQuant);
-      pVarHeader(pointer(fv) - VarHeaderSize)^.ch.h := uint32(vSize) or (FirstFlag or LastFlag);
+      pVarHeader(pointer(fv) - VarHeaderSize)^.ch.h := uint32(vSize) + (FirstFlag or LastFlag);
       fv^.size := vSize;
       fv^.size := vSize;
     end;
     end;
 
 
-    { Result will be allocated at the beginning of fv; maybe format the remainder and push it back to varFree. }
+    { Result will be allocated at the beginning of fv; maybe format the remainder and add it back to varFree. }
     result := fv;
     result := fv;
     vSize := fv^.size - size;
     vSize := fv^.size - size;
     if (vSize > MaxFixedHeaderAndPayload) or
     if (vSize > MaxFixedHeaderAndPayload) or
@@ -979,26 +1258,20 @@ type
       pVarHeader(pointer(fv) - VarHeaderSize)^.threadState := @self;
       pVarHeader(pointer(fv) - VarHeaderSize)^.threadState := @self;
     {$endif}
     {$endif}
       { Remainder is still last in the OS chunk if the original chunk was last. }
       { Remainder is still last in the OS chunk if the original chunk was last. }
-      pVarHeader(pointer(fv) - VarHeaderSize)^.ch.h := pVarHeader(result - VarHeaderSize)^.ch.h and LastFlag or uint32(vSize);
+      pVarHeader(pointer(fv) - VarHeaderSize)^.ch.h := pVarHeader(result - VarHeaderSize)^.ch.h and LastFlag + uint32(vSize);
       fv^.size := vSize;
       fv^.size := vSize;
       if pVarHeader(result - VarHeaderSize)^.ch.h and LastFlag = 0 then
       if pVarHeader(result - VarHeaderSize)^.ch.h and LastFlag = 0 then
         pVarHeader(pointer(fv) + vSize - VarHeaderSize)^.prevSize := vSize;
         pVarHeader(pointer(fv) + vSize - VarHeaderSize)^.prevSize := vSize;
 
 
-      { Add fv to varFree. }
-      varNext := varFree;
-      fv^.prev := nil;
-      fv^.next := varNext;
-      if Assigned(varNext) then
-        varNext^.prev := fv;
-      varFree := fv;
+      varFree.Add(@fv^.rbn);
 
 
       { Allocated chunk is still first in the OS chunk if the original chunk was first. }
       { Allocated chunk is still first in the OS chunk if the original chunk was first. }
-      pVarHeader(result - VarHeaderSize)^.ch.h := pVarHeader(result - VarHeaderSize)^.ch.h and FirstFlag or UsedFlag or uint32(size);
+      pVarHeader(result - VarHeaderSize)^.ch.h := pVarHeader(result - VarHeaderSize)^.ch.h and FirstFlag + UsedFlag + uint32(size);
     end else
     end else
     begin
     begin
       { Use the entire chunk. }
       { Use the entire chunk. }
       size := fv^.size;
       size := fv^.size;
-      pVarHeader(result - VarHeaderSize)^.ch.h := pVarHeader(result - VarHeaderSize)^.ch.h and (FirstFlag or LastFlag) or UsedFlag or uint32(size);
+      pVarHeader(result - VarHeaderSize)^.ch.h := pVarHeader(result - VarHeaderSize)^.ch.h and (FirstFlag or LastFlag) + UsedFlag + uint32(size);
     end;
     end;
     statv := used + size;
     statv := used + size;
     used := statv;
     used := statv;
@@ -1009,27 +1282,21 @@ type
 
 
   function HeapInc.ThreadState.FreeVar(p: pointer): SizeUint;
   function HeapInc.ThreadState.FreeVar(p: pointer): SizeUint;
   var
   var
-  {$ifdef FPC_HAS_FEATURE_THREADING}
-    chunkTs: pThreadState;
-  {$endif}
-    varPrev, varNext: pFreeVarChunk;
     p2: pointer;
     p2: pointer;
     fSizeFlags: SizeUint;
     fSizeFlags: SizeUint;
     osChunk, osPrev, osNext: pVarOSChunk;
     osChunk, osPrev, osNext: pVarOSChunk;
     freeOsNext: pFreeOSChunk;
     freeOsNext: pFreeOSChunk;
   begin
   begin
   {$ifdef FPC_HAS_FEATURE_THREADING}
   {$ifdef FPC_HAS_FEATURE_THREADING}
-    chunkTs := pVarHeader(p - VarHeaderSize)^.threadState;
-    if chunkTs <> @self then
+    if pVarHeader(p - VarHeaderSize)^.threadState <> @self then
     begin
     begin
       EnterCriticalSection(gs.lock);
       EnterCriticalSection(gs.lock);
-      chunkTs := pVarHeader(p - VarHeaderSize)^.threadState;
-      if Assigned(chunkTs) then
+      if Assigned(pVarHeader(p - VarHeaderSize)^.threadState) then
       begin
       begin
-        { Despite atomic Push lock must be held as otherwise target thread might end and destroy chunkTs.
+        { Despite atomic Push lock must be held as otherwise target thread might end and destroy its threadState.
           However, target thread won’t block to free p, so PushToFree instantly invalidates p. }
           However, target thread won’t block to free p, so PushToFree instantly invalidates p. }
         result := pVarHeader(p - VarHeaderSize)^.ch.h and uint32(VarSizeMask) - VarHeaderSize;
         result := pVarHeader(p - VarHeaderSize)^.ch.h and uint32(VarSizeMask) - VarHeaderSize;
-        chunkTs^.PushToFree(p);
+        pVarHeader(p - VarHeaderSize)^.threadState^.PushToFree(p);
         LeaveCriticalSection(gs.lock);
         LeaveCriticalSection(gs.lock);
         exit;
         exit;
       end;
       end;
@@ -1049,17 +1316,8 @@ type
       p2 := p + result;
       p2 := p + result;
       if pVarHeader(p2 - VarHeaderSize)^.ch.h and UsedFlag = 0 then
       if pVarHeader(p2 - VarHeaderSize)^.ch.h and UsedFlag = 0 then
       begin
       begin
-        fSizeFlags := (fSizeFlags + pFreeVarChunk(p2)^.size) or pVarHeader(p2 - VarHeaderSize)^.ch.h and LastFlag;
-
-        { Remove p2 from varFree. }
-        varPrev := pFreeVarChunk(p2)^.prev;
-        varNext := pFreeVarChunk(p2)^.next;
-        if Assigned(varPrev) then
-          varPrev^.next := varNext
-        else
-          varFree := varNext;
-        if Assigned(varNext) then
-          varNext^.prev := varPrev;
+        fSizeFlags := (fSizeFlags + pFreeVarChunk(p2)^.size) + pVarHeader(p2 - VarHeaderSize)^.ch.h and LastFlag;
+        varFree.Remove(@pFreeVarChunk(p2)^.rbn);
       end;
       end;
     end;
     end;
 
 
@@ -1069,17 +1327,8 @@ type
       if pVarHeader(p2 - VarHeaderSize)^.ch.h and UsedFlag = 0 then
       if pVarHeader(p2 - VarHeaderSize)^.ch.h and UsedFlag = 0 then
       begin
       begin
         p := p2;
         p := p2;
-        fSizeFlags := (fSizeFlags + pFreeVarChunk(p2)^.size) or pVarHeader(p2 - VarHeaderSize)^.ch.h and FirstFlag;
-
-        { Remove p2 from varFree. }
-        varPrev := pFreeVarChunk(p2)^.prev;
-        varNext := pFreeVarChunk(p2)^.next;
-        if Assigned(varPrev) then
-          varPrev^.next := varNext
-        else
-          varFree := varNext;
-        if Assigned(varNext) then
-          varNext^.prev := varPrev;
+        fSizeFlags := (fSizeFlags + pFreeVarChunk(p2)^.size) + pVarHeader(p2 - VarHeaderSize)^.ch.h and FirstFlag;
+        varFree.Remove(@pFreeVarChunk(p2)^.rbn);
       end;
       end;
     end;
     end;
 
 
@@ -1090,16 +1339,10 @@ type
       if fSizeFlags and LastFlag = 0 then
       if fSizeFlags and LastFlag = 0 then
         pVarHeader(p + fSizeFlags and VarSizeMask - VarHeaderSize)^.prevSize := fSizeFlags and VarSizeMask;
         pVarHeader(p + fSizeFlags and VarSizeMask - VarHeaderSize)^.prevSize := fSizeFlags and VarSizeMask;
 
 
-      pVarHeader(p - VarHeaderSize)^.ch.h := uint32(fSizeFlags) xor UsedFlag;
+      pVarHeader(p - VarHeaderSize)^.ch.h := uint32(fSizeFlags) - UsedFlag;
       pFreeVarChunk(p)^.size := fSizeFlags and VarSizeMask;
       pFreeVarChunk(p)^.size := fSizeFlags and VarSizeMask;
 
 
-      { Add p to varFree. }
-      varNext := varFree;
-      pFreeVarChunk(p)^.prev := nil;
-      pFreeVarChunk(p)^.next := varNext;
-      if Assigned(varNext) then
-        varNext^.prev := p;
-      varFree := p;
+      varFree.Add(@pFreeVarChunk(p)^.rbn);
     end else
     end else
     begin
     begin
       osChunk := p - (VarOSChunkDataOffset + VarHeaderSize);
       osChunk := p - (VarOSChunkDataOffset + VarHeaderSize);
@@ -1146,7 +1389,6 @@ type
   var
   var
     fp, p2: pointer;
     fp, p2: pointer;
     oldpsize, fSizeFlags, growby, statv: SizeUint;
     oldpsize, fSizeFlags, growby, statv: SizeUint;
-    varNext, varPrev: pFreeVarChunk;
   begin
   begin
     if (size < MinVarHeaderAndPayload - VarHeaderSize)
     if (size < MinVarHeaderAndPayload - VarHeaderSize)
       or (size > GrowHeapSize2) { Not strictly necessary but rejects clearly wrong values early so adding headers to the size doesn’t overflow. }
       or (size > GrowHeapSize2) { Not strictly necessary but rejects clearly wrong values early so adding headers to the size doesn’t overflow. }
@@ -1172,7 +1414,7 @@ type
         if fSizeFlags <= MaxFixedHeaderAndPayload then
         if fSizeFlags <= MaxFixedHeaderAndPayload then
           exit;
           exit;
         dec(used, fSizeFlags);
         dec(used, fSizeFlags);
-        fSizeFlags := fSizeFlags or LastFlag;
+        inc(fSizeFlags, LastFlag);
       end
       end
       else if pVarHeader(p2 - VarHeaderSize)^.ch.h and UsedFlag <> 0 then
       else if pVarHeader(p2 - VarHeaderSize)^.ch.h and UsedFlag <> 0 then
       begin
       begin
@@ -1186,21 +1428,12 @@ type
           exit;
           exit;
         dec(used, fSizeFlags);
         dec(used, fSizeFlags);
         { Has empty chunk to the right: extend with freed space. }
         { Has empty chunk to the right: extend with freed space. }
-        fSizeFlags := (fSizeFlags + pFreeVarChunk(p2)^.size) or pVarHeader(p2 - VarHeaderSize)^.ch.h and LastFlag;
-
-        { Remove p2 from varFree. }
-        varPrev := pFreeVarChunk(p2)^.prev;
-        varNext := pFreeVarChunk(p2)^.next;
-        if Assigned(varPrev) then
-          varPrev^.next := varNext
-        else
-          varFree := varNext;
-        if Assigned(varNext) then
-          varNext^.prev := varPrev;
+        fSizeFlags := fSizeFlags + pFreeVarChunk(p2)^.size + pVarHeader(p2 - VarHeaderSize)^.ch.h and LastFlag;
+        varFree.Remove(@pFreeVarChunk(p2)^.rbn);
       end;
       end;
 
 
       { Update p size. }
       { Update p size. }
-      pVarHeader(result - VarHeaderSize)^.ch.h := uint32(size) or pVarHeader(result - VarHeaderSize)^.ch.h and (FirstFlag or UsedFlag);
+      pVarHeader(result - VarHeaderSize)^.ch.h := uint32(size) + pVarHeader(result - VarHeaderSize)^.ch.h and (FirstFlag or UsedFlag);
     end
     end
     { Grow if there is free space. }
     { Grow if there is free space. }
     else if (pVarHeader(result - VarHeaderSize)^.ch.h and LastFlag = 0) and (pVarHeader(p2 - VarHeaderSize)^.ch.h and UsedFlag = 0) and
     else if (pVarHeader(result - VarHeaderSize)^.ch.h and LastFlag = 0) and (pVarHeader(p2 - VarHeaderSize)^.ch.h and UsedFlag = 0) and
@@ -1215,7 +1448,7 @@ type
         if fSizeFlags < MinVarHeaderAndPayload then
         if fSizeFlags < MinVarHeaderAndPayload then
           fSizeFlags := 0
           fSizeFlags := 0
         else
         else
-          fSizeFlags := fSizeFlags or LastFlag;
+          inc(fSizeFlags, LastFlag);
 
 
       growby := pFreeVarChunk(p2)^.size - fSizeFlags and VarSizeMask;
       growby := pFreeVarChunk(p2)^.size - fSizeFlags and VarSizeMask;
       size := oldpsize + growby;
       size := oldpsize + growby;
@@ -1225,23 +1458,14 @@ type
       if statv > maxUsed then
       if statv > maxUsed then
         maxUsed := statv;
         maxUsed := statv;
 
 
-      { Remove p2 from varFree. }
-      varPrev := pFreeVarChunk(p2)^.prev;
-      varNext := pFreeVarChunk(p2)^.next;
-      if Assigned(varPrev) then
-        varPrev^.next := varNext
-      else
-        varFree := varNext;
-      if Assigned(varNext) then
-        varNext^.prev := varPrev;
-
+      varFree.Remove(@pFreeVarChunk(p2)^.rbn);
       { Update p size. }
       { Update p size. }
-      pVarHeader(result - VarHeaderSize)^.ch.h := uint32(size) or pVarHeader(result - VarHeaderSize)^.ch.h and (FirstFlag or UsedFlag);
+      pVarHeader(result - VarHeaderSize)^.ch.h := uint32(size) + pVarHeader(result - VarHeaderSize)^.ch.h and (FirstFlag or UsedFlag);
       { No empty chunk? }
       { No empty chunk? }
       if fSizeFlags = 0 then
       if fSizeFlags = 0 then
       begin
       begin
         if pVarHeader(p2 - VarHeaderSize)^.ch.h and LastFlag <> 0 then
         if pVarHeader(p2 - VarHeaderSize)^.ch.h and LastFlag <> 0 then
-          pVarHeader(result - VarHeaderSize)^.ch.h := pVarHeader(result - VarHeaderSize)^.ch.h or LastFlag
+          inc(pVarHeader(result - VarHeaderSize)^.ch.h, LastFlag)
         else
         else
           pVarHeader(result + size - VarHeaderSize)^.prevSize := size;
           pVarHeader(result + size - VarHeaderSize)^.prevSize := size;
         exit;
         exit;
@@ -1259,14 +1483,7 @@ type
     pFreeVarChunk(fp)^.size := fSizeFlags and VarSizeMask;
     pFreeVarChunk(fp)^.size := fSizeFlags and VarSizeMask;
     if fSizeFlags and LastFlag = 0 then
     if fSizeFlags and LastFlag = 0 then
       pVarHeader(fp + fSizeFlags and VarSizeMask - VarHeaderSize)^.prevSize := fSizeFlags and VarSizeMask;
       pVarHeader(fp + fSizeFlags and VarSizeMask - VarHeaderSize)^.prevSize := fSizeFlags and VarSizeMask;
-
-    { Add fp to varFree. }
-    varNext := varFree;
-    pFreeVarChunk(fp)^.prev := nil;
-    pFreeVarChunk(fp)^.next := varNext;
-    if Assigned(varNext) then
-      varNext^.prev := fp;
-    varFree := fp;
+    varFree.Add(@pFreeVarChunk(fp)^.rbn);
   end;
   end;
 
 
   { If SysOSFree is available, huge chunks aren’t cached by any means.
   { If SysOSFree is available, huge chunks aren’t cached by any means.
@@ -1509,7 +1726,6 @@ type
   var
   var
     prevSize, size, statv: SizeUint;
     prevSize, size, statv: SizeUint;
     h: uint32;
     h: uint32;
-    varFreeHead: pFreeVarChunk;
   begin
   begin
     repeat
     repeat
       prevSize := pVarHeader(p - VarHeaderSize)^.prevSize;
       prevSize := pVarHeader(p - VarHeaderSize)^.prevSize;
@@ -1529,24 +1745,16 @@ type
       GetOSChunk does not set threadState if it takes the chunk from local freeOS, assuming it is already set. }
       GetOSChunk does not set threadState if it takes the chunk from local freeOS, assuming it is already set. }
     pOSChunk(p - (VarOSChunkDataOffset + VarHeaderSize))^.threadState := @self;
     pOSChunk(p - (VarOSChunkDataOffset + VarHeaderSize))^.threadState := @self;
 
 
-    varFreeHead := varFree;
     repeat
     repeat
       pVarHeader(p - VarHeaderSize)^.threadState := @self;
       pVarHeader(p - VarHeaderSize)^.threadState := @self;
       h := pVarHeader(p - VarHeaderSize)^.ch.h;
       h := pVarHeader(p - VarHeaderSize)^.ch.h;
       size := h and uint32(VarSizeMask);
       size := h and uint32(VarSizeMask);
       if h and UsedFlag = 0 then
       if h and UsedFlag = 0 then
-      begin
-        { Add free chunk to varFree. }
-        pFreeVarChunk(p)^.prev := nil;
-        pFreeVarChunk(p)^.next := varFreeHead;
-        if Assigned(varFreeHead) then
-          varFreeHead^.prev := pFreeVarChunk(p);
-        varFreeHead := p;
-      end else
+        varFree.Add(@pFreeVarChunk(p)^.rbn)
+      else
         inc(used, size); { maxUsed is updated after the loop. }
         inc(used, size); { maxUsed is updated after the loop. }
       inc(p, size);
       inc(p, size);
     until h and LastFlag <> 0;
     until h and LastFlag <> 0;
-    varFree := varFreeHead;
     statv := used + gs.hugeUsed;
     statv := used + gs.hugeUsed;
     if statv > maxUsed then
     if statv > maxUsed then
       maxUsed := statv;
       maxUsed := statv;
@@ -1597,12 +1805,12 @@ type
 {$endif ndef FPC_SECTION_THREADVARS}
 {$endif ndef FPC_SECTION_THREADVARS}
 {$endif FPC_HAS_FEATURE_THREADING}
 {$endif FPC_HAS_FEATURE_THREADING}
 
 
-class function HeapInc.AllocFailed: pointer;
-begin
-  if not ReturnNilIfGrowHeapFails then
-    HandleError(204);
-  result := nil;
-end;
+  class function HeapInc.AllocFailed: pointer;
+  begin
+    if not ReturnNilIfGrowHeapFails then
+      HandleError(204);
+    result := nil;
+  end;
 
 
 function SysGetFPCHeapStatus:TFPCHeapStatus;
 function SysGetFPCHeapStatus:TFPCHeapStatus;
 var
 var
@@ -1755,10 +1963,6 @@ begin
     FillChar(result^, SysMemSize(result), 0);
     FillChar(result^, SysMemSize(result), 0);
 end;
 end;
 
 
-{$endif FPC_NO_DEFAULT_HEAP}
-
-{$ifndef HAS_MEMORYMANAGER}
-
 {*****************************************************************************
 {*****************************************************************************
                                  InitHeap
                                  InitHeap
 *****************************************************************************}
 *****************************************************************************}
@@ -1825,7 +2029,6 @@ begin
 {$endif FPC_HAS_FEATURE_THREADING | defined(HAS_SYSOSFREE)}
 {$endif FPC_HAS_FEATURE_THREADING | defined(HAS_SYSOSFREE)}
 end;
 end;
 
 
-{$endif ndef HAS_MEMORYMANAGER}
+{$endif ndef FPC_NO_DEFAULT_HEAP}
 
 
-{$endif ndef FPC_NO_DEFAULT_MEMORYMANAGER}
-{$endif defined(FPC_HAS_FEATURE_HEAP) or defined(FPC_IN_HEAPMGR)}
+{$endif ndef HAS_MEMORYMANAGER and (defined(FPC_HAS_FEATURE_HEAP) or defined(FPC_IN_HEAPMGR))}