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 变量始终属于当前线程。
所有 3 个主要编译器(msvc、gcc、clang)都基于执行线程永远不会改变的假设来优化 tls 访问,就像您的示例一样。
它甚至比看起来更糟糕——由于内联和 CSE,tls 访问也可以跨函数调用边界进行优化。
你需要的是光纤安全的线程本地存储。
(即 tls 访问需要在每次访问时重新评估索引)
/GT
编译器开关。
gcc 和 clang 没有提供任何官方方式来获得这种行为,并且从他们的问题来看也不打算这样做:
仅举几例:
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 文档)
(qemu 有一个
整洁的宏)
大多数实现还在内部使用线程局部变量(例如errno
errno
的 tls 索引...)不幸的是,这些线程局部变量没有解决方法(因为它们是从库代码访问的),所以不幸的是,你只能靠自己处理这些局部变量(至少在 clang 和 gcc 上)。