1

Related to Doing something in assembly vs having the assembler do it, I've tried my hand at checking the sign of a single number or multiple packed numbers, using the assembler to do much of the heavy lifting:

# Checking sign / unpacking values
# to check the sign, of an N-bit number, we can do check the left-most bit:
#   1 << (num_bits - 1)
# For example: for a signed 16 bit number, we can do:
#   NUM & (1 << 15) --> if this is NOT ZERO, it means the left-most bit was set and thus is a negative (signed) number

# (1) checking a single number
NEG_NUM = -4
mov $NEG_NUM, %ax
test $(1 << (16-1)),  %ax
setnz %r11b

POS_NUM = 4
mov $POS_NUM, %ax
test $(1 << (16-1)),  %ax
setnz %r12b

# (2) checking multiple numbers packed into a single value
# pack two 16-bit values into %eax -- signed short nums[] = {10, -2}
NEG_MULTI_PART_NUM = 0x000AAFFE
mov $NEG_MULTI_PART_NUM, %eax
test $(1 << (32-1) | (1 << (16-1))), %eax
setnz %r13b

POS_MULTI_PART_NUM = 0x0B003F1E
mov $POS_MULTI_PART_NUM, %eax
test $(1 << (32-1) | (1 << (16-1))), %eax
setnz %r14b

I have a few questions related to this:

  • Is test $(1 << (numbits-1))...setnz the most common way to check if a signed number is negative? If not, what's a better way to do so?
  • To check the MSB in two packaged 2-byte values I'm doing test $(1 << (32-1) | (1 << (16-1))). Again, does this seem like a good way to do this? Is the above considered unreadable, or is that clear enough?
carl.hiass
  • 1,526
  • 1
  • 6
  • 26
  • x86 has a Sign Flag, SF. `test same, same` / `sets %r11b`. No need to waste code size on an immediate. Look at compiler output for `int foo(int x) {return x<0;}`. For multiple numbers, yes a sign-bit mask works, normally you'd use SSE2 `pcmpgtw`, unless checking a single pair is relevant. – Peter Cordes Sep 26 '20 at 05:29
  • @PeterCordes could you please explain that a bit with how it would work? When trying it on Compiler Explorer it doesn't give too much when optimizing, just: `movl %edi, %eax`, `shrw $15, %ax`. By the way, I'm hardcoding values just for testing so I can more or less see the results and test to make sure they're correct. – carl.hiass Sep 26 '20 at 05:38
  • Oh right, that's even better. It just shifts the sign bit to the bottom, leaving a 0 or 1 integer. But that wasn't for an `int` return value, was it? – Peter Cordes Sep 26 '20 at 05:42
  • @PeterCordes it's a short (to copy the first example I had) -- here's the link: https://godbolt.org/z/fh7Ts3 – carl.hiass Sep 26 '20 at 05:47
  • Oh, and you picked a `short` for the boolean return value, too, for some reason. Your setz only returns `bool` (i.e. a 0 or 1 byte). – Peter Cordes Sep 26 '20 at 05:50
  • @PeterCordes yea I suppose maybe this would be more technically correct for return value: https://godbolt.org/z/4rWr6T. – carl.hiass Sep 26 '20 at 05:53

1 Answers1

3

x86 has a Sign Flag, SF. test same, same / sets %al would be idiomatic. No need to waste code size on an immediate for test when the bit you want already has a special FLAGS result just for it.

Look at compiler output for int foo(int x) {return x<0;}. How to remove "noise" from GCC/clang assembly output?. Get in the habit of seeing how a compiler does something (with optimization enabled) any time you're wondering, before you take the time to ask other humans about it. (Sometimes you'll realize "oh that makes sense" and not have to ask, or at least it will inform your question.)

Actually, clever compilers will just use a logical right-shift to isolate the sign bit at the bottom of the register, never going through FLAGS at all.


For checking if either of multiple numbers are negative, yes a sign-bit mask works. Normally you'd write it as a hex constant like 0x80008000. The position of the set bits in that is pretty easy to take in with hex, and asm should normally be well commented anyway so any mention of signs in a comment is enough of a clue about exactly what that bitmask is doing for anyone for whom it wasn't immediate obvious.

If you do want to write it as 1<<count, use (1<<31) | (1<<15). The extra noise from the 16-1 is significant. On seeing the -1, for the first half a second I expected it was going to be (1<<16)-1, but it turned out the brackets were different. As well as small powers of 2 being "well known", small 2^n - 1 is also easily recognized by humans. Writing 15 as 16-1 is just distracting and obfuscated, not helpful. Sometimes it can be helpful to write an expression in terms of subtracting two meaningful things, like cmp $'z'-'a', %al (although in practice 25 is just as if not more helpful to understanding the unsigned compare range check trick), but not here where -1 is not "meaningful". We already know that the top bit of a 16-bit number is bit 15 because we count bits from 0 (1<<0).


However, since this is x86-64, normally you'd use SSE2 pcmpgtw to check 16 bytes (8 words) at once against a zeroed register (created with pxor xmm0,xmm0), unless checking just a single pair is relevant.

To check pairs of words in a vector, you could pcmpeqd on the pcmpgtw result, to find dwords where both word elements produced a false (0) result.

x86-64's guaranteed support for SSE2 makes SWAR tricks with integer registers less frequently useful, but yes if you really do only have one pair to test, then integer is more efficient.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • thanks, for the example to check: `mov $NEG_NUM, %ax` / `test $(1 << (16-1)), %ax` / `setnz %r11b` -- how would the `test same, same` / `sets %al` work? Would moving the value into `%ax` set the sign flag and I can immediately do `sets` with it? Sorry if I'm not clear, I think just showing how that example could be translated will help a lot for my understanding. Thanks again for taking the time. – carl.hiass Sep 26 '20 at 06:00
  • @carl.hiass: does `mov` set FLAGS at all? check [the manual](https://www.felixcloutier.com/x86/mov): no, it doesn't. Come on, dude, you already know enough to quickly research the parts of this you might not be sure about. (But of course if it was the result of a computation like `add %cx, %ax`, then SF would already be set "according to the result" of the ADD. `mov` is not an ALU instruction and doesn't set FLAGS. In real code you'd never be doing this at all on a `mov`-immediate ([Doing something in assembly vs having the assembler do it](//stackoverflow.com/q/64071755)) but maybe a load – Peter Cordes Sep 26 '20 at 06:16
  • ok I think I got it then. It would be: `mov $-4, %ax` `test %ax, %ax` `sets %bl`. Where the first instruction is to move a number in for testing. Is that correct? – carl.hiass Sep 26 '20 at 20:42
  • 1
    @carl.hiass: yes. Generally you can omit the mov-immediate from your examples and just talk about testing some value that's already in AX without specifying how it got there. Same idea as writing a function separate from the test-caller that passes some test values. – Peter Cordes Sep 26 '20 at 20:46
  • got it thanks for your help and suggestions as always – carl.hiass Sep 26 '20 at 20:50