I was recently translating a peice of MASM code and ran across this

.while byte ptr 
lodsb
.if al=='\'
mov al,'/'
.endif
stosb
.endw


While it might look benign at first glance, being obfuscated by high level constructs, it assembles as follows...

0x10001310: 8B7508         mov      esi,     ; ARG:0x8
0x10001313: 8DBDFCFEFFFF  lea      edi,buffer  ; VAR:0x104
0x10001319: EB08          jmp      0x10001323                  ; (*+0xA)
0x1000131B: AC            lodsb                                ; <==0x10001326(*+0xB)
0x1000131C: 3C5C          cmp      al,0x5c                   
0x1000131E: 7502          jnz      0x10001322                  ; (*+0x4)
0x10001320: B02F          mov      al,0x2f                   
0x10001322: AA            stosb                                ; <==0x1000131E(*-0x4)
0x10001323: 803E00        cmp      byte ptr ,0x0          ; <==0x10001319(*-0xA)
0x10001326: 75F3          jnz      0x1000131b                  ; (*-0xB)


Now all it really does is replace occurences of "\" in a string with "/" so it doesn't have to be fast but it demonstrates how using high level constructs can lead you to bad code. 3 jumps for a very simple loop, and as MASM has no built in use of the CMOV series of instructions, the obvious replacement of 1 jump is not made.

mov ecx,"/"
L2:
mov al,
cmp al,"\"
cmove eax,ecx
mov ,al
inc edi,esi
test al,al
jnz <L2


Since the default branch prediction circuitry assumes that backwards jumps are always taken there is little trashing of the cache and using CMOVE eliminates a jump completely. Using GoAsm you are forced to think about your loops and will generally take the more logical path while other assemblers allow you the easy way out by hiding sloppy code behind high level constructs.

Donkey
Posted on 2006-04-09 16:35:03 by donkey
While it's true that code doesn't have to be very fast and that I'm personally against micro-optimizations that don't matter, I still feel that's there no reason to do "code pessimization" either. The use of HLL constructs in that piece of code buys very little (if any at all), and certainly doesn't produce nice code (and checking a memory operand when we already have the value in a register? Eek!)
Posted on 2006-04-10 04:58:48 by f0dder
donkey, i think it is always a question of readability vs. optimization:

.while byte ptr 
lodsb
.if al=='\'
mov al,'/'
.endif
stosb
.endw


mov ecx,"/"
L2:
mov al,
cmp al,"\"
cmove eax,ecx
mov ,al
inc edi,esi
test al,al
jnz <L2


In most cases, we need readable code. Why I like MASM is I can always choose between HLL constructs and pure, hand-optimized assembly code.
Posted on 2006-04-11 03:09:34 by MazeGen
I guess readability is in the eye of the beholder - for this particular snippet, I actually find the "raw" approach more readable... and that's even though my main area of work is HLL these days.
Posted on 2006-04-11 03:14:43 by f0dder
Interesting :), I find more readable and intuitive the HLL construct.
Posted on 2006-04-11 03:19:43 by MazeGen
I guess it's because I think differently depending on the language I code in :)
Posted on 2006-04-11 03:31:50 by f0dder
the most usable thing in about HLL constructs is that i don't have to make up label names
for every meaningless loop

.repeat
mov al,
cmp al,"\"
cmove eax,ecx
mov ,al
inc edi,esi
test al,al
.until zero?

of course there are situations where label raw approach is better (like: a bunch of "call api,
if error go to xxxx" ) , but generally its the HLL constructs that are more readable
Posted on 2006-04-11 06:22:21 by drizz
Donkey,
    Sorry I did not get to this sooner.  Yes, one can usually outsmart the algo MASM uses to generate the HLL code.  MASM does not have all the possible HLL constructs, and does not know anything about CMOV as it pertains to HLL.  However, your comparision is not quite fair because you included the instructions for loading ESI and EDI in the assembly listing, which were not in the source.  So without further ado, let me present my version of the snippet.  First the source.


;--------------------------------
MOV CL,'/'
mov ecx,'/'
.while byte ptr
lodsb
.if al=='\'
mov al,'/'
.endif
stosb
.endw
;-------------------------------
mov ecx,'/'
.repeat
mov al,
cmp al,"\"
cmove eax,ecx
mov ,al
inc edi
        inc esi
test al,al
.until zero?
;-------------------------------
B EQU BYTE PTR
.WHILE TRUE
  MOVSB
.BREAK .IF !AL
.CONTINUE .IF B != '\'
  MOV B,'/'
.ENDW
;--------------------------------


Now the assembly

;--------------------------------
00000000  B1 2F MOV CL,'/'
00000002  B9 0000002F mov ecx,'/'
.while byte ptr
00000007  EB 08   *     jmp    @C0001
00000009   *@C0002:
00000009  AC lodsb
.if al=='\'
0000000A  3C 5C   *     cmp    al, '\'
0000000C  75 02   *     jne    @C0003
0000000E  B0 2F mov al,'/'
.endif
00000010   *@C0003:
00000010  AA stosb
.endw
00000011   *@C0001:
00000011  80 3E 00   *     cmp    byte ptr , 000h
00000014  75 F3   *     jne    @C0002
;-------------------------------
00000016  B9 0000002F mov ecx,'/'
.repeat
0000001B   *@C0006:
0000001B  8A 06 mov al,
0000001D  3C 5C cmp al,"\"
0000001F  0F 44 C1 cmove eax,ecx
00000022  88 07 mov ,al
00000024  47 inc edi
00000025  46         inc esi
00000026  84 C0 test al,al
.until zero?
00000028  75 F1   *     jne    @C0006
;-------------------------------
= BYTE PTR B EQU BYTE PTR
.WHILE TRUE
0000002A   *@C0008:
0000002A  A4   MOVSB
.BREAK .IF !AL
0000002B  0A C0   *     or al, al
0000002D  74 0B   *     je    @C0009
.CONTINUE .IF B != '\'
0000002F  80 3E 5C   *     cmp    byte ptr , '\'
00000032  75 F6   *     jne    @C0008
00000034  C6 47 FF 2F   MOV B,'/'
.ENDW
00000038  EB F0   *     jmp    @C0008
0000003A   *@C0009:
;--------------------------------


    Notice that my code does not complete the loop unless it finds the backslash.  Also it does not use the ECX register.  And only one byte for the MOVSB instruction.  By the way, notice how much shorter MOV CL,'/' is compared to MOV ECX,'/'  .  Ratch
Posted on 2006-04-20 01:00:46 by Ratch

Donkey,
    Sorry I did not get to this sooner.  Yes, one can usually outsmart the algo MASM uses to generate the HLL code.  MASM does not have all the possible HLL constructs, and does not know anything about CMOV as it pertains to HLL.  However, your comparision is not quite fair because you included the instructions for loading ESI and EDI in the assembly listing, which were not in the source.  So without further ado, let me present my version of the snippet.  First the source.


Hi Ratch,

I had assumed that including loading esi and edi weren't necessary as they are assumed by the lodsb and movsb instructions so I didn't include them in the source but they ended up in the Olly dump mainly because I didn't think to remove them. I do realize that MASM's HLCs do not know about CMOV and that is the whole point, this was just a small example to demonstrate the perils of relying on them. I have seen code with intensive loops using HLCs and the resulting code was horrible. One of the biggest offenders is using a test/jcc in a loop which should now be replaced by CMOV as that is a much more efficient way to accomplish the same thing.

Notice that my code does not complete the loop unless it finds the backslash.  Also it does not use the ECX register.  And only one byte for the MOVSB instruction.  By the way, notice how much shorter MOV CL,'/' is compared to MOV ECX,'/'  .  Ratch


ECX is a discardable register in all my code, even more so than EAX in some cases, personal thing but I don't usually think twice about using it or trashing it. I could not rely on ECX being empty so rather than zero the register then move only the byte (CMOV requires 32 bit moves) I just move the "\" as  a DWORD.

But as I said, the code I posted was only an example to demonstrate the problem of relying on the constructs and to remind people that they are not always the way to go. Personally I don't use them even on the rare occasion I code in MASM.

GoAsm supports better scoping of labels than MASM so the problem of label names drizz posted is not an issue.
Posted on 2006-04-20 01:16:27 by donkey