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!
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.
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
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
Thanks dsouza123! That makes more sense than multiplying by 1.
Lingo: aren't I suppose to avoid conditional statements (cmp) if at all possible?
What is the difference between cmp and sub? :wink
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.
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
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
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!
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
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
[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
You can, just need the correct syntax.
mov dl, byte ptr [edi]
OR
mov dl, [edi]
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?
*sigh*
Once again after staring at the same code for long enough I figured out what I did wrong. I used XOR where there should have been a NAND. Removing the cmc solves the issue...
cmp edx, 1 ;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]
and ecx, ebx ;if edx < 1 then ecx stays the same else ecx = 0
sub eax, ecx ;subtract trailing byte
inc ebx ;if ebx = -1 then ebx = 0 else ebx = 1
sub edx, ebx ;if ebx = -1 then edx stays the same else edx = edx - 1
cheers