flat assembler
Message board for the users of flat assembler.
![]() |
Author |
|
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 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 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 |
|||
![]() |
|
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 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 |
|||
![]() |
|
< Last Thread | Next Thread > |
Forum Rules:
|
Copyright © 1999-2025, Tomasz Grysztar. Also on GitHub, YouTube.
Website powered by rwasa.