This file can be included to 32bit programs written in Euro Assembler.
It contains OS-independent macros for dynamic memory management.
The file memory32.htm defines memory allocation class POOL (similar to system heap functions),
and its derivations BUFFER, LIST, STACK, STREAM. See also
Memory management.
Derived methods Buffer*, List*, Stack*, Stream* allocate their memory from the object Pool, and the Pool allocation and deallocation depends on macroinstructions MemAlloc and MemFree which are defined in winapi or linapi.
All macros in this library return carry flag set if some error occurs, which usually signalizes lack of system memory or bad parameter. Returned values are not valid when CF=1.
Keyword parameter ErrorHandler= of macro PoolCreate allows to specify a callback procedure which will be invoked on allocation error. Allocation exceptions then can be treated by ErrorHandler (for instance by aborting the program gracefully), so the derived methods don't need to catch errors.
memory32 HEAD ; Library interface.
Object POOL allocates virtual memory from operating system in big blocks (their size is multiple of virtual-memory granularity). Block are not continuous in addressing space, they are bound in a unidirectional list. Each pool block allocated from OS has the block size and the pointer to previous block in 2 DWORDs at its bottom. The POOL structure itself is created in the very first pool block.
Amount of continuos memory requested by PoolNew, BufferStore*, StreamStore* etc. is not limited. Whenever there is no enough free memory in the last pool block, PoolNew or PoolStore will request an additional empty pool block from OS, with size of the requested memory rounded up to allocation granularity.
Allocated memory block cannot be returned to OS individually. The whole list of pool blocks will be freed at once by PoolDestroy.
POOL STRUC .Gran D D ; Granularity of memory allocation in bytes (64 KB). .ErrH D D ; Address of error handler callback procedure. Ignored when 0. .Last D D ; Pointer to the last pool block allocated from OS. .Ptr D D ; Pointer to the unoccupied memory in the last pool block. ENDSTRUC POOL
will allocate one block from OS virtual memory and create the POOL structure near its bottom.
This is the first pool block, other blocks may be allocated later on demand.
PoolCreate %MACRO Size=64K, ErrorHandler=0
PUSHD %ErrorHandler, %Size
CALL PoolCreate@RT:
PoolCreate@RT: PROC1
PUSHAD
XOR EAX,EAX
MOV [ESP+7*4],EAX ; Initialize %ReturnEAX for the case of error.
MOV ECX,[ESP+9*4] ; %Size.
MOV ESI,64K ; Granularity, fixed at 64 KB.
MOV EDX,ESI
; ESI is granularity (64KB), ECX is requested 1st block size, EDX is the final Alloc size.
.10: CMP ECX,EDX
JBE .20:
ADD EDX,ESI
JMP .10:
.20: MemAlloc EDX
.30: MOV ECX,[ESP+10*4] ; Offset of %ErrorHandler.
JNZ .50: ; If MemAlloc worked OK, continue.
STC
JECXZ .90: ; If no ErrorHandler specified.
CALL ECX
STC
JMP .90: ; Abort.
.50: MOV EDI,EAX ; Address of the allocated memory block.
MOV EAX,EDX ; Size of the allocated block.
STOSD ; Store the size of this pool block as the first DWORD at its bottom.
SUB EAX,EAX
STOSD ; Pointer to the Prev block is zero.
MOV EBX,EDI ; Where the POOL structure will be created.
MOV [ESP+7*4],EDI ; %ReturnEAX will point to the POOL structure.
SUB EDI,8 ; EDI now points to the first and only pool block.
LEA EAX,[EBX+SIZE# POOL] ; EAX now points to the vacant memory space.
MOV [EBX+POOL.Gran],64K
MOV [EBX+POOL.ErrH],ECX
MOV [EBX+POOL.Last],EDI
MOV [EBX+POOL.Ptr], EAX
.90:POPAD
RET 2*4
ENDPROC1 PoolCreate@RT:
%ENDMACRO PoolCreate
PoolDestroy %MACRO aPool
PUSHD %aPool
CALL PoolDestroy@RT:
PoolDestroy@RT: PROC1
PUSHAD
MOV EBX,[ESP+9*4] ; Pointer to the POOL structure.
SUB ECX,ECX ; ECX is a counter of total freed memory of this pool.
TEST EBX
STC
JZ .90: ; Abort if bad aPool was provided.
MOV ESI,[EBX+POOL.Last] ; Begin deallocation with the last block.
.10: TEST ESI ; VA of the pool block which is being deallocated.
JZ .90: ; If there are no more pool blocks on the list.
MOV EAX,[ESI+0] ; Brutto size of the block.
MOV EDI,[ESI+4] ; Pointer to the previous block in the list.
ADD ECX,EAX ; Total released pool size.
MemFree ESI,EAX ; Ask OS to free the memory block addressed with ESI.
JC .Error:
MOV ESI,EDI ; Try the previous block.
JMP .10:
.Error:STC
.90: MOV [ESP+7*4],ECX ; Total amount of freed memory in %ReturnEAX.
POPAD
RET 4
ENDPROC1 PoolDestroy@RT
%ENDMACRO PoolDestroy
PoolGetSize %MACRO aPool
PUSH EBX,ESI
MOV EBX,%aPool
SUB ECX,ECX
MOV ESI,[EBX+POOL.Last]
PoolGetSizeA%.:
TEST ESI
JZ PoolGetSizeZ%.:
ADD ECX,[ESI+0] ; Brutto size of the block.
MOV ESI,[ESI+4] ; Pointer to the previous block in the list.
JMP PoolGetSizeA%.:
PoolGetSizeZ%.:
POP ESI,EBX
%ENDMACRO PoolGetSize
POOL.ErrH was called.
PoolNew %MACRO aPool, DataSize, Align=DWORD, ZeroTerminate=No, Zeroed=No
%PoolNewFlags %SETA 0
%IF %ZeroTerminate
%PoolNewFlags %SETA 0x40
%ENDIF
%IF %Zeroed
%PoolNewFlags %SETA 0x20
%ENDIF
%PoolNewFlags %SETA %PoolNewFlags | "%Align[1]" == "O" & 15
%PoolNewFlags %SETA %PoolNewFlags | "%Align[1]" == "Q" & 7
%PoolNewFlags %SETA %PoolNewFlags | "%Align[1]" == "D" & 3
%PoolNewFlags %SETA %PoolNewFlags | "%Align[1]" == "W" & 1
%PoolNewFlags %SETA %PoolNewFlags | "%Align[1]" == "U" & 1
PUSHD %PoolNewFlags, %DataSize, %aPool
CALL PoolNew@RT:
PoolNew@RT: PROC1
PUSHAD
MOV EBX,[ESP+9*4] ; %aPool.
XOR EBP,EBP
MOV [ESP+7*4],EBP ; %ReturnEAX in case of error.
TEST EBX
STC
JZ .90: ; Error if no pool provided.
.10: MOV ECX,[ESP+10*4] ; %DataSize.
MOV EDX,[ESP+11*4] ; %PoolNewFlags.
BTR EDX,6 ; ZeroTerminate?
ADC ECX,2*4 ; Increment if ZeroTerminated=Yes and then add 8 bytes for two bottom DWORDs.
MOV EDI,[EBX+POOL.Ptr]
BTR EDX,5 ; %Zeroed?
SBB EBP,0
ADD EDI,EDX ; Apply the alignment. EDX=0,1,3,7,15.
NOT EDX
AND EDI,EDX ; EDI is now aligned pointer to the new area.
LEA EDX,[EDI+ECX] ; EDX is now pointer to the end of requested area.
MOV ESI,[EBX+POOL.Last] ; Pointer to the current pool block with our area.
ADD ESI,[ESI+0] ; Add pool block size. ESI now points to the top of the last pool block.
CMP EDX,ESI
JA .30: ; Skip if the new area does not fit into remaining space in the current pool block.
MOV [EBX+POOL.Ptr],EDX ; Update pointer to the free space in the last pool block.
JMP .80: ; Almost done. EDI is the aligned memory pointer to allocated memory.
.30: ; Not enough free space is left in this pool block. Allocate a new pool block.
; EBX=^POOL, ECX=DataSize.
MOV EDX,[EBX-2*4] ; Default block size is in DWORD below the POOL pointed to with EBX.
.40: CMP ECX,EDX
JBE .50: ; Continue when the requested size ECX is below the future pool block size EDX.
ADD EDX,[EBX+POOL.Gran] ; Otherwise this particular pool block must be enlarged.
JMP .40:
.50: MemAlloc EDX
MOV EDI,EAX
JNZ .70: ; If allocation succeeded.
MOV ECX,[EBX+POOL.ErrH] ; Allocation error happened.
JECXZ .60:
CALL ECX
.60: STC
JMP .90:
.70: MOV ECX,[EBX+POOL.Last]
MOV [EBX+POOL.Last],EDI
MOV EAX,EDX
STOSD ; Store the size of the new pool block.
MOV EAX,ECX
STOSD ; Store the pointer to its previous pool block.
MOV [EBX+POOL.Ptr],EDI
JMP .10: ; This time the reservation attempt will not fail.
.80: MOV [ESP+7*4],EDI ; %ReturnEAX.
TEST EBP ; %Zeroed?
JZ .90:
MOV ECX,[ESP+10*4] ; %DataSize.
XOR EAX,EAX
REP STOSB
.90:POPAD
RET 3*4
ENDPROC1 PoolNew@RT
%ENDMACRO PoolNew
POOL.ErrH was called.
PoolStore %MACRO aPool, DataPtr, DataSize, Align=BYTE, ZeroTerminate=No
PUSH %DataSize, %DataPtr, %aPool
PUSH ECX,ESI,EDI
MOV EAX,[ESP+3*4] ; %aPool.
MOV ESI,[ESP+4*4] ; %DataPtr.
MOV ECX,[ESP+5*4] ; %DataSize.
PoolNew EAX,ECX,Align=%Align,ZeroTerminate=%ZeroTerminate
: JC PoolStoreError%.:
MOV EDI,EAX
REP MOVSB
%IF %ZeroTerminate
MOV [EDI],CL
%ENDIF
PoolStoreError%.:
POP EDI,ESI,ECX
LEA ESP,[ESP+3*4] ; Discard 3 pushed arguments, keep CF.
%ENDMACRO PoolStore
Object BUFFER is an unformated storage for objects (strings) of arbitrary size.
Items with fixed or variable size can be stored to the buffer one after another and the entire buffer content is always available as a continuous block.
The initial Size specified at BUFFER creation should be large enough to accomodate all expected data. Whenever the total buffer size tries to exceed the allocated size specified at buffer creation, data area is reallocated with doubled size, the old buffer contents is copied to the new position and the original buffer space is abandoned. The BUFFER structure itself always stays in its original position.
Buffer can be destroyed only when the hosting pool is destroyed but it can be cleared and reused.
BUFFER STRUC .Pool D D ; Pool handle obtained from PoolCreate. .Top D D ; Pointer to the end of allocated buffer space. .Ptr D D ; Ptr to the next free position in buffer. .Bottom D D ; Ptr to the beginning of buffer data. ENDSTRUC BUFFER
BufferCreate %MACRO aPool, Size=16K
PUSHD %Size, %aPool
CALL BufferCreate@RT:
BufferCreate@RT: PROC1
PUSHAD
MOV EAX,1K ; Minimal acceptable buffer size.
MOV ECX,[ESP+10*4] ; %Size.
MOV EDX,[ESP+ 9*4] ; %aPool.
CMP ECX,EAX
JAE .20:
XCHG EAX,ECX
.20: PoolNew EDX,ECX, Align=DWORD
MOV [ESP+7*4],EAX ; %ReturnEAX.
JC .90:
MOV EBX,EAX
MOV [EBX+BUFFER.Pool],EDX
LEA EAX,[EBX+SIZE#BUFFER]
MOV [EBX+BUFFER.Ptr],EAX
MOV [EBX+BUFFER.Bottom],EAX
ADD ECX,EBX ; Also set CF=0.
MOV [EBX+BUFFER.Top],ECX
.90: POPAD
RET 2*4
ENDPROC1 BufferCreate@RT:
%ENDMACRO BufferCreate
BufferClear %MACRO aBuffer
PUSH ECX
MOV ECX,%aBuffer
JECXZ BufferClear%.: ; If bad aBuffer was provided, skip.
PUSHD [ECX+BUFFER.Bottom]
POPD [ECX+BUFFER.Ptr]
BufferClear%.:
POP ECX
%ENDMACRO BufferClear
BufferNew %MACRO aBuffer, DataSize, Zeroed=No
%IF %Zeroed
PUSHD -1
%ELSE
PUSHD 0
%ENDIF
PUSHD %DataSize, %aBuffer
CALL BufferNew@RT:
BufferNew@RT: PROC1
PUSHAD
SUB EAX,EAX
MOV EBX,[ESP+9*4] ; %aBuffer.
MOV [ESP+7*4],EAX ; For the case of error.
TEST EBX
STC
JZ .90: ; If bad buffer provided.
.10: MOV ECX,[ESP+10*4] ; %DataSize.
MOV EAX,[EBX+BUFFER.Ptr]
MOV [ESP+7*4],EAX ; %ReturnEAX.
TEST EAX
JNZ .20:
; Buffer not initialized or other error. Abort.
MOV ECX,[EBX+BUFFER.Pool]
JECXZ .Err:
MOV ECX,[ECX+POOL.ErrH]
JECXZ .Err:
CALL ECX ; Call error handler.
.Err: STC
JMP .90: ; Abort.
.20: ADD EAX,ECX ; EAX is now the top of wanna-be-allocated area.
CMP EAX,[EBX+BUFFER.Top]
JBE .70: ; If the amount of free space on buffer is sufficient.
; Buffer is too small. Allocate new space with doubled (or more) size.
MOV EAX,[EBX+BUFFER.Top]
SUB EAX,[EBX+BUFFER.Bottom]
MOV EDX,EAX ; Old buffer size.
CMP EAX,ECX
JAE .30: ; If doubled size is sufficient.
MOV EAX,ECX
.30: ADD EDX,EAX ; Add the old buffer's size.
PoolNew [EBX+BUFFER.Pool],EDX, Align=DWORD
JC .90:
MOV EDI,EAX
MOV ESI,[EBX+BUFFER.Bottom]
MOV [EBX+BUFFER.Bottom],EAX
ADD EAX,EDX
MOV [EBX+BUFFER.Top],EAX
MOV ECX,[EBX+BUFFER.Ptr]
SUB ECX,ESI
REP MOVSB ; Copy the old buffer's contents.
MOV [EBX+BUFFER.Ptr],EDI
JMP .10: ; Try again. This time it will succeed.
.70: MOV EDI,[EBX+BUFFER.Ptr]
MOV [EBX+BUFFER.Ptr],EAX
TESTB [ESP+11*4],1 ; Zeroed?
JZ .90:
SUB EAX,EDI
MOV ECX,EAX
XOR EAX,EAX
REP STOSB
.90:POPAD
RET 3*4
ENDPROC1 BufferNew@RT:
%ENDMACRO BufferNew
BufferStore %MACRO aBuffer, DataPtr, DataSize
PUSHD %DataSize, %DataPtr, %aBuffer
CALL BufferStore@RT:
BufferStore@RT: PROC1
PUSHAD
MOV EDX,[ESP+9*4] ; aBuffer.
MOV ECX,[ESP+11*4] ; DataSize.
TEST EDX
STC
JZ .90:
BufferNew EDX,ECX
JC .90:
MOV EDI,EAX
MOV ESI,[ESP+10*4] ; DataPtr.
REP MOVSB
.90:POPAD
RET 3*4
ENDPROC1 BufferStore@RT:
%ENDMACRO BufferStore
BufferStoreByte %MACRO aBuffer, Value
PUSHD %Value, %aBuffer
CALL BufferStoreByte@RT:
BufferStoreByte@RT: PROC1
PUSHAD
MOV EDX,[ESP+9*4] ; aBuffer.
MOV EBX,[ESP+10*4] ; Value.
BufferNew EDX,1
JC .90:
MOV [EAX],BL ; Store the BYTE value.
.90:POPAD
RET 2*4
ENDPROC1 BufferStoreByte@RT:
%ENDMACRO BufferStoreByte
BufferStoreWord %MACRO aBuffer, Value
PUSHD %Value, %aBuffer
CALL BufferStoreWord@RT
BufferStoreWord@RT: PROC1
PUSHAD
MOV EDX,[ESP+9*4] ; aBuffer.
MOV EBX,[ESP+10*4] ; Value.
BufferNew EDX,2
JC .90:
MOV [EAX],BX ; Store the WORD value.
.90:POPAD
RET 2*4
ENDPROC1 BufferStoreWord@RT:
%ENDMACRO BufferStoreWord
BufferStoreDword %MACRO aBuffer, Value
PUSHD %Value, %aBuffer
CALL BufferStoreDword@RT:
BufferStoreDword@RT: PROC1
PUSHAD
MOV EDX,[ESP+9*4] ; aBuffer.
MOV EBX,[ESP+10*4] ; Value.
BufferNew EDX,4
JC .90:
MOV [EAX],EBX ; Store the DWORD value.
.90:POPAD
RET 2*4
ENDPROC1 BufferStoreDword@RT:
%ENDMACRO BufferStoreDword
BufferStoreQword %MACRO aBuffer, Value
PUSHD %Value, %aBuffer
CALL BufferStoreQword@RT:
BufferStoreQword@RT: PROC1
PUSHAD
MOV ECX,[ESP+9*4] ; aBuffer.
BufferNew ECX,8
JC .90:
MOV EDI,EAX
MOV EAX,[ESP+10*4] ; Value.
CDQ
STOSD
XCHG EDX,EAX
STOSD
.90:POPAD
RET 2*4
ENDPROC1 BufferStoreQword@RT:
%ENDMACRO BufferStoreQword
BufferStore$ %MACRO aBuffer, StringPtr
PUSHD %StringPtr, %aBuffer
CALL BufferStore$@RT:
BufferStore$@RT: PROC1
PUSHAD
MOV EDX,[ESP+9*4] ; aBuffer.
MOV EDI,[ESP+10*4] ; StringPtr.
TEST EDX
MOV ESI,EDI
STC
JZ .90: ; Bad buffer.
XOR ECX,ECX
XOR EAX,EAX
NOT ECX
REPNE SCASB ; Find zero terminator.
NOT ECX
DEC ECX ; ESI,ECX is now the netto string.
JZ .90:
BufferNew EDX,ECX
JC .90:
MOV EDI,EAX ; Pointer to allocated room on buffer.
REP MOVSB ; Store string.
.90:POPAD
RET 2*4
ENDPROC1 BufferStore$@RT:
%ENDMACRO BufferStore$
BufferStorePascalString %MACRO aBuffer, StringPtr, Size=-1
PUSHD %Size, %StringPtr, %aBuffer
CALL BufferStorePascalString@RT:
BufferStorePascalString@RT: PROC1
PUSHAD
MOV EDX,[ESP+9*4] ; aBuffer.
MOV EDI,[ESP+10*4] ; StringPtr.
MOV ECX,[ESP+11*4] ; Size.
TEST EDX
MOV ESI,EDI
STC
JZ .90: ; Bad buffer.
JECXZ .20:
SUB EAX,EAX
REPNE SCASB ; Find zero terminator within specified %Size.
JNE .20:
DEC EDI ; Skip the terminating zero.
.20: MOV EBX,255
SUB EDI,ESI
CMP EDI,EBX
MOV ECX,EDI
JBE .40:
MOV ECX,EBX ; Saturate netto size ECX to max. 0..255 characters.
.40: LEA EDI,[ECX+1]
BufferNew EDX,EDI
JC .90:
MOV EDI,EAX ; Allocated room on buffer.
MOV EAX,ECX
DEC EBX ; 254.
STOSB ; Store netto string size.
CMP EBX,ECX ; Set CF when ECX was saturated to 255.
REP MOVSB ; Store string.
.90:POPAD
RET 3*4
ENDPROC1 BufferStorePascalString@RT:
%ENDMACRO BufferStorePascalString
BufferDecrement %MACRO aBuffer, Size=1
PUSH EAX,ECX
MOV ECX,%aBuffer
STC
JECXZ BufferDecrementC%.:
MOV EAX,[ECX+BUFFER.Ptr]
SUB EAX,%Size
CMP EAX,[ECX+BUFFER.Ptr]
JBE BufferDecrementA%.:
MOV EAX,[ECX+BUFFER.Ptr] ; If %Size was negative - ignore.
BufferDecrementA%.:
CMP EAX,[ECX+BUFFER.Bottom]
JAE BufferDecrementB%.:
MOV EAX,[ECX+BUFFER.Bottom] ; If %Size was greater than buffer contents.
BufferDecrementB%.:
MOV [ECX+BUFFER.Ptr],EAX
CLC
BufferDecrementC%.:
POP ECX,EAX
%ENDMACRO BufferDecrement
moves the buffer pointer by %Bytes value forward or backward. This will virtually change the size of data in aBuffer.
Macro does nothing when %Bytes=0. If %Bytes is negative, the data block returned with subsequent BufferRetrieve will be shrinked. If %Bytes is positive, returned data block will be expanded. The enlarged portion of data contents will be filled with %Stuff bytes.
If the negative %Bytes number is larger than current data contents available in %aBuffer, buffer pointer is reset to bottom, which is equivalent to BufferClear.
If the buffer data enlarged with positive %Bytes is greater than the size allocated at BufferCreate, %aBuffer will be reallocated to this enlarged new size doubled, and old data contents will be copied from previous position to the new reallocated memory.
BufferResize %MACRO aBuffer, Bytes, Stuff=0
PUSHD %Stuff, %Bytes, %aBuffer
CALL BufferResize@RT:
BufferResize@RT: PROC1
PUSHAD
MOV EBX,[ESP+9*4] ; %aBuffer.
MOV EDX,[ESP+10*4] ; %Bytes. It may be negative.
TEST EBX
STC
JZ .90: ; Error: invalid buffer.
TEST EDX
JG .40:
MOV EAX,[EBX+BUFFER.Ptr] ; Shrink the buffer by -EDX bytes.
MOV ESI,[EBX+BUFFER.Bottom]
ADD EAX,EDX ; Reposition the new Ptr in EAX.
CMP EAX,ESI
JNB .10:
MOV EAX,ESI ; Saturate if the new Ptr is below bottom.
.10: MOV [EBX+BUFFER.Ptr],EAX
JMP .80:
.40: MOV EAX,[ESP+11*4] ; Expand by EDX bytes of Stuff in EDI.
.50: BufferStoreByte EBX,EAX
JC .90: ; Abort on error.
DEC EDX
JNZ .50:
.80: CLC
.90: POPAD
RET 3*4
ENDP1 BufferResize@RT:
%ENDMACRO BufferResize
BufferRetrieve %MACRO aBuffer
%IF "%aBuffer" !== "ECX"
MOV ECX,%aBuffer
%ENDIF
STC
MOV ESI,ECX
JECXZ BufferRetrieve%.: ; Return CF on uncreated buffer.
MOV ESI,[ECX+BUFFER.Bottom]
MOV ECX,[ECX+BUFFER.Ptr]
SUB ECX,ESI
BufferRetrieve%.:
%ENDMACRO BufferRetrieve
Object LIST is FIFO/LIFO bidirectional storage for items (leaves) of the same size. Leaves are DWORD aligned. Below the payload data of every leaf are pointers to the next and previous leaf data.
Leaves are appended to the list with ListNew or ListStore methods and the list can be searched sequentially with ListGetNext or ListGetPrev.
The LIST structure is created in pool memory with ListCreate , it can be destroyed only when the hosting pool is destroyed.
LIST STRUC .First D D ; Pointer to the first stored leaf. .Last D D ; Pointer to the last stored leaf. .Pool D D ; Pool handle obtained from PoolCreate. .Size D D ; Data item netto size. .Count D D ; Number of stored items. ENDSTRUC LIST
ListCreate %MACRO aPool, Size
PUSHD %Size, %aPool
CALL ListCreate@RT:
ListCreate@RT: PROC1
PUSHAD
MOV EDX,[ESP+9*4] ; %aPool.
MOV ECX,[ESP+10*4] ; %Size.
PoolNew EDX, SIZE#LIST, Align=DWORD, Zeroed=Yes
MOV [ESP+7*4],EAX ; %ReturnEAX.
JC .90:
MOV [EAX+LIST.Pool],EDX
MOV [EAX+LIST.Size],ECX
.90: POPAD
RET 2*4
ENDPROC1 ListCreate@RT:
%ENDMACRO ListCreate
ListNew %MACRO aList, Zeroed=No
%IF %Zeroed
PUSHD -1
%ELSE
PUSHD 0
%ENDIF
PUSHD %aList
CALL ListNew@RT:
ListNew@RT: PROC1
PUSHAD
MOV EBX,[ESP+9*4] ; %aList.
MOV [ESP+7*4],EBX ; %ReturnEAX.
TEST EBX
STC
JZ .90: ; Bad object.
MOV ECX,[EBX+LIST.Size]
ADD ECX,8 ; Additional room for two pointers.
PoolNew [EBX+LIST.Pool],ECX, Align=DWORD
MOV [ESP+7*4],EAX ; %ReturnEAX.
JC .90:
MOV EDI,EAX
MOV ESI,[EBX+LIST.Last]
SUB EAX,EAX
STOSD ; Pointer Next.
MOV EAX,ESI ; LIST.Last may be 0 if this is the first leaf.
STOSD ; Pointer Prev.
TEST EAX
JZ .30: ; If this leaf EDI is the first.
MOV [ESI-8],EDI ; EDI is pointer to this new leaf.
.30: MOV [EBX+LIST.Last],EDI
CMPD [EBX+LIST.First],0
JNE .80:
MOV [EBX+LIST.First],EDI
.80: MOV [ESP+7*4],EDI ; %ReturnEAX.
INCD [EBX+LIST.Count]
XOR EAX,EAX
CMP EAX,[ESP+10*4] ; %Zeroed.
JE .90: ; If Zeroed=No.
MOV ECX,[EBX+LIST.Size]
REP STOSB ; Clear the leaf payload area.
CLC
.90:POPAD
RET 2*4
ENDPROC1 ListNew@RT:
%ENDMACRO ListNew
ListStore %MACRO aList, DataPtr
PUSHD %DataPtr, %aList
CALL ListStore@RT:
ListStore@RT: PROC1
PUSHAD
MOV EBX,[ESP+9*4] ; %aList.
MOV ESI,[ESP+10*4] ; %DataPtr.
SUB EAX,EAX
TEST EBX
STC
JZ .90:
ListNew EBX, Zeroed=No
JC .90:
MOV EDI,EAX
MOV ECX,[EBX+LIST.Size]
REP MOVSB
.90: MOV [ESP+7*4],EAX ; %ReturnEAX.
POPAD
RET 2*4
ENDPROC1 ListStore@RT:
%ENDMACRO ListStore
ListInsert %MACRO aList, PrevLeaf, DataPtr
PUSHD %DataPtr, %PrevLeaf, %aList
CALL ListInsert@RT:
ListInsert@RT: PROC1
PUSHAD
MOV EBX,[ESP+9*4] ; %aList.
MOV ESI,[ESP+10*4] ; %PrevLeaf.
XOR EAX,EAX
MOV [ESP+7*4],EAX ; %ReturnEAX.
TEST EBX
STC
JZ .90:
MOV ECX,[EBX+LIST.Size]
ADD ECX,8 ; Additional room for two pointers.
PoolNew [EBX+LIST.Pool],ECX,Align=DWORD
JC .90:
INCD [EBX+LIST.Count]
MOV EDI,EAX
TEST ESI
JNZ .30:
MOV EAX,[EBX+LIST.First]
MOV EDX,EAX
STOSD ; Next.
MOV EAX,ESI
STOSD ; Prev.
MOV [EBX+LIST.First],EDI
JMP .40:
.30: MOV EAX,[ESI-8] ; Next.
MOV EDX,EAX
STOSD ; Next.
MOV EAX,ESI
STOSD ; Prev.
MOV [ESI-8],EDI
.40: MOV [ESP+7*4],EDI ; %ReturnEAX.
TEST EDX
JZ .50:
MOV [EDX-4],EDI ; Prev.
.50: JNZ .60:
MOV [EBX+LIST.Last],EDI
.60: SUB ECX,8
MOV ESI,[ESP+11*4] ; %DataPtr.
TEST ESI
JNZ .80:
XOR EAX,EAX
REP STOSB
JMP .90:
.80: REP MOVSB
.90: POPAD
RET 3*4.
ENDPROC1 ListInsert@RT:
%ENDMACRO ListInsert
ListRemove %MACRO aList, LeafPtr
PUSHD %LeafPtr, %aList
CALL ListRemove@RT:
ListRemove@RT: PROC1
PUSHAD
MOV EBX,[ESP+9*4] ; %aList.
MOV EDX,[ESP+10*4] ; %LeafPtr.
TEST EBX
JZ .90:
TEST EDX
JZ .90:
MOV ESI,[EDX-4] ; Prev.
MOV EDI,[EDX-8] ; Next.
TEST ESI
JZ .30:
MOV [ESI-8],EDI ; Next.
TEST EDI
JZ .50:
.20: MOV [EDI-4],ESI ; Prev.
JMP .80:
.30: MOV [EBX+LIST.First],EDI ; The first leaf was removed.
TEST EDI
JNZ .20:
.50: MOV [EBX+LIST.Last],ESI ; The last leaf was removed.
.80: DECD [EBX+LIST.Count]
.90: POPAD
RET 2*4
ENDPROC1 ListRemove@RT:
%ENDMACRO ListRemove
ListGetFirst %MACRO aList
PUSH ECX
MOV ECX,%aList
SUB EAX,EAX
JECXZ ListGetFirst%.:
MOV EAX,[ECX+LIST.First]
ListGetFirst%.:
TEST EAX
POP ECX
%ENDMACRO ListGetFirst
ListGetNext %MACRO LeafPtr
PUSH ECX
MOV ECX,%LeafPtr
SUB EAX,EAX
JECXZ ListGetNext%.:
MOV EAX,[ECX-8]
ListGetNext%.:
TEST EAX
POP ECX
%ENDMACRO ListGetNext
ListGetLast %MACRO aList
PUSH ECX
MOV ECX,%aList
SUB EAX,EAX
JECXZ ListGetLast%.:
MOV EAX,[ECX+LIST.Last]
ListGetLast%.:
TEST EAX
POP ECX
%ENDMACRO ListGetLast
ListGetPrev %MACRO LeafPtr
PUSH ECX
MOV ECX,%LeafPtr
SUB EAX,EAX
JECXZ ListGetPrev%.:
MOV EAX,[ECX-4]
ListGetPrev%.:
TEST EAX
POP ECX
%ENDMACRO ListGetPrev
Object STACK is LIFO storage for items of the same size. Stacked items are DWORD aligned and stored continuously in one pool block. Stack grows upward, STACK.Ptr is increased with StackPush and decreased with StackPop.
Whenever the initial stack depth specified in StackCreate is not big enough, which may happen in StackPush, the stack is reallocated with doubled size, old content is copied to the new location and abandoned. The STACK structure itself stays in its original position.
Stack contents can be erased with StackClear.
The STACK object should not be confused with machine stack adressed with SS:ESP.
STACK STRUC .Pool D D ; Pool handle obtained from PoolCreate. .Size D D ; Size of one stacked item. .Top D D ; Pointer to the end of allocated space. .Ptr D D ; Pointer to the free space on STACK. .Bottom D D ; Pointer to the oldest pushed item. ENDSTRUC STACK
StackCreate %MACRO aPool, Size, Depth=16
PUSHD %Depth, %Size, %aPool
CALL StackCreate@RT:
StackCreate@RT: PROC1
PUSHAD
MOV EAX,[ESP+10*4] ; %Size.
MOV ESI,[ESP+9*4] ; %aPool.
MOV ECX,EAX
MULD [ESP+11*4] ; %Depth.
MOV EBX,EAX
ADD EAX,SIZE#STACK
PoolNew ESI,EAX,Align=DWORD
MOV [ESP+7*4],EAX ; %ReturnEAX.
JC .90:
MOV [EAX+STACK.Pool],ESI
MOV [EAX+STACK.Size],ECX
LEA EDI,[EAX+SIZE#STACK]
MOV [EAX+STACK.Bottom],EDI
MOV [EAX+STACK.Ptr],EDI
ADD EDI,EBX
MOV [EAX+STACK.Top],EDI
.90:POPAD
RET 3*4
ENDPROC1 StackCreate@RT:
%ENDMACRO StackCreate
StackClear %MACRO aStack
PUSHD EBX, %aStack
POP EBX
PUSHD [EBX+STACK.Bottom]
POPD [EBX+STACK.Ptr], EBX
%ENDMACRO StackClear
StackPush %MACRO aStack, DataPtr
PUSHD %DataPtr, %aStack
CALL StackPush@RT:
StackPush@RT: PROC1
PUSHAD
MOV EBX,[ESP+9*4] ; %aStack
TEST EBX
STC
JZ .90:
.10: MOV EDI,[EBX+STACK.Ptr]
CMP EDI,[EBX+STACK.Top]
JB .50:
; Not enough space on stack.
MOV EAX,[EBX+STACK.Top]
MOV ESI,[EBX+STACK.Bottom]
SUB EAX,ESI
MOV ECX,EAX
LEA EDX,[EAX+EAX] ; Double the stack room.
PoolNew [EBX+STACK.Pool],EDX, Align=DWORD
JC .90:
MOV EDI,EAX ; New room with doubled size (STACK.Bottom).
MOV [EBX+STACK.Bottom],EAX
ADD EDX,EAX ; New STACK.Top.
REP MOVSB ; Copy old stack content.
MOV [EBX+STACK.Ptr],EDI
MOV [EBX+STACK.Top],EDX
.50: ; There is sufficient free space on stack now.
MOV ESI,[ESP+10*4] ; %DataPtr.
MOV ECX,[EBX+STACK.Size]
MOV [ESP+7*4],EDI ; STACK.Ptr before StackPush.
TEST ESI
JZ .70: ; Go to store zeroes when NULL data pointer was provided.
REP MOVSB ; Otherwise copy the data.
JMP .80:
.70: SUB EAX,EAX
REP STOSB
.80: MOV [EBX+STACK.Ptr],EDI ; Update the new incremented stack pointer.
CLC
.90:POPAD
RET 2*4
ENDPROC1 StackPush@RT:
%ENDMACRO StackPush
StackPop %MACRO aStack
PUSHD ECX,%aStack
POP ECX
STC
JECXZ StackPop%.: ; Bad parameter.
MOV EAX,[ECX+STACK.Ptr]
SUB EAX,[ECX+STACK.Size]
CMP EAX,[ECX+STACK.Bottom]
JC StackPop%.: ; If the stack is empty.
MOV [ECX+STACK.Ptr],EAX
StackPop%.:
POP ECX
%ENDMACRO StackPop
StackPeekLast %MACRO aStack
PUSH ECX
SUB EAX,EAX
MOV ECX,%aStack
STC
JECXZ StackPeekLast%.:
MOV EAX,[ECX+STACK.Ptr]
SUB EAX,[ECX+STACK.Size]
CMP EAX,[ECX+STACK.Bottom]
StackPeekLast%.:
POP ECX
%ENDMACRO StackPeekLast
StackPeekPrev %MACRO aStack, DataPtr
PUSH ECX
PUSHD %aStack
POP ECX
STC
JECXZ StackPeekPrev%.:
%IF "%DataPtr" == "ECX"
MOV EAX,[ESP]
%ELSE
MOV EAX,%DataPtr
%ENDIF
SUB EAX,[ECX+STACK.Size]
CMP EAX,[ECX+STACK.Bottom]
StackPeekPrev%.:
POP ECX
%ENDMACRO StackPeekPrev
Object STREAM is an unformated FIFO storage for items of unlimited size.
Unlike the BUFFER, data is not guaranteed to be stored continuously.
Memory blocks (containers for stored data) are allocated on demand with fixed size
and their every byte is used, thus the memory is exploited very efficiently.
The STREAM structure itself is appended below the first block,
all other continuation block have pointer to the next block appended below them.
Stream-store operations advance STREAM.WritePtr, stream-read operations advance
STREAM.ReadPtr. Stream should not be read by more threads concurently.
Typical usage of stream is collecting data with StreamStore* and then retrieving all data at once.
STREAM STRUC .Pool D D ; Pool handle obtained from PoolCreate. .BufSize D D ; Netto size of each block (without the linking pointer). .Top D D ; End of the last block. .ReadPtr D D ; Pointer to the unread data. .WritePtr D D ; Pointer to the free position in the last block. .Next D D ; Pointer to the bottom of the next block. NULL if only 1 block is allocated. ENDSTRUC STREAM
StreamCreate %MACRO Pool, BufSize=16K
PUSHD %BufSize, %Pool
CALL StreamCreate@RT:
StreamCreate@RT: PROC1
PUSHAD
MOV EBX,[ESP+9*4] ; %Pool.
MOV ECX,[ESP+10*4] ; %BufSize.
LEA EDX,[ECX+SIZE#STREAM]
PoolNew EBX, EDX, Align=DWORD
MOV [ESP+7*4],EAX ; %ReturnEAX.
JC .90:
MOV [EAX+STREAM.Pool],EBX
LEA EBX,[EAX+SIZE#STREAM]
MOV [EAX+STREAM.BufSize],ECX
MOV [EAX+STREAM.ReadPtr],EBX
MOV [EAX+STREAM.WritePtr],EBX
MOVD [EBX-4],0
ADD EBX,ECX
MOV [EAX+STREAM.Top],EBX
.90: POPAD
RET 2*4
ENDPROC1 StreamCreate@RT:
%ENDMACRO StreamCreate
StreamGetSize %MACRO Stream
PUSHD %Stream
CALL StreamGetSize@RT:
StreamGetSize@RT: PROC1
PUSHAD
SUB EAX,EAX
MOV EBX,[ESP+9*4] ; %Stream.
MOV [ESP+7*4],EAX ; Returned size.
LEA ESI,[EBX+SIZE#STREAM]
MOV EAX,[EBX+STREAM.WritePtr]
MOV ECX,[EBX+STREAM.BufSize]
.10: CMP EAX,ESI
JB .20:
LEA EDI,[ESI+ECX]
CMP EAX,EDI
JNA .30:
.20: ADD [ESP+7*4],ECX ; %ReturnEAX.
MOV ESI,[ESI-4]
JMP .10:
.30: SUB EAX,ESI
ADD [ESP+7*4],EAX ; %ReturnEAX.
.90: POPAD
RET 1*4
ENDPROC1 StreamGetSize@RT:
%ENDMACRO StreamGetSize
StreamReadByte %MACRO Stream
PUSHD %Stream
CALL StreamReadByte@RT:
StreamReadByte@RT: PROC1
PUSHAD
MOV EBX,[ESP+9*4] ; %Stream.
MOV EBP,[EBX+STREAM.BufSize]
MOV EDX,[EBX+STREAM.Next] ; Pointer to the next block, or 0.
LEA ESI,[EBX+SIZE#STREAM] ; Bottom of data in the first block.
LEA ECX,[ESI+EBP] ; Top of the first block.
.10: ; If the .WritePtr is in the block ESI..ECX, decrement ECX to its value.
CMP [EBX+STREAM.WritePtr],ESI
JB .20:
CMP [EBX+STREAM.WritePtr],ECX
JAE .20:
MOV ECX,[EBX+STREAM.WritePtr]
.20: MOV EDI,[EBX+STREAM.ReadPtr]
; If the .ReadPtr is in the block ESI..ECX, read one byte from it.
CMP EDI,ESI
JB .50: ; Try the next block.
CMP EDI,ECX ; Check if STREAM.ReadPtr EDI belongs to this data block ESI..ECX.
JA .50: ; Try the next block.
JE .60: ; If .ReadPtr points to the top of block, move it to the bottom of the next block.
XCHG ESI,EDI
LODSB
MOV [ESP+28],EAX ; %ReturnEAX.
MOV [EBX+STREAM.ReadPtr],ESI
CLC
JMP .90:
.50: TEST EDX ; Is the next block allocated?
STC
JZ .90:
MOV ESI,EDX ; Bottom of data in the next block.
JMP .70:
.60: TEST EDX ; Is the next block allocated?
STC
JZ .90:
MOV ESI,EDX ; Bottom of data in the next block.
MOV [EBX+STREAM.ReadPtr],ESI
.70: MOV EDX,[EDX-4] ; Pointer to the next block, or 0.
LEA ECX,[ESI+EBP]
JMP .10:
.90: POPAD
RET 1*4
ENDPROC1 StreamReadByte@RT:
%ENDMACRO StreamReadByte
StreamReadLn %MACRO Stream, LineBuffer
PUSHD %LineBuffer, %Stream
CALL StreamReadLn@RT:
StreamReadLn@RT: PROC1
PUSHAD
MOV EBX,[ESP+9*4] ; %Stream.
MOV EBP,[EBX+STREAM.BufSize]
MOV EDX,[EBX+STREAM.Next] ; Pointer to the next block, or 0.
LEA ESI,[EBX+SIZE#STREAM] ; Bottom of data in the first block.
LEA ECX,[ESI+EBP] ; Top of the first block.
MOV EAX,10 ; AL=LF, AH=0. Nonzero AH signalizes incomplete line (not LF terminated).
.10: ; Find the physical line starting at .ReadPtr in the block ESI..ECX.
TEST AH,AH
JZ .30:
MOV [EBX+STREAM.ReadPtr],ESI ; Bottom of the next block.
XOR AH,AH
.30: ; If the .WritePtr is in the block ESI..ECX, decrement ECX to its value.
CMP [EBX+STREAM.WritePtr],ESI
JB .40:
CMP [EBX+STREAM.WritePtr],ECX
JAE .40:
MOV ECX,[EBX+STREAM.WritePtr]
.40: ; If the .ReadPtr is in the block ESI..ECX, search for the LF.
MOV EDI,[EBX+STREAM.ReadPtr]
CMP EDI,ESI
JB .50: ; Try the next block.
CMP EDI,ECX ; Check if STREAM.ReadPtr EDI belongs to this data block ESI..ECX.
JA .50: ; Try the next block.
JE .60: ; .ReadPtr points to the top of block. Move it to the bottom of the next block.
SUB ECX,EDI ; Max size of searched data in block EDI..ECX. At least 1.
MOV ESI,EDI ; Remember the start of line.
REPNE SCASB ; Search for the LF.
MOV [EBX+STREAM.ReadPtr],EDI
JE .80: ; If terminator LF found, we're almost done.
OR AH,1 ; The line ESI..EDI is incomplete, perhaps split between blocks.
SUB EDI,ESI
MOV ECX,[ESP+40] ; %LineBuffer.
BufferStore ECX,ESI,EDI ; Store the first part of incomplete line.
; The line continues in the next block.
.50: TEST AH
JZ .55:
TEST EDX ; Is the next block allocated?
JZ .90: ; If not, the last line is incomplete, return it.
.55: TEST EDX ; Is the next block allocated?
STC
JZ .90: ; If not and line not incomplete, signalize end of data.
MOV ESI,EDX ; Bottom of data in the next block.
JMP .70:
.60: TEST EDX ; Is the next block allocated?
STC
JZ .90:
MOV ESI,EDX ; Bottom of data in the next block.
MOV [EBX+STREAM.ReadPtr],ESI
.70: MOV EDX,[EDX-4] ; Pointer to the next block, or 0.
LEA ECX,[ESI+EBP]
JMP .10:
.80: SUB EDI,ESI
MOV EDX,[ESP+10*4] ; %LineBuffer.
BufferStore EDX,ESI,EDI ; Copy line ESI,EDI to the buffer EDX.
CLC
.90: POPAD
RET 2*4
ENDPROC1 StreamReadLn@RT:
%ENDMACRO StreamReadLn
StreamGetLines %MACRO StreamPtr
SUB EAX,EAX
DEC EAX
StreamGetLines%.:
INC EAX
StreamReadLn %StreamPtr, 0 ; The read lines won't be stored due to NULL buffer.
JNC StreamGetLines%.:
%ENDMACRO StreamGetLines
StreamReset %MACRO Stream
PUSH EAX,EBX
MOV EBX,%Stream
LEA EAX,[EBX+SIZE#STREAM]
MOV [EBX+STREAM.ReadPtr],EAX
POP EBX,EAX
%ENDMACRO StreamReset
StreamClear %MACRO Stream
PUSH EAX,EBX
MOV EBX,%Stream
LEA EAX,[EBX+SIZE#STREAM]
MOV [EBX+STREAM.WritePtr],EAX
MOV [EBX+STREAM.ReadPtr],EAX
ADD EAX,[EBX+STREAM.BufSize]
MOV [EBX+STREAM.Top],EAX
POP EBX,EAX
%ENDMACRO StreamClear
StreamDump %MACRO Stream, DumpProc
PUSHD %DumpProc, %Stream
CALL StreamDump@RT:
StreamDump@RT: PROC1
PUSHAD
MOV EBX,[ESP+9*4] ; %Stream.
TEST EBX
JZ .90:
LEA ESI,[EBX+SIZE#STREAM]
MOV EAX,[EBX+STREAM.WritePtr]
TEST EAX
JZ .90:
.20: MOV ECX,[EBX+STREAM.BufSize]
LEA EDX,[ESI+ECX]
CMP EAX,EDX
JNB .40:
CMP EAX,ESI
JNB .70:
.40: PUSH EAX,EBX,ESI
CALL [ESP+40+12] ; %DumpProc, not the last block.
POP ESI,EBX,EAX
JC .90:
MOV ESI,[ESI-4]
TEST ESI
JNZ .20:
JMP .90:
.70: MOV ECX,EAX
SUB ECX,ESI
JZ .90:
CALL [ESP+10*4] ; DumpProc, the last block.
.90: POPAD
RET 8
ENDPROC1 StreamDump@RT:
%ENDMACRO StreamDump
StreamRetrieve %MACRO Stream
PUSHD %Stream
CALL StreamRetrieve@RT:
StreamRetrieve@RT: PROC1
PUSHAD
MOV EBX,[ESP+9*4] ; %Stream.
MOV ECX,[EBX+STREAM.BufSize]
MOV EAX,[EBX+STREAM.ReadPtr]
MOV EDX,[EBX+STREAM.WritePtr]
LEA ESI,[EBX+SIZE#STREAM]
MOV [ESP+1*4],EAX ; %ReturnESI.
.10: LEA EDI,[ESI+ECX]
CMP EDX,ESI
JB .20:
CMP EDX,EDI
JNA .70:
.20: CMP EAX,ESI
JB .30:
CMP EAX,EDI
JNA .50:
.30: MOV ESI,[ESI-4]
TEST ESI
JNZ .10:
JMP .80:
.50: CMP EAX,EDI
JNE .60:
MOV ESI,[ESI-4]
TEST ESI
JZ .80:
MOV [EBX+STREAM.ReadPtr],ESI
MOV [ESP+1*4],ESI ; %ReturnESI.
JMP .10:
.60: MOV ECX,EDI
SUB ECX,EAX
JMP .90:
.70: CMP EAX,ESI
JB .80:
CMP EAX,EDI
JA .80:
MOV ECX,EDX
SUB ECX,EAX
JA .90:
.80: SUB ECX,ECX
.90: MOV [ESP+6*4],ECX ; %ReturnECX.
ADD [EBX+STREAM.ReadPtr],ECX
TEST ECX
POPAD
RET 1*4
ENDPROC1 StreamRetrieve@RT:
%ENDMACRO StreamRetrieve
StreamStoreRT %MACRO ; Common runtime proc for macros StreamStore, StreamStoreLn etc.
StreamStore@RT: PROC1
PUSHAD
MOV EBX,[ESP+40] ; Stream.
SUB EAX,EAX
LEA ESI,[ESP+44] ; 1st data pair.
MOV [ESP+28],EAX ; ReturnEAX.
.10: LODSD ; DataPtr.
MOV EBP,EAX
TEST EAX
JZ .90:
LODSD ; DataSize.
MOV EDX,EAX
ADD [ESP+28],EAX ; Total written size.
PUSH ESI
; EBP=DataPtr EDX=DataSize EBX=Stream
MOV EDI,[EBX+STREAM.WritePtr]
; Start with the first block.
LEA ESI,[EBX+SIZE#STREAM]
.15: CMP EDI,ESI ; Find the block pointed to with ESI, where EDI=WritePtr is in.
JB .20:
MOV ECX,[EBX+STREAM.BufSize]
ADD ECX,ESI
CMP EDI,ECX
JNA .30:
.20: ; WritePtr is not in block ESI. Try the next one.
MOV ESI,[ESI-4]
TEST ESI
JNZ .15:
STC ; Inconsistent data. This should never happen.
JMP .80:
.30: ; EDI=WritePtr inside block ESI..ECX.
SUB ECX,EDI ; Remaining free room on the last buffer.
XCHG ESI,EBP
.35: ; ESI=DataPtr EDI=WritePtr EBP=block EDX=requested data size ECX=remaining room.
CMP EDX,ECX
JA .40:
MOV ECX,EDX
.40: SUB EDX,ECX
REP MOVSB
MOV [EBX+STREAM.WritePtr],EDI
TEST EDX ; Data size not written yet.
JZ .80:
; EDX=remaining nonstored datasize ESI=nonstored data EBX=Stream EBP=block.
MOV EDI,[EBP-4]
TEST EDI
JZ .50:
; Next block EDI is already allocated, reuse it.
MOV EBP,EDI
.45: MOV [EBX+STREAM.WritePtr],EDI
MOV ECX,[EBX+STREAM.BufSize]
JMP .35:
.50: ; Block EBP was the last in chain. A new block needs to be allocated.
MOV ECX,[EBX+STREAM.BufSize]
ADD ECX,4
PoolNew [EBX+STREAM.Pool],ECX
JC .80:
MOVD [EAX],0
ADD EAX,4
MOV [EBP-4],EAX ; Link from the old block.
MOV EBP,EAX
MOV EDI,EAX
JMP .45:
.80: POP ESI
JNC .10:
.90:POPAD
RET
StreamStore$size@RT:
PUSH ECX,EDI
SUB ECX,ECX
SUB EAX,EAX
DEC ECX
MOV EDI,[ESP+12] ; DataPtr
REPNE SCASB
SUB EAX,ECX
SUB EAX,2
MOV [ESP+16],EAX ; DataSize
POP EDI,ECX
RET
ENDPROC1 StreamStore@RT:
%ENDMACRO StreamStoreRT
StreamStore %MACRO Stream, DataPtr, DataSize
%IF %# & 1 = 0
ID=5971, 'Macro "StreamStore" expects odd number of arguments.'
%EXITMACRO StreamStore
%ENDIF
PUSHD 0 ; Mark the end of arguments.
ArgNr %FOR %#..2,STEP=-2
PUSHD %*{%ArgNr}, %*{%ArgNr-1}
%ENDFOR ArgNr
PUSHD %Stream
PUSH ESP
ADDD [ESP],4*(%#+1)
CALL StreamStore@RT:
POP ESP
StreamStoreRT ; Declare the runtime procedure.
%ENDMACRO StreamStore
StreamStore$ %MACRO Stream, DataPtr
PUSHD 0 ; Mark the end of arguments.
ArgNr %FOR %#..2,STEP=-1
PUSHD EAX, %*{%ArgNr} ;
CALL StreamStore$size@RT:
%ENDFOR ArgNr
PUSHD %Stream
PUSH ESP
ADDD [ESP],8*(%#)
CALL StreamStore@RT
POP ESP
StreamStoreRT ; Declare the runtime procedure.
%ENDMACRO StreamStore$
StreamStoreLn %MACRO Stream, DataPtr, DataSize
%IF %# & 1 = 0
%ERROR ID=5972, 'Macro "StreamStoreLn" expects odd number of arguments.'
%EXITMACRO StreamStoreLn
%ENDIF
PUSHD 0x00000A0D ; EOL data.
PUSHD 0 ; Mark the end of arguments.
PUSHD 2 ; Size of EOL.
PUSHD ESP ; Pointer to EOL.
ADDD [ESP],8 ; Adjust the pointer.
ArgNr %FOR %#..2,STEP=-2
PUSHD %*{%ArgNr}, %*{%ArgNr-1}
%ENDFOR ArgNr
PUSHD %Stream
PUSH ESP
ADDD [ESP],4*(%#+4)
CALL StreamStore@RT:
POP ESP
StreamStoreRT ; Declare runtime procedure.
%ENDMACRO StreamStoreLn
StreamStoreByte %MACRO Stream, Data
%IF "%Data" !== "AL"
MOV AL,%Data
%ENDIF
PUSHD EAX
PUSHD 0 ; Mark the end of arguments.
PUSHD 1 ; Size of %Data.
PUSHD ESP ; Pointer to %Data.
ADDD [ESP],8 ; Adjust the pointer.
PUSHD %Stream
PUSH ESP
ADDD [ESP],20
CALL StreamStore@RT:
POP ESP
StreamStoreRT ; Declare runtime procedure.
%ENDMACRO StreamStoreByte
StreamStoreWord %MACRO Stream, Data
%IF "%Data" !== "AX"
MOV AX,%Data
%ENDIF
PUSHD EAX
PUSHD 0 ; Mark the end of arguments.
PUSHD 2 ; Size of %Data.
PUSHD ESP ; Pointer to %Data.
ADDD [ESP],8 ; Adjust the pointer.
PUSHD %Stream
PUSH ESP
ADDD [ESP],20
CALL StreamStore@RT:
POP ESP
StreamStoreRT ; Declare runtime procedure.
%ENDMACRO StreamStoreWord
StreamStoreDword aStream,EBX store the contents of EBX.
StreamStore aStream,EBX,4 store 4 bytes of the memory addressed with EBX.
StreamStoreDword %MACRO Stream, Data
PUSHD %Data
PUSHD 0 ; Mark the end of arguments.
PUSHD 4 ; Size of %Data.
PUSHD ESP ; Pointer to %Data.
ADDD [ESP],8 ; Adjust the pointer.
PUSHD %Stream
PUSH ESP
ADDD [ESP],20
CALL StreamStore@RT:
POP ESP
StreamStoreRT ; Declare runtime procedure.
%ENDMACRO StreamStoreDword
ENDHEAD memory32 ; End of library interface.