内联asm编译器屏障(内存阻塞器)是算作外部函数,还是算作静态函数调用
Does the inline asm compiler barrier (memory clobber) count as an external function, or as static function call?
基本事实介绍/确认
众所周知,使用GCC风格的C和C++编译器,可以使用带有"内存"阻塞器的内联汇编:
asm("":::"memory");
以防止(大多数)代码的重新排序超过它,充当(线程本地)"内存屏障"(例如,为了与异步信号交互)。
注意:这些"编译器障碍"无法实现线程间同步
它相当于对非内联函数的调用,可能读取当前范围之外可以读取的所有对象,并更改所有可以更改的对象(非常量对象):
int i;
void f() {
int j = i;
asm("":::"memory"); // can change i
j += i; // not j *= 2
// ... (assume j isn't unused)
}
从本质上讲,它与调用单独编译的NOP函数相同,只是非内联NOP函数调用稍后(1)内联,因此没有任何内容可以从中幸存下来
(1) 比如说,在编译器中间通过之后,在分析之后
因此,这里j
不能更改,因为它是本地的,并且仍然是旧i
值的副本,但i
可能已经更改,因此编译与几乎相同
volatile int vi;
int f2() {
int j = vi;
; // can "change" vi
j += vi; // not j *= 2
return j;
}
vi
的两个读取都是必需的(出于不同的原因),所以编译器不会将其更改为2*vi
。
到目前为止,我的理解正确吗?(我想是的。否则这个问题就没有意义了。)
真正的问题:外部还是静态
以上只是序言。我遇到的问题是静态变量,对静态函数(或C++等价的匿名命名空间)的可能调用:
如果asm指令的输入参数中没有显式命名,内存阻塞器是否可以访问通过非静态函数无法访问的静态数据,并从其他模块调用无法调用的静态函数,因为这些函数在链接阶段都不可见,
static int si;
int f3() {
int j = si;
asm("":::"memory"); // can access si?
j += si; // optimized to j = si*2; ?
return j;
}
[注意:static的使用有点模糊。建议TU的边界很重要,静态变量是TU私有的,但我没有描述它是如何被操纵的。让我们假设它在那个TU中真的被操纵了,或者编译器可能会假设它实际上是一个常量。]
换言之,这是不是相当于对的调用
- 一个外部NOP函数,它不能直接命名
si
,也不能以任何间接方式访问它,因为TU中没有任何函数传达si
的地址,或者使si
可以间接修改 - 可以访问
si
的本地定义的NOP函数
?
奖励问题:全局优化
如果答案是在这种情况下静态变量不像外部变量那样处理,那么一次编译程序会有什么影响?更具体地说:
在整个程序的全局编译过程中,通过对变量值的全局分析和推断,是否知道这样一个事实:例如,全局变量从未被修改(或从未被分配负值…),除非可能在asm
"clobber"中,优化器的输入?
换句话说,如果非静态i
只在一个TU中命名,那么即使有asm
语句,它是否也可以像静态int
一样进行优化?在这种情况下,全局变量是否应该被明确列为clobber?
它相当于对非内联函数的调用,可能读取当前范围之外可以读取的所有对象,并更改所有可以更改的对象(非常量对象):
否。
编译器可以决定在同一编译单元中内联任何函数(然后,如果该函数不是static
,还可以为其他编译单元中的调用方提供一个单独的"未内联"副本,以便链接器可以找到一个);并且通过链接时间代码优化/链接时间代码生成,链接器可以决定内联不同编译单元中的任何函数。目前唯一不可能内联任何函数的情况是当它在共享库中时;但目前存在这种限制,因为操作系统目前无法进行"加载时间优化"。
换句话说;任何函数出现任何类型的障碍都是优化器弱点的意外副作用,不能保证;因此不能/不应该依赖
真正的问题:内联组装
有5种可能性:
a) 编译器理解所有的程序集,并且能够检查内联程序集并确定哪些程序集被破坏了;没有打击名单(也不需要)。在这种情况下(取决于编译器/优化器的先进程度),编译器可能能够确定诸如"该内存区域可能被破坏,但该内存区域不会被破坏"之类的事情,并避免从未被破坏的内存区域重新加载数据的成本。
b) 编译器不理解任何程序集,也没有clobber列表,因此编译器必须假设所有程序集都将被clobber;这意味着编译器必须在执行内联程序集之前生成代码,将所有内容(例如,寄存器中当前使用的值等)保存到内存中,然后重新加载所有内容,这将导致非常糟糕的性能。
c) 编译器不理解任何程序集,并希望程序员提供一个clobber列表,以避免(一些)不得不假设所有程序都会被clobber的性能灾难。
d) 编译器可以理解某些程序集,但不能理解所有程序集,并且没有clobber列表。如果它不理解这个程序集,它就会认为一切都可能被破坏了。
e) 编译器可以理解某些程序集,但不能理解所有程序集,并且有一个(可选?)clobber列表。如果它不理解程序集,它将依赖于clobber列表(和/或如果没有clobber名单,则返回到"假设所有东西都被clobbed"),如果它理解程序集则忽略clobber清单。
当然,使用"选项c)"的编译器可以改进为使用"选项e)";使用"选项e)"的编译器可以改进为使用"选项a)"。
换句话说;对于类似"asm("":::"memory");
"的东西,任何形式的障碍的出现都是编译器"可改进"的意外副作用;因此不能/不应该依赖
摘要
你提到的任何事情都不是真正的障碍。这一切都只是"意外和不希望的优化失败"。
如果你确实需要一个屏障,那么就使用一个实际的屏障(例如"asm("mfence":::"memory");
")。然而(除非你需要线程间同步并且不使用原子),否则你很可能一开始就不需要屏障。
- 调用外部函数,无法指定类型 C++/MVS
- 加载由 MATLAB Coder 生成的带有函数的 DLL,该函数调用外部函数
- add_cuda_library输出:未解析的外部函数
- 外部函数在 main() 上调用时返回分段错误 11.(C++)
- 如何在Windows Media Foundation中获取相机外部函数?
- 内联asm编译器屏障(内存阻塞器)是算作外部函数,还是算作静态函数调用
- antlr cpp target 的标头部分不允许使用具有默认参数的外部函数
- 将多维数组传递给外部"C"函数为 void *
- llvm 调用一个以 char * 作为参数的外部函数
- 将 std::vector<bool> 传递给外部函数
- 如何在类向量中存储/使用外部函数指针
- LLVM - 如何使嵌套函数看到外部函数的变量
- CUDA9.2及以上版本中模板默认参数存在无法解决的外部函数错误
- 宏导入外部函数
- 如何在LLVM Pass中将字符串传递给外部函数
- C++:是否可以区分外部"C"函数和普通函数?
- 从外部函数访问外部结构属性
- 如何将成员声明为指向外部"C"函数的指针?
- C 将外部函数链接为成员函数
- LLVM JIT编译的程序找不到外部函数