Message board for the users of flat assembler.
> Macroinstructions > Tricky stuff in fasmg, part 2: Namespace separation
This is the second part of a series on advanced trickery that can be done with fasmg. The other installments: part 1, part 3, part 4.
Edit: this part has been largely deprecated by later changes to how the macro recursion is handled by fasmg. With the new syntax a macro can only be recursive when marked as such and unwanted recursion in embedded namespaces is no longer a problem.
The NAMESPACE directive allows to process entire sections of source in a separate contexts, avoiding any name clashes. If the sub-modules are assembled in separate namespaces, then they can not only use the same names for various labels, but even their macroinstructions are confined to their local scope. For example, one module may include macros for 8086 instruction set, while the other one could use instructions of a different processor, and they would not get in each other's way (the PE formatter macros that come with the examples in fasmg package do something like this, when they define 8086 instruction set macros within a local namespace only to assemble the MZ stub).
A separation of modules can look like this:
Giving a definition to the parent symbol of a namespace is a recommended practice if this namespace is going to be accessed by name in some other places (like in the CALL instructions in the above sample). But if for some reason it was only needed to separate the namespaces of two sources - maybe because all they have to do is just generate some data into output - a minimal variant would work just as well:
define First namespace First include 'module1.asm' end namespace define Second namespace Second include 'module2.asm' end namespace call First.main call Second.main
namespace First include 'module1.asm' end namespace namespace Second include 'module2.asm' end namespace
There is however, a dangerous trap hidden there, and it is related to the forward-referencing of symbols.
Let's consider the following framework: we have a global COUNTER variable, initialized in the beginning of source:
Now every module may for some reason need to sometimes increase this counter, with an instruction like:
COUNTER = 0
If we now try to put these modules into their own namespaces, suddenly they are going to start defining COUNTER inside their local contexts. If one such module contains only one command like the above one, it is not only going to define COUNTER as a symbol local to its namespace, but this symbol will be allowed to be forward-referenced (because it has only one definition in the entire source), and this construction becomes a self-referencing definition. It is impossible to fulfill such clause, as there is no value of COUNTER that solves such equation, so the assembly is going to fail.
COUNTER = COUNTER + 1
The same problem can also apply to symbolic variables: if we had global LIST variable, perhaps initialized like this:
and then expanded in modules with commands like:
LIST equ initial
LIST equ LIST, element
then putting the module inside its own namespace would cause the above definition to become circular - this would in theory create ever-growing text, but fasmg catches such circular references early (also the ones of form "a equ b"/"b equ a") and signals the problem.
A possible solution to these problems is very simple: the modules should re-define the global variable with constructions like:
The dot after the name of symbol tells the assembler to look for the already defined symbol with such name, including parent namespaces, so this way we modify the global symbol instead of creating a local one.
COUNTER. = COUNTER + 1 LIST. equ LIST, element
The same principle would apply if we created a special globally-acessible namespace where we would keep these variables:
The principle is the same because again it is the dot in the identifier that makes the assembler look for the defined symbol in parent namespaces, only this time after a dot comes a name of descendant symbol, so this time it is not the global symbol that gets re-defined, but the symbol inside its namespace.
define Globals namespace Globals COUNTER = 0 LIST equ initial end namespace define Module namespace Module Globals.COUNTER = Globals.COUNTER + 1 Globals.LIST equ Globals.LIST, element end namespace
Interestingly, the same problem can also occur in case of macroinstructions. Let's consider that we have some simple global macroinstruction:
and that sub-modules may seek to re-define this mcaroinstruction to meet their requirements:
macro INT value dd value end macro
Normally when such re-defined macro calls its own name, it refers to the previous macro with such name. But if we put the second definition inside a local namespace, we get the same result as with numeric or symbolic variables: the local macro now has just one definition and it can be forward-referenced, and this results in it calling itself recursively. This is very similar to what happens with circularly-defined symbolic value, but this time fasmg is not easily able to detect this and it will only detect an error when it reaches the built-in recursion limit (this limit can be altered with the -r command line option, setting it some small number like 100 allows to catch such errors early).
macro INT values& iterate value, values INT value end iterate end macro
This time adding a dot after the name of a macro is not a valid solution, because a dot causes the assembler to look for the symbol of the expression class, not the instruction class - so it would only find globally defined INT if was also defined as a numeric or symbolic constant or variable there. Using a special namespace would work, but this would require a macro to also be used in this way.
However there is a different possible solution that may help in this case. If we somehow force the local symbol to be considered variable even when it has just one definition, the infinite recursion is going to disappear. When a variable symbol references in its first definition the same name, the assembler looks for the defined value for that name also outside the local namespace, so it is going to use the global value. And we can force local macroinstruction to become variable by creating a dummy definition and immediately removing it with PURGE:
The similar trick can be applied to the symbols of the expression class, but this only makes sense when their modified values need only to be used locally:
macro int value dd value end macro namespace Module ; force variable macro macro int end macro purge int macro int values& iterate value, values int value end iterate end macro int 1,2,3 end namespace
COUNTER = 0 namespace First ; force variable define COUNTER restore COUNTER ; increase counter COUNTER = COUNTER + 1 ; use the local counter value db COUNTER end namespace namespace Second ; force variable define COUNTER restore COUNTER ; counting again from 0 COUNTER = COUNTER + 1 COUNTER = COUNTER + 1 db COUNTER end namespace
There is one case when this problem is going to show up frequently when putting some module into its separated namespace: it is when the module tries to re-define some of the internal instructions of the assembler. All the instructions of fasmg are the built-in global symbols, and when a module tries to re-define such instruction in a way that calls the original one, but it does it inside a local namespace, the infinite recursion is going to kick in.
We can see this effect immediately if we try to encapsulate in such way any complete program that uses the PE formatter, for instance the win32.asm example from the fasmg package:
If we use the "-r100" command line switch to avoid the long wait and detect the recursion early, we are going to notice that it is caused by the re-defined DD instruction.
namespace Win32_Sample include 'win32.asm' ; infinite recursion imminent end namespace
But we already know how to fix this. To make things simpler, let's use this handy macro:
For a given name, it forces such symbol to be variable in all the classes (expression, instruction and labeled instruction). Since DD is defined both as an instruction and as a labeled instruction, it is not much of an overkill here:
macro var? names& iterate name, names define name restore name macro name end macro purge name struc name end struc restruc name end iterate end macro
The PE formatter also re-defines the SECTION instruction, but it does it multiple times on its own, so this one is a variable anyway.
namespace Win32_Sample var dd?,dq? include 'win32.asm' end namespace
Now, this helps with the recursion, but the above sample would still not assemble - this time because of the POSTPONE used by the PE formatter, since the postponed code gets executed outside of the namespace where we tried to encapsulate this whole program. But in the previous part we already had prepared a macro that allows to execute postponed blocks locally:
In fact, we could use the entire set of macros that were used to virtualize output and combine them with the namespace encapsulation, to assemble entire program sources in their own "sandboxes":
namespace Win32_Sample macro postpone?! esc macro postponed end macro macro end?.postpone?! postponed esc end macro end macro macro postponed end macro var dd?,dq? include 'win32.asm' postponed purge postpone?,end?.postpone? end namespace
macro encapsulate? Namespace virtual at 0 $$% = 0 @% = 0 define $% $$% + $ - $$ define $%% $$% + $@ - $$ - 1/