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 XOR EAX,EAX MOV [ESP+7*4],EAX ; Initialize %ReturnEAX for the case of error. MOV ECX,[ESP+9*4] ; %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,[ESP+10*4] ; 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 [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],ESI 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 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 [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: SysAlloc 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 memory ; End of library interface.