Meaning of "The sign is ignored" in _InterlockedCompareExchange documentation

339 views Asked by At

The documentation for _InterlockedCompareExchange says for every parameter

The sign is ignored.

So does it mean that numbers like 0xffff and 0x7fff (for 16-bit version) will be considered equal by _InterlockedCompareExchange16 etc. by other width intrinsics? Or does this mean that the intrinsics accept both signed and unsigned integers? Or something else?

If it's not a bug in the documentation, it seems at least ambiguous.

2

There are 2 answers

0
RbMm On

the _InterlockedCompareExchange this is compiler intrinsic implemented as CMPXCHG instruction. the The sign is ignored mean that when we compare 2 integers for equal only - no different how we interpret high bit - as sign bit or no. this affected only compare for > or < but not for =. and 0xffff of course not equal to 0x7fff

5
Anders On

The sign bit is not ignored, it is compared just like the other bits.

The ..CompareExchange.. functions only care about the equality of the bits and does not interpret them in any special way. On x86 based systems they are implemented with the CMPXCHG/CMPXCHG8B instruction and it compares a CPU register against a memory location. The sign issue becomes a question about types and parameter passing, not the comparison itself.

Because most of the interlocked functions also exist as Windows API functions we can take a look at those first. The basic version takes 32-bit parameters with a LONG type. Smaller signed types will be sign extended to 32-bits:

__declspec(noinline) void WINAPI Number(LONG val)
{
    printf("Number: %5d %#.8x (%d bit)\n", val, val, sizeof(void*) * 8); 
}
__declspec(noinline) INT16 WINAPI GetI16(INT16 num)
{
    return num;
}

...

Number(0xffff); // Not sign extended
const INT16 numi16 = -42;
Number(numi16); // Optimized to 32-bit parameter by the compiler
Number(GetI16(-42)); // Use a helper function to prevent compiler tricks

and this prints:

Number: 65535 0x0000ffff (64 bit)
Number:   -42 0xffffffd6 (64 bit)
Number:   -42 0xffffffd6 (64 bit)

32-bit x86:

; 1040 :    Number(0xffff);

  00022 68 ff ff 00 00  push     65535          ; 0000ffffH
  00027 e8 00 00 00 00  call     ?Number@@YGXJ@Z        ; Number

; 1041 :    const INT16 numi16 = -42;
; 1042 :    Number(numi16);

 0002c  6a d6           push     -42            ; ffffffd6H
 0002e  e8 00 00 00 00  call     ?Number@@YGXJ@Z        ; Number
; 1047 :    Number(GetI16(-42));

 00033  6a d6           push     -42            ; ffffffd6H
 00035  e8 00 00 00 00  call     ?GetI16@@YGFF@Z        ; GetI16
 0003a  0f bf c0        movsx  eax, ax
 0003d  50              push     eax
 0003e  e8 00 00 00 00  call     ?Number@@YGXJ@Z        ; Number

64-bit x86_64/AMD64:

; 1040 :    Number(0xffff);

  00027 b9 ff ff 00 00  mov  ecx, 65535     ; 0000ffffH
  0002c e8 00 00 00 00  call     ?Number@@YAXJ@Z        ; Number

; 1041 :    const INT16 numi16 = -42;
; 1042 :    Number(numi16);

  00031 b9 d6 ff ff ff  mov  ecx, -42       ; ffffffffffffffd6H
  00036 e8 00 00 00 00  call     ?Number@@YAXJ@Z        ; Number

; 1047 :    Number(GetI16(-42));

  0003b 66 b9 d6 ff     mov  cx, -42        ; ffffffffffffffd6H
  0003f e8 00 00 00 00  call     ?GetI16@@YAFF@Z        ; GetI16
  00044 0f bf c8        movsx    ecx, ax
  00047 e8 00 00 00 00  call     ?Number@@YAXJ@Z        ; Number

We can see that the generated code uses MOVSX to sign extend the 16-bit number. This is required by the Windows ABI.

When you use #pragma intrinsic(_InterlockedCompareExchange) things are a little less clear. I could not find a definitive statement in the documentation regarding the ABI of intrinsic functions but we can assume it follows the Windows ABI when it comes to sign extension. The compiler will generate a LOCK CMPXCHG instruction directly without a function call but it will use MOVSX when required.