{ 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.