Intrinsic是编译器内置的特殊函数,由LLVM在IR层硬编码处理,不可取地址、不可链接、不可调试;必须以@llvm.开头,类型通过后缀区分;它向后端明确传达优化意图,绕过优化器模式匹配的不确定性。

Intrinsic 是编译器“认得、但不让你定义”的特殊函数。它不是库函数,也不是你能自己实现的普通函数;它是 LLVM 在 IR 层面预留的语义锚点,由编译器后端硬编码处理——你调用它,LLVM 就知道你想干啥,比如复制内存、数二进制里有几个 1、或者启动向量运算。
为什么不能像普通函数那样声明+定义
普通函数有符号、有地址、能取函数指针、能链接、能被调试器停住;@llvm.memcpy 这类 intrinsic 完全不行:
- 调用
&@llvm.memcpy会触发编译错误:「taking address of intrinsic is illegal」 - 链接时找不到它的符号定义——因为它根本没生成目标文件里的函数体
- 在 GDB 里
break @llvm.memcpy无效,它不会出现在最终二进制里 - Clang 或 LLVM 生成 IR 时直接插入 call 指令,不经过任何头文件或链接步骤
名字带 llvm. 前缀是强制约定
所有合法 intrinsic 名字必须以 @llvm. 开头,比如 @llvm.ctpop.i32、@llvm.va_start。这不是风格建议,而是 IR 解析器的硬性校验规则:
- 前端(如 Clang)生成 IR 时若用了非法前缀,
llc或opt会报错「invalid intrinsic name」 - 自定义函数哪怕行为一模一样,也不能叫
@llvm.my_thing,否则 IR 验证失败 - 重载版本(如
@llvm.ctpop.i8和@llvm.ctpop.i64)靠后缀区分类型,不能省略
后端决定它最终变成几条指令
intrinsic 不保证一对一映射到单条 CPU 指令,但给了后端“明确意图”:
-
@llvm.bswap.i32在 x86 上通常转成一条bswap指令;在无该指令的架构上,可能展开为一系列移位+or -
@llvm.memcpy在小尺寸且对齐时可能内联为几个mov;大尺寸时可能调用 libc 的memcpy - 而你手写一个循环做 memcpy,优化器未必能识别——即使识别了,也依赖 -O2/-O3 级别,且不可靠
真正容易被忽略的是:intrinsic 的存在本身就是为了绕过优化器的模式匹配不确定性。你写 @llvm.ctpop.i64,就是在告诉后端“我要数 popcount,别猜,直接给我最高效的实现”。这种语义直通,是普通函数永远做不到的。

















