وو

وحید آنلاین . آرشیو وبلاگ وحیدمی دات آی آر . شرکت بیان. vahidmy.blog.ir

وو

وحید آنلاین . آرشیو وبلاگ وحیدمی دات آی آر . شرکت بیان. vahidmy.blog.ir

MMX and XMM


MMX and XMM   ...



All throughout its history, the x86 Processor family has been featured with new Instructions. Nowadays, the set is more than 300 instructions long (not counting the variants and forms), but, if you take a look at any real Application, whatever Language, whatever size, you will see that it makes real use of 20 Instructions, sometimes 25, hardly ever any more. The very first goal of the new implementations is making money and in increasing its lead over competition . 


Each time you make use of these newer Instructions, you are saying to your eventual users who might have an older Computer: 'Guys, my Application does not run?


 Normal! Just buy a new Computer'. Old computers are now regularly destroyed and replaced by new ones, therefore following a similar path as the one invented by M$ to sell you several times over the same Operating System. I am not claiming that Computer evolution is not desirable, but forcing people to change every 3 or 4 years is preposterous. We can slow down this greedy spoiling with our own individual decisions.



RosAsm_MMX RosAsm_XMM


~~~~~~~

FPU instructions


FPU instructions  ...



Real numbers declarations examples:


[Real1: R§ 65478     Real2: 0.628      Real3: 0.11123E-12]


'R$' size definition is nothing but Qword. It allows you to declare '55478' as an integer stored real (instead of more procedural syntax like: '65478.0' or 'Q$ r65478', for  example).


>>>>>>>>>>>  Real numbers must be expressed in Decimal  ASCII . <<<<<<<<<


For composed number (ex: F$ 12-3) and multiple negative numbers, apply the same rules as for code numbers. This is to say that the parser strips any space before a +/- sign and that you have to either specify a comma or to precede your signed number by a zero:


[Real1: R§   0-65478,  -12    0+24]             ; Either ',' or '0' to prevent  Parser space stripping.


Here too, I recommend the leading zero version as the preferred cleaner one.



Available size specifiers for Data declarations are:


         - F$ > Real  4 (32 bits)    - Same size as D$ -

         - R$ > Real  8 (64 bits)    - Same size as Q$ -

         - T$ > Real 10 (80 bits)



For Code Reals-immediates, syntax is, for  example:


        push F§0.01

        mov eax F§45


... a bit out of logic but, on one hand, much simpler for the compiler than having to check ALL immediates to know if they are Reals or Integers and, on the other hand, simpler for the user than having to memorize a syntax convention different from the Data/Mem syntax convention. 


Clean clear writing should avoid immediate Real in code Statements and Equates, and should use Data. If you feel you need this feature, you have review the FPU design.



Math processor instructions have some specifics:


FPU registers are noted ST0, ST1, ..., ST7 (without parenthesis)


ST, instead of ST0, does NOT exist.


In most instructions that require a register as parameter, you can omit it if you mean the usual one. for example, all following statements are good and equivalent:


      FDIV | FDIV ST1 | FDIV ST0 ST1

or:

      FDIV ST2 | FDIV ST0 ST2


this is to say that, if a register is required and you do not specify any,  it is supposed to be ST1 as source register (ST0 as destination, of course). This is a bad feature. You should avoid this, and conform to the NASM standard, which is to never assume any register. Your source will be more readable, and easier to debug and maintain, if you always specify all the registers.



Some FPU instructions need particular pre-defined memory areas:

          

       FSAVE  >>>  108 bytes mem

       FLDENV >>>    28 bytes mem

       FRSTOR >>>  180 bytes mem

       FSTENV >>>    28 bytes mem


I previously chose to NOT assume these formats and to refer to them as dWords. From V.2.07 on, I introduce ''X$'' notation to resolve all these weird cases (and others in future), at once, in code:


In Data, just set the desired room. Example:


[FPU_State: B§ 0 #108]


fsave X§FPU_State


 

In all others cases when FPU instructions have a memory parameter, RosAsm fully controls fitting sizes.


~~~~~~~



ParaMacros


ParaMacros  ..



Macros used as Parameters


Normal Macros can not be used as Instructions' Parameters, but an extended feature is available when you want more evoluted formulations of your Instructions' Parameters: With the use of the  '{' and '}' Characters, you may write things like:

_________________________________________


; Normal macros:


[RGB | (#1 or (#2 shl 8) or (#3 shl 16))]


[ReverseByte | (#1 xor 0FF)]


[call | push #L>2 | call #1]


; Normal Procedure:


Proc MyRoutine:

   Arguments @Val1, @RGB, @Val3, 

             @String1Pointer,                            @String2Pointer, 

             @RealPointer

   Local @Integer

   

    Hexprint D@Val1

    hexprint D@RGB

    Hexprint D@Val3

    mov eax D@String1Pointer

    Showme eax

    mov eax D@String2Pointer

    Showme eax

    mov ebx D@RealPointer

    fld R$ebx

    fistp D@Integer

    Hexprint D@Integer  ; 04D2 >>> 1234

EndP

_________________________________________


; Use of Parameters Macros:


call MyRoutine,

     1, 

    {RGB 011, (011*2), {ReverseByte 0}}, 

     3,

    {'Hi!', 0}, 

    {'Coco!', 0},

    {R$ 1234,5}

_________________________________________


The second Parameter shows the usage of a {RGB Macro} unfolded as an expression, which is, finally resolved into one single Immediate. Notice that nested Invocations are allowed: ReverseByte is one another ParaMacro Invocation.


Such ParaMacros, for Parameters, are to be unfolded, of course, into something that should be accepted as any other normal Parameter (Expressions, ...). For example, you can not include Run-Time Values -D$MyValue-, or Mnemonics, as this would be the job of a Compiler (outputting silently things, that you have not written, behind your back), and not of a true Assembler. This feature, like normal Macros and Equates belongs to the Compile-Time process.


Take care that abuse of ParaMacros, with multiple Levels of nested Members, will tend to make your Sources as unreadable as those written in C .



On the Fly Data


In addition to these Parameters Macros, you may, with the same '{' and '}' Characters, to define Data on the fly. 


The frontier between the true Assembler and Compiler is close to being overflown with this feature, as seen in the example's Parameters 4 to 6: In case of direct Data Declarations, the ParaMacros parser outputs something behind your back without your knowledge: That is, Macro Automatic Labels. What is pushed on the Stack, for Parameters 4, 5 and 6, is nothing but Automatic Labels to the concerned Data, in the same way they are generated when you declare Data by normal Macros. 


If the Data are Strings, the String Markers ( '  or '' ) are all you need for a Declaration. For the Values Data (Reals or Integers), you have to provide the Size Markers (R$, Q$, or whatever...), the usual way.


Though I am not enamored with love and desire for this last implementation, I choose to provide it because it seems to be much appreciated by some users, as a useful means to implement quick and dirty definitions of 'one shot' Data. 


~~~~~~~

Macros Examples


Macros Examples .



Mutli Mov


[mov | mov #1 #2]


mov eax 1, ebx 2, ecx 3


Unfolded:


 mov eax 1 | mov ebx 2 | mov ecx 3




MemToMem Move


[move | push #2 | pop #1 | #+1]


move D§Variable1 D§Variable2


Unfolded:


 push D§Variable2 | pop D§Variable1




Mem Exchange


[push #1 | push #2 | pop #1 | pop #2 | #+1]


Exchange D§Variable1 D§Variable2


Unfolded:


 push D§Variable1 | push D§Variable2

pop D§Variable1 | pop D§Variable2




Call


[push | push #1 | #+1]

[call | push #L>2 | call #1]


call MyFunction


Unfolded:


 call MyFunction


Example with parameters:


call MyFunction D§Variable1 D§Variable2


Unfolded:


 push D§Variable2 | pop D§Variable1 | call MyFunction




Using HLL Equates in HLL Macros


[= e, < b, > a, <s l, >s g, =< be, <= be, => ae, >= ae, <> ne]


Usage example, in the next example:




One Intruction Conditional Jump


[On | cmp #1 #3 | jn#2 O1 | #4>l | O1:]


On eax > 55, call MyFunction D§Variable1, D§Variable2, eax


Unfolded:


    push cmp eax 55 | jna O1>

  push eax | push D§Variable2 | push D§Variable1

call MyFunction

O1:



Enum


[Enum | {#2 (#x-1+#F)}| #+1]


Enum 0, One, Two, Three, Four


Unfolded (An Equates Set Declaration is outputted, where the enumeration base is the first number after 'Enum'):


[One 1, Two 2, Three 3, Four 4]




Procedure


Used Internal Strings:


&1, size of Argument(s) (for ending Ret n, in EndP). Set by Argument(s)

&2, size of Local (for Stack Management). Set by Local

&3, what to pop before ret. Set by Uses.


[Proc | &1=0 | &2=0 | &3= | #1 | push ebp | mov ebp esp]


[ExitP | jmp P9>>]


[Arguments | {#1 Arg#x} | #+1 | &1=SizeOf#x]

[Argument  | {#1 Arg#x} | #+1 | &1=SizeOf#x]


[Local | {#1 Local#x} | #+1 | sub esp SizeOf#x | &2=SizeOf#x]


[StrucPtrs | {#3 ebp+#2+#F} | #+2]


[Structure | {#1 ebp-&2-4} | sub esp #2+4 | mov D§#1 esp | StrucPtrs 0-&2-#2-4 #L>3]


[Uses | push #1>L | &3=pop #L>1]


[EndP | P9: | &3 | mov esp ebp | pop ebp | ret &1]


Equates for pointing to transmitted parameters:


[Arg1 ebp+8, Arg2 ebp+12, Arg3 ebp+16, Arg4 ebp+20, Arg5 ebp+24 Arg6 ebp+28, Arg7 ebp+32, Arg8 ebp+36, Arg9 ebp+40, Arg10 ebp+44]


Equates for pointing Local Stack data:


[Local1 ebp-4, Local2 ebp-8, Local3 ebp-12, Local4 ebp-16, Local5 ebp-20, Local6 ebp-24, Local7 ebp-28, Local8 ebp-32, Local9 ebp-36, Local10 ebp-40]


Equates for the Stack Sizes:


[SizeOf1 4, SizeOf2 8, SizeOf3 12, SizeOf4 16, SizeOf5 20, SizeOf6 24, SizeOf7 28, SizeOf8 32, SizeOf9 36, SizeOf10 40]



Proc MyProcedure:

Arguments @Line, @Row

Local @Color

Structure @POINT 8, @xDis 0,  @yDis 4

Uses esi, edi, ebx


; ...

EndP

Notice that this EndP restores the stack by Popping the preserved Registers, as saved into the &3 Internal String, and that the Structure is given by the Structures Dialog




MessageBox


[MessageBox

#If #N=1

call 'USER32.MessageBoxA' &NULL, {#1, 0}, {'Hi', 0}, &MB_OK

#Else_If #N=2

call 'USER32.MessageBoxA' &NULL, #2, {#1, 0}, &MB_OK

#Else_If #N=3

call 'USER32.MessageBoxA' &NULL, #2, {#1, 0}, #3

#Else_If #N=4

call 'USER32.MessageBoxA' #1, #3, {#2, 0}, #4

#Else

#Error 'The MessageBox Macro expects 2, 3 or 4 Parameters'

#End_If]


MessageBox 'Hello'


Unfolded:


 push &MB_OK | push &0 | push &0 | push &NULL

call 'USER32.MessageBoxA'


If more parameters are given in the Evocation (represented by the #N Condition), they are supposed to be: 1) for the Title, 2) for the Style, and 3) for the Parent. If more than four Parameters are passed to the Macro, it produces an Error Message, at Compile-Time.


~~~~~~~

Conditional Macros


Conditional Macros .



The  #If  Keywords


All Conditional Statements begin with a '#' Char:


#If....

    ; Do this

#Else_If....

    ; Do that

#Else

    ; Do in other cases

#End_If


These '#If' Statements can be nested, and the cases of unpairings raise an error Message.



The Conditions


You can test the Macros Evocations Parameters, on Types and Sizes, the Internal Strings on Contents, the Number of Parameters in the Evocation, and the Internal Counters on Contents.



Testing the Types&Sizes


The Types are:    reg, mem, imm, str, sym


... standing for:   register, memory, immediate, string, symbol.


The str (string) Condition, for example, that is:


Else_If  #1=str


... addresses the cases of direct Strings, in the form of:


MyMacro  'My String'


Notice that all Conditions are to be given without any spaces inside. That is, for example:


#1=reg


... never:


#1 = reg



The Sizes are the usual ones, as found in the Sizes Markers List:


B, W, D, Q, F, R, X


Example:


#If  #1=reg

    #If  #1=D

        ; Do this with a dWord Register

    #Else

        ; Do that with other Sizes Registers

    #End_If

#End_If



Testing the Number of Parameters


The 'N' Condition tests the Number of Parameters, in the Evocation. Example:


#If  #N=0

      push 0

#Else

      push #1

#End_If



Testing the Internal Strings


Let us recall that the Internal Strings (the &1,.... &99 thingies), are implemented, in the normal Macros Engine, for storing strings. That is, when you say, for example:


... | &5=12345 | ... &7=#2 |...


... what is stored in the Internal Strings are nothing but the ''12345'' String or the ''Parameter_2'' String.


So, what you can test, with the Conditional Macros, is the content of an Internal String, to know if the room is free or not:


#If  &55=0


or


#If  &55<>0


Notice that you cannot, both at the same time, inside the same Macro Declaration, set up some Internal String and test its content. If  the Internal String was empty before the Macro Evocation, it will still be empty when the Conditional Macro parser considers the content.



Testing the Internal Counters


The only tests actually implemented are for 'empty' / 'not empty' state:


#If &&55=0


#If &&60<>0


For more, see Internal_Counters.



The #Error Statement


Inside Conditional Macros, you can make use of User Defined Error Messages, assuming your own cases of misusage in your own Macros.


Example:


[MyConditionalMacro

; ...

#If  #1=reg

    ; Do this

#Else

    #Error  'MyConditionalMacro works only with Registers'

#End_If

...

#+1]



The #ErrorPos Statement


The Internal Counter also accepts (normal Macros engine) a Statement saying:


... | &&2=Pos | ...


What is then stored, inside the Internal String is the Source Position, at the Macros Job time. This feature is useful for forcing the RosAsm Error Manager (the Compile Time Error Manager), to point to a Source Location that you have defined that way, when stating:


#If &&2<>48

    #ErrorPos &&2,  'Unpaired use of the While Macro'

#End_If


(48 stands for Ascii '0')


In this example of ''While // End_While'' Macros usage, you can make an unpairing error. If you test the content of an internal Counter, - that you use for defining the ''W0 to W9'' Local Label -, at the very first ''While'', and find it to not be ''W0'', this means that an unpairing error has been committed previously. So, this feature offers you to define where, in your Source, the error should be pointed out: not on the actual first ''While'', but on the previous ''While'', that can be, as well, several pages above, in your Source.



General Usage


You can, of course make use of the usual Macro Loop, for testing the Parameters, say, one by one, with the usual '#+1'.


~~~~~~~




Internal Counters


Internal Counters .


The Macro Parser manages 100 internal dWords Counters, that have been implemented at the same time as the Conditional Macros parser. Like the Internal Strings (&0 and &1 to &99), the Internal Counters are from &&0 to &&99.



Storing


... | &&1=25 |...


Notice that, what is stored, here, is a real dWord, not a String, like with the internal Strings


There must not be any space in between the Counter and the equal Char:


... | &&1  =  25 |...  ; Wrong!!!


Though you can provide a space after the Equal Char (required anyway, for Strings defintion):


... | &&1=   'A' |...  


The Numbers >Attribution can be given in Decimal, Hexa and Binary. The Text substitutes can of course not be more than four characters, the same way you can do it in, say:


mov eax  'abcd'


You can also store the value of another Counter in the destination one:


... | &&1=&&30 |...


and indicate a simple Addition / Subtraction:


... | &&1=&&1+4 |...




Pos Storing


This feature is associated with the Conditional Macros User defined error feature. When stating:


... | &&5=Pos |...


...what is stored in the 5th Counter is the Position of the Source parsing. Once this is done, you can define a Conditional error, and force the Assembler to locate the Error Position, at the Statement matching with the Pos Storage.


For example, in cases of unpairing errors, for HLL constructs, you can have the error pointed out, at the Top of the faulty Construction, whereas the error could be, as well, detected, by the Conditional Macro several pages downward.


See usage examples in Conditional_Macros.




Outputting


When using an Internal Counter for writing (for outputting something, into your Macro Unfolding), this is not a dWord, that is written, but a Byte, so that the Internal Counters could be used as a Char manipulation tool.


The interest of this Byte feature comes out when developing HLL Constructs Macros, where a Local Symbol must be incremented / decremented, depending on the nesting level of the HLL Construct. Example: W0 to W9 Local Labels, inside a Conditional ''While'' Macros Set.



Testing


As these Internal Counters have been implemented at the time of the Conditional Macros Implementation, in order to provide the Macros with conditional Values, testing is described in Conditional_Macros.


~~~~~~~



Internal Strings


Internal Strings .



The Macro Parser manages 100 internal Data storages (128 bytes each) where you can store what you want, under a text form, with, for example:


[MacroName1 | ... | &3=Some#1 | ...]


If MacroName1 is invoked with:


MacroName1 Where


... The Macro parser will store 'SomeWhere'  in 'internal_Data_3. If this Evocation of 'MacroName' is followed, for example, by


[MacroName2 | nop | mov D$&3 0 | nop | ...]


... evocation of:


MacroName2 


... will be unfolded as:


Nop | mov D$SomeWhere 0 | nop | ...


The order of Declarations in source has no importance. All this is based on the order of Evocations .


The Internal Data are never zeroed, just overwritten as the Macro Evocations come out, in the source order. The effective order is not the Declarations order but the Evocations order. To clear an internal Data:


[ClearIntern3 | &3= | ...]

 

ClearIntern3


These internal Data and nested Declarations with substitutes are useful for HLL style developments, for building  full HLL- like syntaxes with advanced features (Proc / Arguments / Local / Structures / Uses / EndP).



The Macro Parser Internal storages are from &1 to &99.


&0 is reserved to provide meaningless automatic Labels when declaring Data in Macros.


Example:


[Argh: 'Aaarrrghhh!!!!....', 0]


[MessageBox | {&0=#2} | {&1call 'USER32.MessageBoxA' &NULL, &0, #1, &MB_SYSTEMMODAL__&MB_OK]


... to be called, for example, by:


MessageBox  Argh , 


                        'Oh! No, please!!! not that again!... I want to sleep now!'


The internal replacement Labels are in the form of 'ZZZZZZZZ' / 'ZZZZZZZY' / ... Each time an evocation requires an '&0:' declaration, the Parser decreases the trailing characters and performs the substitutions. Each time a Macro evocation requires an '&0' Label evocation, it uses this substitute.


You cannot use '&0' to store anything on your own, like you do with other 99 internal storages.


Limitation: you can only use the Automatic Labels one by one. That is, you create one Automatic Label with the '&0:' declaration, and you can use it (Evocation) as many times as you like, but you cannot make use of two different Automatic Labels, in one Declaration.


Note that this limitation does not make it impossible to have as many meaningless automatic Labels as you really want: You can, of course, nest the macros. Example:


[MessageBox | {&0: #2 0} | MessageBoxTitle #1

 call 'USER32.MessageBoxA' &NULL, &0,  eax, &MB_SYSTEMMODAL__&MB_OK]


[MessageBoxTitle | {&0: #1 0} | mov eax &0]


MessageBox 'Application Base',  '

   This file is not a Demo. It is a 'StartUp'

   Base You can use to develop your own work.   '



Examples of '&' usages


For/Next Loops


It is a well known organization encountered in all Basic Sources. Here is how to build a Macro for this, with very few limitations (only the number of available characters for local Labels):



[For | push ecx | &4=#5 | &5=1 | &5=#7>L | mov D$#1 #3 | &6=#1 | &7=0 |  &6_&7: | push &4 | push &5]


[Next  | pop ecx | add D$#1 ecx | pop ecx | cmp D$#1 ecx | &6=#1 | &7=0 |  jbe &6_&7<< | pop ecx]

[Negxt | pop ecx | sub D$#1 ecx | pop ecx | cmp D$#1 ecx |  &6=#1 | &7=0 |  jae &6_&7<<  | pop ecx]


; I use a, b, c,... instead of i, j, k, ... because 'i' is  usually for If and friends.

;

; Note: Uses ecx and the Stack internally.


[a: 0    b: 0    c: 0    d: 0    e: 0    f: 0]


; To be used, for example as:


    For a = 1 to 5

        hexprint D$a            ; shows 1 / 2 / 3 / 4 / 5

    Next a


    For a = 1 to 5, Step 2

        hexprint D$a            ; Shows 1 / 3 / 5


        For b = 1 to 2

            hexprint D$b        ; shows 1 / 2 // 1 / 2 // 1 / 2


            For c = 0400 to 0100, Step 0100

                hexprint D$c    ; shows 0400 / 0300 / 0200 / 0100 // ....

            Negxt c


         ; (Negxt is the Negative Stepping form).


        Next b


    Next a


Note, that in:


[For | ...  ]


' | &5=1 | &5=#7>L | ' This is a bit tricky, and may be difficult to understand:


In 'For b = 1 to 2', for example, there is nothing on the line for '&5=#7>L '. So it does nothing and &5 is simply set to 1. In 'For a = 1 to 5, Step 2', '&5=#7>L ' does its job; this is to say, only stores 2 in &5. 'To' and 'Step' keywords are fully dummy representations only and are there only for the visual comfort of the user!  '#7>L ' is used instead of  '&5=#7', just to allow no parameters. This is the most tricky point.



Multi-If


I recommend, for If and friends the use of Macros beginning with points (the one given in the BaseX.exe files). They greatly increase readibility and, so forth, to prevent the many usual organization errors. Another point is that, having too many nested If structures, gives unreadabe and difficult to maintain sources. Your If chunks should be limited to 3 of 4 nested levels. If this is not enough, reorganize with a call to another Routine, even if the internal logic of the source would not require this; or mix with other statements (Select_Case / ...)


But if you really want a true HLL-like If statement, this is possible too. Example with 10 nested Levels:


[.If | cmp #1 #3 | jn#2 I0>>]

[..If | cmp #1 #3 | jn#2 I1>>]

[...If | cmp #1 #3 | jn#2 I2>>]

[....If | cmp #1 #3 | jn#2 I3>>]

[.....If | cmp #1 #3 | jn#2 I4>>]

[......If | cmp #1 #3 | jn#2 J0>>]

[.......If | cmp #1 #3 | jn#2 J1>>]

[........If | cmp #1 #3 | jn#2 J2>>]

[.........If | cmp #1 #3 | jn#2 J3>>]

[..........If | cmp #1 #3 | jn#2 J4>>]


[.Else_If | jmp I5>> | I0: | cmp #1 #3 | jn#2 I0>>]

[..Else_If | jmp I6>> | I1: | cmp #1 #3 | jn#2 I1>>]

[...Else_If | jmp I7>> | I2: | cmp #1 #3 | jn#2 I2>>]

[....Else_If | jmp I8>> | I3: | cmp #1 #3 | jn#2 I3>>]

[.....Else_If | jmp I9>> | I4: | cmp #1 #3 | jn#2 I4>>]

[......Else_If | jmp J5>> | J0: | cmp #1 #3 | jn#2 J0>>]

[.......Else_If | jmp J6>> | J1: | cmp #1 #3 | jn#2 J1>>]

[........Else_If | jmp J7>> | J2: | cmp #1 #3 | jn#2 J2>>]

[.........Else_If | jmp J8>> | J3: | cmp #1 #3 | jn#2 J3>>]

[..........Else_If | jmp J9>> | J4: | cmp #1 #3 | jn#2 J4>>]


[.Else | Jmp I5>> | I0:]

[..Else | Jmp I6>> | I1:]

[...Else | Jmp I7>> | I2:]

[....Else | Jmp I8>> | I3:]

[.....Else | Jmp I9>> | I4:]

[......Else | Jmp J5>> | J0:]

[.......Else | Jmp J6>> | J1:]

[........Else | Jmp J7>> | J2:]

[.........Else | Jmp J8>> | J3:]

[..........Else | Jmp J9>> | J4:]


[.End_If | I0: | I5:]

[..End_If | I1: | I6:]

[...End_If | I2: | I7:]

[....End_If | I3: | I8:]

[.....End_If | I4: | I9:]

[......End_If | j0: | j5:]

[.......End_If | j1: | j6:]

[........End_If | j2: | j7:]

[.........End_If | j3: | j8:]

[..........End_If | j4: | j9:]


[If | &6=&6. | &6If #1>L]

[Else_If | &6Else_If #1>L]

[Else | &6Else]

[End_If | &6End_If | &6=&6!]



mov eax 1, ebx 2, ecx 33


If eax = 1

    hexprint 01

    If ebx = 1

        Hexprint 0101

    Else_If ebx = 2

        Hexprint 0102

        If ecx = 3

           hexprint 010203

        Else

            hexprint 07777

        End_If

    End_If

End_If



In '| &6=&6. |',  we simply add a point to the sixth storage. In '| &6=&6!]' , we simply retrieve a point from it.


The limit is only due to the number of available Characters for local Labels * 5 (!!!). Here, 10 nested levels; this is to say, in my opinion, too much.



General considerations about '&'


'&' is a very powerful and unique feature of RosAsm Macros Parser. It allows you to build organizations that would be fully impossible with another Assembler Macros Parser. As this feature is strictly limited to 9 storages, do not spoil them with few use managements. Try to write your Macros in a way that allow them to run with as few '&x' as possible.


You are responsible for the management of these storages. Pay attention to not reusing an already in use storage. Each time you want a new one, verify carefully that is is not used by another Macro. This kind of error is difficult to point out when done. The 'Proc' Macros I provide use 3 storages, upper 'For' uses 2 and 'Multi If' only 1.


As a side note, give consideration to the fact, that true low level conditional Jumps will always be much more powerful and flexible than any HLL Macros set ever will.


 In the more complex cases, do not be afraid of a little bit of Spaghetti Style. Spaghetti style is not death, as long as it remains confined to a very short scope, in the code flow, and as long as you clearly master what is going on (The art of programming...).


~~~~~~~



Inside Macros


Inside Macros  .



Local Labels inside Macros


In many cases, you will find the need of declaring Local Labels inside your Macros Declarations. This may be a problem if you do not take care of choosing non-conflicting Local Labels Characters. I recommend that you reserve 'L' (L1,.... L9) for usual code Local locations with a very short scope. For simple Macros, that cannot conflict with others, I  recommend the use of the 'M' Character. For all HLL Organization Macros, that could generate naming conflicts, the best way is to reserve one Character per Macro.


Example of organization Macro, using the 'O' Char for 'On':


[On | cmp #1 #3 | jn#2 O9> | #4>L | O9: ]        ; jn#2 >>> jna

                                                                           ; #4>L allow any length statement

On  eax > ebx,  xchg eax ebx


>>>  cmp eax ebx | jna O9>

                    xchg eax ebx

         O9:


In case this chunk of code would be included inside a larger HLL organization flow, for example, an If or a Select_Case group, as long as the If reserved Character will be 'I' (or 'C' for Case, and so on), there will be no problem of Local Labels naming conflicts.



Nesting Declarations inside Macros


You can use Macros to declare Equates, Data, or even other Macros. Example:


[Proc3 | Proc#1 | push ebp | mov ebp esp | {#1! | %=3 | push %L>1 | call Proc#1!} ]


Inside the nested Declaration, '{ }' are substitutes to '[ ]',  and '%' is a substitute to '#' when you do not mean the first level parameters but the nested level parameters. See an  example of this complex macro running in Beginner's Tut 5. 



With '{ }' substitutes, you can declare Macros, Data and Equates inside Macros Declarations. Inside '{ }', '%' is a substitute of '#'. Example:


[FirstMacro | ... | {Included#1 | mov %1 #2} ]


FirstMacro MacroName 32

...

IncludedMacroName eax


will be unfolded as:


mov eax 32    ; (MOV TO 'IncludedMacro first parameter', 'FirstMacro second parameter').


Upper %1 is replaced only at 'IncludedMacro' evocation, and #2 immediately at 'FirstMacro' evocation.



Example of C_like Data Declarations:


[POINT | {#1:  #1.x: D$ 0    #1.y: D$ 0}]


[MSG | {#1: #1.hwnd: D$ 0  #1.message: D$ 0  #1.wParam: D$ 0

                      #1.lParam: D$ 0  #1.time: D$ 0}    POINT #1.Point]


MSG  MyMsg


mov  D$MyMsg.Point.X  0A  |  mov  D$MyMsg.Point.Y  010



Or with integrated  initialization:


[POINTi | {#1:  #1.x: D$ #2    #1.y: D$ #3}]


[MSGi | {#1: #1.hwnd: D$ #2  #1.message: D$ #3  #1.wParam: D$ #4

                      #1.lParam: D$ #5  #1.time: D$ #6}    POINTi  #1.Point  #7  #8]


MSGi  MyMsg   0  0  0  0     0  0A  010


Hexprint  D$MyMsg.Point.X  |  Hexprint  D$MyMsg.Point.Y


~~~~~~~



The Macros Basics


The Macros Basics .



Redefining the Mnemonics


As there is no concept of ''Reversed Words'', in RosAsm, one great feature of RosAsm is that you can redefine mnemonics just like any common macro symbolic:

      

[push | push #1 | #+1]        ; macro declaration

          

push eax, ebx, ecx      ; macro evocation


>>>   push eax | push ebx | push ecx ; unfolding of upper macro evocation



In the upper Declaration #1 means 'Parameter Number One'. But this '1' is 'moveable', this is to say that, when the Macro Parser will unfold the Macro Evocation, it will, in this case, replace this '1' by '1', '2', '3' while unrolling the Macro Loop.


#+1 means 'repeat all previous Macro Declaration Statements (as many times as transmitted parameters), and add 1 to all 'moveable' parameters at each loop'.


This macro does not generate an infinite loop. So, you can entirely redefine your own memonics use rules.


Note: Infinite loops are not completely impossible:


[mov | mov #2 #1]


mov eax, ebx            >>>   mov ebx eax

                                  >>>   mov eax ebx     ;.... and so on


Of course, RosAsm detects such cases.



Cascading unknown numbers of parameter.

          

[call | push #L>2 | call #1]


The #L>2 means 'from Last to second Parameter'. With this convention, you can write Macros that will assume any number of Parameters, including no Parameter, at all


call 'USER32.MessageBoxA' &NULL, Message, Title, &NULL


>>> push &NULL, title, message, &NULL | call 'USER32.MessageBoxA'


and, if upper 'Push' is defined  >>> push &NULL | push Title | push Message

                                                       push &NULL | call 'USER32.MessageBoxA'


If no parameter is transmitted for 'push #L>2', no problem: It allows any parameters number, in this case, including none:


call MyProcedure esi, edi


call MyRoutine



Nesting Macros Evocations

          

In  the previous example, the 'push #L>2' is a nested Evocation.You can nest Macros Evocations levels up to a limit. of... 255.  This limit is useful to keep control upon infinite loops:


[fill_with | mov #2 #F | #+1]  

[clear | fill_with 0, #1>L]

         

clear eax ebx ecx  

>>>  

fill_with 0, eax ebx ecx 

>>>  

mov eax 0 | mov ebx 0 | mov ecx 0



Controlling the Parameters number


There is a simple way to ensure that your evocations will have the required number of Parameters, if this verification is desirable in some macros: You can force the Macros Parser to control this, with  '| #=4 |.  For example:


[CommonColorBits | #=4 | and #F #2 | #+1]


CommonColorBits  eax,  ebx, ecx, edx ; OK.

CommonColorBits  eax,  ebx, ecx ; Compile >>> Error: Missing Parameter.


In all other cases, when this is possible RosAsm controls the number of parameters in the evocation (expressed parameters numbers), even if you don't ask for it with some '| #=4 |', for example:


[mov | mov #1 #2 | #+2]

          

mov eax ebx, ecx edx    

  >>> 

mov eax ebx | mov ecx edx ; OK.

          

mov eax, ebx, ecx          ; >>> error 'missing parameter in macro evocation'



Looking at what the Macros Parser does


If you want to see what RosAsm does with one of your Macro Evocations, just DoubleLeftClick upon the Evocation name to open the Floating Menu, and choose the [Unfold] Option. A Dialog Box will appear, with an Edit Control showing the results outputted by the Macro Parser. Your statement is always preceded by a Dummy label, useful in case of Local Symbols (this feature does not search for what possible upper main Label).


In this second release of the Macro Unfolder, all of the possible nested Macros and Equates are properly computed. This makes it, of course much slower than the previous simplified version, but usage did show that the lack of Equates parsing made it of little help in several complex cases, as it is difficult for users to understand in which order the various Parsers do their respective jobs. So, it is worthy waiting a couple of seconds when working with huge files, and much better than not fully understanding what is going on with complex Macro unfoldings, when  hunting for hidden bugs.


~~~~~~~




Macros Keys


Macros Keys  ..


The macro keys are:


(#  stands for  'parameter number')

           

#1    means   'moveable first parameter' (1 to 9, but 9 is not true last limit).

#F    means   'fixed first parameter'.

#L    means   'fixed last parameter'.

#N    means   'parameters Number'.

#+1  means   'loop from start and add 1 to all parameters numbers' (not to F nor L).

#-2  means   'loop from start and sub 2 to all parameters numbers'.

#5>8 means   'copy parameter 5, 6, 7, 8'               (always '>' never '<' here).

#L>2 means   'copy parameters from Last to second one' (always '>' never '<' here).

#x means   'loop number'.

#=5 means   'Evocation must have 5 Parameters, if not, send me an error message'.

#1!  means   Same as #1, but strip last Character (to build symbols from Labels).

#1!!  means   Same as #1, but strip the two first Chars (For Size Substitutions in CM).

&1=  means   'store what follows in internal Macro Parser Var1'.

&2    means    'replace with internal Macro Parser Var2'.

&0:  means    'I want an Automatic Label here'

&0    means    'replace this by last declared Automatic Label evocation'

NOPE    means    'do nothing here' (Dummy Macros. In fact Dummy Mnemonic: NOPE).



What I call here 'moveable' or 'fixed' is to be understood inside a Macro 'Loop'. Example, :


[Fill_With | mov #2  #F | #+1 ]


 Fill_With  2, eax ebx ecx

>>>

mov eax 2

mov ebx 2

mov ecx 2


#F, for 'First Parameter', is fixed. This is to say remains really the first of transmitted parameters all along the Macro loop unfolding, whereas, #2 is moveable, this is to say that it is increased by '#+1' at each Macro loop unfolding.


Such a '#+1'  notation produces as many loop repetitions of the Macro a number of times equal to the transmitted number of parameters.



#=5 must be written alone, at first and as is, without any space:


[MacroName | #=5 | ... ]

          

         

Limit for expressed numbers is 9 (from 1 to 9); limit for transmitted parameters is 127.

          


'#x' is actually of little use. It is nothing but the internal loop number. Example: 


[ProgressVal | mov D§Value#1 #x | #+1]


ProgressVal a b c d

>>>

mov  D§ValueA  0 | mov  D§ValueB  1

mov  D§ValueC  2 | mov  D§ValueD  3



         

RosAsm macros do not implement conditionnal assembly. In a specific production oriented assembler, this feature would be of little use. If really wanted, I will add it later, but I do not like it. Many programmers go on using it for optimizing old 16 bits like operations, which is a bit out of purpose today.



That's all. This tiny set of writing conventions is enough to realize really great things, including HLL styles:

          

~~~~~~~