Macros to Add Structured Control-flow to any Assembler (part 2) (version 2)

In part 1 (version 2) I showed how to set up the control-flow stack and how to use it to implement the structured conditionals _IF..._ENDIF and _IF..._ELSE..._ENDIF, and the structured loops _REPEAT..._UNTIL and _DO..._WHILE..._ENDW. In this part we look at short-circuit-AND and OR conditionals, switch/case statements and counted loops. These can all be implemented using the 5 basic control-flow elements defined in part 1: _IF, _ENDIF, _REPEAT, _UNTIL and _CS_SWAP (which swaps the top two elements of the control-flow stack).

Short-Circuit Conditionals (introduced using overlapping structures)

A short-circuit conditional is a conditional involving multiple tests where we don’t bother doing the later tests if the result of an earlier test makes this unnecessary. For example, in the AND case, if the first test returns FALSE the overall result must be false. Higher-level languages usually distinguish logical AND and OR from bitwise AND and OR and treat the former in a short-circuiting manner. For example, C has && and || as short-circuiting Boolean operators.

In assembly language, we don’t usually explicitly AND or OR the results of multiple tests together. The logical relationship is implicit in the control-flow. For example, in the AND case, if the first test fails we simply jump past the remaining tests, which is of course what the corresponding short-circuit C-code would compile to.

In the case of a short-circuit-AND with no ELSE clause, this is easy to do using our existing structured control-flow macros. We can simply nest multiple _IF..._ENDIFs.

Instead of                                                 we can write

        tst     R8                        tst     R8
        jnz     label                     _IF     Z
        tst     R9                            tst     R9
        jn      label                         _IF     NN
        mov     R9,R8                             mov     R9,R8
label:                                        _ENDIF
                                          _ENDIF

Unfortunately, short-circuit-OR, and any short-circuit with an ELSE clause, is more difficult to do in a strictly structured manner. By “strictly structured” I mean not only using matching sets of keywords that represent one entry and one exit, but also using them in a strictly nested manner (like parentheses [or brackets]). But we can do them by allowing overlapping structures (like these strange parentheses [and) brackets]. In part 2 version 1 I described only this unconventional method. In this version, I still describe the overlapping-structures method, but only as a stepping-stone to more conventional structures.

For short-circuit-OR with no ELSE clause:

Instead of                                                 we would like to write

        tst     R8                        tst     R8
        jz      label1                    _IF     NZ
        tst     R9                            tst     R9
        jn      label2                        _IF     NN
label1:                                   _ENDIF
        mov     R9,R8                             mov     R9,R8
label2:                                       _ENDIF

 

But we can’t do that because the assembler ignores our indenting and takes the first _ENDIF as terminating the immediately preceding _IF NN when we want it to terminate the prior _IF NZ. The trick is to define a word _END_PRIOR_IF as follows.

 

; Resolve a forward jump due to the _IF, _ELSE or _WHILE
; that came _before_ the most recent unresolved _IF, _ELSE or _WHILE.
 
_END_PRIOR_IF  MACRO
               _CS_SWAP        ; Get the label number of the prior _IF or _ELSE back on top of the stack
               _ENDIF          ; Assemble the label for the prior _IF or _ELSE to jump to, and drop its number off the stack
               ENDM
 

Then for short-circuit-OR, instead of                         we can write

        tst     R8                        tst     R8
        jz      label1                    _IF     NZ
        tst     R9                            tst     R9
        jn      label2                        _IF     NN
label1:                                   _END_PRIOR_IF
        mov     R9,R8                             mov     R9,R8
label2:                                       _ENDIF

 

It turns out that _END_PRIOR_IF is all we need, to do short-circuited structures with any number of ANDed or ORed conditions, and with ELSE clauses.

 

The equivalent of this C-like OR conditional with an ELSE clause

 

IF cond1 || cond2 || cond3 ... ELSE ... ENDIF

 

is written as

 

      ...
      _IF not_cc1
          ...
          _IF not_cc2
              ...
              _IF cc3
          _END_PRIOR_IF
      _END_PRIOR_IF
                  ...
              _ELSE
                ...
              _ENDIF

 

As in part 1, I’m using “...” to stand for any number of lines of assembly language. Note the inversion of all but the last condition in the short-circuit OR. An _IF_NOT macro is slightly simpler than the _IF macro we defined in part 1, because it generates a jump with the same condition code as its macro argument.

 

; Assemble a forward conditional with the opposite condition.

 

_IF_NOT MACRO cond

        _JUMP cond, _LABEL_NUM         ; Assemble a conditional jump with the same condition

        _CS_PUSH    _LABEL_NUM         ; Push its label number

        _INC        _LABEL_NUM         ; Increment the label number

        ENDM

 

 

; MSP430 specific.

; Translate the jump instruction generated by _JUMP above, when the condition code is NN.

 

JNN     MACRO   label

        JN      $+4            ; The best substitute for the non-existent JNN instruction

        JMP     label          ; Thanks to Anders Lindgren

        ENDM

 

 

The equivalent of the C-like AND conditional with an ELSE clause

 

IF cond1 && cond2 && cond3 ... ELSE ... ENDIF

 

is written as

 

      ...
      _IF cc1
          ...
          _IF cc2
              ...
              _IF cc3
                  ...
              _ELSE
          _END_PRIOR_IF
      _END_PRIOR_IF
                  ...
              _ENDIF

 

Notice how the _END_PRIOR_IFs come after the _ELSE in the short-circuit AND, whereas they come after the _IF in the short-circuit OR.

 

The implementation of all the other control-flow words in these articles is derived from the implementation of words with similar functions in the language Forth, thanks to Wil Baden. However the evil idea of _END_PRIOR_IF, and using overlapping control-flow structures to implement short-circuit conditionals, is mine.

 

Short-Circuit Conditionals (without overlapping structures)

Without the colouring, the above overlapping control-flow structures are very difficult to read, so I now show how to define a more readable set of macros for doing short-circuit conditionals. They ultimately resolve to exactly the same macro calls, and hence the same jumps and labels, but without those nasty _END_PRIOR_IF s and strange overlapping indents.

 

Since we typically only use assembly language when optimal speed or code-compactness is required, it is essential that the code generated by these control-flow macros does not compromise speed or compactness in any way, but generates exactly the same efficient code that an experienced assembly-language programmer would write using jumps and labels.

 

Think back to the short-circuit-AND with no ELSE clause above, which can be written simply as nested _IF..._ENDIFs. The first readability improvement, due to Wil Baden, is to eliminate the need for all those _ENDIF s at the end, and hence the need for all that indenting to match them up with their _IFs. We do this by bracketing the conditional with _COND..._ENDIFS (note the plural), which pushes a zero marker onto the control-flow stack at the start, and uses it at the end to terminate the resolution of all the _IF s that appear within it.

 

; Begins a short-circuit conditional.

 

_COND   MACRO

        _CS_PUSH 0             ; Push a zero marker onto the control flow stack

        ENDM

 

 

; Resolves all _IFs or _IF..._ELSEs since the previous _COND.

 

_ENDIFS MACRO

        IF _CS_TOP == 0        ; If we’ve reached the zero marker

               _CS_DROP               ; Drop it off the stack

        ELSE                   ; Else

               _ENDIF                 ; Resolve the most recent _IF or _IF..._ELSE

               _ENDIFS                ; Recurse

        ENDIF

        ENDM

 

And we define:

 

; An alias for _IF. Syntactic sugar for short circuit AND conditionals.

 

_AND_IF MACRO cond

        _IF cond

        ENDM

 

Then the short-circuit-AND conditional with no ELSE clause can then be written as:

 

        _COND

            <test1>

        _AND_IF cc1

            <test2>

        _AND_IF cc2

            <test3>

        _AND_IF cc3

            ...

        _ENDIFS                        ; Note plural

 

 

As in part 1, I’m using “<test1>” etc. here to stand for any number of lines of assembly language, the same as “...” but with the specific purpose of affecting some processor condition flag (status bit).

 

To create a non-overlapping structure for a short-circuit-AND with an ELSE clause, we need a plural version of _END_PRIOR_IF, although we won’t use it directly.

 

; Resolve all _IFs or _IF..._ELSEs except the most recent one, back to the previous _COND.

 

_END_PRIOR_IFS MACRO

        _CS_SWAP                       ; Get the label number of the prior _IF or _ELSE back on top of the stack 

        IF _CS_TOP == 0                ; If we’ve reached the zero marker

               _CS_DROP                       ; Drop it off the stack

        ELSE                           ; Else

               _ENDIF                         ; Resolve the prior _IF or _IF..._ELSE

               _END_PRIOR_IFS                 ; Recurse

        ENDIF

        ENDM

 

By the way, debugging these recursive macros was quite an adventure. It gave the word “re-curse” a whole other meaning. ;-) It turned out I had an underscore in the wrong place.

 

Now finally, the last macro we need for short-circuit ANDs:

 

 

; Used in place of _ELSE for short-circuit AND.

 

_ELSES  MACRO

        _ELSE                          ; Assemble an _ELSE

        _END_PRIOR_IFS                 ; Resolve all prior _IFs back to _COND

        ENDM

 

 

The AND conditional with an ELSE clause can then be written as:

 

        _COND

            <test1>

        _AND_IF cc1

            <test2>

        _AND_IF cc2

            <test3>

        _AND_IF cc3

            ...

        _ELSES                         ; Note the plural

            ...

        _ENDIF                         ; Note the singular. Use the plural _ENDIFS only if there is no _ELSES clause

 

 

Now for the short-circuit OR we use _OR_ELSE as syntactic sugar equivalent to _IF_NOT, and we define _OR_IFS (plural) for the last condition.

 

; All short-circuit OR conditions except the last

 

_OR_ELSE MACRO cond

        _IF_NOT cond

        ENDM

 

; The last short-circuit OR condition

 

_OR_IFS MACRO cond

        _IF cond                       ; Assemble an _IF

        _END_PRIOR_IFS                 ; Resolve all prior _IFs back to _COND

        ENDM

 

 

The equivalent of this C-like OR conditional:

 

IF cond1 || cond2 || cond3 ... ELSE ... ENDIF

 

can now be written as:

 

        _COND

            <test1>

        _OR_ELSE cc1

            <test2>

        _OR_ELSE cc2

            <test3>

        _OR_IFS  cc3           ; Note the plural, irrespective of whether there is an ELSE clause

            ...

        _ELSE                  ; Singular

            ...

        _ENDIF                 ; Singular, irrespective of whether there is an ELSE clause

 

 

What about short-circuit conditionals that combine ANDs and ORs? It’s a beautiful result that we don’t need any additional macros for this, we just have to use the existing macros in the right way. But it only works when ANDs come first, followed by parenthesised ORs.

 

IF cond1 && (cond2 || cond3) ... ENDIF

 

is written as:

 

        _COND                  ; An initial _COND as usual, followed by the ANDs

            <test1>

        _AND_IF cc1

        _COND                      ; A second _COND brackets the ORs

            <test2>

        _OR_ELSE cc2

            <test3>

        _OR_IFS  cc3               ; Plural _OR_IFS for last OR condition, matches the second _COND

            ...

        _ENDIFS                ; Plural _ENDIFS, matches the initial _COND

 

 

Now a combined AND/OR conditional with an ELSE clause:

 

IF cond1 && (cond2 || cond3) ... ELSE ... ENDIF

 

is written as:

 

        _COND

            <test1>

        _AND_IF   cc1

        _COND

            <test2>

        _OR_ELSE  cc2

            <test3>

        _OR_IFS   cc3          ; Plural _OR_IFS for last OR condition

            ...

        _ELSES                 ; Plural _ELSES

            ...

        _ENDIF                 ; Singular _ENDIF because there is an _ELSES clause

 

 

Although they can’t actually be written on a single line, I show the six short-circuit structures that way below, to make it easy to compare them, and I highlight the plurals in red and green. Notice that a plural (one of _OR_IFS, _ELSES or _ENDIFS) is always needed to balance a _COND. The combined AND/OR structures have two _CONDs and two plurals.

 

_COND <test1>  _AND_IF cc1       <test2>  _AND_IF cc2 <test3> _AND_IF  cc3 ... _ENDIFS
_COND <test1>  _AND_IF cc1       <test2>  _AND_IF cc2 <test3> _AND_IF  cc3 ...  _ELSES ... _ENDIF
_COND <test1> _OR_ELSE cc1       <test2> _OR_ELSE cc2 <test3>  _OR_IFS cc3 ... _ENDIF
_COND <test1> _OR_ELSE cc1       <test2> _OR_ELSE cc2 <test3>  _OR_IFS cc3 ...  _ELSE  ... _ENDIF

_COND <test1>  _AND_IF cc1 _COND <test2> _OR_ELSE cc2 <test3>  _OR_IFS cc3 ... _ENDIFS

_COND <test1>  _AND_IF cc1 _COND <test2> _OR_ELSE cc2 <test3>  _OR_IFS cc3 ...  _ELSES ... _ENDIF

 

Despite being inspired by Wil Baden’s control-flow implementation scheme in the language Forth, there was no equivalent of these short-circuit conditionals, either in Forth or in Forth-based assemblers, so I wrote them. See

ReadableFlaglessShortCircuitConditionalsForForth.htm

 

A related form of conditional sometimes seen in higher-level languages is the ELSE-IF chain.

 

IF cond1 ... ELSEIF cond2 ... ELSEIF cond3 ... ELSE ... ENDIF
 

There is no point in having an _ELSEIF macro in assembly language, since we almost-always need some code to perform a test before the IF (and after the ELSE). Where a higher-level language can place the test after the IF textually, in assembly language the IF is inherently postfix. So we simply use the existing macros as follows.

 

        _COND

            <test1>

        _IF cc1

            ...

        _ELSE

            <test2>

        _IF cc2

            ...

        _ELSE

            <test3>

        _IF cc3

            ...

        _ELSE

            ...

        _ENDIFS                ; Note the plural

 

 

CASE statement

 

To implement a simple CASE (or switch) statement we only need to define some aliases for the macros used in the above ELSE-IF chain.

 

_CASE  MACRO

       _COND

       ENDM

 

_OF    MACRO cond

       _IF cond

       ENDM

 

_ENDOF MACRO

       _ELSE

       ENDM

 

_ENDCASE MACRO

       _ENDIFS

       ENDM

 

Then we can write:

 

        _CASE

            <test1>

            _OF cc1

                ...

            _ENDOF

            <test2>

            _OF cc2

                ...

            _ENDOF

            <test3>

            _OF cc3

                ...

            _ENDOF

            ...                ; Default case

        _ENDCASE

 

For convenience we can also define:

 

_OF_EQ  MACRO src,dest

        cmp     src,dest       ; Word comparison

        _OF     EQ

        ENDM

 

_OF_EQ_B MACRO src,dest

        cmp.b   src,dest       ; Byte comparison

        _OF     EQ

        ENDM

 

Typical use:

 

        _CASE

            _OF_EQ   #1,R8       ; Uses a word comparison

                ...

            _ENDOF

            _OF_EQ_B #’A’,R8     ; Uses a byte comparison

                ...

            _ENDOF

            ...                  ; Default case

        _ENDCASE

 

 

Counted Loops

 

Here are some macro definitions for post-tested counted loops that count down to zero. These are typically the fastest loops. We use variants of _FOR..._NEXT.

 

_FOR    MACRO src,dest

        mov src,dest           ; Assemble a move instruction

        _REPEAT                ; Mark the start of a loop

        ENDM

 

_NEXT_DEC MACRO dest

        dec   dest             ; Assemble a decrement instruction

        _UNTIL _Z              ; The conditional end of a post-tested loop

        ENDM

 

_NEXT_DECD MACRO src,dest      ; src must be even

        decd   dest            ; Assemble a double-decrement instruction

        _UNTIL _Z              ; The conditional end of a post-tested loop

        ENDM

 

Typical use:

 

        _FOR  #5,R8

            ...                ; Will be done 5 times. _FOR #0 would do it 65536 times.

        _NEXT_DEC  R8          ; Must have same destination as matching _FOR

 

        _FOR  #10,R9           ; Must be even

            ...                ; Will be done 5 times.

        _NEXT_DECD  R9         ; Minus 2 each time, so R9 takes on only even values.

 

 

The complete source code for the IAR/MSP430 version is here. Please let me know if you port it, so I can include a link here. Don’t hesitate to contact me if you spot any errors or omissions above, have any questions or suggestions, or just to tell me if this was useful.

 

-- Dave Keenan, 2018-Jan-9 (last updated 2018-Nov-14)

thing.gif