How to disable clang expression elimination for thread_local variable

问题描述 投票:0回答:1
thread_local int* tls = nullptr;
// using libcontext to jump stack.
void jump_stack();
void* test() {
    // before jump_stack, assume we are at thread 1.
    int *cur_tls = tls;
    jump_stack();
    // after jump stack, we are at thread 2.
    // we need to reload tls.
    cur_tls = tls;
}

OSX:达尔文内核版本 22.1.0(Apple M1 芯片)
Clang:Apple clang 版本 14.0.0 (clang-1400.0.29.202)

clang++ -c test.cpp --std=c++11 -g -O0
; void* test() {
       0: ff c3 00 d1   sub     sp, sp, #48
       4: fd 7b 02 a9   stp     x29, x30, [sp, #32]
       8: fd 83 00 91   add     x29, sp, #32
       c: 00 00 00 90   adrp    x0, 0x0 <ltmp0+0xc>
      10: 00 00 40 f9   ldr     x0, [x0]
      14: 08 00 40 f9   ldr     x8, [x0]
      18: 00 01 3f d6   blr     x8
      1c: e0 07 00 f9   str     x0, [sp, #8]
;       int *cur_tls = tls;
      20: 08 00 40 f9   ldr     x8, [x0]
      24: e8 0b 00 f9   str     x8, [sp, #16]
;       jump_stack();
      28: 00 00 00 94   bl      0x28 <ltmp0+0x28>
      2c: e0 07 40 f9   ldr     x0, [sp, #8]
;       cur_tls = tls;
      30: 08 00 40 f9   ldr     x8, [x0]
      34: e8 0b 00 f9   str     x8, [sp, #16]
; }
      38: a0 83 5f f8   ldur    x0, [x29, #-8]
      3c: fd 7b 42 a9   ldp     x29, x30, [sp, #32]
      40: ff c3 00 91   add     sp, sp, #48
      44: c0 03 5f d6   ret

jump_stack
之前,
tls
已经缓存到
[sp, #16]
中,在
jump_stack
之后然后重新加载
[sp, #16]
cur_tls
中,
tls
属于
thread 1
而不是
thread 2

是否有任何 clang 选项来禁用此优化以重新加载 thread_local 变量始终属于当前线程。

c++ clang llvm clang++
1个回答
0
投票

所有 3 个主要编译器(msvc、gcc、clang)都基于执行线程永远不会改变的假设来优化 tls 访问,就像您的示例一样。
它甚至比看起来更糟糕——由于内联和 CSE,tls 访问也可以跨函数调用边界进行优化。

你需要的是光纤安全的线程本地存储。
(即 tls 访问需要在每次访问时重新评估索引)

不幸的是,MSVC 是目前唯一提供官方方法的编译器,使用

/GT
编译器开关

gcc 和 clang 没有提供任何官方方式来获得这种行为,并且从他们的问题来看也不打算这样做:

    gcc:
  • 错误 26461 - 跨函数调用的线程局部引用的活跃度 决议:WONTFIX
  • clang:
  • 错误 19177 - 在 clang-cl 中实现对 /GT 选项的支持 自 2014 年以来未解决

你也不是第一个遇到这些问题的人;许多其他使用可以在线程之间切换的协程/纤程的项目遇到了同样的问题。

仅举几例:

    userver:
  • #242 thread_local 变量在与 userver 一起使用时导致数据竞争(UB)
  • ldc:
  • #666 跨线程迁移纤程与 TLS
  • qemu:
  • coroutine-ucontext:使用 QEMU_DEFINE_STATIC_CO_TLS()(提交#34145a307d849d0b6734d0222a7aa0bb9eef7407)

gcc 和 clang 解决方法

gcc 和 clang 的建议解决方法是使用 noinline 函数来包装对线程局部变量的访问,例如:

神箭

thread_local int* tls = nullptr; [[gnu::noinline]] int* getTls() { asm volatile(""); return tls; } [[gnu::noinline]] void setTls(int* val) { asm volatile(""); tls = val; }

  • noinline
     防止编译器直接内联函数
  • asm volatile("");
     是必需的,因为这两个函数都没有任何副作用,并且作为一种特殊的副作用来防止编译器优化对该函数的调用。 (参见 
    gcc noinline 文档
这显然会大大降低您的 tls 访问速度(现在每次访问都需要额外的函数调用,并且每次都需要重新评估 tls 索引)——但至少它会正常工作。

(qemu 有一个

整洁的宏


请注意,这只会解决您自己的线程局部变量的问题。

大多数实现还在内部使用线程局部变量(例如
errno

),这些将遇到相同的 tls 缓存问题。
(这也可能导致竞争条件,例如,如果一个线程试图写入另一个线程的 
errno
 的 tls 索引...)

不幸的是,这些线程局部变量没有解决方法(因为它们是从库代码访问的),所以不幸的是,你只能靠自己处理这些局部变量(至少在 clang 和 gcc 上)。

© www.soinside.com 2019 - 2024. All rights reserved.