3

I am trying to define a calculator in C language based on the Linux command dc the structure of the program is not so important all you need to know that I get two numbers and I want to divide them when typing /. Therefore, I send this two numbers to an assembly function that makes the division (see code below). But this works for positive numbers only.

When typing 999 3 / it returns 333 which is correct but when typing -999 3 / I get the strange number 1431655432 and also when typing both negative numbers like -999 -3 / I get 0 every time for any two negative numbers.

The code in assembly is:

section .text 
global _div 

_div: 
  push rbp            ; Save caller state 
  mov rbp, rsp 

  mov rax, rdi        ; Copy function args to registers: leftmost... 
  mov rbx, rsi        ; Next argument... 

  cqo
  idiv rbx            ; divide 2 arguments 
  mov [rbp-8], rax 

  pop rbp             ; Restore caller state 
Michael Petch
  • 46,082
  • 8
  • 107
  • 198
Shmuel Niraev
  • 81
  • 1
  • 6
  • How are you calling `_div`? – JFMR Apr 01 '18 at 10:38
  • @考えネロク in c program I have 2 integers a and b: _div(a,b) – Shmuel Niraev Apr 01 '18 at 10:40
  • @MichaelPetch It is part of a mission that I need to imlement. The program is in linux(virtual machine) but now I type in windows – Shmuel Niraev Apr 01 '18 at 10:43
  • 4
    Is this all the code? I don't quite understand why you have `mov [rbp-8], rax` and there is no `ret` – Michael Petch Apr 01 '18 at 10:44
  • @MichaelPetch it is not all the code there is ret at the end the problem is somewhere between 'cqo' and 'idiv rbx' – Shmuel Niraev Apr 01 '18 at 10:50
  • 2
    The issue is that you are dividing a 128 bit value RDX:RAX by a 64-bit value, but what is being passed to the function are 32-bit values. For instance -999 as a 32-bit value of FFFFFC19 but if you do 64-bit division the upper 64-bits of RAX are likely zero (and you sign extend RAX to RDX so RDX will be zero) giving you the value 00000000FFFFFC19 . 00000000FFFFFC19 / 3 = 0x55555408 which is 1431655432 (decimal) – Michael Petch Apr 01 '18 at 10:59
  • @MichaelPetch I understood you thank you but how do I fix this making it a division of 32 bits to 32 bits? – Shmuel Niraev Apr 01 '18 at 11:01
  • 1
    You could use `cdq` instead of `cqo` and do `idiv ebx` ? – Michael Petch Apr 01 '18 at 11:02
  • 2
    RBX is a non volatile register in the Linux 64-bit calling convention like RBP so it needs to be preserved by your function. A better choice would be to use one of the other volatile (caller saved registers) like RCX rather than RBX. RBX, RBP, R12 to R15 all need to be preserved by your function of you modify them. – Michael Petch Apr 01 '18 at 11:12
  • @MichaelPetch thank you! – Shmuel Niraev Apr 01 '18 at 11:13
  • There's no point to move `rsi` into `rbx`, you can as well do `idiv rsi` (or after fix to 32b `idiv esi`), minimizing the register usage. The `edx:eax` pair is implicit for 64/32 bit variant of `idiv`, so you can't avoid that move from `edi`, that's ok, but the divisor can be chosen to fit your situation better. – Ped7g Apr 01 '18 at 12:01

2 Answers2

8

Your comments say you are passing integers to _idiv. If you are using int those are 32-bit values:

extern int _div (int a, int b);

When passed to the function a will be in the bottom 32-bits of RDI and b will be in the bottom 32-bits of RSI. The upper 32-bits of the arguments can be garbage but often they are zero, but doesn't have to be the case.

If you use a 64-bit register as a divisor with IDIV then the division is RDX:RAX / 64-bit divisor (in your case RBX). The problem here is that you are using the full 64-bit registers to do 32-bit division. If we assume for arguments sake that the upper bits of RDI and RSI were originally 0 then RSI would be 0x00000000FFFFFC19 (RAX) and RDI would be 0x0000000000000003 (RBX). CQO would zero extend RAX to RDX. The upper most bit of RAX is zero so RDX would be zero. The division would look like:

0x000000000000000000000000FFFFFC19 / 0x0000000000000003 = 0x55555408

0x55555408 happens to be 1431655432 (decimal) which is the result you were seeing. One fix for this is to use 32-bit registers for the division. To sign extend EAX (lower 32-bit of RAX) into EDX you can use CDQ instead of CQO.You can then divide EDX:EAX by EBX. This should get you the 32-bit signed division you are looking for. The code would look like:

cdq
idiv ebx                 ; divide 2 arguments EDX:EAX by EBX

Be aware that RBX, RBP, R12 to R15 all need to be preserved by your function of you modify them (they are volatile registers in the AMD 64-bit ABI). If you modify RBX you need to make sure you save and restore it like you do with RBP. A better alternative is to use one of the volatile registers like RCX instead of RBX.


You don't need the intermediate register to place the divisor into. You could have used RSI (or ESI in the fixed version) directly instead of moving it to a register like RBX.

Ped7g
  • 16,236
  • 3
  • 26
  • 63
Michael Petch
  • 46,082
  • 8
  • 107
  • 198
5

Your issue has to do with how arguments are passed to _div.

Assuming your _div's prototype is:

int64_t _div(int32_t, int32_t);

Then, the arguments are passed in edi and esi (i.e., 32-bit signed integers), the upper halves of the registers rdi and rsi are undefined.

Sign extension is needed when assigning edi and esi to rax and rbx for performing a 64-bit signed division (for performing a 64-bit unsigned division zero extension would be needed instead).

That is, instead of:

mov   rax, rdi       
mov   rbx, rsi 

use the instruction movsx, which sign extends the source, on edi and esi:

movsx rax, edi
movsx rbx, esi

Using true 64-bit operands for the 64-bit division

The previous approach consits of performing a 64-bit division on "fake" 64-bit operands (i.e., sign-extended 32-bit operands). Mixing 64-bit instructions with "32-bit operands" is usually not a very good idea because it may result in worse performance and larger code size.

A better approach would be to simply change the C prototype of your _div function to accept actual 64-bit arguments, i.e.:

int64_t _div(int64_t, int64_t);

This way, the argument will be passed to rdi and rsi (i.e., already 64-bit signed integers) and a 64-bit division will be performed on true 64-bit integers.

Using a 32-bit division instead

You may also want to consider using the 32-bit idiv if it suits your needs, since it performs faster than a 64-bit division and the resulting code size is smaller (no REX prefix):

...   
mov eax, edi      
mov ebx, esi      
cdq
idiv ebx
...

_div's prototype would be:

int32_t _div(int32_t, int32_t);
JFMR
  • 23,265
  • 4
  • 52
  • 76
  • Extending 32 bit integers for `idiv` into fake 64 bit integers is IMO a bit misleading advice, because the 128/64 bit division is much slower than 64/32 bit division. Especially while in this case the simple change of `idiv` argument to 32 bit size will fix it completely. (the change of whole function prototype to support 64 bit integers is valid advice, no problem about that one, I'm talking about fake 32->64 bit promotion) – Ped7g Apr 01 '18 at 12:09
  • What then about the 2nd approach? Those would be true 64-bit operands, aren't they? – JFMR Apr 01 '18 at 12:12
  • Yes, 2nd approach makes the function fully 64 bit integer valid, which makes the performance price acceptable. Even your first part of answer may be interesting and help some people to realize what to do in situations when they must promote 32 bit integer to 64b, but just in this particular case of that `int div(int a, int b)` it's a bit .. wrong. :) – Ped7g Apr 01 '18 at 12:16
  • You forgot to mention that clobbering `rbx` isn't safe, and that it's useless. Use `idiv esi` like a normal person in your final example. And use `movsx rcx, esi`, or `movsx rsi, esi` in your first example. (Unlike with some cases of [zero-extension with zero latency (mov / `movzx` elimination)](https://stackoverflow.com/questions/44169342/can-x86s-mov-really-be-free-why-cant-i-reproduce-this-at-all/44193770#44193770), there's no downside to sign-extending within the same register instead of to a different register.) – Peter Cordes Apr 05 '18 at 22:36