| Index | Manual | Download | Source | Macros | Embedded word Case ins.
Sitemap | Links | Forum | Tests | Projects

# pgm.htm
Class
PGM
Encodings
PgmEnc
Procedures
PgmCombine
PgmCreateImportModule
PgmCreateProgram
PgmDestroy
PgmDetectImportModule
PgmEvalEntry
PgmGetCurrent
PgmGetEntryAddress
PgmGlobalEntry
PgmGroupByModel
PgmCheckDirty
PgmLink
PgmLinkSections
PgmListGlobals
PgmListLiterals
PgmListMap
PgmOrderSegments
PgmOrderSymbols
PgmParameters
PgmRelocResolve
PgmResizeGroups
PgmSelectModules
PgmStoreGlobalSymbols
PgmStreamImage
PgmSymResolve

Pgm represents a program defined with source text between PROGRAM/ENDPROGRAM statements. Product of Pgm assembly is binary/object/executable file.

In other assemblers it is often referred as module or assembly unit . EuroAssembler accepts more than one program in a source file.

Pgm has its own memory pool.

        EUROASM NOWARN=2101
    pgm PROGRAM FORMAT=COFF,MODEL=FLAT,WIDTH=32
        INCLUDEHEAD euroasm.htm, \  Interface (structures, symbols and macros) of other modules used in this source.
                    chunk.htm,ctx.htm,dict.htm,ea.htm,eaopt.htm,exp.htm,lst.htm,msg.htm, \
                    pass.htm,pf.htm,pgmopt.htm,reloc.htm,src.htm,sss.htm,stm.htm,sym.htm,var.htm

    pgm HEAD ; Start of module interface.

↑ PGM
  Object of this class is allocated in Src.Pool.
Program processing object model
  PseudoPROGRAM ; Parse pseudoinstruction PROGRAM. CtxCreate PgmCreateProgram ┌─> PgmParameters ; Reassemble operands of PROGRAM statement. │ PgmoptAssemble │ PgmoptSetDefaults │ SssCreateImplicit │ PassCreate │ │ PseudoENDPROGRAM ; Parse pseudoinstruction ENDPROGRAM. │ PgmCheckDirty ; Detect if something was emitted in this program. │ PgmLinkSections ; Link virtual adress of each section │ SssLinkSections │ PassInspect >──final─┐ ; Check if the final pass just ended. │ PassDestroy │ └─<redirect assembly back │ ; Only if more passes are required. VarListMerge <───────┘ ; Copy %variables and macrodefinitions MacListMerge ; from the ending program to its parent, if it exists. PgmListLiterals ; Append info to the listing. PassDestroy PgmDestroy ChunkTotalLines ; Write info to output. PfOutput ; See Linker object model for more detailed info. PgmLinkSections ; Link emitted code + relocations of each section SssLinkSections PgmListMap ; Append info to the listing. PgmListGlobals ; Append info to the listing. CtxDiscard CtxDestroy continue below ENDPROGRAM statement

    PGM STRUC
    .NamePtr            D DWORD ; Pointer to the program name.
    .NameSize           D DWORD ; Size of the program name.
    .Pool               D DWORD ; Program pool of dynamic memory.
    .Status             D DWORD ; Program Boolean flags in PgmEncoding.
                            ; +10h.
    .PassNr             D DWORD ; Pass number.
    .CurrentSect        D DWORD ; ^SSS with .Status:sssSection - a section which Pgm emits to.
    .CurrentStm         D DWORD ; ^STM with the last parsed statement.
    .LinePtr            D DWORD ; Pointer to memory-mapped physical line with current statement of this PROGRAM.
                            ; +20h.
    .Errorlevel         D DWORD ; Errorlevel of this program.
    .PassPtr            D DWORD ; Pointer to PASS or 0 when no pass is initialized.
    .NrOfInitSegments   D DWORD ; How many groups+segments in image are initialized (trailing BSS and STACK do not count).
    .ModulePgmList      D DWORD ; ^LIST of PGM modules loaded from linked/imported files.
                            ; +30h.
    .SssList            D DWORD ; ^LIST with groups, segments, sections, externs and structures (SSS objects).
    .SymList            D DWORD ; ^LIST with program symbols (SYM objects).
    .SegOrdBuffer       D DWORD ; ^BUFFER with DWORD pointers to segments (^SSS) in image order.
    .SymOrdBuffer       D DWORD ; ^BUFFER with DWORD pointers to symbols (^SYM) in image order.
                            ; +40h.
    ; Synchronous tables describing files included in the program.
    .InclFilesNr        D DWORD ; Number of DWORDS in .InclFilesTable,.InclLinePtrTable,.InclFileTimeTable.
    .InclFilesTable     D DWORD ; Pointer to the table of DWORDs, each contains pointer to FILE structure.
    .InclLinePtrTable   D DWORD ; Pointer to the table of DWORDs, each contains LinePtr to INCLUDE* statement.
    .InclFileTimeTable  D DWORD ; Pointer to the table of DWORDs, each contains DosDateTime of included file.
                            ; +50h.
    ; Synchronous tables describing files linked in the program.
    .MaxLinks           D DWORD ; Copy of EUROASM MAXLINKS= option which was in charge on PgmCreateProgram.
    .LinkFilesNr        D DWORD ; Number of DWORDS in .LinkFileNamesTable and .LinkLinePtrTable.
    .LinkFileNamesTable D DWORD ; Pointer to the table of DWORDs, each contains pointer to ASCIIZ filename.
    .LinkLinePtrTable   D DWORD ; Pointer to the table of DWORDs, each contains LinePtr to LINK statement.
                            ; +60h.
    .MemPeakPass        D DWORD ; Peak memory allocated by Pass.Pool.
    .DSONameBuffer      D DWORD ; ^BUFFER with DWORD pointer+size of the name of dynamically loaded DSO module without path. Empty if it isn't ELFSO format.
    .EntryExp           DS EXP    ; Program entry evaluated to EXP structure by PgmEvalEntry.
    .Pgmopt             DS PGMOPT ; Program options of this program as PGMOPT object.
    .Eaopt              DS EAOPT  ; EUROASM options valid at the start of this program as EAOPT object.
     ENDSTRUC PGM

↑ PgmEnc
  Encoding of flags used in PGM.Status. More program-option flags are defined in PGM.Pgmopt.Status.

    ; Assembly flags.
    pgmEnvelope      = 0x0000_0001 ; Artificial PROGRAM block encapsulating all source file statements.
    pgmEnvelDirty    = 0x0000_0002 ; Something was emitted to envelope program, do list its PROGRAM/ENDPROGRAM statements.
    ;                = 0x0000_0004
    pgmNoMsg         = 0x0000_0008 ; Used to suppress Msg during ChunkSubHead detection.
    pgmLastPass      = 0x0000_0010 ; Program is in the final pass.
    pgmLastJustSet   = 0x0000_0020 ; Set together with pgmLastPass, reset in SrcAssemble when used.
    pgmPassInit      = 0x0000_0040 ; Pass was already initialized with 256/0x7C00 dummy bytes in COM/BOOT format.
    pgmFixingPass    = 0x0000_0080 ; Pass number last but one (MAXPASSES-1).
    ; Linker flags.
    pgmLinking       = 0x0000_0100 ; Program was finally assembled, now it is linked.
    pgmLoadsELFSO    = 0x0000_0800 ; This base program loads at least one ELFSO library.
    pgmFormatELF     = 0x0000_1000 ; Program format=ELF.
    pgmFormatELFX    = 0x0000_2000 ; Program format=ELFX.
    pgmFormatELFSO   = 0x0000_4000 ; Program format=ELFSO.
    pgmEntryReported = 0x0000_8000 ; E7710 or E7711 was already reported, see PgmGetEntryAddress.
    pgmSelected      = 0x0001_0000 ; Module is referenced, it will be combined and linked to base program.
    pgmImpLibMember  = 0x0002_0000 ; Module is a pure import library member.
    pgmIsModule      = 0x0004_0000 ; This program is OMF library module. It requires 512 alignment.
    pgmRealMode      = 0x0008_0000 ; Program format uses by default real-mode segment names [CODE]... rather than prot-mode names [.text]...

      ENDHEAD pgm
    [.text]

↑ PgmCreateProgram StmPtr
  PgmCreateProgram allocates PGM object on Src.Pool, initializes program structures, creates implicit groups, segments, sections. Context ctxPROGRAM with delocalized program name must be already pushed on context stack.
Input
  StmPtr is pointer to parsed PROGRAM statement.
Output
  EAX Pointer to just created PGM object.
Invokes
  CtxPeek
Invoked by
  PseudoPROGRAM

    PgmCreateProgram Procedure StmPtr
         MOV EBX,[%StmPtr]
         PoolNew [Src.Pool::],SIZE# PGM
         MOV EDI,EAX ; ^PGM.
         MOV [%ReturnEAX],EAX
         ; Initialize program object members.
         Clear EDI,Size=SIZE#PGM
         MOV EAX,[EBX+STM.ChunkPtr]
         MOV ECX,'0470'                              ; Assembling program "!1S".
         JNSt [EAX+CHUNK.Status],chunkEnvelope,.10:
         MOV ECX,'0270'                              ; Assembling source "!1S".
         SetSt [EDI+PGM.Status],pgmEnvelope
     .10:Invoke CtxPeek::,ctxPROGRAM,0
         MsgUnexpected cc=C
         Msg ECX,EAX                                 ; Context Assembling source/program "!1S".
         MOV ESI,[EAX+CTX.NamePtr]
         MOV ECX,[EAX+CTX.NameSize]
         MOV [EDI+PGM.NamePtr],ESI
         MOV [EDI+PGM.NameSize],ECX
         PoolCreate Size=%EaPoolSize, ErrorHandler=EaMallocError::
         MOV [EDI+PGM.Pool],EAX
         MOV EDX,EAX
         MOV [EDI+PGM.CurrentStm],EBX
         MOV EAX,[EBX+STM.LinePtr]
         MOV [EDI+PGM.LinePtr],EAX
         ListCreate EDX,SIZE#SYM
         MOV [EDI+PGM.SymList],EAX
         ListCreate EDX,SIZE#PGM
         MOV [EDI+PGM.ModulePgmList],EAX
         PoolNew EDX,SIZE#PASS
         MOV [EDI+PGM.PassPtr],EAX
         ListCreate EDX,SIZE#SSS
         MOV [EDI+PGM.SssList],EAX
         BufferCreate [EDI+PGM.Pool],Size=16*4       ; Initialize for estimated 16 segments in the program.
         MOV [EDI+PGM.SegOrdBuffer],EAX
         BufferCreate [EDI+PGM.Pool],Size=2048*4     ; Initialize for estimated 2048 symbols in the program.
         MOV [EDI+PGM.SymOrdBuffer],EAX
          ; Default segments on SssList will be created later, when PROGRAM keyword will have been assembled to Pgmopt.
         LEA ESI,[EDI+PGM.Eaopt]
         CopyTo ESI, Ea.Eaopt::, Size=SIZE#EAOPT
         LEA ESI,[EDI+PGM.Pgmopt]
         CopyTo ESI, Src.Pgmopt::, Size=SIZE#PGMOPT  ; Initialize Pgmopt from source Pgmopt.
         ; Allocate included files tables Pgm.InclFilesTable,Pgm.InclFileTimeTable,Pgm.InclLinePtrTable.
         MOV ECX,[Ea.Eaopt.MaxInclusions::]
         INC ECX
         SHL ECX,2
         PoolNew [EDI+PGM.Pool],ECX
         MOV [EDI+PGM.InclFilesTable],EAX
         PoolNew [EDI+PGM.Pool],ECX
         MOV [EDI+PGM.InclFileTimeTable],EAX
         PoolNew [EDI+PGM.Pool],ECX
         MOV [EDI+PGM.InclLinePtrTable],EAX
         ; Allocate linked files tables Pgm.LinkFileNamesTable, Pgm.LinkLinePtrTable.
         MOV ECX,[Ea.Eaopt.MaxLinks::]
         MOV [EDI+PGM.MaxLinks],ECX
         INC ECX
         SHL ECX,2
         PoolNew [EDI+PGM.Pool],ECX
         MOV [EDI+PGM.LinkFileNamesTable],EAX
         PoolNew [EDI+PGM.Pool],ECX
         MOV [EDI+PGM.LinkLinePtrTable],EAX
         ; Start with program pass number 1.
         INCD [EDI+PGM.PassNr]
        EndProcedure PgmCreateProgram

↑ PgmDestroy PgmPtr, StmPtr
  PgmDestroy performs the final job when the final pass ended and is being destroyed: it will load other modules linked to this program by pseudoinstruction LINK ( PseudoLINK), link, compile, format and write the output file, using PfOutput.
  The ending program is not removed from context stack yet.
Input
  PgmPtr Pointer to PGM object.
  StmPtr Pointer to STM object with ENDPROGRAM statement.
Output
  CF=0
  EAX=amount of memory allocated by the program.
Error
  CF=1 Errors are reported with macro Msg.
Invokes
  ChunkTotalLines PfOutput PgmCheckDirty PgmListGlobals PgmListMap
Invoked by
  PseudoENDPROGRAM

    PgmDestroy Procedure PgmPtr, StmPtr
          MOV EBX,[%PgmPtr]
          JNSt [EBX+PGM.Status],pgmEnvelope,.50:
          ; Curent program is source envelope, Pgm.Errorlevel will be combined with Src.Errorlevel.
          MOV EAX,[Src.Errorlevel::]
          CMP EAX,[EBX+PGM.Errorlevel]
          JNA .30:
          MOV [EBX+PGM.Errorlevel],EAX
     .30: Invoke ChunkTotalLines::         ; Count lines in source+includes.
          MOV EDX,EAX
          Invoke PgmCheckDirty,EBX         ; Returns CF=1 if something was emitted or linked to the envelope program.
          SBB EDI,EDI                      ; EDI is now -1 if program dirty, 0 if empty.
          JZ .40:                          ; If ending envelope program is empty, output file is not generated.
          Invoke PfOutput::,EBX            ; Format and write envelope program to disk, possibly overwriting embedded program with the same name.
     .40: Msg '0750',EBX,EDX,[EBX+PGM.PassNr],[EBX+PGM.Errorlevel] ; Source program "!1S" (!2D lines) assembled in !3D passes with errorlevel !4D.
          TEST EDI
          JZ .80:                          ; Empty envelope program does not list maps and globals.
          JMPS .60:
     .50: ; Program EBX is not an envelope, i.e. it was explicitly specified as PROGRAM/ENDPROGRAM block.
          Invoke PfOutput::,EBX            ; Generate output file (even when empty).
          Msg '0650',EBX,[EBX+PGM.PassNr],[EBX+PGM.Errorlevel] ; Program "!1S" assembled in !2D passes with errorlevel !3D.
     .60: ; Common continuation for both envelope and embedded program.
          JNSt [EBX+PGM.Pgmopt.Status],pgmoptLISTMAP,.70:
          Invoke PgmListMap,EBX,[%StmPtr]  ; List map of linked groups and segments.
     .70: JNSt [EBX+PGM.Pgmopt.Status],pgmoptLISTGLOBALS,.80:
          Invoke PgmListGlobals,EBX,[%StmPtr] ; List global symbols.
     .80: MOV EDX,[EBX+PGM.Pool]
          PoolDestroy EDX
          Msg cc=C,'2575','Pgm',EDX        ; Deallocation of virtual memory !1C.Pool !2Hh failed.
          ADD EAX,[EBX+PGM.MemPeakPass]
          MOV [%ReturnEAX],EAX
     .90:EndProcedure PgmDestroy

↑ PgmOrderSymbols Pgm

  This procedure decides which symbols should be stored in the symbol table of COFF|ELF module. It will put those symbols in desired order, sort them alphabetically, make them unique and store pointers to those symbols into `Pgm.SymOrdBuffer`.

  Symbols which go to the symbol table:

  1. Special unnamed symbol with index 0 (NUL symbol).
  2. Pseudosymbol `.file` specifying the source file name.
  3. symSe symbols representing the bottom of used (dirty) segments.
  4. Private (local) symbols. Literals are not stored.
  5. Global symbols. Resolved externals are not stored.

  Symbols within each of those divisions are sorted alphabetically by name (FQN) and made unique.

  SYM.NameIndex and SYM.NameDynIndex are updated.

Input
  Pgm points to PGM object whose `PGM.SymList` specifies all program symbols.
Output
  `Pgm.SymOrdBuffer` is filled with DWORD pointers to SYM symbols in the desired order.
Invoked by
  PfcoffCompile PfelfCompile PfelfsoCompile PfelfxCompile
Invokes
  EaBufferRelease EaBufferReserve EaBufferSort

    PgmOrderSymbols Procedure Pgm
    BufEnd    LocalVar                               ; Pointer to the end of buffer contents.
    SymNulBuf LocalVar                               ; ^BUFFER for null and file symbols.
    SymSegBuf LocalVar                               ; Segment symbols.
    SymLocBuf LocalVar                               ; Local symbols.
    SymGlbBuf LocalVar                               ; Global symbols.
        Invoke EaBufferReserve::,PgmOrderSymbols
        MOV [%SymNulBuf],EAX
        Invoke EaBufferReserve::,PgmOrderSymbols
        MOV [%SymSegBuf],EAX
        Invoke EaBufferReserve::,PgmOrderSymbols
        MOV [%SymLocBuf],EAX
        Invoke EaBufferReserve::,PgmOrderSymbols
        MOV [%SymGlbBuf],EAX
        MOV EBX,[%Pgm]
        MOV EDX,[EBX+PGM.SymOrdBuffer]
        TEST EDX
        JNZ .05:
        BufferCreate [EBX+PGM.Pool]
        MOV [EBX+PGM.SymOrdBuffer],EAX
    .05:ListGetFirst [EBX+PGM.SymList]
        MOV EBX,[EBX+PGM.SymOrdBuffer]               ; EBX is now the output buffer.
        JZ .90:
    .10:MOV EDX,[%SymNulBuf]
        CMPD [EAX+SYM.NameSize],0
        JE .70:                                      ; Store NUL and .file symbols.
        MOV EDX,[%SymSegBuf]
        JSt [EAX+SYM.Status],symSe,.70:              ; Store segment's symbol.
    .30:MOV EDX,[%SymLocBuf]                         ; Symbol EAX is global or local.
        JNSt [EAX+SYM.Status],symScopeMask,.70:      ; Go to store local symbols.
        JSt [EAX+SYM.Status],symResolved,.80:        ; Omit already resolved extern symbols.
        MOV EDX,[%SymGlbBuf]                         ; Store public|extern|import|export symbol.
    .70:BufferStoreDword EDX,EAX
    .80:ListGetNext EAX                              ; The next program symbol.
        JNZ .10:

    PgmOrderSymbols.Reorder: PROC1 ; Copy pointers from buffer EDX to buffer EBX, remove duplicates. Clobbers EAX,ECX,EDX,ESI,EDI.
        Invoke EaBufferSort::,EDX                     ; Sort symbols in buffer EDX by name alphabetically.
        BufferRetrieve EDX
        ADD ECX,ESI
        MOV [%BufEnd],ECX
        JECXZ .R9:
        MOV EDI,-1
        MOV EDX,EDI                                  ; EDI,EDX will be the memory of previous symbol name.
    .R1:CMP ESI,[%BufEnd]
        JNB .R9:
        LODSD
        Compare [EAX+SYM.NamePtr],[EAX+SYM.NameSize],EDI,EDX
        JE .R1:                                      ; Skip duplicated symbol.
        MOV EDI,[EAX+SYM.NamePtr]                    ; Prepare for the next Compare.
        MOV EDX,[EAX+SYM.NameSize]
        BufferStoreDword EBX,EAX
        JMP .R1:
    .R9:RET
        ENDP1 PgmOrderSymbols.Reorder:

    .90:MOV EDX,[%SymNulBuf]
        CALL .Reorder:
        MOV EDX,[%SymSegBuf]
        CALL .Reorder:
        MOV EDX,[%SymLocBuf]
        CALL .Reorder:
        BufferRetrieve EBX
        SHR ECX,2
        MOV [%ReturnECX],ECX
        MOV EDX,[%SymGlbBuf]
        CALL .Reorder:
        Invoke EaBufferRelease::,[%SymNulBuf]
        Invoke EaBufferRelease::,[%SymSegBuf]
        Invoke EaBufferRelease::,[%SymLocBuf]
        Invoke EaBufferRelease::,[%SymGlbBuf]
       EndProcedure PgmOrderSymbols

↑ PgmGetCurrent
  PgmGetCurrent returns the currently assembled program object.
Input
  -
Output
  CF=0
  EAX= pointer to PGM object.
Error
  CF=1 if no ctxPGM found on context stack.
  EAX=0
Invokes
  CtxPeek
Invoked by
  ExpEvalIdentifier MsgProc PseudoENDPROC1 SssFindByIndex SssFindByName StmParse SymCreate SymFindByIndex SymFindByInterName SymFindByName VarExpand

    PgmGetCurrent Procedure
          SUB EAX,EAX
          MOV [%ReturnEAX],EAX
     .20: Invoke CtxPeek::, ctxPROGRAM,EAX
          JC .90:
          MOV ECX,[EAX+CTX.ObjPtr]
          JECXZ .20:
          MOV [%ReturnEAX],ECX
     .90:EndProcedure PgmGetCurrent

↑ PgmListLiterals Pgm
  PgmListLiterals employs StmListing to dump all data and code literals defined in the program. They are listed at the end of program listing, just before ENDPROGAM statement.
  PgmListLiterals is invoked from PseudoENDPROGRAM just before the program is destroyed, but only if `LISTLITERALS=ENABLED`.
Input
  Pgm Pointer to PGM whose literals are being listed.
Output
  goes to the listing stream.
Error
  CF=1 Errors are reported with macro Msg.
See also
  StmListing.
Invokes
  EaBufferRelease EaBufferReserve SssFindByName StmClean StmCreate StmDestroy StmListing
Invoked by
  PseudoENDPROGRAM

    PgmListLiterals Procedure Pgm
    LSFound  LocalVar                                ; Boolean flag. Nonzero if subproc .ListSection found the literal section.
    SnBuffer LocalVar                                ; Temporary buffer to keep pointers to literals before sorting.
    @RTnum   LocalVar                                ; Ordinal of runtime literal section (1,2,3..).
    Stm      LocalVar Size=SIZE#STM                  ; Temprary fake statement used to feed StmListing.
    SssName  LocalVar Size=8                         ; Room for the literal section name, such as "@LT4"
        LEA EDI,[%SssName]
        MOV EAX,"@LT#"
        STOSD
        XOR EAX,EAX
        STOSD
        MOV [%@RTnum],EAX
        Invoke EaBufferReserve::,%^PROC
        MOV [%SnBuffer],EAX
        PUSHD [Src.Lst.Status::]                     ; Save the global listing status during PgmListLiteral's performance.
          LEA EBX,[%Stm]
          Invoke StmCreate::,EBX
          ; List data literals.
          MOV EAX,"LT64"
          CALL .ListSection:
          MOV EAX,"LT32"
          CALL .ListSection:
          MOV EAX,"LT16"
          CALL .ListSection:
          MOV EAX,"LT8"
          CALL .ListSection:
          MOV EAX,"LT4"
          CALL .ListSection:
          MOV EAX,"LT2"
          CALL .ListSection:
          MOV EAX,"LT1"
          CALL .ListSection:
          ; List code literals.
          MOV EAX,"RT0"
          CALL .ListSection:
          MOVD [%SssName],"@RT1"
    .50:  MOV EAX,[%@RTnum]
          INC EAX
          MOV [%@RTnum],EAX
          LEA EDI,[%SssName+3]
          StoD EDI,Size=2,Signed=no
          CMP AL,9
          MOV EAX,[%SssName+1]
          JA .60:
          AND EAX,0x00FF_FFFF
    .60:  CALL .ListSection:
          CMPD [%LSFound],0
          JNZ .50:                                   ; If @RT section was found, try the next one.
          Invoke StmDestroy::,EBX                    ; Otherwise all code-literals were listed.
        POPD [Src.Lst.Status::]
        Invoke EaBufferRelease::,[%SnBuffer]

    .ListSection: PROC1 ; List literals from one section.
         ; Input: EAX=section name without '@', e.g. "LT32" or "RT0".
         ;        EBX=Pointer to %Stm.
         ; Output:EBX,EBP unchanged, other register clobbered.
         MOVD [%LSFound],0
         LEA EDI,[%SssName]
         MOV ECX,4                                   ; Section name size.
         MOV [EDI+1],EAX
         TEST EAX,0xFF00_0000                        ; Test if section name has 4 or 5 characters.
         JZ .S1:
         INC ECX
     .S1:Invoke SssFindByName::,sssSection,0,EDI,ECX,0
         JC .S9:
         JNSt [EAX+SSS.Purpose],sssPurposeLITERAL,.S9:
         MOV EDI,EAX                                 ; A literal section was found.
         MOV [%LSFound],EAX
         BufferClear [%SnBuffer]
         MOV EDX,[%Pgm]
         MOV [EDX+PGM.CurrentSect],EDI
         ListGetFirst [EDX+PGM.SymList]              ; Find all literal symbols which belong to section EDI.
         JZ .S4:
     .S2:; EAX is now pointer to a symbol from program's symbol list.
         JNSt [EAX+SYM.Status],symLiteral,.S3:       ; Skip when it's not a literal symbol.
         CMP [EAX+SYM.Section],EDI
         JNE .S3:                                    ; Skip when the symbol EAX does not belong to the listed section EDI.
         BufferStoreDword [%SnBuffer],EAX
     .S3:ListGetNext EAX
         JNZ .S2:                                    ; The next symbol.
     .S4:BufferRetrieve [%SnBuffer]
         SAR ECX,2                                   ; ECX is now number of literal symbols.
         JZ .S9:
         ; Symbols whose pointers are now in array at ESI will be sorted by offset and then listed.
         ; Otherwise in recursive literals, such as =W =W value, the inner literal with higher offset
         ;     would be listed sooner, which looks weird.
         ShellSort ESI,ECX, 4, .ByOffset:
    .ByOffset: PROC1                                 ; Callback sorting procedure.
         PUSH EBX                                    ; EBX,ECX are callee-save registers.
          MOV EBX,[ESI]
          MOV EDX,[EDI]
          MOV EAX,[EDX+SYM.OffsetHigh]
          CMP EAX,[EBX+SYM.OffsetHigh]
          JNE .B5:
          MOV EAX,[EDX+SYM.OffsetLow]
          CMP EAX,[EBX+SYM.OffsetLow]
     .B5: JNC .B9:                                   ; Skip when they are in (desired) ascending order.
          MOV [ESI],EDX                              ; Otherwise swap both elements and return CF=1.
          MOV [EDI],EBX
     .B9:POP EBX
         RET
         ENDP1 .ByOffset:
         ; ESI is now an sorted array of ECX pointers to symbols which will be dumped as literal.
     .S5:LODSD
         PUSH ECX,ESI
           MOV EDX,EAX                               ; EDX is now ^SYM.
           SetSt [Src.Lst.Status::],lstLiteral       ; Tell StmListing how to treat this object.
           RstSt [Src.Lst.Status::],lstNoData+lstTRUE+lstFALSE
           Invoke StmClean::,EBX
           MOV EAX,[%Pgm]
           MOV [EBX+STM.Program],EAX
           MOV [EAX+PGM.CurrentSect],EDI
           MOV [EBX+STM.Section],EDI
           BufferStore [EBX+STM.SrcBuffer],[EDX+SYM.NamePtr],[EDX+SYM.NameSize]
           BufferStore [EBX+STM.SrcBuffer],=W(0x0A0D),2 ; Make new line.
           MOV EAX,[EDX+SYM.Size]
           MOV [EBX+STM.Size],EAX
           MOV EAX,[EDX+SYM.OffsetLow]
           MOV EDX,[EDX+SYM.OffsetHigh]
           MOV [EBX+STM.OffsetLow],EAX
           MOV [EBX+STM.OffsetHigh],EDX
           MOV [EDI+SSS.OrgLow],EAX
           ; Prepare emitted data of this literal to STM.EmitBuffer.
           SUB EAX,[EDI+SSS.BottomLow]
           BufferRetrieve [EDI+SSS.EmitBuffer]
           ADD ESI,EAX
           MOV ECX,[EBX+STM.Size]
           BufferStore [EBX+STM.EmitBuffer],ESI,ECX
           ; Prepare relocations of this literal to STM.RelocBuffer.
           BufferRetrieve [EDI+SSS.RelocBuffer]
           JECXZ .S7:
     .S6:  BufferStore [EBX+STM.RelocBuffer],ESI,SIZE#RELOC
           ADD ESI,SIZE#RELOC
           SUB ECX,SIZE#RELOC
           JA .S6:                                   ; The next relocation.
     .S7:  Invoke StmListing::,EBX
     .S8:POP ESI,ECX
         DEC ECX
         JNZ .S5:                                    ; The next literal symbol.
     .S9:RET
         ENDP1 .ListSection:
       EndProcedure PgmListLiterals

↑ PgmParameters PgmPtr, StmPtr
  PgmParameters updates PGMOPT members from keyword operands of `PROGRAM` pseudoinstruction.
Input
  PgmPtr Program whose options will be updated.
  StmPtr Statement PROGRAM with keywords.
Output
  Program options are updated.
Error
  CF=1 Errors are reported with macro Msg.
See also
  PgmoptAssemble members.
Invokes
  DictLookup PgmGlobalEntry PgmoptAssemble
Invoked by
  PseudoPROGRAM

    PgmParameters Procedure PgmPtr, StmPtr
        MOV EBX,[%StmPtr]
        BufferRetrieve [EBX+STM.KeyBuffer]
        LEA EDX,[ESI+ECX]
    .40:CMP ESI,EDX
        JNB .90:                           ; If no more keywords.
        MOV ECX,ESI
        LODSD                              ; Pointer to keyword name, e.g. MODEL=.
        MOV EDI,EAX
        LODSD                              ; Keyword name size.
        Invoke DictLookup::, DictPgmopt::, EDI,EAX
        Msg cc=C,'7821',ECX,DictPgmopt::   ; Unknown PROGRAM keyword "!1S". Expected one of !2L.
        MOV ECX,EAX                        ; Pgmopt index.
        LODSD                              ; Pointer to keyword value, e.g. SMALL.
        MOV EDI,EAX
        LODSD                              ; Keyword value size.
        JC .40:                            ; Skip when the option was unknown {E7821).
        StripSpaces EDI,EAX
        MOV EBX,[%PgmPtr]
    %ord %SETA 0
    opt %FOR %PgmoptList
    %ord %SETA %ord+1
         %IF "%opt" == "ENTRY"
           %EXITFOR opt
         %ENDIF
        %ENDFOR opt
        CMP ECX,%ord << 16 + varTypeSysPgmopt ; is the parameter ENTRY?                        >>
        JNE .70:
        Invoke PgmGlobalEntry,EBX,EDI,EAX
    .70:LEA EBX,[EBX+PGM.Pgmopt]
        Invoke PgmoptAssemble::, EBX,ECX,EDI,EAX
        JMP .40:
    .90:EndProcedure PgmParameters

↑ PgmGlobalEntry Program, EntryPtr, EntrySize
  PgmGlobalEntry checks if the program parameter `ENTRY=` specifies an identifier and declares a global symbol from it.
Input
  Program Pointer to PGM of the program.
  EntryPtr is pointer to the first character of entry name.
  EntrySize is number of characters in entry.
Output
  Entry global symbol is declared, if possible.
Error
  -
Invokes
  ExpParseIdentifier SymCreate
Invoked by
  PgmParameters

    PgmGlobalEntry Procedure Program, EntryPtr, EntrySize
    TempStm LocalVar Size=SIZE# STM
         ClearLocalVar
         LEA EDI,[%TempStm]
         MOV EBX,[%Program]
         MOV ESI,[%EntryPtr]
         MOV ECX,[%EntrySize]
         MOV [EDI+STM.Program],EBX
         LEA EDX,[ESI+ECX]
         Invoke ExpParseIdentifier::,ESI,EDX,expFullstop
         JC .90:
         Invoke SymCreate::,symReferenced+symGlobalRef+symEntry,ESI,EAX,EDI
    .90: EndProcedure PgmGlobalEntry

↑ PgmListMap Pgm, StmPtr
  PgmListMap displays map of combined (linked) program segments at the end of listing, just below ENDPROGRAM statement. It is invoked only when `PROGRAM LISTMAP=ON` from PgmDestroy after segments have been combined, ordered, linked and streamed by PfOutput.
  PgmListMap uses message buffer of ENDPROGRAM statement, the map is printed in listing but not on stdout.
Input
  Pgm is pointer to PGM - completely assembled, combined and linked program.
  StmPtr is pointer to STM object with ENDPROGRAM statement.
Output
  -
Error
  -
See also
  PgmListGlobals.
Invokes
  DictSearchByData EaBufferRelease EaBufferReserve SssPurposeToText
Invoked by
  PgmDestroy

    PgmListMap Procedure Pgm, StmPtr
    MsgBuffer LocalVar                               ; Local copy of statement MsgBuffer.
    Number LocalVar Size=20
         MOV ESI,[%StmPtr]
         MOV EBX,[%Pgm]
         MOV EAX,[ESI+STM.MsgBuffer]
         MOV [%MsgBuffer],EAX
         JNSt [EBX+PGM.Status],pgmEnvelope,.10:     ; Envelope program is listed only if something was emitted to it.
         JNSt [EBX+PGM.Status],pgmEnvelDirty,.90:
    .10: BufferStore$ [%MsgBuffer],=B'|        **** ListMap "'
         BufferStore [%MsgBuffer],[EBX+PGM.Pgmopt.OutFilePtr],[EBX+PGM.Pgmopt.OutFileSize]
         BufferStore$ [ESI+STM.MsgBuffer],=B'",model='
         MOV EAX,pgmoptModelMask
         AND EAX,[EBX+PGM.Pgmopt.Status]
         MOV EDX,ESI
         Invoke DictSearchByData::,DictProgramModels::,EAX ; Returns ESI=^DICT.
         XCHG EDX,ESI
         BufferStore [ESI+STM.MsgBuffer],[EDX+DICT.Ptr],[EDX+DICT.Size]
         BufferStore$ [ESI+STM.MsgBuffer],=B',groups='
         ; Count linked groups and segments.
         SUB EDI,EDI                                ; Group counter.
         SUB EDX,EDX                                ; Segment counter.
         BufferRetrieve [EBX+PGM.SegOrdBuffer]
         SHR ECX,2
         JZ .22:
    .13: LODSD
         JNSt [EAX+SSS.Status],sssGroup,.16:
         INC EDI
    .16: JNSt [EAX+SSS.Status],sssSegment,.19:
         INC EDX
    .19: LOOP .13:
    .22: MOV EAX,EDI                                ; Number of groups in image.
         LEA EDI,[%Number]
         MOV ECX,EDI
         StoD EDI
         SUB EDI,ECX                                ; ECX,EDI is now decimal number of groups.
         MOV ESI,[%StmPtr]
         BufferStore [%MsgBuffer],ECX,EDI
         BufferStore$ [%MsgBuffer],=B',segments='
         MOV EAX,EDX                                ; Number of segments in image.
         LEA EDI,[%Number]
         MOV ECX,EDI
         StoD EDI
         SUB EDI,ECX                                ; ECX,EDI is now decimal number of segments.
         BufferStore [%MsgBuffer],ECX,EDI
         BufferStore$ [%MsgBuffer],=B',entry='      ; List entry=.
         CMPD [EBX+PGM.Pgmopt.EntrySize],0
         JNZ .32:                                   ; Use %^ENTRY if not empty.
         BufferRetrieve [EBX+PGM.SegOrdBuffer]
         SHR ECX,2
         JZ .29:
    .25: LODSD
         JSt [EAX+SSS.Purpose],sssPurposeCODE,.27:
         LOOP .25:
    .27: MOV ESI,[EAX+SSS.NamePtr]
         MOV ECX,[EAX+SSS.NameSize]
    .29: MOV EAX,[EBX+PGM.Pgmopt.Status]
         AND AL,pgmoptFormatMask
         CMP AL,pgmoptCOM
         JNE .32:
         BufferStoreByte [%MsgBuffer],'['
         BufferStore [%MsgBuffer],ESI,ECX
         BufferStoreWord [%MsgBuffer],']:'
         BufferStore$ [%MsgBuffer],=B'00000100h'
         JMPS .34:
    .32: BufferStore [%MsgBuffer],[EBX+PGM.Pgmopt.EntryPtr],[EBX+PGM.Pgmopt.EntrySize]
    .34: BufferStore$ [%MsgBuffer],=B',stack='      ; List stack=.
         MOV EAX,[%Pgm]                             ; Find the last STACK group|segment to EDX.
         XOR EDX,EDX
         BufferRetrieve [EAX+PGM.SegOrdBuffer]
         SHR ECX,2
         JZ  .49:
    .37: LODSD
         JNSt [EAX+SSS.Purpose],sssPurposeSTACK,.40:
         MOV EDX,EAX
         JSt [EDX+SSS.Status],sssGroup,.42:         ; Prefer the stack group.
    .40: LOOP .37:
         TEST EDX                                   ; Either 1st stack group or the last stack segment.
         JZ .49:                                    ; Display nothing when no stack.
         MOV EDI,EDX                                ; ^SSS stack.
    .42: BufferStoreByte [%MsgBuffer],'['
         BufferStore [%MsgBuffer],[EDX+SSS.NamePtr],[EDX+SSS.NameSize]
         BufferStoreWord [%MsgBuffer],']:'
         MOV EAX,[EBX+PGM.Pgmopt.Status]
         AND EAX,pgmoptFormatMask
         PUSH .Dispatched:
         Dispatch AL,%PfPgmoptList
    .pgmoptRSRC:
    .pgmoptPE:
    .pgmoptDLL:
    .pgmoptCOFF:
    .pgmoptLIBCOF:
    .pgmoptELF:
    .pgmoptELFX:
    .pgmoptELFSO:
         MOV EAX,[EBX+PGM.Pgmopt.SizeOfStackCommitLow]
         MOV EDX,[EBX+PGM.Pgmopt.SizeOfStackCommitHigh]
         RET
    .pgmoptLIBOMF:
    .pgmoptOMF:
    .pgmoptMZ:                                       ; Stack pointer SP equals the size of the last stack segment.
         MOV EDI,EDX
         MOV EAX,[EDI+SSS.TopLow]
         MOV EDX,[EDI+SSS.TopHigh]
         SUB EAX,[EDI+SSS.BottomLow]
         SBB EDX,[EDI+SSS.BottomHigh]
         RET
    .pgmoptBIN:
    .pgmoptBOOT:
    .pgmoptCOM:                                      ; Stack pointer is fixed to the end of [COM] segment.
         MOV EAX,0xFFFE
         SUB EDX,EDX
         RET
    .Dispatched:                                     ; EDX:EAX=stack size.
         LEA EDI,[%Number]
         MOV ECX,EDI
         TEST EDX
         JZ .46:
         XCHG EAX,EDX                               ; Stack size is bigger than 4G.
         StoH EDI,Size=8
         XCHG EDX,EAX
    .46: StoH EDI,Size=8
         SUB EDI,ECX
         BufferStore [%MsgBuffer],ECX,EDI
         BufferStoreByte  [%MsgBuffer],'h'
    .49: BufferStoreWord [%MsgBuffer],0x0A0D        ; Terminate ListMap header line.
         BufferRetrieve [EBX+PGM.SegOrdBuffer]      ; List groups and segments, one per line.
         SHR ECX,2
         JZ .90:
    .52: LODSD                                      ; The loop .52: .. .85: displays all ECX groups and their segments.
    .55: PUSH ECX,ESI
           MOV ESI,EAX ; ^SSS.
           JNSt [ESI+SSS.Status],sssGroup|sssSegment,.85: ; Omit extern pseudosegments.
           BufferStore$ [%MsgBuffer],=B'|        '
           JNSt [ESI+SSS.Status],sssSegment,.58:
           BufferStoreWord [%MsgBuffer],'  '        ; Indent segment by two spaces against its group.
    .58:   BufferStoreByte [%MsgBuffer],'['
           BufferStore [%MsgBuffer],[ESI+SSS.NamePtr],[ESI+SSS.NameSize]
           BufferStore$ [%MsgBuffer],=B'],FA='      ; Display File Address.
           MOV EAX,[ESI+SSS.BottomFA]
           LEA EDI,[%Number]
           MOV EDX,EDI
           StoH EDI,Size=8
           SUB EDI,EDX
           BufferStore [%MsgBuffer],EDX,EDI
           MOV EBX,[%Pgm]
           MOV EAX,[EBX+PGM.Pgmopt.Status]
           AND AL,pgmoptFormatMask
    .VA?RVA:PROC                                     ; Create namespace for dispatching of local .pgmopt* labels.
            Dispatch AL,pgmoptBIN,pgmoptBOOT,pgmoptPE,pgmoptDLL,pgmoptELFX ; Program formats which link at absolute VA rather than RVA.
            BufferStore$ [%MsgBuffer],=B'h,RVA='                           ; Other formats link at RVA relative to ImageBase.
            JMPS PgmListMap.63:
    .pgmoptBIN:
    .pgmoptBOOT:
    .pgmoptPE:
    .pgmoptDLL:
    .pgmoptELFX:
           ENDP .VA?RVA:
           BufferStore$ [%MsgBuffer],=B'h,VA='
    .63:   LEA EDI,[%Number]
           MOV EDX,[ESI+SSS.BottomHigh]
           MOV EAX,[ESI+SSS.BottomLow]
           TEST EDX
           MOV EDX,EDI
           JZ .65:                                   ; VA is displayed as 32bit or 64bit.
           XCHG EAX,ECX                              ; Temporary save EAX to ECX.
             MOV EAX,[ESI+SSS.BottomHigh]
             StoH EDI,Size=8
           XCHG ECX,EAX
    .65:   StoH EDI,Size=8
           SUB EDI,EDX
           BufferStore [%MsgBuffer],EDX,EDI
           BufferStore$ [%MsgBuffer],=B'h,size='
           LEA EDI,[%Number]
           MOV EAX,[ESI+SSS.TopLow]
           MOV EDX,[ESI+SSS.TopHigh]
           SUB EAX,[ESI+SSS.BottomLow]
           SBB EDX,[ESI+SSS.BottomHigh]
           LEA EDI,[%Number]
           MOV EDX,EDI
           StoH EDI,Size=8
           SUB EDI,EDX
           PUSH EAX
             BufferStore [%MsgBuffer],EDX,EDI
             BufferStoreWord [%MsgBuffer],'h='
           POP EAX
           LEA EDI,[%Number]
           MOV EDX,EDI
           StoD EDI
           SUB EDI,EDX
           BufferStore [%MsgBuffer],EDX,EDI
    .67:   JNSt [ESI+SSS.Status],sssGroup,.79:
           BufferStore$ [%MsgBuffer],=B',group'     ; List the names of all group members.
           PUSH ECX,ESI
             MOV EDI,ESI                            ; Enumerate names of all segments which belong to the group EDI.
             MOV EBX,[%Pgm]
             BufferRetrieve [EBX+PGM.SegOrdBuffer]
             SHR ECX,2
    .70:     LODSD
             MOV EBX,EAX
             JNSt [EBX+SSS.Status],sssSegment,.76:
             CMP EDI,[EBX+SSS.GroupPtr]
             JNE .76:
    .73:     BufferStoreWord [%MsgBuffer],' ['
             BufferStore [%MsgBuffer],[EBX+SSS.NamePtr],[EBX+SSS.NameSize]
             BufferStoreByte [%MsgBuffer],']'
    .76:     LOOP .70:
           POP ESI,ECX
           JMP .82:
    .79:   BufferStore$ [%MsgBuffer],=B',width='
           MOV EBX,[ESI+SSS.Status]
           MOV EAX,sssWidthMask
           AND EAX,EBX
           LEA EDI,[%Number]
           MOV EDX,EDI
           SHR EAX,20
           StoD EDI
           SUB EDI,EDX
           BufferStore [%MsgBuffer],EDX,EDI
           BufferStore$ [%MsgBuffer],=B',align='
           MOV EAX,[ESI+SSS.Alignment]
           MOV EDI,EDX
           CMP EAX,8
           JNA .80:                                  ; Stronger alignment than 8 display as hexa.
           StoH EDI,Size=4
           MOV AL,'h'
           STOSB
           JMPS .81:
    .80:   StoD EDI
    .81:   SUB EDI,EDX
           BufferStore [%MsgBuffer],EDX,EDI
           BufferStore$ [%MsgBuffer],=B',purpose='
           Invoke EaBufferReserve::,PgmListMap
           Invoke SssPurposeToText::,[ESI+SSS.Purpose],EAX
           BufferRetrieve EAX
           DEC ECX                                   ; Omit zero terminator.
           BufferStore [%MsgBuffer],ESI,ECX
           Invoke EaBufferRelease::,EAX
    .82:   BufferStoreWord [%MsgBuffer],0x0A0D
    .85: POP ESI,ECX
    .88: DEC ECX
         JNZ .52:                                   ; The next group/segment.
    .90: EndProcedure PgmListMap

↑ PgmListGlobals Pgm, StmPtr
  PgmListGlobals appends aplhabetical list of global symbols to listing, below `ENDPROGRAM` statement and below ListMap.
  It is invoked only when `PROGRAM LISTGLOBALS=ON`.
Input
  Pgm is pointer to PGM - completely assembled, linked and formated program.
  StmPtr is pointer to STM object with ENDPROGRAM statement.
Output
  -
Error
  -
See also
  PgmListMap.
Invokes
  EaBufferRelease EaBufferReserve EaBufferSort SymFrameAddress
Invoked by
  PgmDestroy

    PgmListGlobals Procedure Pgm, StmPtr
    MsgBuffer LocalVar ; Local copy of statement's MsgBuffer borrowed to display the list of symbols.
    InpBuffer LocalVar ; Temporary buffer with pointers to global symbols.
    OutBuffer LocalVar ; Temporary buffer with pointers to global symbols.
    CountGlb  LocalVar ; Counter of global symbols.
    CountPub  LocalVar ; Counter of public symbols.
    CountExt  LocalVar ; Counter of extern symbols.
    CountExp  LocalVar ; Counter of export symbols.
    CountImp  LocalVar ; Counter of import symbols.
    Number    LocalVar Size=12 ; Room for decadic format of counters.
         ClearLocalVar
         MOV ESI,[%StmPtr]
         MOV EBX,[%Pgm]
         MOV EAX,[ESI+STM.MsgBuffer]
         MOV [%MsgBuffer],EAX
         JNSt [EBX+PGM.Status],pgmEnvelope,.10:
         JNSt [EBX+PGM.Status],pgmEnvelDirty,.90:    ; Do not display header when source envelope was not used.
    .10: BufferStore$ [%MsgBuffer],=B'|        **** ListGlobals "' ; Display the header.
         BufferStore [%MsgBuffer],[EBX+PGM.Pgmopt.OutFilePtr],[EBX+PGM.Pgmopt.OutFileSize]
         BufferStore$ [%MsgBuffer],=B'",Global='
         Invoke EaBufferReserve::,PgmListGlobals
         MOV [%InpBuffer],EAX
         Invoke EaBufferReserve::,PgmListGlobals
         MOV [%OutBuffer],EAX
          ; Step 1: store pointers to all unresolved symbols with nonstandard (global) scope to input buffer in the loop .13: .. .16:
         ListGetFirst [EBX+PGM.SymList]
         JZ .40:
    .13: JSt [EAX+SYM.Status],symResolved,.16:
         JNSt [EAX+SYM.Status],symScopeMask,.16:     ; Skip standard private symbols.
         BufferStoreDword [%InpBuffer],EAX
    .16: ListGetNext EAX
         JNZ .13:
         ; Step 2: Sort global symbols alphabeticaly by name.
         Invoke EaBufferSort::,[%InpBuffer]
         ; Step 3: merge public and extern/import symbols with the same name (they may have been left unresolved),
         ;    count and copy them to the output buffer in the loop .20: .. .43:.
         BufferRetrieve [%InpBuffer]
         LEA EDX,[ESI+ECX]                           ; End of pointer array.
    .20: CMP ESI,EDX
         JNB .40:
         LODSD
         CMP ESI,EDX
         JNB .26:
         MOV EBX,[ESI]                               ; EAX and EBX are now pointers to neighbour symbols, perhaps with the same name.
         Compare [EAX+SYM.NamePtr],[EAX+SYM.NameSize],[EBX+SYM.NamePtr],[EBX+SYM.NameSize]
         JNE .26:
         ; Both symbols EAX and EBX have the same name. Let's find which is the public|export one.
         ADD ESI,4 ; Skip the 2nd pointer because they will be merged.
         JSt [EAX+SYM.Status],symPublic|symExport,.23:
         XCHG EAX,EBX
    .23: MOV EBX,[EBX+SYM.Status]                    ; EAX is public|export, EBX is extern|import.
         AND EBX,symImport+symExtern
         SetSt EBX,symGlobal
         SetSt [EAX+SYM.Status],EBX                  ; Symbol EAX will be listed as global, too.
    .26: BufferStoreDword [%OutBuffer],EAX
    .30: ; Update global/public/extern/export/import counter.
         MOV EBX,[EAX+SYM.Status]
         LEA ECX,[%CountExp]
         JSt EBX,symExport,.36:                      ; Count as 'X'.
         LEA ECX,[%CountImp]
         JSt EBX,symImport,.36:                      ; Count as 'I'.
         LEA ECX,[%CountPub]
         JNSt EBX,symPublic,.32:
         JNSt EBX,symExtern,.36:                     ; Count as 'P' if not external, too.
         JMP .34:                                    ; Count as 'G', i.e. public and external.
    .32: LEA ECX,[%CountExt]
         JSt EBX,symExtern,.36:                      ; Count as 'E'.
    .34: LEA ECX,[%CountGlb]
    .36: INCD [ECX]                                  ; Increment one of five counters.
         JMP .20:                                    ; The next symbol.
    .40: MOV EAX,[%CountGlb]
         LEA EDI,[%Number]
         MOV EDX,EDI
         StoD EDI
         SUB EDI,EDX
         BufferStore [%MsgBuffer],EDX,EDI
         BufferStore$ [%MsgBuffer],=B',Public='
         MOV EAX,[%CountPub]
         LEA EDI,[%Number]
         StoD EDI
         SUB EDI,EDX
         BufferStore [%MsgBuffer],EDX,EDI
         BufferStore$ [%MsgBuffer],=B',Extern='
         MOV EAX,[%CountExt]
         LEA EDI,[%Number]
         StoD EDI
         SUB EDI,EDX
         BufferStore [%MsgBuffer],EDX,EDI
         BufferStore$ [%MsgBuffer],=B',eXport='
         MOV EAX,[%CountExp]
         LEA EDI,[%Number]
         StoD EDI
         SUB EDI,EDX
         BufferStore [%MsgBuffer],EDX,EDI
         BufferStore$ [%MsgBuffer],=B',Import='
         MOV EAX,[%CountImp]
         LEA EDI,[%Number]
         StoD EDI
         SUB EDI,EDX
         BufferStore [%MsgBuffer],EDX,EDI
         BufferStoreWord [%MsgBuffer],0x0A0D
          ; List symbols, sorted by name.
         BufferRetrieve [%OutBuffer]
         SAR ECX,2
         JZ .80:
    .43: LODSD ; List properties of global symbol EAX.
         PUSH ECX,ESI
           MOV EBX,EAX
           BufferStore$ [%MsgBuffer],=B'|        '
           BufferStore [%MsgBuffer],[EBX+SYM.NamePtr],[EBX+SYM.NameSize]
           JSt [EBX+SYM.Status],symForwarded,.68:
           JNSt [EBX+SYM.Status],symImport,.53:
           JNSt [EBX+SYM.Status],symImportedByOrd,.46:
           ; Symbol imported by ordinal number will be listed as Name(OrdinalNr).
           BufferStoreByte [%MsgBuffer],'('          ; List ordinal number in parenthesis.
           MOV EAX,[EBX+SYM.OrdinalNr]
           LEA EDI,[%Number]
           MOV EDX,EDI
           StoD EDI
           SUB EDI,EDX
           BufferStore [%MsgBuffer],EDX,EDI
           JMP .50:
    .46:   MOV ECX,[EBX+SYM.InterNameSize]
           JECXZ .53:                                ; If internal name is empty, do not display it.
           ; Symbol imported by name will be listed as Name{InterName} if Name<>InterName.
           Compare [EBX+SYM.NamePtr],[EBX+SYM.NameSize],[EBX+SYM.InterNamePtr],ECX
           JE .53:                                   ; Do not display internal name when it is empty or the same.
           BufferStoreByte [%MsgBuffer],'{'          ; List internal name in curly brackets.
           BufferStore [%MsgBuffer],[EBX+SYM.InterNamePtr],[EBX+SYM.InterNameSize]
    .50:   BufferStoreByte [%MsgBuffer],'}'
    .53:   BufferStoreWord [%MsgBuffer],',['         ; List group name in brackets.
           Invoke SymFrameAddress::,EBX,[%Pgm]       ; Returns EDX:EAX=offset, ECX=^group.
           JECXZ .56:                                ; Scalar symbol has empty segment name.
           BufferStore [%MsgBuffer],[ECX+SSS.NamePtr],[ECX+SSS.NameSize]
    .56:   BufferStoreWord [%MsgBuffer],']:'
           LEA EDI,[%Number]
           MOV EDX,EDI
           StoH EDI,Size=8
           SUB EDI,EDX
           BufferStore [%MsgBuffer],EDX,EDI          ; List symbol offset.
           BufferStoreWord [%MsgBuffer],'h,'
           MOV EDX,[%Pgm]
           MOV EAX,[EDX+PGM.Pgmopt.Status]
           JNSt EAX,pgmoptExecutable|pgmoptImage,.70:
           AND AL,pgmoptFormatMask
           CMP AL,pgmoptBIN
           JE .60:
           CMP AL,pgmoptPE
           JE .60:
           CMP AL,pgmoptDLL
           JE .60:
           CMP AL,pgmoptELFX
           JE .60:
           BufferStoreByte [%MsgBuffer],'R'          ; Display VA= in images, RVA= in linkables.
    .60:   BufferStore$ [%MsgBuffer],=B'VA='
    .63:   SUB EAX,EAX
           MOV ECX,[EBX+SYM.Section]
           JECXZ .66:
           MOV ECX,[ECX+SSS.SegmPtr]
           JECXZ .66:
           MOV EAX,[ECX+SSS.BottomLow]
    .66:   ADD EAX,[EBX+SYM.OffsetLow]
           LEA EDI,[%Number]
           MOV EDX,EDI
           StoH EDI,Size=8
           SUB EDI,EDX
           BufferStore [%MsgBuffer],EDX,EDI          ; List symbol VA.
           BufferStoreByte [%MsgBuffer],'h'
    .68:   BufferStoreByte [%MsgBuffer],','
           ; Display scope=.
    .70:   BufferStore$ [%MsgBuffer],=B"scope='"
           MOV ECX,[EBX+SYM.Status]
           MOV AL,'X'
           JSt ECX,symExport,.76:
           MOV AL,'I'
           JSt ECX,symImport,.76:
           MOV AL,'P'
           JNSt ECX,symPublic,.72:
           JNSt ECX,symExtern,.76:
           JMP .74:
    .72:   MOV AL,'E'
           JSt ECX,symExtern,.76:
    .74:   MOV AL,'G'
    .76:   MOV AH,"'"
           BufferStoreWord [%MsgBuffer],EAX
           ; Display lib= of imported and forwarded symbols.
           JNSt [EBX+SYM.Status],symImport|symForwarded,.78:
           BufferStore$ [%MsgBuffer],=B',lib="'
           MOV ECX,[EBX+SYM.DllNameSize]
           MOV ESI,[EBX+SYM.DllNamePtr]
           TEST ECX
           JNZ .77:
           MOV ESI,=B"%EuroasmDefaultDllName"
           MOV ECX,%EuroasmDefaultDllNameSize
    .77:   BufferStore [%MsgBuffer],ESI,ECX
           BufferStoreByte [%MsgBuffer],'"'
    .78:   ; Display fwd= of forwarded symbols.
           JNSt [EBX+SYM.Status],symForwarded,.79:
           BufferStore$ [%MsgBuffer],=B',fwd='
           BufferStore [%MsgBuffer],[EBX+SYM.InterNamePtr],[EBX+SYM.InterNameSize]
    .79:   BufferStoreWord [%MsgBuffer],0x0A0D
         POP ESI,ECX
         DEC ECX
         JNZ .43:                                   ; The next global symbol.
    .80: Invoke EaBufferRelease::,[%OutBuffer]
         Invoke EaBufferRelease::,[%InpBuffer]
    .90:EndProcedure PgmListGlobals

↑ PgmEvalEntry PgmPtr
  PgmEvalEntry evaluates program option ENTRY= which may be specified as an expression of symbol name, immediate offset value, immediate far address.
  Evaluated expression is stored to Pgm.EntryExp. If program entry is not specified, the output expression is left as is (clear).
Input
  PgmPtr is a pointer to the program PGM.
Output
  `Program.EntryExp` is evaluated.
Error
  Evaluation error is signalized with Pgm.EntryExp.Status='#'.
See also
  ExpReportError
Invokes
  ExpEval ExpReportError
Invoked by
  PfbootCompile PfcomCompile PfmzCompile PgmGetEntryAddress

    PgmEvalEntry Procedure PgmPtr
         MOV EBX,[%PgmPtr]
         LEA EDI,[EBX+PGM.EntryExp]
         MOV ECX,[EBX+PGM.Pgmopt.EntrySize]
         MOV ESI,[EBX+PGM.Pgmopt.EntryPtr]
         StripSpaces ESI,ECX
         JECXZ .90:                            ; If the program option ENTRY= is empty, leave PGM.EntryExp empty as well.
         Invoke ExpEval::,EDI,ESI,ECX,[EBX+PGM.CurrentStm]
         Invoke ExpReportError::,EDI
         JC .90:
         MOV ECX,[EDI+EXP.Sym]
         JECXZ .90:
         SetSt [ECX+SYM.Status],symEntry+symGlobal+symUsed+symProc ; If the ENTRY= referred some symbol, set it a global scope.
         MOV EAX,[ECX+SYM.SymbPtr]
         TEST EAX
         JZ .30:
         MOV ECX,EAX
    .30: MOV EAX,[ECX+SYM.Section]
         JNSt [EAX+SSS.Status],sssLinked,.50:
         MOV EDX,[EAX+SSS.BottomHigh]
         MOV EAX,[EAX+SSS.BottomLow]
         ADD EAX,[ECX+SYM.OffsetLow]
         ADC EDX,[ECX+SYM.OffsetHigh]
         MOV [EDI+EXP.Low],EAX
         MOV [EDI+EXP.High],EDX
         MOVD [EDI+EXP.Seg],0
         MOVB [EDI+EXP.Status],'N'             ; When the entry symbol is in linked segment, change address symbol to a numeric one.
    .50: JNSt [ECX+SYM.Status],symExtern,.90:
         MOV EDX,[ECX+SYM.Section]             ; Symbol ECX is extern, try to resolve it.
         MOV ECX,[EDX+SSS.SymPtr]
         JECXZ .90:
         MOV [EDI+EXP.Sym],ECX                 ; Replace the extern symbol with its homonymous public symbol.
         SetSt [ECX+SYM.Status],symEntry+symGlobal+symUsed+symProc ; If the ENTRY= referred some symbol, set it a global scope.
    .90:EndProcedure PgmEvalEntry

↑ PgmGetEntryAddress PgmPtr
  PgmGetEntryAddress returns virtual address of program entry point or error when program format was executable and `PROGRAM ENTRY=` was empty or if it evaluated with error.
Input
  PgmPtr is a pointer to PGM whose `PGM.EntryExp` contains `PROGRAM ENTRY=` parameter evaluated by PgmEvalEntry.
Output
  CF=0
  EDX:EAX= VA of entry point.
Error
  CF=1,
  EDX:EAX=0,
  Errors E7710, E7711 are reported with macro Msg.
Invoked by
  PfelfEhdr
Invokes
  PgmEvalEntry

    PgmGetEntryAddress Procedure PgmPtr
         MOV EBX,[%PgmPtr]
         XOR EAX,EAX
         XOR EDX,EDX
         CMP [EBX+PGM.Pgmopt.EntrySize],EAX
         JNZ .Ent:
         JNSt [EBX+PGM.Pgmopt.Status],pgmoptExecutable,.End:
         JSt [EBX+PGM.Status],pgmEntryReported,.Err:
         Msg '7710'                                  ; "PROGRAM ENTRY=" is mandatory in this program format.
         SetSt [EBX+PGM.Status],pgmEntryReported
         JMP .Err:                                   ; PROGRAM ENTRY="!1S" symbol was not found.
    .Ent:Invoke PgmEvalEntry,EBX
         LEA ESI,[EBX+PGM.EntryExp]
         MOV ECX,[ESI+EXP.Status]
         Dispatch CL,'A','N'
         JSt [EBX+PGM.Status],pgmEntryReported,.Err:
         LEA ECX,[EBX+PGM.Pgmopt.EntryPtr]
         Msg '7711',ECX                              ; PROGRAM ENTRY="!1S" symbol was not found.
         SetSt [EBX+PGM.Status],pgmEntryReported
    .Err:STC                                         ; Other ENTRY evaluated types than number or symbol are wrong.
         JMP .End:                                   ; Entry was evaluated to error status '#' (already reported) or other nonsense.
    .A:  MOV ECX,[ESI+EXP.Seg]
         JECXZ .End:
         ADD EAX,[ECX+SSS.BottomLow]
         ADC EDX,[ECX+SSS.BottomHigh]
    .N:  ADD EAX,[ESI+EXP.Low]
         ADC EDX,[ESI+EXP.High]
    .End:MOV [%ReturnEAX],EAX
         MOV [%ReturnEDX],EDX
        EndProcedure PgmGetEntryAddress

↑ PgmCombine BasePgm

  PgmCombine will merge linkable programs (modules) which were loaded from disk file(s) by PfLoad as a result of pseudoinstruction LINK . Pointers to those modules has been already stored on `Pgm.ModulePgmList`.

  This procedure combines only modules marked as `pgmSelected` in their PGM.Status by PgmSelectModules (smart linking).

  Pointers to combined modules are left on `Pgm.ModulePgmList` but the emitted contents of homonymous public segments is merged to BasePgm.

Input
  BasePgm Pointer to the main (base) program which is already assembled and which the modules are linked to.
  BasePgm may be empty (no segments, no symbols) when the program is only a linker-script.
Output
  Selected loaded programs (modules) from `BasePgm.ModulePgmList` are combined with the BasePgm.
Error
  Errors are reported with macro Msg.
Invoked by
  PfOutput
Invokes
  PgmResizeGroups RelocCombine SssCombine SssResizeGroup SssResolve SymCombine

    PgmCombine Procedure BasePgm
        MOV EBX,[%BasePgm]
        ; Step 1: Merge SSS externs, segments and groups from loaded modules to BasePgm, including their emited contents and relocations.
        ; Bottom and Top of merged public segment is elevated, but symbols and relocations are not copied yet to the BasePgm.
        ListGetFirst [EBX+PGM.ModulePgmList]
        JZ .90:                                      ; Do nothing when no module is linked to the BasePgm.
    .10:MOV EDI,EAX                                  ; EDI is now one of the modules which were loaded by LINK statement.
        JNSt [EDI+PGM.Status],pgmSelected,.18:       ; Skip the unused library module (smart linking).
        ListGetFirst [EDI+PGM.SssList]               ; EAX is now one of the SSS objects of linked module.
        JZ .18:
    .14:Invoke SssCombine::,EAX,EBX                  ; Combine linked SSS object EAX to the main program EBX (also merge its Emit&RelocBuffer unchanged).
    .16:ListGetNext EAX
        JNZ .14:                                     ; The next SSS object of linked module EDI.
    .18:ListGetNext EDI                              ; The next linked module.
        JNZ .10:
        Invoke SssResolve::,EBX                      ; Update references SSS.SegmPtr, SSS.GroupPtr, SSS.SymPtr  in all SSS objects which were combined to BasePgm.
        ListGetFirst [EBX+PGM.ModulePgmList]
    .38:MOV EDI,EAX                                  ; EDI points to the combined module.
        JNSt [EDI+PGM.Status],pgmSelected,.45:       ; Skip the unused library module (smart linking).
        ListGetFirst [EDI+PGM.SymList]
        JZ .45:
    .40:Invoke SymCombine::,EAX,EBX                  ; Reassign symbols from linked modules to BasePgm, elevate their offsets.
        ListGetNext EAX                              ; The next symbol.
        JNZ .40:
    .45:ListGetNext EDI                              ; The next linked module.
        JNZ .38:

        ; Step 2: update relocations in all segments of the base program EBX.
        ListGetFirst [EBX+PGM.SssList]               ; Resolve relocations from base program.
        JZ .36:
    .20:BufferRetrieve [EAX+SSS.RelocBuffer]         ; Relocations from linked modules were copied here verbatim.
        JC .32:
        ADD ECX,ESI                                  ; ECX is now the end of array of RELOC records.
    .24:CMP ESI,ECX
        JNB .32:
        JSt [ESI+RELOC.Status],relocIgnore,.28:
        Invoke RelocCombine::,ESI,EBX
    .28:ADD ESI,SIZE# RELOC
        JMP .24:                                     ; The next RELOC record.
    .32:ListGetNext EAX                              ; The next segment.
        JNZ .20:
    .36:
        ; Update virtual addresses of all groups combined in BasePgm.
        ListGetFirst [EBX+PGM.SssList]
        JZ .60:
    .50:JNSt [EAX+SSS.Status],sssGroup, .55:
        Invoke SssResizeGroup::,EAX,EBX
    .55:ListGetNext EAX                              ; The next SSS.
        JNZ.50:
    .60:; Step 4: combine entry point from linked modules (if any)  in its unevaluated source form, and copy it to the BasePgm.
        ListGetFirst [EBX+PGM.ModulePgmList]
    .65:MOV EDI,EAX                                  ; EDI points to the linked module.
        JNSt [EDI+PGM.Status],pgmSelected,.75:       ; Skip the unused library module (smart linking).
        MOV ECX,[EDI+PGM.Pgmopt.EntrySize]
        TEST ECX
        JZ .75:                                      ; Skip when the module EDI has no entry point in source form.
        MOV EAX,[EBX+PGM.Pgmopt.EntrySize]
        MOV ESI,[EDI+PGM.Pgmopt.EntryPtr]
        StripColons ESI,ECX
        TEST EAX                                     ; Check if ENTRY= was already specified in the base program EBX.
        JZ .70:                                      ; Skip when the base pgm EBX has vacant entry.
        MOV EDX,[EBX+PGM.Pgmopt.EntryPtr]
        StripColons EDX,EAX
        Compare ESI,ECX,EDX,EAX
        JE .75:                                      ; Do nothing when ENTRY= in module EDI is identical with ENTRY= in the base program EBX.
    .W3925:
        LEA ECX,[EDI+PGM.Pgmopt.EntryPtr]
        LEA EDX,[EBX+PGM.Pgmopt.EntryPtr]
        Msg '3925',ECX,EDI,EDX,EBX                   ; Entry "!1S" specified in linkable module "!2S" collides with the entry "!3S"  in "!4S". Ignored.
        JMPS .75:
    .70:MOV [EBX+PGM.Pgmopt.EntryPtr],ESI            ; Copy the entry point when it wasn't specified in the base program yet.
        MOVW [EBX+ECX+PGM.Pgmopt.EntryPtr],'::'      ; Append two colons to entry name
        INC ECX,ECX
        MOV [EBX+PGM.Pgmopt.EntrySize],ECX
    .75:ListGetNext EDI                              ; The next linked module.
        JNZ .65:
    ;;    ; Step 5: Resolve symbols from loaded modules.
    .80:MOV [EBX+PGM.ModulePgmList],0                ; Invalidate the list of combined modules.
    .90:Invoke PgmResizeGroups,EBX
       EndProcedure PgmCombine

↑ PgmDetectImportModule Pgm

  PgmDetectImportModule tries to detect if the Pgm (just loaded module) is a module from import library, i.e. if it contains import info only, and it will set the flag `Pgm.Status:pgmImpLibMember` in this case.

  Module is pure import library member if

  1. Pgm has not more than 3 symbols,
  2. Pgm has exactly one segment,
  3. the segment name contains the word 'IMPORT' or 'IDATA' (case insensitive),
  4. emitted segment data are 6 or 7 bytes long (indirect proxy JMPN).

  > Import libraries might have been created by a third-party librarian, such as ALIB.exe, The only code they have is an indirect proxy jump through IAT table to dynamically loaded function, and a public symbol referring to this proxy jump. Imported module may also contain an extern symbol with decorated internal name.

Input
  Pgm Pointer to a loaded module PGM.
Output
  pgmImpLibMember flag is modified in `Pgm.Status`.
Error
  -
See also
  PfDetect
Invoked by
  PfomfLoadModule
Invokes
  EaBufferRelease EaBufferReserve

    PgmDetectImportModule Procedure Pgm
        MOV EBX,[%Pgm]
        RstSt [EBX+PGM.Status],pgmImpLibMember       ; First assume it is an ordinary object module.
        SUB ECX,ECX                                  ; Count symbols in module EBX into counter ECX.
        ListGetFirst [EBX+PGM.SymList]
        JZ .90:                                      ; No symbols exits, its not an import library member.
    .10:INC ECX
        ListGetNext EAX
        JNZ .10:
        CMP ECX,3
        JA .90:                                      ; Too many symbols, not an import library member.
        SUB ECX,ECX                                  ; Count segments in module EBX into counter ECX.
        ListGetFirst [EBX+PGM.SssList]
        JZ .90:                                      ; No segments exist, its not an import library member.
    .20:JNSt [EAX+SSS.Status],sssSegment,.30:
        INC ECX
        MOV EDX,EAX                                  ; Remember the segment in EDX.
    .30:ListGetNext EAX
        JNZ .20:
        CMP ECX,1
        JNE .90:                                     ; Too many segments, its not an import library member.
        MOV ECX,[EDX+SSS.NameSize]                   ; EDX is the one and only segment.
        MOV ESI,[EDX+SSS.NamePtr]                    ; Convert the segment name ESI,ECX to uppercase.
        CMP ECX,5
        JB .90:                                      ; Segment name is too short, not IDATA/IMPORT.
        Invoke EaBufferReserve::,PgmDetectImportModule
        MOV EBX,EAX
        BufferStore EBX,ESI,ECX
        BufferRetrieve EBX
        MOV EDI,ESI
    .40:LODSB                                        ; Convert the letter to upper case.
        CMP AL,'a'
        JB .50:
        CMP AL,'z'
        JA .50:
        SUB AL,'a'-'A'
    .50:STOSB
        LOOP .40:
        BufferRetrieve EBX                           ; ESI,ECX is now uppercased segment name.
        Invoke EaBufferRelease::,EBX
        MOV EDI,ESI                                  ; Search the name for 'IMPORT' or 'IDATA'.
        MOV AL,'I'
    .60:REPNE SCASB
        JNE .90:
        CMP ECX,4
        JB .90:
        CMPD [EDI],'DATA'
        JE .70:
        CMP ECX,5
        JB .90:
        CMPB [EDI],'M'
        JNE .60:
        CMPD [EDI+1],'PORT'
        JNE .60:
    .70:BufferRetrieve [EDX+SSS.EmitBuffer]
        CMP ECX,7
        JA .90:
        CMP CL,6
        JB .90:
        MOV EBX,[%Pgm]                               ; Segment name contains 'IMPORT' or 'IDATA'
        SetSt [EBX+PGM.Status],pgmImpLibMember       ;   and its code is 6..7 bytes long. Module is an import library member.
    .90:EndProcedure PgmDetectImportModule

↑ PgmCheckDirty Pgm
  PgmCheckDirty inspects the program if something was emitted or linked to it. Program is dirty when at least one its section is dirty or when Pgm.LinkFilesNr is nonzero.
Input
  Pgm Pointer to PGM of the checked program.
Output
  CF=0 if empty, 1 if dirty (some code, data or label was emitted).
Error
  -
See also
  SssCheckDirty.
Invokes
  SssCheckDirty
Invoked by
  PgmDestroy PseudoENDPROGRAM

    PgmCheckDirty Procedure Pgm
         MOV EBX,[%Pgm]
          MOV ECX,[EBX+PGM.LinkFilesNr]
         JECXZ .10:
         STC
         JMP .90: ; Dirty.
     .10:ListGetFirst [EBX+PGM.SssList]
         JZ .NotDirty:
     .20:Invoke SssCheckDirty::,EAX,EBX
         JC .90: ; Dirty
         ListGetNext EAX
         JNZ .20:
     .NotDirty:
         CLC
    .90: EndProcedure PgmCheckDirty

↑ PgmSelectModules Program

  PgmSelectModules provides smart linking of modules to executable file. It will inspect each module loaded on `Program.ModulePgmList` whether some of its public|export symbols is referenced by some extern|import symbol defined in the base program or in other non-import modules, and vice versa. Such module will be marked `pgmSelected` for later combining. Unreferenced modules from libraries will not be combined and linked.

  When the base Program output format is a library (FORMAT=LIBOMF|LIBCOF|ELFSO) instead of executable, all its loaded modules will be marked `pgmSelected` regardless if they are referenced or not (perhaps they will be referenced later, when the base Program (library) would get linked to an executable format).

  When any of the linked programs is a standalone module (FORMAT=COFF|OMF|ELF) and not a library member (`pgmoptLibMember=0`), it will be marked `pgmSelected` regardless if its symbols are referenced or not (because it was explicitly required with `LINK module.obj`).

Input
  Program is pointer to the compiled base PGM program, to which it is linked.
Output
  Referenced loaded modules are marked with flag `PGM.Status:pgmSelected`.
Error
  -
Invokes
  EaBufferRelease EaBufferReserve PgmStoreGlobalSymbols
Invoked by
  PfOutput

    PgmSelectModules Procedure Program
    PubBuf LocalVar ; Pointer to a buffer which contains DWORD pointers to all public|export symbols.
    ExtBuf LocalVar ; Pointer to a buffer which contains DWORD pointers to all extern|import symbols.
        Invoke EaBufferReserve::,PgmSelectModules
        MOV [%PubBuf],EAX
        MOV ECX,EAX
        Invoke EaBufferReserve::,PgmSelectModules
        MOV [%ExtBuf],EAX
        MOV EDX,EAX
        MOV EBX,[%Program]
        SetSt [EBX+PGM.Status],pgmSelected           ; The base program is always selected.
        JNSt [EBX+PGM.Pgmopt.Status],pgmoptLibrary,.14: ; Skip when the base program is not a library.
        ListGetFirst [EBX+PGM.ModulePgmList]         ; Mark all modules as pgmSelected when a library is being compiled.
        JZ .90:                                      ; Nothing to do when no module was loaded.
    .10:SetSt [EAX+PGM.Status],pgmSelected
        ListGetNext EAX
        JNZ .10:
        JMP .90:
    .14:; Collect pointers to global symbols into ExtBuf EDX and PubBuf ECX from all non-import-only modules.
        Invoke PgmStoreGlobalSymbols,EBX,ECX,EDX     ; Collect globals from the base program EBX.
        ListGetFirst [EBX+PGM.ModulePgmList]
        JZ .90:                                      ; Nothing to do when no module was loaded.
    .18:JSt [EAX+PGM.Status],pgmImpLibMember,.22:    ; Ignore pure import library members.
        Invoke PgmStoreGlobalSymbols,EAX,ECX,EDX     ; Collect globals from the loaded module EAX.
    .22:ListGetNext EAX
        JNZ .18:
    .26:; Inspect each module and flag the module as pgmSelected if
        ;    any of its public symbols is referenced by any symbol in ExtBuf, or
        ;    if the module is not a library (it is a standalone COFF|OMF|ELF file).
        ListGetFirst [EBX+PGM.ModulePgmList]
    .30:MOV EBX,EAX                                  ; EBX is now the inspected module.
        JSt [EBX+PGM.Status],pgmSelected,.54:        ; Skip inspection when its already referenced.
        JSt [EBX+PGM.Pgmopt.Status],pgmoptLibMember,.34:
        SetSt [EBX+PGM.Status],pgmSelected           ; Select loaded standalone COFF|OMF modules.
        JMP .54:
    .34:ListGetFirst [EBX+PGM.SymList]
        JZ .54:                                      ; Skip the module EBX when it has no symbols.
    .38:MOV EDI,EAX                                  ; EDI is now a symbol from module EBX.
        JNSt [EDI+SYM.Status],symPublic|symExport,.50:
        BufferRetrieve [%ExtBuf]                     ; EDI is public symbol in loaded module EBX. Find if it is referenced.
        SHR ECX,2
        JZ .58:                                      ; Skip when no external symbols exist.
    .42:LODSD                                        ; EAX is now one of external/imported symbols. Compare names of symbols EAX and EDI.
        Compare [EAX+SYM.NamePtr],[EAX+SYM.NameSize],[EDI+SYM.NamePtr],[EDI+SYM.NameSize]
        JNE .46:
        SetSt [EBX+PGM.Status],pgmSelected           ; The module was referenced, it will be combined and linked.
        JMP .54:                                     ; Break further inspecting of other symbols in module EBX.
    .46:LOOP .42:
    .50:ListGetNext EDI                              ; The next symbol.
        JNZ .38:
    .54:ListGetNext EBX
        JNZ .30:                                     ; The next loaded module.
    .58:; Collects pointers to global symbols  into ExtBuf EDX and PubBuf ECX again but this time  from referenced modules only.
        MOV EBX,[%Program]
        MOV ECX,[%PubBuf]
        MOV EDX,[%ExtBuf]
        BufferClear ECX
        BufferClear EDX
        Invoke PgmStoreGlobalSymbols,EBX,ECX,EDX     ; Collect globals from the base program.
        ListGetFirst [EBX+PGM.ModulePgmList]
    .62:JNSt [EAX+PGM.Status],pgmSelected,.66:       ; Skip not referenced modules.
        Invoke PgmStoreGlobalSymbols,EAX,ECX,EDX     ; Collect globals from the loaded module.
    .66:ListGetNext EAX
        JNZ .62:                                     ; The next module.
        ; Inspect each module and flags it as pgmSelected if any of its public|import symbols is referenced  by any symbol in ExtBuf.
        ListGetFirst [EBX+PGM.ModulePgmList]
    .70:MOV EBX,EAX                                  ; EBX is now the inspected module.
        ListGetFirst [EBX+PGM.SymList]
        JZ .88:                                      ; Skip the module EBX when it has no symbols.
    .74:MOV EDI,EAX                                  ; EDI is now a symbol from module EBX.
        JNSt [EDI+SYM.Status],symPublic|symImport,.86:
        BufferRetrieve [%ExtBuf]                     ; EDI is public|import symbol in loaded module EBX. Check if it is referenced.
        SHR ECX,2
        JZ .86:
    .78:LODSD                                        ; EAX is now one of external/imported symbols. Compare names of symbols EAX and EDI.
        Compare [EAX+SYM.NamePtr],[EAX+SYM.NameSize],[EDI+SYM.NamePtr],[EDI+SYM.NameSize]
        JNE .82:
        SetSt [EBX+PGM.Status],pgmSelected           ; The module was referenced, it will be combined and linked.
        JMP .88:                                     ; Break further inspecting of other symbols in module EBX.
    .82:LOOP .78:
    .86:ListGetNext EDI                              ; The next symbol.
        JNZ .74:
    .88: ListGetNext EBX                             ; The next loaded module.
         JNZ .70:
    .90: Invoke EaBufferRelease::,[%ExtBuf]
         Invoke EaBufferRelease::,[%PubBuf]
       EndProcedure PgmSelectModules

↑ PgmStoreGlobalSymbols PgmPtr, PubBuffer, ExtBuffer
  PgmStoreGlobalSymbols will store all extern|import symbols to ExtBuffer and all public|export symbols to PubBuffer.
Input
  PgmPtr is pointer to PGM whose symbols are investigated.
  PubBuffer is pointer to a BUFFER reserved by caller.
  ExtBuffer is pointer to a BUFFER reserved by caller.
Output
  Dword pointers to global symbols are stored to PubBuffer and ExtBuffer.
Error
  -
Invoked by
  PgmSelectModules

    PgmStoreGlobalSymbols Procedure PgmPtr,PubBuffer,ExtBuffer
         MOV EDI,[%PgmPtr]
         MOV ECX,[%PubBuffer]
         MOV EDX,[%ExtBuffer]
         ListGetFirst [EDI+PGM.SymList]
         JZ .90:
    .30: JNSt [EAX+SYM.Status],symExtern|symImport,.50:
         BufferStoreDword EDX,EAX
    .50: JNSt [EAX+SYM.Status],symPublic|symExport,.80:
         BufferStoreDword ECX,EAX
    .80: ListGetNext EAX ; The next symbol.
         JNZ .30:
    .90: EndProcedure PgmStoreGlobalSymbols

↑ PgmCreateImportModule BasePgmPtr, DllNamePtr, DllNameSize, OrdinalNr, SymStatus, SymNamePtr, SymNameSize, InterNamePtr, InterNameSize
  PgmCreateImportModule will create a new module in PGM format and store the module on `BasePgm.ModulePgmList`. The module is marked `pgmImpModule` and it contains only the imported symbol.
  Name of the module is SymName. All names may be volatile.
Input
  BasePgmPtr is pointer to PGM where is the module created.
  DllNamePtr is pointer to the name of DLL which the symbol is imported from.
  DllNameSize is the number of bytes in DllName.
  OrdinalNr is the ordinal number of the imported symbol.
  SymStatus is the SYM.Status of the imported symbol, usually `symImport+'A'`.
  SymNamePtr is pointer to the name of imported symbol.
  SymNameSize is the number of bytes in SymName.
  SymInterNamePtr is pointer to the internal (decorated) name of imported symbol.
  SymInterNameSize is the number of bytes in SymInterName. It may be 0 when SymInterName==SymName.
Output
  A new program was added on BasePgm.ModulePgmList.
Error
  -
Invokes
  SssCreateExtern
Invoked by
  PfdllLoadPgm PflibcofLoadImportModule PfomfLoadModule

    PgmCreateImportModule Procedure BasePgmPtr, DllNamePtr, DllNameSize, OrdinalNr, \
                                    SymStatus, SymNamePtr, SymNameSize, \
                                    SymInterNamePtr, SymInterNameSize
        MOV EBX,[%BasePgmPtr]
        MOV EDX,[EBX+PGM.Pool]
        ListNew [EBX+PGM.ModulePgmList],Zeroed=yes
        MOV EDI,EAX
        MOV [EDI+PGM.Pool],EDX     ; Let module's pool parasite on BasePgm.Pool.
        SetSt [EDI+PGM.Status],pgmImpLibMember
        MOV EAX,pgmoptWidthMask
        AND EAX,[EBX+PGM.Pgmopt.Status]
        OR  EAX,pgmoptLibMember
        SetSt [EDI+PGM.Pgmopt.Status],EAX
        ListCreate EDX,SIZE#SSS
        MOV [EDI+PGM.SssList],EAX
        ListCreate EDX,SIZE#SYM
        MOV [EDI+PGM.SymList],EAX
        ListNew EAX,Zeroed=yes
        MOV EBX,EAX ; EBX is now pointer to the imported symbol.
        MOV EAX,[%SymStatus]
        SetSt [EBX+SYM.Status],EAX
        MOV ESI,[%DllNamePtr]
        MOV ECX,[%DllNameSize]
        PoolStore EDX,ESI,ECX
        MOV [EBX+SYM.DllNamePtr],EAX
        MOV [EBX+SYM.DllNameSize],ECX
        MOV ESI,[%SymNamePtr]
        MOV ECX,[%SymNameSize]
        PoolStore EDX,ESI,ECX
        MOV [EDI+PGM.NamePtr],EAX
        MOV [EDI+PGM.NameSize],ECX
        MOV [EBX+SYM.NamePtr],EAX
        MOV [EBX+SYM.NameSize],ECX
        MOV [EBX+SYM.InterNamePtr],EAX
        MOV [EBX+SYM.InterNameSize],ECX
        MOV ESI,[%SymInterNamePtr]
        MOV ECX,[%SymInterNameSize]
        JECXZ .80:
        PoolStore EDX,ESI,ECX
        MOV [EBX+SYM.InterNamePtr],EAX
        MOV [EBX+SYM.InterNameSize],ECX
    .80:MOV EAX,[%OrdinalNr]
        MOV [EBX+SYM.OrdinalNr],EAX
        Invoke SssCreateExtern::,EBX,EDI
       EndProcedure PgmCreateImportModule

↑ PgmStreamImage Pgm, Stream

  PgmStreamImage stores the emitted contents of all initialized segments to the %Stream in the order specified on `Pgm.SegOrdBuffer` by PgmOrderSegments.
  All segments should be linked by PgmLink and their `SSS.BottomFA` set.
  Segment FileAddress will be used to stuff the stream by 0 bytes (or with NOP if both segments have `PURPOSE=CODE`).

Input
  Pgm Pointer to a completely assembled, combined, linked PGM with resolved relocations.
  Stream is pointer to an output STREAM File headers may be pre-stored to the stream.
Output
  Stream is filled with the file image.
Error
  F9341 Internal error: invalid linking
Invoked by
  PfbinCompile PfbootCompile PfcomCompile PfelfCompile PfelfsoCompile PfelfxCompile PfmzCompile

    PgmStreamImage Procedure Pgm, Stream
    Purpose    LocalVar ; Purpose of the current segment.
    SssPtr     LocalVar ; Pointer to the array of pointers to SSS.
    SssPtrTop  LocalVar ; Pointer behind the array of pointers to SSS.
        XOR EDX,EDX
        MOV EBX,[%Pgm]
        MOV [%Purpose],EDX
        BufferRetrieve [EBX+PGM.SegOrdBuffer]
        ADD ECX,ESI
        MOV [%SssPtr],ESI
        MOV [%SssPtrTop],ECX
        MOV EBX,[%Stream]
    .20:MOV ESI,[%SssPtr]
        CMP ESI,[%SssPtrTop]                    ; ESI points to an array of pointers.
        JNB .90:
        LODSD
        MOV [%SssPtr],ESI
        JNSt [EAX+SSS.Status],sssSegment,.20:
        JNSt [EAX+SSS.Status],sssNotBSS,.20:
    .28:                                        ; EAX is an emitting segment (CODE,RODATA,DATA) or special segment (SYMBOLS, STRINGS etc).
          MOV EDI,EAX                           ; EDI is the segment with initialized contents.
          MOV EAX,[EDI+SSS.Purpose]
          XOR EDX,EDX                           ; DL is alignment stuff 0x00 or 0x90.
          XCHG EAX,[%Purpose]
          JNSt EAX,sssPurposeCODE,.30:
          JNSt [%Purpose],sssPurposeCODE,.30:
          MOV DL,0x90                           ; Stuff with NOP when both EDI and the previous segment has purpose CODE.
    .30:  StreamGetSize EBX                     ; Load EAX with the number of already streamed bytes.
          MOV ECX,[EDI+SSS.BottomFA]
          SUB ECX,EAX
          JZ .50:
          JB .F9341:                            ; Incorrectly linked program, abort.
    .40:  StreamStoreByte EBX,DL                ; Intersection alignment stuff.
          LOOP .40:
    .50:  BufferRetrieve [EDI+SSS.EmitBuffer]
          StreamStore [%Stream],ESI,ECX
    .80:  JMP .20:                                ; The next segment.
    .F9341:Msg '9341',EAX                       ; Internal error: invalid linking at file offset !1Hh.
    .90:EndProcedure PgmStreamImage

↑ PgmResizeGroups Prog
  PgmResizeGroups recalculates virtual and file size of all groups in the Prog and in its loaded modules, considering all segments of the program which belong to this group.
  `Group.Bottom` will be the lowest bottom and `Group.Top` the highest top of all its segments.
  `Group.Bottom` will be 0 in program format COM, as the addressing frame respects PSP (256 bytes). Similarily in format BOOT.
  `Group.BottomFA` will be the lowest file bottom and `Group.TopFA` the highest file top of all its segments (this is used in ELFX format).
  PgmResizeGroups will also update group's `SSS.Status:sssNotBSS` flag. It is set when at least one its segment has initialized contents.
  It also updates group's purpose by purposes of all its segments.
Input
  Prog is pointer to PGM where the groups belongs.
Output
  Group.Bottom(FA) and Group.Top(FA) are recalculated, .Status and .Purpose updated..
Error
  Errors are reported with macro Msg.
Invoked by
  PfcoffCompile PfelfLink PfelfsoLink PfelfxLink PfomfStoreModule PgmCombine PgmLink
Invokes
  SssResizeGroup

    PgmResizeGroups Procedure Prog
        MOV EBX,[%Prog]
        ListGetFirst [EBX+PGM.SssList]          ; Resize groups in the base program.
        JZ .30:
        XOR ECX,ECX
    .10:JNSt [EAX+SSS.Status],sssGroup, .20:
        Invoke SssResizeGroup::,EAX,EBX
    .20:ListGetNext EAX
        JNZ .10:
    .30:ListGetFirst [EBX+PGM.ModulePgmList]    ; Resize groups in the loaded modules.
        JZ .90:
    .40:MOV EDI,EAX
        ListGetFirst [EDI+PGM.SssList]
        JZ .70:
    .50:JNSt [EAX+SSS.Status],sssGroup, .60:
        Invoke SssResizeGroup::,EAX,EBX
    .60:ListGetNext EAX
        JNZ .50:
    .70:ListGetNext EDI
        JNZ .40:
    .90:EndProcedure PgmResizeGroups

↑ PgmGroupByModel Program

  PgmGroupByModel will assign orphaned segment to groups in NEAR memory models.
  PgmGroupByModel is invoked when linkable modules have been already combined into this Program.

Input
  Program Pointer to PGM whose segments will be regrouped.
Output
  Each segment of the Program now belongs either to explicit or to implicitly created group.
Error
  -
See also
  SssCreateImplicit,
  PROGRAM MODEL=
Invoked by
  PfbinCompile PfbootCompile PfcomCompile PfmzCompile PfpeCompile
Invokes
  SssCreateGroup SssResizeGroup SymCreateSe

    PgmGroupByModel Procedure Program
        MOV EBX,[%Program]
        MOV EAX,[EBX+PGM.Pgmopt.Status]
        AND EAX,pgmoptModelMask
        Dispatch EAX,pgmoptFLAT,pgmoptSMALL,pgmoptMEDIUM,pgmoptCOMPACT,pgmoptLARGE,pgmoptHUGE
    .pgmopTINY:                                      ; Regroup all orphaned segments to one common group.
        MOV ECX,sssPurposeRegular
        CALL .Regroup:
        JMP .pgmoptFLAT:
    .Regroup:PROC1 ; Find or create a group with purpose ECX and assign nongrouped segments with purpose ECX to the group.
                   ; Input: EBX=^PGM, ECX=purpose
        ListGetFirst [EBX+PGM.SssList]
        JZ .R9:
        MOV EDX,6                                    ; Size of the new group name [?GROUP].
    .R1:JNSt [EAX+SSS.Status],sssGroup,.R2:
        JSt [EAX+SSS.Purpose],ECX,.R5:
    .R2:ListGetNext EAX
        JNZ .R1:
        CMP ECX,sssPurposeRegular
        JNE .R3:
        MOV ESI,=B"COMGROUP"
        INC EDX,EDX                                  ; Size of the new group name [COMGROUP].
        JMP .R4:
    .R3:Dispatch ECX,sssPurposeCODE,sssPurposeSTACK
        MOV ESI,=B"DGROUP"                            ; sssPurposeDATA|RODATA|BSS.
        JMPS .R4:
    .sssPurposeCODE:
        MOV ESI,=B"CGROUP"
        JMPS .R4:
    .sssPurposeSTACK:
        MOV ESI,=B"SGROUP"
    .R4:Invoke SssCreateGroup::,[EBX+PGM.CurrentStm],ESI,EDX,sssGroup+sssImplicit,ECX
        Invoke SymCreateSe::,EAX,EBX
    .R5:MOV EDI,EAX
        ListGetFirst [EBX+PGM.SssList]                ; EDI is the group with the purpose ECX.
    .R6:JNSt [EAX+SSS.Status],sssSegment,.R8:         ; Assign all ungrouped segments with the purpose ECX to the group EDI.
        JNSt [EAX+SSS.Purpose],ECX,.R8:
        MOV EDX,[EAX+SSS.GroupPtr]
        TEST EDX
        JNZ .R8:
        MOV [EAX+SSS.GroupPtr],EDI                    ; Assign ungrouped segment EAX to the group EDI.
    .R8:ListGetNext EAX
        JNZ .R6:
    .R9:RET
       ENDP1 .Regroup:
    .pgmoptSMALL:                                    ; All segments are NEAR.
    .pgmoptCOMPACT:                                  ; Code segments are NEAR, data segments are FAR.
        MOV ECX,sssPurposeCODE
        CALL .Regroup:
        JSt [EBX+PGM.Pgmopt.Status],pgmoptCOMPACT,.pgmoptLARGE:
    .pgmoptMEDIUM:                                   ; Data segments are NEAR, code segments are FAR.
        MOV ECX,sssPurposeDATA+sssPurposeRODATA+sssPurposeBSS
        CALL .Regroup:
    .pgmoptLARGE:                                    ; Code and data segments are FAR.
    .pgmoptHUGE:
        MOV ECX,sssPurposeSTACK                      ; Stack segments are NEAR in all models.
        CALL .Regroup                                ; Find or create [SGROUP] and assign ungrouped stack segments to it.
    .pgmoptFLAT:                                     ; Do not group any segments in FLAT model.
        ListGetFirst [EBX+PGM.SssList]               ; Finally, merge VA, FA, purpose and sssNotBSS status from all group's segments.
        JZ .90:
    .50:JNSt [EAX+SSS.Status],sssGroup,.70:
        Invoke SssResizeGroup::,EAX,EBX
    .70:ListGetNext EAX
        JNZ .50:
    .90:EndProcedure PgmGroupByModel

↑ PgmLinkSections Pgm
  PgmLinkSections is invoked at the end of program assembly when ENDPROGRAM is encountered in the last pass. It links sections to their base segments.
Input
  Pgm Pointer to PGM.
Output
  Each sections is linked to its base segment, its Bottom and Top are adjusted, its emitted contents copied to base segment, relocations fixed up and copied to base segment.
Error
  Errors are reported with macro Msg.
Invokes
  SssLinkSections SssOrderSections
Invoked by
  PseudoENDPROGRAM

    PgmLinkSections Procedure Pgm
        MOV EBX,[%Pgm]
        ListGetFirst [EBX+PGM.SssList]
        JZ .60:
    .10:JNSt [EAX+SSS.Status],sssSegment,.15:   ; Pass 1: link VA of sections, concatenate contents.
        Invoke SssOrderSections::,EAX,EBX
        Invoke SssLinkSections::, EAX
    .15:ListGetNext EAX
        JNZ .10:
        ListGetFirst [EBX+PGM.SssList]          ; Pass 2: update relocations in segments.
    .20:JNSt [EAX+SSS.Status],sssSegment,.50:
        BufferRetrieve [EAX+SSS.RelocBuffer]
        ADD ECX,ESI
    .30:CMP ESI,ECX
        JNB .50:                                ; When there are no more relocations in segment EAX.
        PUSH ECX
          MOV ECX,[ESI+RELOC.Section]
          MOV EDI,[ECX+SSS.BottomLow]
          MOV EDX,[ECX+SSS.BottomHigh]
          MOV ECX,[ECX+SSS.SegmPtr]
          JECXZ .35:
          SUB EDI,[ECX+SSS.BottomLow]
          SBB EDX,[ECX+SSS.BottomHigh]
          ADD [ESI+RELOC.OrgLow],EDI
          ADC [ESI+RELOC.OrgHigh],EDX
          MOV [ESI+RELOC.Section],ECX
    .35:  MOV ECX,[ESI+RELOC.Status]
          JSt ECX,relocIgnore,.40:
          AND ECX,relocTypeMask
          MOV ECX,[ESI+RELOC.Symbol]
          JECXZ .40:
          MOV ECX,[ECX+SYM.Section]
          JECXZ .40:
          MOV EDI,[ECX+SSS.BottomLow]
          MOV EDX,[ECX+SSS.BottomHigh]
          ADD [ESI+RELOC.AddendLow],EDI
          ADC [ESI+RELOC.AddendHigh],EDX
          MOV ECX,[ECX+SSS.SegmPtr]
          JECXZ .40:
          MOV ECX,[ECX+SSS.SymPtr]
          MOV [ESI+RELOC.Symbol],ECX
    .40:POP ECX
        ADD ESI,SIZE# RELOC
        JMP .30:
    .50:ListGetNext EAX
        JNZ .20:
    .60:ListGetFirst [EBX+PGM.SymList]               ; Update symbols.
        JZ .90:
    .70:MOV ECX,[EAX+SYM.Section]
        JECXZ .80:
        JNSt [ECX+SSS.Status],sssSection,.80:
        MOV EBX,[ECX+SSS.SegmPtr]
        MOV EDI,[ECX+SSS.BottomLow]
        MOV EDX,[ECX+SSS.BottomHigh]
        ADD [EAX+SYM.OffsetLow],EDI
        ADC [EAX+SYM.OffsetHigh],EDX
        MOV [EAX+SYM.Section],EBX
        JNSt [EAX+SYM.Status],symSe,.80:
        JSt [ECX+SSS.Status],sssSegment|sssGroup,.80:
        RstSt [EAX+SYM.Status],symSe                  ; Make symSe of sssSection an ordinary symPrivate.
    .80:ListGetNext EAX
        JNZ .70:
    .90:
       EndProcedure PgmLinkSections

↑ PgmSymResolve BasePgm
  Combining linked modules often ends with multiple symbols with the same name in BasePgm. PgmSymResolve will match unresolved external, exported and imported symbols with homonymous public or weak symbol which were combined to the BasePgm. Succesfully matched symbol is then marked with `PgmSymResolved` and its `SYM.SymbPtr` points to the matching public (or weak) symbol in BasePgm.
  PgmSymResolve does not create new symbols.
Input
  BasePgm is pointer to a PGM which it is linked to.
Output
  Symbols in `BasePgm.SymList` and symbols refered in relocations `RELOC.Symbol` are resolved.
Error
  -
Invokes
  SymResolve
Invoked by
  PfOutput PfelfCompile PfelfsoCompile PfpeCompile

    PgmSymResolve Procedure BasePgm
    RelocEnd LocalVar                 ; End of array of RELOC records.
         MOV EBX,[%BasePgm]
        ; Resolve symbols combined in BasePgm.
        ListGetFirst [EBX+PGM.SymList]
        JZ .90:
    .10:MOV EDI,EAX

        mov ecx,[edi+SYM.NamePtr]

    .15:Invoke SymResolve::,EDI,EBX       ; tady to mrzne, protoze LIST of SYMs je serazeny dle abecedy a cykli kolem dokola
    .20:ListGetNext EDI
        JNZ .10:                                     ; The next symbol in BasePgm.
    ; Resolve target symbols of relocations combined in BasePgm.
        ListGetFirst [EBX+PGM.SssList]
        JZ .90:
    .30:MOV EDX,EAX                                  ; EDX is a segment of BasePgm.
        BufferRetrieve [EDX+SSS.RelocBuffer]
        ADD ECX,ESI
        MOV [%RelocEnd],ECX
    .40:CMP ESI,[%RelocEnd]
        JNB .60:                                     ; Skip when there are no (more) relocations in segment EDX.
        JSt [ESI+RELOC.Status],relocResolved,.50:
        MOV EAX,[ESI+RELOC.Symbol]                   ; Resolve relocation target.
        TEST EAX
        JZ .50:
        MOV ECX,[EAX+SYM.SymbPtr]                    ; Is the symbol EAX is redirected to a public ECX?
        JECXZ .50:
        MOV [ESI+RELOC.Symbol],ECX
    .50:ADD ESI,SIZE# RELOC                          ; The next RELOC.
        JMP .40:
    .60:ListGetNext EDX
        JNZ .30:                                     ; The next segment.
    .90:EndProcedure PgmSymResolve

↑ PgmRelocResolve Program
  PgmRelocResolve will resolve all relocations in the Program.
  It is invoked when an executable program is combined and linked, external symbols resolved and virtual segment addresses fixed in image.
Input
  Program is pointer to PGM.
Output
  Data or code in SSS.EmitBuffer of all Program segments is modified: emited relocable Word/Dword/Fword pointed to by `RELOC.Org` is fixed up according to relocation type.
  Resolved relocations in `SSS.RelocBuffer` are then marked as relocResolved.
Error
  -
Invoked by
  PfOutput PfbinCompile PfbootCompile PfcomCompile PfelfxCompile PfmzCompile PfpeCompile
Tested by
  t1642 t1643 t1644 t1647 t1648
Invokes
  SssRelocResolve

    PgmRelocResolve Procedure Program
         MOV EBX,[%Program]
         ListGetFirst [EBX+PGM.SssList]
         JZ .90:
    .10: JNSt [EAX+SSS.Status],sssSegment,.80:
         Invoke SssRelocResolve::,EAX,EBX            ; Resolve all relocation in segment EAX.
    .80: ListGetNext EAX
         JNZ .10:                                    ; The next segment.
    .90:EndProcedure PgmRelocResolve

↑ PgmLink PgmPtr, FAbottom, VAbottom

  Segments and groups specified in `PGM.OrdBuffer` will be virtually linked to the final image of executable or linkable file. PgmLink is invoked when all linked modules have been loaded and their pointers stored on `PGM.ModulePgmList`, all modules have been combined by PgmCombine into the main PGM and when segment order was already established by PgmOrderSegments.
  Segment virtual size is calculated as `SSS.Top - SSS.Bottom` for both initialized and uninitialized segments.

  PgmLink manipulates with three members of SSS objects (groups and segments):

  1. FA - file address `SSS.BottomFA`
  2. SVA - segment virtual address `SSS.SVA`
  3. VA - image virtual address `SSS.Bottom`

  FA alias `SSS.BottomFA` of 1st segment starts at `FAbottom` aligned by the program option `%^FileAlign`.

  FA of ELF special sections is aligned by the section's own alignment. FA of regular and PE-special sections is aligned by the greater of section's own alignment and program option FileAlign=.

  SVA alias `SSS.SVA` is virtual address related to the addressing frame, i.e. to the start of segment's group (or to the start of ungrouped segment), assuming that the segment register (CS, DS, SS) was loaded with paragraph address of the adressing frame. SVA takes memory model into account.
  SVA=0 in FAR models. It is 0 in monosegment NEAR models, too, but SVA>0 when there are more segments with the similar purpose, for instance when MODEL=SMALL and several code or data segments with different names have been linked from other modules to the main MZ executable, or when `DS=PARA# [DGROUP]` is used as a data addressing frame and the datagroup encompasses three segments: `[DGROUP] GROUP [RODATA],[DATA],[BSS]`. Contents of all those segments is accessible without updating DS, which needs to be initialized only once in the beginning of the program (loading DS with `PARA# [DGROUP]`). In this case SVA of segment [DATA] is increased by the aligned size of [RODATA], and SVA of [BSS] is increased by the aligned sizes of [RODATA] plus [DATA].
  SVA is used to resolve relocations in realmode programs. SVA and RVA are aligned by the greater of `%^SectionAlign` and segment's own alignment.

  VA alias `SSS.BottomLow` in executable images is absolute segment address related to the `%^ImageBase` . It is used to resolve relocations in FLAT memory models. VA is calculated as a sum of program option `%^ImageBase` plus `VAbottom`, aligned by the greater of the program option `%^SectionAlign` and the segment's own alignment `SSS.Alignment`. When VA is adjusted, `SSS.Status` is marked as `sssLinked`, thus relocations targeting this segment are resolvable.
  VA of segments in linkable modules (`pgmoptLinkable`) is not modified and remains zero. Segment status `sssLinked` remains reset.

  PgmLink does not change the contents of `SSS.EmitBuffer, SSS.RelocBuffer`, the actual raw contents of segments is not concatenated into image here because relocations are not resolved yet.

Input
  PgmPtr is pointer to the program PGM whose segments are being linked into virtual image.
  FAbottom is the unaligned size of PE|DLL MZ stub+PE headers, or unaligned size of ELF|ELFX|ELFSO file+program+section headers, or zero in other program formats.
  VAbottom is the unaligned size of PE|DLL MZ stub+PE headers, 0x100 in COM, 0x7C00 in BOOT, zero in other program formats.
Output
  Segment and group addresses are adjusted.
Error
  Errors are reported with macro Msg.
See also
  PgmLink PfelfLink PfelfxLink PfelfsoLink
Invokes
  ExpAlign PgmResizeGroups
Invoked by
  PfbinCompile PfbootCompile PfcomCompile PfmzCompile PfpeCompile
Tested by
  t7921 t7922 t7923 t7924 t7925 t7926

    PgmLink Procedure PgmPtr, FAbottom, VAbottom
    ArrayPtr     LocalVar ; Pointer to the array of pointers to SSS segments.
    ArrayEnd     LocalVar ; Behind the last pointer.
    Size         LocalVar ; Virtual (emitted or reserved) segment size.
    FileAlign    LocalVar ; Special ELF section Alignment or the greater of %^FileAlign and regular segment's Alignment.
    SectAlign    LocalVar ; Greater of %^SectionAlign and segment Alignment.
    VALow        LocalVar ; Segment virtual address when linked,
    VAHigh       LocalVar ;   including ImageBase.
    AssumedCS    LocalVar ; SVA of code segment  (relative to the assumed CS).
    AssumedDS    LocalVar ; SVA of data segment  (relative to the assumed DS).
    AssumedSS    LocalVar ; SVA of stack segment (relative to the assumed SS).
        MOV EBX,[%PgmPtr]
        MOV ESI,[%FAbottom]                          ; Initialize file~ and virtual addresses.
        XOR EDI,EDI
        Invoke ExpAlign::,ESI,[EBX+PGM.Pgmopt.FileAlign],EDI
        ADD [%FAbottom],ECX                          ; Alignment stuff following the headers.
        MOV ESI,[%VAbottom]
        MOV EAX,[EBX+PGM.Pgmopt.ImageBaseLow]
        MOV EDX,[EBX+PGM.Pgmopt.ImageBaseHigh]
        ADD EAX,ESI
        ADC EDX,EDI
        Invoke ExpAlign::,EAX,[EBX+PGM.Pgmopt.SectionAlign],EDI
        ADD EAX,ECX                                  ; Alignment stuff following the headers.
        ADC EDX,EDI
        MOV [%VALow],EAX
        MOV [%VAHigh],EDX
        MOV [%AssumedCS],EAX
        MOV [%AssumedDS],EAX
        MOV [%AssumedSS],EAX
        BufferRetrieve [EBX+PGM.SegOrdBuffer]
        ADD ECX,ESI
        MOV [%ArrayPtr],ESI
        MOV [%ArrayEnd],ECX
    .25:MOV ESI,[%ArrayPtr]                          ; The main loop: Link FA, SVA and VA of all segments.
        CMP ESI,[%ArrayEnd]
        JNB .80:
        LODSD
        MOV [%ArrayPtr],ESI
        JNSt [EAX+SSS.Status],sssSegment,.25:        ; Omit groups and external pseudosegments.
        CMPD [EAX+SSS.NameSize],0                    ; Omit the empty section [].
        JZ .25:
        MOV EDI,EAX                                  ; Link segment EDI.
        JSt [EBX+PGM.Pgmopt.Status],pgmoptLinkable,.28:
        SetSt [EDI+SSS.Status],sssLinked
    .28:JSt [EDI+SSS.Purpose],sssPurposePHDR,.25:    ; Do not elevate VA os [PSP].
        MOV EDX,[EDI+SSS.Alignment]                  ; EDX is segment's own alignment.
        MOV ECX,[EBX+PGM.Pgmopt.SectionAlign]        ; Compute effective alignment of VA..
        CMP ECX,EDX
        JAE .30:
        MOV ECX,EDX
    .30:MOV [%SectAlign],ECX
        JNSt [EDI+SSS.Purpose],sssPurposeRegular|sssPurposeOptionalMask,.34:
        MOV ECX,[EBX+PGM.Pgmopt.FileAlign]           ; Compute effective alignment of FA..
        CMP ECX,EDX
        JAE .35:
    .34:MOV ECX,EDX
    .35:MOV [%FileAlign],ECX
        MOV EAX,[EDI+SSS.TopLow]                     ; Compute segment size in memory.
        MOV EDX,[EDI+SSS.TopHigh]
        SUB EAX,[EDI+SSS.BottomLow]
        SBB EDX,[EDI+SSS.BottomHigh]
        Msg cc=NZ,'8525',EDI                         ; Size of segment [!1S] exceeded 4 GB.
        MOV [%Size],EAX
        MOV ESI,EAX
        MOV EDX,[%FAbottom]                          ; Unaligned FA of the previous segment.
        Invoke ExpAlign::,EDX,[%FileAlign],0         ; Compute segment's file address.
        ADD ECX,EDX                                  ; Alignment stuff following the headers|previous segment contents.
        MOV [EDI+SSS.BottomFA],ECX                   ; Aligned FA of this segment.
        JSt [EDI+SSS.Status],sssImagePrefix,.40:     ; Skip when EDI is COM|BOOT pseudosegment (PSP).
        JNSt [EDI+SSS.Status],sssNotBSS,.40:         ; Skip when ESI is BSS segment.
        ADD ECX,ESI                                  ; Add %Size in the file.
    .40:MOV [EDI+SSS.TopFA],ECX
        MOV [%FAbottom],ECX                          ; Unaligned FA of the next segment.
        JSt [EBX+PGM.Pgmopt.Status],pgmoptLinkable,.43:
        MOV EAX,[%VALow]                             ; Compute segment's absolute virtual address.
        MOV EDX,[%VAHigh]                            ; EDX:EAX is unaligned VA of the previous segment.
        Invoke ExpAlign::,EAX,[%SectAlign],0         ; Align the virtual top of previous segment.
        ADD EAX,ECX                                  ; Add returned stuff size.
        ADC EDX,0
        JNSt [EBX+PGM.Pgmopt.Status],pgmoptELFbased,.42:
        MOV ECX,[EBX+PGM.Pgmopt.SectionAlign]        ; Make ELF section VA and FA congruent modulo %^SectionAlign.
        DEC ECX
        AND ECX,[EDI+SSS.BottomFA]
        ADD EAX,ECX
        ADC EDX,0
    .42:MOV [EDI+SSS.BottomLow],EAX
        MOV [EDI+SSS.BottomHigh],EDX
        ADD EAX,[%Size]
        ADC EDX,0
        MOV [EDI+SSS.TopLow],EAX
        MOV [EDI+SSS.TopHigh],EDX
        MOV [%VALow],EAX
        MOV [%VAHigh],EDX                            ; Unaligned VA of the next segment.
    .43:MOV ECX,[EDI+SSS.GroupPtr]                   ; Compute segment relative offset to its frame group.
        MOV EAX,[EDI+SSS.BottomLow]
        MOV EDX,[EDI+SSS.BottomHigh]
        JECXZ .55:
        CMP EDX,[ECX+SSS.BottomHigh]
        JB .45:
        JA .50:
        CMP EAX,[ECX+SSS.BottomLow]
        JAE .50:
    .45:MOV [ECX+SSS.BottomLow],EAX                  ; Group bottom is the lowest of all its segments.
        MOV [ECX+SSS.BottomHigh],EDX
    .50:SUB EAX,[ECX+SSS.BottomLow]
        SBB EDX,[ECX+SSS.BottomHigh]
        Msg cc=NZ,'8515',EDI                         ; Segment [!1S] over 4 GB.
        MOV [EDI+SSS.SVA],EAX
    .55:JMP .25:
    .80:Invoke PgmResizeGroups,EBX                   ; Update VA of groups by their segments.
    .90:EndProcedure PgmLink

↑ PgmOrderSegments Pgm

  PgmOrderSegments decides about group and segment ordering in output program file. Pointers to SSS objects will be stored to Pgm.SegOrdBuffer in the desired order. Each SSS object is counted exactly once.
  Group membership of all segments have already been established.
  Ordinal number of each segment in PGM.SegOrdBuffer will be written to SSS.SegmIndex.

  The order depends on SSS properties: access rights (read|write|execute), purpose, initialization status (sssNotBSS), alignment (descending).
  Though many groups and segments might not exist, ordering rules are common for all program formats supported by €ASM except for ELF-based formats, which are ordered in Pfelf*Compile.

  0. Ungrouped code segments
  1. Grouped Image prefix segment [PSP]
  2. Grouped code segments
  3. Ungrouped read-only data segments
  4. Grouped read-only data segments
  5. Ungrouped data segments
  6. Grouped data segments
  7. Other regular initialized segments
  8. Ungrouped regular uninitialized segments
  9. Grouped regular uninitialized segments
  10. Ungrouped stack segments
  11. Grouped stack segments
  12. Other uninitialized segments

  Group and segment order will be visible in ListMap displayed in listing.

Input
  Pgm points to PGM - program whose segments are being sorted. Virtual address of each segment is 0.
Output
  Pgm.SegOrdBuffer is filled with DWORD pointers to SSS objects (groups and segments) in their final order.
  Number of stored groups and segments with initialized code or data (sssNotBSS) is written to `Pgm.NrOfInitSegments`. If there are more pointers in `Pgm.SegOrdBuffer` than `Pgm.NrOfInitSegments`, they all are uninitialized and their raw data may be omitted from image.
Manual
  Ordering of segments
Invoked by
  PfbinCompile PfbootCompile PfcoffCompile PfcomCompile PfmzCompile PfomfStoreModule PfpeCompile
Tested by
  t7901 t7904

    PgmOrderSegments Procedure Pgm
    SegmIndex    LocalVar                               ; SSS.SegmIndex (ordinal number in SHDR table).
    ArrayPtr     LocalVar ; Pointer to the array of DWORD pointers to SSS segments.
    ArrayEnd     LocalVar ; Behind the last pointer.
    Ptr1         LocalVar ; Pointer to the 1st pointer of subarray.
        MOV EBX,[%Pgm]
        MOV ECX,[EBX+PGM.SegOrdBuffer]
        JECXZ .10:
        BufferClear ECX
        JMPS .15:
    .10:BufferCreate [EBX+PGM.Pool],Size=128         ; Create PGM.SegOrdBuffer if it wasn't initialized.
        MOV [EBX+PGM.SegOrdBuffer],EAX
    .15:; Pass 1 - prepare flags.
        MOVD [%SegmIndex],-1                         ; Segment index is 0-based.
        ListGetFirst [EBX+PGM.SssList]
        JZ .99:
    .20:RstSt [EAX+SSS.Status],sssOrdered+sssUsed    ; Reset flag sssOrdered which guarantees that segments&groups are ordered just once
    .25:ListGetNext EAX                              ;  and the groups (except for [PHDR] are not empty.
        JNZ .20:
    .30:; Pass 2 - examine used (nonempty) groups.
        ListGetFirst [EBX+PGM.SssList]
    .35:JNSt [EAX+SSS.Status],sssSegment,.40:
        MOV ECX,[EAX+SSS.GroupPtr]
        JECXZ .40:
        SetSt [ECX+SSS.Status],sssUsed               ; Mark the group ECX of segment EAX as used.
        MOV EDX,[EAX+SSS.Purpose]                    ; Update purposes of each group.
        OR [ECX+SSS.Purpose],EDX                     ; Group ECX inherits each purpose of its segments.
        JNSt [EAX+SSS.Status],sssNotBSS,.40:
        SetSt [ECX+SSS.Status],sssNotBSS             ; If segment EAX is initialized, group ECX is too.
    .40:ListGetNext EAX
        JNZ .35:
    .45:; Pass 3 - remove unused (empty) groups.
        ListGetFirst [EBX+PGM.SssList]
    .50:JNSt [EAX+SSS.Status],sssGroup,.55:
        JSt [EAX+SSS.Status],sssUsed,.55:
        ListRemove [EBX+PGM.SssList],EAX             ; Remove unused group.
        JMP .45:                                     ; Start the list again.
    .55:ListGetNext EAX
        JNZ .50:
    .57:
        ; Step 4 - the actual ordering employs macros OrderSegment and OrderGroup.

    OrderGroup %MACRO Purpose=sssPurposeAny, NamePtr=0, NameSize=0
        %IF "%Purpose" !== "EDX"
          MOV EDX,%Purpose
        %ENDIF
        MOV ESI,%NamePtr
        MOV ECX,%NameSize
        CALL PgmOrderSegments.OrderGroup:
       %ENDMACRO OrderGroup

    OrderSegment %MACRO Purpose=sssPurposeAny, NamePtr=0, NameSize=0, Group=-1
        %IF "%Purpose" !== "EDX"
          MOV EDX,%Purpose
        %ENDIF
        MOV ESI,%NamePtr
        MOV ECX,%NameSize
        %IF "%Group" !== "EDI"
          MOV EDI,%Group
        %ENDIF
        CALL PgmOrderSegments.OrderSegment:
       %ENDMACRO OrderSegment

        ; The actual group&segment order.

    .G1:OrderGroup NamePtr= =B'COMGROUP',NameSize=8
        JZ .C5:
        MOV EDI,EAX
    .G3:OrderSegment Purpose=sssPurposePHDR,Group=EDI  ; Pseudosegment [PSP] (used in COM).
    .G4:OrderSegment Purpose=sssPurposeCODE,Group=EDI
        JNZ .G4:
    .G5:OrderSegment Purpose=sssPurposeRODATA,Group=EDI
        JNZ .G5:
    .G6:OrderSegment Purpose=sssPurposeDATA,Group=EDI
        JNZ .G6:
    .G7:OrderSegment Purpose=sssPurposeBSS,Group=EDI
        JNZ .G7:
    .G8:OrderSegment Purpose=sssPurposeSTACK,Group=EDI
        JNZ .G8:

    .C5:OrderGroup Purpose=sssPurposeCODE
        JZ .C9:
        MOV EDI,EAX
    .C6:OrderSegment Purpose=sssPurposeCODE,Group=EDI
        JNZ .C6:
        JMP .C5:                                       ; The next code group.
    .C9:OrderSegment Purpose=sssPurposeCODE            ; Ungrouped code segments.
        JNZ .C9:

    .R5:OrderGroup Purpose=sssPurposeRODATA            ; Monodata memory models may group rodada, data and bss segments.
        JZ .R9:
        MOV EDI,EAX
    .R6:OrderSegment Purpose=sssPurposeRODATA,Group=EDI
        JNZ .R6:
    .R7:OrderSegment Purpose=sssPurposeDATA,Group=EDI
        JNZ .R7:
    .R8:OrderSegment Purpose=sssPurposeBSS,Group=EDI
        JNZ .R8:
        JMP .R5:                                       ; The next rodata group.
    .R9:OrderSegment Purpose=sssPurposeRODATA         ; Ungrouped rodata segments.
        JNZ .R9:

    .D5:OrderGroup Purpose=sssPurposeDATA
        JZ .D9:
        MOV EDI,EAX
    .D6:OrderSegment Purpose=sssPurposeRODATA,Group=EDI    ; Monodata memory models may group rodada, data and bss segments.
        JNZ .D6:
    .D7:OrderSegment Purpose=sssPurposeDATA,Group=EDI
        JNZ .D7:
    .D8:OrderSegment Purpose=sssPurposeBSS,Group=EDI
        JNZ .D8:
        JMP .D5:                                        ; The next data group.
    .D9:OrderSegment Purpose=sssPurposeDATA          ; Ungrouped data segments.
        JNZ .D9:

    .B5:OrderGroup Purpose=sssPurposeBSS
        JZ .B9:
        MOV EDI,EAX
    .B6:OrderSegment Purpose=sssPurposeRODATA,Group=EDI    ; Monodata memory models may group rodada, data and bss segments.
        JNZ .B6:
    .B7:OrderSegment Purpose=sssPurposeDATA,Group=EDI
        JNZ .B7:
    .B8:OrderSegment Purpose=sssPurposeBSS,Group=EDI
        JNZ .B8:
        JMP .B5:                                        ; The next bss group.
    .B9:OrderSegment Purpose=sssPurposeBSS              ; Ungrouped bss segments.
        JNZ .B9:

    .S1:OrderGroup Purpose=sssPurposeSTACK
        JZ .S5:
        MOV EDI,EAX
    .S2:OrderSegment Purpose=EDX,Group=EDI
        JNZ .S2:
        JMP .S1:                                      ; The next stack group.
    .S5:OrderSegment Purpose=sssPurposeSTACK          ; Ungrouped stack segments.
        JNZ .S5:

        OrderSegment    sssPurposeEXPORT               ; Order PE-special segment [.edata].
        OrderSegment    sssPurposeIMPORT               ; Order PE-special segment [.idata].
        OrderSegment    sssPurposeRESOURCE             ; Order PE-special segment [.rsrc].
        OrderSegment    sssPurposeBASERELOC            ; Order PE-special segment [.reloc].
        OrderSegment                                  ; Any other remaining segments.

        ; Alphabetically reorder segments from the same group with identical purposes.
        BufferRetrieve [EBX+PGM.SegOrdBuffer]
        ADD ECX,ESI                                     ; ESI..ECX is an array of DWORD pointers to segments (perhaps in wrong order).
        MOV [%ArrayPtr],ESI
        MOV [%ArrayEnd],ECX
    .60:MOV ESI,[%ArrayPtr]                             ; Find a group.
        CMP ESI,[%ArrayEnd]
        JNB .99:
        LODSD
        MOV [%ArrayPtr],ESI
    .65:JNSt [EAX+SSS.Status],sssGroup,.60:
        MOV EBX,EAX                                     ; EBX is pointer to a group. Pointers to its segments may follow.
    .70:MOV ESI,[%ArrayPtr]
        CMP ESI,[%ArrayEnd]
        JNB .60:
        MOV [%Ptr1],ESI
        LODSD
        MOV [%ArrayPtr],ESI
        JNSt [EAX+SSS.Status],sssSegment,.65:
        CMP [EAX+SSS.GroupPtr],EBX
        JNE .60:
        MOV EDX,[EAX+SSS.Purpose]
    .75:                                                 ; Find more segments with identical group EBX and purpose EDX.
        MOV ESI,[%ArrayPtr]
        CMP ESI,[%ArrayEnd]
        JNB .85:
        LODSD
        MOV [%ArrayPtr],ESI
        JNSt [EAX+SSS.Status],sssSegment,.80:
        CMP [EAX+SSS.GroupPtr],EBX
        JNE .80:
        CMP [EAX+SSS.Purpose],EDX
        JE .75:
    .80:MOV ESI,[%ArrayPtr]                             ; The just loaded object at %ArrayPtr is not a suitable segment. Roll back.
        SUB ESI,4
        MOV [%ArrayPtr],ESI
    .85:MOV EDX,[%ArrayPtr]
        SUB EDX,[%Ptr1]                                 ; Array [%Ptr1] .. [%ArrayPtr] contains pointers to groupen segments with identical purpose.
        CMP EDX,4
        JNA .70:                                        ; Skip when nothing to sort.
        SHR EDX,2                                       ; EDX is number of segments to sort.
        ; If at least one segment has $ in its name, sort all of them alphabetically.
        MOV ESI,[%Ptr1]
    .90:CMP ESI,[%ArrayPtr]
        JNB .70:
        LODSD
        MOV EDI,[EAX+SSS.NamePtr]
        MOV ECX,[EAX+SSS.NameSize]
        MOV AL,'$'
        REPNE SCASB
        JNE .90:                                     ; No '$' in segment name, try the next segment.
        ShellSort [%Ptr1],EDX,4,.ByName
        JMP .70:

    .ByName: PROC                                    ; Sort callback procedure. Input: ESI and EDI points to DWORD ^SSS.
        PUSH ECX,EBX,EBP
          MOV EBX,[ESI]
          MOV EBP,[EDI]
          MOV ECX,[EBX+SSS.NameSize]
          MOV EDX,[EBP+SSS.NameSize]
          CMP ECX,EDX
          JBE .N2:
          MOV ECX,EDX
    .N2:  PUSH ESI,EDI
            MOV ESI,[EBX+SSS.NamePtr]
            MOV EDI,[EBP+SSS.NamePtr]
            REPE CMPSB                                  ; Compare shorter part of segment names.
          POP EDI,ESI
          JA .Swap:
          JB .Ord:
          CMP EDX,[EBP+SSS.NameSize]                    ; When they match, compare lengths.
          JB  .Swap:
    .Ord: CLC                                           ; Both segments EBX,EBP are in order.
          JMPS .N9:
    .Swap:MOV ECX,[EBX+SSS.SegmIndex]                   ; Swap pointers at ESI,EDI and their segment indexes.
          MOV EDX,[EBP+SSS.SegmIndex]
          MOV [EBX+SSS.SegmIndex],EDX
          MOV [EBP+SSS.SegmIndex],ECX
          MOV [ESI],EBP
          MOV [EDI],EBX
          STC                                            ; Signalize that they were swapped.
    .N9:POP EBP,EBX,ECX
        RET
       ENDP .ByName:

    .OrderGroup: PROC1                               ; Runtime procedure of macro OrderGroup.
        ; Input: EDX=purpose, ESI=^name, ECX=SIZE# name, EBX=^PGM.
        ; Output: ZF=0, EAX=^SSS, EAX stored to PGM.SegOrdBuffer.
        ; Error:  ZF=1, EAX=0 when the group was not found.
        ListGetFirst [EBX+PGM.SssList]
    .G1:JSt [EAX+SSS.Status],sssOrdered,.G7:
        JNSt [EAX+SSS.Status],sssGroup,.G7:
        JNSt [EAX+SSS.Purpose],EDX,.G7:
        TEST ESI                                      ; ESI=0 allows any name.
        JZ .G8:
        CMP ECX,[EAX+SSS.NameSize]
        JNE .G7:
        JECXZ .G8:
        PUSH ECX,ESI,EDI
          MOV EDI,[EAX+SSS.NamePtr]
          REPE CMPSB
        POP EDI,ESI,ECX
        JE .G8:
    .G7:ListGetNext EAX                               ; Try the next group.
        JNZ .G1:
        RET                                           ; ZF=1, EAX=0, not found.
    .G8:BufferStoreDword [EBX+PGM.SegOrdBuffer],EAX   ; Store group EAX.
        SetSt [EAX+SSS.Status],sssOrdered
        RET                                           ; ZF=0, EAX=^SSS, found.
       ENDP1 .OrderGroup

    .OrderSegment: PROC1                              ; Runtime procedure of macro OrderSegment.
        ; Input: EDX=purpose, ESI=^name, ECX=SIZE# name, EDI=^SSS group, EBX=^PGM.
        ; Output: ZF=0, EAX=^SSS, EAX stored to PGM.SegOrdBuffer.
        ; Error:  ZF=1, EAX=0 when the segment was not found.
        ListGetFirst [EBX+PGM.SssList]
    .S1:JSt [EAX+SSS.Status],sssOrdered,.S7:
        JNSt [EAX+SSS.Status],sssSegment,.S7:
        JNSt [EAX+SSS.Purpose],EDX,.S7:
        CMP EDI,-1                                    ; Any group membership?
        JE .S5:
        CMP [EAX+SSS.GroupPtr],EDI
        JNE .S7:
    .S5:TEST ESI                                      ; ESI=0 allows any name.
        JZ .S8:
        CMP ECX,[EAX+SSS.NameSize]
        JNE .S7:
        JECXZ .S8:
        PUSH ECX,ESI,EDI
          MOV EDI,[EAX+SSS.NamePtr]
          REPE CMPSB
        POP EDI,ESI,ECX
        JE .S8:
    .S7:ListGetNext EAX                               ; Try the next segment.
        JNZ .S1:
        RET                                           ; ZF=1, EAX=0, not found.
    .S8:BufferStoreDword [EBX+PGM.SegOrdBuffer],EAX   ; Order segment EAX.
        SetSt [EAX+SSS.Status],sssOrdered
        INCD [%SegmIndex]
        PUSHD [%SegmIndex]
        POPD [EAX+SSS.SegmIndex]
        TEST EAX
        RET                                           ; ZF=0, EAX=^SSS, found.
        ENDP1 .OrderSegment:

    .99:EndProcedure PgmOrderSegments

       ENDPROGRAM pgm

▲Back to the top▲