flat assembler
Message board for the users of flat assembler.

Index > Macroinstructions > Pre-linking

Author
Thread Post new topic Reply to topic
Tomasz Grysztar



Joined: 16 Jun 2003
Posts: 8367
Location: Kraków, Poland
Tomasz Grysztar 07 Jul 2018, 13:03
One of the traditional ways to use fasm's multi-pass assembly is to have a "proc" macro with an "if used" clause that causes an entire procedure to be assembled only when it is actually needed. This is especially useful when an executable is assembled directly, without a linker - because normally it is the linker's job to determine which objects are needed to form the final file and which can be omitted. Therefore I'm calling the process of assembly with "if used" clauses a "pre-linking", since it does similar thing but at much earlier stage.

One of the advantages of such pre-linking is that functions that are not needed are not assembled at all. But it should be noted that this process follows the usual rules for forward-referencing and predicting values of the multi-pass assembly and therefore in more complex cases it may end up being sub-optimal.

Consider the following example, with maximally simplified "proc" macro (fasm 1 syntax):
Code:
macro proc name
{ 
  if used name 
    name: 
} 

macro endp 
{ 
  end if 
} 

start:
        call    main

proc alpha 
        ret 
endp 

proc beta 
        ret 
endp 

proc gamma 
        call    beta 
        ret 
endp 

proc delta 
        call    gamma 
        ret 
endp 

proc main 
        call    delta 
        ret 
endp    
This requires five passes to resolve. Because until assembled sees some name used, it predicts it to be not used, the functions are initially not assembled except for the "main" which is used before it is defined. When assembling "main" fasm notices that it uses "delta", so it the next pass it assembles "delta" and now it sees that it also needed to assemble "gamma", and so on.

We could get rid of this cascade of passes by switching from optimistic to pessimistic multi-pass assembly, that is by making "proc" always assemble the function initially and only stop assembling it in later passes when it sees that it was not used:
Code:
macro proc name
{
  if ~ defined name#..tracer
    name#..tracer = 0
  else
    name#..tracer = 1
  end if
  if name#..tracer = 0 | used name
    name:
}

macro endp
{
  end if
}

start:
        call    main

proc alpha
        ret
endp

proc beta
        ret
endp

proc gamma
        call    beta
        ret
endp

proc delta
        call    gamma
        ret
endp

proc main
        call    delta
        ret
endp    
But this, while reduces the number of passes, causes all the functions to be assembled in the initial pass, if they are large and complex this may also be something we'd prefer to avoid. Is there a way?

If we can assume that the only way in which functions are used is with "call" instruction, we can prepare a "call" macro that would make fasm's preprocessor collect all the dependencies. For every function we can create a list of names of function which are called from its body. Then at the end of source we can start with a list of functions called from the "root" code and proceed recursively, marking the functions as "used" by assigning them to some dummy variable.

Recursion in the preprocessor of fasm 1 is tricky but doable. To iterate through the list of collected names we can use IRPV:
Code:
define __MODULE__ __ROOT__

macro call name
{
  call name
  match module, __MODULE__
  \{ define __CALLEDBY__\#module name  \}
}

macro proc name
{ 
  define __MODULE__ name
  if used name
    name: 
} 

macro endp 
{ 
  end if
  restore __MODULE__
}

macro define_recursive_prelinker
{
  macro recursive_prelinker module
  \{
     irpv called, __CALLEDBY__\#module
     \\{
       \\local DUMMY
       if called eqtype DUMMY
         label DUMMY at called
       end if
       define_recursive_prelinker
       recursive_prelinker called
     \\}
  \}
}

postpone
{
  define_recursive_prelinker
  recursive_prelinker __ROOT__
}


start:
        call    main

proc alpha
        ret
endp

proc beta
        ret
endp

proc gamma
        call    beta
        ret
endp

proc delta
        call    gamma
        ret
endp

proc main
        call    delta
        ret
endp    
With this set of macros, after the first pass assembler already has all the recursively required functions marked as used, so it requires just as many passes as in the previous example. But this time functions that are not needed are never assembled at all - it can be easily confirmed by putting some invalid instructions in the body of "alpha".
Post 07 Jul 2018, 13:03
View user's profile Send private message Visit poster's website Reply with quote
Tomasz Grysztar



Joined: 16 Jun 2003
Posts: 8367
Location: Kraków, Poland
Tomasz Grysztar 07 Jul 2018, 14:05
Another possible approach is to perform the preprocessing separately and cut the unnecessary functions out from the source completely before passing it to the assembly.

We can, for instance, use fasmg as an external preprocessor. Here is my prototype script:
Code:
PREPROCESSOR := __FILE__

namespace PREPROCESSOR

        define MODULE __ROOT__

        BLOCK_NUMBER = 0
        macro numbered_virtual
                virtual
                repeat 1, n:BLOCK_NUMBER
                        BLOCK_#n::
                        MODULE_#n equ MODULE
                end repeat
                BLOCK_NUMBER = BLOCK_NUMBER + 1
        end macro
        numbered_virtual

        macro scheduled
        end macro

        define symlinks include:macro:purge:if:else:end:namespace:__FILE__:PREPROCESSOR
        match include:macro:purge:if:else:end:namespace:__FILE__:PREPROCESSOR, symlinks

                macro prelink file
                        local empty
                        macro empty.include?! file
                               include file
                        end macro
                        namespace empty
                        macro ? line&
                                if __FILE__ = PREPROCESSOR
                                        purge ?
                                        line
                                else
                                        namespace PREPROCESSOR
                                                match =proc name, line
                                                        define MODULE name
                                                        end virtual
                                                        numbered_virtual
                                                        db `name,':',13,10
                                                else match =endp, line
                                                        restore MODULE
                                                        end virtual
                                                        numbered_virtual
                                                else
                                                        match =call? name, line
                                                                match car cdr, name  ; ignore any multi-token arguments
                                                                else
                                                                        match caller, MODULE
                                                                                define CALLEDBY.caller name
                                                                        end match
                                                                end match
                                                        end match
                                                        db `line,13,10
                                                end match
                                        end namespace
                                end if
                        end macro
                        include file
                        end namespace
                end macro

        end match

        prelink source

        end virtual

        macro use name
                restore USE.name
                USE.name = 1
                irpv dependency, CALLEDBY.name
                        if ~ defined USE.dependency
                                use dependency
                        end if
                end irpv
        end macro

        use __ROOT__

        repeat BLOCK_NUMBER, n:0

                match caller, MODULE_#n
                        if defined USE.caller & USE.caller
                                virtual BLOCK_#n
                                        load block:$ from 0
                                end virtual
                                db block
                        else
                                display 'Cut: ',`caller,13,10
                        end if
                end match

        end repeat

end namespace    
It collects the preprocessed source in multiple pieces, each in its own VIRTUAL block, and then puts the source together, omitting the pieces that correspond to functions that are not required. Everything is done is such way that fasmg does not need more than one pass to process everything.

Such preprocessed source then requires only two passes when assembled with fasm 1, and the functions that are not required were cut from the source completely, so fasm does not even see or parse them.

Running the script needs to be done with a command like:
Code:
fasmg prelink.asm -isource='test.asm' test_prelinked.asm    
Follow the link about using fasmg as a preprocessor for more information.
Post 07 Jul 2018, 14:05
View user's profile Send private message Visit poster's website Reply with quote
Display posts from previous:
Post new topic Reply to topic

Jump to:  


< Last Thread | Next Thread >
Forum Rules:
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum
You cannot attach files in this forum
You can download files in this forum


Copyright © 1999-2025, Tomasz Grysztar. Also on GitHub, YouTube.

Website powered by rwasa.