0

I am fairly new to x86 Intel assembly. Currently, I am trying to take the absolute value of a number passed in by a user. At the moment, the code simply spits out the number that is passed in back out. If it's -5, then the program states it's absolute value is still -5. If it's 5, the program states it's absolute value is 5. I am attempting to work with conditionals and ONLY jump to a section of code that would flip our sign bit when our number is less than zero. I do not think I am correctly jumping to this section of code, I do not know why. My code so far is as follows:

#compare with 0 and if less then flip sign

#Syntax: int abs_val(int num)
#Returns the absolute value of num
abs_val:
#num will be stored in %rdi
push %rbp
mov %rsp, %rbp
cmpq $0, %rdi
jl signFlip

leave
ret

signFlip:
    neg %rdi
    mov %rdi, %rax
    leave
    ret

Please note: I am not looking for someone to type out the entire solution for me. I would prefer if I could be led in the right direction as in, "You need to use xyz function call instead of neg" or "You are not storing the value properly, try moving blah blah into this and that"

Thank you in advance!

Illu
  • 15
  • 5
  • 2
    Looks like you forgot the `mov %rdi, %rax` in case the value is positive. So do that and a conditional `neg %rax`. (Or do what compilers normally do, and use `cmov` for a branchless `abs()`. See compiler output for `long foo(long x) { return std::abs(x); }` from a C++ compiler with optimization enabled, e.g. on https://godbolt.org/.) – Peter Cordes Sep 29 '18 at 19:51
  • That is true but I don't see how that would produce the claimed behavior. – Jester Sep 29 '18 at 19:53
  • 1
    Wait, did you declare this `int` in C as well? But you're operating on qword `long` operands, after telling the compiler the upper 32 bits of the arg didn't matter. – Peter Cordes Sep 29 '18 at 19:57
  • Yeah that sounds more like it. – Jester Sep 29 '18 at 20:01
  • A little over my head but I appreciate the input, going to do a little more digging. I think my conditional is constantly being triggered as I now ALWAYS flip signs. I added the initial mov rdi, rax statement in the beginning which I forgot to do thank you. After that I proceed to change my conditional to the neg %rax. However, after doing this I am uncertain that I am corectly using leave and ret. Why would I always trigger the conditional? – Illu Sep 29 '18 at 20:07

1 Answers1

2

You declare this int in C as well? int is a 32-bit type in the x86-64 System V ABI, which you're using.

But you're operating on qword long operands, after telling the compiler the upper 32 bits of the arg didn't matter. So when you pass -5 as an int arg, you get 0x00000000fffffffb in RDI, which is a 64-bit 2's complement positive integer, 4294967291. (This is assuming that the upper bits are zero, e.g. if the caller used mov $-5, %edi like a C compiler would with a constant arg. But you could get arbitrary garbage if casting a long to an int: the caller will assume that this function ignores high bits as advertised.)

You forgot to mov %rdi, %rax in case the arg is positive. (Then do a conditional neg %rax) Or actually, mov %edi, %eax / neg %eax.

So your function leaves RAX unmodified when RDI is positive. (But since the caller was probably un-optimized C from gcc -O0, it happens to also hold a copy of the arg. This explains the abs(-5) => -5 behaviour, rather than the semi-random garbage you'd expect from a bug like this).


The x86-64 System V calling convention does not require the caller to sign-extend narrow args or return values to full register width, so your cmp / jl depends on whatever garbage the caller left in the upper half of RDI.

(Is a sign or zero extension required when adding a 32bit offset to a pointer for the x86-64 ABI?). There's an undocumented behaviour of sign or zero extending narrow args to 32-bit, but not to 64.


The natural / default size for most operations is 32-bit operand-size, 64-bit address size, with implicit zero-extension to 64-bit when writing a 32-bit register. Use 32-bit operand-size unless you have a specific reason for using 64-bit (e.g. for pointers).


Look at what compilers normally do; they normally use cmov for a branchless abs(). See compiler output for long foo(long x) { return std::abs(x); } from a C++ compiler with optimization enabled, e.g. https://godbolt.org/z/I3NSIZ for long and int versions.

# clang7.0  -O3
foo(int):                                # @foo(int)
    movl    %edi, %eax
    negl    %eax                   # neg sets flags according to eax = 0-eax
    cmovll  %edi, %eax             # copy the original if that made it negative
    retq

 # gcc7.3 -O3
foo(int):
    movl    %edi, %edx
    movl    %edi, %eax
    sarl    $31, %edx
    xorl    %edx, %eax    # 2's complement identity hack.  Maybe nice with slow cmov
    subl    %edx, %eax
    ret

But unfortunately gcc doesn't switch over to cmov even with -march=skylake where it's 1 uop.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • Hi, Peter! Thank you so much for a detailed explanation. I didn't know that x86-64 systems didn't explicitly require the method I was trying to implement, I was under the assumption it was the ONLY way to do it. I do have one question about negl and cmovll. Is this saying to negate our number, then heck if we actually made it negative and if so just return the original? If so, how does cmovll know that and what exactly does the "ll" extension mean? – Illu Sep 29 '18 at 20:24
  • @Illu: `cmovll` is [`cmovl` (copy if the `l`ess-than condition in true)](http://felixcloutier.com/x86/CMOVcc.html) with a redundant explicit `l` operand-size, because compilers typically always print an operand-size. So `retval = 0 - x;` `retval = retval<0 ? x : retval;` – Peter Cordes Sep 29 '18 at 21:04