mirror of
https://github.com/vapaamies/KolibriOS.git
synced 2024-11-09 17:50:30 +01:00
1923 lines
62 KiB
ObjectPascal
1923 lines
62 KiB
ObjectPascal
{
|
|
|
|
Fast Memory Manager 2.07
|
|
|
|
Description:
|
|
A fast replacement memory manager for Borland Delphi Win32 applications that
|
|
scales well under multi-threaded situations, is not prone to memory
|
|
fragmentation, and supports shared memory without the use of external .DLL
|
|
files.
|
|
|
|
Advantages:
|
|
- Fast
|
|
- Low overhead (2 bytes per block)
|
|
- Highly aligned memory blocks (GetMem always returns blocks aligned on 16 byte
|
|
boundaries which is great for use with SSE/SSE2)
|
|
- Good scaling under multi-threaded apps
|
|
- Will catch most bad pointers on freemem calls (helps during debugging).
|
|
- Intelligent realloc (Reserves extra space if a block is upsized so that
|
|
future upsizes can be done in place.)
|
|
- Resistant to address space fragmentation
|
|
- No external DLL required when sharing memory between the application and
|
|
external libraries (provided both use this memory manager)
|
|
- Optionally reports small block memory leaks on program shutdown. (This check
|
|
is only performed if Delphi is currently running on the machine, so end users
|
|
won't be bothered by the error message.)
|
|
|
|
Usage:
|
|
Place this unit as the very first unit under the "uses" section in your
|
|
project's .dpr file. Requires Delphi 7 or later (Win32) compiler. May compile
|
|
under Delphi 5/6 with NoMMX defined (not tested). When sharing memory between
|
|
an application and a DLL (e.g. when passing a long string or dynamic array to a
|
|
DLL function), both the main application and the DLL must be compiled using
|
|
this memory manager. The are some conditional defines (specified below) which
|
|
may be used to tweak the memory manager.
|
|
|
|
License:
|
|
This work is copyright Professional Software Development / Pierre le Riche. It
|
|
is released under the Mozilla Public Licence 1.1 (MPL 1.1). The licence terms
|
|
are described on this page: http://www.mozilla.org/MPL/MPL-1.1.html. There is
|
|
no obligation to pay for FastMM, but if you find it useful or you would like to
|
|
support further development, a donation would be much appreciated. My banking
|
|
details are:
|
|
Country: South Africa
|
|
Bank: ABSA Bank Ltd
|
|
Branch: Somerset West
|
|
Branch Code: 334-712
|
|
Account Name: PSD (Distribution)
|
|
Account No.: 4041827693
|
|
Swift Code: ABSAZAJJ
|
|
My PayPal account is:
|
|
bof@psd.co.za
|
|
|
|
Contact Details:
|
|
Below are my contact details if you would like to get in touch with me. If you
|
|
use this memory manager I would like to hear from you: please e-mail me your
|
|
comments - good and bad.
|
|
Snailmail:
|
|
PO Box 2514
|
|
Somerset West
|
|
7129
|
|
South Africa
|
|
E-mail:
|
|
plr@psd.co.za
|
|
Webpage:
|
|
fastmm.sourceforge.net
|
|
|
|
Disclaimer:
|
|
FastMM has been tested extensively with both single and muiltithreaded
|
|
applications, but unfortunately I am not in a position to make any
|
|
guarantees. Use it at your own risk.
|
|
|
|
Change log:
|
|
Version 1.00 (28 June 2004):
|
|
First version (called PSDMemoryManager). Based on RecyclerMM (free block
|
|
stack approach) by Eric Grange.
|
|
Version 2.00 (3 November 2004):
|
|
Complete redesign and rewrite from scratch. Name changed to FastMM to reflect
|
|
this fact. Uses a linked-list approach. Is faster, has less memory overhead,
|
|
and will now catch most bad pointers on FreeMem calls.
|
|
Version 2.01 (9 November 2004):
|
|
Added CountThreadContentions option. Counts the number of times any thread
|
|
had to wait for another to perform a memory manager operation. (For
|
|
statistical purposes).
|
|
Version 2.02 (14 November 2004):
|
|
1) Added option to no longer free the last empty batch or chunk. Increases
|
|
residual memory usage slightly, but possibly saves many VirtualAlloc and
|
|
GetChunk calls (on by default).
|
|
2) Added an option never to downsize blocks on ReallocMem. Significantly
|
|
increases speed of reallocations, but can dramatically increase memory
|
|
consumption.
|
|
3) Added NoMMX option to disable the use of MMX (for very old CPUs).
|
|
Version 2.03 (15 November 2004):
|
|
Added an option to count all GetMem and FreeMem calls. (For statistical
|
|
purposes.)
|
|
Version 2.04 (10 January 2005):
|
|
Optionally reports memory leaks on program shutdown. (Provided Delphi is
|
|
running on the machine.)
|
|
Version 2.05 (18 January 2005)
|
|
Added a sleep(10) in spinlock loops to avoid livelocks in heavy multithreaded
|
|
usage.
|
|
Version 2.06 (20 April 2005)
|
|
Fixed a memory leak that occurred when FastMM was used inside a .DLL file
|
|
with the option AlwaysFreeChunks disabled.
|
|
Version 2.07 (31 May 2005)
|
|
Disabled checking for memory leaks when shutting down a DLL that was sharing
|
|
the main application's MM.
|
|
}
|
|
|
|
unit FastMM;
|
|
|
|
interface
|
|
|
|
{Remove the . in the next line to disable MMX and use a (slower) alternative
|
|
move routine. This will allow this memory manager to be used with older
|
|
80486 and original Pentium CPUs.}
|
|
{$define NoMMX}
|
|
|
|
{Remove the . in the next line to disable pointer checks for a slight
|
|
performance increase (at the cost of not catching bad pointers on
|
|
freemem calls).}
|
|
{.$define NoPointerChecks}
|
|
|
|
{Remove the . in the next line to enable debug mode. Uses (mostly) Pascal code
|
|
instead of asm (slower, for debugging the MM only).}
|
|
{.$define DebugMM}
|
|
|
|
{Remove the . in the next line to always free empty chunks. This lowers the
|
|
residual memory consumption, but may in certain cicumstances cause many more
|
|
GetChunk/FreeChunk calls than would otherwise have been necessary (which could
|
|
impact performance). If this options is left off, the last empty 64K chunk for
|
|
each block size as well as the last empty 2M batch is never freed (except
|
|
during process termination of course).}
|
|
{.$define AlwaysFreeChunks}
|
|
|
|
{Remove the . in the next line to never downsize blocks. When this is enabled
|
|
a ReallocMem request will always be performed in place if the new size is
|
|
smaller than the old size. This provides a nice speed boost when doing
|
|
frequent reallocations, but at the cost of increased memory consumption.}
|
|
{.$define NeverDownsizeOnReallocMem}
|
|
|
|
{Remove the . in the next line to count thread contentions. No real purpose
|
|
other than to provide an interesting statistic.}
|
|
{.$define CountThreadContentions}
|
|
|
|
{Remove the . in the next line to count getmem and freemem operations
|
|
(ReallocMem will thus count as 2 operations). No real purpose other than to
|
|
provide an interesting statistic.}
|
|
{.$define CountGetMemAndFreeMemOperations}
|
|
|
|
{Remove the . in the next line to disable reporting of memory leaks. Memory
|
|
leaks are only reported if the Delphi IDE is currently running on the computer
|
|
(so as not to alarm end users).}
|
|
{$define NoMemoryLeakReporting}
|
|
|
|
{Suppress platform warnings}
|
|
{$WARN SYMBOL_PLATFORM OFF}
|
|
|
|
{$ifdef CountThreadContentions}
|
|
var
|
|
{The number of small block operations (freemem/getmem) and the number of
|
|
times that a thread had to wait for another thread when attempting a small
|
|
block operation}
|
|
SmallBlockOperations, SmallBlockContentions: Int64;
|
|
{The number of batch operations (GetChunk/FreeChunk) and the number of times
|
|
that a thread had to wait for another thread when attempting to allocate or
|
|
free a chunk.}
|
|
BatchOperations, BatchContentions: Int64;
|
|
{$endif}
|
|
|
|
{$ifdef CountGetMemAndFreeMemOperations}
|
|
var
|
|
{The total number of GetMem and FreeMem operations. ReallocMem will count
|
|
as either 0 (inplace), 1 (free) or 2 (resize) operations. This counter has
|
|
no real purpose other than to give you an indication of how memory management
|
|
intensive your application is.}
|
|
TotalGetMemAndFreeMemOperations: Int64;
|
|
{$endif}
|
|
|
|
implementation
|
|
|
|
uses
|
|
{$ifdef KolibriOS}
|
|
KolibriOS;
|
|
{$else}
|
|
Windows;
|
|
{$endif}
|
|
|
|
const
|
|
{The number of small block types}
|
|
NumSmallBlockTypes = 64;
|
|
{The number of chunks addressable by this process. VirtualAlloc has a 64K
|
|
granularity, so chunks are always aligned on a 64K boundary}
|
|
MaxNumChunks = 32768; // = 2GB / 64K
|
|
{The size of an allocation chunk}
|
|
ChunkSize = 64 * 1024;
|
|
{The number chunks per batch}
|
|
NumChunksPerBatch = 32;
|
|
{The size of a batch}
|
|
BatchSize = ChunkSize * NumChunksPerBatch;
|
|
{The maximum number of batches.}
|
|
MaxNumBatches = MaxNumChunks div NumChunksPerBatch;
|
|
{The size of the largest small block handled. Must be a multiple of 16.}
|
|
MaximumSmallBlockSize = 32752;
|
|
{$ifndef NeverDownsizeOnReallocMem}
|
|
{Block sizes smaller than this will never be downsized. Blocks of this size
|
|
or greater will be downsized if reallocmem is called with a new size which
|
|
is less than a quarter of the current block size.}
|
|
MinimumBlockSizeToAllowDownsizing = 48;
|
|
{$endif}
|
|
{When a pointer is reallocated and the new size is larger than the old size,
|
|
the new requested size is multiplied with the factor (1 + 1 / 2^x) to
|
|
facilitate faster subsequent reallocations.}
|
|
ReallocPaddingFactor = 2;
|
|
{The block type index for large blocks}
|
|
LargeBlockTypeIndex = 100;
|
|
{The granularity of large blocks. Large blocks are allocated directly by
|
|
VirtualAlloc. Address space granularity under Windows is 64K, so allocating
|
|
large blocks in smaller chunks just wastes address space.}
|
|
LargeBlockGranularity = 64 * 1024;
|
|
{Hexadecimal characters}
|
|
LHexTable: ShortString = '0123456789ABCDEF';
|
|
{Special purpose allocation IDs}
|
|
LastBlockMarker = 65535;
|
|
AllocatedBlockMarker = ord('a') + ord('b') * 256;
|
|
{Sleep times when a resource (batch or small block manager) is in use}
|
|
InitialSleepTime = 0;
|
|
{Used when the resource is still in use after the first sleep}
|
|
AdditionalSleepTime = 10;
|
|
|
|
type
|
|
|
|
{Chunk Manager - 16 bytes in size}
|
|
PChunkManager = ^TChunkManager;
|
|
TChunkManager = packed record
|
|
{The block type}
|
|
BlockType: byte;
|
|
{Padding byte}
|
|
Reserved1: byte;
|
|
{The batch managing this chunk}
|
|
BatchIndex: word;
|
|
{The large/small block info structures}
|
|
case integer of
|
|
0: {Small Block Chunk}
|
|
(
|
|
{The previous and next chunks that has free blocks of
|
|
the same size.}
|
|
PreviousPartiallyFreeChunk: PChunkManager;
|
|
NextPartiallyFreeChunk: PChunkManager;
|
|
{The first free block number.}
|
|
FirstFreeBlockNumber: word;
|
|
{The number of blocks allocated in this chunk}
|
|
BlocksInUse: word;
|
|
);
|
|
1: {Large Block Chunk}
|
|
(
|
|
{The size requested by the user program}
|
|
LargeBlockRequestedSize: Cardinal;
|
|
{Allocated block size}
|
|
LargeBlockAllocatedSize: Cardinal;
|
|
);
|
|
end;
|
|
|
|
var
|
|
{---------------Small block type info-----------------}
|
|
{The first partially free chunk for the given block size}
|
|
FirstPartiallyFreeChunk: array[0..NumSmallBlockTypes - 1] of PChunkManager;
|
|
{This is 2^32 / BlockSize. Multiply by this value and take the high dword
|
|
to get the block number from an offset into the chunk}
|
|
BlockSizeInverse: array[0..NumSmallBlockTypes - 1] of Cardinal;
|
|
{All the different small block sizes. Block sizes must be a multiple of 16.
|
|
Block sizes below are hand-picked to ensure minimal memory wastage and a
|
|
reasonable progression of block sizes.}
|
|
BlockSize: array[0..NumSmallBlockTypes - 1] of word = (
|
|
{16 byte jumps}
|
|
16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240, 256,
|
|
{32 byte jumps}
|
|
288, 320, 352, 384, 416, 448, 480, 512, 544, 576, 608, 640, 672, 704, 736, 768,
|
|
{48 byte jumps}
|
|
816, 864, 912, 960, 1008,
|
|
{arbitrary jumps}
|
|
1072, 1168, 1280, 1392, 1520, 1632, 1760, 1920, 2096, 2336, 2608, 2976,
|
|
3440, 3840, 4352, 5024, 5456, 5952, 6544, 7264, 8176, 9344, 10912, 13088,
|
|
16368, 21824, MaximumSmallBlockSize);
|
|
{The highest block number offset in the chunk. Corresponds to the address of
|
|
the last block number.}
|
|
HighestBlockNumberOffset: array[0..NumSmallBlockTypes - 1] of word;
|
|
{The offset of the start of the blocks. Follows after the chunk manager and
|
|
blocks list, aligned to a 16-byte boundary}
|
|
FirstBlockOffset: array[0..NumSmallBlockTypes - 1] of word;
|
|
{Indicates whether a block type is locked or not}
|
|
BlockTypeLocked: array[0..NumSmallBlockTypes - 1] of boolean;
|
|
{Lookup table to convert an allocation size to a block type index. Calculate
|
|
(allocation size - 1) div 16 to get an index into the table.}
|
|
AllocationSizeToBlockType: array[0..MaximumSmallBlockSize div 16 - 1] of byte;
|
|
{-------------------Batch info----------------------}
|
|
{Are the batch managers locked?}
|
|
BatchManagersLocked: boolean;
|
|
{Batch manager usage bitmaps (4 * 1025 = 4K)}
|
|
BatchUsageBitmap: array[0..MaxNumBatches] of Cardinal;
|
|
{The address of teh start of each batch (4 * 1025 = 4K)}
|
|
BatchStartAddress: array[0..MaxNumBatches] of Pointer;
|
|
{The previous batch manager in the linked list (2 * 1025 = 2K)}
|
|
PreviousBatchIndex: array[0..MaxNumBatches] of word;
|
|
{The next batch manager in the linked list. NextBatchIndex[0] points to the
|
|
first batch that has unallocated chunks. (2 * 1025 = 2K)}
|
|
NextBatchIndex: array[0..MaxNumBatches] of word;
|
|
{-----------------Chunk info---------------------}
|
|
{The dummy chunk manager - used in place of nil chunk pointers to avoid
|
|
pointer checks}
|
|
DummyChunkManager: TChunkManager;
|
|
{--------------Other info--------------}
|
|
{The memory manager that was replaced}
|
|
OldMemoryManager: TMemoryManager;
|
|
{The replacement memory manager}
|
|
NewMemoryManager: TMemoryManager;
|
|
{$ifndef KolibriOS}
|
|
{A string uniquely identifying the current process (for sharing memory managers)}
|
|
UniqueProcessIDString: ShortString = '????????_PID_FastMM'#0;
|
|
{The handle of the MM window}
|
|
MMWindow: HWND;
|
|
{Is the MM in place a shared memory manager?}
|
|
OwnsMMWindow: Boolean;
|
|
{$endif KolibriOS}
|
|
|
|
{----------------Windows Emulation Functions for KolibriOS Support-----------------}
|
|
{$ifdef KolibriOS}
|
|
const
|
|
{Virtual memory constants}
|
|
MEM_COMMIT = $1000;
|
|
MEM_RELEASE = $8000;
|
|
MEM_TOP_DOWN = $100000;
|
|
PAGE_READWRITE = 4;
|
|
|
|
function VirtualAlloc(lpvAddress: Pointer; dwSize, flAllocationType, flProtect: LongWord): Pointer; stdcall;
|
|
begin
|
|
Result := KolibriOS.HeapAllocate(dwSize);
|
|
end;
|
|
|
|
function VirtualFree(lpAddress: Pointer; dwSize, dwFreeType: LongWord): LongBool; stdcall;
|
|
begin
|
|
Result := LongBool(KolibriOS.HeapFree(lpAddress));
|
|
end;
|
|
|
|
procedure Sleep(dwMilliseconds: LongWord); stdcall;
|
|
begin
|
|
KolibriOS.Sleep((dwMilliseconds + 10 div 2) div 10);
|
|
end;
|
|
|
|
function SleepEx(dwMilliseconds: LongWord; bAlertable: LongBool): LongWord; stdcall;
|
|
begin
|
|
Sleep(dwMilliseconds);
|
|
Result := 0;
|
|
end;
|
|
{$endif KolibriOS}
|
|
|
|
{Moves data from source to dest in 16 byte chunks (rounded up). Does not
|
|
support overlapping. Count must be > 0}
|
|
procedure Move16(ASource, ADestination: Pointer; ACount: Cardinal);
|
|
asm
|
|
{On entry:
|
|
eax = ASource
|
|
edx = ADestination
|
|
ecx = ACount}
|
|
{Round up to the nearest 16 bytes}
|
|
add ecx, 15
|
|
and ecx, -16
|
|
{Convert the counter to negative based}
|
|
add eax, ecx
|
|
add edx, ecx
|
|
neg ecx
|
|
{Is there an uneven number of 16 byte blocks to copy?}
|
|
test ecx, 16
|
|
jz @MoveLoop
|
|
{$ifndef NoMMX}
|
|
{Uneven number of 16-byte blocks: Move the first 16 bytes}
|
|
movq mm0, [eax + ecx]
|
|
movq mm1, [eax + ecx + 8]
|
|
movq [edx + ecx], mm0
|
|
movq [edx + ecx + 8], mm1
|
|
add ecx, 16
|
|
jns @Done
|
|
{Do blocks of 32 bytes}
|
|
@MoveLoop:
|
|
movq mm0, [eax + ecx]
|
|
movq mm1, [eax + ecx + 8]
|
|
movq mm2, [eax + ecx + 16]
|
|
movq mm3, [eax + ecx + 24]
|
|
movq [edx + ecx], mm0
|
|
movq [edx + ecx + 8], mm1
|
|
movq [edx + ecx + 16], mm2
|
|
movq [edx + ecx + 24], mm3
|
|
add ecx, 32
|
|
js @MoveLoop
|
|
@Done:
|
|
{Exits mmx state}
|
|
emms
|
|
{$else}
|
|
{Uneven number of 16-byte blocks: Move the first 16 bytes}
|
|
fild qword ptr [eax + ecx + 8]
|
|
fild qword ptr [eax + ecx]
|
|
fistp qword ptr [edx + ecx]
|
|
fistp qword ptr [edx + ecx + 8]
|
|
add ecx, 16
|
|
jns @Done
|
|
{Do blocks of 32 bytes}
|
|
@MoveLoop:
|
|
fild qword ptr [eax + ecx + 24]
|
|
fild qword ptr [eax + ecx + 16]
|
|
fild qword ptr [eax + ecx + 8]
|
|
fild qword ptr [eax + ecx]
|
|
fistp qword ptr [edx + ecx]
|
|
fistp qword ptr [edx + ecx + 8]
|
|
fistp qword ptr [edx + ecx + 16]
|
|
fistp qword ptr [edx + ecx + 24]
|
|
add ecx, 32
|
|
js @MoveLoop
|
|
@Done:
|
|
{$endif}
|
|
end;
|
|
|
|
{$ifdef DebugMM}
|
|
{Locks the batch managers so only this thread can access them}
|
|
procedure LockBatchManagers;
|
|
asm
|
|
{$ifdef CountThreadContentions}
|
|
add dword ptr BatchOperations, 1
|
|
adc dword ptr BatchOperations[4], 0
|
|
{$endif}
|
|
@LockLoop:
|
|
xor al, al
|
|
mov dl, 1
|
|
{Attempt to lock the batches}
|
|
lock cmpxchg BatchManagersLocked, dl
|
|
jz @Done
|
|
{Couldn't lock the batches - sleep and try again}
|
|
{$ifdef CountThreadContentions}
|
|
add dword ptr BatchContentions, 1
|
|
adc dword ptr BatchContentions[4], 0
|
|
{$endif}
|
|
push False
|
|
push InitialSleepTime
|
|
call SleepEx
|
|
{Try again}
|
|
xor al, al
|
|
mov dl, 1
|
|
{Attempt to lock the batches}
|
|
lock cmpxchg BatchManagersLocked, dl
|
|
jz @Done
|
|
{Couldn't lock the batches - sleep and try again}
|
|
{$ifdef CountThreadContentions}
|
|
add dword ptr BatchContentions, 1
|
|
adc dword ptr BatchContentions[4], 0
|
|
{$endif}
|
|
push False
|
|
push AdditionalSleepTime
|
|
call SleepEx
|
|
jmp @LockLoop
|
|
@Done:
|
|
end;
|
|
{$endif}
|
|
|
|
{$ifdef DebugMM}
|
|
{Allocates a 64K chunk and returns its index. Returns 0 if out of memory.
|
|
(Pascal version)}
|
|
function GetChunk: PChunkManager;
|
|
var
|
|
i, LBatchIndex: Cardinal;
|
|
LPChunk: PChunkManager;
|
|
begin
|
|
{Lock the batch managers}
|
|
LockBatchManagers;
|
|
{Is there a batch manager with space?}
|
|
if NextBatchIndex[0] > 0 then
|
|
begin
|
|
{Find the unused block number}
|
|
asm
|
|
{Get a pointer to the usage bitmap}
|
|
movzx eax, word ptr [NextBatchIndex]
|
|
{Find a set bit and put the index in ecx}
|
|
bsf ecx, dword ptr [BatchUsageBitmap + eax * 4]
|
|
{Reset this bit}
|
|
btr dword ptr [BatchUsageBitmap + eax * 4], ecx
|
|
{Multiply the bit number by 64K}
|
|
shl ecx, 16
|
|
{Add the start address of the batch}
|
|
add ecx, dword ptr [BatchStartAddress + eax * 4]
|
|
{Store the result}
|
|
mov Result, ecx
|
|
end;
|
|
{Is this batch manager now full?}
|
|
if BatchUsageBitmap[NextBatchIndex[0]] = 0 then
|
|
begin
|
|
{Remove this batch manager}
|
|
NextBatchIndex[0] := NextBatchIndex[NextBatchIndex[0]];
|
|
PreviousBatchIndex[NextBatchIndex[0]] := 0;
|
|
end;
|
|
end
|
|
else
|
|
begin
|
|
{No available chunks - allocate a batch}
|
|
Result := VirtualAlloc(nil, BatchSize, MEM_COMMIT, PAGE_READWRITE);
|
|
if Result <> nil then
|
|
begin
|
|
{The batch manager index is the address divided by 2M + 1}
|
|
LBatchIndex := Cardinal(Result) shr 21 + 1;
|
|
{Update the chunk index to batch index translation table}
|
|
LPChunk := Result;
|
|
for i := 0 to NumChunksPerBatch - 1 do
|
|
begin
|
|
LPChunk.BatchIndex := LBatchIndex;
|
|
LPChunk := PChunkManager(Cardinal(LPChunk) + 65536);
|
|
end;
|
|
{Set up the batch manager (it is the new first batch manager)}
|
|
BatchStartAddress[LBatchIndex] := Result;
|
|
BatchUsageBitmap[LBatchIndex] := $fffffffe; //Only the first chunk is currently used
|
|
PreviousBatchIndex[LBatchIndex] := 0;
|
|
NextBatchIndex[LBatchIndex] := 0;
|
|
NextBatchIndex[0] := LBatchIndex;
|
|
end;
|
|
end;
|
|
{Batch managers are no longer locked}
|
|
BatchManagersLocked := False;
|
|
end;
|
|
{$else}
|
|
{Allocates a 64K chunk and returns its index. Returns 0 if out of memory.
|
|
(asm version)}
|
|
function GetChunk: PChunkManager;
|
|
asm
|
|
{Lock the batch managers}
|
|
{$ifdef CountThreadContentions}
|
|
add dword ptr BatchOperations, 1
|
|
adc dword ptr BatchOperations[4], 0
|
|
{$endif}
|
|
@LockLoop:
|
|
xor al, al
|
|
mov dl, 1
|
|
{Attempt to lock the batches}
|
|
lock cmpxchg BatchManagersLocked, dl
|
|
jz @BatchesLocked
|
|
{Couldn't lock the batches - sleep and try again}
|
|
{$ifdef CountThreadContentions}
|
|
add dword ptr BatchContentions, 1
|
|
adc dword ptr BatchContentions[4], 0
|
|
{$endif}
|
|
push False
|
|
push InitialSleepTime
|
|
call SleepEx
|
|
{Try again}
|
|
xor al, al
|
|
mov dl, 1
|
|
{Attempt to lock the batches}
|
|
lock cmpxchg BatchManagersLocked, dl
|
|
jz @BatchesLocked
|
|
{Couldn't lock the batches - sleep and try again}
|
|
{$ifdef CountThreadContentions}
|
|
add dword ptr BatchContentions, 1
|
|
adc dword ptr BatchContentions[4], 0
|
|
{$endif}
|
|
push False
|
|
push AdditionalSleepTime
|
|
call SleepEx
|
|
{Try again}
|
|
jmp @LockLoop
|
|
@BatchesLocked:
|
|
{Get the index of the first batch with space in edx}
|
|
movzx edx, word ptr [NextBatchIndex]
|
|
{Is there a batch with space?}
|
|
test edx, edx
|
|
jz @AllocateBatch
|
|
{Get the current usage bitmap}
|
|
mov ecx, dword ptr [BatchUsageBitmap + edx * 4]
|
|
{Find the set bit and put the index in eax}
|
|
bsf eax, ecx
|
|
{Reset this bit}
|
|
btr ecx, eax
|
|
{Store the new usage bitmap}
|
|
mov dword ptr [BatchUsageBitmap + edx * 4], ecx
|
|
{Multiply the bit number by 64K}
|
|
shl eax, 16
|
|
{Add the start address of the batch}
|
|
add eax, dword ptr [BatchStartAddress + edx * 4]
|
|
{Is the batch now fully used?}
|
|
test ecx, ecx
|
|
jnz @GetChunkDone
|
|
{The batch is full - remove it from the partially free list, and make the
|
|
next batch the first entry}
|
|
movzx ecx, word ptr [NextBatchIndex + edx + edx];
|
|
mov word ptr NextBatchIndex, cx
|
|
mov word ptr [PreviousBatchIndex + ecx + ecx], 0
|
|
@GetChunkDone:
|
|
{Unlock the batch managers}
|
|
mov BatchManagersLocked, False
|
|
{Done}
|
|
ret
|
|
@AllocateBatch:
|
|
{Call virtualalloc to allocate a batch}
|
|
push PAGE_READWRITE
|
|
push MEM_COMMIT
|
|
push BatchSize
|
|
push 0
|
|
call VirtualAlloc
|
|
{Out of memory?}
|
|
test eax, eax
|
|
jz @GetChunkDone
|
|
{Get the batch index in edx: The batch manager index is the address
|
|
divided by 2M + 1}
|
|
mov edx, eax
|
|
shr edx, 21
|
|
inc edx
|
|
{Set up the batch manager: start address}
|
|
mov dword ptr [BatchStartAddress + edx * 4], eax
|
|
{Only one chunk used so far}
|
|
mov dword ptr [BatchUsageBitmap + edx * 4], $fffffffe
|
|
{No previous batch}
|
|
mov word ptr [PreviousBatchIndex + edx + edx], 0
|
|
{No next batch}
|
|
mov word ptr [NextBatchIndex + edx + edx], 0
|
|
{Set up the batch manager (it is the new first batch manager)}
|
|
mov word ptr [NextBatchIndex], dx
|
|
{Set the batch index in all the chunks}
|
|
mov TChunkManager([eax]).BatchIndex, dx
|
|
mov TChunkManager([eax + $010000]).BatchIndex, dx
|
|
mov TChunkManager([eax + $020000]).BatchIndex, dx
|
|
mov TChunkManager([eax + $030000]).BatchIndex, dx
|
|
mov TChunkManager([eax + $040000]).BatchIndex, dx
|
|
mov TChunkManager([eax + $050000]).BatchIndex, dx
|
|
mov TChunkManager([eax + $060000]).BatchIndex, dx
|
|
mov TChunkManager([eax + $070000]).BatchIndex, dx
|
|
mov TChunkManager([eax + $080000]).BatchIndex, dx
|
|
mov TChunkManager([eax + $090000]).BatchIndex, dx
|
|
mov TChunkManager([eax + $0A0000]).BatchIndex, dx
|
|
mov TChunkManager([eax + $0B0000]).BatchIndex, dx
|
|
mov TChunkManager([eax + $0C0000]).BatchIndex, dx
|
|
mov TChunkManager([eax + $0D0000]).BatchIndex, dx
|
|
mov TChunkManager([eax + $0E0000]).BatchIndex, dx
|
|
mov TChunkManager([eax + $0F0000]).BatchIndex, dx
|
|
mov TChunkManager([eax + $100000]).BatchIndex, dx
|
|
mov TChunkManager([eax + $110000]).BatchIndex, dx
|
|
mov TChunkManager([eax + $120000]).BatchIndex, dx
|
|
mov TChunkManager([eax + $130000]).BatchIndex, dx
|
|
mov TChunkManager([eax + $140000]).BatchIndex, dx
|
|
mov TChunkManager([eax + $150000]).BatchIndex, dx
|
|
mov TChunkManager([eax + $160000]).BatchIndex, dx
|
|
mov TChunkManager([eax + $170000]).BatchIndex, dx
|
|
mov TChunkManager([eax + $180000]).BatchIndex, dx
|
|
mov TChunkManager([eax + $190000]).BatchIndex, dx
|
|
mov TChunkManager([eax + $1A0000]).BatchIndex, dx
|
|
mov TChunkManager([eax + $1B0000]).BatchIndex, dx
|
|
mov TChunkManager([eax + $1C0000]).BatchIndex, dx
|
|
mov TChunkManager([eax + $1D0000]).BatchIndex, dx
|
|
mov TChunkManager([eax + $1E0000]).BatchIndex, dx
|
|
mov TChunkManager([eax + $1F0000]).BatchIndex, dx
|
|
{Unlock the batch managers}
|
|
mov BatchManagersLocked, False
|
|
end;
|
|
{$endif}
|
|
|
|
{$ifdef DebugMM}
|
|
{Frees a 64K chunk (pascal version)}
|
|
procedure FreeChunk(APChunkManager: PChunkManager);
|
|
var
|
|
LOldUsageBitmap, LNewUsageBitmap, LBatchIndex, LBitNumber: Cardinal;
|
|
begin
|
|
{Lock the batch managers}
|
|
LockBatchManagers;
|
|
{Get the batch manager index for this chunk}
|
|
LBatchIndex := APChunkManager.BatchIndex;
|
|
{Get the bit number from the chunk index}
|
|
LBitNumber := (Cardinal(APChunkManager) - Cardinal(BatchStartAddress[LBatchIndex])) shr 16;
|
|
{Get the old usage bitmap}
|
|
LOldUsageBitmap := BatchUsageBitmap[LBatchIndex];
|
|
LNewUsageBitmap := LOldUsageBitmap or (1 shl LBitNumber);
|
|
{Set the new usage bitmap}
|
|
BatchUsageBitmap[LBatchIndex] := LNewUsageBitmap;
|
|
{Are all chunks now unused?}
|
|
if LNewUsageBitmap <> $ffffffff then
|
|
begin
|
|
{Was this batch fully used? If so - put it back in the chunk list as the
|
|
first batch with space}
|
|
if LOldUsageBitmap = 0 then
|
|
begin
|
|
PreviousBatchIndex[NextBatchIndex[0]] := LBatchIndex;
|
|
NextBatchIndex[LBatchIndex] := NextBatchIndex[0];
|
|
PreviousBatchIndex[LBatchIndex] := 0;
|
|
NextBatchIndex[0] := LBatchIndex;
|
|
end;
|
|
{Batch managers are no longer locked}
|
|
BatchManagersLocked := False;
|
|
end
|
|
else
|
|
begin
|
|
{$ifndef AlwaysFreeChunks}
|
|
{Remove this batch manager if it is not the last batch in the list with free space}
|
|
if NextBatchIndex[LBatchIndex] <> 0 then
|
|
begin
|
|
{$endif}
|
|
PreviousBatchIndex[NextBatchIndex[LBatchIndex]] := PreviousBatchIndex[LBatchIndex];
|
|
NextBatchIndex[PreviousBatchIndex[LBatchIndex]] := NextBatchIndex[LBatchIndex];
|
|
{Batch managers are no longer locked}
|
|
BatchManagersLocked := False;
|
|
{Free the memory allocated by this batch}
|
|
VirtualFree(BatchStartAddress[LBatchIndex], 0, MEM_RELEASE);
|
|
{$ifndef AlwaysFreeChunks}
|
|
end
|
|
else
|
|
begin
|
|
{Batch managers are no longer locked}
|
|
BatchManagersLocked := False;
|
|
end;
|
|
{$endif}
|
|
end;
|
|
end;
|
|
{$else}
|
|
{Frees a 64K chunk (asm version)}
|
|
procedure FreeChunk(APChunkManager: PChunkManager);
|
|
asm
|
|
{On entry: eax = APChunkManager}
|
|
{$ifdef CountThreadContentions}
|
|
add dword ptr BatchOperations, 1
|
|
adc dword ptr BatchOperations[4], 0
|
|
{$endif}
|
|
{Save ebx}
|
|
push ebx
|
|
{ebx = chunk to free}
|
|
mov ebx, eax
|
|
{Lock the batch managers}
|
|
@LockLoop:
|
|
xor al, al
|
|
mov dl, 1
|
|
{Attempt to lock the batches}
|
|
lock cmpxchg BatchManagersLocked, dl
|
|
jz @BatchesLocked
|
|
{Couldn't lock the batches - sleep and try again}
|
|
{$ifdef CountThreadContentions}
|
|
add dword ptr BatchContentions, 1
|
|
adc dword ptr BatchContentions[4], 0
|
|
{$endif}
|
|
push False
|
|
push InitialSleepTime
|
|
call SleepEx
|
|
{Try again}
|
|
xor al, al
|
|
mov dl, 1
|
|
{Attempt to lock the batches}
|
|
lock cmpxchg BatchManagersLocked, dl
|
|
jz @BatchesLocked
|
|
{Couldn't lock the batches - sleep and try again}
|
|
{$ifdef CountThreadContentions}
|
|
add dword ptr BatchContentions, 1
|
|
adc dword ptr BatchContentions[4], 0
|
|
{$endif}
|
|
push False
|
|
push AdditionalSleepTime
|
|
call SleepEx
|
|
{Try again}
|
|
jmp @LockLoop
|
|
@BatchesLocked:
|
|
{Get the batch manager index for this chunk in edx}
|
|
movzx edx, TChunkManager([ebx]).BatchIndex
|
|
{Get the bit number from the chunk index in ecx}
|
|
mov ecx, ebx
|
|
sub ecx, dword ptr [BatchStartAddress + edx * 4]
|
|
shr ecx, 16
|
|
{eax = the or value}
|
|
mov eax, 1
|
|
shl eax, cl
|
|
{Get the old usage bitmap in ecx}
|
|
mov ecx, dword ptr [BatchUsageBitmap + edx * 4]
|
|
{Get the new usage bitmap in eax}
|
|
or eax, ecx
|
|
{Store the new usage bitmap}
|
|
mov dword ptr [BatchUsageBitmap + edx * 4], eax
|
|
{Is the whole batch now free?}
|
|
cmp eax, -1
|
|
je @BatchEmpty
|
|
{Was the batch previously completely used?}
|
|
test ecx, ecx
|
|
jnz @FreeChunkDone
|
|
{Add the batch back into the linked list as the first batch manager}
|
|
movzx eax, word ptr [NextBatchIndex]
|
|
mov word ptr [PreviousBatchIndex + eax + eax], dx
|
|
mov word ptr [NextBatchIndex + edx + edx], ax
|
|
mov word ptr [PreviousBatchIndex + edx + edx], 0
|
|
mov word ptr [NextBatchIndex], dx
|
|
@FreeChunkDone:
|
|
{Unlock the batch managers}
|
|
mov BatchManagersLocked, False
|
|
pop ebx
|
|
ret
|
|
@BatchEmpty:
|
|
{Get the next partially free batch index}
|
|
movzx eax, word ptr [NextBatchIndex + edx + edx]
|
|
{$ifndef AlwaysFreeChunks}
|
|
{Is this the last batch in the linked list? Do not free if it is.}
|
|
test eax, eax
|
|
jz @FreeChunkDone
|
|
{$endif}
|
|
{Remove the batch from the linked list}
|
|
movzx ecx, word ptr [PreviousBatchIndex + edx + edx]
|
|
mov word ptr [NextBatchIndex + ecx + ecx], ax
|
|
mov word ptr [PreviousBatchIndex + eax + eax], cx
|
|
{Unlock the batch managers}
|
|
mov BatchManagersLocked, False
|
|
{Get the start address of the batch}
|
|
mov eax, dword ptr [BatchStartAddress + edx * 4]
|
|
{Free the chunk}
|
|
push MEM_RELEASE
|
|
push 0
|
|
push eax
|
|
call VirtualFree
|
|
{Restore ebx}
|
|
pop ebx
|
|
end;
|
|
{$endif}
|
|
|
|
{$ifdef DebugMM}
|
|
{Locks the given block type so only this thread can access it}
|
|
procedure LockBlockType(ABlockTypeIndex: Cardinal);
|
|
asm
|
|
{On entry: eax = Block Type Index}
|
|
{$ifdef CountThreadContentions}
|
|
add dword ptr SmallBlockOperations, 1
|
|
adc dword ptr SmallBlockOperations[4], 0
|
|
{$endif}
|
|
{ecx = Block Type Index}
|
|
mov ecx, eax
|
|
@LockLoop:
|
|
xor al, al
|
|
mov dl, 1
|
|
{Attempt to grab the block type}
|
|
lock cmpxchg [ecx + BlockTypeLocked], dl
|
|
jz @Done
|
|
{Couldn't grab the block type - sleep and try again}
|
|
{$ifdef CountThreadContentions}
|
|
add dword ptr SmallBlockContentions, 1
|
|
adc dword ptr SmallBlockContentions[4], 0
|
|
{$endif}
|
|
push ecx
|
|
push False
|
|
push InitialSleepTime
|
|
call SleepEx
|
|
pop ecx
|
|
{Try again}
|
|
xor al, al
|
|
mov dl, 1
|
|
{Attempt to grab the block type}
|
|
lock cmpxchg [ecx + BlockTypeLocked], dl
|
|
jz @Done
|
|
{Couldn't grab the block type - sleep and try again}
|
|
{$ifdef CountThreadContentions}
|
|
add dword ptr SmallBlockContentions, 1
|
|
adc dword ptr SmallBlockContentions[4], 0
|
|
{$endif}
|
|
push ecx
|
|
push False
|
|
push AdditionalSleepTime
|
|
call SleepEx
|
|
pop ecx
|
|
{Try again}
|
|
jmp @LockLoop
|
|
@Done:
|
|
end;
|
|
{$endif}
|
|
|
|
{$ifdef DebugMM}
|
|
{Replacement for SysGetMem (pascal version)}
|
|
function FastGetMem(ASize: Integer): Pointer;
|
|
var
|
|
LSmallBlockTypeIndex, LBlockNumber, LNewFirstFreeBlockNumber,
|
|
LAllocSize: Cardinal;
|
|
LPChunkManager, LPNewFirstChunkManager: PChunkManager;
|
|
LBlockListPointer, LBlockListOffset: integer;
|
|
begin
|
|
{$ifdef CountGetMemAndFreeMemOperations}
|
|
{Increment the number of getmem/freemem operations}
|
|
Inc(TotalGetMemAndFreeMemOperations);
|
|
{$endif}
|
|
if Cardinal(ASize) <= MaximumSmallBlockSize then
|
|
begin
|
|
{Get the block type index}
|
|
LSmallBlockTypeIndex := AllocationSizeToBlockType[(ASize - 1) shr 4];
|
|
{Lock the block type}
|
|
LockBlockType(LSmallBlockTypeIndex);
|
|
{Get the first manager for this block type}
|
|
LPChunkManager := FirstPartiallyFreeChunk[LSmallBlockTypeIndex];
|
|
{Is there a chunk with space?}
|
|
if LPChunkManager <> @DummyChunkManager then
|
|
begin
|
|
{Increment the number of allocated blocks}
|
|
Inc(LPChunkManager.BlocksInUse);
|
|
{Get the block number}
|
|
LBlockNumber := LPChunkManager.FirstFreeBlockNumber;
|
|
{Get a pointer to the block in the linked list}
|
|
LBlockListPointer := Integer(LPChunkManager)
|
|
+ SizeOf(TChunkManager) + Integer(LBlockNumber) * 2;
|
|
{Get the new first free block number}
|
|
LNewFirstFreeBlockNumber := PWord(LBlockListPointer)^;
|
|
{$ifndef NoPointerChecks}
|
|
{Mark the block as allocated}
|
|
PWord(LBlockListPointer)^ := AllocatedBlockMarker;
|
|
{$endif}
|
|
{Set the new first free block number}
|
|
LPChunkManager.FirstFreeBlockNumber := LNewFirstFreeBlockNumber;
|
|
{Is the chunk now full?}
|
|
if LNewFirstFreeBlockNumber = LastBlockMarker then
|
|
begin
|
|
{Chunk is full - remove it from the partially free list}
|
|
LPNewFirstChunkManager := LPChunkManager.NextPartiallyFreeChunk;
|
|
FirstPartiallyFreeChunk[LSmallBlockTypeIndex] := LPNewFirstChunkManager;
|
|
LPNewFirstChunkManager.PreviousPartiallyFreeChunk := @DummyChunkManager;
|
|
end;
|
|
{Get the pointer to this block}
|
|
Result := Pointer(Cardinal(LPChunkManager)
|
|
+ FirstBlockOffset[LSmallBlockTypeIndex]
|
|
+ LBlockNumber * BlockSize[LSmallBlockTypeIndex]);
|
|
end
|
|
else
|
|
begin
|
|
{Try to allocate a chunk}
|
|
LPChunkManager := GetChunk;
|
|
if LPChunkManager <> nil then
|
|
begin
|
|
{Set up this manager}
|
|
LPChunkManager.BlockType := LSmallBlockTypeIndex;
|
|
LPChunkManager.PreviousPartiallyFreeChunk := @DummyChunkManager;
|
|
LPChunkManager.NextPartiallyFreeChunk := @DummyChunkManager;
|
|
LPChunkManager.FirstFreeBlockNumber := 1;
|
|
LPChunkManager.BlocksInUse := 1;
|
|
{Make this the first manager for this block type}
|
|
FirstPartiallyFreeChunk[LSmallBlockTypeIndex] := LPChunkManager;
|
|
{Configure the available blocks stack}
|
|
LBlockListPointer := integer(LPChunkManager) + HighestBlockNumberOffset[LSmallBlockTypeIndex];
|
|
LBlockListOffset := integer(LPChunkManager) + SizeOf(TChunkManager) - LBlockListPointer;
|
|
LBlockNumber := 1;
|
|
while LBlockListOffset < 0 do
|
|
begin
|
|
PWord(LBlockListPointer + LBlockListOffset)^ := LBlockNumber;
|
|
Inc(LBlockListOffset, 2);
|
|
Inc(LBlockNumber);
|
|
end;
|
|
{Mark the last block}
|
|
PWord(LBlockListPointer)^ := LastBlockMarker;
|
|
{$ifndef NoPointerChecks}
|
|
{Mark the first block as allocated}
|
|
PWord(Cardinal(LPChunkManager) + SizeOf(TChunkManager))^ := AllocatedBlockMarker;
|
|
{$endif}
|
|
{Set the result pointer}
|
|
Result := Pointer(Cardinal(LPChunkManager) + FirstBlockOffset[LSmallBlockTypeIndex]);
|
|
end
|
|
else
|
|
begin
|
|
{Out of memory}
|
|
Result := nil;
|
|
end;
|
|
end;
|
|
{Unlock this block type}
|
|
BlockTypeLocked[LSmallBlockTypeIndex] := False;
|
|
end
|
|
else
|
|
begin
|
|
{Larger block: Add the size of the chunk manager and round up to the next
|
|
64K boundary}
|
|
LAllocSize := (ASize + (LargeBlockGranularity - 1 + SizeOf(TChunkManager))) and -LargeBlockGranularity;
|
|
{Allocate directly through VirtualAlloc}
|
|
Result := VirtualAlloc(nil, LAllocSize, MEM_COMMIT or MEM_TOP_DOWN, PAGE_READWRITE);
|
|
if Result <> nil then
|
|
begin
|
|
{Get the chunk manager}
|
|
LPChunkManager := Result;
|
|
{Flag the block type as a large block}
|
|
LPChunkManager.BlockType := LargeBlockTypeIndex;
|
|
LPChunkManager.LargeBlockRequestedSize := ASize;
|
|
LPChunkManager.LargeBlockAllocatedSize := LAllocSize - SizeOf(TChunkManager);
|
|
{Set the result}
|
|
Inc(PByte(Result), SizeOf(TChunkManager));
|
|
end;
|
|
end;
|
|
end;
|
|
{$else}
|
|
{Replacement for SysGetMem (asm version)}
|
|
function FastGetMem(ASize: Integer): Pointer;
|
|
{$ifndef NoMMX}
|
|
const
|
|
{The linked list initialization numbers}
|
|
InitVal0: Int64 = $0004000300020001;
|
|
InitVal1: Int64 = $0008000700060005;
|
|
{The MMX linked list initialization add number}
|
|
InitAdd: Int64 = $0008000800080008;
|
|
{$endif}
|
|
asm
|
|
{On entry:
|
|
eax = the requested block size}
|
|
{$ifdef CountGetMemAndFreeMemOperations}
|
|
{Increment the number of getmem/freemem operations}
|
|
add dword ptr TotalGetMemAndFreeMemOperations, 1;
|
|
adc dword ptr TotalGetMemAndFreeMemOperations[4], 0;
|
|
{$endif}
|
|
{Save registers}
|
|
push ebx
|
|
{Is it a small or large block?}
|
|
cmp eax, MaximumSmallBlockSize
|
|
ja @LargeBlock
|
|
{$ifdef CountThreadContentions}
|
|
add dword ptr SmallBlockOperations, 1
|
|
adc dword ptr SmallBlockOperations[4], 0
|
|
{$endif}
|
|
{Subtract 1 byte and divide the requested size by 16 to get the block size
|
|
lookup number}
|
|
dec eax
|
|
shr eax, 4
|
|
{Get the block type in ebx}
|
|
movzx ebx, byte ptr AllocationSizeToBlockType[eax]
|
|
@LockBlockTypeLoop:
|
|
xor al, al
|
|
mov cl, 1
|
|
{Attempt to grab the block type}
|
|
lock cmpxchg byte ptr [BlockTypeLocked + ebx], cl
|
|
je @GotLockOnBlockType
|
|
{Couldn't grab the block type - sleep and try again}
|
|
{$ifdef CountThreadContentions}
|
|
add dword ptr SmallBlockContentions, 1
|
|
adc dword ptr SmallBlockContentions[4], 0
|
|
{$endif}
|
|
push False
|
|
push InitialSleepTime
|
|
call SleepEx
|
|
{Try again}
|
|
xor al, al
|
|
mov cl, 1
|
|
{Attempt to grab the block type}
|
|
lock cmpxchg byte ptr [BlockTypeLocked + ebx], cl
|
|
je @GotLockOnBlockType
|
|
{Couldn't grab the block type - sleep and try again}
|
|
{$ifdef CountThreadContentions}
|
|
add dword ptr SmallBlockContentions, 1
|
|
adc dword ptr SmallBlockContentions[4], 0
|
|
{$endif}
|
|
push False
|
|
push AdditionalSleepTime
|
|
call SleepEx
|
|
{Try again}
|
|
jmp @LockBlockTypeLoop
|
|
@GotLockOnBlockType:
|
|
{Get the first chunk manager with space in eax}
|
|
mov eax, dword ptr [FirstPartiallyFreeChunk + ebx * 4]
|
|
{Is the chunk valid?}
|
|
cmp eax, offset DummyChunkManager
|
|
je @AllocateChunk
|
|
{Increment the number of allocated blocks}
|
|
inc TChunkManager([eax]).BlocksInUse;
|
|
{Get the block number in edx}
|
|
movzx edx, TChunkManager([eax]).FirstFreeBlockNumber
|
|
{Get the new first free block number in ecx}
|
|
movzx ecx, word ptr [eax + Type(TChunkManager) + edx * 2]
|
|
{$ifndef NoPointerChecks}
|
|
{Flag this block as allocated}
|
|
mov word ptr [eax + Type(TChunkManager) + edx * 2], AllocatedBlockMarker
|
|
{$endif}
|
|
{Set the new first free block number}
|
|
mov TChunkManager([eax]).FirstFreeBlockNumber, cx
|
|
{Are there no longer any free blocks? If so, remove the chunk from the list}
|
|
cmp cx, LastBlockMarker
|
|
jne @ChunkNotFull
|
|
{Chunk is full - remove it from the partially free list}
|
|
{Get the next chunk manager in ecx}
|
|
mov ecx, TChunkManager([eax]).NextPartiallyFreeChunk
|
|
mov dword ptr [FirstPartiallyFreeChunk + ebx * 4], ecx
|
|
mov TChunkManager([ecx]).PreviousPartiallyFreeChunk, offset DummyChunkManager
|
|
@ChunkNotFull:
|
|
{Set the result pointer}
|
|
{Get the block size in ax}
|
|
mov ax, word ptr [BlockSize + ebx + ebx]
|
|
{Multiply with the block number}
|
|
mul ax, dx
|
|
{Add the first block offset}
|
|
add ax, word ptr [FirstBlockOffset + ebx + ebx]
|
|
@AllocateDone:
|
|
{Unlock the block type.}
|
|
mov byte ptr [BlockTypeLocked + ebx], 0
|
|
{Restore ebx}
|
|
pop ebx
|
|
{Done}
|
|
ret
|
|
@AllocateChunk:
|
|
{ebx = pointer to block type}
|
|
{Get a chunk}
|
|
call GetChunk
|
|
{Out of memory?}
|
|
test eax, eax
|
|
jz @AllocateDone
|
|
{Set up this manager}
|
|
mov TChunkManager([eax]).BlockType, bl
|
|
mov TChunkManager([eax]).PreviousPartiallyFreeChunk, offset DummyChunkManager
|
|
mov TChunkManager([eax]).NextPartiallyFreeChunk, offset DummyChunkManager
|
|
mov dword ptr TChunkManager([eax]).FirstFreeBlockNumber, $00010001
|
|
{Make this the first manager for this block type}
|
|
mov dword ptr [FirstPartiallyFreeChunk + ebx * 4], eax
|
|
{Configure the available blocks stack}
|
|
{point edx to the last block number}
|
|
mov edx, eax
|
|
mov dx, word ptr [HighestBlockNumberOffset + ebx + ebx]
|
|
{Get the loop counter in ecx}
|
|
lea ecx, [eax + Type(TChunkManager)]
|
|
sub ecx, edx
|
|
{$ifndef NoMMX}
|
|
movq mm0, InitVal0
|
|
movq mm1, InitVal1
|
|
movq mm2, InitAdd
|
|
@ListInitLoop:
|
|
movq [edx + ecx], mm0
|
|
movq [edx + ecx + 8], mm1
|
|
paddw mm0, mm2
|
|
paddw mm1, mm2
|
|
add ecx, 16
|
|
js @ListInitLoop
|
|
{Exit mmx state}
|
|
emms
|
|
{$else}
|
|
push eax
|
|
mov eax, $00020001
|
|
@ListInitLoop:
|
|
mov [edx + ecx], eax
|
|
add eax, $00020002
|
|
mov [edx + ecx + 4], eax
|
|
add eax, $00020002
|
|
add ecx, 8
|
|
js @ListInitLoop
|
|
pop eax
|
|
{$endif}
|
|
{$ifndef NoPointerChecks}
|
|
{Mark the first block as allocated}
|
|
mov word ptr [eax + Type(TChunkManager)], AllocatedBlockMarker
|
|
{$endif}
|
|
{Mark the last block}
|
|
mov word ptr [edx], LastBlockMarker;
|
|
{Set the result pointer}
|
|
mov ax, word ptr [FirstBlockOffset + ebx * 2]
|
|
{Done}
|
|
jmp @AllocateDone
|
|
@LargeBlock:
|
|
{Save esi}
|
|
push esi
|
|
{Large block: Round up to the next boundary, adding the size of the
|
|
chunk manager}
|
|
lea ebx, [eax + LargeBlockGranularity - 1 + Type(TChunkManager)]
|
|
and ebx, -LargeBlockGranularity
|
|
{Save the requested size in esi}
|
|
mov esi, eax
|
|
{Call VirtualAlloc directly}
|
|
push PAGE_READWRITE
|
|
push MEM_COMMIT or MEM_TOP_DOWN
|
|
push ebx
|
|
push 0
|
|
call VirtualAlloc
|
|
{Out of memory?}
|
|
test eax, eax
|
|
jz @LargeAllocDone
|
|
{Flag the block type as a large block}
|
|
mov TChunkManager([eax]).BlockType, LargeBlockTypeIndex
|
|
mov TChunkManager([eax]).LargeBlockRequestedSize, esi
|
|
sub ebx, Type(TChunkManager)
|
|
mov TChunkManager([eax]).LargeBlockAllocatedSize, ebx
|
|
add eax, Type(TChunkManager)
|
|
@LargeAllocDone:
|
|
pop esi
|
|
pop ebx
|
|
end;
|
|
{$endif}
|
|
|
|
{$ifdef DebugMM}
|
|
{Replacement for SysFreeMem (pascal version)}
|
|
function FastFreeMem(APointer: Pointer): Integer;
|
|
var
|
|
LSmallBlockTypeIndex, LBlockNumber: Cardinal;
|
|
LPChunkManager, LPOldFirstManager, LPPrevManager, LPNextManager: PChunkManager;
|
|
LPBlockListPointer: PWord;
|
|
begin
|
|
{$ifdef CountGetMemAndFreeMemOperations}
|
|
{Increment the number of getmem/freemem operations}
|
|
Inc(TotalGetMemAndFreeMemOperations);
|
|
{$endif}
|
|
{Get the chunk manager}
|
|
LPChunkManager := PChunkManager(Cardinal(APointer) and $ffff0000);
|
|
{Get the block type index}
|
|
LSmallBlockTypeIndex := LPChunkManager.BlockType;
|
|
{Is it a large or small block chunk?}
|
|
if LSmallBlockTypeIndex < NumSmallBlockTypes then
|
|
begin
|
|
{Small Block}
|
|
{Lock this block type for multithreaded apps}
|
|
LockBlockType(LSmallBlockTypeIndex);
|
|
{Determine the block number}
|
|
LBlockNumber := ((Cardinal(APointer) and $ffff) - FirstBlockOffset[LSmallBlockTypeIndex])
|
|
div BlockSize[LSmallBlockTypeIndex];
|
|
{Get a pointer to the block in the linked list}
|
|
LPBlockListPointer := PWord(Cardinal(LPChunkManager) + SizeOf(TChunkManager)
|
|
+ LBlockNumber * 2);
|
|
{$ifndef NoPointerChecks}
|
|
{Was this block allocated?}
|
|
if LPBlockListPointer^ <> AllocatedBlockMarker then
|
|
begin
|
|
{Unlock this block type}
|
|
BlockTypeLocked[LSmallBlockTypeIndex] := False;
|
|
{No error}
|
|
Result := -1;
|
|
{abort}
|
|
exit;
|
|
end;
|
|
{$endif}
|
|
{Update the next available block}
|
|
LPBlockListPointer^ := LPChunkManager.FirstFreeBlockNumber;
|
|
{Was this chunk previously full?}
|
|
if LPChunkManager.FirstFreeBlockNumber = LastBlockMarker then
|
|
begin
|
|
{Insert this as the first partially free chunk for the block size}
|
|
LPOldFirstManager := FirstPartiallyFreeChunk[LSmallBlockTypeIndex];
|
|
LPChunkManager.NextPartiallyFreeChunk := LPOldFirstManager;
|
|
LPOldFirstManager.PreviousPartiallyFreeChunk := LPChunkManager;
|
|
LPChunkManager.PreviousPartiallyFreeChunk := @DummyChunkManager;
|
|
FirstPartiallyFreeChunk[LSmallBlockTypeIndex] := LPChunkManager;
|
|
end;
|
|
{Set this as the first available block}
|
|
LPChunkManager.FirstFreeBlockNumber := LBlockNumber;
|
|
{Decrement the number of allocated blocks}
|
|
Dec(LPChunkManager.BlocksInUse);
|
|
{Is the entire chunk now free?}
|
|
if (LPChunkManager.BlocksInUse = 0) then
|
|
begin
|
|
{Get the next chunk manager}
|
|
LPNextManager := LPChunkManager.NextPartiallyFreeChunk;
|
|
{$ifndef AlwaysFreeChunks}
|
|
{Is this the last one? Free it if not.}
|
|
if LPNextManager <> @DummyChunkManager then
|
|
begin
|
|
{$endif}
|
|
{Remove this manager}
|
|
LPPrevManager := LPChunkManager.PreviousPartiallyFreeChunk;
|
|
if LPPrevManager <> @DummyChunkManager then
|
|
LPPrevManager.NextPartiallyFreeChunk := LPNextManager
|
|
else
|
|
FirstPartiallyFreeChunk[LSmallBlockTypeIndex] := LPNextManager;
|
|
LPNextManager.PreviousPartiallyFreeChunk := LPPrevManager;
|
|
{Unlock this block type}
|
|
BlockTypeLocked[LSmallBlockTypeIndex] := False;
|
|
{Release this chunk}
|
|
FreeChunk(LPChunkManager);
|
|
{$ifndef AlwaysFreeChunks}
|
|
end
|
|
else
|
|
begin
|
|
BlockTypeLocked[LSmallBlockTypeIndex] := False;
|
|
end;
|
|
{$endif}
|
|
end
|
|
else
|
|
begin
|
|
{Unlock this block type}
|
|
BlockTypeLocked[LSmallBlockTypeIndex] := False;
|
|
end;
|
|
{No error}
|
|
Result := 0;
|
|
end
|
|
else
|
|
begin
|
|
{Large block: Free it through VirtualFree}
|
|
Dec(PByte(APointer), SizeOf(TChunkManager));
|
|
if VirtualFree(APointer, 0, MEM_RELEASE) then
|
|
Result := 0
|
|
else
|
|
Result := -1;
|
|
end;
|
|
end;
|
|
{$else}
|
|
{Replacement for SysFreeMem (asm version)}
|
|
function FastFreeMem(APointer: Pointer): Integer;
|
|
asm
|
|
{On entry: eax = APointer}
|
|
{$ifdef CountGetMemAndFreeMemOperations}
|
|
{Increment the number of getmem/freemem operations}
|
|
add dword ptr TotalGetMemAndFreeMemOperations, 1;
|
|
adc dword ptr TotalGetMemAndFreeMemOperations[4], 0;
|
|
{$endif}
|
|
{save registers}
|
|
push ebx
|
|
push esi
|
|
{Get the chunk manager in esi}
|
|
mov esi, eax
|
|
and esi, $ffff0000
|
|
{Get the block type in ecx}
|
|
movzx ecx, TChunkManager([esi]).BlockType
|
|
{Save the pointer in ebx}
|
|
mov ebx, eax
|
|
{Is it a small block type?}
|
|
cmp cl, NumSmallBlockTypes
|
|
jae @LargeBlock
|
|
{$ifdef CountThreadContentions}
|
|
add dword ptr SmallBlockOperations, 1
|
|
adc dword ptr SmallBlockOperations[4], 0
|
|
{$endif}
|
|
{Lock the block type}
|
|
@LockBlockTypeLoop:
|
|
xor al, al
|
|
mov dl, 1
|
|
{Attempt to grab the block type}
|
|
lock cmpxchg byte ptr [BlockTypeLocked + ecx], dl
|
|
je @GotLockOnBlockType
|
|
{Couldn't grab the block type - sleep and try again}
|
|
{$ifdef CountThreadContentions}
|
|
add dword ptr SmallBlockContentions, 1
|
|
adc dword ptr SmallBlockContentions[4], 0
|
|
{$endif}
|
|
push ecx
|
|
push False
|
|
push InitialSleepTime
|
|
call SleepEx
|
|
pop ecx
|
|
{Try again}
|
|
xor al, al
|
|
mov dl, 1
|
|
{Attempt to grab the block type}
|
|
lock cmpxchg byte ptr [BlockTypeLocked + ecx], dl
|
|
je @GotLockOnBlockType
|
|
{Couldn't grab the block type - sleep and try again}
|
|
{$ifdef CountThreadContentions}
|
|
add dword ptr SmallBlockContentions, 1
|
|
adc dword ptr SmallBlockContentions[4], 0
|
|
{$endif}
|
|
push ecx
|
|
push False
|
|
push AdditionalSleepTime
|
|
call SleepEx
|
|
pop ecx
|
|
{Try again}
|
|
jmp @LockBlockTypeLoop
|
|
@GotLockOnBlockType:
|
|
{Get the block number in edx}
|
|
sub bx, word ptr [FirstBlockOffset + ecx * 2]
|
|
movzx eax, bx
|
|
mul dword ptr [BlockSizeInverse + ecx * 4]
|
|
{$ifndef NoPointerChecks}
|
|
mov eax, -1
|
|
cmp word ptr [esi + Type(TChunkManager) + edx * 2], AllocatedBlockMarker
|
|
jne @DoneResultAlreadySet
|
|
{$endif}
|
|
{Update the next available block}
|
|
mov ax, TChunkManager([esi]).FirstFreeBlockNumber
|
|
mov word ptr [esi + Type(TChunkManager) + edx * 2], ax
|
|
{Set this as the new first free block}
|
|
mov TChunkManager([esi]).FirstFreeBlockNumber, dx
|
|
{Decrement the allocated block count. Is the entire chunk now free?}
|
|
dec TChunkManager([esi]).BlocksInUse
|
|
jz @ChunkEmpty
|
|
{Was this chunk previously full?}
|
|
cmp ax, LastBlockMarker
|
|
je @ChunkWasFull
|
|
@SmallFreeDone:
|
|
xor eax, eax
|
|
@DoneResultAlreadySet:
|
|
mov byte ptr [BlockTypeLocked + ecx], 0
|
|
pop esi
|
|
pop ebx
|
|
ret
|
|
@ChunkWasFull:
|
|
{Insert this as the first partially free chunk for the block size}
|
|
mov TChunkManager([esi]).PreviousPartiallyFreeChunk, offset DummyChunkManager
|
|
mov eax, dword ptr [FirstPartiallyFreeChunk + ecx * 4]
|
|
mov TChunkManager([esi]).NextPartiallyFreeChunk, eax
|
|
mov TChunkManager([eax]).PreviousPartiallyFreeChunk, esi
|
|
mov dword ptr [FirstPartiallyFreeChunk + ecx * 4], esi
|
|
jmp @SmallFreeDone
|
|
@ChunkEmpty:
|
|
mov edx, TChunkManager([esi]).NextPartiallyFreeChunk
|
|
{$ifndef AlwaysFreeChunks}
|
|
{Is this the last partially free chunk for this block type? -> We don't free
|
|
the last one}
|
|
cmp edx, offset DummyChunkManager
|
|
je @SmallFreeDone
|
|
{$endif}
|
|
{Remove this manager}
|
|
mov eax, TChunkManager([esi]).PreviousPartiallyFreeChunk
|
|
mov TChunkManager([edx]).PreviousPartiallyFreeChunk, eax
|
|
cmp eax, offset DummyChunkManager
|
|
je @FirstManager
|
|
mov TChunkManager([eax]).NextPartiallyFreeChunk, edx
|
|
jmp @NotFirstManager
|
|
@FirstManager:
|
|
mov dword ptr [FirstPartiallyFreeChunk + ecx * 4], edx
|
|
@NotFirstManager:
|
|
{Unlock the block type}
|
|
mov byte ptr [BlockTypeLocked + ecx], 0
|
|
{Free this chunk}
|
|
mov eax, esi
|
|
call FreeChunk
|
|
xor eax, eax
|
|
pop esi
|
|
pop ebx
|
|
ret
|
|
@LargeBlock:
|
|
{Larger block: Free the block through VirtualFree}
|
|
push MEM_RELEASE
|
|
push 0
|
|
push esi
|
|
call VirtualFree
|
|
{VirtualFree returns >0 if all is ok}
|
|
cmp eax, 1
|
|
{Return 0 on all ok}
|
|
sbb eax, eax
|
|
{Restore registers}
|
|
pop esi
|
|
pop ebx
|
|
end;
|
|
{$endif}
|
|
|
|
{$ifdef DebugMM}
|
|
{Replacement for SysReallocMem (pascal version)}
|
|
function FastReallocMem(APointer: Pointer; ASize: Integer): Pointer;
|
|
var
|
|
LCurrentBlockSize, LUserAllocatedSize, LNewUserAllocatedSize: integer;
|
|
LBlockTypeIndex, LMoveSize: Cardinal;
|
|
LPChunkManager: PChunkManager;
|
|
begin
|
|
{The manager index is the address divided by 64K}
|
|
LPChunkManager := PChunkManager(Cardinal(APointer) and $ffff0000);
|
|
{Get the block type index}
|
|
LBlockTypeIndex := LPChunkManager.BlockType;
|
|
{Is it a large or small block chunk?}
|
|
if LBlockTypeIndex < NumSmallBlockTypes then
|
|
begin
|
|
{Small Block: Get the current block size}
|
|
LCurrentBlockSize := BlockSize[LBlockTypeIndex];
|
|
{We don't track the allocated size for small blocks - assume its the same
|
|
as the block size.}
|
|
LUserAllocatedSize := LCurrentBlockSize;
|
|
end
|
|
else
|
|
begin
|
|
{It's a large block chunk: Get the current block size}
|
|
LCurrentBlockSize := LPChunkManager.LargeBlockAllocatedSize;
|
|
{Get the size allocated by the user}
|
|
LUserAllocatedSize := LPChunkManager.LargeBlockRequestedSize;
|
|
end;
|
|
{Do we need to do a physical reallocation? We only reallocate if the new
|
|
size is larger than the current block size, or if the new size is less
|
|
than a quarter of the block size (and it's not already the smallest
|
|
available block size.)}
|
|
if (ASize <= LCurrentBlockSize)
|
|
{$ifndef NeverDownsizeOnReallocMem}
|
|
and ((ASize >= (LCurrentBlockSize shr 2))
|
|
or (LCurrentBlockSize < MinimumBlockSizeToAllowDownsizing))
|
|
{$endif}
|
|
then
|
|
begin
|
|
{No need to reallocate}
|
|
Result := APointer;
|
|
LNewUserAllocatedSize := LCurrentBlockSize;
|
|
end
|
|
else
|
|
begin
|
|
{Determine the number of bytes to move across}
|
|
if ASize > LUserAllocatedSize then
|
|
begin
|
|
LMoveSize := LUserAllocatedSize;
|
|
{This pointer is being reallocated to a larger block and therefore it is
|
|
logical to assume that it may be enlarged again. Since reallocations are
|
|
expensive, we pad the requested size to avoid unnecessary future move
|
|
operations.}
|
|
LNewUserAllocatedSize := ASize + ASize shr ReallocPaddingFactor;
|
|
end
|
|
else
|
|
begin
|
|
LMoveSize := ASize;
|
|
LNewUserAllocatedSize := ASize;
|
|
end;
|
|
{Attempt to allocate the new block}
|
|
Result := FastGetMem(LNewUserAllocatedSize);
|
|
if Result <> nil then
|
|
begin
|
|
{Move the data across}
|
|
Move16(APointer, Result, LMoveSize);
|
|
{Free the old block}
|
|
FastFreeMem(APointer);
|
|
end;
|
|
end;
|
|
{Is the new block a large block?}
|
|
if (LNewUserAllocatedSize > MaximumSmallBlockSize) then
|
|
begin
|
|
{Set the correct requested size in the manager}
|
|
LPChunkManager := PChunkManager(Cardinal(Result) and $ffff0000);
|
|
LPChunkManager.LargeBlockRequestedSize := ASize;
|
|
end;
|
|
end;
|
|
{$else}
|
|
{Replacement for SysReallocMem (asm version)}
|
|
function FastReallocMem(APointer: Pointer; ASize: Integer): Pointer;
|
|
asm
|
|
{On entry:
|
|
eax = pointer
|
|
edx = requested size}
|
|
{Save registers}
|
|
push ebx
|
|
{Get the chunk manager in ebx}
|
|
mov ebx, eax
|
|
and ebx, $ffff0000
|
|
{Get the block type index in ecx}
|
|
movzx ecx, TChunkManager([ebx]).BlockType
|
|
{Is it a large or small block chunk?}
|
|
cmp cl, NumSmallBlockTypes
|
|
jae @LargeBlock
|
|
{Small block - get current size in ecx}
|
|
movzx ecx, word ptr [BlockSize + ecx + ecx]
|
|
{The user requested size is the same for small blocks}
|
|
mov ebx, ecx
|
|
@GotOldBlockDetails:
|
|
{Current block size in ecx, User allocated size in ebx:
|
|
Do we need to do a physical reallocation? We only reallocate if the new
|
|
size is larger than the current block size, or if the new size is less
|
|
than a quarter of the block size (and it's not already the smallest
|
|
available block size.)}
|
|
{New block larger? - must realloc if so}
|
|
cmp edx, ecx
|
|
jg @MustRealloc
|
|
{$ifndef NeverDownsizeOnReallocMem}
|
|
{Current block size too small - no realloc}
|
|
cmp ecx, MinimumBlockSizeToAllowDownsizing
|
|
jl @NoRealloc
|
|
{Less than 25% of block used - realloc}
|
|
shr ecx, 2
|
|
cmp edx, ecx
|
|
jl @MustRealloc
|
|
add ecx, ecx
|
|
add ecx, ecx
|
|
@NoRealloc:
|
|
{$endif}
|
|
{The pointer remains the same, but if this is a large block, we must update
|
|
the user size}
|
|
cmp ecx, MaximumSmallBlockSize
|
|
jng @NoReallocDone
|
|
{Set the correct user size in the manager}
|
|
mov ebx, eax
|
|
and ebx, $ffff0000
|
|
mov TChunkManager([ebx]).LargeBlockRequestedSize, edx
|
|
@NoReallocDone:
|
|
pop ebx
|
|
ret
|
|
@LargeBlock:
|
|
{It's a large block chunk: Get the current block size}
|
|
mov ecx, TChunkManager([ebx]).LargeBlockAllocatedSize
|
|
{Get the size requested by the user}
|
|
mov ebx, TChunkManager([ebx]).LargeBlockRequestedSize
|
|
jmp @GotOldBlockDetails
|
|
@MustRealloc:
|
|
{Save registers}
|
|
push esi
|
|
push edi
|
|
push ebp
|
|
{Default new block size = the requested size}
|
|
mov esi, edx
|
|
{Determine the number of bytes to move across: grow or shrink?}
|
|
cmp edx, ebx
|
|
jb @Shrink
|
|
{Pad the block size for an upsize. Bytes to move is the old block size}
|
|
shr esi, ReallocPaddingFactor
|
|
add esi, edx
|
|
jmp @DoGetMem
|
|
@Shrink:
|
|
{Shrink - byte move count is the new block size}
|
|
mov ebx, edx
|
|
@DoGetMem:
|
|
{eax = old pointer, edx = requested size, esi = new block size, ebx = move count}
|
|
{Save data}
|
|
mov ebp, eax
|
|
mov edi, edx
|
|
{ebp = old pointer, edi = requested size, esi = new block size, ebx = move count}
|
|
mov eax, esi
|
|
call FastGetMem
|
|
{Failed?}
|
|
test eax, eax
|
|
jz @ReallocDone
|
|
{Move the data across}
|
|
mov ecx, ebx
|
|
mov ebx, eax
|
|
mov edx, eax
|
|
mov eax, ebp
|
|
call Move16
|
|
{ebp = old pointer, edi = requested size, esi = new block size, ebx = new pointer}
|
|
{Free the old block}
|
|
mov eax, ebp
|
|
call FastFreeMem
|
|
{Return the new pointer}
|
|
mov eax, ebx
|
|
{Is the new block a large block?}
|
|
cmp esi, MaximumSmallBlockSize
|
|
jng @ReallocDone
|
|
{Set the correct user size in the manager}
|
|
and ebx, $ffff0000
|
|
mov TChunkManager([ebx]).LargeBlockRequestedSize, edi
|
|
@ReallocDone:
|
|
pop ebp
|
|
pop edi
|
|
pop esi
|
|
pop ebx
|
|
end;
|
|
{$endif}
|
|
|
|
{Sets up and installs the memory manager}
|
|
procedure InstallMemoryManager;
|
|
var
|
|
LPreviousBlockSize, LBlockTypeIndex, LAllocInd: word;
|
|
{$ifndef KolibriOS}
|
|
i, LCurrentProcessID,
|
|
{$endif}
|
|
LBlocksPerChunk: Cardinal;
|
|
{$ifdef DebugMM}
|
|
j, blkoff, k: Cardinal;
|
|
{$endif}
|
|
begin
|
|
{$ifndef KolibriOS}
|
|
{Build a string identifying the current process}
|
|
LCurrentProcessID := GetCurrentProcessId;
|
|
for i := 0 to 7 do
|
|
begin
|
|
UniqueProcessIDString[8 - i] :=
|
|
LHexTable[1 + ((LCurrentProcessID shr (i * 4)) and $F)];
|
|
end;
|
|
{Is the replacement memory manager already installed for this process?}
|
|
MMWindow := FindWindow('STATIC', PChar(@UniqueProcessIDString[1]));
|
|
if MMWindow = 0 then
|
|
begin
|
|
{$endif KolibriOS}
|
|
{Not installed yet: Initialize the small block type info}
|
|
LPreviousBlockSize := 0;
|
|
for LBlockTypeIndex := 0 to NumSmallBlockTypes - 1 do
|
|
begin
|
|
{No chunk manager for this type yet}
|
|
FirstPartiallyFreeChunk[LBlockTypeIndex] := @DummyChunkManager;
|
|
{Get the number of blocks per chunk. The block list and chunk manager is
|
|
allocated in the same chunk as the blocks themselves, so there is a two
|
|
byte overhead per block.}
|
|
LBlocksPerChunk := (65536 - SizeOf(TChunkManager))
|
|
div (BlockSize[LBlockTypeIndex] + 2);
|
|
{Align the block start to 16 bytes, and check that it still fits}
|
|
if (((SizeOf(TChunkManager) + LBlocksPerChunk * 2 + 15) and -16)
|
|
+ BlockSize[LBlockTypeIndex] * LBlocksPerChunk) > 65536 then
|
|
begin
|
|
{The last block is dropped due to alignment requirements}
|
|
Dec(LBlocksPerChunk);
|
|
end;
|
|
{Set the highest block index}
|
|
HighestBlockNumberOffset[LBlockTypeIndex] := SizeOf(TChunkManager) + (LBlocksPerChunk - 1) * 2;
|
|
{Set the offset of the first block}
|
|
FirstBlockOffset[LBlockTypeIndex] := (SizeOf(TChunkManager) + LBlocksPerChunk * 2 + 15) and -16;
|
|
{Update the block size lookup table}
|
|
for LAllocInd := (LPreviousBlockSize shr 4) to (BlockSize[LBlockTypeIndex] shr 4) - 1 do
|
|
AllocationSizeToBlockType[LAllocInd] := LBlockTypeIndex;
|
|
{Update previous block size}
|
|
LPreviousBlockSize := BlockSize[LBlockTypeIndex];
|
|
{Set the reciprocal block size scaled by 2^32. Scaling by 2^32 gives us at
|
|
least 5 digits accuracy when stored as a cardinal, which is sufficient for
|
|
this case since the maximum multiplier is 5 digits.}
|
|
BlockSizeInverse[LBlockTypeIndex] :=
|
|
{$ifndef KolibriOS}
|
|
($100000000 + LPreviousBlockSize - 1) div LPreviousBlockSize;
|
|
{$else}
|
|
($FFFFFFFF + LPreviousBlockSize) div LPreviousBlockSize;
|
|
{$endif}
|
|
{$ifdef DebugMM}
|
|
{Check the validity of the block size inverse}
|
|
for j := 0 to LBlocksPerChunk - 1 do
|
|
begin
|
|
blkoff := j * BlockSize[LBlockTypeIndex];
|
|
asm
|
|
mov eax, blkoff
|
|
movzx edx, LBlockTypeIndex
|
|
mov edx, dword ptr [BlockSizeInverse + edx * 4]
|
|
mul edx
|
|
mov k, edx
|
|
end;
|
|
if k <> j then
|
|
System.Error(reInvalidPtr);
|
|
end;
|
|
{$endif}
|
|
end;
|
|
{$ifdef DebugMM}
|
|
{Check the block size lookup table for validity}
|
|
for k := 1 to MaximumSmallBlockSize do
|
|
begin
|
|
LBlockTypeIndex := AllocationSizeToBlockType[(k - 1) shr 4];
|
|
if (BlockSize[LBlockTypeIndex] < k) then
|
|
System.Error(reInvalidPtr);
|
|
if (LBlockTypeIndex > 0) and (BlockSize[LBlockTypeIndex - 1] >= k) then
|
|
System.Error(reInvalidPtr);
|
|
end;
|
|
{Check that the first block offset is valid}
|
|
if (HighestBlockNumberOffset[LBlockTypeIndex] + 2) > FirstBlockOffset[LBlockTypeIndex] then
|
|
System.Error(reInvalidPtr);
|
|
{Check that the block size is a multiple of 16}
|
|
if BlockSize[LBlockTypeIndex] <> (BlockSize[LBlockTypeIndex] div 16 * 16) then
|
|
System.Error(reInvalidPtr);
|
|
{$endif}
|
|
{$ifndef KolibriOS}
|
|
{No memory manager installed yet - create the invisible window}
|
|
MMWindow := CreateWindow('STATIC',
|
|
PChar(@UniqueProcessIDString[1]),
|
|
WS_POPUP, 0, 0, 0, 0,
|
|
0, 0, LCurrentProcessID, nil);
|
|
{The window data is a pointer to this memory manager}
|
|
SetWindowLong(MMWindow, GWL_USERDATA, Integer(@NewMemoryManager));
|
|
{$endif KolibriOS}
|
|
{We will be using this memory manager}
|
|
NewMemoryManager.GetMem := FastGetMem;
|
|
NewMemoryManager.FreeMem := FastFreeMem;
|
|
NewMemoryManager.ReallocMem := FastReallocMem;
|
|
{$ifndef KolibriOS}
|
|
{Owns the MMWindow}
|
|
OwnsMMWindow := True;
|
|
end
|
|
else
|
|
begin
|
|
{Get the address of the shared memory manager}
|
|
NewMemoryManager := PMemoryManager(GetWindowLong(MMWindow, GWL_USERDATA))^;
|
|
{The MMWindow is owned by the main program (not this DLL)}
|
|
OwnsMMWindow := False;
|
|
end;
|
|
{$endif KolibriOS}
|
|
{Save the old memory manager}
|
|
GetMemoryManager(OldMemoryManager);
|
|
{Replace the memory manager with either this one or the shared one.}
|
|
SetMemoryManager(NewMemoryManager);
|
|
end;
|
|
|
|
procedure UninstallMemoryManager;
|
|
{$ifndef KolibriOS}
|
|
var
|
|
LBlockTypeInd: byte;
|
|
LChunkManager, LNextChunkManager: PChunkManager;
|
|
LBatchIndex: integer;
|
|
{$endif KolibriOS}
|
|
begin
|
|
{$ifndef KolibriOS}
|
|
{Is this the owner of the shared MM window?}
|
|
if OwnsMMWindow then
|
|
begin
|
|
DestroyWindow(MMWindow);
|
|
{$ifndef AlwaysFreeChunks}
|
|
{Free all empty chunks}
|
|
for LBlockTypeInd := 0 to NumSmallBlockTypes - 1 do
|
|
begin
|
|
LNextChunkManager := FirstPartiallyFreeChunk[LBlockTypeInd];
|
|
while LNextChunkManager <> nil do
|
|
begin
|
|
LChunkManager := LNextChunkManager;
|
|
LNextChunkManager := LNextChunkManager.NextPartiallyFreeChunk;
|
|
{Should we free the chunk?}
|
|
if LChunkManager.BlocksInUse = 0 then
|
|
FreeChunk(LChunkManager);
|
|
end;
|
|
end;
|
|
{Free all empty batches}
|
|
LBatchIndex := NextBatchIndex[0];
|
|
while LBatchIndex > 0 do
|
|
begin
|
|
if BatchUsageBitmap[LBatchIndex] = $ffffffff then
|
|
VirtualFree(BatchStartAddress[LBatchIndex], 0, MEM_RELEASE);
|
|
LBatchIndex := NextBatchIndex[LBatchIndex];
|
|
end;
|
|
{$endif}
|
|
end;
|
|
{$endif KolibriOS}
|
|
{Restore the old memory manager}
|
|
SetMemoryManager(OldMemoryManager);
|
|
end;
|
|
|
|
{$ifndef NoMemoryLeakReporting}
|
|
function DelphiIsRunning: boolean;
|
|
begin
|
|
Result := (FindWindow('TPropertyInspector', nil) <> 0)
|
|
and (FindWindow('TAppBuilder', nil) <> 0);
|
|
end;
|
|
|
|
{Converts an integer to string at the buffer location, returning the new
|
|
buffer position. Only does 5 digits.}
|
|
function CardinalToStrBuf(ACardinal: Cardinal; ABuffer: PChar): PChar;
|
|
asm
|
|
push ebx
|
|
push esi
|
|
mov ecx, edx
|
|
xor esi, esi
|
|
{10000s}
|
|
xor edx, edx
|
|
mov ebx, 10000
|
|
div ebx
|
|
mov esi, eax
|
|
add al, '0'
|
|
mov [ecx], al
|
|
cmp esi, 1
|
|
sbb ecx, -1
|
|
{1000s}
|
|
mov eax, edx
|
|
xor edx, edx
|
|
mov ebx, 1000
|
|
div ebx
|
|
or esi, eax
|
|
add al, '0'
|
|
mov [ecx], al
|
|
cmp esi, 1
|
|
sbb ecx, -1
|
|
{100s}
|
|
mov eax, edx
|
|
xor edx, edx
|
|
mov ebx, 100
|
|
div ebx
|
|
or esi, eax
|
|
add al, '0'
|
|
mov [ecx], al
|
|
cmp esi, 1
|
|
sbb ecx, -1
|
|
{10s}
|
|
mov eax, edx
|
|
xor edx, edx
|
|
mov ebx, 10
|
|
div ebx
|
|
or esi, eax
|
|
add al, '0'
|
|
mov [ecx], al
|
|
cmp esi, 1
|
|
sbb ecx, -1
|
|
{1s}
|
|
add dl, '0'
|
|
mov [ecx], dl
|
|
{Return the next position}
|
|
lea eax, [ecx + 1]
|
|
pop esi
|
|
pop ebx
|
|
end;
|
|
|
|
procedure CheckForMemoryLeaks;
|
|
const
|
|
LeakMessageHeader = 'This application has leaked memory. The leaks in (count x block size) format are:'#13#10#13#10;
|
|
LeakMessageFooter = '.'#13#10#13#10'You may use a tool like MemProof to help you track down the source of these leaks. '
|
|
+ 'Steps to use MemProof:'#13#10' 1) Remove FastMM from the project.'#13#10' 2) Enable TD32 debug info in compiler options.'#13#10
|
|
+ ' 3) Build (not compile) the application.'#13#10' 4) Ensure that the MemProof search directories are configured correctly.'#13#10
|
|
+ ' 5) Run the application inside MemProof.'#13#10
|
|
+ 'MemProof is freeware and can be downloaded from http://www.automatedqa.com/downloads/memproof.'#13#10#13#10
|
|
+ 'Note: This memory leak check is only performed if Delphi is currently running. To disable this check completely, define "NoMemoryLeakReporting".'#0;
|
|
var
|
|
LHasLeaks: boolean;
|
|
LBlockTypeInd: byte;
|
|
LNumLeaks: integer;
|
|
LChunkManager: PChunkManager;
|
|
LMsgPtr: PChar;
|
|
LLeakMessage: array[0..4095] of char;
|
|
begin
|
|
{No leaks have been found so far}
|
|
LHasLeaks := False;
|
|
{Prepare the memory leak message}
|
|
System.Move(LeakMessageHeader, LLeakMessage[0], Length(LeakMessageHeader));
|
|
{Get the pointer to the output message}
|
|
LMsgPtr := @LLeakMessage[Length(LeakMessageHeader)];
|
|
{Check all the small block types for leaks}
|
|
for LBlockTypeInd := 0 to NumSmallBlockTypes - 1 do
|
|
begin
|
|
LNumLeaks := 0;
|
|
LChunkManager := FirstPartiallyFreeChunk[LBlockTypeInd];
|
|
while LChunkManager <> nil do
|
|
begin
|
|
Inc(LNumLeaks, LChunkManager.BlocksInUse);
|
|
LChunkManager := LChunkManager.NextPartiallyFreeChunk;
|
|
end;
|
|
{Any leaks?}
|
|
if LNumLeaks > 0 then
|
|
begin
|
|
if not LHasLeaks then
|
|
begin
|
|
LHasLeaks := True;
|
|
end
|
|
else
|
|
begin
|
|
LMsgPtr^ := ',';
|
|
Inc(LMsgPtr);
|
|
LMsgPtr^ := ' ';
|
|
Inc(LMsgPtr);
|
|
end;
|
|
LMsgPtr^ := '(';
|
|
Inc(LMsgPtr);
|
|
LMsgPtr := CardinalToStrBuf(LNumLeaks, LMsgPtr);
|
|
LMsgPtr^ := 'x';
|
|
Inc(LMsgPtr);
|
|
LMsgPtr := CardinalToStrBuf(BlockSize[LBlockTypeInd], LMsgPtr);
|
|
LMsgPtr^ := ')';
|
|
Inc(LMsgPtr);
|
|
end;
|
|
end;
|
|
{Display the leak message if required}
|
|
if LHasLeaks then
|
|
begin
|
|
{Set the message footer}
|
|
System.Move(LeakMessageFooter, LMsgPtr^, Length(LeakMessageFooter));
|
|
{Show the message}
|
|
MessageBox(0, LLeakMessage, 'FastMM: Memory Leak Detected', MB_OK or MB_ICONERROR or MB_TASKMODAL);
|
|
end;
|
|
end;
|
|
{$endif}
|
|
|
|
initialization
|
|
{Has another MM been set, or has the Borland MM been used? If so, this file
|
|
is not the first unit in the uses clause of the project's .dpr file.}
|
|
if IsMemoryManagerSet {or (GetHeapStatus.TotalAllocated <> 0)} then
|
|
RunError(ERROR_INVALID_POINTER){System.Error(reInvalidPtr)};
|
|
|
|
{Install the memory manager}
|
|
InstallMemoryManager;
|
|
|
|
finalization
|
|
{$ifndef NoMemoryLeakReporting}
|
|
{Check for memory leaks}
|
|
if OwnsMMWindow and DelphiIsRunning then
|
|
CheckForMemoryLeaks;
|
|
{$endif}
|
|
{Restore the old memory manager}
|
|
UninstallMemoryManager;
|
|
|
|
end.
|