{ This file is part of the Free Pascal run time library. Copyright (c) 1999-2000 by the Free Pascal development team. functions for heap management in the data segment See the file COPYING.FPC, included in this distribution, for details about the copyright. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. **********************************************************************} {****************************************************************************} { Do not use standard memory manager } { $define HAS_MEMORYMANAGER} { Memory manager } {$ifndef EMBEDDED} const MemoryManager: TMemoryManager = ( NeedLock: false; // Obsolete GetMem: @SysGetMem; FreeMem: @SysFreeMem; FreeMemSize: @SysFreeMemSize; AllocMem: @SysAllocMem; ReAllocMem: @SysReAllocMem; MemSize: @SysMemSize; InitThread: nil; DoneThread: nil; RelocateHeap: nil; GetHeapStatus: @SysGetHeapStatus; GetFPCHeapStatus: @SysGetFPCHeapStatus; ); {$else} {$ifndef FPC_IN_HEAPMGR} const MemoryManager: TMemoryManager = ( NeedLock: false; // Obsolete GetMem: nil; FreeMem: nil; FreeMemSize: nil; AllocMem: nil; ReAllocMem: nil; MemSize: nil; InitThread: nil; DoneThread: nil; RelocateHeap: nil; GetHeapStatus: nil; GetFPCHeapStatus: nil; );public name 'FPC_SYSTEM_MEMORYMANAGER'; {$endif FPC_IN_HEAPMGR} {$endif EMBEDDED} { Try to find the best matching block in general freelist } { define BESTMATCH} { DEBUG: Dump info when the heap needs to grow } { define DUMPGROW} { Memory profiling: at moment in time of max heap size usage, keep statistics of number of each size allocated (with 16 byte granularity) } { define DUMP_MEM_USAGE} {$ifdef DUMP_MEM_USAGE} {$define SHOW_MEM_USAGE} {$endif} {$ifndef HAS_MEMORYMANAGER} const {$ifdef CPU64} blocksize = 32; { at least size of freerecord } blockshift = 5; { shr value for blocksize=2^blockshift} maxblocksize = 512+blocksize; { 1024+8 needed for heaprecord } {$else} blocksize = 16; { at least size of freerecord } blockshift = 4; { shr value for blocksize=2^blockshift} maxblocksize = 512+blocksize; { 1024+8 needed for heaprecord } {$endif} maxblockindex = maxblocksize div blocksize; { highest index in array of lists of memchunks } { common flags } fixedsizeflag = 1; { flag if the block is of fixed size } { memchunk var flags } usedflag = 2; { flag if the block is used or not } lastblockflag = 4; { flag if the block is the last in os chunk } firstblockflag = 8; { flag if the block is the first in os chunk } { os chunk flags } ocrecycleflag = 1; { above flags stored in size field } sizemask = not(blocksize-1); fixedoffsetshift = 12; fixedsizemask = sizemask and ((1 shl fixedoffsetshift) - 1); { After how many successive allocations of oschunks for fixed freelist purposes should we double the size of locgrowheapsizesmall for the current thread. Since the allocations of oschunks are added together for all blocksizes, this is only a fuzzy indication of when the size will be doubled rather than a hard and fast boundary. } fixedallocthreshold = (maxblocksize shr blockshift) * 8; { maximum size to which locgrowheapsizesmall can grow } maxgrowheapsizesmall = 256*1024; {****************************************************************************} {$ifdef DUMPGROW} {$define DUMPBLOCKS} {$endif} { We use 'fixed' size chunks for small allocations, and os chunks with variable sized blocks for big allocations. * a block is an area allocated by user * a chunk is a block plus our bookkeeping * an os chunk is a collection of chunks Memory layout: fixed: < chunk size > [ ... user data ... ] variable: < prev chunk size > < chunk size > [ ... user data ... ] When all chunks in an os chunk are free, we keep a few around but otherwise it will be freed to the OS. Fixed os chunks can be converted to variable os chunks and back (if not too big). To prevent repeated conversion overhead in case of user freeing/allocing same or a small set of sizes, we only do the conversion to the new fixed os chunk size format after we reuse the os chunk for another fixed size, or variable. Note that while the fixed size os chunk is on the freelists.oslist, it is also still present in a freelists.fixedlists, therefore we can easily remove the os chunk from the freelists.oslist if this size is needed again; we don't need to search freelists.oslist in alloc_oschunk, since it won't be present anymore if alloc_oschunk is reached. Note that removing from the freelists.oslist is not really done, only the recycleflag is set, allowing to reset the flag easily. alloc_oschunk will clean up the list while passing over it, that was a slow function anyway. } type pfreelists = ^tfreelists; poschunk = ^toschunk; toschunk = record size : 0..high(ptrint); {Cannot be ptruint because used field is signed.} next_free : poschunk; prev_any : poschunk; next_any : poschunk; used : ptrint; { 0: free, >0: fixed, -1: var } freelists : pfreelists; { padding inserted automatically by alloc_oschunk } end; ppmemchunk_fixed = ^pmemchunk_fixed; pmemchunk_fixed = ^tmemchunk_fixed; tmemchunk_fixed = record { aligning is done automatically in alloc_oschunk } size : ptruint; next_fixed, prev_fixed : pmemchunk_fixed; end; ppmemchunk_var = ^pmemchunk_var; pmemchunk_var = ^tmemchunk_var; tmemchunk_var = record prevsize : ptruint; freelists : pfreelists; size : ptruint; next_var, prev_var : pmemchunk_var; end; { ``header'', ie. size of structure valid when chunk is in use } { should correspond to tmemchunk_var_hdr structure starting with the last field. Reason is that the overlap is starting from the end of the record. } tmemchunk_fixed_hdr = record { aligning is done automatically in alloc_oschunk } size : ptruint; end; tmemchunk_var_hdr = record prevsize : ptruint; freelists : pfreelists; size : ptruint; end; pfpcheapstatus = ^tfpcheapstatus; tfixedfreelists = array[1..maxblockindex] of pmemchunk_fixed; tfreelists = record oslist : poschunk; { os chunks free, available for use } fixedlists : tfixedfreelists; oscount : dword; { number of os chunks on oslist } { how many oschunks have been allocated in this thread since the last time we doubled the locgrowheapsizesmall size } fixedallocated: dword; { the size of oschunks allocated for fixed allocations in this thread; initialised on thread creation with the global growheapsizesmall setting } locgrowheapsizesmall: ptruint; oslist_all : poschunk; { all os chunks allocated } varlist : pmemchunk_var; { chunks waiting to be freed from other thread } waitfixed : pmemchunk_fixed; waitvar : pmemchunk_var; { heap statistics } internal_status : TFPCHeapStatus; end; const fixedfirstoffset = ((sizeof(toschunk) + sizeof(tmemchunk_fixed_hdr) + $f) and not $f) - sizeof(tmemchunk_fixed_hdr); varfirstoffset = ((sizeof(toschunk) + sizeof(tmemchunk_var_hdr) + $f) and not $f) - sizeof(tmemchunk_var_hdr); {$ifdef BESTMATCH} matcheffort = high(longint); {$else} matcheffort = 10; {$endif} var orphaned_freelists : tfreelists; {$ifdef FPC_HAS_FEATURE_THREADING} heap_lock : trtlcriticalsection; heap_lock_use : integer; {$endif} threadvar freelists : tfreelists; {$ifdef DUMP_MEM_USAGE} const sizeusageshift = 4; sizeusageindex = 2049; sizeusagesize = sizeusageindex shl sizeusageshift; type tsizeusagelist = array[0..sizeusageindex] of longint; threadvar sizeusage, maxsizeusage: tsizeusagelist; {$endif} {$endif HAS_MEMORYMANAGER} {***************************************************************************** Memory Manager *****************************************************************************} {$ifndef FPC_IN_HEAPMGR} procedure GetMemoryManager(var MemMgr:TMemoryManager); begin MemMgr := MemoryManager; end; procedure SetMemoryManager(const MemMgr:TMemoryManager); begin MemoryManager := MemMgr; end; function IsMemoryManagerSet:Boolean; begin {$ifdef HAS_MEMORYMANAGER} Result:=false; {$else HAS_MEMORYMANAGER} IsMemoryManagerSet := (MemoryManager.GetMem<>@SysGetMem) or (MemoryManager.FreeMem<>@SysFreeMem); {$endif HAS_MEMORYMANAGER} end; {$ifdef FPC_HAS_FEATURE_HEAP} procedure GetMem(Out p:pointer;Size:ptruint); begin p := MemoryManager.GetMem(Size); end; procedure GetMemory(Out p:pointer;Size:ptruint); begin GetMem(p,size); end; procedure FreeMem(p:pointer;Size:ptruint); begin MemoryManager.FreeMemSize(p,Size); end; procedure FreeMemory(p:pointer;Size:ptruint); begin FreeMem(p,size); end; function GetHeapStatus:THeapStatus; begin Result:=MemoryManager.GetHeapStatus(); end; function GetFPCHeapStatus:TFPCHeapStatus; begin Result:=MemoryManager.GetFPCHeapStatus(); end; function MemSize(p:pointer):ptruint; begin MemSize := MemoryManager.MemSize(p); end; { Delphi style } function FreeMem(p:pointer):ptruint;[Public,Alias:'FPC_FREEMEM_X']; begin FreeMem := MemoryManager.FreeMem(p); end; function FreeMemory(p:pointer):ptruint; cdecl; begin FreeMemory := FreeMem(p); end; function GetMem(size:ptruint):pointer; begin GetMem := MemoryManager.GetMem(Size); end; function GetMemory(size:ptruint):pointer; cdecl; begin GetMemory := GetMem(size); end; function AllocMem(Size:ptruint):pointer; begin AllocMem := MemoryManager.AllocMem(size); end; function ReAllocMem(var p:pointer;Size:ptruint):pointer; begin ReAllocMem := MemoryManager.ReAllocMem(p,size); end; function ReAllocMemory(p:pointer;Size:ptruint):pointer; cdecl; begin ReAllocMemory := ReAllocMem(p,size); end; { Needed for calls from Assembler } function fpc_getmem(size:ptruint):pointer;compilerproc;[public,alias:'FPC_GETMEM']; begin fpc_GetMem := MemoryManager.GetMem(size); end; procedure fpc_freemem(p:pointer);compilerproc;[public,alias:'FPC_FREEMEM']; begin MemoryManager.FreeMem(p); end; {$endif FPC_HAS_FEATURE_HEAP} {$endif FPC_IN_HEAPMGR} {$if defined(FPC_HAS_FEATURE_HEAP) or defined(FPC_IN_HEAPMGR)} {$ifndef HAS_MEMORYMANAGER} {***************************************************************************** GetHeapStatus *****************************************************************************} function SysGetFPCHeapStatus:TFPCHeapStatus; var status: pfpcheapstatus; begin status := @freelists.internal_status; status^.CurrHeapFree := status^.CurrHeapSize - status^.CurrHeapUsed; result := status^; end; function SysGetHeapStatus :THeapStatus; var status: pfpcheapstatus; begin status := @freelists.internal_status; status^.CurrHeapFree := status^.CurrHeapSize - status^.CurrHeapUsed; result.TotalAllocated :=status^.CurrHeapUsed; result.TotalFree :=status^.CurrHeapFree; result.TotalAddrSpace :=status^.CurrHeapSize; result.TotalUncommitted :=0; result.TotalCommitted :=0; result.FreeSmall :=0; result.FreeBig :=0; result.Unused :=0; result.Overhead :=0; result.HeapErrorCode :=0; end; {$ifdef DUMPBLOCKS} // TODO procedure DumpBlocks(loc_freelists: pfreelists); var s,i,j : ptruint; hpfixed : pmemchunk_fixed; hpvar : pmemchunk_var; begin { fixed freelist } for i := 1 to maxblockindex do begin hpfixed := loc_freelists^.fixedlists[i]; j := 0; while assigned(hpfixed) do begin inc(j); hpfixed := hpfixed^.next_fixed; end; writeln('Block ',i*blocksize,': ',j); end; { var freelist } hpvar := loc_freelists^.varlist; j := 0; s := 0; while assigned(hpvar) do begin inc(j); if hpvar^.size>s then s := hpvar^.size; hpvar := hpvar^.next_var; end; writeln('Variable: ',j,' maxsize: ',s); end; {$endif} {***************************************************************************** Forwards *****************************************************************************} procedure finish_waitfixedlist(loc_freelists: pfreelists); forward; procedure finish_waitvarlist(loc_freelists: pfreelists); forward; function try_finish_waitfixedlist(loc_freelists: pfreelists): boolean; forward; procedure try_finish_waitvarlist(loc_freelists: pfreelists); forward; {***************************************************************************** List adding/removal *****************************************************************************} procedure append_to_list_var(pmc: pmemchunk_var); inline; var varlist: ppmemchunk_var; begin varlist := @pmc^.freelists^.varlist; pmc^.prev_var := nil; pmc^.next_var := varlist^; if varlist^<>nil then varlist^^.prev_var := pmc; varlist^ := pmc; end; {$ifdef HEAP_DEBUG} function find_fixed_mc(loc_freelists: pfreelists; chunkindex: ptruint; pmc: pmemchunk_fixed): boolean; var pmc_temp: pmemchunk_fixed; begin pmc_temp := loc_freelists^.fixedlists[chunkindex]; while pmc_temp <> nil do begin if pmc_temp = pmc then exit(true); pmc_temp := pmc_temp^.next_fixed; end; result := false; end; {$endif} procedure remove_from_list_fixed(pmc: pmemchunk_fixed; fixedlist: ppmemchunk_fixed); inline; begin if assigned(pmc^.next_fixed) then pmc^.next_fixed^.prev_fixed := pmc^.prev_fixed; if assigned(pmc^.prev_fixed) then pmc^.prev_fixed^.next_fixed := pmc^.next_fixed else fixedlist^ := pmc^.next_fixed; end; procedure remove_from_list_var(pmc: pmemchunk_var); inline; begin if assigned(pmc^.next_var) then pmc^.next_var^.prev_var := pmc^.prev_var; if assigned(pmc^.prev_var) then pmc^.prev_var^.next_var := pmc^.next_var else pmc^.freelists^.varlist := pmc^.next_var; end; procedure remove_freed_fixed_chunks(poc: poschunk); { remove all fixed chunks from the fixed free list, as this os chunk is going to be used for other purpose } var pmc, pmc_end: pmemchunk_fixed; fixedlist: ppmemchunk_fixed; chunksize: ptruint; begin { exit if this is a var size os chunk, function only applicable to fixed size } if poc^.used < 0 then exit; pmc := pmemchunk_fixed(pointer(poc)+fixedfirstoffset); chunksize := pmc^.size and fixedsizemask; pmc_end := pmemchunk_fixed(pointer(poc)+(poc^.size and sizemask)-chunksize); fixedlist := @poc^.freelists^.fixedlists[chunksize shr blockshift]; repeat remove_from_list_fixed(pmc, fixedlist); pmc := pointer(pmc)+chunksize; until pmc > pmc_end; end; procedure free_oschunk(loc_freelists: pfreelists; poc: poschunk); var pocsize: ptruint; begin remove_freed_fixed_chunks(poc); if assigned(poc^.prev_any) then poc^.prev_any^.next_any := poc^.next_any else loc_freelists^.oslist_all := poc^.next_any; if assigned(poc^.next_any) then poc^.next_any^.prev_any := poc^.prev_any; if poc^.used >= 0 then dec(loc_freelists^.fixedallocated); pocsize := poc^.size and sizemask; dec(loc_freelists^.internal_status.currheapsize, pocsize); SysOSFree(poc, pocsize); end; procedure append_to_oslist(poc: poschunk); var loc_freelists: pfreelists; begin loc_freelists := poc^.freelists; { check if already on list } if (poc^.size and ocrecycleflag) <> 0 then begin inc(loc_freelists^.oscount); poc^.size := poc^.size and not ocrecycleflag; exit; end; { decide whether to free block or add to list } {$ifdef HAS_SYSOSFREE} if (loc_freelists^.oscount >= MaxKeptOSChunks) or ((poc^.size and sizemask) > growheapsize2) then begin free_oschunk(loc_freelists, poc); end else begin {$endif} poc^.next_free := loc_freelists^.oslist; loc_freelists^.oslist := poc; inc(loc_freelists^.oscount); {$ifdef HAS_SYSOSFREE} end; {$endif} end; procedure append_to_oslist_var(pmc: pmemchunk_var); var poc: poschunk; begin // block eligable for freeing poc := pointer(pmc)-varfirstoffset; remove_from_list_var(pmc); append_to_oslist(poc); end; procedure modify_oschunk_freelists(poc: poschunk; new_freelists: pfreelists); var pmcv: pmemchunk_var; begin poc^.freelists := new_freelists; { only if oschunk contains var memchunks, we need additional assignments } if poc^.used <> -1 then exit; pmcv := pmemchunk_var(pointer(poc)+varfirstoffset); repeat pmcv^.freelists := new_freelists; if (pmcv^.size and lastblockflag) <> 0 then break; pmcv := pmemchunk_var(pointer(pmcv)+(pmcv^.size and sizemask)); until false; end; function modify_freelists(loc_freelists, new_freelists: pfreelists): poschunk; var poc: poschunk; begin poc := loc_freelists^.oslist_all; if assigned(poc) then begin repeat { fixed and var freelist for orphaned freelists do not need maintenance } { we assume the heap is not severely fragmented at thread exit } modify_oschunk_freelists(poc, new_freelists); if not assigned(poc^.next_any) then exit(poc); poc := poc^.next_any; until false; end; modify_freelists := nil; end; {***************************************************************************** Split block *****************************************************************************} function split_block(pcurr: pmemchunk_var; size: ptruint): ptruint; var pcurr_tmp : pmemchunk_var; size_flags, oldsize, sizeleft: ptruint; begin size_flags := pcurr^.size; oldsize := size_flags and sizemask; sizeleft := oldsize-size; if sizeleft>=sizeof(tmemchunk_var) then begin pcurr_tmp := pmemchunk_var(pointer(pcurr)+size); { update prevsize of block to the right } if (size_flags and lastblockflag) = 0 then pmemchunk_var(pointer(pcurr)+oldsize)^.prevsize := sizeleft; { inherit the lastblockflag } pcurr_tmp^.size := sizeleft or (size_flags and lastblockflag); pcurr_tmp^.prevsize := size; pcurr_tmp^.freelists := pcurr^.freelists; { the block we return is not the last one anymore (there's now a block after it) } { decrease size of block to new size } pcurr^.size := size or (size_flags and (not sizemask and not lastblockflag)); { insert the block in the freelist } append_to_list_var(pcurr_tmp); result := size; end else result := oldsize; end; {***************************************************************************** Try concat freerecords *****************************************************************************} procedure concat_two_blocks(mc_left, mc_right: pmemchunk_var); var mc_tmp : pmemchunk_var; size_right : ptruint; begin // mc_right can't be a fixed size block if mc_right^.size and fixedsizeflag<>0 then HandleError(204); // left block free, concat with right-block size_right := mc_right^.size and sizemask; inc(mc_left^.size, size_right); // if right-block was last block, copy flag if (mc_right^.size and lastblockflag) <> 0 then begin mc_left^.size := mc_left^.size or lastblockflag; end else begin // there is a block to the right of the right-block, adjust it's prevsize mc_tmp := pmemchunk_var(pointer(mc_right)+size_right); mc_tmp^.prevsize := mc_left^.size and sizemask; end; // remove right-block from doubly linked list remove_from_list_var(mc_right); end; function try_concat_free_chunk_forward(mc: pmemchunk_var): boolean; var mc_tmp : pmemchunk_var; begin { try concat forward } result := false; if (mc^.size and lastblockflag) = 0 then begin mc_tmp := pmemchunk_var(pointer(mc)+(mc^.size and sizemask)); if (mc_tmp^.size and usedflag) = 0 then begin // next block free: concat concat_two_blocks(mc, mc_tmp); result := true; end; end; end; function try_concat_free_chunk(mc: pmemchunk_var): pmemchunk_var; var mc_tmp : pmemchunk_var; begin try_concat_free_chunk_forward(mc); { try concat backward } if (mc^.size and firstblockflag) = 0 then begin mc_tmp := pmemchunk_var(pointer(mc)-mc^.prevsize); if (mc_tmp^.size and usedflag) = 0 then begin // prior block free: concat concat_two_blocks(mc_tmp, mc); mc := mc_tmp; end; end; result := mc; end; {***************************************************************************** Grow Heap *****************************************************************************} function find_free_oschunk(loc_freelists: pfreelists; minsize, maxsize: ptruint; var size: ptruint): poschunk; var prev_poc, poc: poschunk; pocsize: ptruint; begin poc := loc_freelists^.oslist; prev_poc := nil; while poc <> nil do begin if (poc^.size and ocrecycleflag) <> 0 then begin { oops! we recycled this chunk; remove it from list } poc^.size := poc^.size and not ocrecycleflag; poc := poc^.next_free; if prev_poc = nil then loc_freelists^.oslist := poc else prev_poc^.next_free := poc; continue; end; pocsize := poc^.size and sizemask; if (pocsize >= minsize) and (pocsize <= maxsize) then begin size := pocsize; if prev_poc = nil then loc_freelists^.oslist := poc^.next_free else prev_poc^.next_free := poc^.next_free; dec(loc_freelists^.oscount); remove_freed_fixed_chunks(poc); break; end; prev_poc := poc; poc := poc^.next_free; end; result := poc; end; function alloc_oschunk(loc_freelists: pfreelists; chunkindex, size: ptruint): pointer; var pmc, pmc_next : pmemchunk_fixed; pmcv : pmemchunk_var; poc : poschunk; minsize, maxsize, i : ptruint; chunksize : ptruint; status : pfpcheapstatus; begin { increase size by size needed for os block header } minsize := size + varfirstoffset; { for fixed size chunks we keep offset from os chunk to mem chunk in upper bits, so maximum os chunk size is 64K on 32bit for fixed size } if chunkindex<>0 then maxsize := 1 shl (32-fixedoffsetshift) else maxsize := high(ptruint); poc:=nil; { blocks available in freelist? } { do not reformat fixed size chunks too quickly } if loc_freelists^.oscount >= MaxKeptOSChunks then poc := find_free_oschunk(loc_freelists, minsize, maxsize, size); { if none available, try to recycle orphaned os chunks } if not assigned(poc) and (assigned(orphaned_freelists.waitfixed) or assigned(orphaned_freelists.waitvar) or (orphaned_freelists.oscount > 0)) then begin {$ifdef FPC_HAS_FEATURE_THREADING} entercriticalsection(heap_lock); {$endif} finish_waitfixedlist(@orphaned_freelists); finish_waitvarlist(@orphaned_freelists); if orphaned_freelists.oscount > 0 then begin { blocks available in orphaned freelist ? } poc := find_free_oschunk(@orphaned_freelists, minsize, maxsize, size); if assigned(poc) then begin { adopt this os chunk } poc^.freelists := loc_freelists; if assigned(poc^.prev_any) then poc^.prev_any^.next_any := poc^.next_any else orphaned_freelists.oslist_all := poc^.next_any; if assigned(poc^.next_any) then poc^.next_any^.prev_any := poc^.prev_any; poc^.next_any := loc_freelists^.oslist_all; if assigned(loc_freelists^.oslist_all) then loc_freelists^.oslist_all^.prev_any := poc; poc^.prev_any := nil; loc_freelists^.oslist_all := poc; end; end; {$ifdef FPC_HAS_FEATURE_THREADING} leavecriticalsection(heap_lock); {$endif} end; if poc = nil then begin {$ifdef DUMPGROW} writeln('growheap(',size,') allocating ',(size+sizeof(toschunk)+$ffff) and not $ffff); DumpBlocks(loc_freelists); {$endif} { allocate by 64K size } size := (size+varfirstoffset+$ffff) and not $ffff; { allocate smaller blocks for fixed-size chunks } if chunkindex<>0 then begin poc := SysOSAlloc(loc_freelists^.LocGrowHeapSizeSmall); if poc<>nil then size := loc_freelists^.LocGrowHeapSizeSmall; end { first try 256K (default) } else if size<=GrowHeapSize1 then begin poc := SysOSAlloc(GrowHeapSize1); if poc<>nil then size := GrowHeapSize1; end { second try 1024K (default) } else if size<=GrowHeapSize2 then begin poc := SysOSAlloc(GrowHeapSize2); if poc<>nil then size := GrowHeapSize2; end { else allocate the needed bytes } else poc := SysOSAlloc(size); { try again } if poc=nil then begin poc := SysOSAlloc(size); if poc=nil then begin if ReturnNilIfGrowHeapFails then begin result := nil; exit end else HandleError(203); end; end; poc^.freelists := loc_freelists; poc^.prev_any := nil; poc^.next_any := loc_freelists^.oslist_all; if assigned(loc_freelists^.oslist_all) then loc_freelists^.oslist_all^.prev_any := poc; loc_freelists^.oslist_all := poc; { set the total new heap size } status := @loc_freelists^.internal_status; inc(status^.currheapsize, size); if status^.currheapsize > status^.maxheapsize then status^.maxheapsize := status^.currheapsize; end; { initialize os-block } poc^.size := size; if chunkindex<>0 then begin poc^.used := 0; { chop os chunk in fixedsize parts, maximum of $ffff elements are allowed, otherwise there will be an overflow } chunksize := chunkindex shl blockshift; if ptruint(size-chunksize)>maxsize then HandleError(204); { we need to align the user pointers to 8 byte at least for mmx/sse and doubles on sparc, align to 16 bytes } i := fixedfirstoffset; result := pointer(poc) + i; pmc := pmemchunk_fixed(result); pmc^.prev_fixed := nil; repeat pmc^.size := fixedsizeflag or chunksize or (i shl fixedoffsetshift); inc(i, chunksize); if i > ptruint(size - chunksize) then break; pmc_next := pmemchunk_fixed(pointer(pmc)+chunksize); pmc^.next_fixed := pmc_next; pmc_next^.prev_fixed := pmc; pmc := pmc_next; until false; pmc_next := loc_freelists^.fixedlists[chunkindex]; pmc^.next_fixed := pmc_next; if pmc_next<>nil then pmc_next^.prev_fixed := pmc; loc_freelists^.fixedlists[chunkindex] := pmemchunk_fixed(result); { check whether we should increase the size of the fixed freelist blocks } inc(loc_freelists^.fixedallocated); if loc_freelists^.fixedallocated > fixedallocthreshold then begin if loc_freelists^.locgrowheapsizesmall < maxgrowheapsizesmall then inc(loc_freelists^.locgrowheapsizesmall, loc_freelists^.locgrowheapsizesmall); { also set to zero in case we did not grow the blocksize to prevent oveflows of this counter in case the rtl is compiled range/overflow checking } loc_freelists^.fixedallocated := 0; end; end else begin poc^.used := -1; { we need to align the user pointers to 8 byte at least for mmx/sse and doubles on sparc, align to 16 bytes } result := pointer(poc)+varfirstoffset; pmcv := pmemchunk_var(result); pmcv^.size := (ptruint(size-varfirstoffset) and sizemask) or (firstblockflag or lastblockflag); pmcv^.prevsize := 0; pmcv^.freelists := loc_freelists; append_to_list_var(pmcv); end; end; {***************************************************************************** SysGetMem *****************************************************************************} function SysGetMem_Fixed(chunksize: ptruint): pointer; var pmc, pmc_next: pmemchunk_fixed; poc: poschunk; chunkindex: ptruint; loc_freelists: pfreelists; begin { try to find a block in one of the freelists per size } chunkindex := chunksize shr blockshift; loc_freelists := @freelists; pmc := loc_freelists^.fixedlists[chunkindex]; { no free blocks ? } if assigned(pmc) then begin { remove oschunk from free list in case we recycle it } poc := poschunk(pointer(pmc) - (pmc^.size shr fixedoffsetshift)); if poc^.used = 0 then begin poc^.size := poc^.size or ocrecycleflag; dec(loc_freelists^.oscount); end; end else if try_finish_waitfixedlist(loc_freelists) then { freed some to-be freed chunks, retry allocation } exit(SysGetMem_Fixed(chunksize)) else begin pmc := alloc_oschunk(loc_freelists, chunkindex, chunksize); if not assigned(pmc) then exit(nil); poc := poschunk(pointer(pmc)-fixedfirstoffset); end; prefetch(poc^.used); { get a pointer to the block we should return } result := pointer(pmc)+sizeof(tmemchunk_fixed_hdr); { update freelist } pmc_next := pmc^.next_fixed; loc_freelists^.fixedlists[chunkindex] := pmc_next; prefetch((pointer(@chunksize)-4)^); if assigned(pmc_next) then pmc_next^.prev_fixed := nil; { statistics } with loc_freelists^.internal_status do begin inc(currheapused, chunksize); if currheapused > maxheapused then begin maxheapused := currheapused; {$ifdef DUMP_MEM_USAGE} maxsizeusage := sizeusage; {$endif} end; end; inc(poc^.used); end; function SysGetMem_Var(size: ptruint): pointer; var pcurr : pmemchunk_var; pbest : pmemchunk_var; loc_freelists : pfreelists; iter : cardinal; begin result:=nil; { check for maximum possible allocation (everything is rounded up to the next multiple of 64k) } if (size>high(ptruint)-$ffff) then if ReturnNilIfGrowHeapFails then exit else HandleError(204); { free pending items } loc_freelists := @freelists; try_finish_waitvarlist(loc_freelists); pbest := nil; pcurr := loc_freelists^.varlist; iter := high(iter); while assigned(pcurr) and (iter>0) do begin if (pcurr^.size>=size) then begin if not assigned(pbest) or (pcurr^.size maxheapused then begin maxheapused := currheapused; {$ifdef DUMP_MEM_USAGE} maxsizeusage := sizeusage; {$endif} end; end; end; function SysGetMem(size : ptruint):pointer; begin { Something to allocate ? } if size=0 then { we always need to allocate something, using heapend is not possible, because heappend can be changed by growheap (PFV) } size := 1; { calc to multiple of 16 after adding the needed bytes for memchunk header } if size <= (maxblocksize - sizeof(tmemchunk_fixed_hdr)) then begin size := (size+(sizeof(tmemchunk_fixed_hdr)+(blocksize-1))) and fixedsizemask; result := sysgetmem_fixed(size); end else begin if size < high(ptruint)-((sizeof(tmemchunk_var_hdr)+(blocksize-1))) then size := (size+(sizeof(tmemchunk_var_hdr)+(blocksize-1))) and sizemask; result := sysgetmem_var(size); end; {$ifdef DUMP_MEM_USAGE} size := sysmemsize(result); if size > sizeusagesize then inc(sizeusage[sizeusageindex]) else inc(sizeusage[size shr sizeusageshift]); {$endif} end; {***************************************************************************** SysFreeMem *****************************************************************************} procedure waitfree_fixed(pmc: pmemchunk_fixed; poc: poschunk); begin {$ifdef FPC_HAS_FEATURE_THREADING} entercriticalsection(heap_lock); {$endif} pmc^.next_fixed := poc^.freelists^.waitfixed; poc^.freelists^.waitfixed := pmc; {$ifdef FPC_HAS_FEATURE_THREADING} leavecriticalsection(heap_lock); {$endif} end; procedure waitfree_var(pmcv: pmemchunk_var); begin {$ifdef FPC_HAS_FEATURE_THREADING} entercriticalsection(heap_lock); {$endif} pmcv^.next_var := pmcv^.freelists^.waitvar; pmcv^.freelists^.waitvar := pmcv; {$ifdef FPC_HAS_FEATURE_THREADING} leavecriticalsection(heap_lock); {$endif} end; function SysFreeMem_Fixed(loc_freelists: pfreelists; pmc: pmemchunk_fixed): ptruint; var chunkindex, chunksize: ptruint; poc: poschunk; pmc_next: pmemchunk_fixed; pocfreelists: pfreelists; begin poc := poschunk(pointer(pmc)-(pmc^.size shr fixedoffsetshift)); { start memory access to poc^.freelists already } pocfreelists := poc^.freelists; chunksize := pmc^.size and fixedsizemask; if loc_freelists = pocfreelists then begin { decrease used blocks count (well in advance of poc^.used check below, to avoid stalling due to a dependency) } dec(poc^.used); { insert the block in its freelist } chunkindex := chunksize shr blockshift; pmc_next := loc_freelists^.fixedlists[chunkindex]; pmc^.prev_fixed := nil; pmc^.next_fixed := pmc_next; if assigned(pmc_next) then pmc_next^.prev_fixed := pmc; loc_freelists^.fixedlists[chunkindex] := pmc; dec(loc_freelists^.internal_status.currheapused, chunksize); if poc^.used <= 0 then begin { decrease used blocks count } if poc^.used<0 then HandleError(204); { osblock can be freed? } append_to_oslist(poc); end; end else begin { deallocated in wrong thread! add to to-be-freed list of correct thread } waitfree_fixed(pmc, poc); end; result := chunksize; end; function SysFreeMem_Var(loc_freelists: pfreelists; pmcv: pmemchunk_var): ptruint; var chunksize: ptruint; begin chunksize := pmcv^.size and sizemask; if loc_freelists <> pmcv^.freelists then begin { deallocated in wrong thread! add to to-be-freed list of correct thread } waitfree_var(pmcv); exit(chunksize); end; { insert the block in its freelist } pmcv^.size := pmcv^.size and (not usedflag); append_to_list_var(pmcv); pmcv := try_concat_free_chunk(pmcv); if (pmcv^.size and (firstblockflag or lastblockflag)) = (firstblockflag or lastblockflag) then append_to_oslist_var(pmcv); dec(loc_freelists^.internal_status.currheapused, chunksize); result := chunksize; end; function SysFreeMem(p: pointer): ptruint; var pmc: pmemchunk_fixed; loc_freelists: pfreelists; {$ifdef DUMP_MEM_USAGE} size: sizeint; {$endif} begin pmc := pmemchunk_fixed(p-sizeof(tmemchunk_fixed_hdr)); prefetch(pmc^.size); if p=nil then begin result:=0; exit; end; {$ifdef DUMP_MEM_USAGE} size := sysmemsize(p); if size > sizeusagesize then dec(sizeusage[sizeusageindex]) else dec(sizeusage[size shr sizeusageshift]); {$endif} { loc_freelists is a threadvar, so it can be worth it to prefetch } loc_freelists := @freelists; prefetch(loc_freelists^.internal_status.currheapused); { check if this is a fixed- or var-sized chunk } if (pmc^.size and fixedsizeflag) = 0 then result := sysfreemem_var(loc_freelists, pmemchunk_var(p-sizeof(tmemchunk_var_hdr))) else result := sysfreemem_fixed(loc_freelists, pmc); end; procedure finish_waitfixedlist(loc_freelists: pfreelists); { free to-be-freed chunks, return whether we freed anything } var pmc: pmemchunk_fixed; begin while loc_freelists^.waitfixed <> nil do begin { keep next_fixed, might be destroyed } pmc := loc_freelists^.waitfixed; loc_freelists^.waitfixed := pmc^.next_fixed; SysFreeMem_Fixed(loc_freelists, pmc); end; end; function try_finish_waitfixedlist(loc_freelists: pfreelists): boolean; begin if loc_freelists^.waitfixed = nil then exit(false); {$ifdef FPC_HAS_FEATURE_THREADING} entercriticalsection(heap_lock); {$endif} finish_waitfixedlist(loc_freelists); {$ifdef FPC_HAS_FEATURE_THREADING} leavecriticalsection(heap_lock); {$endif} result := true; end; procedure finish_waitvarlist(loc_freelists: pfreelists); { free to-be-freed chunks, return whether we freed anything } var pmcv: pmemchunk_var; begin while loc_freelists^.waitvar <> nil do begin { keep next_var, might be destroyed } pmcv := loc_freelists^.waitvar; loc_freelists^.waitvar := pmcv^.next_var; SysFreeMem_Var(loc_freelists, pmcv); end; end; procedure try_finish_waitvarlist(loc_freelists: pfreelists); begin if loc_freelists^.waitvar = nil then exit; {$ifdef FPC_HAS_FEATURE_THREADING} entercriticalsection(heap_lock); {$endif} finish_waitvarlist(loc_freelists); {$ifdef FPC_HAS_FEATURE_THREADING} leavecriticalsection(heap_lock); {$endif} end; {***************************************************************************** SysFreeMemSize *****************************************************************************} Function SysFreeMemSize(p: pointer; size: ptruint):ptruint; begin if size=0 then exit(0); { can't free partial blocks, ignore size } result := SysFreeMem(p); end; {***************************************************************************** SysMemSize *****************************************************************************} function SysMemSize(p: pointer): ptruint; begin result := pmemchunk_fixed(pointer(p)-sizeof(tmemchunk_fixed_hdr))^.size; if (result and fixedsizeflag) = 0 then begin result := result and sizemask; dec(result, sizeof(tmemchunk_var_hdr)); end else begin result := result and fixedsizemask; dec(result, sizeof(tmemchunk_fixed_hdr)); end; end; {***************************************************************************** SysAllocMem *****************************************************************************} function SysAllocMem(size: ptruint): pointer; begin result := MemoryManager.GetMem(size); if result<>nil then FillChar(result^,MemoryManager.MemSize(result),0); end; {***************************************************************************** SysResizeMem *****************************************************************************} function SysTryResizeMem(var p: pointer; size: ptruint): boolean; var chunksize, oldsize, currsize : ptruint; pcurr : pmemchunk_var; loc_freelists : pfreelists; begin SysTryResizeMem := false; { fix p to point to the heaprecord } chunksize := pmemchunk_fixed(p-sizeof(tmemchunk_fixed_hdr))^.size; { handle fixed memchuncks separate. Only allow resizes when the new size fits in the same block } if (chunksize and fixedsizeflag) <> 0 then begin currsize := chunksize and fixedsizemask; { 1. Resizing to smaller sizes will never allocate a new block. We just keep the current block. This is needed for the expectations that resizing to a small block will not move the contents of a memory block 2. For resizing to greater size first check if the size fits in the fixed block range to prevent "truncating" the size by the fixedsizemask } if ((size <= (maxblocksize - sizeof(tmemchunk_fixed_hdr))) and ((size+(sizeof(tmemchunk_fixed_hdr)+(blocksize-1))) and sizemask <= currsize)) then begin systryresizemem:=true; exit; end; { we need to allocate a new fixed or var memchunck } exit; end; { var memchunk } { do not fragment the heap with small shrinked blocks } { also solves problem with var sized chunks smaller than sizeof(tmemchunk_var) } if size < maxblocksize div 2 then exit(false); currsize := chunksize and sizemask; size := (size+sizeof(tmemchunk_var_hdr)+(blocksize-1)) and sizemask; { is the allocated block still correct? } if (currsize>=size) and (size>ptruint(currsize-blocksize)) then begin SysTryResizeMem := true; exit; end; { get pointer to block } loc_freelists := @freelists; pcurr := pmemchunk_var(pointer(p)-sizeof(tmemchunk_var_hdr)); if pcurr^.freelists <> loc_freelists then exit; oldsize := currsize; { do we need to allocate more memory ? } if try_concat_free_chunk_forward(pcurr) then currsize := pcurr^.size and sizemask; if size>currsize then begin { adjust statistics (try_concat_free_chunk_forward may have merged a free block into the current block, which we will subsequently free (so the combined size will be freed -> make sure the combined size is marked as used) } with loc_freelists^.internal_status do begin inc(currheapused, currsize-oldsize); if currheapused > maxheapused then maxheapused := currheapused; end; { the size is bigger than the previous size, we need to allocate more mem but we could not concatenate with next block or not big enough } exit; end else { is the size smaller then we can adjust the block to that size and insert the other part into the freelist } if currsize>size then currsize := split_block(pcurr, size); with loc_freelists^.internal_status do begin inc(currheapused, currsize-oldsize); if currheapused > maxheapused then maxheapused := currheapused; end; SysTryResizeMem := true; end; {***************************************************************************** SysResizeMem *****************************************************************************} function SysReAllocMem(var p: pointer; size: ptruint):pointer; var newsize, oldsize, minsize : ptruint; p2 : pointer; begin { Free block? } if size=0 then begin if p<>nil then begin MemoryManager.FreeMem(p); p := nil; end; end else { Allocate a new block? } if p=nil then begin p := MemoryManager.GetMem(size); end else begin { Resize block } {$ifdef DUMP_MEM_USAGE} oldsize:=SysMemSize(p); {$endif} if not SysTryResizeMem(p,size) then begin oldsize:=MemoryManager.MemSize(p); { Grow with bigger steps to prevent the need for multiple getmem/freemem calls for fixed blocks. It might cost a bit of extra memory, but in most cases a reallocmem is done multiple times. } if oldsizenewsize then newsize:=size; end else newsize:=size; { calc size of data to move } minsize:=oldsize; if newsize < minsize then minsize := newsize; p2 := MemoryManager.GetMem(newsize); if p2<>nil then Move(p^,p2^,minsize); MemoryManager.FreeMem(p); p := p2; {$ifdef DUMP_MEM_USAGE} end else begin size := sysmemsize(p); if size <> oldsize then begin if oldsize > sizeusagesize then dec(sizeusage[sizeusageindex]) else if oldsize >= 0 then dec(sizeusage[oldsize shr sizeusageshift]); if size > sizeusagesize then inc(sizeusage[sizeusageindex]) else if size >= 0 then inc(sizeusage[size shr sizeusageshift]); end; {$endif} end; end; SysReAllocMem := p; end; {$endif HAS_MEMORYMANAGER} {$ifndef HAS_MEMORYMANAGER} {***************************************************************************** InitHeap *****************************************************************************} { This function will initialize the Heap manager and need to be called from the initialization of the system unit } {$ifdef FPC_HAS_FEATURE_THREADING} procedure InitHeapThread; var loc_freelists: pfreelists; begin if heap_lock_use > 0 then begin entercriticalsection(heap_lock); inc(heap_lock_use); leavecriticalsection(heap_lock); end; loc_freelists := @freelists; fillchar(loc_freelists^,sizeof(tfreelists),0); { initialise the local blocksize for allocating oschunks for fixed freelists with the default starting value } loc_freelists^.locgrowheapsizesmall:=growheapsizesmall; {$ifdef DUMP_MEM_USAGE} fillchar(sizeusage,sizeof(sizeusage),0); fillchar(maxsizeusage,sizeof(sizeusage),0); {$endif} end; {$endif} procedure InitHeap; public name '_FPC_InitHeap'; var loc_freelists: pfreelists; begin {$ifdef FPC_HAS_FEATURE_THREADING} { we cannot initialize the locks here yet, thread support is not loaded yet } heap_lock_use := 0; {$endif} loc_freelists := @freelists; fillchar(loc_freelists^,sizeof(tfreelists),0); { initialise the local blocksize for allocating oschunks for fixed freelists with the default starting value } loc_freelists^.locgrowheapsizesmall:=growheapsizesmall; fillchar(orphaned_freelists,sizeof(orphaned_freelists),0); end; procedure RelocateHeap; var loc_freelists: pfreelists; begin { this function should be called in main thread context } {$ifdef FPC_HAS_FEATURE_THREADING} if heap_lock_use > 0 then exit; heap_lock_use := 1; initcriticalsection(heap_lock); {$endif} loc_freelists := @freelists; { loc_freelists still points to main thread's freelists, but they have a reference to the global main freelists, fix them to point to the main thread specific variable } modify_freelists(loc_freelists, loc_freelists); if MemoryManager.RelocateHeap <> nil then MemoryManager.RelocateHeap(); end; procedure FinalizeHeap; var poc, poc_next: poschunk; loc_freelists: pfreelists; {$ifdef FPC_HAS_FEATURE_THREADING} last_thread: boolean; {$endif} {$ifdef DUMP_MEM_USAGE} i : longint; {$endif} begin loc_freelists := @freelists; {$ifdef FPC_HAS_FEATURE_THREADING} if heap_lock_use > 0 then begin entercriticalsection(heap_lock); finish_waitfixedlist(loc_freelists); finish_waitvarlist(loc_freelists); end; {$endif} {$ifdef HAS_SYSOSFREE} poc := loc_freelists^.oslist; while assigned(poc) do begin poc_next := poc^.next_free; { check if this os chunk was 'recycled' i.e. taken in use again } if (poc^.size and ocrecycleflag) = 0 then free_oschunk(loc_freelists, poc) else poc^.size := poc^.size and not ocrecycleflag; poc := poc_next; end; loc_freelists^.oslist := nil; loc_freelists^.oscount := 0; {$endif HAS_SYSOSFREE} {$ifdef FPC_HAS_FEATURE_THREADING} if heap_lock_use > 0 then begin poc := modify_freelists(loc_freelists, @orphaned_freelists); if assigned(poc) then begin poc^.next_any := orphaned_freelists.oslist_all; if assigned(orphaned_freelists.oslist_all) then orphaned_freelists.oslist_all^.prev_any := poc; orphaned_freelists.oslist_all := loc_freelists^.oslist_all; end; dec(heap_lock_use); last_thread := heap_lock_use = 0; leavecriticalsection(heap_lock); if last_thread then donecriticalsection(heap_lock); end; {$endif} {$ifdef SHOW_MEM_USAGE} writeln('Max heap used/size: ', loc_freelists^.internal_status.maxheapused, '/', loc_freelists^.internal_status.maxheapsize); flush(output); {$endif} {$ifdef DUMP_MEM_USAGE} for i := 0 to sizeusageindex-1 do if maxsizeusage[i] <> 0 then writeln('size ', i shl sizeusageshift, ' usage ', maxsizeusage[i]); writeln('size >', sizeusagesize, ' usage ', maxsizeusage[sizeusageindex]); flush(output); {$endif} end; {$endif HAS_MEMORYMANAGER} {$endif defined(FPC_HAS_FEATURE_HEAP) or defined(FPC_IN_HEAPMGR)}