Object Ctx represents one program block . Whenever a block statement is executed, one Ctx object is pushed on Src.CtxStack and it is popped when the corresponding ENDblock is encounterred.
Proper block nesting must be maintained in noemitting state too.
EUROASM NOWARN=2101 ctx PROGRAM FORMAT=COFF,MODEL=FLAT,WIDTH=32 INCLUDEHEAD "euroasm.htm" ; Interface (structures, symbols and macros) of other modules. INCLUDEHEAD \ ; Include headers of another modules used in this module. ea.htm, \ eaopt.htm, \ exp.htm, \ member.htm, \ msg.htm, \ pass.htm, \ pgm.htm, \ pgmopt.htm, \ stm.htm, \ syswin.htm, \ ;;
ctx HEAD ; Start of module interface.
Src.CtxStack
.
CTX STRUC
.NamePtr D D ; Pointer to block identifier delocalized name.
.NameSize D D ; Size of block identifier.
.ExpansionNr D D ; Value of dynamic %variable %.
which makes unique labels.
.Status D D ; Context boolean properties, see CtxEnc below.
; +10h.
.ObjPtr D D ; Pointer to the corresponding object, depending on ctxAnyType, see CtxEnc below.
.MacPtr D D ; Pointer to MAC during macro definition.
.PreviousSect D D ; ^SSS with section which was current before entering the block.
.BlockSect D D ; ^SSS with section of block-begin pseudoinstruction, possibly changed by AUTOSEGMENT=ON.
; +20h.
.LinePtr D D ; Pointer to the 1st physical line of block-begin statement.
.ChunkPtr D D ; Pointer to CHUNK where .LinePtr belongs to.
.LineNext D D ; Pointer to the end of source line with macroinstruction invocation.
.ChunkNext D D ; Pointer to CHUNK where .LineNext belongs to.
; +30h.
.LineEnd D D ; Pointer behind the 1st physical line of block-begin statement.
.Shift D D ; Ordinal operands number modifier. Default=0, increased by %SHIFT (%MACRO).
.ValBuffer D D ; ^BUFFER keeping values of operands in buffers below until the end of block expansion.
.ObjBuffer D D ; ^BUFFER which contains ^CTX_FOR or ^CTX_MAC object at block expansion.
; +40h.
.OrdBuffer D D ; ^BUFFER of 2*DDs keeping values of ordinal operands at %MACRO/%FOR invocation.
.KeyBuffer D D ; ^BUFFER of 4*DDs keeping keyword operands at macro invocation.
.FrmBuffer D D ; ^BUFFER of 4*DD keeping ordinal and keyword formal parameters at %MACRO/%FOR invocation.
; Their names are without %. In case of ctxFOR this buffer contains just one parameter.
ENDSTRUC CTX
CTX.ObjBuffer
contains an object
of CTX_MAC structure. It is created at invocations of a macro.CTX_MAC STRUC .LabelPtr D D ; Pointer to the label of macro invocation. .LabelSize D D ; Size of the label of macro invocation. .MacroNamePtr D D ; Pointer to the name of macro. .MacroNameSize D D ; Size of the name of macro. .ProtoLinePtr D D ; Pointer to the prototype (%MACRO statement). .InvokStmStatus D D ; STM.Status of the statement which invoked the macro. ENDSTRUC CTX_MAC
CTX.ObjBuffer
contains an object
of CTX_FOR structure.
CTX_FOR STRUC
.OrdinalNr D D ; Curent ordinal number of processed %FOR operand.
.NrOfOrdinals D D ; Maximal ordinal number of %FOR operands.
.Step D Q ; Current STEP value (signed integer). The value is -1 or +1 when flag CTX.Status:ctxStep0
is set.
.Value D Q ; Current formal variable numeric value. Also stored to CTX.FrmBuffer as a string.
.RangeLeft D Q ; Current operand range value. Ignored when the operand is not a range.
.RangeRight D Q ; Current operand range value. Ignored when the operand is not a range.
ENDSTRUC CTX_FOR
ctxAnyType = 0x0000_0FFF ; Block type mask. See also CtxGetEndTypename.CtxEndTypenameTable. ctxPROGRAM = 0x0000_0001 ; CTX.ObjPtr=^PGM ctxPROC = 0x0000_0002 ; CTX.ObjPtr=^SYM ctxPROC1 = 0x0000_0004 ; CTX.ObjPtr=^SYM ctxSTRUC = 0x0000_0008 ; CTX.ObjPtr=^SSS ctxMACRO = 0x0000_0010 ; CTX.ObjPtr=^CTX_MAC ctxFOR = 0x0000_0020 ; CTX.ObjPtr=^CTX_FOR ctxWHILE = 0x0000_0040 ; CTX.ObjPtr=0 ctxREPEAT = 0x0000_0080 ; CTX.ObjPtr=0 ctxIF = 0x0000_0100 ; CTX.ObjPtr=0 ; = 0x0000_0200 ; Unused. ctxHEAD = 0x0000_0400 ; CTX.ObjPtr=0 ctxCOMMENT = 0x0000_0800 ; CTX.ObjPtr=0 ; Other context properties. ctxDefinition = 0x0000_1000 ; Block is being defined (STRUC, %MACRO). ctxExpansion = 0x0000_2000 ; Block is being expanded (STRUC, %MACRO, %FOR, %WHILE, %REPEAT). ctxPrototype = 0x0000_4000 ; %MACRO prototype is being prepared. ctxMacExpList = 0x0000_8000 ; Macroinstruction entered when LIST=ON,LISTMACRO=ON, ; its expansion should be +listed even when the macro is defined in included file and LISTINCLUDE=OFF. ctxNamespace = 0x0001_0000 ; This block defines namespace. ctxExpandable = 0x0002_0000 ; Block has %. property (%FOR,%WHILE,%REPEAT,%MACRO). ctxFormal = 0x0004_0000 ; Block has formal parameters (%FOR, %MACRO). All buffers reserved. ctxRepeat = 0x0008_0000 ; This block is %FOR or %WHILE or %REPEAT (see LISTREPEAT). ctx1stRepeat = 0x0010_0000 ; Repeat block is expanded for the 1st time. Display in listing. ctxNestingOff = 0x0020_0000 ; This block was specified with key NESTINGCHECK=OFF. ctxIsRange = 0x0040_0000 ; Current %FOR operand is numeric range. ctxStep0 = 0x0080_0000 ; Set if STEP=0 or if STEP= was not explicitly specified. Empiric slope will be used. ctxExited = 0x0100_0000 ; %EXITblock encountered, ignore possible nesting errors. ctxElsed = 0x0200_0000 ; %ELSE statement was already encountered in this ctxIF. ctxIfEmit = 0x0400_0000 ; %IF block enterred in emitting status. ; = 0x0800_0000 ; Unused. ctxPgmPassed = 0x1000_0000 ; PGM has performed at least 1 pass. Used to skip PROGRAM stm in repeated passes. ctxPgmReturned = 0x2000_0000 ; ENDPROGRAM just returned to its PROGRAM statement. Reset in PseudoPROGRAM. ctxMacLabeled = 0x4000_0000 ; Macro definition has formal label %: declared in one of its statements. ctxNoEmit = 0x8000_0000 ; Nonemitting part of block reached, such as false %IF, %COMMENT, EXITed etc.
ENDHEAD ctx ; End of module interface.
[.text]
CtxPeek Procedure CtxType, CtxStackPtr SUB EAX,EAX MOV [%ReturnEAX],EAX MOV EBX,[Src.CtxStack::] MOV EAX,[%CtxStackPtr] CMP EBX,1 MOV EDX,[%CtxType] JC .99: TEST EAX JNZ .30: StackPeekLast EBX .10: JC .99: TEST [EAX+CTX.Status],EDX JNZ .80: .30: StackPeekPrev EBX,EAX JMP .10: .80: MOV [%ReturnEAX],EAX .99: EndProcedure CtxPeek
CtxGetEndTypename Procedure CtxType MOV EBX,[%CtxType] MOV ESI,.CtxEndTypenameTable: JNSt EBX,ctxExited,.10: MOV ESI,.CtxExitTypenameTable: .10:MOV EAX, .Dummy AND EBX,ctxAnyType BSF EDX,EBX ; EDX=0 (ctxPROGRAM), 1(ctxPROC), 2(ctxPROC1)..11(ctxCOMMENT) JZ .80: CMP EDX,SIZE#.CtxEndTypenameTable /4 JNB .80: MOV EAX,[ESI+4*EDX] .80: MOV [%ReturnEAX],EAX ; JMPS .90: [.data] ALIGN 4 .Dummy DD 0,0 .CtxEndTypenameTable DD \ ; The order must match CTX.Status:ctxAnyType. Dict_PseudoENDPROGRAM::,Dict_PseudoENDPROC::, Dict_PseudoENDPROC1::,\ Dict_PseudoENDSTRUC::, Dict_PseudopcENDMACRO::, Dict_PseudopcENDFOR::,\ Dict_PseudopcENDWHILE::,Dict_PseudopcENDREPEAT::,Dict_PseudopcENDIF::, \ .Dummy, Dict_PseudoENDHEAD::, Dict_PseudopcENDCOMMENT:: .CtxExitTypenameTable DD \ ; The order must match CTX.Status:ctxAnyType. Dict_PseudoENDPROGRAM::,Dict_PseudoENDPROC::, Dict_PseudoENDPROC1::,\ Dict_PseudoENDSTRUC::, Dict_PseudopcEXITMACRO::,Dict_PseudopcEXITFOR::,\ Dict_PseudopcEXITWHILE::,Dict_PseudopcEXITREPEAT::,Dict_PseudopcELSE::, \ .Dummy, Dict_PseudoENDHEAD::, Dict_PseudopcENDCOMMENT:: DS 0*CTX_MAC [.text] .90:EndProcedure CtxGetEndTypename
CtxCreate Procedure CtxPtr, CtxStatus, StmPtr MOV EDI,[%CtxPtr] MOV EBX,[%StmPtr] Clear EDI,Size=SIZE#CTX Invoke EaBufferReserve::,CtxCreate MOV [EDI+CTX.ValBuffer],EAX ; ValBuffer is always reserved, regardless of ctxExpandable. MOV EDX,[%CtxStatus] ; Set context name. MOV ECX,[EBX+STM.LabelSize] MOV ESI,[EBX+STM.LabelPtr] JNSt EDX,ctxMACRO,.20: JNSt EDX,ctxExpansion,.20: MOV ECX,[EBX+STM.OperationSize] ; Macro invocation has context name in Stm.Operation instead of Stm.Label. MOV ESI,[EBX+STM.OperationPtr] JMP .30: .20: JNSt EDX,ctxREPEAT, .30: ; %REPEAT statement is an exception: it has block name in its 1st ordinal instead of label field. BufferRetrieve [EBX+STM.OrdBuffer] JECXZ .50: MOV ECX,[ESI+4] MOV ESI,[ESI+0] StripColons ESI,ECX .30: TEST ECX JNZ .40: JSt EDX,ctxIF|ctxRepeat|ctxCOMMENT|ctxHEAD, .50: ; Those blocks may have no name. .40: Invoke SymDelocalName::,ESI,ECX,[EDI+CTX.ValBuffer],memberDelocal BufferRetrieve [EDI+CTX.ValBuffer] MOV [EDI+CTX.NamePtr],ESI ; Persistent delocalized block name. MOV [EDI+CTX.NameSize],ECX .50: MOV EAX,[EBX+STM.LinePtr] MOV ECX,[EBX+STM.LineEnd] MOV ESI,[EBX+STM.ChunkPtr] MOV EBX,[EBX+STM.Section] MOV [EDI+CTX.LinePtr],EAX MOV [EDI+CTX.LineEnd],ECX MOV [EDI+CTX.ChunkPtr],ESI MOV [EDI+CTX.PreviousSect],EBX MOV [EDI+CTX.BlockSect],EBX .60: JNSt EDX,ctxFormal,.80: Invoke EaBufferReserve::,CtxCreate MOV [EDI+CTX.ObjBuffer],EAX Invoke EaBufferReserve::,CtxCreate MOV [EDI+CTX.OrdBuffer],EAX Invoke EaBufferReserve::,CtxCreate MOV [EDI+CTX.KeyBuffer],EAX Invoke EaBufferReserve::,CtxCreate MOV [EDI+CTX.FrmBuffer],EAX .80: MOV [EDI+CTX.Status],EDX .90: EndProcedure CtxCreate
CtxDestroy Procedure CtxPtr MOV EBX,[%CtxPtr] Invoke EaBufferRelease::,[EBX+CTX.ObjBuffer] Invoke EaBufferRelease::,[EBX+CTX.OrdBuffer] Invoke EaBufferRelease::,[EBX+CTX.KeyBuffer] Invoke EaBufferRelease::,[EBX+CTX.FrmBuffer] Invoke EaBufferRelease::,[EBX+CTX.ValBuffer] EndProcedure CtxDestroy
CTX_FOR.OrdinalNr
and CTX_FOR.Value
will be updated, too.
CtxForNext Procedure CtxPtr RangeRightPtr LocalVar RangeRightSize LocalVar CtxForNumber LocalVar Size=20 ; Room for the expanded number. MOV EBX,[%CtxPtr] MOV EDI,[EBX+CTX.ObjPtr] ; ^CTX_FOR JSt [EBX+CTX.Status],ctxIsRange,.60: .20:MOV EDX,[EDI+CTX_FOR.OrdinalNr] ; Get the next ordinal. INC EDX CMP [EDI+CTX_FOR.NrOfOrdinals],EDX JC .99: ; No more ordinals. MOV [EDI+CTX_FOR.OrdinalNr],EDX BufferRetrieve [EBX+CTX.OrdBuffer] LEA ESI,[ESI+8*EDX-8] ; ESI now points to pair OrdValuePtr,OrdValueSize. MOV ECX,[ESI+4] MOV ESI,[ESI] LEA EDX,[ESI+ECX] Invoke ExpParseRange::,ESI,EDX JNC .30: ; If the ordinal is numeric range. RstSt [EBX+CTX.Status],ctxIsRange MOV EDI,ESI MOV EDX,ECX BufferRetrieve [EBX+CTX.FrmBuffer] MOV [ESI+8],EDI ; Copy nonrange operand to formal variable value. MOV [ESI+12],EDX JMP .90: .30:SetSt [EBX+CTX.Status],ctxIsRange ; %FOR loop has just enterred the next numeric range operand. ; ESI..EDX is range source notation, EAX points behind the range operator. SUB EDX,EAX MOV [%RangeRightPtr],EAX ; Save it for later evaluation. MOV [%RangeRightSize],EDX LEA ECX,[EAX-2] SUB ECX,ESI Invoke ExpEvalNum::,ESI,ECX ; RangeLeft evaluation. JC .20: ; If syntax error, report Msg and go to the next operand. MOV [EDI+CTX_FOR.RangeLeft+0],EAX MOV [EDI+CTX_FOR.RangeLeft+4],EDX Invoke ExpEvalNum::,[%RangeRightPtr],[%RangeRightSize] JC .20: ; If syntax error, report Msg and go to the next operand. MOV [EDI+CTX_FOR.RangeRight+0],EAX MOV [EDI+CTX_FOR.RangeRight+4],EDX JNSt [EBX+CTX.Status],ctxStep0,.40: ; If STEP=0, it will be actually +1 or -1. SUB ECX,ECX SUB EAX,[EDI+CTX_FOR.RangeLeft+0] SBB EDX,[EDI+CTX_FOR.RangeLeft+4] JS .35: ; If range slope negative. MOV [EDI+CTX_FOR.Step+4],ECX INC ECX MOV [EDI+CTX_FOR.Step+0],ECX ; Step:= +1. JMPS .40: .35:DEC ECX ; Negative range slope. MOV [EDI+CTX_FOR.Step+0],ECX MOV [EDI+CTX_FOR.Step+4],ECX ; Step:= -1. .40:JSt [EBX+CTX.Status],ctxStep0,.50: ; If nonzero range slope differs from the step sign, this operand will be skipped. MOV EAX,[EDI+CTX_FOR.RangeRight+0] MOV EDX,[EDI+CTX_FOR.RangeRight+4] SUB EAX,[EDI+CTX_FOR.RangeLeft+0] SBB EDX,[EDI+CTX_FOR.RangeLeft+4] JS .45: ; If the range slope is negative. JNZ .43: ; If the range slope is positive. TEST EAX JZ .50: ; If the range slope is zero, e.g. "1..1" .43:; Range slope is positive. TESTB [EDI+CTX_FOR.Step+7],0x80 JNZ .20: ; Ignore this range when the step is negative. JMPS .50: .45:TESTB [EDI+CTX_FOR.Step+7],0x80 JZ .20: ; If range slope is negative and step is positive, skip the operand. .50:MOV EAX,[EDI+CTX_FOR.RangeLeft+0] ; Start with the left range value. MOV EDX,[EDI+CTX_FOR.RangeLeft+4] MOV [EDI+CTX_FOR.Value+0],EAX MOV [EDI+CTX_FOR.Value+4],EDX JMP .80: .60: ; %FOR processing is inside a numeric range. MOV EAX,[EDI+CTX_FOR.Value+0] MOV EDX,[EDI+CTX_FOR.Value+4] ADD EAX,[EDI+CTX_FOR.Step+0] ADC EDX,[EDI+CTX_FOR.Step+4] MOV [EDI+CTX_FOR.Value+0],EAX MOV [EDI+CTX_FOR.Value+4],EDX TESTB [EDI+CTX_FOR.Step+7],0x80 JNZ .70: CMP EDX,[EDI+CTX_FOR.RangeRight+4] ; Step is positive. EDX:EAX is the incremented formal value. JL .80: JG .20: ; Out of range, continue with the next operand. CMP EAX,[EDI+CTX_FOR.RangeRight+0] JBE .80: JMP .20: ; Out of range, continue with the next operand. .70:CMP EDX,[EDI+CTX_FOR.RangeRight+4] ; Step is negative. EDX:EAX is the decremented formal value. JG .80: JL .20: ; Out of range, continue with the next operand. CMP EAX,[EDI+CTX_FOR.RangeRight+0] JB .20: ; Out of range, continue with the next operand. .80: ; Convert CTX_FOR.Value to decadic into CTX.KeyBuffer and update CTX.FrmBuffer. BufferClear [EBX+CTX.KeyBuffer] LEA ECX,[%CtxForNumber] MOV EAX,[EDI+CTX_FOR.Value+0] MOV EDX,[EDI+CTX_FOR.Value+4] StoQD ECX,Size=20,Signed=yes SUB EDI,ECX ; Context.KeyBuffer will be misused to persistently keep the formal value. BufferStore [EBX+CTX.KeyBuffer],ECX,EDI BufferRetrieve [EBX+CTX.KeyBuffer] MOV EAX,ESI BufferRetrieve [EBX+CTX.FrmBuffer] MOV [ESI+8],EAX ; Pointer within KeyBuffer. MOV [ESI+12],EDI ; Number of digits in decadic value. .90:CLC .99:EndProcedure CtxForNext
CtxExpansionNrUpdate Procedure CtxPtr, StmPtr MOV EBX,[%StmPtr] MOV EDI,[%CtxPtr] MOV ESI,[EBX+STM.Program] TEST ESI JZ .90: MOV ECX,[ESI+PGM.PassPtr] JECXZ .90: MOV EAX,[ECX+PASS.ExpansionNr] INC EAX MOV [ECX+PASS.ExpansionNr],EAX MOV [EDI+CTX.ExpansionNr],EAX LEA ESI,[ESI+PGM.Pgmopt] DEC EAX MOV ECX,[ESI+PGMOPT.MaxExpansions] CMP EAX,ECX CMC Msg cc=E,'7140',ECX ; Number of expansions exceeded MAXEXPANSIONS=!1D. .90:EndProcedure CtxExpansionNrUpdate
ctx1stRepeat
if all those underlying contexts
have ctx1stRepeat set. Otherwise it returns 0
.Ctx1stRepeat Procedure SUB EAX,EAX MOV EDX,ctx1stRepeat .10:Invoke CtxPeek,ctxRepeat,EAX JC .90: JSt [EAX+CTX.Status],ctx1stRepeat,.10: XOR EDX,EDX .90:MOV [%ReturnEAX],EDX EndProcedure Ctx1stRepeat
CtxStatusAll Procedure MOV EBX,[Src.CtxStack::] XOR ECX,ECX StackPeekLast EBX JC .90: OR ECX,[EAX+CTX.Status] .20:StackPeekPrev EBX,EAX JC .90: OR ECX,[EAX+CTX.Status] JMP .20: .90:MOV [%ReturnEAX],ECX EndProcedure CtxStatusAll
CtxFind Procedure CtxType, StmPtr MOVD [%ReturnEAX],0 MOV EDX,[%CtxType] MOV EBX,[%StmPtr] StackPeekLast [Src.CtxStack::] JMPS .20: .10:StackPeekPrev [Src.CtxStack::],EDI .20:MOV CL,-1 ; Signalize that no matching blockId was found. JC .70: ; If the context stack is empty. MOV EDI,EAX JNSt [EDI+CTX.Status],EDX,.10: CMPD [%ReturnEAX],0 JNZ .30: MOV [%ReturnEAX],EDI ; Prepare to return the first context found for the case of not-matching blockId. .30:TEST EBX ; EDI is context of desired type. If StmPtr provided, check blockId. JZ .80: ; If blockId match cannot be tested, the 1st context will be used. MOV ESI,[EBX+STM.LabelPtr] MOV ECX,[EBX+STM.LabelSize] CMPD [EBX+STM.OperationData],PseudopcENDREPEAT::, IMM=DWORD JE .40: CMPD [EBX+STM.OperationData],PseudopcUNTIL::, IMM=DWORD JE .40: BufferRetrieve [EBX+STM.OrdBuffer] ; Other than %ENDREPEAT have blockId in 1st ordinal. TEST ECX JZ .80: ; Empty ENDblockId is always matching. MOV ECX,[ESI+4] MOV ESI,[ESI+0] StripColons ESI,ECX ; Get rid of trailing colon(s), if any. .40:TEST ECX ; ESI,ECX is now the ENDblock identifier. JZ .80: ; Empty ENDblockId is always matching. Invoke EaBufferReserve::,CtxFind Invoke SymDelocalName::,ESI,ECX,EAX,memberDelocalParent ; In case that block name has local scope. BufferRetrieve EAX Invoke EaBufferRelease::,EAX .50:Compare [EDI+CTX.NamePtr],[EDI+CTX.NameSize],ESI,ECX MOV CL,0 ; Mark matching blockId found. JE .80: ; If blockId match, done. ZF=1. JMP .10: ; Otherwise try to find a better context. .70:; Stack peeked but no matching blockId found. MOV EDI,[%ReturnEAX] .80:MOV [%ReturnEAX],EDI ; EDI=0 if no CtxType found. CL=0 if blockId match. TEST EDI STC JZ .90: ; Return CF=ZF=1 if no such context is on stack. TEST CL ; Return CF=0, ZF=1 if blockId match, otherwise CF=ZF=0. .90:EndProcedure CtxFind
Stm.CtxStatusAll:ctxExited
, report E7110 or E7120 on each block mismatch detected.CtxDiscard Procedure CtxPtr, StmPtr CtxDbuffer LocalVar ; Temporary buffer for contextes with NESTINGCHECK=OFF. Invoke EaBufferReserve::,CtxDiscard MOV [%CtxDbuffer],EAX MOV ESI,[%CtxPtr] MOV EBX,[%StmPtr] TEST ESI JZ .80: MOV EDX,[ESI+CTX.Status] AND EDX,ctxAnyType .10:StackPop [Src.CtxStack::] JC .60: MOV EDI,EAX CMP EDI,[%CtxPtr] JE .50: ; If this is the context to discard. JSt [EBX+STM.CtxStatusAll],ctxExited,.50: ; On EXITblock do not report mismatch. JNSt [EBX+STM.CtxStatusAll],ctxNestingOff,.20: ; If any context on stack has ctxNestingOff, do not report mismatch and ; do not remove contexts above the one which is being discarded. Store them to CtxDbuffer instead. BufferStore [%CtxDbuffer],EDI,SIZE#CTX ; Silently save it to be pushed back later in .70:. JMP .10: .20:JNSt [EBX+STM.CtxStatusAll],ctxRepeat,.30: JNSt [EBX+STM.CtxStatusAll],ctx1stRepeat,.50: .30:Invoke CtxGetEndTypename,[EDI+CTX.Status] JNSt [EDI+CTX.Status],ctxREPEAT,.40: XCHG EAX,EDI .40:Msg '7110',EAX,EDI ; Wrong nesting, expected "!1S !2S".',0 .50:Invoke CtxDestroy,EDI CMP EDI,[%CtxPtr] JNE .10: .60:BufferRetrieve [%CtxDbuffer] ; Zero or more undestroyed CTX objects. .70:LEA EDI,[ESI+ECX-SIZE#CTX] ; The last one. CMP EDI,ESI JB .80: ; If no more saved contexts. StackPush [Src.CtxStack::],EDI ; Return contexts back to stack in original order. SUB ECX,SIZE#CTX JMP .70: .80:Invoke EaBufferRelease::,[%CtxDbuffer] .90:EndProcedure CtxDiscard
ENDPROGRAM ctx