EAX is the low 4 bytes of RAX.
test eax,eax ; sets flags the same as cmp eax,0, like from eax-0
jnl I_was_non_negative ; jumps if EAX was *not* less-than 0. Synonym for jge
Test whether a register is zero with CMP reg,0 vs OR reg,reg? explains why test
is slightly more efficient than cmp
for comparing against zero. (Mostly the 1 byte of code-size).
The l
ess-than condition tests SF and OF, but compare against zero can't overflow, so it's equivalent to just testing SF (the sign flag, set from the MSB of the result). In this case you have the choice of jnl
or jns
(not less or not signed), or jge
. Pick whichever one has the semantic meaning you like best.
int
in C is 4 bytes, not 8. (In all the standard 32 and 64-bit x86 ABIs, including x86-64 System V that you'll find on Linux).
Problems with your attempt to implement -I < I
(I think) using the 2's complement identity
-x = ~x + 1
xor rax, 0xFFFFFF
only flips the low 24 bits (that's 6 F
digits, not 8).
But xor rax, 0xFFFFFFFF
is not encodable, because it doesn't fit in a 32-bit sign-extended immediate. x86-64 still uses 8 or 32 bit immediates even for 64-bit operand-size, not 8 or 64, because that would be very bloated code size. See https://felixcloutier.com/x86/XOR.html for what's encodable. (There is a mov r64, imm64
, but that's the only instruction that takes a 64-bit immediate.)
So if you'd used xor eax, -1
(or not eax
) instead of insisting on using 64-bit operand-size, then maybe your weird code would have worked for comparing -I < I
. But flipping the low 32 or 24 bits of a 64 bit register, and then doing a 64-bit compare, isn't helpful. The upper bits will always be zero.
If you had used a 32-bit compare, then you'd have a problem for the most-negative number. 0x80000000
is its own 2's complement inverse. i.e. neg eax
would leave it unchanged (and set OF because 0 - 0x80000000
causes signed overflow back to negative).
If you had sign-extended your 4-byte input in EAX to 8 bytes in RAX before negating, then it could have worked.
mov eax, I
movsxd rax, eax ; or cdqe
; not rax
; inc rax
neg rax
cmp rax, I ; use I as a sign-extended 32-bit immediate
jl I_is_positive ; won't be taken for 0 either.
Note the different between positive and non-negative. -I < I
is false for I=0, but you asked about checking for negative (the opposite of non-negative, not the opposite of positive).
I
is an assemble-time constant
You can use the NASM preprocessor to test it.
default rel ; always a good idea to use RIP-relative for static data
; %define I 9
extern puts
global main
main:
%if I < 0
lea rdi, [rel msg]
xor eax, eax
call puts ; puts appends a newline
%endif
xor eax,eax ; return 0. Otherwise we might as well jmp puts to tailcall it
ret
section .rodata ; read-only data can go here
msg: db "I is negative", 0 ; colon after labels is always good style in NASM
I commented out the %define
so I could pass it on the command line:
$ nasm -felf64 -DI=0 nasm.asm && gcc -no-pie nasm.o && ./a.out # no output for I=0
$ nasm -felf64 -DI=9 nasm.asm && gcc -no-pie nasm.o && ./a.out # or I=9
$ nasm -felf64 -DI=-9 nasm.asm && gcc -no-pie nasm.o && ./a.out
I is negative
$ nasm -felf64 -DI=0x80000000 nasm.asm && gcc -no-pie nasm.o && ./a.out # NASM doesn't truncate to 32-bit 2's complement though.
$ nasm -felf64 -DI=0x8000000000000000 nasm.asm && gcc -no-pie nasm.o && ./a.out # apparently it's 64-bit.
I had to use -no-pie
because I used call puts
instead of call puts@plt
or any PIE-compatible thing. For some reason the linker doesn't rewrite direct calls to use the PLT for you when making a PIE instead of a position-dependent executable.