heap.inc 76 KB

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