25

I am currently writing a simple C compiler, that takes a .c file as input and generates assembly code (X86, AT&T syntax). Everyting is good, but when I try to execute a IDIVQ instruction, I get a floating-point exception. Here's my input:

int mymain(int x){
  int d;
  int e;
  d = 3;
  e = 6 / d;
  return e;
}

And here is my generated code:

mymain:
.LFB1:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    movq    %rsp, %rbp
    .cfi_offset 6, -16
    .cfi_def_cfa_register 6
    movq    %rdi, -40(%rbp)
    movq    $3, -8(%rbp)
    movq    $6, %rax
    movq    -8(%rbp), %rdx
    movq    %rdx, %rbx
    idivq   %rbx
    movq    %rax, -16(%rbp)
    movq    -16(%rbp), %rax
    leave
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE1:
    .size mymain, .-mymain

According to http://www.cs.virginia.edu/~evans/cs216/guides/x86.html, idivq %rbx should produce 6/d (the quotient) in %rax. But I'm getting a floating-point exception, and I can't seem to find the problem.

Any help will be much appreciated!

elyas-bhy
  • 772
  • 1
  • 14
  • 23
  • Unrelated to this question, but should you be doing `movq %rdi, -40(%rbp)` without having adjusted the `esp` register? Or is that OK because of the x64 'red zone'? – Michael Burr Apr 27 '12 at 00:51
  • @MichaelBurr: That's ok because the x86-64 System V guarantees a red-zone of 128 bytes below RSP; this is what mainstream compilers like GCC do: [Why is there no "sub rsp" instruction in this function prologue and why are function parameters stored at negative rbp offsets?](https://stackoverflow.com/q/28693863). And of course you shouldn't adjust *ESP*, that would truncate RSP to 32 bits and make later stack operations fault, if you did any before `leave`. – Peter Cordes Sep 29 '20 at 01:37

2 Answers2

28

The first part of Mysticials answer is correct, idiv does a 128/64 bit division, so the value of rdx, which holds the upper 64 bit from the dividend must not contain a random value. But a zero extension is the wrong way to go.

As you have signed variables, you need to sign extend rax to rdx:rax. There is a specific instruction for this, cqto (convert quad to oct) in AT&T and cqo in Intel syntax. AFAIK newer versions of gas accept both names.

movq    %rdx, %rbx
cqto                  # sign extend rax to rdx:rax
idivq   %rbx
Gunther Piez
  • 29,760
  • 6
  • 71
  • 103
  • Indeed, I was running my tests and encountered an error when handling signed values. I haven't seen this instruction before, but it seems to solve the issue now. Thank you! – elyas-bhy Apr 27 '12 at 13:11
  • NASM version of the same question&answer: [nasm idiv a negative value](https://stackoverflow.com/q/9072922) – Peter Cordes Jun 04 '23 at 06:09
13

The idivq instruction divides a 128-bit integer (rdx:rax) by the given source operand.

  • rax holds the lower 64-bits of the dividend.
  • rdx holds the upper 64-bits of the dividend.

When the quotient doesn't fit into 64-bits, idiv will fault (#DE exception, which the OS handles by delivering a SIGFPE signal as required by POSIX for arithmetic exceptions).

Since you're compiling code that uses signed int, you also need to sign extend rax to rdx:rax, that means copying the rax sign bit to every bit of rdx and is accomplished with cqo alias cqto:

movq    %rdx, %rbx        # or load into RBX or RCX in the first place
cqo
idivq   %rbx              # signed division of RDX:RAX / RBX

If you'd been doing unsigned division, you'd zero RDX to zero-extend RAX into RDX:RAX:

movq    %rdx, %rbx
xor     %edx, %edx      # zero "rdx"
divq    %rbx            # unsigned division of RDX:RAX / RBX

Also note that in the x86-64 System V ABI, int is a 32-bit signed type, not 64-bit. Widening it to 64-bit is legal in this case (because the result is the same) but makes your code slower, especially for division.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
Mysticial
  • 464,885
  • 45
  • 335
  • 332
  • 5
    Zeroing rdx will work with positive numbers, but in case of negative rax probably rdx = -1 is needed... Isn't it? – marekb Apr 27 '12 at 06:55
  • 5
    I think marekb is right - shouldn't the `xorq` instrcution be a `cqo` instruction to sign extend `rax` into `rdx:rax`? – Michael Burr Apr 27 '12 at 07:56
  • In this case, with signed typing: I think so yes. – Marco van de Voort Apr 27 '12 at 12:16
  • 4
    Unsigned: `xor %rdx,%rdx` / `div`. Signed: `cqo` / `idiv`. Using idiv after zeroing rdx is only appropriate if your divisor is signed but your dividend is unsigned. (And you want a signed result). A C compiler should only generate that as an optimization after casting a `uint64_t` to an `__int128_t`, when it can prove there won't be a divide exception. Also note that 64bit `div` is somewhat faster than 64bit `idiv` on recent Intel CPUs. – Peter Cordes Jun 11 '16 at 00:44