GCC Builtin

Table of Contents

1. GCC Builtin

1.1. builtin_define

TARGET_CPU_CPP_BUILTINS 通过 builtin_define 来定义 runtime builtin macro (例如 __riscv_mul)

1.2. builtins

通用的 builtin 定义在 builtins.def 中, 实现在 builtins.c 的 expand_builtin 中.

target 自定义的 builtin 由 TARGET_INIT_BUILTINSTARGET_EXPAND_BUILTIN 定义和实现.

builtin 总的来说可以分成两大类:

  1. lib builtin, 即在标准库中有对应的实现, 例如 strcmp
  2. gcc builtin, 即在标准库中没有实现, 例如 __builtin_expect

前者通过 __builtin_xxx 和 xxx 都可以使用, 后者必须通过 __builtin_xxx 的方式使用.

通过 `-fno-builtin` 可以强制 `strcmp` fallback 到标准库的实现, 但对 `__builtin_xxx` 形式的调用没有作用.

builtin 具体可以分配几类:

  • math builtins
  • _Complex math builtins.
  • string/memory builtins.
  • stdio builtins.
  • ctype builtins.
  • wctype builtins.
  • integer overflow checking builtins.
  • miscellaneous builtins.

其中有一些实现特殊功能的 builtin:

1.2.1. __builtin_expect

#include <stdio.h>
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)

int foo(int x) {
    if (unlikely(x > 0)) {
        return x + 1;
    } else {
        return -x + 1;
    }
}

int main(int argc, char *argv[]) {
    int ret = 0;
    for (int i = 0; i < 1000; i++) {
        ret += foo(argc);
    }
    printf("%d\n", ret);
    return 0;
}

使用 __builtin_expect 时 gcc 编译出来的 foo 是:

$> gdb-multiarch ./a.out -ex "disass foo" --batch

Dump of assembler code for function foo:
   0x0000000000010464 <+0>:     bgtz    a0,0x10470 <foo+12>
   0x0000000000010468 <+4>:     li      a5,1
   0x000000000001046a <+6>:     subw    a0,a5,a0
   0x000000000001046e <+10>:    ret
   0x0000000000010470 <+12>:    addiw   a0,a0,1
   0x0000000000010472 <+14>:    ret
End of assembler dump.

去掉 unlikely:

int foo(int x) {
    if (x > 0) {
        return x + 1;
    } else {
        return -x + 1;
    }
}

以后, foo 变成:

Dump of assembler code for function foo:
   0x0000000000010464 <+0>:     blez    a0,0x1046c <foo+8>
   0x0000000000010468 <+4>:     addiw   a0,a0,1
   0x000000000001046a <+6>:     ret
   0x000000000001046c <+8>:     li      a5,1
   0x000000000001046e <+10>:    subw    a0,a5,a0
   0x0000000000010472 <+14>:    ret
End of assembler dump.

`__builtin_export(xxx, N)` 从逻辑上等价于 `xxx`

Backlinks

GCC Branch Prediction (GCC Branch Prediction): 1. 通过 __builtin_expect 这类的 hint

1.2.2. __builtin_frame_address

Backlinks

GCC Target Hook (GCC Target Hook > stack 相关 > FRAME_ADDR_RTX): __builtin_frame_address

1.2.3. __builtin_return_address

Backlinks

GCC Target Hook (GCC Target Hook > stack 相关 > RETURN_ADDR_RTX): 这个宏会生成一个返回 `return address` 的 rtx, 主要用来支持 __builtin_return_address, 在 riscv 上就是返回 x1 (ra) 的值

1.2.4. __builtin_object_size

1.2.5. __builtin_prefetch

1.2.6. __builtin_unreachable

1.2.7. __builtin_types_compatible_p

1.2.8. __builtin_constant_p

1.2.9. __builtin_ffs

1.2.10. __builtin_clz

1.2.11. __builtin_ctz

1.2.12. __builtin_popcount

1.2.13. __builtin_bswap32

1.3. builtin optimization

lib builtin 可以利用编译时信息生成优化的代码, 而 libc 中的代码无法利用编译时信息 (因为它们是已经编译过的代码). 以 strlen 为例: libc 中的 strlen 无法针对 strlen("abc") 优化, 因为它的代码编译时已经是确定的, 但 strlen builtin 却可以直接返回 3 (参考 expand_builtin_strlen)

另外, builtin 还可以利用 mtune 信息产生针对某一个 tune 的优化代码, 但 libc 中只能产生针对某个 arch 的 `通用` 代码. 例如 branch_cost

1.4. how to add a builtin

假设需要添加一个 __builtin_dummy, 完成 x=x+1 的动作

1.4.1. builtins.def

;; dummy 的函数原型是 int dummy(int), 所以这里使用 BT_FN_INT_INT
DEF_GCC_BUILTIN (BUILT_IN_DUMMY,
         "dummy", BT_FN_INT_INT,ATTR_NOTHROW_LEAF_LIST)

1.4.2. optabs.def

OPTAB_D (dummy_optab, "dummy$I$a2")

1.4.3. riscv.md

(define_c_enum "unspec" [
  ;; ...
  UNSPEC_DUMMY
  ;; ...
])

(define_insn "dummysi2"
  [(set (match_operand:SI 0 "register_operand" "=r")
    (unspec:SI [(match_operand:SI 1 "register_operand" " r")]
               UNSPEC_DUMMY))]
  ""
  "addi \t%0,%1,10"
  [(set_attr "type" "arith")
   (set_attr "mode" "SI")])

1.4.4. builtins.c

rtx expand_builtin(
    tree exp, rtx target, rtx subtarget, machine_mode mode, int ignore) {
    /* ... */
    case BUILT_IN_DUMMY:
        target = expand_builtin_dummy(exp, target, target_mode);
        if (target) return target;
        break;
    /* ... */
}

static rtx expand_builtin_dummy(
    tree exp, rtx target, machine_mode target_mode) {
    rtx op0 = expand_normal(CALL_EXPR_ARG(exp, 0));
    return expand_unop(GET_MODE(op0), dummy_optab, op0, target, 0);
}

1.5. riscv builtin

riscv 使用 riscv_init_builtins, riscv_expand_builtin 以及 DIRECT_BUILTIN 简化了定义 builtin 的步骤.

处理 builtin 有两步:

  1. init builtin

    init builtin 的作用是让 frontend 能识别出某个函数是 builtin

  2. expand builtin

    expand builtin 的作用是找到 builtin 对应的 md 中的 insn

下面以 thead 的 p extension 实现为例 (https://github.com/T-head-Semi/gcc/commit/f342d033dbaa0a748c4867b1edb7d97dddd71873), 说明定义 riscv builtin 的详细过程

1.5.1. riscv_build_function_type

为了让 frontend 在解析代码时能认出某个函数是 builtin, 需要告诉它这些函数的 fndecl (即 signature), fndecl 包含两部分:

  1. function type
  2. function name

function type 即 ftype, 代表函数的 prototype.

backend 用 DIRECT_BUILTIN 定义 builtin 时使用 riscv_function_type 枚举类型定义了不同的 ftype, 例如 RISCV_SI_FTYPE_SI

enum riscv_function_type {
#define DEF_RISCV_FTYPE(NARGS, LIST) RISCV_FTYPE_NAME##NARGS LIST,
#include "config/riscv/riscv-ftypes-v.def"
#undef DEF_RISCV_FTYPE
  RISCV_MAX_FTYPE_MAX
};

/* config/riscv/riscv-ftypes-v.def */
DEF_RISCV_FTYPE (1, (SI, SI))
DEF_RISCV_FTYPE (1, (UV2HI, UV2HI))
DEF_RISCV_FTYPE (2, (DI, DI, DI))
DEF_RISCV_FTYPE (2, (V4HI, V4QI, V4QI))
/* ... */

/* riscv-builtins.c */
#define RISCV_FTYPE_NAME1(A, B) RISCV_##A##_FTYPE_##B

/* 以 DEF_RISCV_FTYPE (1, (SI, SI)) 为例, 它会展开为:
 * RISCV_SI_FTYPE_SI, 表示该函数原型是 int x(int) */

riscv_build_function_type 的任务是把定义 builtin 时使用的 riscv_function_type 枚举类型 (例如 RISCV_SI_FTYPE_SI) 转换为 frontend 认识的 tree code

static tree riscv_build_function_type(enum riscv_function_type type) {
    static tree types[(int)RISCV_MAX_FTYPE_MAX];

    if (types[(int)type] == NULL_TREE) switch (type) {
#define DEF_RISCV_FTYPE(NUM, ARGS)                                             \
    case RISCV_FTYPE_NAME##NUM ARGS:                                           \
        types[(int)type] =                                                     \
            build_function_type_list(RISCV_FTYPE_ATYPES##NUM ARGS, NULL_TREE); \
        break;
#include "config/riscv/riscv-ftypes-p.def"
#undef DEF_RISCV_FTYPE
            default:
                gcc_unreachable();
        }

    return types[(int)type];
}

// 以 DEF_RISCV_FTYPE (1, (SI, SI)) 为例, RISCV_FTYPE_NAME##... 展开后为:
case RISCV_SI_FTYPE_SI:
  types[RISCV_SI_FTYPE_SI] = build_function_type_list(RISCV_ATYPE_SI, RISCV_ATYPE_SI, NULL_TREE);

// RISCV_ATYPE_SI 开始与 frontend 有关联, 因为 intSI_type_node 是 frontend 代表 int 的 tree:
#define RISCV_ATYPE_SI intSI_type_node

// build_function_type_list 用来生成对应 int x(int) 的 tree. 这个函数在 frontend
// 解析源码时也会调用, 从而导致 frontend 解析 int x(int) 时可以找到之前
// riscv_build_function_type 时生成的 tree
build_function_type_list(tree return_type, ...);
  return build_function_type_list_1 (false, return_type, p);
    return build_function_type (return_type, args);
      // 生成一个 function type tree node, 其中 FUNCTION_TYPE 定义在 tree.def 中
      t = make_node (FUNCTION_TYPE);
      TREE_TYPE (t) = value_type;
      TYPE_ARG_TYPES (t) = arg_types;
      /* If we already have such a type, use the old one.  */
      hashval_t hash = type_hash_canon_hash (t);
      t = type_hash_canon (hash, t);
      // ...
      return t

1.5.2. riscv_init_builtins

gcc 通过 TARGET_INIT_BUILTINS 允许 target 来注册 builtin, riscv 的实现在 riscv_init_builtins

void riscv_init_builtins ():
 for (size_t i = 0; i < ARRAY_SIZE (riscv_builtins); i++) {
      const struct riscv_builtin_description *d = &riscv_builtins[i];
      if (d->avail ()):
        // 前面提到的 riscv_build_function_type
        tree type = riscv_build_function_type (d->prototype);
        size_t fcode = i;
        // NOTE: add_builtin_function 是 backend 与 frontend 关联最重要的一步:它
        // 定义了 int hello(int) 函数对应的 fndecl 是一个 builtin, 并把 fndecl
        // 通过 pushdecl 添加到 name lookup
        riscv_builtin_decls[i]
              = add_builtin_function (d->name, type, fcode, BUILT_IN_MD, NULL, NULL);
        riscv_builtin_decl_index[d->icode] = i;

其中 riscv_builtins 是通过 DIRECT_BUILTIN 填充的:

static const struct riscv_builtin_description riscv_builtins[] = {
  #include "config/riscv/riscv-builtins-v.def"
};

/* config/riscv/riscv-builtins-v.def */
DIRECT_BUILTIN (ave_si, RISCV_SI_FTYPE_SI_SI, dsp32),
/* ... */

/* DIRECT_BUILTIN 展开后为: */
{ CODE_FOR_riscv_ave_si, "__builtin_riscv_ave_si", RISCV_BUILTIN_DIRECT, RISCV_SI_FTYPE_SI_SI, riscv_builtin_avail_dsp32}
/*|d->icode              |d->name                                        |d->prototype         |d->avail()   */

/* CODE_FOR_riscv_ave_si 直接对应着 md 中 insn 的定义, 这个 enum 由 genopinit 生成 */
(define_insn "riscv_ave_<mode>"
  [(set (match_operand:GPR 0 "register_operand" "=r")
     (unspec:GPR
      [(match_operand:GPR 1 "register_operand" "r")
       (match_operand:GPR 2 "register_operand" "r")]
       UNSPEC_AVE))]
  "TARGET_XTHEAD_DSP"
  "ave\\t%0,%1,%2"
)

1.5.3. expand_expr

frontend 在 expand_expr 时识别 builtin

/* expr.c */
expand_expr_real_1
  // NOTE: fndecl 对应前面 add_builtin_function 生成的 tree, 其中
  // fndecl_built_in_p 是 DECL_BUILT_IN_CLASS (node) != NOT_BUILT_IN, 参考
  // add_builtin_function 时使用的 BUILT_IN_MD 参数
  if (fndecl && fndecl_built_in_p (fndecl)):
    return expand_builtin (exp, target, subtarget, tmode, ignore);

1.5.4. riscv_expand_builtin

gcc 通过 TARGET_EXPAND_BUILTIN 允许 target 自己 expand builtin, riscv 的实现在 riscv_expand_builtin

rtx riscv_expand_builtin (tree exp, rtx target, rtx subtarget ATTRIBUTE_UNUSED,
              machine_mode mode ATTRIBUTE_UNUSED,
              int ignore ATTRIBUTE_UNUSED):
  tree name_id = DECL_NAME (fndecl);
  size_t i;

  unsigned int fcode = DECL_FUNCTION_CODE (fndecl);
  /* NOTE: fcode 参考前面 add_builtin_function 时的 fcode 参数  */
  d = &riscv_builtins[fcode];

  switch (d->builtin_type):
    case RISCV_BUILTIN_DIRECT:
      // d->icode 即是 CODE_FOR_xxx, 用它可以和 md 关联
      return riscv_expand_builtin_direct (d->icode, target, exp, true);

可见 riscv_expand_builtin 是直接根据 builtin 找到 icode, 并不需要像 DEF_GCC_BUILTIN 那样用 optab 中转

1.5.5. vector type

前面的例子里使用了 SI 做为参数和返回值的类型, 但实际上 p 扩展中有些函数会定义 vector 类型例如 int8x4_t, 表示 `4 个 int8 构成的 vector`. gcc 本身有 vector extension, 已经支持这种 vector 类型:

  1. frontend 通过 vector attribute 来表示数据类型是 vector

    typedef signed char int8x4_t __attribute__ ((vector_size (4)));
    

    frontend 的 handle_vector_size_attribute 负责构造对应 RISCV_ATYPE_V4SI 的 tree

    static tree handle_vector_size_attribute(
        tree *node, tree name, tree args, int ARG_UNUSED(flags),
        bool *no_add_attrs) {
    
        /* Determine the "base" type to apply the attribute to.  */
        tree type = type_for_vector_size(*node);
        unsigned HOST_WIDE_INT nunits;
        type = type_valid_for_vector_size(type, name, args, &nunits);
    
        /* NOTE: 后面提到的 riscv_build_vector_type 最终也会调用到 build_vector_type */
        tree new_type = build_vector_type(type, nunits);
    
        /* ... */
    }
    
  2. backend 可以使用 RISCV_ATYPE_V4SI 这种 mode 来构造 ftype (以及 fndecl)

    #define RISCV_ATYPE_V4SI riscv_build_vector_type (intSI_type_node, V4SImode)
    

1.5.6. Recap

riscv_builtins 是核心的数据结构, 它由 DIRECT_BUILTIN 构造, 包含了以下信息:

  1. builtin 对应的 fndecl, 这部分由 riscv_init_builtins 负责处理, 通过 riscv_build_function_type, add_builtin_function 等函数把 ftype 等枚举类型转换成 tree node. frontend 在 expand_expr 时会根据 tree node 识别出 builtin
  2. builtin 对应的 insn, 这部分由 riscv_expand_builtin 负责处理, backend 根据 icode 找到 builtin 对应的 insn

Backlinks

RISC-V P Extension (RISC-V Vector Extension > misc > RISC-V P Extension): riscv builtin

Backlinks

GCC (GCC > Builtin): Builtin

GCC Backend (GCC Backend > misc > intrinsic): intrinsic

Author: [email protected]
Date: 2017-08-04 Fri 00:00
Last updated: 2023-04-27 Thu 19:42

知识共享许可协议