{ 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 } { Custom memory manager is Multi Threaded and does not require locking } { define HAS_MT_MEMORYMANAGER} { Do not use standard memory manager } { Custom memory manager requires locking when threading is used } { define HAS_MEMORYMANAGER} { Try to find the best matching block in general freelist } { define BESTMATCH} { DEBUG: Dump info when the heap needs to grow } { define DUMPGROW} {$ifdef HAS_MT_MEMORYMANAGER} {$define HAS_MEMORYMANAGER} {$endif HAS_MT_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 } maxreusebigger = 8; { max reuse bigger tries } { 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 } sizemask = not(blocksize-1); fixedoffsetshift = 16; fixedsizemask = sizemask and ((1 shl fixedoffsetshift) - 1); {****************************************************************************} {$ifdef DUMPGROW} {$define DUMPBLOCKS} {$endif} { Forward defines } procedure SysHeapMutexInit;forward; procedure SysHeapMutexDone;forward; procedure SysHeapMutexLock;forward; procedure SysHeapMutexUnlock;forward; { Memory manager } const MemoryManager: TMemoryManager = ( {$ifdef HAS_MT_MEMORYMANAGER} NeedLock: false; {$else HAS_MT_MEMORYMANAGER} NeedLock: true; {$endif HAS_MT_MEMORYMANAGER} GetMem: @SysGetMem; FreeMem: @SysFreeMem; FreeMemSize: @SysFreeMemSize; AllocMem: @SysAllocMem; ReAllocMem: @SysReAllocMem; MemSize: @SysMemSize; GetHeapStatus: @SysGetHeapStatus; GetFPCHeapStatus: @SysGetFPCHeapStatus; ); MemoryMutexManager: TMemoryMutexManager = ( MutexInit: @SysHeapMutexInit; MutexDone: @SysHeapMutexDone; MutexLock: @SysHeapMutexLock; MutexUnlock: @SysHeapMutexUnlock; ); {$ifndef HAS_MEMORYMANAGER} type poschunk = ^toschunk; toschunk = record size : ptrint; next, prev : poschunk; used : ptrint; { padding inserted automatically by alloc_oschunk } end; pmemchunk_fixed = ^tmemchunk_fixed; tmemchunk_fixed = record { aligning is done automatically in alloc_oschunk } size : ptrint; next_fixed, prev_fixed : pmemchunk_fixed; end; pmemchunk_var = ^tmemchunk_var; tmemchunk_var = record prevsize : ptrint; size : ptrint; 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 : ptrint; end; tmemchunk_var_hdr = record prevsize : ptrint; size : ptrint; end; tfreelists = array[1..maxblockindex] of pmemchunk_fixed; pfreelists = ^tfreelists; 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); var internal_status : TFPCHeapStatus; freelists_fixed : tfreelists; freelists_free_chunk : array[1..maxblockindex] of boolean; freelist_var : pmemchunk_var; freeoslist : poschunk; freeoslistcount : dword; {$endif HAS_MEMORYMANAGER} {***************************************************************************** Memory Manager *****************************************************************************} procedure SetMemoryMutexManager(var MutexMgr: TMemoryMutexManager); begin { Release old mutexmanager, the default manager does nothing so calling this without initializing is safe } MemoryMutexManager.MutexDone; { Copy new mutexmanager } MemoryMutexManager := MutexMgr; { Init new mutexmanager } MemoryMutexManager.MutexInit; end; procedure GetMemoryManager(var MemMgr:TMemoryManager); begin if IsMultiThread and MemoryManager.NeedLock then begin try MemoryMutexManager.MutexLock; MemMgr := MemoryManager; finally MemoryMutexManager.MutexUnlock; end; end else begin MemMgr := MemoryManager; end; end; procedure SetMemoryManager(const MemMgr:TMemoryManager); begin if IsMultiThread and MemoryManager.NeedLock then begin try MemoryMutexManager.MutexLock; MemoryManager := MemMgr; finally MemoryMutexManager.MutexUnlock; end; end else begin MemoryManager := MemMgr; end; end; function IsMemoryManagerSet:Boolean; begin if IsMultiThread and MemoryManager.NeedLock then begin try MemoryMutexManager.MutexLock; IsMemoryManagerSet := (MemoryManager.GetMem<>@SysGetMem) or (MemoryManager.FreeMem<>@SysFreeMem); finally MemoryMutexManager.MutexUnlock; end; end else begin IsMemoryManagerSet := (MemoryManager.GetMem<>@SysGetMem) or (MemoryManager.FreeMem<>@SysFreeMem); end; end; procedure GetMem(Var p:pointer;Size:ptrint); begin if IsMultiThread and MemoryManager.NeedLock then begin try MemoryMutexManager.MutexLock; p := MemoryManager.GetMem(Size); finally MemoryMutexManager.MutexUnlock; end; end else begin p := MemoryManager.GetMem(Size); end; end; procedure GetMemory(Var p:pointer;Size:ptrint); begin GetMem(p,size); end; procedure FreeMem(p:pointer;Size:ptrint); begin if IsMultiThread and MemoryManager.NeedLock then begin try MemoryMutexManager.MutexLock; MemoryManager.FreeMemSize(p,Size); finally MemoryMutexManager.MutexUnlock; end; end else begin MemoryManager.FreeMemSize(p,Size); end; end; procedure FreeMemory(p:pointer;Size:ptrint); begin FreeMem(p,size); end; function GetHeapStatus:THeapStatus; begin if IsMultiThread and MemoryManager.NeedLock then begin try MemoryMutexManager.MutexLock; result:=MemoryManager.GetHeapStatus(); finally MemoryMutexManager.MutexUnlock; end; end else begin result:=MemoryManager.GetHeapStatus(); end; end; function GetFPCHeapStatus:TFPCHeapStatus; begin if IsMultiThread and MemoryManager.NeedLock then begin try MemoryMutexManager.MutexLock; result:=MemoryManager.GetFPCHeapStatus(); finally MemoryMutexManager.MutexUnlock; end; end else begin Result:=MemoryManager.GetFPCHeapStatus(); end; end; function MemSize(p:pointer):ptrint; begin if IsMultiThread and MemoryManager.NeedLock then begin try MemoryMutexManager.MutexLock; MemSize := MemoryManager.MemSize(p); finally MemoryMutexManager.MutexUnlock; end; end else begin MemSize := MemoryManager.MemSize(p); end; end; { Delphi style } function FreeMem(p:pointer):ptrint;[Public,Alias:'FPC_FREEMEM_X']; begin if IsMultiThread and MemoryManager.NeedLock then begin try MemoryMutexManager.MutexLock; Freemem := MemoryManager.FreeMem(p); finally MemoryMutexManager.MutexUnlock; end; end else begin Freemem := MemoryManager.FreeMem(p); end; end; function FreeMemory(p:pointer):ptrint; begin FreeMemory := FreeMem(p); end; function GetMem(size:ptrint):pointer; begin if IsMultiThread and MemoryManager.NeedLock then begin try MemoryMutexManager.MutexLock; GetMem := MemoryManager.GetMem(Size); finally MemoryMutexManager.MutexUnlock; end; end else begin GetMem := MemoryManager.GetMem(Size); end; end; function GetMemory(size:ptrint):pointer; begin GetMemory := Getmem(size); end; function AllocMem(Size:ptrint):pointer; begin if IsMultiThread and MemoryManager.NeedLock then begin try MemoryMutexManager.MutexLock; AllocMem := MemoryManager.AllocMem(size); finally MemoryMutexManager.MutexUnlock; end; end else begin AllocMem := MemoryManager.AllocMem(size); end; end; function ReAllocMem(var p:pointer;Size:ptrint):pointer; begin if IsMultiThread and MemoryManager.NeedLock then begin try MemoryMutexManager.MutexLock; ReAllocMem := MemoryManager.ReAllocMem(p,size); finally MemoryMutexManager.MutexUnlock; end; end else begin ReAllocMem := MemoryManager.ReAllocMem(p,size); end; end; function ReAllocMemory(var p:pointer;Size:ptrint):pointer; begin ReAllocMemory := ReAllocMem(p,size); end; { Needed for calls from Assembler } function fpc_getmem(size:ptrint):pointer;compilerproc;[public,alias:'FPC_GETMEM']; begin if IsMultiThread and MemoryManager.NeedLock then begin try MemoryMutexManager.MutexLock; fpc_GetMem := MemoryManager.GetMem(size); finally MemoryMutexManager.MutexUnlock; end; end else begin fpc_GetMem := MemoryManager.GetMem(size); end; end; procedure fpc_freemem(p:pointer);compilerproc;[public,alias:'FPC_FREEMEM']; begin if IsMultiThread and MemoryManager.NeedLock then begin try MemoryMutexManager.MutexLock; if p <> nil then MemoryManager.FreeMem(p); finally MemoryMutexManager.MutexUnlock; end; end else begin if p <> nil then MemoryManager.FreeMem(p); end; end; {$ifndef HAS_MEMORYMANAGER} {***************************************************************************** GetHeapStatus *****************************************************************************} function SysGetFPCHeapStatus:TFPCHeapStatus; begin internal_status.CurrHeapFree:=internal_status.CurrHeapSize-internal_status.CurrHeapUsed; result:=internal_status; end; function SysGetHeapStatus :THeapStatus; begin internal_status.CurrHeapFree:=internal_status.CurrHeapSize-internal_status.CurrHeapUsed; result.TotalAllocated :=internal_status.CurrHeapUsed; result.TotalFree :=internal_status.CurrHeapFree; result.TotalAddrSpace :=0; 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; var s,i,j : ptrint; hpfixed : pmemchunk_fixed; hpvar : pmemchunk_var; begin { fixed freelist } for i := 1 to maxblockindex do begin hpfixed := freelists_fixed[i]; j := 0; while assigned(hpfixed) do begin inc(j); hpfixed := hpfixed^.next_fixed; end; writeln('Block ',i*blocksize,': ',j); end; { var freelist } hpvar := freelist_var; 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} {***************************************************************************** List adding/removal *****************************************************************************} procedure append_to_list_var(pmc: pmemchunk_var); inline; begin pmc^.prev_var := nil; pmc^.next_var := freelist_var; if freelist_var<>nil then freelist_var^.prev_var := pmc; freelist_var := pmc; end; procedure remove_from_list_fixed(blockindex: ptrint; pmc: pmemchunk_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 freelists_fixed[blockindex] := 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 freelist_var := pmc^.next_var; end; procedure append_to_oslist(poc: poschunk); begin { decide whether to free block or add to list } {$ifdef HAS_SYSOSFREE} if (freeoslistcount >= MaxKeptOSChunks) or (poc^.size > growheapsize2) then begin dec(internal_status.currheapsize, poc^.size); SysOSFree(poc, poc^.size); end else begin {$endif} poc^.prev := nil; poc^.next := freeoslist; if freeoslist <> nil then freeoslist^.prev := poc; freeoslist := poc; inc(freeoslistcount); {$ifdef HAS_SYSOSFREE} end; {$endif} end; procedure remove_from_oslist(poc: poschunk); begin if assigned(poc^.next) then poc^.next^.prev := poc^.prev; if assigned(poc^.prev) then poc^.prev^.next := poc^.next else freeoslist := poc^.next; dec(freeoslistcount); 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 append_to_oslist_fixed(chunkindex, chunksize: ptrint; poc: poschunk); var pmc: pmemchunk_fixed; i, size: ptrint; begin size := poc^.size; i := fixedfirstoffset; repeat pmc := pmemchunk_fixed(pointer(poc)+i); remove_from_list_fixed(chunkindex, pmc); inc(i, chunksize); until i > size - chunksize; append_to_oslist(poc); end; {***************************************************************************** Split block *****************************************************************************} procedure split_block(pcurr: pmemchunk_var; size: ptrint); var pcurr_tmp : pmemchunk_var; sizeleft: ptrint; begin sizeleft := (pcurr^.size and sizemask)-size; if sizeleft>=blocksize then begin pcurr_tmp := pmemchunk_var(pointer(pcurr)+size); { update prevsize of block to the right } if (pcurr^.size and lastblockflag) = 0 then pmemchunk_var(pointer(pcurr)+(pcurr^.size and sizemask))^.prevsize := sizeleft; { inherit the lastblockflag } pcurr_tmp^.size := sizeleft or (pcurr^.size and lastblockflag); pcurr_tmp^.prevsize := size; { 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 (pcurr^.size and (not sizemask and not lastblockflag)); { insert the block in the freelist } append_to_list_var(pcurr_tmp); end; end; {***************************************************************************** Try concat freerecords *****************************************************************************} procedure concat_two_blocks(mc_left, mc_right: pmemchunk_var); var mc_tmp : pmemchunk_var; size_right : ptrint; 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; procedure try_concat_free_chunk_forward(mc: pmemchunk_var); var mc_tmp : pmemchunk_var; begin { try concat forward } 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); 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; function check_concat_free_chunk_forward(mc: pmemchunk_var;reqsize:ptrint):boolean; var mc_tmp : pmemchunk_var; freesize : ptrint; begin check_concat_free_chunk_forward:=false; freesize:=0; mc_tmp:=mc; repeat inc(freesize,mc_tmp^.size and sizemask); if freesize>=reqsize then begin check_concat_free_chunk_forward:=true; exit; end; if (mc_tmp^.size and lastblockflag) <> 0 then break; mc_tmp := pmemchunk_var(pointer(mc_tmp)+(mc_tmp^.size and sizemask)); if (mc_tmp^.size and usedflag) <> 0 then break; until false; end; {***************************************************************************** Grow Heap *****************************************************************************} function alloc_oschunk(chunkindex, size: ptrint): pointer; var pmc, pmc_next : pmemchunk_fixed; pmcv : pmemchunk_var; poc : poschunk; minsize, maxsize, i : ptrint; chunksize : ptrint; 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(ptrint); { blocks available in freelist? } poc := freeoslist; while poc <> nil do begin if (poc^.size >= minsize) and (poc^.size <= maxsize) then begin size := poc^.size; remove_from_oslist(poc); break; end; poc := poc^.next; end; if poc = nil then begin {$ifdef DUMPGROW} writeln('growheap(',size,') allocating ',(size+sizeof(toschunk)+$ffff) and not $ffff); DumpBlocks; {$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(GrowHeapSizeSmall); if poc<>nil then size := GrowHeapSizeSmall; 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; { set the total new heap size } inc(internal_status.currheapsize,size); if internal_status.currheapsize>internal_status.maxheapsize then internal_status.maxheapsize:=internal_status.currheapsize; end; { initialize os-block } poc^.used := 0; poc^.size := size; if chunkindex<>0 then begin { chop os chunk in fixedsize parts, maximum of $ffff elements are allowed, otherwise there will be an overflow } chunksize := chunkindex shl blockshift; if 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); pmc^.next_fixed := pointer(pmc)+chunksize; inc(i, chunksize); if i <= size - chunksize then begin pmc := pmemchunk_fixed(pointer(pmc)+chunksize); pmc^.prev_fixed := pointer(pmc)-chunksize; end else break; until false; pmc_next := freelists_fixed[chunkindex]; pmc^.next_fixed := pmc_next; if pmc_next<>nil then pmc_next^.prev_fixed := pmc; freelists_fixed[chunkindex] := pmemchunk_fixed(result); end else begin { 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); append_to_list_var(pmcv); pmcv^.size := ((size-varfirstoffset) and sizemask) or (firstblockflag or lastblockflag); pmcv^.prevsize := 0; end; end; {***************************************************************************** SysGetMem *****************************************************************************} function SysGetMem_Fixed(chunksize: ptrint): pointer; var pmc, pmc_next: pmemchunk_fixed; poc: poschunk; chunkindex: ptrint; begin { try to find a block in one of the freelists per size } chunkindex := chunksize shr blockshift; pmc := freelists_fixed[chunkindex]; result:=nil; { no free blocks ? } if not assigned(pmc) then begin pmc := alloc_oschunk(chunkindex, chunksize); if not assigned(pmc) then exit; end; { get a pointer to the block we should return } result := pointer(pmc)+sizeof(tmemchunk_fixed_hdr); { update freelist } pmc_next := pmc^.next_fixed; freelists_fixed[chunkindex] := pmc_next; if assigned(pmc_next) then pmc_next^.prev_fixed := nil; poc := poschunk(pointer(pmc) - (pmc^.size shr fixedoffsetshift)); if (poc^.used = 0) then freelists_free_chunk[chunkindex] := false; inc(poc^.used); { statistics } inc(internal_status.currheapused,chunksize); if internal_status.currheapused>internal_status.maxheapused then internal_status.maxheapused:=internal_status.currheapused; end; function SysGetMem_Var(size: ptrint): pointer; var pcurr : pmemchunk_var; {$ifdef BESTMATCH} pbest : pmemchunk_var; {$endif} begin result:=nil; {$ifdef BESTMATCH} pbest := nil; {$endif} pcurr := freelist_var; while assigned(pcurr) do begin {$ifdef BESTMATCH} if pcurr^.size=size then begin break; end else begin if (pcurr^.size>size) then begin if (not assigned(pbest)) or (pcurr^.size=size then break; {$endif BESTMATCH} pcurr := pcurr^.next_var; end; {$ifdef BESTMATCH} if not assigned(pcurr) then pcurr := pbest; {$endif} if not assigned(pcurr) then begin // all os-chunks full, allocate a new one pcurr := alloc_oschunk(0, size); if not assigned(pcurr) then exit; end; { get pointer of the block we should return } result := pointer(pcurr)+sizeof(tmemchunk_var_hdr); { remove the current block from the freelist } remove_from_list_var(pcurr); { create the left over freelist block, if at least 16 bytes are free } split_block(pcurr, size); { flag block as used } pcurr^.size := pcurr^.size or usedflag; { statistics } inc(internal_status.currheapused,size); if internal_status.currheapused>internal_status.maxheapused then internal_status.maxheapused:=internal_status.currheapused; end; function SysGetMem(size : ptrint):pointer; begin { Something to allocate ? } if size<=0 then begin { give an error for < 0 } if size<0 then HandleError(204); { we always need to allocate something, using heapend is not possible, because heappend can be changed by growheap (PFV) } size := 1; end; { 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 size := (size+(sizeof(tmemchunk_var_hdr)+(blocksize-1))) and sizemask; result := sysgetmem_var(size); end; end; {***************************************************************************** SysFreeMem *****************************************************************************} function SysFreeMem_Fixed(pmc: pmemchunk_fixed): ptrint; var chunkindex, chunksize: ptrint; poc: poschunk; pmc_next: pmemchunk_fixed; begin chunksize := pmc^.size and fixedsizemask; dec(internal_status.currheapused, chunksize); { insert the block in it's freelist } chunkindex := chunksize shr blockshift; pmc_next := freelists_fixed[chunkindex]; pmc^.prev_fixed := nil; pmc^.next_fixed := pmc_next; if assigned(pmc_next) then pmc_next^.prev_fixed := pmc; freelists_fixed[chunkindex] := pmc; { decrease used blocks count } poc := poschunk(pointer(pmc)-(pmc^.size shr fixedoffsetshift)); dec(poc^.used); if poc^.used <= 0 then begin { decrease used blocks count } if poc^.used=-1 then HandleError(204); { osblock can be freed? } if freelists_free_chunk[chunkindex] then append_to_oslist_fixed(chunkindex, chunksize, poc) else freelists_free_chunk[chunkindex] := true; end; result := chunksize; end; function SysFreeMem_Var(pmcv: pmemchunk_var): ptrint; var chunksize: ptrint; begin chunksize := pmcv^.size and sizemask; dec(internal_status.currheapused,chunksize); { insert the block in it's 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); result := chunksize; end; function SysFreeMem(p: pointer): ptrint; var pmc: pmemchunk_fixed; begin if p=nil then begin result:=0; exit; end; pmc := pmemchunk_fixed(p-sizeof(tmemchunk_fixed_hdr)); { check if this is a fixed- or var-sized chunk } if (pmc^.size and fixedsizeflag) = 0 then result := sysfreemem_var(pmemchunk_var(p-sizeof(tmemchunk_var_hdr))) else result := sysfreemem_fixed(pmc); end; {***************************************************************************** SysFreeMemSize *****************************************************************************} Function SysFreeMemSize(p: pointer; size: ptrint):ptrint; begin if size<=0 then begin if size<0 then HandleError(204); exit(0); end; { can't free partial blocks, ignore size } result := SysFreeMem(p); end; {***************************************************************************** SysMemSize *****************************************************************************} function SysMemSize(p: pointer): ptrint; 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: ptrint): pointer; begin result := MemoryManager.GetMem(size); if result<>nil then FillChar(result^,MemoryManager.MemSize(result),0); end; {***************************************************************************** SysResizeMem *****************************************************************************} function SysTryResizeMem(var p: pointer; size: ptrint): boolean; var chunksize, oldsize, currsize : ptrint; pcurr : pmemchunk_var; 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 memchunck } 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>(currsize-blocksize)) then begin SysTryResizeMem := true; exit; end; { get pointer to block } pcurr := pmemchunk_var(pointer(p)-sizeof(tmemchunk_var_hdr)); oldsize := currsize; { do we need to allocate more memory ? } if size>currsize then begin { the size is bigger than the previous size, we need to allocated more mem. We first check if the blocks after the current block are free. If not then we simply call getmem/freemem to get the new block } if check_concat_free_chunk_forward(pcurr,size) then repeat concat_two_blocks(pcurr,pmemchunk_var(pointer(pcurr)+currsize)); currsize := pcurr^.size and sizemask; until currsize>=size else exit; end; { 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 split_block(pcurr, size); inc(internal_status.currheapused,size-oldsize); SysTryResizeMem := true; end; {***************************************************************************** SysResizeMem *****************************************************************************} function SysReAllocMem(var p: pointer; size: ptrint):pointer; var newsize, oldsize, minsize : ptrint; 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 { Resize block } 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; end; SysReAllocMem := p; end; {$endif HAS_MEMORYMANAGER} {***************************************************************************** MemoryMutexManager default hooks *****************************************************************************} procedure SysHeapMutexInit; begin { nothing todo } end; procedure SysHeapMutexDone; begin { nothing todo } end; procedure SysHeapMutexLock; begin {$ifndef HAS_MT_MEMORYMANAGER} { give an runtime error. the program is running multithreaded without any heap protection. this will result in unpredictable errors so stopping here with an error is more safe (PFV) } runerror(244); {$endif} end; procedure SysHeapMutexUnLock; begin {$ifndef HAS_MT_MEMORYMANAGER} { see SysHeapMutexLock for comment } runerror(244); {$endif} end; {$ifndef HAS_MEMORYMANAGER} {***************************************************************************** InitHeap *****************************************************************************} {$if not(defined(gba)) and not(defined(nds))} { This function will initialize the Heap manager and need to be called from the initialization of the system unit } procedure InitHeap; begin FillChar(freelists_fixed,sizeof(tfreelists),0); FillChar(freelists_free_chunk,sizeof(freelists_free_chunk),0); freelist_var := nil; freeoslist := nil; freeoslistcount := 0; fillchar(internal_status,sizeof(internal_status),0); end; {$endif} procedure FinalizeHeap; var poc : poschunk; pmc : pmemchunk_fixed; i : longint; begin {$ifdef HAS_SYSOSFREE} for i:=low(freelists_free_chunk) to high(freelists_free_chunk) do if freelists_free_chunk[i] then begin pmc := freelists_fixed[i]; poc := poschunk(pointer(pmc)-(pmc^.size shr fixedoffsetshift)); SysOSFree(poc,poc^.size); end; while assigned(freeoslist) do begin poc:=freeoslist^.next; SysOSFree(freeoslist, freeoslist^.size); dec(freeoslistcount); freeoslist:=poc; end; {$endif HAS_SYSOSFREE} { release mutex } MemoryMutexManager.MutexDone; end; {$endif HAS_MEMORYMANAGER}