为什么LLVM语言中有一些内置函数?

15

我发现在LLVM中有一些内置函数,比如llvm.memcpyllvm.va_start

然而我不知道它们为什么存在以及其他函数为什么不是内置的。例如,由于memcpy的原型在string.h中,为什么其他函数(如strcpy)不被视为内置函数呢?

我注意到前端在某些情况下可能会生成特殊的内置函数调用。对于一个简单的例子:

#include<string.h>

int foo(void){
    char str[10] = "str";
    return 0;
}

clang 生成的 foo 的 LLVM IR 如下:

define i32 @foo() #0 {
entry:
  %str = alloca [10 x i8], align 1
  %0 = bitcast [10 x i8]* %str to i8*
  call void @llvm.memcpy.p0i8.p0i8.i64(i8* %0, i8* getelementptr inbounds ([10 x i8]* @foo.str, i32 0, i32 0), i64 10, i32 1, i1 false)
  ret i32 0
}

llvm.memcpy在IR中被调用,但不在源代码中。但是前端是否可以生成没有这个内部函数的LLVM IR呢?

我还查阅了一份关于llvm语言参考的早期版本文档,发现一些特殊函数如mallocfree包含在LLVM指令中(显然它们已不再存在)。

那么,llvm指令的设计背后有什么深意呢?

2个回答

6
当然,你可以不用memcpy来实现你示例中的功能,只是有点困难(尽管对于只有4个字节的情况可能并不困难,因为可以使用四个单字节移动来完成,跟使用memcpy差别不大。但是如果你初始化字符串“str”使用的是128字节 [而且str足够长以容纳它],那么使用一系列128个单字节移动来完成则相当麻烦,并且生成循环也有点笨拙)。
然而,内在函数的主要意义在于允许编译器(后端)“理解发生了什么”,因为编译器将能够确定[至少对于常量]复制的大小,并且例如生成两个32位移动来存储“str”值到你的str变量中。或者,如果数量很大,则调用真正的memcpy,或为中间大小创建循环.
最终,简单的答案是“因为编译器可以生成比替代方案更好的代码”。
我猜测没有strcpy的原因是,对于常量字符串,strcpy可以被(更有效地)替换为memcpy,而如果字符串不是常量,则strcpymemcpy更复杂,因此对于制作内联优化的好处不那么显著。
理论上,各种功能都可以成为内在功能,但必须进行“成本/效益”分析-你会获得多少收益,以及编写执行它的代码需要多长时间。
[当然,我只是从使用LLVM的经验中推断出这一点,我并不知道谁在LLVM中实现了内在函数。]

2
另一个不使用strcpy的原因是它仅适用于以null结尾的C风格字符串。LLVM不仅适用于C,许多其他语言(例如Fortran、C++)倾向于使用已知长度的字符串,对于这种情况,memcpy更有效率。 - John Zwinck
非常感谢!您能否展示一下没有llvm.memcpy的(伪)IR会是什么样子? - Hongxu Chen
2
基本上只是一个“将常量存储到str [0..3]序列中”的过程。 - Mats Petersson

4
拥有内在函数能够更轻松地扩展LLVM,以利用硬件的功能执行特定操作,否则这些操作就必须编写成软件。在某些CPU类型中,一些操作(例如从一个位置复制数据到另一个位置)可以完全由硬件执行,但在其他类型中则必须编写为普通函数。使用这些内在函数允许LLVM输出对内在函数的调用,然后由编码人员将其转换为目标处理器的最有效形式,即专门的机器指令或实际函数的调用。理论上,你可以拥有单独的特殊IR指令来覆盖所有这些情况,但那样不太具有可扩展性。随着时间的推移,需要创建的指令数量会显著增加。在LLVM文档中,它说:几乎所有对LLVM的扩展都应该从内在函数开始,然后根据需要将其转换为指令。

我觉得 call @llvm.dbg.* 内嵌函数很奇怪。为什么要使用 call 来调用它们呢?它们永远不会成为目标指令。也许这是为了帮助定义依赖关系,以便优化器能够理解它们之间的关系。或者也许内嵌函数最初是为了函数而创建的概念,而他们只是代替了创建新的内嵌元数据 IR 指令。 - vaughan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接