4

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.

Serge Rogatch
  • 13,865
  • 7
  • 86
  • 158
  • this mean that accept both signed and unsigned integers - - because compare only for equal (but not for less or greater) - no difference signed or unsigned – RbMm Sep 06 '17 at 11:10
  • 1
    The docs look incorrect or at least ambiguous as the types are signed types anyway so it maybe irrelevant, the methods https://msdn.microsoft.com/en-us/library/windows/desktop/ms683560(v=vs.85).aspx don't mention this at all. I'd raise this with MS – EdChum Sep 06 '17 at 11:10
  • @EdChum, I've submitted feedback on that page. – Serge Rogatch Sep 06 '17 at 11:14
  • I rarely use the intrinsics when there are explicit methods such as the 32 and 64-bit versions so I'm surprised it even states this. My understanding is that it makes no sense to state this, especially when it takes signed types, it would still work with signed and unsigned types here is my expectation – EdChum Sep 06 '17 at 11:17
  • @EdChum, what explicit methods do you mean? I'm restraining from using `std::atomic` because it doesn't guarantee it wouldn't use a global mutex, which would ruin my multi-threading... – Serge Rogatch Sep 06 '17 at 11:27
  • I mean the 32-bit [https://msdn.microsoft.com/en-us/library/windows/desktop/ms683560(v=vs.85).aspx](version) and 64-bit [version](https://msdn.microsoft.com/en-us/library/windows/desktop/ms683562(v=vs.85).aspx), these will use intrinsics if supported – EdChum Sep 06 '17 at 12:00
  • But in practice `std::atomic` or `` will be lock-free on any normal compiler for x86-64, and with most compilers in 32-bit mode. You can verify that with [`static_assert( foo.is_always_lock_free() )`](http://en.cppreference.com/w/cpp/atomic/atomic/is_always_lock_free) in C++17, or `foo.is_lock_free()` in C++11 (which will hopefully optimize away at compile time when it's always or never lock free). – Peter Cordes Sep 07 '17 at 20:40
  • I think you'd only need to worry about `cmpxchg16b`, since MSVC's `std::atomic` doesn't do lock-free 16-byte atomics last I checked. – Peter Cordes Sep 07 '17 at 20:41
  • 1
    Any decent `std::atomic` implementation will use an array of locks for objects that aren't lock-free, using a hash of the object address to select which lock. (Could be as simple as using the low bits as an index) So different objects will use different locks, not a single global lock for all objects! – Peter Cordes Sep 07 '17 at 20:49
  • I was curious, so I checked. 32-bit MSVC has lock-free `atomic` using `cmpxchg8b`. It sometimes chooses not to inline it, but it does in a CAS retry loop https://godbolt.org/g/74P5bP. It uses `cmpxchg8b` even for pure loads / store (like clang does), where gcc uses SSE or x87 for atomic 64-bit loads/stores, and only `cmpxchg8b` for RMW. 64-bit MSVC inlines `cmpxchg` and works like you'd expect. – Peter Cordes Sep 07 '17 at 21:10

2 Answers2

2

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.

Community
  • 1
  • 1
Anders
  • 97,548
  • 12
  • 110
  • 164
  • how *value* passed to the function will be extended depend only from value type, but not from *parameter* type. signed value will be signed extended (*movsx*) when unsigned value will be zero extended (*movzx*). say you can declare `Number(ULONG val)` or `Number(LONG val)` and view that result will be *not depend* from *parameter* type (*ULONG* or *LONG*). result will be only depend from value type: say `Number((signed char)-1); -> 0xffffffff` and `Number((unsigned char)-1);->0xff`. so all this (how value will be extend) at all not depend from `_InterlockedCompareExchange` - this is separate – RbMm Sep 07 '17 at 23:31
  • The value (when passed as a parameter) will be sign-extended if the values type is signed and it will be sign-extended up to the size of the parameter type. The signedness of the parameter type does not matter, that is correct. – Anders Sep 08 '17 at 00:41
  • yes, but I exactly this and (try) say in my comment, only on bad English. so how value (when passed as a parameter) will be sign-extended not depend from *_InterlockedCompareExchange* at all - this is common c/c++ rule. so situation absolute clear - in comparison (cmpxchg) used all bits and how value extended - at all separate question – RbMm Sep 08 '17 at 00:47
  • You can't say it like that, "how value (when passed as a parameter) will be sign-extended not depend from _InterlockedCompareExchange at all" is not correct because the function signature does play a role. **If** it will be sign-extended depends on the values type, **how "wide"** (as a minimum) it will be extended depends on the functions parameter type. – Anders Sep 08 '17 at 00:51
  • yes, you correct about how "wide", I mean how it type parameter is declared - signed or unsigned. – RbMm Sep 08 '17 at 00:54
0

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

RbMm
  • 31,280
  • 3
  • 35
  • 56