The IBM Macro Assembler 2.0 included the SALUT program, which was a preprocessor for assembly language for structured programming. It was also included in the IBM Macro Assembler/2.

I am looking for the documentation of this program, but I haven't been able to source the relevant manuals of the IBM Macro Assembler 2.0 or IBM Macro Assembler/2.

  • 4
    A quick look on Internet Archive: IBM Macro Assembler/2 Language Reference. The /2 is in reference to OS/2 not version. It does contain various references to salut and mentions more detail can be found in the IBM Macro Assembler/2 Assemble, Link, and Run book and in the IBM Macro Assembler/2 Fundamentals book. Neither book is currently available from the Internet Archive.
    – Brian
    Sep 21, 2023 at 11:42
  • 1
    @Brian Thanks. I have known this reference, but - as you say - it does not contain any details on SALUT; thus my question here. Sep 21, 2023 at 13:15
  • 1
    The floppies for IBM Macro Assembler-2 1.0 are available on winworldpc.com and while the install doesn't seem to work in DOSBOX you can mount them and look at the contents. The third disk (Utils 2) has some sample .SAL files while the binary is on disk one.
    – Brian
    Sep 21, 2023 at 15:57

1 Answer 1


[Before anyone asks, yes, this question hits a soft spot with me. After all, Structured Programming is everything one needs to follow - all later paradigms are just extensions thereof :))]

Documentation for the SALUT preprocessor of the IBM Macro Assembler 2.0

As mentioned by Brian, it's described in the books "Assemble, Link, and Run" and "Fundamentals" - except noone seem to have cared to scan them so far :(

Likewise the files on the IBM Macro Assembler disk are of little help as of the 5 files used in the SALEX example only two contain SALUT instructions.

Now, while I can not provide any book or link reference,... Found one:

But there's a third party book with a section about SALUT,

8086/8088/80286 Assembly Language
by Leo J. Scanlon
ISBN 0-13-246919-7

which is even available at archive.org (registration required to 'borrow'.

It features on page 311 to 333 a quite comprehensive description of SALUT instructions and their use. Note that it's naming and structure does diverge from IBM's wording - especially reusing the term COMPLEX for a different structure as SALUT's keyword COMPLEX. Nonetheless it'll cover all basic use cases.

Second, while SALUT is not a direct down port of IBM's HLASM Toolkit, its instructions do reassemble the toolkits macros for structured programming. Looking at the mainframe documentation may give some insight. Additionally there's a real nice introduction written in 1999 (*1).

Here a short writeup of what I remembered when browsing thru the SALEX1.SAL file. Hope it's what you're looking for.

Caveat: I never really used SALUT. I had my own system build with macros (no preprocessor needed), modelled after a mainframe programming system COLUMBUS. Though I studied its workings to see if it would be a viable alternative - which it was not. So this is a short read mostly from memory (*2)


SALUT - Structured Assembly Language UTility - is a preprocessor based on direct replacement of Pseudo-Statements by labels and 8086 instructions. It will not rearrange or change any other instruction - although it as well beautifies the source (*3). SALUT provides only structures for selection and iteration. Pseudo-statement will only be turned into labels and (one or more) jump instructions.

SALUT's primary goal is to relief the annoying burden of inventing code labels and selecting the right jump instruction. It does not evolve past basic assembly toward more abstract concepts of structured programming.

Statement Structure

All statements are marked with a leasing dollar sign ($). Most require parameters:

  • Primary structure statements may carry a specifier to select a variant.
  • Conditional statements take
    • an 8086 condition codes,
    • an optional logical operator (AND/OR) and
    • an optional distance marker (8086 short vs near)

All conditional statements must have condition creating statement(s) (like a CMP) before it's placement as SALUT does only do line by line replacement, no reordering or code analysis.

For examples for all cases will be in the IF section below.

All labels generated begin with $$ so it'll be best to avoid that combination in primary (.SAL) source code.

Condition Code

SALUT uses the usual 8086 condition names like

  • A - Above
  • AE - Above or Equal
  • B - Below
  • G - Greater
  • E - Equal
  • Z - Zero
  • ...

As well as their counter arts:

  • NA - Not Above
  • NAE - Not Above or Equal
  • NE - Not Equal
  • NZ - Not Zero
  • ...

In addition CXZ, NXCZ may be used in some cases (*4)

Iterations (ENDDO, ENDSRCH) may use LOOP/E/NE(/Z/NZ) to condition, closing the loop.

Logical Operator

Condition testing statements may have parameters to join them with following statements of the same type by adding and AND or OR parameter. These are 'evaluated' in strict sequence with no priority.

Distance Marker

By nature all 8086 conditional jumps are of SHORT type(*5), that is an 8 bit offset allows a target distance of +/-127 locations. To allow structures holding statement sections of more than 127 bytes SALUT may insert a combination of conditional and near jump, but it needs to be told so by adding a parameter named 'LONG' (*6)

Structures Provided

SALUT provides three structure constructs:

  1. IF-(THEN)-ELSE-ENDIF for simple conditions
  2. DO-ENDDO for generic repetition
  3. SEARCH-ENDSRCH for repetition with two different exit strategies

Selection Using IF

IF/THEN(/ELSE) is about the most basic selection offering to follow one of two code paths. Usually it's formulated as something like

   code block
   code block]

Being the simple search and exchange engine SALUT is its form takes an unusual path:

 condition creating code
 $IF condition
   code block
   code block]

Condition creating code may be anything or nothing at all. The $IF statement will be turned into the rejecting (inverse) version of that jump, i.e. E is turned into NE and vice versa. If an $ELSE statment is present it will generate an uncoditional (short) jump around the following code toward the label generated by $ENDIF.

To compound condition tests additional $IF statements can be added by having the preceding $IF marking the relation (AND/OR) is to be used. A construct like

IF AL=0Dh OR AL=0Ah THEN (handle linefeed) ELSE (char out) (*7)

will have to be written like this and produce code as shown:

[pre SALUT source   ->   post SALUT source]
     CMP  AL,0Dh    ->           CMP     AL,0Dh 
     $IF  E,OR      ->   ;       $IF     E,OR
                    +>           JE $$LL1
     CMP  AL,0Ah    ->           CMP     AL,0Dh 
     $IF  E         ->   ;       $IF     E
                    +>           JNE $$IF1
                    +>   $$LL1:
;(handle linefeed)  ->   ;(handle linefeed)
     $ELSE          ->   ;       $ELSE
                    +>           JMP SHORT $$EN1
                    +>   $$IF1:
;(char out)         ->   ;(char out)
     $ENDIF         ->   ;       $ENDIF
                    +>   $$EN1:

If either block surpassing 128 byes, a LONG parameter needs to be added to $IF and/or $ELSE:

     CMP  AL,0Dh    ->           CMP     AL,0Dh 
     $IF  E,OR,LONG ->   ;       $IF     E,OR,LONG
                    +>           JE $$LL1
     CMP  AL,0Ah    ->           CMP     AL,0Dh 
     $IF  E,LONG    ->   ;       $IF     E,LONG
                    +>           JE $$XL1
                    +>           JMP $$IF1
                    +>   $$XL1:
                    +>   $$LL1:
;(handle linefeed)  ->   ;(handle linefeed)
     $ELSE LONG     ->   ;       $ELSE   LONG
                    +>           JMP $$EN1
                    +>   $$IF1:
;(char out)         ->   ;(char out)
     $ENDIF         ->   ;       $ENDIF
                    +>   $$EN1:

The resulting code shows an important weakness: Condition tests are always generated as basic Jxx statements, not obeying the LONG keyword, making the code not compile if the condition creating statements in total pass 127 bytes (*8).

As mentioned, the condition tests are evaluated in sequence with no way to create sub-clauses (like IF (a OR b) AND (c OR d)), not even a simple precedence of AND over OR, like C provides. As a result code like

     CMP  AL,1
     $IF  E,AND
     CMP  BL,2
     $IF  E,OR
     CMP  AL,3
     $IF  E

will never (do-something) if AL isn't 1 in the first place.

Any complex combination will need nested IF and possible duplication of tests, making SALUT rather adding complications.

Looping By DO

DO provides a generic loop construct which can be configured with termination at the end (think DO UNTIL) as well as early exit (covering DO WHILE and/or BREAK). It consists of three keywords:

  1. $DO starts a loop. There are no parameters.
  2. $LEAVE checks a condition to exit the loop (think conditional BREAK)
  3. $ENDDO closes the loop unconditionally or with an optional condition

LEAVE and ENDDO can optional be linked by AND/OR parameters - also running into the same issues as IF does. ENDDO can also use the LOOP/E/NE condition allowing to count using CX.

Classic loop constructs using DO may look like this:

; Counting down loop (special case)
     MOV    CX,count
     (some code)

     (some code)
     CMP    AL,0Dh    ; CR?
     $ENDDO E         ; End if CR was processed

     CMP    AL,09h    ; TAB?
     $LEAVE E         ; Do not process TAB
     (some code)

     (some code)
     CMP    AL,'$'
     $LEAVE E        ; Do continue if a $ is found
     (some more code)

Looping By SEARCH

SEARCH is a rather unique concept, not exactly standard structured programming. Or even saving much at all in terms of code.

Essentially it's the very same as a DO loop, except that sequence of condition and code blocks differ from prior constructs as there is an additional code block which gets executed before exiting and one after the loop part which only gets executed when leaving thru the $ENDLOOP statement. SEARCH is structured like this:

;Block A            -> Always executed
     $EXITIF  cond
;Block B            -> Executed if <cond> is met
                       Execution continues with Block E 
;Block C            -> Executed if <cond> at $EXITIF was not met
     $ENDLOOP cond  -> loop is continued until <cond> is met
;Block D            -> Executed if <cond> at $ENSLOOP was not met
;Block E            -> Regular execution continues here

If not confused by now, insert additional LEAVE statements (known from DO) before EXITIF or after ORELSE :))

Of course they managed to add even more complexity(*9): One can add a COMPLEX keyword to SEARCH which then would enter the loop code only after a following SARTSRCH statement, allowing to jump over some code during first iteration (*10).

[I'm too tired to come up with a good example - maybe some fs search with template and magic number checks or alike. My brain is simply way too much wired for classic structure blocks to come up with something where this would be useful. At least not tonight. Ask me tomorrow again.]

In any case, the basic idea behind that whole hodgepodge of structure is to have a distinct code block executed if the loop was exited by its primary condition (EXITIF) or a different one if exited due looping until an end (ENDLOOP) or extraordinary (LEAVE) condition was met.

The only reason would be to save an additional check after exiting the loop about which way of exiting was taken. Something that at best save a check and a jump. Not really worth fiddling with that massive mind bender of screwed up structure.

There are a few more variants, but they don't really provide much gain.

Interested parties may go ahead and try SALUT at PCJS. A simple sequence of

<test code like above>

will show all its glory (*11). The virtual disk still has 8 KiB unused space, enough even for complex test scenarios. Have fun.

*1 - The paper was presented at a Share meeting ca. 2000, to convince programmers about the advantage of structured programming. A nice remainder Assembly is anything but a single mindset frozen in the 1960s. Of course that can be seen with other 'primitive' languages as well - like some guys still programming K&R C using a C++20 compiler (GCC 13/MSVC 14) ... yes, guilty as charged :))

*2 - No, my memory is as bad as anyone elses if not way worse, but a short DEBUG session with the version at PCJS did help retrieving the most basic parts.

*3 - Beside outputting a new Assembler source it produced also a beautified version of the input file. A feature I really liked and used a lot to turn ugly micro sources into ones formatted as Assembly was intended to be :

  • Labels at 1,
  • Mnemonics at 9
  • Operands at 17
  • Comments at 41

If only it had also turned all lowercase into uppercase :))

*4 - That's due the fact that only JNCXZ does not exist as 8086 instruction and SALUT does not provide any replacement code when needed.

*5 - It wasn't until the 386 that a NEAR (16 bit) relative distance was introduced.

*6 - Kind of an odd choice when Intel already defined that addressing as NEAR.

*7 - Yes, stupid example. My only excuse it that it's 4 AM.

*8 - Personally I wouldn't consider this an issue as condition creating statements need to be short - except IBM does exactly that in it's only example.

*9 - That whole SEARCH construct is waste of time- way too complicated and only good to serve an edge case. SALUT would be way more useful by adding more basic SP constructs. Just imagine how much a basic case/switch statement can improve handling and readability over nested IF mining.

*10 - Seriously, I have a hard time to express how much this anoyes me by going against everything structured programming is about. SP is meant to make life simple by adding straight structures, easy be followed in top down fashion with no hidden traps and detours. It is IN NO WAY about providing syntactical sugar for byzantine constructs.

*11 - I would think that SALUT is written using CP/M compatible FCB access as produced file length always end up in multiples of 128 bytes.

  • Wow! That was really helpful. Now, I have something to try out. Of course, it would still be nice if someone could source the original documentation, but thanks to your memory, it is no longer that important—greetings from the city west of you. Sep 22, 2023 at 12:01
  • 1
    @MarcNieper-Wißkirchen Those tools were plenty and SALUT is one of the least imaginative and useful ones - it was already outdated by 20+ years when published. Adding COMPLEX allows to have a initialising section after $SEARCH, terminated by $STRTSRCH. It's just eye candy making a bad construct look even worse. Any serious attempt to use SALUT should stay with $IF and $DO. IIRC COMPLEX can also be used with $DO for the same purpose. Would need to search for that syntax again.
    – Raffzahn
    Sep 22, 2023 at 14:48
  • 1
    I experimented a bit; $DO COMPLEX needs a $STRTDO. Everything between the two constructs is executed at the beginning of every loop iteration after the first. Sep 22, 2023 at 15:22
  • 1
    @MarcNieper-Wißkirchen Found a (some) reference. Seems also to be not the worst possible book to earn 86 assembler.
    – Raffzahn
    Sep 24, 2023 at 21:17
  • 1
    Thank you; that's a great reference for my question. And it proves that your memory served you well! (NB: The author of the book also decided to skip the "DO COMPLEX" and "SEARCH COMPLEX" forms. ;)) Sep 26, 2023 at 6:01

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .