GCC Builtin
Table of Contents
- 1. GCC Builtin
- 1.1. builtin_define
- 1.2. builtins
- 1.2.1. __builtin_expect
- 1.2.2. __builtin_frame_address
- 1.2.3. __builtin_return_address
- 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
- 1.4. how to add a builtin
- 1.5. riscv builtin
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_BUILTINS 和 TARGET_EXPAND_BUILTIN 定义和实现.
builtin 总的来说可以分成两大类:
- lib builtin, 即在标准库中有对应的实现, 例如 strcmp
- 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 有两步:
init builtin
init builtin 的作用是让 frontend 能识别出某个函数是 builtin
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 包含两部分:
- function type
- 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 类型:
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); /* ... */ }
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 构造, 包含了以下信息:
- builtin 对应的 fndecl, 这部分由 riscv_init_builtins 负责处理, 通过 riscv_build_function_type, add_builtin_function 等函数把 ftype 等枚举类型转换成 tree node. frontend 在 expand_expr 时会根据 tree node 识别出 builtin
- 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 Backend (GCC Backend > misc > intrinsic): intrinsic