I am trying to divide two numbers 50 and 5. This is my code:
function Divide(Num1, Num2: Integer): Integer;
asm
MOV EAX, Num1
CDQ
MOV ECX, Num2
IDIV ECX
MOV @RESULT, ECX
end;
It gives me a DivisionByZeroException
exception in Delphi.
Can someone tell me what am I doing wrong ?
It's the CDQ
instruction. From an online reference:
Converts signed DWORD in EAX to a signed quad word in EDX:EAX by extending the high order bit of EAX throughout EDX
The problem is, Num2
being the second parameter, is stored in EDX, and since you're running CDQ
before loading EDX to ECX, what ends up in ECX is 0. Rewrite it like this, and your routine works as expected:
function Divide(Num1, Num2: integer): integer;
asm
MOV EAX, Num1
MOV ECX, Num2
CDQ
IDIV ECX
MOV @Result, EAX
end;
Mason's answer is accurate and clearly explains the error due to CDQ sign extending overwriting the input parameter in EDX. No need for me to say more, Mason got it spot on. And note the correction that IDIV returns the quotient in EAX rather than ECX.
I would like to try to offer some more general advice on writing asm. I believe that your fundamental problem here is the use of the parameter names in your asm, rather than register names.
Since you use a register calling convention, it really pays to be explicit about the fact that parameters arrive in registers. Had you done that it might have been clearer what was happening. Trying to use variable names gives you an illusion of abstraction. In reality that abstraction is not there. By hiding the register parameter passing from view you make it hard to spot such errors, and sure enough you stomped on your input!
First of all let's write the code in Mason's answer in terms of registers. Include comments for added clarity. Like this:
function Divide(Num1, Num2: integer): integer;
// Input: EAX: Num1, EDX: Num2
// Output: EAX: Result
asm
MOV EAX, EAX
MOV ECX, EDX
CDQ
IDIV ECX
MOV EAX, EAX
end;
Right away we get an immediate benefit that the first and last lines are blatantly pointless. You could not see that in your version because of the use of variable names.
So we can write it like this:
function Divide(Num1, Num2: integer): integer;
// Input: EAX: Num1, EDX: Num2
// Output: EAX: Result
asm
MOV ECX, EDX
CDQ
IDIV ECX
end;
Of course it is no coincidence that most arithmetic operations return the result in EAX, and that same register is used for function return values.
The point is that writing asm is all about understanding register use, and re-use. Don't obscure that with variable names. Keep register use front and centre, in plain sight. Once you start doing so, you'll not make hard to spot errors like in the question, and you'll be able to remove spurious operations when values happen to land in the right registers.
My advice is never to use parameter names, or Result
in asm code.
The other very obvious point is that you are re-implementing the div
operator. By placing this in an asm function you are inevitably making the code less efficient, and less readable.
For what it is worth, this particular function can actually be written more efficiently as Pascal. Consider the following program:
{$APPTYPE CONSOLE}
function DivideAsm(Num1, Num2: integer): integer;
// Input: EAX: Num1, EDX: Num2
// Output: EAX: Result
asm
MOV ECX, EDX
CDQ
IDIV ECX
end;
function DividePas(Num1, Num2: integer): integer;
begin
Result := Num1 div Num2;
end;
function DividePasInline(Num1, Num2: integer): integer; inline;
begin
Result := Num1 div Num2;
end;
var
i, j, k, l: Integer;
begin
i := 666;
j := 42;
l := 0;
inc(l, i div j);
inc(l, DivideAsm(i, j));
inc(l, DividePas(i, j));
inc(l, DividePasInline(i, j));
Writeln(l);
end.
Now, DividePas
is worse than DivideAsm
. The former is compiled, with optimisation, to:
0040524C 53 push ebx 0040524D 8BDA mov ebx,edx 0040524F 8BC8 mov ecx,eax 00405251 8BC1 mov eax,ecx 00405253 99 cdq 00405254 F7FB idiv ebx 00405256 5B pop ebx 00405257 C3 ret
Clearly DivideAsm
wins by dint of skipping the prolog/epilog.
But let's look at the main body of the code:
SO22570866.dpr.28: i := 666; 004060D7 BE9A020000 mov esi,$0000029a SO22570866.dpr.29: j := 42; 004060DC BF2A000000 mov edi,$0000002a SO22570866.dpr.30: l := 0; 004060E1 33DB xor ebx,ebx SO22570866.dpr.31: inc(l, i div j); 004060E3 8BC6 mov eax,esi 004060E5 99 cdq 004060E6 F7FF idiv edi 004060E8 03D8 add ebx,eax SO22570866.dpr.32: inc(l, DivideAsm(i, j)); 004060EA 8BD7 mov edx,edi 004060EC 8BC6 mov eax,esi 004060EE E851F1FFFF call DivideAsm 004060F3 03D8 add ebx,eax SO22570866.dpr.33: inc(l, DividePas(i, j)); 004060F5 8BD7 mov edx,edi 004060F7 8BC6 mov eax,esi 004060F9 E84EF1FFFF call DividePas 004060FE 03D8 add ebx,eax SO22570866.dpr.34: inc(l, DividePasInline(i, j)); 00406100 8BC6 mov eax,esi 00406102 99 cdq 00406103 F7FF idiv edi 00406105 03D8 add ebx,eax
You can see that the compiler has more freedom of register use with the inline version. The compiler is not tied to the calling convention ABI. That allows it to emit fewer MOV
operations. In fact, the interaction between the inline engine and the optimiser is very good. Here's the first version of the code that I wrote:
inc(l, DivideAsm(i, j));
inc(l, DividePas(i, j));
inc(l, i div j);
inc(l, DividePasInline(i, j));
But the optimiser defeats me on the last two statements:
SO22570866.dpr.33: inc(l, i div j); 004060F9 8BC6 mov eax,esi 004060FB 99 cdq 004060FC F7FF idiv edi 004060FE 8BC8 mov ecx,eax 00406100 03D9 add ebx,ecx SO22570866.dpr.34: inc(l, DividePasInline(i, j)); 00406102 03D9 add ebx,ecx
The optimiser is able to recognize that the ECX
register already contains the result of DividePasInline
and skips the code altogether!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With