Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / assembler

How to Turn MacroAssembler into a High Level Language (2)

3.33/5 (3 votes)
20 Sep 2021CPOL6 min read 5.2K   68  
Macros to help assembler programmers to improve source code (continued)
Ampliation of macro definitions for Assembler to be able to use sentences as SWITCH / CASE / [BREAK] / [DEFAULT] and ENDSWITCH

Introduction

As mentioned in the previous article, the power of high level languages resides on the capability of writing human readable syntax that the compiler translates to machine code. In the previous article, I commented the way of writing macros such as "IF/THEN/[ELSE]/ENDIF", "FOR..NEXT", "WHILE..WEND" or "REPEAT..UNTIL". In this article, I explain how to build macros for "SWITCH/CASE/[BREAK]/[DEFAULT]/ENDSWITCH" although I think, SWITCH / CASE in High Level Assembler syntax shouldn't be implemented because, in structured programming, this high level syntax is a sequence of blocks of 'if condition(s) then /*actions*/' statements.

Anyway, they can try to develop these macros for those who want to use them.

Background

MacroAssembler is not a friendly language. With the use of these macros, you can improve the reading of your code and write a more comprehensible text. Have you tried to modify a program you had written in the past?. (You'll know what I mean). What about 'debugging'? (The same). Have you listed an .ASM source code?. (So on.)

Using the Code

In this article, I post the definition of the previous macros ("IF/THEN/[ELSE]/ENDIF", "FOR..NEXT", "WHILE..WEND" and "REPEAT..UNTIL") as a file to include in your source code (macros.inc) and the definition of new macros ("SWITCH, CASE, [BREAK], [DEFAULT] and ENDSWITCH") as a file to include (switch.inc) after the previous one. Also is included a little source program to test the macros (Test.asm).

Explanation of Switch, Case, Break, Default and EndSwitch

Studying high level languages syntax, we can observe little differences between languages:

VB Syntax VC Syntax
select case n
    case 0:  ...         
    case 6 to 9, 11 to 12 : ...
    [case else] ...
end select
switch(n) {
    case 0: ... ; break;
    case 6,7,8,9,11,12: ... ; break;     
    [default] ...
}

The great difference between VB and VC is that VB doesn't have a token as 'break', so in each 'case' and after the last action, VB jumps outside the 'Select Case' (that means that 'break' is implicit in each 'case' in VB). By the other side, if we include 'break' as the last token in all VC 'cases', then the similarity to VB in its functionality is evident, but ... 'break' is an optional token in VC. So, if it is included after the last token in a 'case', it will 'jump_out_of_switch' after the execution of the actions, but if it's not included, then the compiler will do a little jump to the first action of the following 'case' statement, ignoring the next 'case' test conditions.

To be a little more clear, let's examine a function in VC:

C++
void function(int i)
{    
    int value = -1;
    switch(i)
    {
        case 1:
        case 2:
        case 3: value=0; 
            break;        //jump out of switch
        case 4: value=1;  //note break is not included here
        case 5:
        case 6:
            error(value);
            break;        //jump out of switch
        default:
            DoSomething(value);
    }
}
//The cases 1,2,3 do the same thing : value=0 and jump_out_of_switch.
//The case 4, assigns 1 to value, but as no 'break' is provided, 
//execution will continue with error(1), skipping the labels 'case 5' and 'case 6'

In other words, VC is more flexible and powerful than VB in SWITCH/CASE statements, but increases the complexity of compilation.

In both languages, a Switch starts with a variable (n in the sample) to test against constants. In Assembler, we'll give the possibility of using a variable or register to test against the constants for each 'switch'. So, the first thing we have to do is remember which variable or register has been assigned to a 'switch' and which number of 'switch' we are processing, because these kind of blocks could be nested too. This variable will be $switchcount, and starts at zero at the beginning of our program.

$SWITCH Macro VarOrReg

The SWITCH macro consists basically of two jumps:

  1. a jmp to Start_of_Cases and
  2. a jmp Out_Of_Switch that will be assigned by $ENDSWITCH. All cases with 'break' will jump here, and then, Out_Of_Switch

We could create labels in our program as Out_Of_Switch_1, Out_Of_Switch_2 ... after every $ENDSWITCH and assign the jmp Out_Of_Switch in a way like $SWITCH i, Out_of_this_switchLabel, but this doesn't seem to me a beautiful solution.

As said, we have to remember info about every 'switch' as which variable or register to use in each case and the address of its jump out_the_switch_block. So, we will define two helpers for the $SWITCH macro: $SetSwitchInfo and $GetSwitchInfo (see the companion code for these macros as well as for $SWITCH)

$CASE Macros

The syntax of a 'case' statement is quite complex to implement, so we are going to create two different syntaxes to process a 'case', that will be explained later:

  1. $CASE <argList> to process a list of constant values, i.e., $CASE <1,2,3,5,18>
  2. $CASEFROMTO const1, const2 to process a range of values, i.e., $CASEFROMTO 5,15

To start with, remember that when a VC compiler finds a 'case', the internal processing is something like:

C++
//Start_Of_Case:
        /* Test conditions */
        if none of the conditions is meet {
//NoCondsAreMeet:
            jump_to_next_case;
        }

//Start_of_Actions:
        /* process the actions provided */
        /* jump_to_somewhere */
        // where somewhere is:
        //   out_of_switch if 'break' is provided or
        //   start_of_actions_of_the_next_case if not;

So, here starts the complications:

  1. The actions provided for this case are outside the macros $CASE or $CASEFROMTO and aren't assembled yet
  2. Next_Case, start_of_actions_of_the_next_case and somewhere are undefined because they haven't been assembled yet
  3. $BREAK, if provided after this 'case' macro, is outside the macro too.

Fortunately, jump_to_somewhere is the last instruction in a 'case' block being placed after the actions of the block. So, when we start processing the following 'case', we know that the address of the preceding jump_to_somewhere is just the address where assembler is assembling just now. At this point, we know too if the preceding 'case' has used 'break' or not.

Quote:

We can create the jump_to_somewhere of the preceding 'case' at the start of the current one.

To do so, we need to keep track of which number is this 'case' into the current 'switch'. When we start a 'case', we increment the 'cases' counter of the 'switch', and when this counter is greater than one,
If the previous 'case' as used 'break', we will assemble a jmp out_of_switch
else we will assemble a jmp start_of_actions in this 'case'.

To keep control over each 'case', we'll define helper macros such as $GetCaseInfo, $SetCaseInfo. In each 'case', we'll assign control variables that respectively mean:

$PrevSomewhere: is the address of the previous 'case' jump_to_somewhere, followed by three nops that will became a jump out of switch (if break is used in the previous case) or a jump to 1st action of the next case (if break is not used)

$This_Start_Of_Case: Address of the real logic starting address of this case

$This_JmpToNextCase: At runtime, when none of the constants is equal to the test value, we have to jump to the next_case_Start_Of_case. This is the address to put the jump (known at the evaluation of the next case).

$This_1stAction: Address of first action of this case that will be assembled a bit later

The helper $AdjustPrevCase assigns the previous 'case' jump_to_next_case to $This_Start_Of_Case and its jump_to_somewhe (this $PrevSomewhere) as mentioned previously. $SetCaseInfo will save information about the current 'case'.

$CASE Macro argList

ASM
Local TestOK
    $PrevSomewhere = this near
    $reservejump

    $IncSwitchCases %$switchcount   ;;Cases(switch)++

    $This_Start_Of_Case = this near
    IRP arg,<argList>
        cmp    $switchname, arg
        jz  TestOK
    endm
    $This_JmpToNextCase = this near,
    $reservejump

    $This_1stAction = this near
TestOK:
    $AdjustPrevCase
    $SetCaseInfo %$switchcount, %$switchcase ;;Finally, we save info of this case
    endm

$CASERANGE Macro FromValue, ToValue

The structure or this syntax is equal to the $CASE. Only change the way of testing the constants passed to the macro.

[$DEFAULT Macro]

[$DEFAULT] is a special 'case' that has no test values but only actions. The internal structure and process of previous 'cases' is the same as $CASE and $CASERANGE.

[$BREAK Macro]

[$BREAK] does nothing, since the required jumps are processed at the start of $CASE, $CASERANGE or $DEFAULT. It only identifies if the previous $CASE, $CASERANGE or $DEFAULT uses 'break'.

$ENDSWITCH Macro

When a switch block is ended with $ENDSWITH, we have to replace its last 'case' jump_to_the_next_case nops with a jump outside the block. Also, we have to set the address $switchquit of the block with a jump to this AddressOfOutOfSwith.

ASM
AddressOfOutOfSwitch = this near
$PrevSomewhere      = this near
$This_JmpToNextCase = this near
$This_Start_Of_Case = this near
$This_1stAction     = this near

if $switchcase gt 0
    $SetJump2NextCase %$switchcount, %$switchcase
endif

$ReplaceNops4Jmp $switchquit, AddressOfOutOfSwitch
    $switchcount = $switchcount-1
if $switchcount gt 0
    $GetSwitchInfo %$switchcount
endif
endm

Points of Interest

And that's all folks! You can play the companion sample program to test this macros.

Once the mechanism of the 'cases' has been understood, you could implement other syntaxes for them. By the way, I've changed the name of the previous $makejums macro @ macros.inc to $reservejump, because this name is more intuitive and I've changed the definition of the first (3 consecutive nops) to db 90h, 90h, 90h in the second, because the expansion of the macro is shorter in the listings.

History

  • 19th September, 2021: First version of SWITCH/CASE/[BREAK]/[DEFAULT]/ENDSWITCH macros. Few changes are being included in the first version of macros.inc.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)