Conditional Assembly

From: Randall Hyde (randyhyde_at_earthlink.net)
Date: 08/30/04


Date: Mon, 30 Aug 2004 18:22:46 GMT

Conditional assembly is one of those basic features
you would expect every assembler to support -- surprisingly,
this feature is missing from many different "hobby" assemblers
out there. In a modern assembler, conditional assembly is
probably a more fundamental requirement than macro facilities.

So what is conditional assembly? Quite literally, conditional
assembly is the ability to specify whether a section of code
in an assembly language source file is ignored or assembled
during the assembly process. Conditional assembly traditionally
takes the form of a HLL "IF" statement, though the semantics
of a conditional assembly statement have nothing to do with
the run-time if statement. Here's a typical example from the
HLA assembly language:

#if( SomeConst < 10 )
    mov( 10, eax );
#endif

During compilation (assembly), the assembler evaluates the
boolean expression associated with the conditional assembly
"IF" statement ("#IF", in HLA's case). If the expression evaluates
true, then the assembler processes all the text between the
conditional IF and the corresponding ENDIF (#ENDIF in
HLA's case). If the expression evaluates false, then the assembler
skips over all the text between the start of the IF and the corresponding
ENDIF. In fact, this is often used to "comment out" large blocks of
code, avoiding the "nested comment" function. For example, in
HLA you could comment out a large section of code thusly:

#if( false )

    << large block to comment out >>

#endif

This technique is often used to comment out documentation
or other text appearing at the start of an HLA source file, so
you don't have to worry about starting each line with a "//"
or enclosing the block in /* and */ (and worrying about
possible nested "/*..*/" blocks inside the comment).

One of the most important facts to realize about a conditional
assembly IF statement is that the corresponding boolean
expression is a *compile-time* expression, not a run-time
expression. That is, the assembler must be capable of
processing the expression during assembly. Therefore, it may
only contain constant objects whose values have been defined
prior to the assembler encountering the IF statement. In particular,
these constant expressions may not contain register or run-time
variable names whose values aren't known until the program
executes at some later time. Any identifiers appearing in the
boolean expression must be constants (or identifiers plus some
operator that yields a constant, such as the @defined HLA
compile-time function).

Probably the most traditional use for conditional assembly
(or, more generally, conditional compilation) is to allow the
parameterization of a source file so that you can compile it
for different environments. For example, in the HLA Standard
Library there are a couple of constants defined in the "os"
namespace with the names "win32" and "linux". These are
boolean constants set to true or false depending upon which
operating system you're using. This allows you to do the
following:

#if( os.win32 )

    << code that is Windows Specific >>

#endif

#if( os.linux )

    << code that is Linux specific >>

#endif

Most conditional assembly systems also support an "ELSE"
clause and an "ELSEIF" clause to handle cases where
certain options are mutually exclusive. For example, it's
never possible for the os.linux and os.win32 constants to
both contain true simultaneously, so the previous #IF statements
could be written better as:

#if( os.win32 )

    << code that is Windows specific >>

#elseif( os.linux )

    << code that is Linux specific >>

#else

    #error( "This code is only valid for Windows or Linux" )

#endif

This same technique can be used to handle specific hardware
in an application. For example, suppose you want to include
some extra code if the program is intended to work with
a special USB device; to handle this, you could write code
like the following:

#if( useUSB )

    << code that works with the USB device >>

#else

    << code that runs if the USB device is not present >>

#endif

Unlike the os.win32 and os.linux constants, which are
automatically defined when you include the HLA Standard
Library header files, the "useUSB" symbol probably isn't
predefined for you. If a conditional assembly statement
encounters an undefined symbol, HLA reports an error
("undefined symbol error", intuitively enough). Therefore,
in order for this statement to assemble properly, you must
define the useUSB symbol as a boolean constant as some
point in your program prior to the #if statement. This can
be done in one of four ways: in an HLA CONST section,
in an HLA VAL section, with the "?" compile-time assignment
statement, or from the HLA command line. Here are examples
of each of these mechanism (note that you'd only use one of these
in HLA, you wouldn't use all four):

const
    useUSB := true; // or false, depending on what you want.

val
    useUSB := false; // or true, depending on what you want.

?useUSB := true; // or whatever

HLA -DuseUSB pgm.hla

The HLA "-D" command-line option defines "useUSB" as
a boolean constant and sets it to true. Typically, you wouldn't
use the command-line option in this manner because there
is no way to set "useUSB" to false from the command line.
However, you could use a conditional assembly statement like
this at the beginning of your source file:

#if( !@defined( useUSB ))

    ?useUSB := false;

#endif

Now, if "useUSB" is not defined at the beginning of the
assembly, then the conditional assembly statements above
will define it and set it to false. If it is defined (as it was defined
on the command line), then HLA doesn't mess with it's value at all.

While controlling the configuration of an assembly is one of the
common uses for conditional assembly, it is, by no means, the
only use for this facility. Another common use for conditional
assembly is to help produce more efficient code, especially in
macros. Suppose, for example, you want to write a macro that
compares a value in EAX against a macro parameter and branch
to a second macro parameter if EAX equals that value. You
could write a trivial macro to handle this as follows:

#macro braEAXeq( theValue, target );

    cmp( eax, theValue );
    je target;

#endmacro

However, suppose that zero is a common
"theValue" parameter value. While the above
would generate correct code, it might be better
to generate code like the following in this case:

test( eax, eax );
jz target;

This is easily handled with conditional assembly, as
follows:

#macro braEAXeq( theValue, target );

    #if( theValue = 0 )

        test( eax, eax );
        jz target;

    #else

        cmp( eax, theValue );
        je target;

    #endif

#endmacro

If it's legal to pass in objects that are not constants,
it's probably worthwhile to first test to see if you've
got a constant value prior to the condition IF above.
This can be done using the HLA @isConst compile-
time function, e.g.,

    #if( @isConst( theValue )

        #if( theValue = 0 )

            test( eax, eax );
            jz target;

        #else

            cmp( eax, theValue );
            je target;

        #endif

    #else

        cmp( eax, theValue );
        je target;

    #endif

Using conditional assembly (and compile-time functions
like @isConst and @defined) you can create some
sophisticated code sequences that emit the perfect
code sequence based on the parameters you pass a
macro.

Handling recursive macros is another area where
conditional assembly is absolutely necessary. Without
conditional assembly (or a similar facility), it is impossible
to terminate recursive macro invocations, so recusion
isn't practical. Though recursive macro invocation
isn't commonly used, when you need it there is no other
way to achieve the task at hand.

Conditional assembly has many other uses within an
assembly lnaguage source file; far too many, in fact,
to consider here. But one area where conditional
assembly is invaluable is within a "compile-time
language", that is, a "scripting language" built in to the
assembler to help you write better code. But the discussion
of conditional assembly within a compile-time language
will have to wait for a future articles.

Beginning assembly language programmers often don't
see the need for conditional assembly. Experienced
programmers, however, clearly understand the benefits
to having conditional assembly available and wouldn't
consider a tool to be of "professional calibre" without
this feature. While most assemblers do provide this
facility, some do not. So even if you don't see the
need for conditional assembly today, you should still
choose an assembler that supports conditional assembly,
because someday you *will* require this facility.
Cheers,
Randy Hyde



Relevant Pages

  • Re: Evolution
    ... > If HLA cant handle mov eax, ebx in the correct direction, ... the u32expr macro actually works. ... // leaves this result sitting in the register specified ... use improving his *assembler*. ...
    (alt.lang.asm)
  • Re: Rocket Science
    ... >> Also, logically, if MASM is the underlying assembler being used by ... then MASM must always be being run in addition to HLA so ... the MASM or FASM or whatever underlying assembler's speed into its own ...
    (alt.lang.asm)
  • Re: .EXE -> .ASM -> .EXE
    ... HLA, why would they even come you your forum? ... becoming RosAsm users). ... needs to learn assembly language. ... I do think it would be much more an improvement you write an assembler, ...
    (alt.lang.asm)
  • Re: "We Never Use Assembly Language"
    ... conditional branch macro I've experienced in any assmebler. ... Rosasm doesn't like the comment lines. ... doing HLA is what you want, then HLA is your only option. ... then you need an assembler. ...
    (alt.lang.asm)
  • Re: Questions about the HLA installation...
    ... > HLA, assembler and linker in order - depending on your command line ... Windows98 is probably the 'stablest' Windows ... *and* maintain in bare bones assembler. ... all of it pales in comparsion to his 'adventure' with Iraq. ...
    (alt.lang.asm)