heap.inc 66 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847
  1. {
  2. This file is part of the Free Pascal run time library.
  3. Copyright (c) 1999-2000 by the Free Pascal development team.
  4. functions for heap management in the data segment
  5. See the file COPYING.FPC, included in this distribution,
  6. for details about the copyright.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  10. **********************************************************************}
  11. {****************************************************************************}
  12. { Do not use standard memory manager }
  13. { $define HAS_MEMORYMANAGER}
  14. { Memory manager }
  15. {$if not defined(FPC_NO_DEFAULT_MEMORYMANAGER)}
  16. const
  17. MemoryManager: TMemoryManager = (
  18. NeedLock: false; // Obsolete
  19. GetMem: {$ifndef FPC_NO_DEFAULT_HEAP}@SysGetMem{$else}nil{$endif};
  20. FreeMem: {$ifndef FPC_NO_DEFAULT_HEAP}@SysFreeMem{$else}nil{$endif};
  21. FreeMemSize: {$ifndef FPC_NO_DEFAULT_HEAP}@SysFreeMemSize{$else}nil{$endif};
  22. AllocMem: {$ifndef FPC_NO_DEFAULT_HEAP}@SysAllocMem{$else}nil{$endif};
  23. ReAllocMem: {$ifndef FPC_NO_DEFAULT_HEAP}@SysReAllocMem{$else}nil{$endif};
  24. MemSize: {$ifndef FPC_NO_DEFAULT_HEAP}@SysMemSize{$else}nil{$endif};
  25. InitThread: nil;
  26. DoneThread: nil;
  27. RelocateHeap: nil;
  28. GetHeapStatus: {$ifndef FPC_NO_DEFAULT_HEAP}@SysGetHeapStatus{$else}nil{$endif};
  29. GetFPCHeapStatus: {$ifndef FPC_NO_DEFAULT_HEAP}@SysGetFPCHeapStatus{$else}nil{$endif};
  30. );
  31. {$elseif not defined(FPC_IN_HEAPMGR)}
  32. const
  33. MemoryManager: TMemoryManager = (
  34. NeedLock: false; // Obsolete
  35. GetMem: nil;
  36. FreeMem: nil;
  37. FreeMemSize: nil;
  38. AllocMem: nil;
  39. ReAllocMem: nil;
  40. MemSize: nil;
  41. InitThread: nil;
  42. DoneThread: nil;
  43. RelocateHeap: nil;
  44. GetHeapStatus: nil;
  45. GetFPCHeapStatus: nil;
  46. );public name 'FPC_SYSTEM_MEMORYMANAGER';
  47. {$endif FPC_IN_HEAPMGR}
  48. {*****************************************************************************
  49. Memory Manager
  50. *****************************************************************************}
  51. {$ifndef FPC_IN_HEAPMGR}
  52. procedure GetMemoryManager(var MemMgr:TMemoryManager);
  53. begin
  54. MemMgr := MemoryManager;
  55. end;
  56. procedure SetMemoryManager(const MemMgr:TMemoryManager);
  57. begin
  58. MemoryManager := MemMgr;
  59. end;
  60. function IsMemoryManagerSet:Boolean;
  61. begin
  62. {$if defined(HAS_MEMORYMANAGER) or defined(FPC_NO_DEFAULT_MEMORYMANAGER)}
  63. Result:=false;
  64. {$else not FPC_NO_DEFAULT_MEMORYMANAGER}
  65. IsMemoryManagerSet := (MemoryManager.GetMem<>@SysGetMem)
  66. or (MemoryManager.FreeMem<>@SysFreeMem);
  67. {$endif HAS_MEMORYMANAGER or FPC_NO_DEFAULT_MEMORYMANAGER}
  68. end;
  69. {$ifdef FPC_HAS_FEATURE_HEAP}
  70. procedure GetMem(Out p:pointer;Size:ptruint);
  71. begin
  72. p := MemoryManager.GetMem(Size);
  73. end;
  74. procedure GetMemory(Out p:pointer;Size:ptruint);
  75. begin
  76. GetMem(p,size);
  77. end;
  78. procedure FreeMem(p:pointer;Size:ptruint);
  79. begin
  80. MemoryManager.FreeMemSize(p,Size);
  81. end;
  82. procedure FreeMemory(p:pointer;Size:ptruint);
  83. begin
  84. FreeMem(p,size);
  85. end;
  86. function GetHeapStatus:THeapStatus;
  87. begin
  88. Result:=MemoryManager.GetHeapStatus();
  89. end;
  90. function GetFPCHeapStatus:TFPCHeapStatus;
  91. begin
  92. Result:=MemoryManager.GetFPCHeapStatus();
  93. end;
  94. function MemSize(p:pointer):ptruint;
  95. begin
  96. MemSize := MemoryManager.MemSize(p);
  97. end;
  98. { Delphi style }
  99. function FreeMem(p:pointer):ptruint;
  100. begin
  101. FreeMem := MemoryManager.FreeMem(p);
  102. end;
  103. function FreeMemory(p:pointer):ptruint; cdecl;
  104. begin
  105. FreeMemory := FreeMem(p);
  106. end;
  107. function GetMem(size:ptruint):pointer;
  108. begin
  109. GetMem := MemoryManager.GetMem(Size);
  110. end;
  111. function GetMemory(size:ptruint):pointer; cdecl;
  112. begin
  113. GetMemory := GetMem(size);
  114. end;
  115. function AllocMem(Size:ptruint):pointer;
  116. begin
  117. AllocMem := MemoryManager.AllocMem(size);
  118. end;
  119. function ReAllocMem(var p:pointer;Size:ptruint):pointer;
  120. begin
  121. ReAllocMem := MemoryManager.ReAllocMem(p,size);
  122. end;
  123. function ReAllocMemory(p:pointer;Size:ptruint):pointer; cdecl;
  124. begin
  125. ReAllocMemory := ReAllocMem(p,size);
  126. end;
  127. { Needed for calls from Assembler }
  128. function fpc_getmem(size:ptruint):pointer;compilerproc;[public,alias:'FPC_GETMEM'];
  129. begin
  130. fpc_GetMem := MemoryManager.GetMem(size);
  131. end;
  132. procedure fpc_freemem(p:pointer);compilerproc;[public,alias:'FPC_FREEMEM'];
  133. begin
  134. MemoryManager.FreeMem(p);
  135. end;
  136. {$endif FPC_HAS_FEATURE_HEAP}
  137. {$endif FPC_IN_HEAPMGR}
  138. {$if (defined(FPC_HAS_FEATURE_HEAP) or defined(FPC_IN_HEAPMGR)) and not defined(HAS_MEMORYMANAGER)}
  139. type
  140. {
  141. We use 'fixed' size chunks for small allocations,
  142. os chunks with variable sized blocks for bigger allocations,
  143. and (almost) directly use os chunks for huge allocations.
  144. * a block is an area allocated by user
  145. * a chunk is a block plus our bookkeeping
  146. * an os chunk is a collection of chunks
  147. Memory layout:
  148. fixed: < CommonHeader > [ ... user data ... ]
  149. variable: [ VarHeader < CommonHeader > ] [ ... user data ... ]
  150. huge: HugeChunk < CommonHeader > [ ... user data ... ]
  151. When all chunks in an os chunk are free, we keep a few around
  152. but otherwise it will be freed to the OS.
  153. }
  154. {$ifdef ENDIAN_LITTLE}
  155. {$define HEAP_INC_USE_SETS} { Potentially better codegen than “or 1 shl” etc. (at least on x86). Can be adapted for big endian, too, but I have no such platform to test. }
  156. {$endif ENDIAN_LITTLE}
  157. HeapInc = object
  158. const
  159. { Alignment requirement for blocks. All fixed sizes (among other things) are assumed to be divisible. }
  160. Alignment = 2 * sizeof(pointer);
  161. { Fixed chunk sizes are:
  162. ┌──── step = 16 ────┐┌─── step = 32 ────┐┌──── step = 48 ───┐┌ step 64 ┐
  163. 16, 32, 48, 64, 80, 96, 128, 160, 192, 224, 272, 320, 368, 416, 480, 544
  164. #0 #1 #2 #3 #4 #5 #6 #7 #8 #9 #10 #11 #12 #13 #14 #15 }
  165. MinFixedHeaderAndPayload = 16;
  166. MaxFixedHeaderAndPayload = 544;
  167. FixedSizesCount = 16;
  168. FixedSizes: array[0 .. FixedSizesCount - 1] of uint16 = (16, 32, 48, 64, 80, 96, 128, 160, 192, 224, 272, 320, 368, 416, 480, 544);
  169. SizeMinus1Div16ToIndex: array[0 .. (MaxFixedHeaderAndPayload - 1) div 16] of uint8 =
  170. { 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 }
  171. ( 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);
  172. class function SizeMinus1ToIndex(sizeMinus1: SizeUint): SizeUint; static; inline; { sizeMinus1 + 1 ≤ MaxFixedHeaderAndPayload }
  173. class function IndexToSize(sizeIndex: SizeUint): SizeUint; static; inline;
  174. const
  175. OSChunkVarSizeQuant = 64 * 1024;
  176. FixedArenaSizeQuant = 4 * 1024;
  177. MinFixedArenaSize = 8 * 1024;
  178. MaxFixedArenaSize = 64 * 1024;
  179. MaxKeptFixedArenas = 4;
  180. { Adjustable part ends here~ }
  181. const
  182. SizeIndexBits = 1 + trunc(ln(FixedSizesCount - 1) / ln(2));
  183. SizeIndexMask = 1 shl SizeIndexBits - 1;
  184. FixedBitPos = {$if SizeIndexBits >= 3} SizeIndexBits {$else} 3 {$endif}; { Variable chunks use 3 low bits for used / last / fixed arena. }
  185. FixedFlag = 1 shl FixedBitPos;
  186. FixedArenaOffsetShift = {$if FixedBitPos + 1 >= 5} FixedBitPos + 1 {$else} 5 {$endif}; { VarSizeQuant is expected to be 2^5. }
  187. UsedFlag = 1 shl 0;
  188. LastFlag = 1 shl 1;
  189. FixedArenaFlag = 1 shl 2;
  190. VarSizeQuant = 1 shl FixedArenaOffsetShift; {$if VarSizeQuant <> 32} {$error Should in principle work but explanations below assume exactly 32. :)} {$endif}
  191. VarSizeMask = uint32(-VarSizeQuant);
  192. HugeHeader = 0; { Special header value for huge chunks. FixedFlag must be 0, and the value must be impossible for a variable chunk. 0 turns out to be suitable. :) }
  193. { Variable chunk sizes, not counting extra MaxFixedHeaderAndPayload added to each of these:
  194. 32 sizes in the range +1 .. 1 024 (2^10) rounded up to the multiple of 32 = 2^ 5, + 0, max = 1 024 = %100 0000 0000
  195. 32 sizes in the range +1 .. 2 048 (2^11) rounded up to the multiple of 64 = 2^ 6, + 1024, max = 3 072 = %1100 0000 0000
  196. 32 sizes in the range +1 .. 4 096 (2^12) rounded up to the multiple of 128 = 2^ 7, + 1024 + 2048, max = 7 168 = %1 1100 0000 0000
  197. 32 sizes in the range +1 .. 8 192 (2^13) rounded up to the multiple of 256 = 2^ 8, + 2^10 + .. + 2^12, max = 15 360 = %11 1100 0000 0000
  198. 32 sizes in the range +1 .. 16 384 (2^14) rounded up to the multiple of 512 = 2^ 9, + 2^10 + .. + 2^13, max = 31 744 = %111 1100 0000 0000
  199. 32 sizes in the range +1 .. 32 768 (2^15) rounded up to the multiple of 1 024 = 2^10, + 2^10 + .. + 2^14, max = 64 512 = %1111 1100 0000 0000
  200. 32 sizes in the range +1 .. 65 536 (2^16) rounded up to the multiple of 2 048 = 2^11, + 2^10 + .. + 2^15, max = 130 048 = %1 1111 1100 0000 0000
  201. 32 sizes in the range +1 .. 131 072 (2^17) rounded up to the multiple of 4 096 = 2^12, + 2^10 + .. + 2^16, max = 261 120 = %11 1111 1100 0000 0000
  202. 32 sizes in the range +1 .. 262 144 (2^18) rounded up to the multiple of 8 192 = 2^13, + 2^10 + .. + 2^17, max = 523 264 = %111 1111 1100 0000 0000
  203. 32 sizes in the range +1 .. 524 288 (2^19) rounded up to the multiple of 16 384 = 2^14, + 2^10 + .. + 2^18, max = 1 047 552 = %1111 1111 1100 0000 0000 }
  204. FirstVarRangeP2 = 10;
  205. FirstVarStepP2 = FixedArenaOffsetShift; {$if FirstVarStepP2 <> 5} {$error :|} {$endif}
  206. VarSizeClassesCount = 10;
  207. VarSizesPerClass = 32;
  208. VarSizesCount = VarSizeClassesCount * VarSizesPerClass;
  209. L0BinSize = 32;
  210. MinEmptyVarHeaderAndPayload = (MaxFixedHeaderAndPayload + 1 shl FirstVarStepP2 + VarSizeQuant - 1) and -VarSizeQuant;
  211. MaxVarHeaderAndPayload = (MaxFixedHeaderAndPayload + (1 shl VarSizeClassesCount - 1) shl FirstVarRangeP2) and -VarSizeQuant; {$if MaxVarHeaderAndPayload <> MaxFixedHeaderAndPayload + 1047552} {$error does not match the explanation above :D} {$endif}
  212. class function VarSizeToBinIndex(size: SizeUint; roundUp: boolean): SizeUint; static;
  213. {$ifdef DEBUG_HEAP_INC}
  214. class function BinIndexToVarSize(binIndex: SizeUint): SizeUint; static;
  215. {$endif DEBUG_HEAP_INC}
  216. type
  217. { Common header of any memory chunk, residing immediately to the left of the ~payload~ (block).
  218. Fixed chunk header, assuming SizeIndexBits = 4:
  219. h[0:3] = size index (= h and SizeIndexMask)
  220. h[4] = 1 (h and FixedFlag <> 0)
  221. h[5:31] — offset in the FixedArena (= h shr FixedArenaOffsetShift)
  222. Variable chunk header, assuming SizeIndexBits = 4:
  223. h[0] = used flag (h and UsedFlag <> 0)
  224. h[1] = last flag (h and LastFlag <> 0)
  225. h[2] = fixed arena flag (h and FixedArenaFlag <> 0)
  226. h[3] = unused
  227. h[4] = 0 (h and FixedFlag = 0)
  228. h[5:31] = size, rounded up to 32 (VarSizeQuant), shr 5; in other words, size = h and VarSizeMask.
  229. Huge chunk header:
  230. h[4] = 0 (h and FixedFlag = 0)
  231. h[0:31] = HugeHeader }
  232. pCommonHeader = ^CommonHeader;
  233. CommonHeader = record
  234. h: uint32;
  235. end;
  236. pThreadState = ^ThreadState;
  237. { Chunk that has been freed. Reuses the now-uninteresting payload, so payload must always fit its size.
  238. Used for fixed freelists and cross-thread to-free queue. }
  239. pFreeChunk = ^FreeChunk;
  240. FreeChunk = record
  241. next: pFreeChunk;
  242. end;
  243. OSChunkBase = object { Shared between OSChunk and HugeChunk. }
  244. size: SizeUint; { Full size asked from SysOSAlloc. }
  245. end;
  246. pOSChunk = ^OSChunk;
  247. OSChunk = object(OSChunkBase) { Common header for all OS chunks. }
  248. prev, next: pointer; { pOSChunk, but used for different subtypes. }
  249. end;
  250. pFreeOSChunk = ^FreeOSChunk;
  251. FreeOSChunk = object(OSChunk)
  252. end;
  253. FreeOSChunkList = object
  254. first, last: pFreeOSChunk;
  255. {$ifdef HAS_SYSOSFREE}
  256. n: SizeUint;
  257. {$endif}
  258. function Get(minSize, maxSize: SizeUint): pOSChunk;
  259. {$ifdef HAS_SYSOSFREE}
  260. function FreeOne: SizeUint;
  261. procedure FreeAll;
  262. {$endif}
  263. end;
  264. pFixedArena = ^FixedArena;
  265. FixedArena = record
  266. { Allocated with AllocVar(isArena := true), so has VarHeader to the left.
  267. Data starts at FixedArenaDataOffset and spans for “maxSize” (virtual value, does not exist directly) bytes, of which:
  268. — first “formattedSize” are either allocated (“used”; counted in usedSizeMinus1) or in the freelist (firstFreeChunk; size = “formattedSize” - (usedSizeMinus1 + 1)),
  269. — the rest “maxSize” - “formattedSize” are yet unallocated space.
  270. This design, together with tracking free chunks per FixedArena rather than per fixed size, trivializes reusing the fixed arenas.
  271. Chopping all available space at once would get rid of the “unallocated space” entity, but is a lot of potentially wasted work:
  272. https://gitlab.com/freepascal.org/fpc/source/-/issues/40447.
  273. Values are multiples of the chunk size instead of counts (could be chunksUsed, chunksFormatted, chunksMax) to save on multiplications.
  274. Moreover, instead of “maxSize” from the explanation above, almostFullThreshold is used, which is such a value that the chunk is full if usedSizeMinus1 - chunk size >= almostFullThreshold.
  275. maxSize = RoundUp(almostFullThreshold + chunk size + 1, chunk size).
  276. Reasons are, calculating almostFullThreshold does not require division, and it is more convenient (in terms of code generation) for AllocFixed / FreeFixed.
  277. “formattedSize” is a virtual value, too; it equals to usedSizeMinus1 + 1 + <total size of the freelist> and is used only when said freelist is empty, so is in practice int32(usedSizeMinus1) + 1 (see AllocFixed). }
  278. firstFreeChunk: pFreeChunk;
  279. usedSizeMinus1, almostFullThreshold: uint32;
  280. prev, next: pFixedArena;
  281. end;
  282. pVarOSChunk = ^VarOSChunk;
  283. VarOSChunk = object(OSChunk)
  284. end;
  285. pVarHeader = ^VarHeader;
  286. VarHeader = record
  287. {$ifdef FPC_HAS_FEATURE_THREADING}
  288. threadState: pThreadState; { Protected with gs.lock. Nil if orphaned. }
  289. {$endif}
  290. prevSize: uint32; { Always 0 for the first chunk. }
  291. { Assumed to indeed match chunk’s CommonHeader, i.e. that there is no padding after this field.
  292. Otherwise must be accessed as pCommonHeader(pointer(varHdr) + (VarHeaderSize - CommonHeaderSize))^ :D. }
  293. ch: CommonHeader;
  294. end;
  295. { Reuses the payload of variable chunks whose ch.h and UsedFlag = 0, so variable chunk payload must always fit its size. }
  296. pFreeVarChunk = ^FreeVarChunk;
  297. FreeVarChunk = record
  298. prev, next: pFreeVarChunk;
  299. binIndex: uint32;
  300. end;
  301. pHugeChunk = ^HugeChunk;
  302. HugeChunk = object(OSChunkBase)
  303. end;
  304. NonZeroDWord = 1 .. High(uint32); { MAYBE IT WILL WORK ONE DAY (https://gitlab.com/freepascal.org/fpc/source/-/issues/41179). }
  305. {$ifdef HEAP_INC_USE_SETS}
  306. Set32 = set of 0 .. 31;
  307. {$endif HEAP_INC_USE_SETS}
  308. VarFreeMap = object
  309. { Two-level bitfield that allows to search for minimal-size fits (up to the quantization) using up to two “Bsf”s.
  310. Bit 1 in L1 means that the corresponding cell of L0 is non-0.
  311. Bit 1 in L0 means that the corresponding cell of bins is non-nil. }
  312. L1: uint32;
  313. L0: array[0 .. (VarSizesCount + L0BinSize - 1) div L0BinSize - 1] of uint32;
  314. bins: array[0 .. VarSizesCount - 1] of pFreeVarChunk;
  315. procedure Add(c: pFreeVarChunk; binIndex: SizeUint);
  316. procedure Remove(c: pFreeVarChunk);
  317. end;
  318. ThreadState = object
  319. emptyArenas: pFixedArena; { Empty fixed arenas to be reused instead of slower AllocVar. Singly linked list, “prev”s are garbage. }
  320. nEmptyArenas: SizeUint; { # of items in emptyArenas. }
  321. freeOS: FreeOSChunkList; { Completely empty OS chunks. }
  322. {$ifdef FPC_HAS_FEATURE_THREADING}
  323. toFree: pFreeChunk; { Free requests from other threads, atomic. }
  324. {$endif}
  325. used, maxUsed, allocated, maxAllocated: SizeUint; { “maxUsed” and “maxAllocated” include gs.hugeUsed; “used” and “allocated” don’t. }
  326. varOS: pVarOSChunk;
  327. { Fixed arenas with at least 1 free chunk (including unformatted space), but not completely empty.
  328. Fixed arenas that become completely empty are moved to emptyArenas, completely full are... not present in any list. }
  329. partialArenas: array[0 .. FixedSizesCount - 1] of pFixedArena;
  330. { Only to calculate preferable new fixed arena sizes...
  331. (Updated infrequently, as opposed to possible “usedPerArena”. When a new arena is required, all existing arenas of its size are full.) }
  332. allocatedByFullArenas: array[0 .. FixedSizesCount - 1] of SizeUint;
  333. varFree: VarFreeMap;
  334. {$ifdef DEBUG_HEAP_INC}
  335. procedure Dump(var f: text);
  336. {$endif}
  337. function ChooseFixedArenaSize(sizeIndex: SizeUint): SizeUint;
  338. function AllocFixed(size: SizeUint): pointer; inline;
  339. function FreeFixed(p: pointer): SizeUint; inline;
  340. function GetOSChunk(minSize, maxSize: SizeUint): pOSChunk;
  341. function AllocateOSChunk(minSize, maxSize: SizeUint): pOSChunk;
  342. function AllocVar(size: SizeUint; isArena: boolean): pointer;
  343. function FreeVar(p: pointer): SizeUint;
  344. function TryResizeVar(p: pointer; size: SizeUint): pointer;
  345. function AllocHuge(size: SizeUint): pointer;
  346. function FreeHuge(p: pointer): SizeUint;
  347. function TryResizeHuge(p: pointer; size: SizeUint): pointer;
  348. procedure UpdateMaxStats(hugeUsed: SizeUint);
  349. {$ifdef FPC_HAS_FEATURE_THREADING}
  350. procedure PushToFree(p: pFreeChunk);
  351. procedure FlushToFree;
  352. procedure Orphan;
  353. procedure AdoptArena(arena: pFixedArena);
  354. procedure AdoptVarOwner(p: pointer); { Adopts the OS chunk that contains p. Must be performed under gs.lock. }
  355. class procedure ChangeThreadState(vOs: pVarOSChunk; ts: pThreadState); static;
  356. {$ifndef FPC_SECTION_THREADVARS}
  357. procedure FixupSelfPtr;
  358. {$endif ndef FPC_SECTION_THREADVARS}
  359. {$endif FPC_HAS_FEATURE_THREADING}
  360. end;
  361. GlobalState = record
  362. hugeUsed: SizeUint; { Same as non-existing “hugeAllocated” as huge chunks don’t have free space.
  363. Protected by gs.lock, but can be read unprotected if unreliability is tolerable.
  364. Huge chunks don’t have thread affinity, so are tracked here. Presently, this value is added to all memory statistics.
  365. Not a good idea and makes multithreaded statistics a strange and unreliable mix, but alternatives are even worse. }
  366. {$ifdef FPC_HAS_FEATURE_THREADING}
  367. lock: TRTLCriticalSection;
  368. lockUse: int32;
  369. { Data from dead threads (“orphaned”), protected by gs.lock. }
  370. varOS: pVarOSChunk;
  371. {$ifndef HAS_SYSOSFREE}
  372. freeOS: FreeOSChunkList;
  373. {$endif not HAS_SYSOSFREE}
  374. {$endif FPC_HAS_FEATURE_THREADING}
  375. end;
  376. class function AllocFailed: pointer; static;
  377. class var
  378. gs: GlobalState;
  379. {$ifdef FPC_HAS_FEATURE_THREADING}
  380. class threadvar
  381. {$endif FPC_HAS_FEATURE_THREADING}
  382. thisTs: ThreadState;
  383. const
  384. CommonHeaderSize = sizeof(CommonHeader);
  385. {$if MinFixedHeaderAndPayload < CommonHeaderSize + sizeof(FreeChunk)} {$error MinFixedHeaderAndPayload does not fit CommonHeader + FreeChunk.} {$endif}
  386. FixedArenaDataOffset = (sizeof(FixedArena) + CommonHeaderSize + Alignment - 1) and -Alignment - CommonHeaderSize;
  387. VarHeaderSize = sizeof(VarHeader);
  388. VarOSChunkDataOffset = (sizeof(VarOSChunk) + VarHeaderSize + Alignment - 1) and -Alignment - VarHeaderSize;
  389. HugeChunkDataOffset = (sizeof(HugeChunk) + CommonHeaderSize + Alignment - 1) and -Alignment - CommonHeaderSize;
  390. end;
  391. class function HeapInc.SizeMinus1ToIndex(sizeMinus1: SizeUint): SizeUint;
  392. begin
  393. result := SizeMinus1Div16ToIndex[sizeMinus1 div 16];
  394. end;
  395. class function HeapInc.IndexToSize(sizeIndex: SizeUint): SizeUint;
  396. begin
  397. result := FixedSizes[sizeIndex];
  398. end;
  399. class function HeapInc.VarSizeToBinIndex(size: SizeUint; roundUp: boolean): SizeUint;
  400. var
  401. maxv, binClassIndex: SizeUint;
  402. begin
  403. if size >= MaxVarHeaderAndPayload then { Large sizes go to the last bin, assuming searches never search for more than MaxVarHeaderAndPayload. }
  404. exit(VarSizeClassesCount * VarSizesPerClass - 1);
  405. dec(size, MaxFixedHeaderAndPayload);
  406. binClassIndex := SizeUint(BsrDWord(NonZeroDWord(size)) - FirstVarRangeP2);
  407. if SizeInt(binClassIndex) < 0 then binClassIndex := 0;
  408. maxv := (SizeUint(2) shl binClassIndex - 1) shl FirstVarRangeP2;
  409. if size <= maxv then
  410. begin
  411. maxv := maxv shr 1; { Turn into “minv” to be subtracted from size. If size > maxv, “minv” is maxv. :) }
  412. maxv := maxv and SizeUint(-SizeInt(1 shl FirstVarRangeP2));
  413. dec(SizeInt(binClassIndex)); { Compensate inc(binClassIndex) below, so in the end, it is increased if size > maxv. All of this prevents having an “else” branch with its extra jump. }
  414. end;
  415. dec(size, maxv);
  416. inc(SizeInt(binClassIndex));
  417. result := binClassIndex * VarSizesPerClass + SizeUint(size - 1) shr (FirstVarStepP2 + binClassIndex);
  418. if not roundUp and (size and SizeUint(SizeUint(1) shl (FirstVarStepP2 + binClassIndex) - 1) <> 0) then
  419. dec(result);
  420. end;
  421. {$ifdef DEBUG_HEAP_INC}
  422. class function HeapInc.BinIndexToVarSize(binIndex: SizeUint): SizeUint;
  423. begin
  424. result := binIndex div VarSizesPerClass;
  425. result := MaxFixedHeaderAndPayload + (SizeUint(1) shl result - 1) shl FirstVarRangeP2 + (1 + binIndex mod VarSizesPerClass) shl (FirstVarStepP2 + result);
  426. end;
  427. {$endif DEBUG_HEAP_INC}
  428. function HeapInc.FreeOSChunkList.Get(minSize, maxSize: SizeUint): pOSChunk;
  429. var
  430. prev, next: pFreeOSChunk;
  431. begin
  432. result := first;
  433. while Assigned(result) and not ((result^.size >= minSize) and (result^.size <= maxSize)) do
  434. result := result^.next;
  435. if not Assigned(result) then
  436. exit;
  437. prev := result^.prev;
  438. next := result^.next;
  439. if Assigned(prev) then
  440. prev^.next := next
  441. else
  442. first := next;
  443. if Assigned(next) then
  444. next^.prev := prev
  445. else
  446. last := prev;
  447. {$ifdef HAS_SYSOSFREE} dec(n); {$endif}
  448. end;
  449. {$ifdef HAS_SYSOSFREE}
  450. function HeapInc.FreeOSChunkList.FreeOne: SizeUint;
  451. var
  452. best, prev: pFreeOSChunk;
  453. begin
  454. { Presently: the last one (which means LRU, as they are pushed to the beginning). }
  455. best := last;
  456. prev := best^.prev;
  457. if Assigned(prev) then
  458. prev^.next := nil
  459. else
  460. first := nil;
  461. last := prev;
  462. dec(n);
  463. result := best^.size;
  464. SysOSFree(best, best^.size);
  465. end;
  466. procedure HeapInc.FreeOSChunkList.FreeAll;
  467. var
  468. cur, next: pFreeOSChunk;
  469. begin
  470. cur := first;
  471. first := nil;
  472. last := nil;
  473. n := 0;
  474. while Assigned(cur) do
  475. begin
  476. next := cur^.next;
  477. SysOSFree(cur, cur^.size);
  478. cur := next;
  479. end;
  480. end;
  481. {$endif HAS_SYSOSFREE}
  482. procedure HeapInc.VarFreeMap.Add(c: pFreeVarChunk; binIndex: SizeUint);
  483. var
  484. next: pFreeVarChunk;
  485. iL0: SizeUint;
  486. vL0 {$ifdef HEAP_INC_USE_SETS}, vL1 {$endif}: uint32;
  487. begin
  488. next := bins[binIndex];
  489. c^.prev := nil;
  490. c^.next := next;
  491. c^.binIndex := binIndex;
  492. bins[binIndex] := c;
  493. if Assigned(next) then
  494. next^.prev := c
  495. else
  496. begin
  497. iL0 := binIndex div L0BinSize;
  498. vL0 := L0[iL0];
  499. {$ifdef HEAP_INC_USE_SETS}
  500. if vL0 = 0 then
  501. begin
  502. vL1 := L1;
  503. Include(Set32(vL1), iL0);
  504. L1 := vL1;
  505. end;
  506. Include(Set32(vL0), binIndex mod L0BinSize);
  507. L0[iL0] := vL0;
  508. {$else}
  509. if vL0 = 0 then
  510. L1 := L1 or uint32(1) shl iL0;
  511. L0[iL0] := vL0 or uint32(1) shl (binIndex mod L0BinSize);
  512. {$endif}
  513. end;
  514. end;
  515. procedure HeapInc.VarFreeMap.Remove(c: pFreeVarChunk);
  516. var
  517. prev, next: pFreeVarChunk;
  518. binIndex, iL0: SizeUint;
  519. v: uint32;
  520. begin
  521. prev := c^.prev;
  522. next := c^.next;
  523. if Assigned(next) then
  524. next^.prev := prev;
  525. if Assigned(prev) then
  526. prev^.next := next
  527. else
  528. begin
  529. binIndex := c^.binIndex;
  530. bins[binIndex] := next;
  531. if not Assigned(next) then
  532. begin
  533. iL0 := binIndex div L0BinSize;
  534. {$ifdef HEAP_INC_USE_SETS}
  535. v := L0[iL0];
  536. Exclude(Set32(v), binIndex mod L0BinSize);
  537. L0[iL0] := v;
  538. if v = 0 then
  539. begin
  540. v := L1;
  541. Exclude(Set32(v), iL0);
  542. L1 := v;
  543. end;
  544. {$else}
  545. v := L0[iL0] xor (uint32(1) shl (binIndex mod L0BinSize));
  546. L0[iL0] := v;
  547. if v = 0 then
  548. L1 := L1 xor (uint32(1) shl iL0);
  549. {$endif}
  550. end;
  551. end;
  552. end;
  553. {$ifdef DEBUG_HEAP_INC}
  554. procedure HeapInc.ThreadState.Dump(var f: text);
  555. var
  556. i: SizeInt;
  557. fix: pFixedArena;
  558. fr: pFreeOSChunk;
  559. {$ifdef FPC_HAS_FEATURE_THREADING}
  560. tf: pFreeChunk;
  561. {$endif}
  562. vf: pFreeVarChunk;
  563. vOs: pVarOSChunk;
  564. p: pointer;
  565. needLE, anything: boolean;
  566. procedure MaybeLE;
  567. begin
  568. if needLE then
  569. writeln(f);
  570. needLE := false;
  571. end;
  572. begin
  573. writeln(f, 'used = ', used, ', allocated = ', allocated, ', hugeUsed = ', gs.hugeUsed, ', maxUsed = ', maxUsed, ', maxAllocated = ', maxAllocated);
  574. needLE := true;
  575. anything := false;
  576. for i := 0 to FixedSizesCount - 1 do
  577. begin
  578. if not Assigned(partialArenas[i]) and (allocatedByFullArenas[i] = 0) then
  579. continue;
  580. MaybeLE;
  581. anything := true;
  582. write(f, 'Size #', i, ' (', IndexToSize(i), '):');
  583. if allocatedByFullArenas[i] <> 0 then
  584. write(f, ' allocatedByFullArenas = ', allocatedByFullArenas[i]);
  585. if Assigned(partialArenas[i]) then
  586. begin
  587. writeln(f);
  588. fix := partialArenas[i];
  589. repeat
  590. writeln(f, 'arena size = ', pVarHeader(fix)[-1].ch.h and VarSizeMask - VarHeaderSize - FixedArenaDataOffset, ', usedSizeMinus1 = ', fix^.usedSizeMinus1, ', almostFullThreshold = ', fix^.almostFullThreshold);
  591. fix := fix^.next;
  592. until not Assigned(fix);
  593. end
  594. else if allocatedByFullArenas[i] <> 0 then
  595. writeln(f);
  596. end;
  597. needLE := needLE or anything;
  598. if nEmptyArenas <> 0 then
  599. begin
  600. MaybeLE;
  601. writeln(f, 'nEmptyArenas = ', nEmptyArenas);
  602. needLE := true;
  603. end;
  604. vOs := varOS;
  605. while Assigned(vOs) do
  606. begin
  607. MaybeLE;
  608. writeln(f, 'Var OS chunk, size ', vOs^.size);
  609. p := pointer(vOs) + (VarOSChunkDataOffset + VarHeaderSize);
  610. repeat
  611. write(f, HexStr(p), ': ',
  612. 'prevSize = ', pVarHeader(p - VarHeaderSize)^.prevSize, ', size = ', pVarHeader(p - VarHeaderSize)^.ch.h and VarSizeMask);
  613. if pVarHeader(p - VarHeaderSize)^.ch.h and UsedFlag <> 0 then
  614. write(f, ', used')
  615. else
  616. write(f, ', f r e e');
  617. if pVarHeader(p - VarHeaderSize)^.ch.h and LastFlag <> 0 then
  618. write(f, ', last');
  619. if pVarHeader(p - VarHeaderSize)^.ch.h and FixedArenaFlag <> 0 then
  620. write(f, ', fixed arena');
  621. writeln(f);
  622. if pVarHeader(p - VarHeaderSize)^.ch.h and LastFlag <> 0 then
  623. break;
  624. p := p + pVarHeader(p - VarHeaderSize)^.ch.h and VarSizeMask;
  625. until false;
  626. needLE := true;
  627. vOs := vOs^.next;
  628. end;
  629. fr := freeOS.first;
  630. if Assigned(fr) then
  631. begin
  632. MaybeLE;
  633. repeat
  634. writeln(f, 'Free OS: ', HexStr(fr), ', size = ', fr^.size);
  635. fr := fr^.next;
  636. until not Assigned(fr);
  637. needLE := true;
  638. end;
  639. if varFree.L1 <> 0 then
  640. begin
  641. MaybeLE;
  642. write(f, 'L1:');
  643. for i := 0 to VarSizesCount div L0BinSize - 1 do
  644. if varFree.L1 shr i and 1 <> 0 then
  645. begin
  646. write(f, ' #', i, ' ', BinIndexToVarSize(i * L0BinSize), '-');
  647. if i = VarSizesCount div L0BinSize - 1 then
  648. write(f, 'inf')
  649. else
  650. write(f, BinIndexToVarSize((i + 1) * L0BinSize) - 1);
  651. end;
  652. writeln(f);
  653. write(f, 'L0 (bins):');
  654. for i := 0 to VarSizesCount - 1 do
  655. begin
  656. if varFree.L0[SizeUint(i) div L0BinSize] shr (SizeUint(i) mod L0BinSize) and 1 <> 0 then
  657. begin
  658. write(f, ' #', i, ' ', BinIndexToVarSize(i), '-');
  659. if i = VarSizesCount - 1 then
  660. write(f, 'inf')
  661. else
  662. write(f, BinIndexToVarSize(i + 1) - 1);
  663. end;
  664. if Assigned(varFree.bins[i]) then
  665. begin
  666. write(f, ' (');
  667. vf := varFree.bins[i];
  668. repeat
  669. if Assigned(vf^.prev) then write(f, ' ');
  670. write(f, pVarHeader(vf)[-1].ch.h and VarSizeMask);
  671. vf := vf^.next;
  672. until not Assigned(vf);
  673. write(f, ')');
  674. end;
  675. end;
  676. writeln(f);
  677. needLE := true;
  678. end;
  679. {$ifdef FPC_HAS_FEATURE_THREADING}
  680. tf := toFree;
  681. if Assigned(tf) then
  682. begin
  683. MaybeLE;
  684. write(f, 'To-free:');
  685. repeat
  686. if pCommonHeader(pointer(tf) - CommonHeaderSize)^.h and FixedFlag <> 0 then
  687. write(f, ' f ', CommonHeaderSize + SysMemSize(tf))
  688. else
  689. write(f, ' v ', VarHeaderSize + SysMemSize(tf));
  690. tf := tf^.next;
  691. until not Assigned(tf);
  692. writeln(f);
  693. end;
  694. {$endif FPC_HAS_FEATURE_THREADING}
  695. end;
  696. {$endif DEBUG_HEAP_INC}
  697. function HeapInc.ThreadState.ChooseFixedArenaSize(sizeIndex: SizeUint): SizeUint;
  698. begin
  699. result := (allocatedByFullArenas[sizeIndex] div 8 + (FixedArenaSizeQuant - 1)) and SizeUint(-FixedArenaSizeQuant); { 12.5% of memory allocated by the size. }
  700. if result < MinFixedArenaSize then
  701. result := MinFixedArenaSize;
  702. if result > MaxFixedArenaSize then
  703. result := MaxFixedArenaSize;
  704. dec(result, VarHeaderSize + VarSizeQuant); { Prettier fit into OS chunks. }
  705. end;
  706. function HeapInc.ThreadState.AllocFixed(size: SizeUint): pointer;
  707. var
  708. sizeIndex, sizeUp, statv: SizeUint;
  709. usedSizeMinus1: int32;
  710. arena, nextArena: pFixedArena;
  711. begin
  712. sizeIndex := SizeMinus1ToIndex(size + (CommonHeaderSize - 1));
  713. arena := partialArenas[sizeIndex];
  714. if not Assigned(arena) then
  715. begin
  716. {$ifdef FPC_HAS_FEATURE_THREADING}
  717. if Assigned(toFree) then
  718. begin
  719. FlushToFree;
  720. arena := partialArenas[sizeIndex];
  721. end;
  722. if not Assigned(arena) then
  723. {$endif FPC_HAS_FEATURE_THREADING}
  724. begin
  725. arena := emptyArenas;
  726. if Assigned(arena) then
  727. begin
  728. emptyArenas := arena^.next;
  729. dec(nEmptyArenas);
  730. end else
  731. begin
  732. arena := AllocVar(ChooseFixedArenaSize(sizeIndex), true);
  733. if not Assigned(arena) then
  734. exit(nil);
  735. { Size index of the first chunk in the arena is used to determine if it can be reused. Set a purposely mismatching value for freshly allocated arena. }
  736. pCommonHeader(pointer(arena) + FixedArenaDataOffset)^.h := uint32(not sizeIndex);
  737. end;
  738. if pCommonHeader(pointer(arena) + FixedArenaDataOffset)^.h and SizeIndexMask = sizeIndex then
  739. { Lucky! Just don’t reset the chunk and use its old freelist. }
  740. else
  741. begin
  742. arena^.firstFreeChunk := nil;
  743. arena^.usedSizeMinus1 := uint32(-1);
  744. arena^.almostFullThreshold := pVarHeader(arena)[-1].ch.h and VarSizeMask - 2 * IndexToSize(sizeIndex) - (VarHeaderSize + FixedArenaDataOffset); { available space - 2 * chunk size. }
  745. end;
  746. { Add arena to partialArenas[sizeIndex]. }
  747. nextArena := partialArenas[sizeIndex];
  748. arena^.prev := nil;
  749. arena^.next := nextArena;
  750. if Assigned(nextArena) then
  751. nextArena^.prev := arena;
  752. partialArenas[sizeIndex] := arena;
  753. end;
  754. end;
  755. sizeUp := IndexToSize(sizeIndex); { Not reusing the “size” variable saved a register at the time of writing this comment. }
  756. statv := used + sizeUp;
  757. used := statv;
  758. inc(statv, gs.hugeUsed);
  759. if statv > maxUsed then
  760. maxUsed := statv;
  761. { arena from partialArenas has either free chunk or free unformatted space for a new chunk. }
  762. usedSizeMinus1 := int32(arena^.usedSizeMinus1);
  763. result := arena^.firstFreeChunk;
  764. if not Assigned(result) then
  765. begin
  766. { Freelist is empty, so “formattedSize” = usedSizeMinus1 + 1. This “+ 1” is folded into constants. }
  767. result := pointer(arena) + (FixedArenaDataOffset + CommonHeaderSize + 1) + usedSizeMinus1;
  768. pCommonHeader(result - CommonHeadersize)^.h := uint32(int32(sizeIndex) + int32(usedSizeMinus1 shl FixedArenaOffsetShift) +
  769. (FixedFlag + (FixedArenaDataOffset + CommonHeaderSize + 1) shl FixedArenaOffsetShift) { ← const });
  770. end else
  771. arena^.firstFreeChunk := pFreeChunk(result)^.next;
  772. arena^.usedSizeMinus1 := uint32(usedSizeMinus1 + int32(sizeUp));
  773. if usedSizeMinus1 >= int32(arena^.almostFullThreshold) then { Uses usedSizeMinus1 value before adding sizeUp, as assumed by almostFullThreshold. }
  774. begin
  775. inc(allocatedByFullArenas[sizeIndex], pVarHeader(arena)[-1].ch.h); { Without masking with VarSizeMask, ch.h has parasite bits, but they don’t matter as long as they are unchanged, so the same value will be subtracted. }
  776. { Remove arena from partialArenas[sizeIndex]. (It was first.) }
  777. nextArena := arena^.next;
  778. partialArenas[sizeIndex] := nextArena;
  779. if Assigned(nextArena) then
  780. nextArena^.prev := nil;
  781. end;
  782. end;
  783. function HeapInc.ThreadState.FreeFixed(p: pointer): SizeUint;
  784. var
  785. sizeIndex: SizeUint;
  786. usedSizeMinus1: int32;
  787. arena, prevArena, nextArena: pFixedArena;
  788. begin
  789. arena := p - pCommonHeader(p - CommonHeaderSize)^.h shr FixedArenaOffsetShift;
  790. {$ifdef FPC_HAS_FEATURE_THREADING}
  791. { This can be checked without blocking; <arena>.threadState can only change from one value not equal to @self to another value not equal to @self. }
  792. if pVarHeader(arena)[-1].threadState <> @self then
  793. begin
  794. EnterCriticalSection(gs.lock);
  795. if Assigned(pVarHeader(arena)[-1].threadState) then
  796. begin
  797. { Despite atomic Push lock must be held as otherwise target thread might end and destroy its threadState.
  798. However, target thread won’t block to free p, so PushToFree instantly invalidates p. }
  799. result := IndexToSize(pCommonHeader(p - CommonHeaderSize)^.h and SizeIndexMask) - CommonHeaderSize;
  800. pVarHeader(arena)[-1].threadState^.PushToFree(p);
  801. LeaveCriticalSection(gs.lock);
  802. exit;
  803. end;
  804. AdoptVarOwner(arena); { ...And continue! }
  805. LeaveCriticalSection(gs.lock);
  806. end;
  807. {$endif FPC_HAS_FEATURE_THREADING}
  808. pFreeChunk(p)^.next := arena^.firstFreeChunk;
  809. arena^.firstFreeChunk := p;
  810. sizeIndex := pCommonHeader(p - CommonHeaderSize)^.h and SizeIndexMask;
  811. result := IndexToSize(sizeIndex);
  812. dec(used, result);
  813. usedSizeMinus1 := int32(arena^.usedSizeMinus1) - int32(result);
  814. arena^.usedSizeMinus1 := uint32(usedSizeMinus1);
  815. dec(result, CommonHeaderSize);
  816. { “(usedSizeMinus1 = -1) or (usedSizeMinus1 >= arena^.almostFullThreshold)” as 1 comparison. }
  817. if uint32(usedSizeMinus1) >= arena^.almostFullThreshold then
  818. if usedSizeMinus1 <> -1 then
  819. begin
  820. dec(allocatedByFullArenas[sizeIndex], pVarHeader(arena)[-1].ch.h);
  821. { Add arena to partialArenas[sizeIndex]. }
  822. nextArena := partialArenas[sizeIndex];
  823. arena^.next := nextArena;
  824. if Assigned(nextArena) then
  825. nextArena^.prev := arena;
  826. partialArenas[sizeIndex] := arena;
  827. end else
  828. begin
  829. { Remove arena from partialArenas[sizeIndex], add to emptyArenas (maybe). }
  830. prevArena := arena^.prev;
  831. nextArena := arena^.next;
  832. if Assigned(prevArena) then
  833. prevArena^.next := nextArena
  834. else
  835. partialArenas[sizeIndex] := nextArena;
  836. if Assigned(nextArena) then
  837. nextArena^.prev := prevArena;
  838. if nEmptyArenas < MaxKeptFixedArenas then
  839. begin
  840. arena^.next := emptyArenas;
  841. emptyArenas := arena;
  842. inc(nEmptyArenas);
  843. end else
  844. FreeVar(arena);
  845. end;
  846. end;
  847. function HeapInc.ThreadState.GetOSChunk(minSize, maxSize: SizeUint): pOSChunk;
  848. {$if defined(FPC_HAS_FEATURE_THREADING) and not defined(HAS_SYSOSFREE)}
  849. var
  850. statv: SizeUint;
  851. {$endif FPC_HAS_FEATURE_THREADING and not HAS_SYSOSFREE}
  852. begin
  853. result := freeOS.Get(minSize, maxSize);
  854. if Assigned(result) then
  855. exit;
  856. {$if defined(FPC_HAS_FEATURE_THREADING) and not defined(HAS_SYSOSFREE)}
  857. if Assigned(gs.freeOS.first) then { Racing precheck. }
  858. begin
  859. EnterCriticalSection(gs.lock);
  860. result := gs.freeOS.Get(minSize, maxSize);
  861. LeaveCriticalSection(gs.lock);
  862. if Assigned(result) then
  863. begin
  864. statv := allocated + result^.size;
  865. allocated := statv;
  866. inc(statv, gs.hugeUsed);
  867. if statv > maxAllocated then
  868. maxAllocated := statv;
  869. exit;
  870. end;
  871. end;
  872. {$endif FPC_HAS_FEATURE_THREADING and not HAS_SYSOSFREE}
  873. result := AllocateOSChunk(minSize, maxSize);
  874. end;
  875. function HeapInc.ThreadState.AllocateOSChunk(minSize, maxSize: SizeUint): pOSChunk;
  876. var
  877. query, statv: SizeUint;
  878. begin
  879. query := used div 16 + minSize div 2; { Base: 6.25% of the memory used, so if GrowHeapSize2 = 1 Mb, 1 Mb OS allocations start at 16 Mb used. }
  880. if query > maxSize then { Limit by maxSize (usually GrowHeapSize2). }
  881. query := maxSize;
  882. if query < minSize then { But of course allocate at least the amount requested. Also triggers if maxSize was wrong (smaller than minSize). }
  883. query := minSize;
  884. query := (query + (OSChunkVarSizeQuant - 1)) and SizeUint(-OSChunkVarSizeQuant); { Quantize. }
  885. result := SysOSAlloc(query);
  886. if not Assigned(result) and (query > minSize) then
  887. begin
  888. query := minSize;
  889. result := SysOSAlloc(query);
  890. end;
  891. if not Assigned(result) then
  892. exit(AllocFailed);
  893. result^.size := query;
  894. statv := allocated + query;
  895. allocated := statv;
  896. inc(statv, gs.hugeUsed);
  897. if statv > maxAllocated then
  898. maxAllocated := statv;
  899. end;
  900. function HeapInc.ThreadState.AllocVar(size: SizeUint; isArena: boolean): pointer;
  901. var
  902. fv: pFreeVarChunk;
  903. osChunk, osNext: pVarOSChunk;
  904. binIndex, vSizeFlags, statv: SizeUint;
  905. mask: uint32;
  906. begin
  907. size := (size + (VarHeaderSize + VarSizeQuant - 1)) and SizeUint(-VarSizeQuant);
  908. {$if (MaxFixedHeaderAndPayload - CommonHeaderSize + 1 + VarHeaderSize + VarSizeQuant - 1) div VarSizeQuant * VarSizeQuant < MinEmptyVarHeaderAndPayload}
  909. { Chunk will get freed one day. As a result, it might turn into a free chunk of the same size.
  910. Consequently, it must not be smaller than MinEmptyVarHeaderAndPayload.
  911. This can be a dead case depending on the constants, which is checked by the enclosing compile-time check. :)
  912. Also applies to TryResizeVar. }
  913. if size < MinEmptyVarHeaderAndPayload then
  914. size := MinEmptyVarHeaderAndPayload;
  915. {$endif}
  916. {$ifdef FPC_HAS_FEATURE_THREADING}
  917. if Assigned(toFree) then
  918. FlushToFree;
  919. {$endif}
  920. { Search varFree for (roughly) smallest chunk ≥ size. }
  921. binIndex := VarSizeToBinIndex(size, true);
  922. fv := varFree.bins[binIndex];
  923. osChunk := nil; { If remains nil, fv comes from varFree and must be removed. }
  924. if not Assigned(fv) then
  925. begin
  926. mask := varFree.L0[binIndex div L0BinSize] shr (binIndex mod L0BinSize); { Logically should be “1 + binIndex mod L0BinSize” but the bit that represents the binIndex-th bin is 0 anyway. }
  927. if mask <> 0 then
  928. fv := varFree.bins[binIndex + BsfDWord(NonZeroDWord(mask))]
  929. else
  930. begin
  931. mask := varFree.L1 and (SizeUint(-2) shl (binIndex div L0BinSize));
  932. if mask <> 0 then
  933. begin
  934. binIndex := BsfDWord(NonZeroDWord(mask)); { Index at L0. }
  935. fv := varFree.bins[binIndex * L0BinSize + BsfDWord(NonZeroDWord(varFree.L0[binIndex]))];
  936. end else
  937. begin
  938. { No such a chunk, allocate a new one. }
  939. osChunk := pVarOSChunk(GetOSChunk(VarOSChunkDataOffset + size, GrowHeapSize2));
  940. if not Assigned(osChunk) then
  941. exit(nil);
  942. { Add osChunk to varOS. }
  943. osNext := varOS;
  944. osChunk^.prev := nil;
  945. osChunk^.next := osNext;
  946. if Assigned(osNext) then
  947. osNext^.prev := osChunk;
  948. varOS := osChunk;
  949. { Format new free var chunk spanning the entire osChunk. }
  950. fv := pointer(osChunk) + (VarOSChunkDataOffset + VarHeaderSize);
  951. pVarHeader(pointer(fv) - VarHeaderSize)^.prevSize := 0;
  952. {$ifdef FPC_HAS_FEATURE_THREADING}
  953. pVarHeader(pointer(fv) - VarHeaderSize)^.threadState := @self;
  954. {$endif}
  955. pVarHeader(pointer(fv) - VarHeaderSize)^.ch.h := (uint32(osChunk^.size) - VarOSChunkDataOffset) and VarSizeMask + LastFlag;
  956. end;
  957. end;
  958. end;
  959. if not Assigned(osChunk) then
  960. varFree.Remove(fv);
  961. { Result will be allocated at the beginning of fv; maybe format the remainder and add it back to varFree. }
  962. result := fv;
  963. vSizeFlags := pVarHeader(fv)[-1].ch.h - size; { Inherits LastFlag. }
  964. if vSizeFlags >= MinEmptyVarHeaderAndPayload then { Logically “vSizeFlags and VarSizeMask” but here it’s okay to not mask. }
  965. begin
  966. inc(pointer(fv), size); { result = allocated block, fv = remainder. }
  967. pVarHeader(pointer(fv) - VarHeaderSize)^.prevSize := size;
  968. {$ifdef FPC_HAS_FEATURE_THREADING}
  969. pVarHeader(pointer(fv) - VarHeaderSize)^.threadState := @self;
  970. {$endif}
  971. pVarHeader(pointer(fv) - VarHeaderSize)^.ch.h := vSizeFlags;
  972. if vSizeFlags and LastFlag = 0 then
  973. pVarHeader(pointer(fv) + vSizeFlags - VarHeaderSize)^.prevSize := vSizeFlags; { All flags are 0. }
  974. varFree.Add(fv, VarSizeToBinIndex(vSizeFlags and VarSizeMask, false));
  975. pVarHeader(result - VarHeaderSize)^.ch.h := uint32(size) + UsedFlag;
  976. end else
  977. begin
  978. { Use the entire chunk. }
  979. inc(vSizeFlags, size);
  980. pVarHeader(result - VarHeaderSize)^.ch.h := uint32(vSizeFlags) + UsedFlag;
  981. size := vSizeFlags and VarSizeMask;
  982. end;
  983. if isArena then
  984. inc(pVarHeader(result)[-1].ch.h, FixedArenaFlag) { Arenas aren’t counted in “used” directly. }
  985. else
  986. begin
  987. statv := used + size;
  988. used := statv;
  989. inc(statv, gs.hugeUsed);
  990. if statv > maxUsed then
  991. maxUsed := statv;
  992. end;
  993. end;
  994. function HeapInc.ThreadState.FreeVar(p: pointer): SizeUint;
  995. var
  996. p2: pointer;
  997. fSizeFlags, prevSize, hPrev, hNext: SizeUint;
  998. osChunk, osPrev, osNext: pVarOSChunk;
  999. freeOsNext: pFreeOSChunk;
  1000. begin
  1001. {$ifdef FPC_HAS_FEATURE_THREADING}
  1002. if pVarHeader(p - VarHeaderSize)^.threadState <> @self then
  1003. begin
  1004. EnterCriticalSection(gs.lock);
  1005. if Assigned(pVarHeader(p - VarHeaderSize)^.threadState) then
  1006. begin
  1007. { Despite atomic Push lock must be held as otherwise target thread might end and destroy its threadState.
  1008. However, target thread won’t block to free p, so PushToFree instantly invalidates p. }
  1009. result := pVarHeader(p - VarHeaderSize)^.ch.h and VarSizeMask - VarHeaderSize;
  1010. pVarHeader(p - VarHeaderSize)^.threadState^.PushToFree(p);
  1011. LeaveCriticalSection(gs.lock);
  1012. exit;
  1013. end;
  1014. AdoptVarOwner(p); { ...And continue! }
  1015. LeaveCriticalSection(gs.lock);
  1016. end;
  1017. {$endif FPC_HAS_FEATURE_THREADING}
  1018. fSizeFlags := pVarHeader(p - VarHeaderSize)^.ch.h;
  1019. result := fSizeFlags and VarSizeMask;
  1020. if fSizeFlags and FixedArenaFlag = 0 then
  1021. dec(used, result)
  1022. else
  1023. dec(fSizeFlags, FixedArenaFlag);
  1024. { If next/prev are free, remove them from varFree and merge with f — (f)uture (f)ree chunk that starts at p, has fSizeFlags,
  1025. and conveniently always inherits prevSize of its final location. }
  1026. if fSizeFlags and LastFlag = 0 then
  1027. begin
  1028. p2 := p + result;
  1029. hNext := pVarHeader(p2 - VarHeaderSize)^.ch.h;
  1030. if uint32(hNext) and UsedFlag = 0 then
  1031. begin
  1032. inc(fSizeFlags, hNext); { Inherit LastFlag, other p2 flags must be 0. }
  1033. varFree.Remove(p2);
  1034. end;
  1035. end;
  1036. prevSize := pVarHeader(p - VarHeaderSize)^.prevSize;
  1037. if prevSize <> 0 then
  1038. begin
  1039. p2 := p - prevSize;
  1040. hPrev := pVarHeader(p2 - VarHeaderSize)^.ch.h;
  1041. if uint32(hPrev) and UsedFlag = 0 then
  1042. begin
  1043. p := p2;
  1044. inc(fSizeFlags, hPrev); { All p2 flags must be 0. }
  1045. varFree.Remove(p2);
  1046. end;
  1047. end;
  1048. { Turn p into a free chunk and add it back to varFree...
  1049. unless it spans the entire OS chunk, in which case instead move the chunk from varOS to freeOS. }
  1050. if (fSizeFlags and LastFlag = 0) or (pVarHeader(p - VarHeaderSize)^.prevSize <> 0) then
  1051. begin
  1052. dec(fSizeFlags, UsedFlag);
  1053. if fSizeFlags and LastFlag = 0 then
  1054. pVarHeader(p + fSizeFlags - VarHeaderSize)^.prevSize := fSizeFlags; { All fSizeFlags flags are 0. }
  1055. pVarHeader(p - VarHeaderSize)^.ch.h := fSizeFlags;
  1056. varFree.Add(p, VarSizeToBinIndex(fSizeFlags and VarSizeMask, false));
  1057. end else
  1058. begin
  1059. osChunk := p - (VarOSChunkDataOffset + VarHeaderSize);
  1060. { Remove osChunk from varOS. }
  1061. osPrev := osChunk^.prev;
  1062. osNext := osChunk^.next;
  1063. if Assigned(osPrev) then
  1064. osPrev^.next := osNext
  1065. else
  1066. varOS := osNext;
  1067. if Assigned(osNext) then
  1068. osNext^.prev := osPrev;
  1069. { Instantly free if huge. }
  1070. {$ifdef HAS_SYSOSFREE}
  1071. if osChunk^.size > GrowHeapSize2 then
  1072. begin
  1073. dec(allocated, osChunk^.size);
  1074. SysOSFree(osChunk, osChunk^.size);
  1075. end else
  1076. {$endif}
  1077. begin
  1078. { Add to freeOS. }
  1079. freeOsNext := freeOS.first;
  1080. osChunk^.prev := nil;
  1081. osChunk^.next := freeOsNext;
  1082. if Assigned(freeOsNext) then
  1083. freeOsNext^.prev := osChunk
  1084. else
  1085. freeOS.last := pFreeOSChunk(osChunk);
  1086. freeOS.first := pFreeOSChunk(osChunk);
  1087. {$ifdef HAS_SYSOSFREE}
  1088. inc(freeOS.n);
  1089. if freeOS.n > MaxKeptOSChunks then
  1090. dec(allocated, freeOS.FreeOne);
  1091. {$endif}
  1092. end;
  1093. end;
  1094. dec(result, VarHeaderSize);
  1095. end;
  1096. function HeapInc.ThreadState.TryResizeVar(p: pointer; size: SizeUint): pointer;
  1097. var
  1098. fp, p2: pointer;
  1099. oldpsize, fSizeFlags, growby, statv: SizeUint;
  1100. begin
  1101. if (size <= MaxFixedHeaderAndPayload - CommonHeaderSize)
  1102. or (size > GrowHeapSize2) { Not strictly necessary but rejects clearly wrong values early so adding headers to the size doesn’t overflow. }
  1103. {$ifdef FPC_HAS_FEATURE_THREADING}
  1104. or (pVarHeader(p - VarHeaderSize)^.threadState <> @self)
  1105. {$endif}
  1106. then
  1107. exit(nil);
  1108. size := (size + (VarHeaderSize + VarSizeQuant - 1)) and SizeUint(-VarSizeQuant);
  1109. {$if (MaxFixedHeaderAndPayload - CommonHeaderSize + 1 + VarHeaderSize + VarSizeQuant - 1) div VarSizeQuant * VarSizeQuant < MinEmptyVarHeaderAndPayload}
  1110. if size < MinEmptyVarHeaderAndPayload then
  1111. size := MinEmptyVarHeaderAndPayload;
  1112. {$endif}
  1113. result := p; { From now on use result instead of p (saves a register). }
  1114. oldpsize := pVarHeader(result - VarHeaderSize)^.ch.h and VarSizeMask;
  1115. p2 := result + oldpsize;
  1116. { (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. }
  1117. if size <= oldpsize then
  1118. begin
  1119. { Shrink. Maybe. }
  1120. fSizeFlags := oldpsize - size;
  1121. if (pVarHeader(result - VarHeaderSize)^.ch.h and LastFlag <> 0) or (pVarHeader(p2 - VarHeaderSize)^.ch.h and UsedFlag <> 0) then
  1122. begin
  1123. { No empty chunk to the right: create free chunk if ≥ MinEmptyVarHeaderAndPayload, otherwise report success but change nothing. }
  1124. if fSizeFlags < MinEmptyVarHeaderAndPayload then
  1125. exit;
  1126. dec(used, fSizeFlags);
  1127. inc(fSizeFlags, pVarHeader(result - VarHeaderSize)^.ch.h and LastFlag);
  1128. end else
  1129. begin
  1130. if fSizeFlags = 0 then { Exit early if going to be a no-op. Branch above does the same with a broader check. }
  1131. exit;
  1132. dec(used, fSizeFlags);
  1133. { Has empty chunk to the right: extend with freed space. }
  1134. inc(fSizeFlags, pVarHeader(p2 - VarHeaderSize)^.ch.h); { Adds size and last flag, other bits are 0. }
  1135. varFree.Remove(p2);
  1136. end;
  1137. { Update p size. }
  1138. pVarHeader(result - VarHeaderSize)^.ch.h := uint32(size) + UsedFlag;
  1139. end
  1140. { Grow if there is free space. Note this can result in a chunk larger than e.g. SysGetMem allows (GrowHeapSize div 2 or so). That’s okay as it saves a Move. }
  1141. else if (pVarHeader(result - VarHeaderSize)^.ch.h and LastFlag = 0) and (pVarHeader(p2 - VarHeaderSize)^.ch.h and UsedFlag = 0) and
  1142. (pVarHeader(p2)[-1].ch.h >= SizeUint(size - oldpsize)) { Can check without “and VarSizeMask”, will remain ≥ anyway. }
  1143. then
  1144. begin
  1145. fSizeFlags := pVarHeader(p2)[-1].ch.h - (size - oldpsize); { Inherits LastFlag, other flags are 0. }
  1146. if fSizeFlags < MinEmptyVarHeaderAndPayload then
  1147. fSizeFlags := fSizeFlags and LastFlag;
  1148. growby := pVarHeader(p2)[-1].ch.h - fSizeFlags;
  1149. size := oldpsize + growby;
  1150. statv := used + growby;
  1151. used := statv;
  1152. inc(statv, gs.hugeUsed);
  1153. if statv > maxUsed then
  1154. maxUsed := statv;
  1155. varFree.Remove(p2);
  1156. { Update p size. }
  1157. pVarHeader(result - VarHeaderSize)^.ch.h := uint32(size) + UsedFlag;
  1158. { No empty chunk? }
  1159. if fSizeFlags <= LastFlag then
  1160. begin
  1161. inc(pVarHeader(result - VarHeaderSize)^.ch.h, fSizeFlags); { Either += LastFlag or a no-op. }
  1162. if fSizeFlags = 0 then { logically “and LastFlag = 0” }
  1163. pVarHeader(result + size - VarHeaderSize)^.prevSize := size;
  1164. exit;
  1165. end;
  1166. end else
  1167. { Possible another case to handle: on growth, if there is no space to the right but there is space to the LEFT, move the data there, avoiding the GetMem + FreeMem.
  1168. Probably not common enough, but I didn’t even investigate. }
  1169. exit(nil);
  1170. { Format new free var chunk. }
  1171. fp := result + size;
  1172. pVarHeader(fp - VarHeaderSize)^.prevSize := size;
  1173. {$ifdef FPC_HAS_FEATURE_THREADING}
  1174. pVarHeader(fp - VarHeaderSize)^.threadState := @self;
  1175. {$endif}
  1176. pVarHeader(fp - VarHeaderSize)^.ch.h := fSizeFlags;
  1177. if fSizeFlags and LastFlag = 0 then
  1178. pVarHeader(fp + fSizeFlags - VarHeaderSize)^.prevSize := fSizeFlags; { All flags are 0. }
  1179. varFree.Add(fp, VarSizeToBinIndex(fSizeFlags and VarSizeMask, false));
  1180. end;
  1181. { If SysOSFree is available, huge chunks aren’t cached by any means.
  1182. If SysOSFree is not available, there’s no choice but to cache them.
  1183. Caching is done directly into gs.freeOS if FPC_HAS_FEATURE_THREADING, otherwise ThreadState.freeOS. }
  1184. function HeapInc.ThreadState.AllocHuge(size: SizeUint): pointer;
  1185. var
  1186. userSize, hugeUsed: SizeUint;
  1187. begin
  1188. userSize := size;
  1189. size := (size + (HugeChunkDataOffset + CommonHeaderSize + OSChunkVarSizeQuant - 1)) and SizeUint(-OSChunkVarSizeQuant);
  1190. if size < userSize then { Overflow. }
  1191. exit(AllocFailed);
  1192. {$ifdef FPC_HAS_FEATURE_THREADING}
  1193. if Assigned(toFree) then
  1194. FlushToFree;
  1195. {$endif}
  1196. {$ifdef HAS_SYSOSFREE}
  1197. result := SysOSAlloc(size);
  1198. if not Assigned(result) then
  1199. exit(AllocFailed);
  1200. pHugeChunk(result)^.size := size;
  1201. {$else HAS_SYSOSFREE}
  1202. result := GetOSChunk(size, High(SizeUint));
  1203. if not Assigned(result) then
  1204. exit; { GetOSChunk throws an error if required. }
  1205. size := pOSChunk(result)^.size;
  1206. dec(allocated, size); { After GetOSChunk chunk size is counted in “allocated”; don’t count. }
  1207. {$endif HAS_SYSOSFREE}
  1208. pCommonHeader(result + HugeChunkDataOffset)^.h := HugeHeader;
  1209. inc(result, HugeChunkDataOffset + CommonHeaderSize);
  1210. {$ifdef FPC_HAS_FEATURE_THREADING} EnterCriticalSection(gs.lock); {$endif}
  1211. hugeUsed := gs.hugeUsed + size;
  1212. gs.hugeUsed := hugeUsed;
  1213. {$ifdef FPC_HAS_FEATURE_THREADING} LeaveCriticalSection(gs.lock); {$endif}
  1214. UpdateMaxStats(hugeUsed);
  1215. end;
  1216. function HeapInc.ThreadState.FreeHuge(p: pointer): SizeUint;
  1217. {$ifndef HAS_SYSOSFREE}
  1218. var
  1219. fOs: ^FreeOSChunkList;
  1220. osPrev: pOSChunk;
  1221. {$endif ndef HAS_SYSOSFREE}
  1222. begin
  1223. dec(p, HugeChunkDataOffset + CommonHeaderSize);
  1224. result := pHugeChunk(p)^.size;
  1225. {$ifdef FPC_HAS_FEATURE_THREADING} EnterCriticalSection(gs.lock); {$endif}
  1226. dec(gs.hugeUsed, result);
  1227. {$ifndef HAS_SYSOSFREE} { But you’d better have SysOSFree... }
  1228. {$ifdef FPC_HAS_FEATURE_THREADING}
  1229. fOs := @gs.freeOS; { gs.freeOS aren’t counted anywhere (for now). }
  1230. {$else FPC_HAS_FEATURE_THREADING}
  1231. fOs := @freeOS;
  1232. inc(allocated, result); { ThreadState.freeOS are counted in ThreadState.allocated. But since “size” (= result) is just moved from “hugeUsed” to “allocated”, it won’t affect maximums. }
  1233. {$endif FPC_HAS_FEATURE_THREADING}
  1234. { Turn p into FreeOSChunk and add to fOs; add to the end to reduce the chance for this chunk to be reused
  1235. (other OS chunks are added to the beginning and searched from the beginning). }
  1236. osPrev := fOs^.last;
  1237. pFreeOSChunk(p)^.prev := osPrev;
  1238. pFreeOSChunk(p)^.next := nil;
  1239. if Assigned(osPrev) then
  1240. osPrev^.next := p
  1241. else
  1242. fOs^.first := p;
  1243. fOs^.last := p;
  1244. {$endif ndef HAS_SYSOSFREE}
  1245. {$ifdef FPC_HAS_FEATURE_THREADING} LeaveCriticalSection(gs.lock); {$endif}
  1246. {$ifdef HAS_SYSOSFREE} SysOSFree(p, result); {$endif}
  1247. dec(result, HugeChunkDataOffset + CommonHeaderSize);
  1248. end;
  1249. function HeapInc.ThreadState.TryResizeHuge(p: pointer; size: SizeUint): pointer;
  1250. var
  1251. userSize, oldSize: SizeUint;
  1252. begin
  1253. userSize := size;
  1254. size := (size + (HugeChunkDataOffset + CommonHeaderSize + OSChunkVarSizeQuant - 1)) and SizeUint(-OSChunkVarSizeQuant);
  1255. if (size < userSize) or { Overflow. }
  1256. (size < GrowHeapSize2 div 4) { Limit on shrinking huge chunks. }
  1257. then
  1258. exit(nil);
  1259. oldSize := pHugeChunk(p - (HugeChunkDataOffset + CommonHeaderSize))^.size;
  1260. if size = oldSize then
  1261. exit(p);
  1262. {$ifdef FPC_SYSTEM_HAS_SYSOSREALLOC}
  1263. result := SysOSRealloc(p - (HugeChunkDataOffset + CommonHeaderSize), oldSize, size);
  1264. if Assigned(result) then
  1265. begin
  1266. {$ifdef FPC_HAS_FEATURE_THREADING} EnterCriticalSection(gs.lock); {$endif}
  1267. gs.hugeUsed := gs.hugeUsed - oldSize + size;
  1268. {$ifdef FPC_HAS_FEATURE_THREADING} LeaveCriticalSection(gs.lock); {$endif}
  1269. if size > oldSize then
  1270. UpdateMaxStats(gs.hugeUsed);
  1271. pHugeChunk(result)^.size := size;
  1272. inc(result, HugeChunkDataOffset + CommonHeaderSize);
  1273. end;
  1274. {$else FPC_SYSTEM_HAS_SYSOSREALLOC}
  1275. result := nil; { Just don’t. Note shrinking 20 Mb to 19 will require temporary 39 because of this. }
  1276. {$endif FPC_SYSTEM_HAS_SYSOSREALLOC}
  1277. end;
  1278. procedure HeapInc.ThreadState.UpdateMaxStats(hugeUsed: SizeUint);
  1279. var
  1280. statv: SizeUint;
  1281. begin
  1282. statv := used + hugeUsed;
  1283. if statv > maxUsed then
  1284. maxUsed := statv;
  1285. statv := allocated + hugeUsed;
  1286. if statv > maxAllocated then
  1287. maxAllocated := statv;
  1288. end;
  1289. {$ifdef FPC_HAS_FEATURE_THREADING}
  1290. procedure HeapInc.ThreadState.PushToFree(p: pFreeChunk);
  1291. var
  1292. next: pFreeChunk;
  1293. begin
  1294. repeat
  1295. next := toFree;
  1296. p^.next := next;
  1297. WriteBarrier; { Write p after p^.next. }
  1298. until InterlockedCompareExchange(toFree, p, next) = next;
  1299. end;
  1300. procedure HeapInc.ThreadState.FlushToFree;
  1301. var
  1302. tf, nx: pFreeChunk;
  1303. begin
  1304. tf := InterlockedExchange(toFree, nil);
  1305. while Assigned(tf) do
  1306. begin
  1307. ReadDependencyBarrier; { Read toFree^.next after toFree. }
  1308. nx := tf^.next;
  1309. SysFreeMem(tf);
  1310. tf := nx;
  1311. end;
  1312. end;
  1313. procedure HeapInc.ThreadState.Orphan;
  1314. var
  1315. arena: pFixedArena;
  1316. vOs, nextVOs, lastVOs: pVarOSChunk;
  1317. {$ifndef HAS_SYSOSFREE}
  1318. lastFree, nextFree: pFreeOSChunk;
  1319. {$endif not HAS_SYSOSFREE}
  1320. begin
  1321. if gs.lockUse > 0 then
  1322. EnterCriticalSection(HeapInc.gs.lock);
  1323. FlushToFree; { Performing it under gs.lock guarantees there will be no new toFree requests. }
  1324. { Has to free all empty arenas, otherwise the chunk that contains only empty arenas will leak (no one will ever adopt it, as it has nothing to free). }
  1325. while nEmptyArenas > 0 do
  1326. begin
  1327. arena := emptyArenas;
  1328. emptyArenas := arena^.next;
  1329. dec(nEmptyArenas);
  1330. FreeVar(arena);
  1331. end;
  1332. {$ifndef HAS_SYSOSFREE}
  1333. { Prepend freeOS to gs.freeOS. }
  1334. lastFree := freeOS.last;
  1335. if Assigned(lastFree) then
  1336. begin
  1337. nextFree := gs.freeOS.first;
  1338. lastFree^.next := nextFree;
  1339. if Assigned(nextFree) then
  1340. nextFree^.prev := lastFree
  1341. else
  1342. gs.freeOS.last := lastFree;
  1343. gs.freeOS.first := freeOS.first;
  1344. end;
  1345. {$endif not HAS_SYSOSFREE}
  1346. { Prepend varOS to gs.varOS. }
  1347. vOs := varOS;
  1348. if Assigned(vOs) then
  1349. begin
  1350. nextVOs := gs.varOS;
  1351. gs.varOS := vOs;
  1352. repeat
  1353. lastVOs := vOs;
  1354. ChangeThreadState(vOs, nil);
  1355. vOs := vOs^.next;
  1356. until not Assigned(vOs);
  1357. lastVOs^.next := nextVOs;
  1358. if Assigned(nextVOs) then
  1359. nextVOs^.prev := lastVOs;
  1360. end;
  1361. if gs.lockUse > 0 then
  1362. LeaveCriticalSection(gs.lock);
  1363. {$ifdef HAS_SYSOSFREE}
  1364. freeOS.FreeAll; { Does not require gs.lock. }
  1365. {$endif HAS_SYSOSFREE}
  1366. { 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: }
  1367. // DoneThread; { Assume everything is idempotent there }
  1368. FillChar(self, sizeof(self), 0);
  1369. end;
  1370. procedure HeapInc.ThreadState.AdoptArena(arena: pFixedArena);
  1371. var
  1372. sizeIndex: SizeUint;
  1373. nextArena: pFixedArena;
  1374. begin
  1375. sizeIndex := pCommonHeader(pointer(arena) + FixedArenaDataOffset)^.h and SizeIndexMask;
  1376. inc(used, arena^.usedSizeMinus1 + 1); { maxUsed is updated at the end of AdoptVarOwner. }
  1377. { Orphan frees all empty arenas, so adopted arena can’t be empty. }
  1378. if arena^.usedSizeMinus1 < arena^.almostFullThreshold + IndexToSize(sizeIndex) then
  1379. begin
  1380. { Add arena to partialArenas[sizeIndex]. }
  1381. nextArena := partialArenas[sizeIndex];
  1382. arena^.prev := nil;
  1383. arena^.next := nextArena;
  1384. if Assigned(nextArena) then
  1385. nextArena^.prev := arena;
  1386. partialArenas[sizeIndex] := arena;
  1387. end else
  1388. inc(allocatedByFullArenas[sizeIndex], pVarHeader(arena)[-1].ch.h);
  1389. end;
  1390. procedure HeapInc.ThreadState.AdoptVarOwner(p: pointer);
  1391. var
  1392. prevSize, statv: SizeUint;
  1393. h: uint32;
  1394. vOs, osPrev, osNext: pVarOSChunk;
  1395. begin
  1396. repeat
  1397. prevSize := pVarHeader(p - VarHeaderSize)^.prevSize;
  1398. dec(p, prevSize);
  1399. until prevSize = 0;
  1400. { Move OS chunk from gs.varOS to varOS. }
  1401. vOs := p - (VarOSChunkDataOffset + VarHeaderSize);
  1402. osPrev := vOs^.prev;
  1403. osNext := vOs^.next;
  1404. if Assigned(osPrev) then
  1405. osPrev^.next := osNext
  1406. else
  1407. gs.varOS := osNext;
  1408. if Assigned(osNext) then
  1409. osNext^.prev := osPrev;
  1410. vOs^.prev := nil;
  1411. osNext := varOS;
  1412. vOs^.next := osNext;
  1413. if Assigned(osNext) then
  1414. osNext^.prev := vOs;
  1415. varOS := vOs;
  1416. statv := allocated + vOs^.size;
  1417. allocated := statv;
  1418. inc(statv, gs.hugeUsed);
  1419. if statv > maxAllocated then
  1420. maxAllocated := statv;
  1421. repeat
  1422. pVarHeader(p - VarHeaderSize)^.threadState := @self;
  1423. h := pVarHeader(p - VarHeaderSize)^.ch.h;
  1424. if h and UsedFlag = 0 then
  1425. varFree.Add(p, pFreeVarChunk(p)^.binIndex)
  1426. else if h and FixedArenaFlag <> 0 then
  1427. AdoptArena(p)
  1428. else
  1429. inc(used, h and VarSizeMask); { maxUsed is updated after the loop. }
  1430. inc(p, h and VarSizeMask);
  1431. until h and LastFlag <> 0;
  1432. statv := used + gs.hugeUsed;
  1433. if statv > maxUsed then
  1434. maxUsed := statv;
  1435. end;
  1436. class procedure HeapInc.ThreadState.ChangeThreadState(vOs: pVarOSChunk; ts: pThreadState);
  1437. var
  1438. h: uint32;
  1439. p: pointer;
  1440. begin
  1441. p := pointer(vOs) + (VarOSChunkDataOffset + VarHeaderSize);
  1442. repeat
  1443. pVarHeader(p - VarHeaderSize)^.threadState := ts;
  1444. h := pVarHeader(p - VarHeaderSize)^.ch.h;
  1445. inc(p, h and VarSizeMask);
  1446. until h and LastFlag <> 0;
  1447. end;
  1448. {$ifndef FPC_SECTION_THREADVARS}
  1449. procedure HeapInc.ThreadState.FixupSelfPtr;
  1450. var
  1451. vOs: pVarOSChunk;
  1452. begin
  1453. vOs := varOS;
  1454. while Assigned(vOs) do
  1455. begin
  1456. ChangeThreadState(vOs, @self);
  1457. vOs := vOs^.next;
  1458. end;
  1459. end;
  1460. {$endif ndef FPC_SECTION_THREADVARS}
  1461. {$endif FPC_HAS_FEATURE_THREADING}
  1462. class function HeapInc.AllocFailed: pointer;
  1463. begin
  1464. if not ReturnNilIfGrowHeapFails then
  1465. HandleError(204);
  1466. result := nil;
  1467. end;
  1468. function SysGetFPCHeapStatus:TFPCHeapStatus;
  1469. var
  1470. ts: HeapInc.pThreadState;
  1471. hugeUsed: SizeUint;
  1472. begin
  1473. ts := @HeapInc.thisTs;
  1474. hugeUsed := HeapInc.gs.hugeUsed;
  1475. ts^.UpdateMaxStats(hugeUsed); { Cheat to avoid clearly implausible values like current > max. }
  1476. result.MaxHeapSize := ts^.maxAllocated;
  1477. result.MaxHeapUsed := ts^.maxUsed;
  1478. result.CurrHeapSize := hugeUsed + ts^.allocated;
  1479. result.CurrHeapUsed := hugeUsed + ts^.used;
  1480. result.CurrHeapFree := result.CurrHeapSize - result.CurrHeapUsed;
  1481. end;
  1482. function SysGetHeapStatus :THeapStatus;
  1483. var
  1484. fhs: TFPCHeapStatus;
  1485. begin
  1486. fhs := SysGetFPCHeapStatus;
  1487. FillChar((@result)^, sizeof(result), 0);
  1488. result.TotalAllocated := fhs.CurrHeapUsed;
  1489. result.TotalFree := fhs.CurrHeapSize - fhs.CurrHeapUsed;
  1490. result.TotalAddrSpace := fhs.CurrHeapSize;
  1491. end;
  1492. function SysGetMem(size : ptruint):pointer;
  1493. var
  1494. ts: HeapInc.pThreadState;
  1495. begin
  1496. ts := @HeapInc.thisTs;
  1497. if size <= HeapInc.MaxFixedHeaderAndPayload - HeapInc.CommonHeaderSize then
  1498. result := ts^.AllocFixed(size)
  1499. else if (size < GrowHeapSize2 div 2) { Approximate idea on the max size of the variable chunk. Approximate because size does not include headers but GrowHeapSize2 does. }
  1500. and (size < HeapInc.MaxVarHeaderAndPayload - HeapInc.VarHeaderSize) then
  1501. result := ts^.AllocVar(size, false)
  1502. else
  1503. result := ts^.AllocHuge(size);
  1504. end;
  1505. function SysFreeMem(p: pointer): ptruint;
  1506. var
  1507. ts: HeapInc.pThreadState;
  1508. begin
  1509. if Assigned(p) then
  1510. begin
  1511. ts := @HeapInc.thisTs;
  1512. if HeapInc.pCommonHeader(p - HeapInc.CommonHeaderSize)^.h and HeapInc.FixedFlag <> 0 then
  1513. result := ts^.FreeFixed(p)
  1514. else if HeapInc.pCommonHeader(p - HeapInc.CommonHeaderSize)^.h <> HeapInc.HugeHeader then
  1515. result := ts^.FreeVar(p)
  1516. else
  1517. result := ts^.FreeHuge(p);
  1518. end
  1519. else
  1520. result := 0;
  1521. end;
  1522. function SysTryResizeMem(var p: pointer; size: ptruint): boolean;
  1523. var
  1524. ts: HeapInc.pThreadState;
  1525. h: uint32;
  1526. newp: pointer;
  1527. begin
  1528. h := HeapInc.pCommonHeader(p - HeapInc.CommonHeaderSize)^.h;
  1529. if h and HeapInc.FixedFlag <> 0 then
  1530. { Don’t shrink fixed chunk. }
  1531. result := size <= SizeUint(HeapInc.IndexToSize(h and HeapInc.SizeIndexMask) - HeapInc.CommonHeaderSize)
  1532. else
  1533. begin
  1534. ts := @HeapInc.thisTs;
  1535. {$ifdef FPC_HAS_FEATURE_THREADING}
  1536. if Assigned(ts^.toFree) then
  1537. ts^.FlushToFree;
  1538. {$endif FPC_HAS_FEATURE_THREADING}
  1539. if h <> HeapInc.HugeHeader then
  1540. newp := ts^.TryResizeVar(p, size)
  1541. else
  1542. newp := ts^.TryResizeHuge(p, size);
  1543. result := Assigned(newp);
  1544. if result then
  1545. p := newp;
  1546. end;
  1547. end;
  1548. function SysMemSize(p: pointer): ptruint;
  1549. var
  1550. h: uint32;
  1551. begin
  1552. if not Assigned(p) then
  1553. exit(0);
  1554. h := HeapInc.pCommonHeader(p - HeapInc.CommonHeaderSize)^.h;
  1555. if h and HeapInc.FixedFlag <> 0 then
  1556. result := HeapInc.IndexToSize(h and HeapInc.SizeIndexMask) - HeapInc.CommonHeaderSize
  1557. else if h <> HeapInc.HugeHeader then
  1558. result := HeapInc.pVarHeader(p - HeapInc.VarHeaderSize)^.ch.h and uint32(HeapInc.VarSizeMask) - HeapInc.VarHeaderSize
  1559. else
  1560. result := HeapInc.pHugeChunk(p - (HeapInc.HugeChunkDataOffset + HeapInc.CommonHeaderSize))^.size - (HeapInc.HugeChunkDataOffset + HeapInc.CommonHeaderSize);
  1561. end;
  1562. function SysReAllocMem(var p: pointer; size: ptruint):pointer;
  1563. var
  1564. oldsize, newsize, tocopy: SizeUint;
  1565. begin
  1566. if size = 0 then
  1567. begin
  1568. SysFreeMem(p);
  1569. result := nil;
  1570. p := nil;
  1571. end
  1572. else if not Assigned(p) then
  1573. begin
  1574. result := SysGetMem(size);
  1575. p := result;
  1576. end
  1577. else if SysTryResizeMem(p, size) then
  1578. result := p
  1579. else
  1580. begin
  1581. oldsize := SysMemSize(p);
  1582. newsize := size;
  1583. result := SysGetMem(newsize);
  1584. if not Assigned(result) then
  1585. begin
  1586. if size <= oldsize then
  1587. { Don’t fail if shrinking. }
  1588. result := p;
  1589. exit; { If growing failed, return nil, but keep the old p. }
  1590. end;
  1591. tocopy := oldsize;
  1592. if tocopy > newsize then
  1593. tocopy := newsize;
  1594. Move(p^, result^, tocopy);
  1595. SysFreeMem(p);
  1596. p := result;
  1597. end;
  1598. end;
  1599. Function SysFreeMemSize(p: pointer; size: ptruint):ptruint;
  1600. begin
  1601. { can't free partial blocks, ignore size }
  1602. result := SysFreeMem(p);
  1603. end;
  1604. function SysAllocMem(size: ptruint): pointer;
  1605. begin
  1606. result := SysGetMem(size);
  1607. if Assigned(result) then
  1608. FillChar(result^, SysMemSize(result), 0);
  1609. end;
  1610. {*****************************************************************************
  1611. InitHeap
  1612. *****************************************************************************}
  1613. {$ifndef FPC_NO_DEFAULT_HEAP}
  1614. { This function will initialize the Heap manager and need to be called from
  1615. the initialization of the system unit }
  1616. {$ifdef FPC_HAS_FEATURE_THREADING}
  1617. procedure InitHeapThread;
  1618. begin
  1619. if HeapInc.gs.lockUse>0 then
  1620. InterlockedIncrement(HeapInc.gs.lockUse);
  1621. end;
  1622. {$endif}
  1623. procedure InitHeap; public name '_FPC_InitHeap';
  1624. begin
  1625. { we cannot initialize the locks here yet, thread support is
  1626. not loaded yet }
  1627. end;
  1628. procedure RelocateHeap;
  1629. begin
  1630. {$ifdef FPC_HAS_FEATURE_THREADING}
  1631. if HeapInc.gs.lockUse > 0 then
  1632. exit;
  1633. HeapInc.gs.lockUse := 1;
  1634. InitCriticalSection(HeapInc.gs.lock);
  1635. {$ifndef FPC_SECTION_THREADVARS}
  1636. { threadState pointers still point to main thread's thisTs, but they
  1637. have a reference to the global main thisTs, fix them to point
  1638. to the main thread specific variable.
  1639. even if section threadvars are used, this shouldn't cause problems as threadState pointers simply
  1640. do not change but we do not need it }
  1641. HeapInc.thisTs.FixupSelfPtr;
  1642. {$endif FPC_SECTION_THREADVARS}
  1643. if MemoryManager.RelocateHeap <> nil then
  1644. MemoryManager.RelocateHeap();
  1645. {$endif FPC_HAS_FEATURE_THREADING}
  1646. end;
  1647. procedure FinalizeHeap;
  1648. begin
  1649. { Do not try to do anything if the heap manager already reported an error }
  1650. if (errorcode=203) or (errorcode=204) then
  1651. exit;
  1652. {$if defined(FPC_HAS_FEATURE_THREADING)}
  1653. HeapInc.thisTs.Orphan;
  1654. if (HeapInc.gs.lockUse > 0) and (InterlockedDecrement(HeapInc.gs.lockUse) = 0) then
  1655. DoneCriticalSection(HeapInc.gs.lock);
  1656. {$elseif defined(HAS_SYSOSFREE)}
  1657. HeapInc.thisTs.freeOS.FreeAll;
  1658. {$endif FPC_HAS_FEATURE_THREADING | defined(HAS_SYSOSFREE)}
  1659. end;
  1660. {$endif ndef FPC_NO_DEFAULT_HEAP}
  1661. {$endif ndef HAS_MEMORYMANAGER and (defined(FPC_HAS_FEATURE_HEAP) or defined(FPC_IN_HEAPMGR))}