Я пытаюсь использовать 64-битный интеграл в качестве растрового изображения и получать/освобождать право собственности на отдельные биты атомарно.
С этой целью я написал следующий код без блокировки:
#include <cstdint>
#include <atomic>
static constexpr std::uint64_t NO_INDEX = ~std::uint64_t(0);
class AtomicBitMap {
public:
    static constexpr std::uint64_t occupied() noexcept {
        return ~std::uint64_t(0);
    }
    std::uint64_t acquire() noexcept {
        while (true) {
            auto map = mData.load(std::memory_order_relaxed);
            if (map == occupied()) {
                return NO_INDEX;
            }
            std::uint64_t index = __builtin_ctzl(~map);
            auto previous =
                mData.fetch_or(bit(index), std::memory_order_relaxed);
            if ((previous & bit(index)) == 0) {
                return index;
            }
        }
    }
private:
    static constexpr std::uint64_t bit(std::uint64_t index) noexcept {
        return std::uint64_t(1) << index;
    }
    std::atomic_uint64_t mData{ 0 };
};
int main() {
    AtomicBitMap map;
    return map.acquire();
}
 Который, на godbolt, дает следующую сборку в изоляции:
main:
  mov QWORD PTR [rsp-8], 0
  jmp .L3
.L10:
  not rax
  rep bsf rax, rax
  mov edx, eax
  mov eax, eax
  lock bts QWORD PTR [rsp-8], rax
  jnc .L9
.L3:
  mov rax, QWORD PTR [rsp-8]
  cmp rax, -1
  jne .L10
  ret
.L9:
  movsx rax, edx
  ret
 Это именно то, что я ожидал 1.
@Jester героически сумел сократить мой 97- релейный ресивер до более простого 44-полосного репродуктора, который я еще уменьшил до 35 строк:
using u64 = unsigned long long;
struct Bucket {
    u64 mLeaves[16] = {};
};
struct BucketMap {
    u64 acquire() noexcept {
        while (true) {
            u64 map = mData;
            u64 index = (map & 1) ? 1 : 0;
            auto mask = u64(1) << index;
            auto previous =
                __atomic_fetch_or(&mData, mask, __ATOMIC_SEQ_CST);
            if ((previous & mask) == 0) {
                return index;
            }
        }
    }
    __attribute__((noinline)) Bucket acquireBucket() noexcept {
        acquire();
        return Bucket();
    }
    volatile u64 mData = 1;
};
int main() {
    BucketMap map;
    map.acquireBucket();
    return 0;
}
 Что генерирует следующую сборку:
BucketMap::acquireBucket():
  mov r8, rdi
  mov rdx, rsi
.L2:
  mov rax, QWORD PTR [rsi]
  xor eax, eax
  lock bts QWORD PTR [rdx], rax
  setc al
  jc .L2
  mov rdi, r8
  mov ecx, 16
  rep stosq
  mov rax, r8
  ret
main:
  sub rsp, 152
  lea rsi, [rsp+8]
  lea rdi, [rsp+16]
  mov QWORD PTR [rsp+8], 1
  call BucketMap::acquireBucket()
  xor eax, eax
  add rsp, 152
  ret
  xor eax,eax означает, что сборка здесь всегда пытается получить индекс 0... в результате получается бесконечный цикл.
Я могу видеть только два объяснения этой сборки:
- Я каким-то образом вызвал Undefined Behavior.
 - В gcc есть ошибка генерации кода.
 
И я исчерпал все свои идеи относительно того, что может вызвать UB.
 Может ли кто-нибудь объяснить, почему gcc будет генерировать этот xor eax,eax?
Примечание: предварительно указывается gcc как https://gcc.gnu.org/bugzilla/show_bug.cgi?id=86314.
Используемая версия компилятора:
$ gcc --version
gcc (GCC) 7.3.0
Copyright (C) 2017 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is 
NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR 
PURPOSE.
 Флаги компилятора:
-Wall -Wextra -Werror -Wduplicated-cond -Wnon-virtual-dtor -Wvla 
-rdynamic -Wno-deprecated-declarations -Wno-type-limits 
-Wno-unused-parameter -Wno-unused-local-typedefs -Wno-unused-value 
-Wno-aligned-new -Wno-implicit-fallthrough -Wno-deprecated 
-Wno-noexcept-type -Wno-register -ggdb -fno-strict-aliasing 
-std=c++17 -Wl,--no-undefined -Wno-sign-compare 
-g -O3 -mpopcnt
  1 На самом деле, это лучше, чем я ожидал, компилятор понимает, что fetch_or(bit(index)) за которым следует previous & bit(index) является эквивалентом использования bts и проверка флага CF является чистым золотом.