News:

MASM32 SDK Description, downloads and other helpful links
MASM32.com New Forum Link
masmforum WebSite

Newbie Question: reset byte based on boolean argument

Started by kwadrofonik, February 22, 2007, 05:55:27 AM

Previous topic - Next topic

kwadrofonik

There's gotta be an easy way of doing this without a conditional statement. Suppose I only want to subtract one byte from another based on whether an argument supplied is either 1 or 0. This is the best I can do...

Provided:
- [ebp+32] contains an argument called DoSub, a dword with either 1 or 0. If DoSub = 1 then perform subtraction.
- eax starts with the initial value
- edi points to the operand


        mov      edx, [edi]      ;let edx = subtraction operand
        movzx    edx, dl         ;zero high bytes

        push     eax             ;borrow eax
        mov      ebx, [ebp+32]   ;set ebx pointer to DoSub argument
        mov      eax, [ebx]      ;let eax = DoSub (0 or 1)
        movzx    eax, al         ;zero high bytes

        mul      dl              ;ax = al * dl (Operand * DoSub)
        mov      dx, ax          ;dx = ax
        movzx    edx, dl         ;zero high bytes

        pop      eax             ;bring back initial value

        sub      eax, edx        ;subtract edx from eax if DoSub = 0


Thanks!

dsouza123

Your code will actually work as DoSub = 0 .. 255, giving results you may not want.

replace
   movzx    eax, al
with
   and    eax, 1
then DoSub will equal 0 or 1.

dsouza123


        mov      edx, [edi]      ;let edx = subtraction operand

        push     eax             ;borrow eax
        mov      ebx, [ebp+32]   ;set ebx pointer to DoSub argument
        mov      eax, [ebx]      ;let eax = DoSub (0 or 1)
        and      eax, 1          ;zero all but bit 0, to have a true 0 or 1

        neg      eax             ; eax = 0 <-- 0, -1 <-- 1  (-1 is all bits set)
        and      edx, eax
        movzx    edx, dl         ;zero high bytes

        pop      eax             ;bring back initial value

        sub      eax, edx        ;subtract edx from eax always, if DoSub = 0 effect of subtracting 0

lingo


mov    ecx, [ebp+32]          ; set ecx pointer to DoSub argument
movzx  edx, byte ptr [edi+3]  ; edx = substraction operand with zeroed high bytes
cmp    dword ptr [ecx], 1     ; cmp DoSub, 1
sbb    ecx, ecx               ; if DoSub=0 ecx=0FFFFFFFFh or if DoSub=1 ecx=0
and    edx, ecx               ; if ecx=0FFFFFFFFh(DoSub=0) edx=substraction operand
                              ; with zeroed high bytes  or
                              ; if ecx=0(DoSub=1) edx=0
sub     eax, edx              ; subtract edx from eax if DoSub = 0 or
                              ; subtract 0 from eax if DoSub = 1
   

kwadrofonik

Thanks dsouza123! That makes more sense than multiplying by 1.

Lingo: aren't I suppose to avoid conditional statements (cmp) if at all possible?

lingo


kwadrofonik

From what I've read, cmp hinders the cpu from doing predictive calculations, since there are two logical branches the program can take. Avoiding cmp has the same effect on performance as unrolling loops. Hope I explained that right. Maybe a hutch or another asm guru can step in here...


Update: I'm sorry. I was confused with conditional jump statements. cmp by itself just sets the flags. It's using je, jne, etc. that should be avoided since they are slow.

TNick

one just posted a reply :) lingo is really great when it comes to speed, and you can trust his words about this. If my connection didn't bothered me earlier, I would told you that the problem is not cmp, but jmp and jxx that follows those directives most often. On a 486, cmp takes 1 or 2 cycles and, as lingo suggested, all it does is a subtraction without storing the result.

Regards,
Nick

dsouza123

A few less instructions than my previous post, no pushing and poping

mov    ecx, [ebp+32]  ; get the pointer
mov    ecx, [ecx]     ; dereference the pointer
                      ; gets DoSub, the value the pointer was pointing to
mov    edx, [edi]     ; dereference the pointer
                      ; gets the full dword, will chop it later
                      ; the full dword has bytes [edi+0,edi+1,edi+2,edi+3] low to high
                      ; chopping will only keep [edi+0] the low byte

and    ecx, 1         ; force the DoSub to 0 or 1, remove this line if your certain DoSub is 0 or 1
neg    ecx            ; 0 --> 0,  1 --> -1  (-1 is all bits set)
and    edx, ecx       ; either turn edx into 0, or leave it as is

movzx  edx, dl        ; chop it now, only keep low byte
sub    eax, edx       ; subtract edx from eax


The original description of DoSub, where 1 means do subtraction, matched the original code.
but the code line comments were the reverse.

The two snippets I posted use the convention :
1 means do an effective subtraction
0 ends up subtracting 0

kwadrofonik

Tell me, can I use ES to store DoSub? Would there be a better nook or cranny to store it so I don't have to push something or waste a main register?


mov      ebx, [ebp+32]
mov      byte es, [ebx]
and      es, 1
neg      es

~~/~~

mov      edx, [edi]
and      dl, es
movzx    edx, dl
sub      eax, edx       


I really appreciate everyone's help!

dsouza123

I don't believe you can safely use a segment register, es in this case,
as a temporary storage spot.

You can use a variable for example
  tem dd 0
along with using edx earlier so only edx and eax are used.


mov      edx, [ebp+32]
mov      edx, [edx]
mov      tem, edx
and      tem, 1
neg      tem
mov      edx, [edi]
and      edx, tem
movzx    edx, dl
sub      eax, edx


or if there is some other spot on the stack that can safely hold
a temporary dword value, then something like this (UNTESTED)


mov   ebx, [ebp+32]
mov   edx, [ebx]
mov   [ebp+36], edx    ; just made up [ebp+36],
                       ; what would be safe location
                       ; is something you will have to determine
and   dword ptr [ebp+36], 1
neg   dword ptr [ebp+36]
mov   edx, [edi]
and   edx, [ebp+36]
movzx edx, dl
sub   eax, edx


or since in your example you alreay used ebx to hold the pointer


mov      ebx, [ebp+32]
mov      ebx, [ebx]
and      ebx, 1
neg      ebx
mov      edx, [edi]
and      edx, ebx
movzx    edx, dl
sub      eax, edx


or even this UNTESTED code using only edx and eax (also ebp and edi indirectly)
taking advantage of two of the 8 bit sub registers of edx, dl and dh


mov   edx, [ebp+32]
mov   edx, [edx]   ; edx holds DoSub, only need the low byte, and of that the low bit
and   edx, 1
neg   edx
mov   dh, dl    ; dh now holds DoSub  could do  shl edx, 8  but very likely slower
mov   dl, byte ptr [edi]   ; the idea is dh is left undisturbed
and   dl, dh
movzx edx, dl
sub   eax, edx

zooba

A 'safe' stack location over a short section of code without any calls is [esp-4]. Alternatively, you can push 0 and use [esp] and you'll be fine as long as you pop it when you're done.

Personally, I think you're much better off preserving a register around the code and using it.

Cheers,

Zooba :U

kwadrofonik

[message erased] nevermind I figured it out.

Question though. Why can't I move a byte from memory to a low byte of a register?


mov dl, byte [edi]  ; doesn't work

movzx edx, byte [edi] ; have to use this

dsouza123

You can, just need the correct syntax.


mov dl, byte ptr [edi]

OR

mov dl, [edi]

kwadrofonik

I'm sorry to keep bugging you guys. You have no idea how much I appreciate your help!

I had the code working well last week until I decided to change it. Suppose DoSub is not a boolean value, but rather it is a counter. When DoSub counts down to 0 then apply subtraction:

suppose edx = DoSub counter


      xor      ebx, ebx
      cmp      edx, dword 1    ;if edx < 1 then CF = 1 else CF = 0
      cmc                      ;if edx >= 1 then CF = 1 else CF = 0
      sbb      ebx, ebx        ;if edx >= 1 then ebx = -1 else ebx = 0
     
      movzx    ecx, byte [edi]
      xor      ecx, ebx        ;if edx = 0 (ebx = 0) then ecx stays the same ecx else ecx = 0 (ebx = -1)
      sub      eax, ecx        ;subtract trailing byte

      add      edx, ebx        ;if edx = 0 then edx stays 0 else edx = edx - 1


The above code doesn't seem to work. Any idea why?