This file can be included to 32bit programs written in Euro Assembler.
It contains OS-independent macros for dynamic memory management.
The file memory.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 SysGetAllocationGranularity, SysAlloc and SysFree which encapsulate system calls. Those three system macros must be defined before any macro from this library is expanded.
For an example of MS Windows implementation of such functions see
SysGetAllocationGranularity,
SysAlloc and
SysFree in easource/syswin.htm.
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.
memory 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 ; Memory granularity (usually 64K). .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
MOV EBP,ESP
XOR EAX,EAX
MOV [EBP+28],EAX ; Initialize %ReturnEAX for the case of error.
MOV ECX,[EBP+36] ; %Size.
SysGetAllocationGranularity
TEST EAX
JZ .30: ; If SysGetAllocationGranularity failed, abort.
MOV ESI,EAX
MOV EDX,EAX
; 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: SysAlloc EDX
.30: MOV ECX,[EBP+40] ; Offset of ErrorHandler.
JNZ .50: ; If SysAlloc 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 [EBP+28],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],ESI
MOV [EBX+POOL.ErrH],ECX
MOV [EBX+POOL.Last],EDI
MOV [EBX+POOL.Ptr],EAX
.90: MOV ESP,EBP
POPAD
RET 2*4
ENDPROC1 PoolCreate@RT::
%ENDMACRO PoolCreate
PoolDestroy %MACRO aPool
PUSHD %aPool
CALL PoolDestroy@RT::
PoolDestroy@RT:: PROC1
PUSHAD
MOV EBP,ESP
MOV EBX,[EBP+36] ; Pointer to the POOL structure.
SUB ECX,ECX ; ECX is a counter of total freed memory of this pool.
TEST EBX
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.
SysFree ESI,EAX ; Ask OS to free the memory block addressed with ESI.
JZ .Error:
MOV ESI,EDI ; Try the previous block.
JMP .10:
.Error:STC
.90: MOV [EBP+28],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
%PoolNewFlags %SETA "%ZeroTerminate[1]" !== "N" & 0x8000_0000
%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 EBP,ESP
MOV EBX,[EBP+36] ; %aPool.
XOR EDI,EDI
TEST EBX
JZ .60: ; Error if no pool provided.
.10: MOV ECX,[EBP+40] ; %DataSize.
MOV EDX,[EBP+44] ; %PoolNewFlags.
BTR EDX,31 ; ZeroTerminate?
ADC ECX,8 ; Increment if ZeroTerminated=Yes and add 8 bytes for two bottom DWORDs.
MOV EDI,[EBX+POOL.Ptr]
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.
CLC
JMP .80: ; Done. EDI is the aligned memory pointer.
.30: ; Not enough free space in this pool block. Allocate a new pool block.
; EBX=^POOL, ECX=DataSize.
MOV EDX,[EBX-8] ; 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: SysAlloc EDX
MOV EDI,EAX
JNZ .70: ; If allocation succeeded.
MOV ECX,[EBX+POOL.ErrH] ; Allocation error happened.
JECXZ .60:
CALL ECX
.60: SUB EDI,EDI
STC
JMP .80:
.70: MOV ECX,[EBX+POOL.Last]
MOV [EBX+POOL.Last],EDI
MOV [EDI+0],EDX ; Store the size of the new pool block.
MOV [EDI+4],ECX ; Store the pointer to its previous pool block.
ADD EDI,8
MOV [EBX+POOL.Ptr],EDI
JMP .10: ; This time the reservation attempt will not fail.
.80: MOV [%ReturnEAX],EDI
.90: MOV ESP,EBP
POPAD
RET 12
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+12] ; %aPool.
MOV ESI,[ESP+16] ; %DataPtr.
MOV ECX,[ESP+20] ; %DataSize.
PoolNew EAX,ECX,Align=%Align,ZeroTerminate=%ZeroTerminate
: JC PoolStoreError%.:
MOV EDI,EAX
REP MOVSB
%IF "%ZeroTerminate[1]" !== "N"
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 EBP,ESP
MOV EAX,1K ; Minimal acceptable buffer size.
MOV ECX,[%Param2] ; %Size.
MOV EDX,[%Param1] ; aPool.
CMP ECX,EAX
JAE .20:
XCHG EAX,ECX
.20: PoolNew EDX,ECX, Align=DWORD
MOV [%ReturnEAX],EAX
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 8
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
PUSHD %DataSize, %aBuffer
CALL BufferNew@RT::
BufferNew@RT:: PROC1
PUSHAD
MOV EBP,ESP
SUB EAX,EAX
MOV EBX,[%Param1] ; %aBuffer.
MOV [%ReturnEAX],EAX ; For the case of error.
TEST EBX
STC
JZ .90: ; If bad buffer provided.
.10: MOV ECX,[%Param2] ; %DataSize
MOV EAX,[EBX+BUFFER.Ptr]
MOV [%ReturnEAX],EAX
TEST EAX
JNZ .20:
; Buffer not initialized or other error. Abort.
MOV ECX,[EBX+BUFFER.Pool]
JECXZ .15:
MOV EAX,[ECX+POOL.ErrH]
TEST EAX
JZ .15:
CALL EAX
.15: 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 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: CLC
.80: MOV [EBX+BUFFER.Ptr],EAX
.90:POPAD
RET 8
ENDPROC1 BufferNew@RT::
%ENDMACRO BufferNew
BufferStore %MACRO aBuffer, DataPtr, DataSize
PUSHD %DataSize, %DataPtr, %aBuffer
CALL BufferStore@RT::
BufferStore@RT:: PROC1
PUSHAD
MOV EBP,ESP
MOV EDX,[%Param1] ; aBuffer.
MOV ECX,[%Param3] ; DataSize.
TEST EDX
STC
JZ .90:
BufferNew EDX,ECX
JC .90:
MOV EDI,EAX
MOV ESI,[%Param2] ; 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 EBP,ESP
MOV EDX,[%Param1] ; aBuffer.
MOV EBX,[%Param2] ; Value.
TEST EDX
STC
JZ .90:
BufferNew EDX,1
JC .90:
MOV [EAX],BL ; Store the BYTE value.
.90:POPAD
RET 8
ENDPROC1 BufferStoreByte@RT::
%ENDMACRO BufferStoreByte
BufferStoreWord %MACRO aBuffer, Value
PUSHD %Value, %aBuffer
CALL BufferStoreWord@RT
BufferStoreWord@RT:: PROC1
PUSHAD
MOV EBP,ESP
MOV EDX,[%Param1] ; aBuffer.
MOV EBX,[%Param2] ; Value.
TEST EDX
STC
JZ .90:
BufferNew EDX,2
JC .90:
MOV [EAX],BX ; Store the WORD value.
.90:POPAD
RET 8
ENDPROC1 BufferStoreWord@RT::
%ENDMACRO BufferStoreWord
BufferStoreDword %MACRO aBuffer, Value
PUSHD %Value, %aBuffer
CALL BufferStoreDword@RT::
BufferStoreDword@RT:: PROC1
PUSHAD
MOV EBP,ESP
MOV EDX,[%Param1] ; aBuffer.
MOV EBX,[%Param2] ; Value.
TEST EDX
STC
JZ .90:
BufferNew EDX,4
JC .90:
MOV [EAX],EBX ; Store the DWORD value.
.90:POPAD
RET 8
ENDPROC1 BufferStoreDword@RT::
%ENDMACRO BufferStoreDword
BufferStorePascalString %MACRO aBuffer, StringPtr, Size=-1
PUSHD %Size, %StringPtr, %aBuffer
CALL BufferStorePascalString@RT::
BufferStorePascalString@RT:: PROC1
PUSHAD
MOV EBP,ESP
MOV EDX,[%Param1] ; aBuffer.
MOV EDI,[%Param2] ; StringPtr.
MOV ECX,[%Param3] ; 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 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 12
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
Macro does nothing when %Bytes=0. If %Bytes is negative, the data block returned with subsequent BufferRetrieve will be shorter. If %Bytes is positive, returned data block will be larger. The enlarged portion of data contents is undefined, it may contain old information previously stored to %aBuffer and abandoned by BufferDecrement or by previous invokation of BufferResize with negative %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
PUSHD %Bytes, %aBuffer
CALL BufferResize@RT::
BufferResize@RT:: PROC1
PUSHAD
MOV EBX,[ESP+36] ; ^aBuffer.
MOV EDX,[ESP+40] ; Bytes.
TEST EBX
STC
JZ .90: ; No valid buffer.
TEST EDX
JZ .90: ; Resize by zero does nothing.
MOV EAX,[EBX+BUFFER.Ptr]
MOV ESI,[EBX+BUFFER.Bottom]
ADD EAX,EDX ; Resize .Ptr.
CMP EAX,ESI
JB .70: ; Underflowed, saturate .Ptr to .Bottom.
.20: CMP EAX,[EBX+BUFFER.Top]
JNA .80: ; If not overflowed.
; Buffer EBX is not large enough.
SUB EAX,ESI
MOV EDX,EAX ; EDX is now the new requested size.
LEA ECX,[EAX+EAX]; It will be doubled to ECX bytes.
PoolNew [EBX+BUFFER.Pool],ECX, Align=DWORD
JC .90:
MOV EDI,EAX
MOV [EBX+BUFFER.Bottom],EAX
ADD EDX,EAX ; Resized .Ptr in the new buffer.
ADD EAX,ECX
MOV ECX,[EBX+BUFFER.Top]
MOV [EBX+BUFFER.Top],EAX
SUB ECX,ESI ; Old buffer allocated size.
REP MOVSB
MOV ESI,EDX
.70: MOV EAX,ESI
.80: MOV [EBX+BUFFER.Ptr],EAX
CLC
.90: POPAD
RET
ENDP1 BufferResize@RT::
%ENDMACRO BufferResize
BufferRetrieve %MACRO aBuffer
%IF "%aBuffer" !== "ECX"
MOV ECX,%aBuffer
%ENDIF
STC
JECXZ BufferRetrieve%.:
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 EBP,ESP
MOV EDX,[%Param1] ; %aPool.
MOV ECX,[%Param2] ; %Size.
PoolNew EDX, SIZE#LIST, Align=DWORD
MOV [%ReturnEAX],EAX
JC .90:
MOV [EAX+LIST.Pool],EDX
MOV [EAX+LIST.Size],ECX
.90: POPAD
RET 8
ENDPROC1 ListCreate@RT::
%ENDMACRO ListCreate
ListNew %MACRO aList, Zeroed=No
%IF "%Zeroed[1]"=="N"
PUSHD 0
%ELSE
PUSHD -1
%ENDIF
PUSHD %aList
CALL ListNew@RT::
ListNew@RT:: PROC1
PUSHAD
MOV EBP,ESP
MOV EBX,[%Param1] ; %aList.
MOV [%ReturnEAX],EBX
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 [%ReturnEAX],EAX
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 [%ReturnEAX],EDI
INCD [EBX+LIST.Count]
XOR EAX,EAX
CMP EAX,[%Param2] ; %Zeroed.
JE .90: ; If Zeroed=No.
MOV ECX,[EBX+LIST.Size]
REP STOSB ; Clear the leaf payload area.
CLC
.90:POPAD
RET 8
ENDPROC1 ListNew@RT::
%ENDMACRO ListNew
ListStore %MACRO aList, DataPtr
PUSHD %DataPtr, %aList
CALL ListStore@RT::
ListStore@RT:: PROC1
PUSHAD
MOV EBP,ESP
MOV EBX,[%Param1] ; %aList.
MOV ESI,[%Param2] ; %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 [%ReturnEAX],EAX
POPAD
RET 8
ENDPROC1 ListStore@RT::
%ENDMACRO ListStore
ListInsert %MACRO aList, PrevLeaf, DataPtr
PUSHD %DataPtr, %PrevLeaf, %aList
CALL ListInsert@RT::
ListInsert@RT:: PROC1
PUSHAD
MOV EBP,ESP
MOV EBX,[%Param1] ; %aList.
MOV ESI,[%Param2] ; %PrevLeaf.
XOR EAX,EAX
MOV [%ReturnEAX],EAX
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 [%ReturnEAX],EDI
TEST EDX
JZ .50:
MOV [EDX-4],EDI ; Prev.
.50: JNZ .60:
MOV [EBX+LIST.Last],EDI
.60: SUB ECX,8
MOV ESI,[%Param3] ; %DataPtr.
TEST ESI
JNZ .80:
XOR EAX,EAX
REP STOSB
JMP .90:
.80: REP MOVSB
.90: POPAD
RET 12
ENDPROC1 ListInsert@RT::
%ENDMACRO ListInsert
ListRemove %MACRO aList, LeafPtr
PUSHD %LeafPtr, %aList
CALL ListRemove@RT::
ListRemove@RT:: PROC1
PUSHAD
MOV EBP,ESP
MOV EBX,[%Param1] ; %aList.
MOV EDX,[%Param2] ; %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 8
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 EBP,ESP
MOV EAX,[%Param2] ; %Size.
MOV ESI,[%Param1] ; %aPool.
MOV ECX,EAX
MULD [%Param3] ; %Depth.
MOV EBX,EAX
ADD EAX,SIZE#STACK
PoolNew ESI,EAX,Align=DWORD
MOV [%ReturnEAX],EAX
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 12
ENDPROC1 StackCreate@RT::
%ENDMACRO StackCreate
StackClear %MACRO aStack
PUSHD EAX,EBX,%aStack
POP EBX
MOV EAX,[EBX+STACK.Bottom]
MOV [EBX+STACK.Ptr],EAX
POP EBX,EAX
%ENDMACRO StackClear
StackPush %MACRO aStack, DataPtr
PUSHD %DataPtr, %aStack
CALL StackPush@RT::
StackPush@RT:: PROC1
PUSHAD
MOV EBP,ESP
MOV EBX,[%Param1] ; %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 a free space on stack now.
MOV ESI,[%Param2] ; %DataPtr.
MOV ECX,[EBX+STACK.Size]
MOV [%ReturnEAX],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 8
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 EBP,ESP
MOV EBX,[%Param1] ; %Pool.
MOV ECX,[%Param2] ; %BufSize.
LEA EDX,[ECX+SIZE#STREAM]
PoolNew EBX, EDX, Align=DWORD
MOV [%ReturnEAX],EAX
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 8
ENDPROC1 StreamCreate@RT::
%ENDMACRO StreamCreate
StreamGetSize %MACRO Stream
PUSHD %Stream
CALL StreamGetSize@RT::
StreamGetSize@RT:: PROC1
PUSHAD
MOV EBP,ESP
SUB EAX,EAX
MOV EBX,[%Param1] ; %Stream.
MOV [%ReturnEAX],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 [%ReturnEAX],ECX
MOV ESI,[ESI-4]
JMP .10:
.30: SUB EAX,ESI
ADD [%ReturnEAX],EAX
.90: POPAD
RET 4
ENDPROC1 StreamGetSize@RT::
%ENDMACRO StreamGetSize
StreamReadByte %MACRO Stream
PUSHD %Stream
CALL StreamReadByte@RT::
StreamReadByte@RT:: PROC1
PUSHAD
MOV EBX,[ESP+36] ; %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
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 4
ENDPROC1 StreamReadByte@RT::
%ENDMACRO StreamReadByte
StreamReadLn %MACRO Stream, LineBuffer
PUSHD %LineBuffer, %Stream
CALL StreamReadLn@RT::
StreamReadLn@RT:: PROC1
PUSHAD
MOV EBX,[ESP+36] ; %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+40] ; %LineBuffer.
BufferStore EDX,ESI,EDI ; Copy line ESI,EDI to the buffer EDX.
CLC
.90: POPAD
RET 8
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+36] ; %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+40] ; 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 EBP,ESP
MOV EBX,[%Param1] ; %Stream.
MOV ECX,[EBX+STREAM.BufSize]
MOV EAX,[EBX+STREAM.ReadPtr]
MOV EDX,[EBX+STREAM.WritePtr]
LEA ESI,[EBX+SIZE#STREAM]
MOV [%ReturnESI],EAX
.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 [%ReturnESI],ESI
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 [%ReturnECX],ECX
ADD [EBX+STREAM.ReadPtr],ECX
TEST ECX
POPAD
RET 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 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 memory ; End of library interface.