看看这段代码

extern "C" long _InterlockedCompareExchange(long volatile * _Destination, long _Exchange, long _Comparand);

#define MAGIC 1

// Unlike InterlockedIncrement this function not increment from 0 to 1, but return FALSE

bool TryLock(long* pLock)
{
    long Value = *pLock, NewValue;

    for ( ; Value; Value = NewValue)
    {
        NewValue = _InterlockedCompareExchange(pLock, Value + 1, Value);

        if (
#if MAGIC
            NewValue == Value
#else
            Value == NewValue
#endif
            ) return true;
    }

    return false;
}

如果设置 #define MAGIC 0 改变了什么?根据想法一定不能 . 但如果我们将 NewValue == Value 改为 Value == NewValue (只是长值),则使用 CL.EXE 64位编译器 - 生成的代码严重改变了!

我尝试使用两个版本的 CL - 最新 19.00.24210.014.00.50727.762 (超过10年 - 2006年12月)我在所有测试中都得到了绝对相同的代码 . 使用标志 cl /c /FA /O1 编译 - 所以 /O1 优化(与 /Oxs 相同的结果)

MAGIC 1NewValue == Value

TryLock PROC
    mov eax, [pLock]
    jmp @@
@@loop:
    lea edx, [rax+1]
    lock cmpxchg [pLock], edx
    je  @@exit
@@:
    test    eax, eax
    jne @@loop
    ret
@@exit:
    mov al, 1
    ret
TryLock ENDP

但是 MAGIC 0Value == NewValue

TryLock PROC
    mov r8d, [pLock]
    test    r8d, r8d
    je  @@0
@@loop:
    lea edx, [r8+1]
    mov eax, r8d
    lock cmpxchg [pLock], edx
    cmp r8d, eax        ; !!!!!!!!
    je  @@exit
    test    eax, eax
    mov r8d, eax
    jne @@loop
@@0:
    xor al, al
    ret
@@exit:
    mov al, 1
    ret
TryLock ENDP

代码变大,但主要是指令上的显着差异

cmp Value, NewValue

lock cmpxchg 之后的第二个变体 . 真的 lock cmpxchg [p], NewValue 自己设置或重置 ZF 标志和额外 cmp Value, NewValue 变得多余 . 如果我们在汇编中写,我们可以省略它,但是在 c/c++ 上我们无法使用 ZF 作为条件分支 . 没有像 ifzf { /* if ZF == 1 */ } else { /* if ZF == 0 */ } 这样的语句我们需要写 if (NewValue == Value) {} else {} ,结果必须是生成的程序集中的 cmp NewValue, Value . 但我如何为 CL x64 (但不是 x86 !)已经发现了10年(想想所有版本)

这段代码

NewValue = _InterlockedCompareExchange(p, fn(OldValue), OldValue);
if (OldValue == NewValue) ...

转换成

mov eax, OldValue
lock cmpxchg [p], fn(OldValue)
mov NewValue, eax
cmp OldValue, eax ; !!!!
jne @@
....

但这段代码

NewValue = _InterlockedCompareExchange(p, fn(OldValue), OldValue);
if (NewValue == OldValue) ...

转换成

mov eax, OldValue
lock cmpxchg [p], fn(OldValue)
mov NewValue, eax
jne @@
...

所以 CL 理解 cmpxchg 语义并且可以做优化,但仅限于某些情况 .

我在几个测试函数中测试了这个功能,并且两个地方都得到了相同的结果(非常新旧 CL

extern "C" long _InterlockedCompareExchange(long volatile * _Destination, long _Exchange, long _Comparand);

typedef long (*FN)(long* pLock, long Value);

#define MAGIC 1

void TestZF1(long* pLock)
{
    long Value = *pLock, NewValue;

    do 
    {
        Value++;
        NewValue = _InterlockedCompareExchange(pLock, Value ^ 1, Value);
    } while (
#if MAGIC
        NewValue != Value
#else
        Value != NewValue
#endif
        );
}

long TestZF2(long* pLock, FN fn1, FN fn2)
{
    long Value = *pLock, NewValue;

    NewValue = _InterlockedCompareExchange(pLock, Value ^ 1, Value);

    return (
#if MAGIC
        NewValue == Value
#else
        Value == NewValue
#endif
        ? fn1 : fn2) (pLock, NewValue);
}

和生成的程序集:

TestZF1 PROC
    mov r8d, DWORD PTR [rcx]
@@loop:
    add r8d, 1
    mov edx, r8d
    mov eax, r8d
    xor edx, 1
    lock cmpxchg [rcx], edx
IF !MAGIC
    cmp r8d,eax     ; ! in TestZF1 different exactly in this instruction
ENDIF
    jne @@loop
    ret 0
TestZF1 ENDP

IF MAGIC

TestZF2 PROC
    mov r9d, [rcx]
    mov eax, [rcx]
    xor r9d, 1
    lock cmpxchg [rcx], r9d
    cmove   r8, rdx
    mov edx, eax
    jmp r8
TestZF2 ENDP

ELSE

TestZF2 PROC
    mov r10d, [rcx]
    mov r9d, r10d
    xor r9d, 1
    mov eax, r10d
    lock cmpxchg [rcx], r9d
    cmp r10d, eax   ; !!!!!!!!
    cmove   r8, rdx     
    mov edx, eax
    jmp r8
TestZF2 ENDP

ENDIF

几个问题:

  • 为什么 CL x64 优化案例 if (NewValue == Value) 但不优化 if (Value == NewValue)

  • 这是有意识的,特别设计的,还是突然而且未知?

  • 为什么 CL x86 不做这个优化?我所有测试中的最小值 cmp Value,NewValue 指令是否存在

  • 是否可以在没有汇编程序的 c/c++ 上编写代码,以便在带有 CL 的x86上实现它?

  • 有趣 - 是另一个 c/c++ 编译器对 _InterlockedCompareExchange[Pointer] 进行了这种优化吗?