LLVM编译器中的内置(built-in)函数

什么是built-in 函数?

在一些.h头文件中或者实现代码中经常会看到一些以__builtin_开头的函数声明或者调用,比如下面的头文件#include <secure/_string.h>中的函数定义:

//这里的memcpy函数的由内置函数__builtin___memcpy_chk来实现。
#if __has_builtin(__builtin___memcpy_chk) || defined(__GNUC__)
#undef memcpy
/* void *memcpy(void *dst, const void *src, size_t n) */
#define memcpy(dest, ...) \
        __builtin___memcpy_chk (dest, __VA_ARGS__, __darwin_obsz0 (dest))
#endif

这些__builtin_开头的符号其实是一些编译器内置的函数或者编译优化处理开关等,其作用类似于宏。宏是高级语言用于预编译时进行替换的源代码块,而内置函数则是用于在编译阶段进行替换的机器指令块。因此编译器的这些内置函数其实并不是真实的函数,而只是一段指令块,起到编译时的内联功能。

内置函数和非内置函数的调用的区别

在一些编译器中会对一些标准库的函数实现改用内置函数来代替,可以起到性能优化的作用。因为执行这些函数调用会在编译时变为直接指令块的执行,而不会产生指令跳转、堆栈等相关的操作而引起的函数调用开销(有一些函数直接就有一条对应的机器指令来实现,如果改用普通函数调用势必性能大打折扣)。不同的编译器对内置函数的支持不尽相同,而且对于是否用内置函数来实现标准库函数也没有统一的标准。比如对于GCC来说它所支持的内置函数都在GCC内置函数列表中被定义和声明,这些内置函数大部分也被LLVM编译器所支持。

本文不会介绍所有的内置函数,而是只介绍其中几个特殊的内置函数以及使用方法。熟练使用这些内置函数可以提升程序的运行性能以及扩展一些编程的模式。

  • __builtin_types_compatible_p()
    这个函数用来判断两个变量的类型是否一致,如果一致返回true否则返回false。这里的变量会忽略一些修饰关键字,比如const int 和 int 会被认为是相同的变量类型。可以用这个函数来判断某个变量是否是特定的类型,还可以用这个函数来做一些类型检查相关的防护逻辑。一般这个函数都和typeof关键字一起使用。
 int a, b
 long c;

int ret1= __builtin_types_compatible_p(typeof(a), typeof(b));  //true
int ret2 = __builtin_types_compatible_p(typeof(a), typeof(c)); //false 
int ret3 = __builtin_types_compatible_p(int , const int);  //true
if  (__builtin_types_compatible_p(typeof(a), int))   //true
{
    
}

  • __builtin_constant_p()
    这个函数用来判断某个表达式是否是一个常量,如果是常量返回true否则返回false。
   int a = 10;
   const int b = 10;
   int ret1  = __builtin_constant_p(10);  //true
   int ret2 = __builtin_constant_p(a);  //false
   int ret3 = __builtin_constant_p(b);  //true
  • __builtin_offsetof()
    这个函数用来获取一个结构体成员在结构中的偏移量。函数的第一个参数是结构体类型,第二个参数是其中的数据成员的名字。
struct S
{
    char m_a;
    long m_b;
};

int offset1 = __builtin_offsetof(struct S, m_a);  //0
int offset2 = __builtin_offsetof(struct S, m_b);  //8

struct S s;
s.m_a = 'a';
s.m_b = 10;
    
char m_a = *(char*)((char*)&s + offset1);   //'a'
long m_b = *(long*)((char*)&s + offset2);  // 10
  • __builtin_return_address()
    这个函数返回调用函数的返回地址,参数为调用返回的层级,从0开始,并且只能是一个常数。假如有一个函数调用栈为A->B->C->D。那么在D函数内调用__builtin_return_address(0)返回的是C函数调用D函数的下一条指令的地址,如果调用的是__builtin_return_address(1)则返回B函数调用C函数的下一条指令的地址,依次类推。这个函数的一个应用场景是被调用者内部可以根据外部调用者的不同而进行差异化处理。
//这个例子演示一个函数foo。如果是被fout1函数调用则返回1,被其他函数调用时则返回0。

#include <dlfcn.h>

extern int foo();

void fout1()
{
    printf("ret1 = %d\n", foo());    //ret1 = 1
}

void fout2()
{
    printf("ret2 = %d\n", foo());    //ret2= 0
}

int foo()
{
     void *retaddr = __builtin_return_address(0);  //这个返回地址就是调用者函数的某一处的地址。  
    //根据返回地址可以通过dladdr函数获取调用者函数的信息。
    Dl_info dlinfo;
    dladdr(retaddr, &dlinfo);
    if (dlinfo.dli_saddr == fout1)
        return 1;
    else
        return 0;
}

__builtin_return_address()函数的另外一个经典的应用是iOS系统中用ARC进行内存管理时对返回值是OC对象的函数和方法的特殊处理。比如一个函数foo返回一个OC对象时,系统在编译时会对返回的对象调用objc_autoreleaseReturnValue函数,而在调用foo函数时则会在编译时插入如下的三条汇编指令:

//arm64位的指令
bl foo
mov fp, fp    //这条指令看似无意义,其实这是一条特殊标志指令。
bl objc_retainAutoreleasedReturnValue

如果考察objc_autoreleaseReturnValue函数的内部实现就会发现其内部用了__builtin_return_address函数。objc_autoreleaseReturnValue函数通过调用__builtin_return_address(0)返回的地址的内容是否是mov fp,fp来进行特殊的处理。具体原理可以参考这些函数的实现,因为它们都已经开源。

  • __builtin_frame_address()
    这个函数返回调用函数执行时栈内存为其分配的栈帧(stack frame)区间中的高位地址值。参数为调用函数的层级,从0开始并且只能是一个常数。这个函数可以用来实现防止栈内存溢出的栈保护处理。因为调用函数内定义的任何的局部变量的地址都必须要小于这个地址值。
void foo(char *buf)
{
   void *frameaddr =  __builtin_frame_address(0);
  
   //定义栈内存变量,长度为100个字节。
   char local[100];

   int buflen = strlen(buf);   //获取传递进来的缓存字符串的长度。
   if (local + buflen > frameaddr)  //进行栈内存溢出判断。
   {
         ptrinf("可能会出现栈内存溢出");
         return;
   }
   
  strcpy(local, buf);
}

  • __builtin_choose_expr()
    这个函数主要用于实现在编译时进行分支判断和选择处理,从而可以实现在编译级别上的函数重载的能力。函数的格式为:
    __builtin_choose_expr(exp, e1, e2)
    其所表达的意思是判断表达式exp的值,如果值为真则使用e1代码块的内容,而如果值为假时则使用e2代码块的内容。这个函数一般都和__builtin_types_compatible_p函数一起使用,将类型判断作为表达式参数。比如下面的代码:
void fooForInt(int a)
{
    printf("int a = %d\n", a);
}

void fooForDouble(double a)
{
    printf("double a=%f\n", a);
}

//如果x的数据类型是整型则使用fooForInt函数,否则使用fooForDouble函数。
#define fooFor(x) __builtin_choose_expr(__builtin_types_compatible_p(typeof(x), int), fooForInt(x), fooForDouble(x))

//根据传递进入的参数类型来决定使用哪个具体的函数。
fooFor(10);
fooFor(10.0);

  • __builtin_expect()
    这个函数的主要作用是进行条件分支预测。 函数主要有两个参数: 第一个参数是一个布尔表达式、第二个参数表明第一个参数的值为真值的概率,这个参数只能取1或者0,当取值为1时表示布尔表达式大部分情况下的值是真值,而取值为0时则表示布尔表达式大部分情况下的值是假值。函数的返回就是第一个参数的表达式的值。
    在一条指令执行时,由于流水线的作用,CPU可以完成下一条指令的取指,这样可以提高CPU的利用率。在执行一条条件分支指令时,CPU也会预取下一条执行,但是如果条件分支跳转到了其他指令,那CPU预取的下一条指令就没用了,这样就降低了流水线的效率。__builtin_expect 函数可以优化程序编译后的指令序列,使指令尽可能的顺序执行,从而提高CPU预取指令的正确率。例如:
if (__builtin_expect (x, 0))
     foo ();

表示x的值大部分情况下可能为假,因此foo()函数得到执行的机会比较少。这样编译器在编译这段代码时就不会将foo()函数的汇编指令紧挨着if条件跳转指令。再例如:

if (__builtin_expect (x, 1))
     foo ();

表示x的值大部分情况下可能为真,因此foo()函数得到执行的机会比较大。这样编译器在编译这段代码时就会将foo()函数的汇编指令紧挨着if条件跳转指令。

为了简化函数的使用,iOS系统的两个宏fastpath和slowpath来实现这种分支优化判断处理。


#define fastpath(x) (__builtin_expect(bool(x), 1))
#define slowpath(x) (__builtin_expect(bool(x), 0))

本节参考自:https://blog.csdn.net/jasonchen_gbd/article/details/44948523

  • __builtin_prefetch()
    这个函数主要用来实现内存数据的预抓取处理。一般CPU内部都会提供几级高速缓存,在高速缓存中进行数据存取要比在内存中速度快。因此为了提升性能,可以预先将某个内存地址中的数据读取或写入到高速缓存中去,这样当真实需要对内存地址进行存取时实际上是在高速缓存中进行。而__builtin_prefetch函数就是用来将某个内存中的数据预先加载或写入到高速缓存中去。函数的格式如下:
    __builtin_prefetch(addr, rw, locality)
    其中addr就是要进行预抓取的内存地址。 rw是一个可选参数取值只能取0或者1,0表示未来要预计对内存进行读操作,而1表示预计对内存进行写操作。locality 取值必须是常数,也称为“时间局部性”(temporal locality) 。时间局部性是指,如果程序中某一条指令一旦执行,则不久之后该指令可能再被执行;如果某数据被访问,则不久之后该数据会被再次访问。该值的范围在 0 - 3 之间。为 0 时表示,它没有时间局部性,也就是说,要访问的数据或地址被访问之后的短时间内不会再被访问;为 3 时表示,被访问的数据或地址具有高 时间局部性,也就是说,在被访问不久之后非常有可能再次访问;对于值 1 和 2,则分别表示具有低 时间局部性 和中等 时间局部性。该值默认为 3 。
    一般执行数据预抓取的操作都是在地址将要被访问之前的某个时间进行。通过数据预抓取可以有效的提高数据的存取访问速度。比如下面的代码实现对数组中的所有元素执行频繁的写之前进行预抓取处理:
//定义一个数组,在接下来的时间中需要对数组进行频繁的写处理,因此可以将数组的内存地址预抓取到高速缓存中去。
int arr[10];
for (int i = 0; i < 10; i++)
{
     __builtin_prefetch(arr+i, 1, 3);
}

//后面会频繁的对数组元素进行写入处理,因此如果不调用预抓取函数的话,每次写操作都是直接对内存地址进行写处理。
//而当使用了高速缓存后,这些写操作可能只是在高速缓存中执行。
for (int i = 0; i < 1000000; i++)
{
     arr[i%10] = i;
}

本节参考自:https://blog.csdn.net/chrysanthemumcao/article/details/8907566


欢迎大家访问欧阳大哥2013的github地址简书地址

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容

  • 创业不等于从0到1,而是从0到N的过程 创新中的从0到1只是完成了万里长城的第一步。真正的创新,是要走完从0到N的...
    丿卿阅读 642评论 0 0
  • 晴 大概是晴天?上课的一天,被刑分支配的一天。很难受。 没什么好说的,依旧是越来越听不下去,连教室里面的人都越来越...
    Cheryl_ak717阅读 181评论 0 0
  • 第193章回顾 此时虽然已经是下午,皇宫里还是一日既往的热闹,宫女们在各个殿之间穿梭着,皇宫的军营里也同样是一片熙...
    陈瀛Neptune阅读 347评论 27 22
  • 1 我很喜欢一个人旅游,说走就走,说停就停,感觉比人多时更放松、放空。 至于安全,好女子浑身是胆。跑得过色狼,...
    青梨夜阅读 2,025评论 2 21
  • 又一年的七夕节。于我,更多的不是爱情,而是我满脑子的亲情。 七仙女,金牛姑。 关于七夕,我的记忆版本是:七夕这天,...
    peach桃子阅读 251评论 0 0