flat assembler
Message board for the users of flat assembler.

flat assembler > Macroinstructions > Pre-linking

Author
Thread Post new topic Reply to topic
Tomasz Grysztar
Assembly Artist


Joined: 16 Jun 2003
Posts: 6873
Location: Kraków, Poland
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
Assembly Artist


Joined: 16 Jun 2003
Posts: 6873
Location: Kraków, Poland
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 © 2004-2018, Tomasz Grysztar.

Powered by rwasa.