Skip to main content

rv64 gcc 的 atomic 编译问题

atomic 分为 8-bit/16-bit/32-bit/64-bit 几种不同精度。涉及到 8-bit/16-bit 原子操作的指令,在 riscv64 上边是无法用 gcc 编译通过的 (使用 clang 则是没有任何问题)。

我们可以尝试编译 atomic-test.cpp (llvm-project_CheckAtomic.cmake at llvmorg-15.0.5):

#include <atomic>
std::atomic<int> x; // 64 bit
std::atomic<short> y; // 16 bit
std::atomic<char> z; // 8 bit
int main() {
  // 此处是几个简单的 ++ 运算,但是可以观察到当前架构支持哪些精度的原子计算
  ++z;
  ++y;
  return ++x;
}

如果没有 riscv64 的环境,可以在 gentoo 使用 crossdev 部署交叉编译工具链来复现:

sudo emerge --ask sys-devel/crossdev
sudo crossdev --target riscv64-unknown-linux-gnu
riscv64-unknown-linux-gnu-g++ /tmp/atomic-test.cpp

会看到以下报错

/usr/libexec/gcc/riscv64-unknown-linux-gnu/ld: /tmp/ccuxf3jX.o: in function `.L0 ':
atomic-test.cpp:(.text._ZNSt13__atomic_baseIcEppEv[_ZNSt13__atomic_baseIcEppEv]+0x16): undefined reference to `__atomic_fetch_add_1'
/usr/libexec/gcc/riscv64-unknown-linux-gnu/ld: atomic-test.cpp:(.text._ZNSt13__atomic_baseIsEppEv[_ZNSt13__atomic_baseIsEppEv]+0x16): undefined reference to `__atomic_fetch_add_2'
collect2: error: ld returned 1 exit status

报错找不到 __atomic_fetch_add_1 ? 肯定是因为没有链接 libatomic ,我们来试一下

riscv64-unknown-linux-gnu-g++ -latomic /tmp/atomic-test.cpp

这里编译通过了。

但是稍微修改代码,在只保留 64 位的时候,不加 -latomic 也是可以编译通过的,并不需要手动链接,这里比较让人迷惑。

cmake

GCC 在文档里也推荐加 -latomic,他们也是这么做的,比如 gcc/config/riscv/linux.h

/* Because RISC-V only has word-sized atomics, it requries libatomic where
   others do not.  So link libatomic by default, as needed.  */
#undef LIB_SPEC
#ifdef LD_AS_NEEDED_OPTION
#define LIB_SPEC GNU_USER_TARGET_LIB_SPEC \
  " %{pthread:" LD_AS_NEEDED_OPTION " -latomic " LD_NO_AS_NEEDED_OPTION "}"
#else
#define LIB_SPEC GNU_USER_TARGET_LIB_SPEC " -latomic "
#endif

这里我们发现了什么?对!在 --as-needed 的情况下,-latomic 只在有 -pthread (不是 -lpthread ) 的前提下才会自动加上,gcc 把他们绑定在一起了。

我们可以尝试一下 -pthread 能否让我们编译通过

$ riscv64-unknown-linux-gnu-g++ -pthread /tmp/atomic-test.cpp

可以编译通过。

在 CMake 项目中可以通过添加 set(THREADS_PREFER_PTHREAD_FLAG ON) 来将 -lpthread 的 flags 改为 -pthread,此时 -latomic 会被自动带进去。

cmake with glibc>=2.34

但是这在 glibc 2.34 以后就行不通了,glibc 2.34 自己实现了 pthread,不再需要 libpthread。

此时需要新方法: 在 CMake 项目中引用 CheckAtomic.cmake:

以下引用的代码来自 https://www.bilibili.com/video/BV1HZ4y1U7M2/ , 我没测试过

include(DetermineGCCCompatible.cmake)
include(CheckAtomic.cmake)
if (NOT HAVE_CXX_ATOMICS_WITHOUT_LIB)
  link_libraries(atomic)
endif()

也可以参考 [cmake] link atomic library for certain CPU architectures by dlan17 · Pull Request #21743 · xbmc_xbmc, FindAtomic.cmake 可以看作是 llvm 里 CheckAtomic.cmake 的一种简写

据说,在其他架构上比如 ARMEL/MIPSEL/PPC 没有实现 64 位原子操作,也会出现类似的问题,但是我没有证实。

conclusion

总结一下,问题应该怎么修呢:

  1. 对于 Makefile 来说,需要添加在 Makefile 里边加上 -latomic
  2. 对于 CMake 项目来说,需要引用 CheckAtomic.cmake
  3. Gentoo 上可以用 append-libs "-latomic" 或者 append-atomic-flags

我觉得 append-atomic-flags 那段测试代码不如 6856e417 commit message 里边的覆盖全, 但是覆盖到了 risv64 现在的错误场景, 也够用了)

还有一些不那么优雅,可能有后遗症,甚至不一定能用的解法馊主意:

  1. 把 gcc 里边的 pthread 检查去掉,换成这个 https://github.com/riscv-collab/riscv-gcc/commit/2c4857d0981501b7c50bbf228de9e287611f8ae5
  2. 降级 glibc 到 2.34 以下
  3. 换 llvm,并嘲笑 gcc

PS: 至今还不知道 LLVM 为什么没这个问题,是因为加了 CheckAtomic.cmake? 或者是生成了全部原子操作的 inline code? 因为没有报错,我也不知道从哪里下手去看代码。如果你知道,请告诉我!

Some questions

到底需不需要手动链接呢? 我找到了这篇 Slide: CMake 立大功:glibc 更新引出的陈年旧案。 这里边说 64-bit 不需要手动 link libatomic 的原因是 gcc 只给 32 位和 64 位的 atomic value 生成 inline code,其它的(8,16,128位)都会调用 libatomic 中的对应函数。

这么说的话,这个错误不该由下游来修啊,GCC 那边怎么说?最新的进度是,有人给 GCC 提了 PATCH 来实现 inlining subword atomic,并且已经更新到了第四版。

[PATCH v4] RISC-V: Add support for inlining subword atomic operations

第四版里已经实现了 compare_and_exchange/exchange/fetch 这几个操作,等实现完了所有的原子操作,并且合入 GCC,那么以后不需要再操心了。

Ref

除了上文中提到的链接,我还参考了很多。都看不太懂,但是很厉害的样子!

  1. https://github.com/xbmc/xbmc/pull/21743/commits/ac3213e683e4c62c50dc02fef3b168d883245094
  2. https://blog.jiejiss.com/A-RISC-V-gcc-pitfall-revealed-by-a-glibc-update/
  3. https://lists.llvm.org/pipermail/llvm-dev/2018-June/123993.html
  4. https://lists.gnu.org/archive/html/info-gnu/2021-08/msg00001.html
  5. https://www.bilibili.com/video/BV1HZ4y1U7M2/
  6. https://www.bilibili.com/video/BV1Vq4y1a7Ny/
  7. https://github.com/plctlab/PLCT-Open-Reports/blob/master/20220406-CMake%E7%AB%8B%E5%A4%A7%E5%8A%9F%EF%BC%9Aglibc%E6%9B%B4%E6%96%B0%E5%BC%95%E5%8F%91%E7%9A%84%E9%99%88%E5%B9%B4%E6%97%A7%E6%A1%88-panruizhe.pdf
  8. https://github.com/felixonmars/archriscv-packages/wiki/FAQ#%E5%A6%82%E4%BD%95%E8%A7%A3%E5%86%B3-atomic-%E7%9B%B8%E5%85%B3%E7%9A%84%E9%97%AE%E9%A2%98
  9. https://github.com/riscv-collab/riscv-gcc/issues/337
  10. https://www.mail-archive.com/gcc-patches@gcc.gnu.org/msg279752.html
  11. https://www.mail-archive.com/gcc-patches@gcc.gnu.org/msg281336.html
  12. https://www.mail-archive.com/gcc-patches@gcc.gnu.org/msg283119.html