|
@@ -0,0 +1,1762 @@
|
|
|
|
+{
|
|
|
|
+ This file is part of the Free Pascal run time library.
|
|
|
|
+ Copyright (c) 1999-2000 by the Free Pascal development team.
|
|
|
|
+
|
|
|
|
+ functions for heap management in the data segment
|
|
|
|
+
|
|
|
|
+ See the file COPYING.FPC, included in this distribution,
|
|
|
|
+ for details about the copyright.
|
|
|
|
+
|
|
|
|
+ This program is distributed in the hope that it will be useful,
|
|
|
|
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
|
|
+
|
|
|
|
+ **********************************************************************}
|
|
|
|
+
|
|
|
|
+{****************************************************************************}
|
|
|
|
+{ Do not use standard memory manager }
|
|
|
|
+{ $define HAS_MEMORYMANAGER}
|
|
|
|
+
|
|
|
|
+{ Memory manager }
|
|
|
|
+{$if not defined(FPC_NO_DEFAULT_MEMORYMANAGER)}
|
|
|
|
+const
|
|
|
|
+ MemoryManager: TMemoryManager = (
|
|
|
|
+ NeedLock: false; // Obsolete
|
|
|
|
+ GetMem: {$ifndef FPC_NO_DEFAULT_HEAP}@SysGetMem{$else}nil{$endif};
|
|
|
|
+ FreeMem: {$ifndef FPC_NO_DEFAULT_HEAP}@SysFreeMem{$else}nil{$endif};
|
|
|
|
+ FreeMemSize: {$ifndef FPC_NO_DEFAULT_HEAP}@SysFreeMemSize{$else}nil{$endif};
|
|
|
|
+ AllocMem: {$ifndef FPC_NO_DEFAULT_HEAP}@SysAllocMem{$else}nil{$endif};
|
|
|
|
+ ReAllocMem: {$ifndef FPC_NO_DEFAULT_HEAP}@SysReAllocMem{$else}nil{$endif};
|
|
|
|
+ MemSize: {$ifndef FPC_NO_DEFAULT_HEAP}@SysMemSize{$else}nil{$endif};
|
|
|
|
+ InitThread: nil;
|
|
|
|
+ DoneThread: nil;
|
|
|
|
+ RelocateHeap: nil;
|
|
|
|
+ GetHeapStatus: {$ifndef FPC_NO_DEFAULT_HEAP}@SysGetHeapStatus{$else}nil{$endif};
|
|
|
|
+ GetFPCHeapStatus: {$ifndef FPC_NO_DEFAULT_HEAP}@SysGetFPCHeapStatus{$else}nil{$endif};
|
|
|
|
+ );
|
|
|
|
+{$elseif not defined(FPC_IN_HEAPMGR)}
|
|
|
|
+const
|
|
|
|
+ MemoryManager: TMemoryManager = (
|
|
|
|
+ NeedLock: false; // Obsolete
|
|
|
|
+ GetMem: nil;
|
|
|
|
+ FreeMem: nil;
|
|
|
|
+ FreeMemSize: nil;
|
|
|
|
+ AllocMem: nil;
|
|
|
|
+ ReAllocMem: nil;
|
|
|
|
+ MemSize: nil;
|
|
|
|
+ InitThread: nil;
|
|
|
|
+ DoneThread: nil;
|
|
|
|
+ RelocateHeap: nil;
|
|
|
|
+ GetHeapStatus: nil;
|
|
|
|
+ GetFPCHeapStatus: nil;
|
|
|
|
+ );public name 'FPC_SYSTEM_MEMORYMANAGER';
|
|
|
|
+{$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
|
|
|
|
+*****************************************************************************}
|
|
|
|
+
|
|
|
|
+{$ifndef FPC_IN_HEAPMGR}
|
|
|
|
+procedure GetMemoryManager(var MemMgr:TMemoryManager);
|
|
|
|
+begin
|
|
|
|
+ MemMgr := MemoryManager;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+procedure SetMemoryManager(const MemMgr:TMemoryManager);
|
|
|
|
+begin
|
|
|
|
+ MemoryManager := MemMgr;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function IsMemoryManagerSet:Boolean;
|
|
|
|
+begin
|
|
|
|
+{$if defined(HAS_MEMORYMANAGER) or defined(FPC_NO_DEFAULT_MEMORYMANAGER)}
|
|
|
|
+ Result:=false;
|
|
|
|
+{$else not FPC_NO_DEFAULT_MEMORYMANAGER}
|
|
|
|
+ IsMemoryManagerSet := (MemoryManager.GetMem<>@SysGetMem)
|
|
|
|
+ or (MemoryManager.FreeMem<>@SysFreeMem);
|
|
|
|
+{$endif HAS_MEMORYMANAGER or FPC_NO_DEFAULT_MEMORYMANAGER}
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+{$ifdef FPC_HAS_FEATURE_HEAP}
|
|
|
|
+procedure GetMem(Out p:pointer;Size:ptruint);
|
|
|
|
+begin
|
|
|
|
+ p := MemoryManager.GetMem(Size);
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+procedure GetMemory(Out p:pointer;Size:ptruint);
|
|
|
|
+begin
|
|
|
|
+ GetMem(p,size);
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+procedure FreeMem(p:pointer;Size:ptruint);
|
|
|
|
+begin
|
|
|
|
+ MemoryManager.FreeMemSize(p,Size);
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+procedure FreeMemory(p:pointer;Size:ptruint);
|
|
|
|
+begin
|
|
|
|
+ FreeMem(p,size);
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+function GetHeapStatus:THeapStatus;
|
|
|
|
+begin
|
|
|
|
+ Result:=MemoryManager.GetHeapStatus();
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+function GetFPCHeapStatus:TFPCHeapStatus;
|
|
|
|
+begin
|
|
|
|
+ Result:=MemoryManager.GetFPCHeapStatus();
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+function MemSize(p:pointer):ptruint;
|
|
|
|
+begin
|
|
|
|
+ MemSize := MemoryManager.MemSize(p);
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+{ Delphi style }
|
|
|
|
+function FreeMem(p:pointer):ptruint;
|
|
|
|
+begin
|
|
|
|
+ FreeMem := MemoryManager.FreeMem(p);
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function FreeMemory(p:pointer):ptruint; cdecl;
|
|
|
|
+begin
|
|
|
|
+ FreeMemory := FreeMem(p);
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function GetMem(size:ptruint):pointer;
|
|
|
|
+begin
|
|
|
|
+ GetMem := MemoryManager.GetMem(Size);
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function GetMemory(size:ptruint):pointer; cdecl;
|
|
|
|
+begin
|
|
|
|
+ GetMemory := GetMem(size);
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function AllocMem(Size:ptruint):pointer;
|
|
|
|
+begin
|
|
|
|
+ AllocMem := MemoryManager.AllocMem(size);
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+function ReAllocMem(var p:pointer;Size:ptruint):pointer;
|
|
|
|
+begin
|
|
|
|
+ ReAllocMem := MemoryManager.ReAllocMem(p,size);
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function ReAllocMemory(p:pointer;Size:ptruint):pointer; cdecl;
|
|
|
|
+begin
|
|
|
|
+ ReAllocMemory := ReAllocMem(p,size);
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+{ Needed for calls from Assembler }
|
|
|
|
+function fpc_getmem(size:ptruint):pointer;compilerproc;[public,alias:'FPC_GETMEM'];
|
|
|
|
+begin
|
|
|
|
+ fpc_GetMem := MemoryManager.GetMem(size);
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+procedure fpc_freemem(p:pointer);compilerproc;[public,alias:'FPC_FREEMEM'];
|
|
|
|
+begin
|
|
|
|
+ MemoryManager.FreeMem(p);
|
|
|
|
+end;
|
|
|
|
+{$endif FPC_HAS_FEATURE_HEAP}
|
|
|
|
+{$endif FPC_IN_HEAPMGR}
|
|
|
|
+
|
|
|
|
+{$if defined(FPC_HAS_FEATURE_HEAP) or defined(FPC_IN_HEAPMGR)}
|
|
|
|
+{$ifndef HAS_MEMORYMANAGER}
|
|
|
|
+
|
|
|
|
+{
|
|
|
|
+ We use 'fixed' size chunks for small allocations,
|
|
|
|
+ and os chunks with variable sized blocks for big
|
|
|
|
+ allocations.
|
|
|
|
+
|
|
|
|
+ * a block is an area allocated by user
|
|
|
|
+ * a chunk is a block plus our bookkeeping
|
|
|
|
+ * an os chunk is a collection of chunks
|
|
|
|
+
|
|
|
|
+ Memory layout:
|
|
|
|
+ fixed: < CommonHeader > [ ... user data ... ]
|
|
|
|
+ variable: [ VarHeader < CommonHeader > ] [ ... user data ... ]
|
|
|
|
+
|
|
|
|
+ When all chunks in an os chunk are free, we keep a few around
|
|
|
|
+ but otherwise it will be freed to the OS.
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+type
|
|
|
|
+ HeapInc = object
|
|
|
|
+ const
|
|
|
|
+ { Alignment requirement for blocks. All fixed sizes (among other things) are assumed to be divisible. }
|
|
|
|
+ Alignment = 2 * sizeof(pointer);
|
|
|
|
+
|
|
|
|
+ { Fixed chunk sizes are:
|
|
|
|
+ ┌──── step = 16 ────┐┌─── step = 32 ────┐┌──── step = 48 ───┐┌ step 64 ┐
|
|
|
|
+ 16, 32, 48, 64, 80, 96, 128, 160, 192, 224, 272, 320, 368, 416, 480, 544
|
|
|
|
+ #0 #1 #2 #3 #4 #5 #6 #7 #8 #9 #10 #11 #12 #13 #14 #15 }
|
|
|
|
+ MinFixedHeaderAndPayload = 16;
|
|
|
|
+ MaxFixedHeaderAndPayload = 544;
|
|
|
|
+ FixedSizesCount = 16;
|
|
|
|
+ FixedSizes: array[0 .. FixedSizesCount - 1] of uint16 = (16, 32, 48, 64, 80, 96, 128, 160, 192, 224, 272, 320, 368, 416, 480, 544);
|
|
|
|
+ SizeMinus1Div16ToIndex: array[0 .. (MaxFixedHeaderAndPayload - 1) div 16] of uint8 =
|
|
|
|
+ { 16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240, 256, 272, 288, 304, 320, 336, 352, 368, 384, 400, 416, 432, 448, 464, 480, 496, 512, 528, 544 }
|
|
|
|
+ ( 0, 1, 2, 3, 4, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 10, 11, 11, 11, 12, 12, 12, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15);
|
|
|
|
+
|
|
|
|
+ class function SizeMinus1ToIndex(sizeMinus1: SizeUint): SizeUint; static; inline; { sizeMinus1 + 1 ≤ MaxFixedHeaderAndPayload }
|
|
|
|
+ class function IndexToSize(sizeIndex: SizeUint): SizeUint; static; inline;
|
|
|
|
+
|
|
|
|
+ const
|
|
|
|
+ OSChunkFixedSizeQuant = 32 * 1024;
|
|
|
|
+ OSChunkVarSizeQuant = 64 * 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. }
|
|
|
|
+ MinVarHeaderAndPayload = MaxFixedHeaderAndPayload * 3 div 4;
|
|
|
|
+
|
|
|
|
+ { Adjustable part ends here~ }
|
|
|
|
+
|
|
|
|
+ const
|
|
|
|
+ SizeIndexBits = 1 + trunc(ln(FixedSizesCount - 1) / ln(2));
|
|
|
|
+ SizeIndexMask = 1 shl SizeIndexBits - 1;
|
|
|
|
+ FixedBitPos = {$if SizeIndexBits >= 3} SizeIndexBits {$else} 3 {$endif}; { Variable chunks use 3 low bits for used / first / last. }
|
|
|
|
+ FixedFlag = 1 shl FixedBitPos;
|
|
|
|
+ ChunkOffsetShift = FixedBitPos + 1;
|
|
|
|
+
|
|
|
|
+ { Not really used; MaxFixedChunkSize limit on fixed OS chunks assumed to be strictly enforced and (much!) more restricting than MaxChunkOffset.
|
|
|
|
+ MaxFixedChunkSize = 256 Kb.
|
|
|
|
+ MaxChunkOffset ~ 2^(32 - 6) ~ 67 Mb.
|
|
|
|
+ Indices could be stored instead, but offsets avoid multiplications. }
|
|
|
|
+ MaxChunkOffset = High(uint32) shr ChunkOffsetShift;
|
|
|
|
+
|
|
|
|
+ UsedFlag = 1 shl 0;
|
|
|
|
+ FirstFlag = 1 shl 1;
|
|
|
|
+ LastFlag = 1 shl 2;
|
|
|
|
+ VarSizeQuant = 1 shl ChunkOffsetShift; {$if VarSizeQuant < Alignment} {$error Assumed to be >= Alignment.} {$endif}
|
|
|
|
+ VarSizeMask = SizeUint(-VarSizeQuant);
|
|
|
|
+
|
|
|
|
+ type
|
|
|
|
+ { Common header of any memory chunk, residing immediately to the left of the ~payload~ (block).
|
|
|
|
+
|
|
|
|
+ Fixed chunk header, assuming SizeIndexBits = 4:
|
|
|
|
+ h[0:3] = size index (= h and SizeIndexMask)
|
|
|
|
+ h[4] = 1 (h and FixedFlag <> 0)
|
|
|
|
+ h[5:31] — offset in the OS chunk (= h shr ChunkOffsetShift)
|
|
|
|
+
|
|
|
|
+ Variable chunk header, assuming SizeIndexBits = 4:
|
|
|
|
+ h[0] = used flag (h and UsedFlag <> 0)
|
|
|
|
+ h[1] = first flag (h and FirstFlag <> 0)
|
|
|
|
+ h[2] = last flag (h and LastFlag <> 0)
|
|
|
|
+ h[3] = unused
|
|
|
|
+ h[4] = 0 (h and FixedFlag = 0)
|
|
|
|
+ h[5:31] = size, rounded up to 32 (VarSizeQuant), shr 5; in other words, size = h and VarSizeMask.
|
|
|
|
+
|
|
|
|
+ If sizeof(SizeUint) > 4: “h and VarSizeMask” is low part of size, high part is stored in VarChunk.sizeHi. }
|
|
|
|
+
|
|
|
|
+ pCommonHeader = ^CommonHeader;
|
|
|
|
+ CommonHeader = record
|
|
|
|
+ h: uint32;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ pThreadState = ^ThreadState;
|
|
|
|
+
|
|
|
|
+ { Chunk that has been freed. Reuses the now-uninteresting payload, so payload must always fit its size.
|
|
|
|
+ Used for fixed freelists and cross-thread to-free queue. }
|
|
|
|
+ pFreeChunk = ^FreeChunk;
|
|
|
|
+ FreeChunk = record
|
|
|
|
+ next: pFreeChunk;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ pOSChunk = ^OSChunk;
|
|
|
|
+ OSChunk = object { Common header for all OS chunks. }
|
|
|
|
+ size: SizeUint; { Full size asked from SysOSAlloc. }
|
|
|
|
+ {$ifdef FPC_HAS_FEATURE_THREADING}
|
|
|
|
+ threadState: pThreadState; { Protected with gs.lock. Nil if orphaned. }
|
|
|
|
+ {$endif}
|
|
|
|
+ prev, next: pointer; { pOSChunk, but used for different subtypes. }
|
|
|
|
+
|
|
|
|
+ { For what purpose this chunk was used the last time. -1 — var, N ≥ 0 — fixed size N. Allows for a small optimization when reusing fixed chunks. }
|
|
|
|
+ sizeIndex: SizeInt;
|
|
|
|
+
|
|
|
|
+ { Remove from list src and push to list dest. }
|
|
|
|
+ procedure MoveTo(var src, dest: pOSChunk);
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ pFreeOSChunk = ^FreeOSChunk;
|
|
|
|
+ FreeOSChunk = object(OSChunk)
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ FreeOSChunkList = object
|
|
|
|
+ first, last: pFreeOSChunk;
|
|
|
|
+ {$ifdef HAS_SYSOSFREE}
|
|
|
|
+ n: SizeUint;
|
|
|
|
+ {$endif}
|
|
|
|
+
|
|
|
|
+ function Get(minSize, maxSize: SizeUint): pOSChunk;
|
|
|
|
+ {$ifdef HAS_SYSOSFREE}
|
|
|
|
+ function FreeOne: SizeUint;
|
|
|
|
+ procedure FreeAll;
|
|
|
|
+ {$endif}
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ pFixedOSChunk = ^FixedOSChunk;
|
|
|
|
+ FixedOSChunk = object(OSChunk)
|
|
|
|
+ { Data starts at FixedOSChunkDataOffset and spans for “maxSize” (virtual value, does not exist directly) bytes, of which:
|
|
|
|
+ — first formattedSize are either allocated (“used”; counted in usedSize) or in the freelist (firstFreeChunk; size = formattedSize - usedSize),
|
|
|
|
+ — the rest “maxSize” - formattedSize are yet unallocated space.
|
|
|
|
+
|
|
|
|
+ This design, together with tracking free chunks per FixedOSChunk rather than per fixed size, trivializes reusing the chunk.
|
|
|
|
+ Chopping all available space at once would get rid of the “unallocated space” entity, but is a lot of potentially wasted work:
|
|
|
|
+ https://gitlab.com/freepascal.org/fpc/source/-/issues/40447.
|
|
|
|
+
|
|
|
|
+ Values are multiples of the chunk size instead of counts (could be chunksUsed, chunksFormatted, chunksMax) to save on multiplications.
|
|
|
|
+ Moreover, instead of “maxSize” from the explanation above, fullThreshold is used, which is such a value that the chunk is full if usedSize >= fullThreshold.
|
|
|
|
+ maxSize = RoundUp(fullThreshold, chunk size).
|
|
|
|
+ Reason is, calculating fullThreshold does not require division. }
|
|
|
|
+
|
|
|
|
+ firstFreeChunk: pFreeChunk;
|
|
|
|
+ usedSize, formattedSize, fullThreshold: uint32;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ pVarOSChunk = ^VarOSChunk;
|
|
|
|
+ VarOSChunk = object(OSChunk)
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ pVarHeader = ^VarHeader;
|
|
|
|
+ VarHeader = record
|
|
|
|
+ prevSize: SizeUint; { Always 0 for the first chunk. }
|
|
|
|
+ {$ifdef FPC_HAS_FEATURE_THREADING}
|
|
|
|
+ threadState: pThreadState;
|
|
|
|
+ {$endif}
|
|
|
|
+ {$if sizeof(SizeUint) > 4}
|
|
|
|
+ sizeHi: uint32;
|
|
|
|
+ {$endif}
|
|
|
|
+ { Assumed to indeed match chunk’s CommonHeader, i.e. that there is no padding after this field.
|
|
|
|
+ Otherwise must be accessed as pCommonHeader(pointer(varHdr) + (VarHeaderSize - CommonHeaderSize))^ :D. }
|
|
|
|
+ ch: CommonHeader;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ { Reuses the payload of variable chunks whose ch.h and UsedFlag = 0, so variable chunk payload must always fit its size. }
|
|
|
|
+ pFreeVarChunk = ^FreeVarChunk;
|
|
|
|
+ FreeVarChunk = record
|
|
|
|
+ prev, next: pFreeVarChunk;
|
|
|
|
+ size: SizeUint; { Cached size for easier access when working with free chunks, always equals to header.sizeHi shl 32 or header.ch.h and VarSizeMask. }
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ ThreadState = object
|
|
|
|
+ fullOS: pFixedOSChunk; { Completely filled fixed OS chunks. }
|
|
|
|
+ freeOS: FreeOSChunkList; { Completely empty OS chunks. }
|
|
|
|
+ {$ifdef FPC_HAS_FEATURE_THREADING}
|
|
|
|
+ toFree: pFreeChunk; { Free requests from other threads, atomic. }
|
|
|
|
+ {$endif}
|
|
|
|
+
|
|
|
|
+ used, maxUsed, allocated, maxAllocated: SizeUint; { Statistics. }
|
|
|
|
+
|
|
|
|
+ varOS: pVarOSChunk;
|
|
|
|
+ varFree: pFreeVarChunk;
|
|
|
|
+
|
|
|
|
+ { 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. }
|
|
|
|
+ fixedPartialOS: array[0 .. FixedSizesCount - 1] of pFixedOSChunk;
|
|
|
|
+
|
|
|
|
+ { Only to calculate preferable new fixed OS chunk sizes...
|
|
|
|
+ (Updated infrequently, as opposed to possible “fixedUsed”. When a new fixed OS chunk is required, all existing chunks of its size are full.) }
|
|
|
|
+ allocatedByFullFixed: array[0 .. FixedSizesCount - 1] of SizeUint;
|
|
|
|
+
|
|
|
|
+ {$ifdef DEBUG_HEAP_INC}
|
|
|
|
+ procedure Dump(var f: text);
|
|
|
|
+ {$endif}
|
|
|
|
+
|
|
|
|
+ function AllocFixed(size: SizeUint): pointer;
|
|
|
|
+ function FreeFixed(p: pointer): SizeUint;
|
|
|
|
+
|
|
|
|
+ function GetOSChunk(minSize, maxSize: SizeUint; sizeIndex: SizeInt): pOSChunk;
|
|
|
|
+ function AllocateOSChunk(minSize: SizeUint; sizeIndex: SizeInt): pOSChunk;
|
|
|
|
+
|
|
|
|
+ function AllocVar(size: SizeUint): pointer;
|
|
|
|
+ function FreeVar(p: pointer): SizeUint;
|
|
|
|
+ function TryResizeVar(p: pointer; size: SizeUint): pointer;
|
|
|
|
+ {$ifdef HAS_SYSOSREALLOC}
|
|
|
|
+ function TrySysOSRealloc(p: pointer; oldSize, newSize: SizeUint): pointer;
|
|
|
|
+ {$endif}
|
|
|
|
+
|
|
|
|
+ {$ifdef FPC_HAS_FEATURE_THREADING}
|
|
|
|
+ procedure PushToFree(p: pFreeChunk);
|
|
|
|
+ procedure FlushToFree;
|
|
|
|
+
|
|
|
|
+ procedure Orphan; { Must be performed under gs.lock. }
|
|
|
|
+ class procedure Orphan(list: pFixedOSChunk); static;
|
|
|
|
+ procedure Adopt(osChunk: pFixedOSChunk); { Must be performed under gs.lock. }
|
|
|
|
+ procedure AdoptVarOwner(p: pointer); { Adopts the OS chunk that contains p. Must be performed under gs.lock. }
|
|
|
|
+
|
|
|
|
+ class function ChangeThreadStates(list: pOSChunk; ts: pThreadState): pOSChunk; static; { Returns the last item of the list. }
|
|
|
|
+ class procedure ChangeThreadState(vOs: pVarOSChunk; ts: pThreadState); static;
|
|
|
|
+
|
|
|
|
+ {$ifndef FPC_SECTION_THREADVARS}
|
|
|
|
+ procedure FixupSelfPtr;
|
|
|
|
+ {$endif ndef FPC_SECTION_THREADVARS}
|
|
|
|
+ {$endif FPC_HAS_FEATURE_THREADING}
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ {$ifdef FPC_HAS_FEATURE_THREADING}
|
|
|
|
+ GlobalState = record
|
|
|
|
+ lock: TRTLCriticalSection;
|
|
|
|
+ lockUse: int32;
|
|
|
|
+
|
|
|
|
+ { Data from dead threads (“orphaned”), protected by gs.lock. }
|
|
|
|
+ fixedOS: pFixedOSChunk;
|
|
|
|
+ freeOS: FreeOSChunkList;
|
|
|
|
+ varOS: pVarOSChunk;
|
|
|
|
+ end;
|
|
|
|
+ {$endif FPC_HAS_FEATURE_THREADING}
|
|
|
|
+
|
|
|
|
+{$ifdef FPC_HAS_FEATURE_THREADING}
|
|
|
|
+ class var
|
|
|
|
+ gs: GlobalState;
|
|
|
|
+ class threadvar
|
|
|
|
+ thisTs: ThreadState;
|
|
|
|
+{$else FPC_HAS_FEATURE_THREADING}
|
|
|
|
+ class var
|
|
|
|
+ thisTs: ThreadState;
|
|
|
|
+{$endif FPC_HAS_FEATURE_THREADING}
|
|
|
|
+
|
|
|
|
+ const
|
|
|
|
+ CommonHeaderSize = sizeof(CommonHeader);
|
|
|
|
+ {$if MinFixedHeaderAndPayload < CommonHeaderSize + sizeof(FreeChunk)} {$error MinFixedHeaderAndPayload does not fit CommonHeader + FreeChunk.} {$endif}
|
|
|
|
+ FixedOSChunkDataOffset = (sizeof(FixedOSChunk) + CommonHeaderSize + Alignment - 1) and -Alignment - CommonHeaderSize;
|
|
|
|
+ VarHeaderSize = sizeof(VarHeader);
|
|
|
|
+ VarOSChunkDataOffset = (sizeof(VarOSChunk) + VarHeaderSize + Alignment - 1) and -Alignment - VarHeaderSize;
|
|
|
|
+ MaxVarPayload = High(SizeUint) - (VarOSChunkDataOffset + VarHeaderSize + OSChunkVarSizeQuant); { Absolute limit on chunk sizes. }
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ class function HeapInc.SizeMinus1ToIndex(sizeMinus1: SizeUint): SizeUint;
|
|
|
|
+ begin
|
|
|
|
+ result := SizeMinus1Div16ToIndex[sizeMinus1 div 16];
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ class function HeapInc.IndexToSize(sizeIndex: SizeUint): SizeUint;
|
|
|
|
+ begin
|
|
|
|
+ result := FixedSizes[sizeIndex];
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ procedure HeapInc.OSChunk.MoveTo(var src, dest: pOSChunk);
|
|
|
|
+ var
|
|
|
|
+ osNext, osPrev: pOSChunk;
|
|
|
|
+ begin
|
|
|
|
+ osPrev := prev;
|
|
|
|
+ osNext := next;
|
|
|
|
+ if Assigned(osPrev) then
|
|
|
|
+ osPrev^.next := osNext
|
|
|
|
+ else
|
|
|
|
+ src := osNext;
|
|
|
|
+ if Assigned(osNext) then
|
|
|
|
+ osNext^.prev := osPrev;
|
|
|
|
+ prev := nil;
|
|
|
|
+ osNext := dest;
|
|
|
|
+ next := osNext;
|
|
|
|
+ if Assigned(osNext) then
|
|
|
|
+ osNext^.prev := @self;
|
|
|
|
+ dest := @self;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ function HeapInc.FreeOSChunkList.Get(minSize, maxSize: SizeUint): pOSChunk;
|
|
|
|
+ var
|
|
|
|
+ prev, next: pFreeOSChunk;
|
|
|
|
+ begin
|
|
|
|
+ result := first;
|
|
|
|
+ while Assigned(result) and not ((result^.size >= minSize) and (result^.size <= maxSize)) do
|
|
|
|
+ result := result^.next;
|
|
|
|
+ if not Assigned(result) then
|
|
|
|
+ exit;
|
|
|
|
+
|
|
|
|
+ prev := result^.prev;
|
|
|
|
+ next := result^.next;
|
|
|
|
+ if Assigned(prev) then
|
|
|
|
+ prev^.next := next
|
|
|
|
+ else
|
|
|
|
+ first := next;
|
|
|
|
+ if Assigned(next) then
|
|
|
|
+ next^.prev := prev
|
|
|
|
+ else
|
|
|
|
+ last := prev;
|
|
|
|
+ {$ifdef HAS_SYSOSFREE} dec(n); {$endif}
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+{$ifdef HAS_SYSOSFREE}
|
|
|
|
+ function HeapInc.FreeOSChunkList.FreeOne: SizeUint;
|
|
|
|
+ var
|
|
|
|
+ best, prev: pFreeOSChunk;
|
|
|
|
+ begin
|
|
|
|
+ { Presently: the last one (which means LRU, as they are pushed to the beginning). }
|
|
|
|
+ best := last;
|
|
|
|
+ prev := best^.prev;
|
|
|
|
+ if Assigned(prev) then
|
|
|
|
+ prev^.next := nil
|
|
|
|
+ else
|
|
|
|
+ first := nil;
|
|
|
|
+ last := prev;
|
|
|
|
+ dec(n);
|
|
|
|
+ result := best^.size;
|
|
|
|
+ SysOSFree(best, best^.size);
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ procedure HeapInc.FreeOSChunkList.FreeAll;
|
|
|
|
+ var
|
|
|
|
+ cur, next: pFreeOSChunk;
|
|
|
|
+ begin
|
|
|
|
+ cur := first;
|
|
|
|
+ first := nil;
|
|
|
|
+ last := nil;
|
|
|
|
+ n := 0;
|
|
|
|
+ while Assigned(cur) do
|
|
|
|
+ begin
|
|
|
|
+ next := cur^.next;
|
|
|
|
+ SysOSFree(cur, cur^.size);
|
|
|
|
+ cur := next;
|
|
|
|
+ end;
|
|
|
|
+ end;
|
|
|
|
+{$endif HAS_SYSOSFREE}
|
|
|
|
+
|
|
|
|
+{$ifdef DEBUG_HEAP_INC}
|
|
|
|
+ procedure HeapInc.ThreadState.Dump(var f: text);
|
|
|
|
+ var
|
|
|
|
+ i: SizeInt;
|
|
|
|
+ fix: pFixedOSChunk;
|
|
|
|
+ fr: pFreeOSChunk;
|
|
|
|
+ {$ifdef FPC_HAS_FEATURE_THREADING}
|
|
|
|
+ tf: pFreeChunk;
|
|
|
|
+ {$endif}
|
|
|
|
+ vf: pFreeVarChunk;
|
|
|
|
+ vOs: pVarOSChunk;
|
|
|
|
+ p: pointer;
|
|
|
|
+ begin
|
|
|
|
+ writeln(f, 'used = ', used, ', allocated = ', allocated, ', maxUsed = ', maxUsed, ', maxAllocated = ', maxAllocated);
|
|
|
|
+ fix := fullOS;
|
|
|
|
+ if Assigned(fix) then
|
|
|
|
+ begin
|
|
|
|
+ writeln(f);
|
|
|
|
+ repeat
|
|
|
|
+ writeln(f, 'Full fixed: size = ', fix^.size, ', usedSize = ', fix^.usedSize, ', formattedSize = ', fix^.formattedSize, ', fullThreshold = ', fix^.fullThreshold);
|
|
|
|
+ fix := fix^.next;
|
|
|
|
+ until not Assigned(fix);
|
|
|
|
+ end;
|
|
|
|
+ for i := 0 to FixedSizesCount - 1 do
|
|
|
|
+ if Assigned(fixedPartialOS[i]) then
|
|
|
|
+ begin
|
|
|
|
+ write(f, 'Size #', i, ' (', IndexToSize(i), ')');
|
|
|
|
+ if allocatedByFullFixed[i] <> 0 then
|
|
|
|
+ write(f, ': allocatedByFullFixed = ', allocatedByFullFixed[i]);
|
|
|
|
+ writeln(f);
|
|
|
|
+ fix := fixedPartialOS[i];
|
|
|
|
+ while Assigned(fix) do
|
|
|
|
+ begin
|
|
|
|
+ writeln(f, 'size = ', fix^.size, ', usedSize = ', fix^.usedSize, ', formattedSize = ', fix^.formattedSize, ', fullThreshold = ', fix^.fullThreshold);
|
|
|
|
+ fix := fix^.next;
|
|
|
|
+ end;
|
|
|
|
+ end;
|
|
|
|
+ vOs := varOS;
|
|
|
|
+ while Assigned(vOs) do
|
|
|
|
+ begin
|
|
|
|
+ writeln(f, LineEnding, 'Var OS chunk, size ', vOs^.size);
|
|
|
|
+ p := pointer(vOs) + (VarOSChunkDataOffset + VarHeaderSize);
|
|
|
|
+ repeat
|
|
|
|
+ write(f, HexStr(p), ': ',
|
|
|
|
+ 'prevSize = ', pVarHeader(p - VarHeaderSize)^.prevSize, ', size = ',
|
|
|
|
+ {$if sizeof(SizeUint) > 4} SizeUint(pVarHeader(p - VarHeaderSize)^.sizeHi) shl 32 or {$endif} pVarHeader(p - VarHeaderSize)^.ch.h and VarSizeMask);
|
|
|
|
+ if pVarHeader(p - VarHeaderSize)^.ch.h and UsedFlag <> 0 then
|
|
|
|
+ write(f, ', used')
|
|
|
|
+ else
|
|
|
|
+ write(f, ', f r e e');
|
|
|
|
+ if pVarHeader(p - VarHeaderSize)^.ch.h and FirstFlag <> 0 then
|
|
|
|
+ write(f, ', first');
|
|
|
|
+ if pVarHeader(p - VarHeaderSize)^.ch.h and LastFlag <> 0 then
|
|
|
|
+ write(f, ', last');
|
|
|
|
+ writeln(f);
|
|
|
|
+ if pVarHeader(p - VarHeaderSize)^.ch.h and LastFlag <> 0 then
|
|
|
|
+ break;
|
|
|
|
+ p := p + ({$if sizeof(SizeUint) > 4} SizeUint(pVarHeader(p - VarHeaderSize)^.sizeHi) shl 32 or {$endif} pVarHeader(p - VarHeaderSize)^.ch.h and uint32(VarSizeMask));
|
|
|
|
+ until false;
|
|
|
|
+ vOs := vOs^.next;
|
|
|
|
+ end;
|
|
|
|
+ fr := freeOS.first;
|
|
|
|
+ if Assigned(fr) then
|
|
|
|
+ begin
|
|
|
|
+ writeln(f);
|
|
|
|
+ repeat
|
|
|
|
+ writeln(f, 'Free OS: ', HexStr(fr), ', size = ', fr^.size);
|
|
|
|
+ fr := fr^.next;
|
|
|
|
+ until not Assigned(fr);
|
|
|
|
+ end;
|
|
|
|
+ vf := varFree;
|
|
|
|
+ if Assigned(vf) then
|
|
|
|
+ begin
|
|
|
|
+ write(f, LineEnding, 'Var free:');
|
|
|
|
+ repeat
|
|
|
|
+ write(f, ' ', vf^.size);
|
|
|
|
+ vf := vf^.next;
|
|
|
|
+ until not Assigned(vf);
|
|
|
|
+ writeln(f);
|
|
|
|
+ end;
|
|
|
|
+ {$ifdef FPC_HAS_FEATURE_THREADING}
|
|
|
|
+ tf := toFree;
|
|
|
|
+ if Assigned(tf) then
|
|
|
|
+ begin
|
|
|
|
+ write(f, LineEnding, 'To-free:');
|
|
|
|
+ repeat
|
|
|
|
+ if pCommonHeader(pointer(tf) - CommonHeaderSize)^.h and FixedFlag <> 0 then
|
|
|
|
+ write(f, ' f ', CommonHeaderSize + SysMemSize(tf))
|
|
|
|
+ else
|
|
|
|
+ write(f, ' v ', VarHeaderSize + SysMemSize(tf));
|
|
|
|
+ tf := tf^.next;
|
|
|
|
+ until not Assigned(tf);
|
|
|
|
+ writeln(f);
|
|
|
|
+ end;
|
|
|
|
+ {$endif FPC_HAS_FEATURE_THREADING}
|
|
|
|
+ end;
|
|
|
|
+{$endif DEBUG_HEAP_INC}
|
|
|
|
+
|
|
|
|
+ function HeapInc.ThreadState.AllocFixed(size: SizeUint): pointer;
|
|
|
|
+ var
|
|
|
|
+ sizeIndex: SizeUint;
|
|
|
|
+ osChunk, osNext: pFixedOSChunk;
|
|
|
|
+ begin
|
|
|
|
+ sizeIndex := SizeMinus1ToIndex(size + (CommonHeaderSize - 1));
|
|
|
|
+
|
|
|
|
+ osChunk := fixedPartialOS[sizeIndex];
|
|
|
|
+ if not Assigned(osChunk) then
|
|
|
|
+ begin
|
|
|
|
+ {$ifdef FPC_HAS_FEATURE_THREADING}
|
|
|
|
+ if Assigned(toFree) then
|
|
|
|
+ begin
|
|
|
|
+ FlushToFree;
|
|
|
|
+ osChunk := fixedPartialOS[sizeIndex];
|
|
|
|
+ end;
|
|
|
|
+ if not Assigned(osChunk) then
|
|
|
|
+ {$endif FPC_HAS_FEATURE_THREADING}
|
|
|
|
+ begin
|
|
|
|
+ osChunk := pFixedOSChunk(GetOSChunk(FixedOSChunkDataOffset + MaxFixedHeaderAndPayload, MaxFixedChunkSize, sizeIndex));
|
|
|
|
+ if not Assigned(osChunk) then
|
|
|
|
+ exit(nil);
|
|
|
|
+ if SizeUint(osChunk^.sizeIndex) = sizeIndex then
|
|
|
|
+ { Lucky! Just don’t reset the chunk and use its old freelist. }
|
|
|
|
+ else
|
|
|
|
+ begin
|
|
|
|
+ osChunk^.sizeIndex := sizeIndex;
|
|
|
|
+ osChunk^.firstFreeChunk := nil;
|
|
|
|
+ osChunk^.usedSize := 0;
|
|
|
|
+ osChunk^.formattedSize := 0;
|
|
|
|
+ osChunk^.fullThreshold := osChunk^.size - IndexToSize(sizeIndex) - (FixedOSChunkDataOffset - 1); { available OS chunk space - chunk size + 1. }
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ { Add osChunk to fixedPartialOS[sizeIndex]. }
|
|
|
|
+ osNext := fixedPartialOS[sizeIndex];
|
|
|
|
+ osChunk^.prev := nil;
|
|
|
|
+ osChunk^.next := osNext;
|
|
|
|
+ if Assigned(osNext) then
|
|
|
|
+ osNext^.prev := osChunk;
|
|
|
|
+ fixedPartialOS[sizeIndex] := osChunk;
|
|
|
|
+ end;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ size := IndexToSize(sizeIndex);
|
|
|
|
+ inc(used, size);
|
|
|
|
+ if used > maxUsed then
|
|
|
|
+ maxUsed := used;
|
|
|
|
+
|
|
|
|
+ { osChunk from the fixedPartialOS list has either free chunk or free unformatted space for a new chunk. }
|
|
|
|
+ result := osChunk^.firstFreeChunk;
|
|
|
|
+ if Assigned(result) then
|
|
|
|
+ osChunk^.firstFreeChunk := pFreeChunk(result)^.next
|
|
|
|
+ else
|
|
|
|
+ begin
|
|
|
|
+ result := pointer(osChunk) + (FixedOSChunkDataOffset + CommonHeaderSize) + osChunk^.formattedSize;
|
|
|
|
+ pCommonHeader(result - CommonHeadersize)^.h := sizeIndex + osChunk^.formattedSize shl ChunkOffsetShift +
|
|
|
|
+ (FixedFlag + (FixedOSChunkDataOffset + CommonHeaderSize) shl ChunkOffsetShift); { ← const }
|
|
|
|
+ inc(osChunk^.formattedSize, size);
|
|
|
|
+ end;
|
|
|
|
+ inc(osChunk^.usedSize, size);
|
|
|
|
+ if osChunk^.usedSize >= osChunk^.fullThreshold then
|
|
|
|
+ begin
|
|
|
|
+ inc(allocatedByFullFixed[sizeIndex], osChunk^.size);
|
|
|
|
+ { Remove osChunk from fixedPartialOS[sizeIndex], add to fullOS. }
|
|
|
|
+ osNext := osChunk^.next;
|
|
|
|
+ fixedPartialOS[sizeIndex] := osNext;
|
|
|
|
+ if Assigned(osNext) then
|
|
|
|
+ osNext^.prev := nil;
|
|
|
|
+ osNext := fullOS;
|
|
|
|
+ osChunk^.next := osNext; { osChunk^.prev is already nil because osChunk was the first item of fixedPartialOS[sizeIndex]. }
|
|
|
|
+ if Assigned(osNext) then
|
|
|
|
+ osNext^.prev := osChunk;
|
|
|
|
+ fullOS := osChunk;
|
|
|
|
+ end;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ function HeapInc.ThreadState.FreeFixed(p: pointer): SizeUint;
|
|
|
|
+ var
|
|
|
|
+ sizeIndex, usedSize: SizeUint;
|
|
|
|
+ osChunk, osPrev, osNext: pFixedOSChunk;
|
|
|
|
+ freeOsNext: pFreeOSChunk;
|
|
|
|
+ {$ifdef FPC_HAS_FEATURE_THREADING}
|
|
|
|
+ chunkTs: pThreadState;
|
|
|
|
+ {$endif}
|
|
|
|
+ begin
|
|
|
|
+ osChunk := p - pCommonHeader(p - CommonHeaderSize)^.h shr ChunkOffsetShift;
|
|
|
|
+
|
|
|
|
+ {$ifdef FPC_HAS_FEATURE_THREADING}
|
|
|
|
+ { This can be checked without blocking; osChunk^.threadState can only change from one value not equal to @self to another value not equal to @self. }
|
|
|
|
+ if osChunk^.threadState <> @self then
|
|
|
|
+ begin
|
|
|
|
+ EnterCriticalSection(gs.lock);
|
|
|
|
+ chunkTs := osChunk^.threadState;
|
|
|
|
+ if Assigned(chunkTs) then
|
|
|
|
+ begin
|
|
|
|
+ { Despite atomic Push lock must be held as otherwise target thread might end and destroy chunkTs.
|
|
|
|
+ However, target thread won’t block to free p, so PushToFree instantly invalidates p. }
|
|
|
|
+ result := IndexToSize(pCommonHeader(p - CommonHeaderSize)^.h and SizeIndexMask) - CommonHeaderSize;
|
|
|
|
+ chunkTs^.PushToFree(p);
|
|
|
|
+ LeaveCriticalSection(gs.lock);
|
|
|
|
+ exit;
|
|
|
|
+ end;
|
|
|
|
+ Adopt(osChunk); { ...And continue! }
|
|
|
|
+ LeaveCriticalSection(gs.lock);
|
|
|
|
+ end;
|
|
|
|
+ {$endif FPC_HAS_FEATURE_THREADING}
|
|
|
|
+
|
|
|
|
+ pFreeChunk(p)^.next := osChunk^.firstFreeChunk;
|
|
|
|
+ osChunk^.firstFreeChunk := p;
|
|
|
|
+ sizeIndex := pCommonHeader(p - CommonHeaderSize)^.h and SizeIndexMask;
|
|
|
|
+ result := IndexToSize(sizeIndex);
|
|
|
|
+ dec(used, result);
|
|
|
|
+ usedSize := osChunk^.usedSize;
|
|
|
|
+ if usedSize >= osChunk^.fullThreshold then
|
|
|
|
+ begin
|
|
|
|
+ dec(allocatedByFullFixed[sizeIndex], osChunk^.size);
|
|
|
|
+
|
|
|
|
+ { Remove osChunk from fullOS, add to fixedPartialOS[sizeIndex]. }
|
|
|
|
+ osPrev := osChunk^.prev;
|
|
|
|
+ osNext := osChunk^.next;
|
|
|
|
+ if Assigned(osPrev) then
|
|
|
|
+ osPrev^.next := osNext
|
|
|
|
+ else
|
|
|
|
+ fullOS := osNext;
|
|
|
|
+ if Assigned(osNext) then
|
|
|
|
+ osNext^.prev := osPrev;
|
|
|
|
+ osChunk^.prev := nil;
|
|
|
|
+ osNext := fixedPartialOS[sizeIndex];
|
|
|
|
+ osChunk^.next := osNext;
|
|
|
|
+ if Assigned(osNext) then
|
|
|
|
+ osNext^.prev := osChunk;
|
|
|
|
+ fixedPartialOS[sizeIndex] := osChunk;
|
|
|
|
+ end;
|
|
|
|
+ dec(usedSize, result);
|
|
|
|
+ osChunk^.usedSize := usedSize;
|
|
|
|
+ if usedSize = 0 then
|
|
|
|
+ begin
|
|
|
|
+ { Remove osChunk from fixedPartialOS[sizeIndex], add to freeOS. }
|
|
|
|
+ osPrev := osChunk^.prev;
|
|
|
|
+ osNext := osChunk^.next;
|
|
|
|
+ if Assigned(osPrev) then
|
|
|
|
+ osPrev^.next := osNext
|
|
|
|
+ else
|
|
|
|
+ fixedPartialOS[sizeIndex] := osNext;
|
|
|
|
+ if Assigned(osNext) then
|
|
|
|
+ osNext^.prev := osPrev;
|
|
|
|
+
|
|
|
|
+ freeOsNext := freeOS.first;
|
|
|
|
+ pFreeOSChunk(osChunk)^.prev := nil;
|
|
|
|
+ pFreeOSChunk(osChunk)^.next := freeOsNext;
|
|
|
|
+ if Assigned(freeOsNext) then
|
|
|
|
+ freeOsNext^.prev := osChunk
|
|
|
|
+ else
|
|
|
|
+ freeOS.last := pFreeOSChunk(osChunk);
|
|
|
|
+ freeOS.first := pFreeOSChunk(osChunk);
|
|
|
|
+ {$ifdef HAS_SYSOSFREE}
|
|
|
|
+ inc(freeOS.n);
|
|
|
|
+ if freeOS.n > MaxKeptOSChunks then
|
|
|
|
+ dec(allocated, freeOS.FreeOne);
|
|
|
|
+ {$endif}
|
|
|
|
+ end;
|
|
|
|
+ dec(result, CommonHeaderSize);
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ function HeapInc.ThreadState.GetOSChunk(minSize, maxSize: SizeUint; sizeIndex: SizeInt): pOSChunk;
|
|
|
|
+ begin
|
|
|
|
+ result := freeOS.Get(minSize, maxSize);
|
|
|
|
+ if Assigned(result) then
|
|
|
|
+ exit;
|
|
|
|
+ {$ifdef FPC_HAS_FEATURE_THREADING}
|
|
|
|
+ if Assigned(gs.freeOS.first) then { Racing precheck. }
|
|
|
|
+ begin
|
|
|
|
+ EnterCriticalSection(gs.lock);
|
|
|
|
+ result := gs.freeOS.Get(minSize, maxSize);
|
|
|
|
+ LeaveCriticalSection(gs.lock);
|
|
|
|
+ if Assigned(result) then
|
|
|
|
+ begin
|
|
|
|
+ result^.threadState := @self;
|
|
|
|
+ inc(allocated, result^.size);
|
|
|
|
+ if allocated > maxAllocated then
|
|
|
|
+ maxAllocated := allocated;
|
|
|
|
+ exit;
|
|
|
|
+ end;
|
|
|
|
+ end;
|
|
|
|
+ {$endif FPC_HAS_FEATURE_THREADING}
|
|
|
|
+ result := AllocateOSChunk(minSize, sizeIndex);
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ function HeapInc.ThreadState.AllocateOSChunk(minSize: SizeUint; sizeIndex: SizeInt): pOSChunk;
|
|
|
|
+ var
|
|
|
|
+ preferredSize: SizeUint;
|
|
|
|
+ begin
|
|
|
|
+ if sizeIndex < 0 then
|
|
|
|
+ begin
|
|
|
|
+ if minSize <= GrowHeapSize1 then { 256K by default. }
|
|
|
|
+ preferredSize := GrowHeapSize1
|
|
|
|
+ else if minSize <= GrowHeapSize2 then { 1M by default. }
|
|
|
|
+ preferredSize := GrowHeapSize2
|
|
|
|
+ else
|
|
|
|
+ preferredSize := (minSize + (OSChunkVarSizeQuant - 1)) and SizeUint(-OSChunkVarSizeQuant);
|
|
|
|
+ end else
|
|
|
|
+ begin
|
|
|
|
+ preferredSize := (allocatedByFullFixed[sizeIndex] div 8 + (OSChunkFixedSizeQuant - 1)) and SizeUint(-OSChunkFixedSizeQuant); { 12.5% of memory allocated by the size. }
|
|
|
|
+ if preferredSize < growheapsizesmall then
|
|
|
|
+ preferredSize := growheapsizesmall;
|
|
|
|
+ if preferredSize > MaxFixedChunkSize then
|
|
|
|
+ preferredSize := MaxFixedChunkSize;
|
|
|
|
+ end;
|
|
|
|
+ result := SysOSAlloc(preferredSize);
|
|
|
|
+ if not Assigned(result) and (preferredSize > minSize) then
|
|
|
|
+ begin
|
|
|
|
+ preferredSize := minSize;
|
|
|
|
+ result := SysOSAlloc(preferredSize);
|
|
|
|
+ end;
|
|
|
|
+ if not Assigned(result) then
|
|
|
|
+ if ReturnNilIfGrowHeapFails then
|
|
|
|
+ exit
|
|
|
|
+ else
|
|
|
|
+ HandleError(204);
|
|
|
|
+ inc(allocated, preferredSize);
|
|
|
|
+ if allocated > maxAllocated then
|
|
|
|
+ maxAllocated := allocated;
|
|
|
|
+ result^.size := preferredSize;
|
|
|
|
+ {$ifdef FPC_HAS_FEATURE_THREADING}
|
|
|
|
+ result^.threadState := @self;
|
|
|
|
+ {$endif}
|
|
|
|
+ result^.sizeIndex := -2; { Neither −1 nor ≥0. }
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ function HeapInc.ThreadState.AllocVar(size: SizeUint): pointer;
|
|
|
|
+ var
|
|
|
|
+ fv, fv2: pFreeVarChunk;
|
|
|
|
+ osChunk, osNext: pVarOSChunk;
|
|
|
|
+ varPrev, varNext: pFreeVarChunk;
|
|
|
|
+ vSize, minSize, maxSize: SizeUint;
|
|
|
|
+ {$if MatchEffort >= 0} fv2Size: SizeUint; {$endif}
|
|
|
|
+ {$if MatchEffort > 1} triesLeft: uint32; {$endif}
|
|
|
|
+ begin
|
|
|
|
+ if size > MaxVarPayload then
|
|
|
|
+ if ReturnNilIfGrowHeapFails then
|
|
|
|
+ exit(nil)
|
|
|
|
+ else
|
|
|
|
+ HandleError(204);
|
|
|
|
+ size := (size + (VarHeaderSize + VarSizeQuant - 1)) and SizeUint(-VarSizeQuant);
|
|
|
|
+
|
|
|
|
+ {$ifdef FPC_HAS_FEATURE_THREADING}
|
|
|
|
+ if Assigned(toFree) then
|
|
|
|
+ FlushToFree;
|
|
|
|
+ {$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}
|
|
|
|
+
|
|
|
|
+ if Assigned(fv) then
|
|
|
|
+ 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;
|
|
|
|
+ end else
|
|
|
|
+ begin
|
|
|
|
+ minSize := VarOSChunkDataOffset + size;
|
|
|
|
+ if minSize <= GrowHeapSize1 then
|
|
|
|
+ maxSize := GrowHeapSize1
|
|
|
|
+ else if minSize <= GrowHeapSize2 then
|
|
|
|
+ maxSize := GrowHeapSize2
|
|
|
|
+ else
|
|
|
|
+ maxSize := High(SizeUint);
|
|
|
|
+ osChunk := pVarOSChunk(GetOSChunk(minSize, maxSize, -1));
|
|
|
|
+ if not Assigned(osChunk) then
|
|
|
|
+ exit(nil);
|
|
|
|
+ osChunk^.sizeIndex := -1;
|
|
|
|
+
|
|
|
|
+ { Add osChunk to varOS. }
|
|
|
|
+ osNext := varOS;
|
|
|
|
+ osChunk^.prev := nil;
|
|
|
|
+ osChunk^.next := osNext;
|
|
|
|
+ if Assigned(osNext) then
|
|
|
|
+ osNext^.prev := osChunk;
|
|
|
|
+ varOS := osChunk;
|
|
|
|
+
|
|
|
|
+ { Format new free var chunk spanning the entire osChunk (but don’t add to varFree, it is expected to be removed). }
|
|
|
|
+ fv := pointer(osChunk) + (VarOSChunkDataOffset + VarHeaderSize);
|
|
|
|
+ pVarHeader(pointer(fv) - VarHeaderSize)^.prevSize := 0;
|
|
|
|
+ {$ifdef FPC_HAS_FEATURE_THREADING}
|
|
|
|
+ pVarHeader(pointer(fv) - VarHeaderSize)^.threadState := @self;
|
|
|
|
+ {$endif}
|
|
|
|
+ vSize := SizeUint(osChunk^.size - VarOSChunkDataOffset) and SizeUint(-VarSizeQuant);
|
|
|
|
+ {$if sizeof(SizeUint) > 4}
|
|
|
|
+ pVarHeader(pointer(fv) - VarHeaderSize)^.sizeHi := vSize shr 32;
|
|
|
|
+ {$endif}
|
|
|
|
+ pVarHeader(pointer(fv) - VarHeaderSize)^.ch.h := uint32(vSize) or (FirstFlag or LastFlag);
|
|
|
|
+ fv^.size := vSize;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ { Result will be allocated at the beginning of fv; maybe format the remainder and push it back to varFree. }
|
|
|
|
+ result := fv;
|
|
|
|
+ vSize := fv^.size - size;
|
|
|
|
+ if (vSize > MaxFixedHeaderAndPayload) or
|
|
|
|
+ { If fv is last in the OS chunk, remainder ≤ MaxFixedHeaderAndPayload is guaranteedly wasted.
|
|
|
|
+ If fv is not last, there is a hope that occupied chunk to the right might get freed one day and merge with the remainder. }
|
|
|
|
+ (vSize >= MinVarHeaderAndPayload) and (pVarHeader(result - VarHeaderSize)^.ch.h and LastFlag = 0) then
|
|
|
|
+ begin
|
|
|
|
+ inc(pointer(fv), size); { result = allocated block, fv = remainder. }
|
|
|
|
+ pVarHeader(pointer(fv) - VarHeaderSize)^.prevSize := size;
|
|
|
|
+ {$ifdef FPC_HAS_FEATURE_THREADING}
|
|
|
|
+ pVarHeader(pointer(fv) - VarHeaderSize)^.threadState := @self;
|
|
|
|
+ {$endif}
|
|
|
|
+ {$if sizeof(SizeUint) > 4}
|
|
|
|
+ pVarHeader(pointer(fv) - VarHeaderSize)^.sizeHi := vSize shr 32;
|
|
|
|
+ {$endif}
|
|
|
|
+ { 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);
|
|
|
|
+ fv^.size := vSize;
|
|
|
|
+ if pVarHeader(result - VarHeaderSize)^.ch.h and LastFlag = 0 then
|
|
|
|
+ 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;
|
|
|
|
+
|
|
|
|
+ { 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);
|
|
|
|
+ end else
|
|
|
|
+ begin
|
|
|
|
+ { Use the entire chunk. }
|
|
|
|
+ size := fv^.size;
|
|
|
|
+ pVarHeader(result - VarHeaderSize)^.ch.h := pVarHeader(result - VarHeaderSize)^.ch.h and (FirstFlag or LastFlag) or UsedFlag or uint32(size);
|
|
|
|
+ end;
|
|
|
|
+ {$if sizeof(SizeUint) > 4}
|
|
|
|
+ pVarHeader(result - VarHeaderSize)^.sizeHi := size shr 32;
|
|
|
|
+ {$endif}
|
|
|
|
+ inc(used, size);
|
|
|
|
+ if used > maxUsed then
|
|
|
|
+ maxUsed := used;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ function HeapInc.ThreadState.FreeVar(p: pointer): SizeUint;
|
|
|
|
+ var
|
|
|
|
+ {$ifdef FPC_HAS_FEATURE_THREADING}
|
|
|
|
+ chunkTs: pThreadState;
|
|
|
|
+ {$endif}
|
|
|
|
+ varPrev, varNext: pFreeVarChunk;
|
|
|
|
+ p2: pointer;
|
|
|
|
+ fSizeFlags: SizeUint;
|
|
|
|
+ osChunk, osPrev, osNext: pVarOSChunk;
|
|
|
|
+ freeOsNext: pFreeOSChunk;
|
|
|
|
+ begin
|
|
|
|
+ {$ifdef FPC_HAS_FEATURE_THREADING}
|
|
|
|
+ chunkTs := pVarHeader(p - VarHeaderSize)^.threadState;
|
|
|
|
+ if chunkTs <> @self then
|
|
|
|
+ begin
|
|
|
|
+ EnterCriticalSection(gs.lock);
|
|
|
|
+ chunkTs := pVarHeader(p - VarHeaderSize)^.threadState;
|
|
|
|
+ if Assigned(chunkTs) then
|
|
|
|
+ begin
|
|
|
|
+ { Despite atomic Push lock must be held as otherwise target thread might end and destroy chunkTs.
|
|
|
|
+ However, target thread won’t block to free p, so PushToFree instantly invalidates p. }
|
|
|
|
+ result := {$if sizeof(SizeUint) > 4} SizeUint(pVarHeader(p - VarHeaderSize)^.sizeHi) shl 32 or {$endif} pVarHeader(p - VarHeaderSize)^.ch.h and uint32(VarSizeMask) - VarHeaderSize;
|
|
|
|
+ chunkTs^.PushToFree(p);
|
|
|
|
+ LeaveCriticalSection(gs.lock);
|
|
|
|
+ exit;
|
|
|
|
+ end;
|
|
|
|
+ AdoptVarOwner(p); { ...And continue! }
|
|
|
|
+ LeaveCriticalSection(gs.lock);
|
|
|
|
+ end;
|
|
|
|
+ {$endif FPC_HAS_FEATURE_THREADING}
|
|
|
|
+
|
|
|
|
+ fSizeFlags := {$if sizeof(SizeUint) > 4} SizeUint(pVarHeader(p - VarHeaderSize)^.sizeHi) shl 32 or {$endif} pVarHeader(p - VarHeaderSize)^.ch.h;
|
|
|
|
+ result := fSizeFlags and VarSizeMask;
|
|
|
|
+ dec(used, result);
|
|
|
|
+
|
|
|
|
+ { If next/prev are free, remove them from varFree and merge with f — (f)uture (f)ree chunk that starts at p, has fSizeFlags,
|
|
|
|
+ and conveniently always inherits prevSize of its final location. }
|
|
|
|
+ if fSizeFlags and LastFlag = 0 then
|
|
|
|
+ begin
|
|
|
|
+ p2 := p + result;
|
|
|
|
+ if pVarHeader(p2 - VarHeaderSize)^.ch.h and UsedFlag = 0 then
|
|
|
|
+ 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;
|
|
|
|
+ end;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ if fSizeFlags and FirstFlag = 0 then
|
|
|
|
+ begin
|
|
|
|
+ p2 := p - pVarHeader(p - VarHeaderSize)^.prevSize;
|
|
|
|
+ if pVarHeader(p2 - VarHeaderSize)^.ch.h and UsedFlag = 0 then
|
|
|
|
+ begin
|
|
|
|
+ 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;
|
|
|
|
+ end;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ { Turn p into a free chunk and add it back to varFree...
|
|
|
|
+ unless it spans the entire OS chunk, in which case instead move the chunk from varOS to freeOS. }
|
|
|
|
+ if fSizeFlags and (FirstFlag or LastFlag) <> FirstFlag or LastFlag then
|
|
|
|
+ begin
|
|
|
|
+ if fSizeFlags and LastFlag = 0 then
|
|
|
|
+ pVarHeader(p + fSizeFlags and VarSizeMask - VarHeaderSize)^.prevSize := fSizeFlags and VarSizeMask;
|
|
|
|
+
|
|
|
|
+ {$if sizeof(SizeUint) > 4}
|
|
|
|
+ pVarHeader(p - VarHeaderSize)^.sizeHi := fSizeFlags shr 32;
|
|
|
|
+ {$endif}
|
|
|
|
+ pVarHeader(p - VarHeaderSize)^.ch.h := uint32(fSizeFlags) xor UsedFlag;
|
|
|
|
+ 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;
|
|
|
|
+ end else
|
|
|
|
+ begin
|
|
|
|
+ osChunk := p - (VarOSChunkDataOffset + VarHeaderSize);
|
|
|
|
+
|
|
|
|
+ { Remove osChunk from varOS. }
|
|
|
|
+ osPrev := osChunk^.prev;
|
|
|
|
+ osNext := osChunk^.next;
|
|
|
|
+ if Assigned(osPrev) then
|
|
|
|
+ osPrev^.next := osNext
|
|
|
|
+ else
|
|
|
|
+ varOS := osNext;
|
|
|
|
+ if Assigned(osNext) then
|
|
|
|
+ osNext^.prev := osPrev;
|
|
|
|
+
|
|
|
|
+ { Instantly free if huge. }
|
|
|
|
+ {$ifdef HAS_SYSOSFREE}
|
|
|
|
+ if osChunk^.size > GrowHeapSize2 then
|
|
|
|
+ begin
|
|
|
|
+ dec(allocated, osChunk^.size);
|
|
|
|
+ SysOSFree(osChunk, osChunk^.size);
|
|
|
|
+ end else
|
|
|
|
+ {$endif}
|
|
|
|
+ begin
|
|
|
|
+ { Add to freeOS. }
|
|
|
|
+ freeOsNext := freeOS.first;
|
|
|
|
+ osChunk^.prev := nil;
|
|
|
|
+ osChunk^.next := freeOsNext;
|
|
|
|
+ if Assigned(freeOsNext) then
|
|
|
|
+ freeOsNext^.prev := osChunk
|
|
|
|
+ else
|
|
|
|
+ freeOS.last := pFreeOSChunk(osChunk);
|
|
|
|
+ freeOS.first := pFreeOSChunk(osChunk);
|
|
|
|
+ {$ifdef HAS_SYSOSFREE}
|
|
|
|
+ inc(freeOS.n);
|
|
|
|
+ if freeOS.n > MaxKeptOSChunks then
|
|
|
|
+ dec(allocated, freeOS.FreeOne);
|
|
|
|
+ {$endif}
|
|
|
|
+ end;
|
|
|
|
+ end;
|
|
|
|
+ dec(result, VarHeaderSize);
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ function HeapInc.ThreadState.TryResizeVar(p: pointer; size: SizeUint): pointer;
|
|
|
|
+ var
|
|
|
|
+ fp, p2: pointer;
|
|
|
|
+ oldpsize, fSizeFlags, growby: SizeUint;
|
|
|
|
+ varNext, varPrev: pFreeVarChunk;
|
|
|
|
+ begin
|
|
|
|
+ if (size < MinVarHeaderAndPayload - VarHeaderSize) or (size > MaxVarPayload)
|
|
|
|
+ {$ifdef FPC_HAS_FEATURE_THREADING}
|
|
|
|
+ or (pVarHeader(p - VarHeaderSize)^.threadState <> @self)
|
|
|
|
+ {$endif}
|
|
|
|
+ then
|
|
|
|
+ exit(nil);
|
|
|
|
+ size := (size + (VarHeaderSize + VarSizeQuant - 1)) and SizeUint(-VarSizeQuant);
|
|
|
|
+ result := p; { From now on use result instead of p (saves a register). }
|
|
|
|
+
|
|
|
|
+ oldpsize := {$if sizeof(SizeUint) > 4} SizeUint(pVarHeader(result - VarHeaderSize)^.sizeHi) shl 32 or {$endif} pVarHeader(result - VarHeaderSize)^.ch.h and uint32(VarSizeMask);
|
|
|
|
+ p2 := result + oldpsize;
|
|
|
|
+ { (f)uture (f)ree chunk starting at p + size and having fSizeFlags will be created at the end, must exit before that if not required. }
|
|
|
|
+ if size <= oldpsize then
|
|
|
|
+ begin
|
|
|
|
+ { Shrink. Maybe. }
|
|
|
|
+ fSizeFlags := oldpsize - size;
|
|
|
|
+
|
|
|
|
+ if pVarHeader(result - VarHeaderSize)^.ch.h and LastFlag <> 0 then
|
|
|
|
+ begin
|
|
|
|
+ { Has nothing to the right: create free chunk if > MaxFixedHeaderAndPayload, otherwise report success but change nothing. }
|
|
|
|
+ if fSizeFlags <= MaxFixedHeaderAndPayload then
|
|
|
|
+ exit;
|
|
|
|
+ dec(used, fSizeFlags);
|
|
|
|
+ fSizeFlags := fSizeFlags or LastFlag;
|
|
|
|
+ end
|
|
|
|
+ else if pVarHeader(p2 - VarHeaderSize)^.ch.h and UsedFlag <> 0 then
|
|
|
|
+ begin
|
|
|
|
+ { Has used chunk to the right: create free chunk if ≥ MinVarHeaderAndPayload, following the same logic as in AllocVar. }
|
|
|
|
+ if fSizeFlags < MinVarHeaderAndPayload then
|
|
|
|
+ exit;
|
|
|
|
+ dec(used, fSizeFlags);
|
|
|
|
+ end else
|
|
|
|
+ begin
|
|
|
|
+ dec(used, fSizeFlags);
|
|
|
|
+ { 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;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ { Update p size. }
|
|
|
|
+ {$if sizeof(SizeUint) > 4}
|
|
|
|
+ pVarHeader(result - VarHeaderSize)^.sizeHi := size shr 32;
|
|
|
|
+ {$endif}
|
|
|
|
+ pVarHeader(result - VarHeaderSize)^.ch.h := uint32(size) or pVarHeader(result - VarHeaderSize)^.ch.h and (FirstFlag or UsedFlag);
|
|
|
|
+ end
|
|
|
|
+ { 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
|
|
|
|
+ (pFreeVarChunk(p2)^.size >= size - oldpsize) then
|
|
|
|
+ begin
|
|
|
|
+ fSizeFlags := pFreeVarChunk(p2)^.size - (size - oldpsize);
|
|
|
|
+ if pVarHeader(p2 - VarHeaderSize)^.ch.h and LastFlag = 0 then
|
|
|
|
+ begin
|
|
|
|
+ if fSizeFlags <= MaxFixedHeaderAndPayload then
|
|
|
|
+ fSizeFlags := 0;
|
|
|
|
+ end else
|
|
|
|
+ if fSizeFlags < MinVarHeaderAndPayload then
|
|
|
|
+ fSizeFlags := 0
|
|
|
|
+ else
|
|
|
|
+ fSizeFlags := fSizeFlags or LastFlag;
|
|
|
|
+
|
|
|
|
+ growby := pFreeVarChunk(p2)^.size - fSizeFlags and VarSizeMask;
|
|
|
|
+ size := oldpsize + growby;
|
|
|
|
+ inc(used, growby);
|
|
|
|
+ if used > maxUsed then
|
|
|
|
+ maxUsed := used;
|
|
|
|
+
|
|
|
|
+ { 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;
|
|
|
|
+
|
|
|
|
+ { Update p size. }
|
|
|
|
+ {$if sizeof(SizeUint) > 4}
|
|
|
|
+ pVarHeader(result - VarHeaderSize)^.sizeHi := size shr 32;
|
|
|
|
+ {$endif}
|
|
|
|
+ pVarHeader(result - VarHeaderSize)^.ch.h := uint32(size) or pVarHeader(result - VarHeaderSize)^.ch.h and (FirstFlag or UsedFlag);
|
|
|
|
+ { No empty chunk? }
|
|
|
|
+ if fSizeFlags = 0 then
|
|
|
|
+ begin
|
|
|
|
+ if pVarHeader(p2 - VarHeaderSize)^.ch.h and LastFlag <> 0 then
|
|
|
|
+ pVarHeader(result - VarHeaderSize)^.ch.h := pVarHeader(result - VarHeaderSize)^.ch.h or LastFlag
|
|
|
|
+ else
|
|
|
|
+ pVarHeader(result + size - VarHeaderSize)^.prevSize := size;
|
|
|
|
+ exit;
|
|
|
|
+ end;
|
|
|
|
+ end
|
|
|
|
+ {$ifdef HAS_SYSOSREALLOC}
|
|
|
|
+ else if (oldpsize >= 64 * 1024) and { Don’t do SysOSRealloc if the source is under 64 Kb (arbitrary value). }
|
|
|
|
+ (pVarHeader(result - VarHeaderSize)^.ch.h and FirstFlag <> 0) and
|
|
|
|
+ ((pVarHeader(result - VarHeaderSize)^.ch.h and LastFlag <> 0) or (pVarHeader(p2 - VarHeaderSize)^.ch.h and (LastFlag or UsedFlag) = LastFlag)) then
|
|
|
|
+ exit(TrySysOSRealloc(result, oldpsize, size))
|
|
|
|
+ {$endif}
|
|
|
|
+ else
|
|
|
|
+ exit(nil);
|
|
|
|
+
|
|
|
|
+ { Format new free var chunk. }
|
|
|
|
+ fp := result + size;
|
|
|
|
+ pVarHeader(fp - VarHeaderSize)^.prevSize := size;
|
|
|
|
+ {$ifdef FPC_HAS_FEATURE_THREADING}
|
|
|
|
+ pVarHeader(fp - VarHeaderSize)^.threadState := @self;
|
|
|
|
+ {$endif}
|
|
|
|
+ {$if sizeof(SizeUint) > 4}
|
|
|
|
+ pVarHeader(fp - VarHeaderSize)^.sizeHi := fSizeFlags shr 32;
|
|
|
|
+ {$endif}
|
|
|
|
+ pVarHeader(fp - VarHeaderSize)^.ch.h := uint32(fSizeFlags);
|
|
|
|
+ pFreeVarChunk(fp)^.size := fSizeFlags and VarSizeMask;
|
|
|
|
+ if fSizeFlags and LastFlag = 0 then
|
|
|
|
+ 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;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+{$ifdef HAS_SYSOSREALLOC}
|
|
|
|
+ function HeapInc.ThreadState.TrySysOSRealloc(p: pointer; oldSize, newSize: SizeUint): pointer;
|
|
|
|
+ var
|
|
|
|
+ newOSSize: SizeUint;
|
|
|
|
+ hasFreeChunkToTheRight: boolean;
|
|
|
|
+ vf, varPrev, varNext: pFreeVarChunk;
|
|
|
|
+ begin
|
|
|
|
+ { Either p is the only chunk or has last empty chunk to the right. }
|
|
|
|
+ hasFreeChunkToTheRight := pVarHeader(p - VarHeaderSize)^.ch.h and LastFlag = 0;
|
|
|
|
+
|
|
|
|
+ { Don’t do SysOSRealloc if the source chunk is <12.5% (arbitrary value) of the empty chunk to the right. }
|
|
|
|
+ if hasFreeChunkToTheRight and (oldSize < pFreeVarChunk(p + oldSize)^.size div 8) then
|
|
|
|
+ exit(nil);
|
|
|
|
+
|
|
|
|
+ newOSSize := (newSize + (VarOSChunkDataOffset + OSChunkVarSizeQuant - 1)) and SizeUint(-OSChunkVarSizeQuant);
|
|
|
|
+ p := SysOSRealloc(p - (VarOSChunkDataOffset + VarHeaderSize), pVarOSChunk(p - (VarOSChunkDataOffset + VarHeaderSize))^.size, newOSSize);
|
|
|
|
+ if not Assigned(p) then
|
|
|
|
+ exit(nil);
|
|
|
|
+
|
|
|
|
+ inc(allocated, newOSSize - pVarOSChunk(p)^.size);
|
|
|
|
+ if allocated > maxAllocated then
|
|
|
|
+ maxAllocated := allocated;
|
|
|
|
+ pVarOSChunk(p)^.size := newOSSize;
|
|
|
|
+ { For simplicity, new chunk spans the entire OS chunk. }
|
|
|
|
+ newOSSize := (newOSSize - VarOSChunkDataOffset) and SizeUint(-VarSizeQuant);
|
|
|
|
+ inc(used, newOSSize - oldSize);
|
|
|
|
+ if used > maxUsed then
|
|
|
|
+ maxUsed := used;
|
|
|
|
+
|
|
|
|
+ { Update p size. }
|
|
|
|
+ {$if sizeof(SizeUint) > 4}
|
|
|
|
+ pVarHeader(p + VarOSChunkDataOffset)^.sizeHi := newOSSize shr 32;
|
|
|
|
+ {$endif}
|
|
|
|
+ pVarHeader(p + VarOSChunkDataOffset)^.ch.h := uint32(newOSSize) or (FirstFlag or LastFlag or UsedFlag);
|
|
|
|
+
|
|
|
|
+ { Careful! Old pointers into p are invalidated and must be fixed.
|
|
|
|
+ There are up to 3 invalidated pointers: OS chunk in varOS, old p itself (p is reused for new OS chunk pointer), maybe empty chunk to the right in varFree. }
|
|
|
|
+ if Assigned(pVarOSChunk(p)^.next) then
|
|
|
|
+ pVarOSChunk(pVarOSChunk(p)^.next)^.prev := p;
|
|
|
|
+ if Assigned(pVarOSChunk(p)^.prev) then
|
|
|
|
+ pVarOSChunk(pVarOSChunk(p)^.prev)^.next := p
|
|
|
|
+ else
|
|
|
|
+ varOS := p;
|
|
|
|
+
|
|
|
|
+ result := p + (VarOSChunkDataOffset + VarHeaderSize);
|
|
|
|
+ if hasFreeChunkToTheRight then
|
|
|
|
+ begin
|
|
|
|
+ vf := result + oldSize;
|
|
|
|
+
|
|
|
|
+ { Remove vf from varFree. }
|
|
|
|
+ varPrev := vf^.prev;
|
|
|
|
+ varNext := vf^.next;
|
|
|
|
+ if Assigned(varPrev) then
|
|
|
|
+ varPrev^.next := varNext
|
|
|
|
+ else
|
|
|
|
+ varFree := varNext;
|
|
|
|
+ if Assigned(varNext) then
|
|
|
|
+ varNext^.prev := varPrev;
|
|
|
|
+ end;
|
|
|
|
+ end;
|
|
|
|
+{$endif HAS_SYSOSREALLOC}
|
|
|
|
+
|
|
|
|
+{$ifdef FPC_HAS_FEATURE_THREADING}
|
|
|
|
+ procedure HeapInc.ThreadState.PushToFree(p: pFreeChunk);
|
|
|
|
+ var
|
|
|
|
+ next: pFreeChunk;
|
|
|
|
+ begin
|
|
|
|
+ repeat
|
|
|
|
+ next := toFree;
|
|
|
|
+ p^.next := next;
|
|
|
|
+ WriteBarrier; { Write p after p^.next. }
|
|
|
|
+ until InterlockedCompareExchange(toFree, p, next) = next;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ procedure HeapInc.ThreadState.FlushToFree;
|
|
|
|
+ var
|
|
|
|
+ tf, nx: pFreeChunk;
|
|
|
|
+ begin
|
|
|
|
+ tf := InterlockedExchange(toFree, nil);
|
|
|
|
+ while Assigned(tf) do
|
|
|
|
+ begin
|
|
|
|
+ ReadDependencyBarrier; { Read toFree^.next after toFree. }
|
|
|
|
+ nx := tf^.next;
|
|
|
|
+ if pCommonHeader(pointer(tf) - CommonHeaderSize)^.h and FixedFlag <> 0 then
|
|
|
|
+ FreeFixed(tf)
|
|
|
|
+ else
|
|
|
|
+ FreeVar(tf);
|
|
|
|
+ tf := nx;
|
|
|
|
+ end;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ procedure HeapInc.ThreadState.Orphan;
|
|
|
|
+ var
|
|
|
|
+ sizeIndex: SizeUint;
|
|
|
|
+ lastFree, nextFree: pFreeOSChunk;
|
|
|
|
+ vOs, nextVOs, lastVOs: pVarOSChunk;
|
|
|
|
+ begin
|
|
|
|
+ FlushToFree;
|
|
|
|
+ Orphan(fullOS);
|
|
|
|
+ for sizeIndex := 0 to High(fixedPartialOS) do
|
|
|
|
+ Orphan(fixedPartialOS[sizeIndex]);
|
|
|
|
+ { Prepend freeOS to gs.freeOS. }
|
|
|
|
+ lastFree := freeOS.last;
|
|
|
|
+ if Assigned(lastFree) then
|
|
|
|
+ begin
|
|
|
|
+ nextFree := gs.freeOS.first;
|
|
|
|
+ lastFree^.next := nextFree;
|
|
|
|
+ if Assigned(nextFree) then
|
|
|
|
+ nextFree^.prev := lastFree
|
|
|
|
+ else
|
|
|
|
+ gs.freeOS.last := lastFree;
|
|
|
|
+ gs.freeOS.first := freeOS.first;
|
|
|
|
+ {$ifdef HAS_SYSOSFREE}
|
|
|
|
+ inc(gs.freeOS.n, freeOS.n);
|
|
|
|
+ while gs.freeOS.n > MaxKeptOSChunks do
|
|
|
|
+ gs.freeOS.FreeOne;
|
|
|
|
+ {$endif}
|
|
|
|
+ end;
|
|
|
|
+ { Prepend varOS to gs.varOS. }
|
|
|
|
+ vOs := varOS;
|
|
|
|
+ if Assigned(vOs) then
|
|
|
|
+ begin
|
|
|
|
+ nextVOs := gs.varOS;
|
|
|
|
+ gs.varOS := vOs;
|
|
|
|
+ repeat
|
|
|
|
+ lastVOs := vOs;
|
|
|
|
+ ChangeThreadState(vOs, nil);
|
|
|
|
+ vOs := vOs^.next;
|
|
|
|
+ until not Assigned(vOs);
|
|
|
|
+ lastVOs^.next := nextVOs;
|
|
|
|
+ if Assigned(nextVOs) then
|
|
|
|
+ nextVOs^.prev := lastVOs;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ { Zeroing is probably required, because Orphan is called from FinalizeHeap which is called from DoneThread which can be called twice, according to this comment from syswin.inc: }
|
|
|
|
+ // DoneThread; { Assume everything is idempotent there }
|
|
|
|
+ FillChar(self, sizeof(self), 0);
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ class procedure HeapInc.ThreadState.Orphan(list: pFixedOSChunk);
|
|
|
|
+ var
|
|
|
|
+ last, osNext: pFixedOSChunk;
|
|
|
|
+ begin
|
|
|
|
+ if not Assigned(list) then
|
|
|
|
+ exit;
|
|
|
|
+ last := pFixedOSChunk(ChangeThreadStates(list, nil));
|
|
|
|
+ { Prepend list to gs.fixedOS. }
|
|
|
|
+ osNext := gs.fixedOS;
|
|
|
|
+ last^.next := osNext;
|
|
|
|
+ if Assigned(osNext) then
|
|
|
|
+ osNext^.prev := last;
|
|
|
|
+ gs.fixedOS := list;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ procedure HeapInc.ThreadState.Adopt(osChunk: pFixedOSChunk);
|
|
|
|
+ var
|
|
|
|
+ sizeIndex: SizeUint;
|
|
|
|
+ dest: ^pFixedOSChunk;
|
|
|
|
+ begin
|
|
|
|
+ sizeIndex := pCommonHeader(pointer(osChunk) + FixedOSChunkDataOffset)^.h and SizeIndexMask;
|
|
|
|
+ inc(used, osChunk^.usedSize);
|
|
|
|
+ if used > maxUsed then
|
|
|
|
+ maxUsed := used;
|
|
|
|
+ inc(allocated, osChunk^.size);
|
|
|
|
+ if allocated > maxAllocated then
|
|
|
|
+ maxAllocated := allocated;
|
|
|
|
+
|
|
|
|
+ { Remove osChunk from gs.fixedOS, add to fullOS or fixedPartialOS[sizeIndex] as appropriate. }
|
|
|
|
+ dest := @fixedPartialOS[sizeIndex];
|
|
|
|
+ if osChunk^.usedSize >= osChunk^.fullThreshold then
|
|
|
|
+ begin
|
|
|
|
+ inc(allocatedByFullFixed[sizeIndex], osChunk^.size);
|
|
|
|
+ dest := @fullOS;
|
|
|
|
+ end;
|
|
|
|
+ osChunk^.MoveTo(gs.fixedOS, dest^);
|
|
|
|
+
|
|
|
|
+ osChunk^.threadState := @self;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ procedure HeapInc.ThreadState.AdoptVarOwner(p: pointer);
|
|
|
|
+ var
|
|
|
|
+ prevSize, size: SizeUint;
|
|
|
|
+ h: uint32;
|
|
|
|
+ varFreeHead: pFreeVarChunk;
|
|
|
|
+ begin
|
|
|
|
+ repeat
|
|
|
|
+ prevSize := pVarHeader(p - VarHeaderSize)^.prevSize;
|
|
|
|
+ dec(p, prevSize);
|
|
|
|
+ until prevSize = 0;
|
|
|
|
+
|
|
|
|
+ { Move OS chunk from gs.varOS to varOS. }
|
|
|
|
+ pOSChunk(p - (VarOSChunkDataOffset + VarHeaderSize))^.MoveTo(gs.varOS, varOS);
|
|
|
|
+ inc(allocated, pOSChunk(p - (VarOSChunkDataOffset + VarHeaderSize))^.size);
|
|
|
|
+ if allocated > maxAllocated then
|
|
|
|
+ maxAllocated := allocated;
|
|
|
|
+
|
|
|
|
+ { Careful: even though VarHeaders have own threadState links, correct threadState in the OS chunk is required,
|
|
|
|
+ as the chunk might be orphaned, then adopted with this function, then become free, then be reused as fixed chunk.
|
|
|
|
+ GetOSChunk does not set threadState if it takes the chunk from local freeOS, assuming it is already set. }
|
|
|
|
+ pOSChunk(p - (VarOSChunkDataOffset + VarHeaderSize))^.threadState := @self;
|
|
|
|
+
|
|
|
|
+ varFreeHead := varFree;
|
|
|
|
+ repeat
|
|
|
|
+ pVarHeader(p - VarHeaderSize)^.threadState := @self;
|
|
|
|
+ h := pVarHeader(p - VarHeaderSize)^.ch.h;
|
|
|
|
+ size := {$if sizeof(SizeUint) > 4} SizeUint(pVarHeader(p - VarHeaderSize)^.sizeHi) shl 32 or {$endif} h and uint32(VarSizeMask);
|
|
|
|
+ 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
|
|
|
|
+ inc(used, size); { maxUsed is updated after the loop. }
|
|
|
|
+ inc(p, size);
|
|
|
|
+ until h and LastFlag <> 0;
|
|
|
|
+ varFree := varFreeHead;
|
|
|
|
+ if used > maxUsed then
|
|
|
|
+ maxUsed := used;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ class function HeapInc.ThreadState.ChangeThreadStates(list: pOSChunk; ts: pThreadState): pOSChunk; static; { Returns the last item of list. }
|
|
|
|
+ begin
|
|
|
|
+ if not Assigned(list) then
|
|
|
|
+ exit(nil);
|
|
|
|
+ repeat
|
|
|
|
+ list^.threadState := ts;
|
|
|
|
+ result := list;
|
|
|
|
+ list := list^.next;
|
|
|
|
+ until not Assigned(list);
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ class procedure HeapInc.ThreadState.ChangeThreadState(vOs: pVarOSChunk; ts: pThreadState);
|
|
|
|
+ var
|
|
|
|
+ h: uint32;
|
|
|
|
+ p: pointer;
|
|
|
|
+ begin
|
|
|
|
+ vOs^.threadState := ts; { Not really required (for now), but done for symmetry; also see the comment on setting OSChunk.threadState in AdoptVarOwner. }
|
|
|
|
+ p := pointer(vOs) + (VarOSChunkDataOffset + VarHeaderSize);
|
|
|
|
+ repeat
|
|
|
|
+ pVarHeader(p - VarHeaderSize)^.threadState := ts;
|
|
|
|
+ h := pVarHeader(p - VarHeaderSize)^.ch.h;
|
|
|
|
+ inc(p, {$if sizeof(SizeUint) > 4} SizeUint(pVarHeader(p - VarHeaderSize)^.sizeHi) shl 32 or {$endif} h and uint32(VarSizeMask));
|
|
|
|
+ until h and LastFlag <> 0;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+{$ifndef FPC_SECTION_THREADVARS}
|
|
|
|
+ procedure HeapInc.ThreadState.FixupSelfPtr;
|
|
|
|
+ var
|
|
|
|
+ sizeIndex: SizeUint;
|
|
|
|
+ vOs: pVarOSChunk;
|
|
|
|
+ begin
|
|
|
|
+ ChangeThreadStates(fullOS, @self);
|
|
|
|
+ for sizeIndex := 0 to High(fixedPartialOS) do
|
|
|
|
+ ChangeThreadStates(fixedPartialOS[sizeIndex], @self);
|
|
|
|
+ ChangeThreadStates(freeOS.first, @self);
|
|
|
|
+ vOs := varOS;
|
|
|
|
+ while Assigned(vOs) do
|
|
|
|
+ begin
|
|
|
|
+ ChangeThreadState(vOs, @self);
|
|
|
|
+ vOs := vOs^.next;
|
|
|
|
+ end;
|
|
|
|
+ end;
|
|
|
|
+{$endif ndef FPC_SECTION_THREADVARS}
|
|
|
|
+{$endif FPC_HAS_FEATURE_THREADING}
|
|
|
|
+
|
|
|
|
+function SysGetFPCHeapStatus:TFPCHeapStatus;
|
|
|
|
+var
|
|
|
|
+ ts: HeapInc.pThreadState;
|
|
|
|
+begin
|
|
|
|
+ ts := @HeapInc.thisTs;
|
|
|
|
+ result.MaxHeapSize := ts^.maxAllocated;
|
|
|
|
+ result.MaxHeapUsed := ts^.maxUsed;
|
|
|
|
+ result.CurrHeapSize := ts^.allocated;
|
|
|
|
+ result.CurrHeapUsed := ts^.used;
|
|
|
|
+ result.CurrHeapFree := result.CurrHeapSize - result.CurrHeapUsed;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function SysGetHeapStatus :THeapStatus;
|
|
|
|
+var
|
|
|
|
+ ts: HeapInc.pThreadState;
|
|
|
|
+begin
|
|
|
|
+ FillChar((@result)^, sizeof(result), 0);
|
|
|
|
+ ts := @HeapInc.thisTs;
|
|
|
|
+ result.TotalAllocated :=ts^.used;
|
|
|
|
+ result.TotalFree :=ts^.allocated - ts^.used;
|
|
|
|
+ result.TotalAddrSpace :=ts^.allocated;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function SysGetMem(size : ptruint):pointer;
|
|
|
|
+var
|
|
|
|
+ ts: HeapInc.pThreadState;
|
|
|
|
+begin
|
|
|
|
+ ts := @HeapInc.thisTs;
|
|
|
|
+ if size <= HeapInc.MaxFixedHeaderAndPayload - HeapInc.CommonHeaderSize then
|
|
|
|
+ result := ts^.AllocFixed(size)
|
|
|
|
+ else
|
|
|
|
+ result := ts^.AllocVar(size);
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function SysFreeMem(p: pointer): ptruint;
|
|
|
|
+var
|
|
|
|
+ ts: HeapInc.pThreadState;
|
|
|
|
+begin
|
|
|
|
+ result := 0;
|
|
|
|
+ if Assigned(p) then
|
|
|
|
+ begin
|
|
|
|
+ ts := @HeapInc.thisTs;
|
|
|
|
+ if HeapInc.pCommonHeader(p - HeapInc.CommonHeaderSize)^.h and HeapInc.FixedFlag <> 0 then
|
|
|
|
+ result := ts^.FreeFixed(p)
|
|
|
|
+ else
|
|
|
|
+ result := ts^.FreeVar(p);
|
|
|
|
+ end;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function SysTryResizeMem(var p: pointer; size: ptruint): boolean;
|
|
|
|
+var
|
|
|
|
+ ts: HeapInc.pThreadState;
|
|
|
|
+ h: uint32;
|
|
|
|
+ newp: pointer;
|
|
|
|
+begin
|
|
|
|
+ h := HeapInc.pCommonHeader(p - HeapInc.CommonHeaderSize)^.h;
|
|
|
|
+ if h and HeapInc.FixedFlag <> 0 then
|
|
|
|
+ { Don’t shrink fixed chunk. }
|
|
|
|
+ result := size <= SizeUint(HeapInc.IndexToSize(h and HeapInc.SizeIndexMask) - HeapInc.CommonHeaderSize)
|
|
|
|
+ else
|
|
|
|
+ begin
|
|
|
|
+ ts := @HeapInc.thisTs;
|
|
|
|
+ {$ifdef FPC_HAS_FEATURE_THREADING}
|
|
|
|
+ if Assigned(ts^.toFree) then
|
|
|
|
+ ts^.FlushToFree;
|
|
|
|
+ {$endif FPC_HAS_FEATURE_THREADING}
|
|
|
|
+ newp := ts^.TryResizeVar(p, size);
|
|
|
|
+ result := Assigned(newp);
|
|
|
|
+ if result then
|
|
|
|
+ p := newp;
|
|
|
|
+ end;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function SysMemSize(p: pointer): ptruint;
|
|
|
|
+var
|
|
|
|
+ h: uint32;
|
|
|
|
+begin
|
|
|
|
+ if not Assigned(p) then
|
|
|
|
+ exit(0);
|
|
|
|
+ h := HeapInc.pCommonHeader(p - HeapInc.CommonHeaderSize)^.h;
|
|
|
|
+ if h and HeapInc.FixedFlag <> 0 then
|
|
|
|
+ result := HeapInc.IndexToSize(h and HeapInc.SizeIndexMask) - HeapInc.CommonHeaderSize
|
|
|
|
+ else
|
|
|
|
+ result := {$if sizeof(SizeUint) > 4} SizeUint(HeapInc.pVarHeader(p - HeapInc.VarHeaderSize)^.sizeHi) shl 32 or {$endif}
|
|
|
|
+ HeapInc.pVarHeader(p - HeapInc.VarHeaderSize)^.ch.h and uint32(HeapInc.VarSizeMask)
|
|
|
|
+ - HeapInc.VarHeaderSize;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function SysReAllocMem(var p: pointer; size: ptruint):pointer;
|
|
|
|
+var
|
|
|
|
+ oldsize, newsize, tocopy: SizeUint;
|
|
|
|
+begin
|
|
|
|
+ if size = 0 then
|
|
|
|
+ begin
|
|
|
|
+ SysFreeMem(p);
|
|
|
|
+ result := nil;
|
|
|
|
+ p := nil;
|
|
|
|
+ end
|
|
|
|
+ else if not Assigned(p) then
|
|
|
|
+ begin
|
|
|
|
+ result := SysGetMem(size);
|
|
|
|
+ p := result;
|
|
|
|
+ end
|
|
|
|
+ else if SysTryResizeMem(p, size) then
|
|
|
|
+ result := p
|
|
|
|
+ else
|
|
|
|
+ begin
|
|
|
|
+ oldsize := SysMemSize(p);
|
|
|
|
+ newsize := size;
|
|
|
|
+ result := SysGetMem(newsize);
|
|
|
|
+ if not Assigned(result) then
|
|
|
|
+ begin
|
|
|
|
+ if size <= oldsize then
|
|
|
|
+ { Don’t fail if shrinking. }
|
|
|
|
+ result := p;
|
|
|
|
+ exit; { If growing failed, return nil, but keep the old p. }
|
|
|
|
+ end;
|
|
|
|
+ tocopy := oldsize;
|
|
|
|
+ if tocopy > newsize then
|
|
|
|
+ tocopy := newsize;
|
|
|
|
+ Move(p^, result^, tocopy);
|
|
|
|
+ SysFreeMem(p);
|
|
|
|
+ p := result;
|
|
|
|
+ end;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+Function SysFreeMemSize(p: pointer; size: ptruint):ptruint;
|
|
|
|
+begin
|
|
|
|
+ { can't free partial blocks, ignore size }
|
|
|
|
+ result := SysFreeMem(p);
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function SysAllocMem(size: ptruint): pointer;
|
|
|
|
+begin
|
|
|
|
+ result := SysGetMem(size);
|
|
|
|
+ if Assigned(result) then
|
|
|
|
+ FillChar(result^, SysMemSize(result), 0);
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+{$endif FPC_NO_DEFAULT_HEAP}
|
|
|
|
+
|
|
|
|
+{$ifndef HAS_MEMORYMANAGER}
|
|
|
|
+
|
|
|
|
+{*****************************************************************************
|
|
|
|
+ InitHeap
|
|
|
|
+*****************************************************************************}
|
|
|
|
+
|
|
|
|
+{$ifndef FPC_NO_DEFAULT_HEAP}
|
|
|
|
+{ This function will initialize the Heap manager and need to be called from
|
|
|
|
+ the initialization of the system unit }
|
|
|
|
+{$ifdef FPC_HAS_FEATURE_THREADING}
|
|
|
|
+procedure InitHeapThread;
|
|
|
|
+begin
|
|
|
|
+ if HeapInc.gs.lockUse>0 then
|
|
|
|
+ InterlockedIncrement(HeapInc.gs.lockUse);
|
|
|
|
+end;
|
|
|
|
+{$endif}
|
|
|
|
+
|
|
|
|
+procedure InitHeap; public name '_FPC_InitHeap';
|
|
|
|
+begin
|
|
|
|
+ { we cannot initialize the locks here yet, thread support is
|
|
|
|
+ not loaded yet }
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+procedure RelocateHeap;
|
|
|
|
+begin
|
|
|
|
+{$ifdef FPC_HAS_FEATURE_THREADING}
|
|
|
|
+ if HeapInc.gs.lockUse > 0 then
|
|
|
|
+ exit;
|
|
|
|
+ HeapInc.gs.lockUse := 1;
|
|
|
|
+ InitCriticalSection(HeapInc.gs.lock);
|
|
|
|
+{$ifndef FPC_SECTION_THREADVARS}
|
|
|
|
+ { threadState pointers still point to main thread's thisTs, but they
|
|
|
|
+ have a reference to the global main thisTs, fix them to point
|
|
|
|
+ to the main thread specific variable.
|
|
|
|
+ even if section threadvars are used, this shouldn't cause problems as threadState pointers simply
|
|
|
|
+ do not change but we do not need it }
|
|
|
|
+ HeapInc.thisTs.FixupSelfPtr;
|
|
|
|
+{$endif FPC_SECTION_THREADVARS}
|
|
|
|
+ if MemoryManager.RelocateHeap <> nil then
|
|
|
|
+ MemoryManager.RelocateHeap();
|
|
|
|
+{$endif FPC_HAS_FEATURE_THREADING}
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+procedure FinalizeHeap;
|
|
|
|
+begin
|
|
|
|
+ { Do not try to do anything if the heap manager already reported an error }
|
|
|
|
+ if (errorcode=203) or (errorcode=204) then
|
|
|
|
+ exit;
|
|
|
|
+{$ifdef FPC_HAS_FEATURE_THREADING}
|
|
|
|
+ if HeapInc.gs.lockUse > 0 then
|
|
|
|
+ EnterCriticalSection(HeapInc.gs.lock);
|
|
|
|
+ HeapInc.thisTs.Orphan;
|
|
|
|
+ if HeapInc.gs.lockUse > 0 then
|
|
|
|
+ begin
|
|
|
|
+ LeaveCriticalSection(HeapInc.gs.lock);
|
|
|
|
+ if InterlockedDecrement(HeapInc.gs.lockUse) = 0 then
|
|
|
|
+ begin
|
|
|
|
+ DoneCriticalSection(HeapInc.gs.lock);
|
|
|
|
+ {$ifdef HAS_SYSOSFREE}
|
|
|
|
+ HeapInc.gs.freeOS.FreeAll;
|
|
|
|
+ {$endif}
|
|
|
|
+ end;
|
|
|
|
+ end;
|
|
|
|
+{$else FPC_HAS_FEATURE_THREADING}
|
|
|
|
+ HeapInc.thisTs.freeOS.FreeAll;
|
|
|
|
+{$endif FPC_HAS_FEATURE_THREADING}
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+{$endif ndef HAS_MEMORYMANAGER}
|
|
|
|
+
|
|
|
|
+{$endif ndef FPC_NO_DEFAULT_MEMORYMANAGER}
|
|
|
|
+{$endif defined(FPC_HAS_FEATURE_HEAP) or defined(FPC_IN_HEAPMGR)}
|