GCC Backend

Table of Contents

1. GCC Backend

https://github.com/sunwayforever/gcc-toy/

GNU Compiler Collection (GCC) Internals

GCC RISC-V backend 的最初提交

gcc backend 的功能主要包括:

  1. instruction (insn) selection
  2. rtl optimization
  3. insn schedule
  4. register allocation
  5. code emission

其中 backend 开发的工作主要有:

  1. 完成 machine desc 文件, 用来声明:

    1. backend 支持的 optab
    2. optab 对应的 rtl (Register TransferLanguage)
    3. optab 对应的汇编模板

    md 文件主要用来支持 insn selection 和 code emission

  2. 实现一些 rtl 优化, 例如 combine 和 peephole
  3. 实现一些 target hook 和 target macro, 给通用算法 (register allocation, optimization 等) 提供必要的 backend 相关信息
  4. 如果有需要, 再实现一些 intrinsic

1.1. insn selection

insn selection 通过 `pass_expand` 完成, 目的是生成 gimple 对应的 rtl, 基本过程是:

  1. 根据 gimple 的 tree code (例如 PLUS_EXPR) 找到 optab (例如 add_optab)
  2. 根据 optab 找到 insn_code (例如 CODE_FOR_addsf3)
  3. 根据 insn_code 找到对应的 rtl

1.1.1. gimple tree code

gimple 定义的 tree code 在 `tree.def` 中, 例如:

DEFTREECODE (PLUS_EXPR, "plus_expr", tcc_binary, 2)
DEFTREECODE (MINUS_EXPR, "minus_expr", tcc_binary, 2)
DEFTREECODE (MULT_EXPR, "mult_expr", tcc_binary, 2)
DEFTREECODE (LSHIFT_EXPR, "lshift_expr", tcc_binary, 2)
DEFTREECODE (ABS_EXPR, "abs_expr", tcc_unary, 1)

有些 tree code 并没有直接对应的 C 语法 (例如 ABS_EXPR, MAX_EXPR), 这些 tree code 要么通过 gimple 的 `Match-and-simplify patterns` 或其它一些 gimple optimization 来生成的, 要么通过 builtin 来使用, 例如:

  • MAX_EXPR

    MAX_EXPR 可以通过优化 `a>b?a:b` 或 `if (a>b) {return a; } return b;` 得到:

    #define A(a, b) ((a) > (b) ? (a) : (b))
    
    int main(int argc, char *argv[]) {
        int x = argc;
        int y = -argc;
        int z = A(x, y);
        return z;
    }
    

    生成的 gimple 为:

    main (int argc, char * * argv)
    {
      int D.1506;
    
      {
        int x;
        int y;
        int z;
    
        x = argc;
        y = -argc;
        z = MAX_EXPR <y, x>;
        D.1506 = z;
        return D.1506;
      }
      D.1506 = 0;
      return D.1506;
    }
    
  • ABS_EXPR

    abs 可以通过 `__builtin_fabs` 生成, 也可以通过优化 max(a,-a) 来生成:

    match.pd:
    
    /* max(a,-a) -> abs(a).  */
    (simplify
     (max:c @0 (negate @0))
     (if (TREE_CODE (type) != COMPLEX_TYPE
          && (! ANY_INTEGRAL_TYPE_P (type)
          || TYPE_OVERFLOW_UNDEFINED (type)))
      (abs @0)))
    

    以前面的代码为例, 优化后的 gimple 会变成:

    main (int argc, char * * argv)
    {
      int z;
    
      <bb 2> [local count: 1073741824]:
      z_2 = ABS_EXPR <argc_1(D)>;
      return z_2;
    
    }
    

1.1.2. optab

optab 的定义在 optabs.def, 它是 gimple 与 rtl 之间的桥梁:

  • gimple 的 tree code (例如 PLUS_EXPR) 通过 `optab_for_tree_code` 映射到 optab (例如 add_optab)
  • optabl 通过 optab_handler 映射到 rtl 的 insn_code (例如 CODE_FOR_addsf3)

optabs.def 的部分内容:

OPTAB_NL(add_optab, "add$P$a3", PLUS, "add", '3', gen_int_fp_fixed_libfunc)
OPTAB_NL(sub_optab, "sub$P$a3", MINUS, "sub", '3', gen_int_fp_fixed_libfunc)
OPTAB_NL(smul_optab, "mul$Q$a3", MULT, "mul", '3', gen_int_fp_fixed_libfunc)
OPTAB_NL(sdiv_optab, "div$a3", DIV, "div", '3', gen_int_fp_signed_fixed_libfunc)

OPTAB_NL(and_optab, "and$a3", AND, "and", '3', gen_int_libfunc)
OPTAB_NL(xor_optab, "xor$a3", XOR, "xor", '3', gen_int_libfunc)

OPTAB_NL(ashl_optab, "ashl$a3", ASHIFT, "ashl", '3', gen_int_fixed_libfunc)

OPTAB_NL(eq_optab, NULL, EQ, "eq", '2', gen_fp_libfunc)
OPTAB_DC(cbranch_optab, "cbranch$a4", COMPARE)

OPTAB_NL(neg_optab, "neg$P$a2", NEG, "neg", '2', gen_int_fp_fixed_libfunc)
OPTAB_NC(abs_optab, "abs$P$a2", ABS)

OPTAB_NL(clz_optab, "clz$a2", CLZ, "clz", '2', gen_int_libfunc)
OPTAB_NC(sqrt_optab, "sqrt$a2", SQRT)

OPTAB_DC(mov_optab, "mov$a", SET)
OPTAB_D (atomic_add_fetch_optab, "atomic_add_fetch$I$a")

有些 optab 与 gimple 的 tree code 是对应的 (例如 add, sub, mul, shift 等), 另一些 optab (例如 clz, sqrt) 在 C 的语法中并不存在对应的东西, 这些 optab 主要是给 builtin (intrinsic) 或 tree optimizer 使用.

以 sqrt_optab 为例, 如果 backend 支持 sqrt_optab (通过 TARGET_OPTAB_SUPPORTED_P), 则 gimple 优化阶段会把 `pow (x, 0.5)` 替换成对 `__builtin_sqrt` 的调用, 后者在 insn selection 阶段会找到 sqrt_optab.

1.1.2.1. 构造 optab_handler

optab_handler 相当于一个注册表, 保存着 optab 与 insn_code 的对应关系

out/gcc/insn-opinit.c:

inline enum insn_code optab_handler(optab op, machine_mode mode) {
    unsigned scode = (op << 16) | mode;
    return raw_optab_handler(scode);
}

enum insn_code raw_optab_handler(unsigned scode) {
    int i = lookup_handler(scode);
    return (
        i >= 0 && this_fn_optabs->pat_enable[i] ? pats[i].icode
                                                : CODE_FOR_nothing);
}

static const struct optab_pat pats[NUM_OPTAB_PATTERNS] = {
  { 0x010405, CODE_FOR_extendqihi2 },
  { 0x010406, CODE_FOR_extendqisi2 },
  { 0x010407, CODE_FOR_extendqidi2 },
  { 0x010505, CODE_FOR_extendhihi2 },
  { 0x010506, CODE_FOR_extendhisi2 },
  { 0x010507, CODE_FOR_extendhidi2 },
  /* ... */
}

这个 insn-opinit.c 是 genopinit.c 通过扫描 optab.def 和 md 生成的

1.1.2.1.1. genopinit
genopinit.c:

/* read_md_rtx 是从 md 中依次读入 rtx, 例如
 * addsf3, adddf3, addsi3, ...
 * 需要注意的是 md 中通过 define_mode_iterator 定义的 insn (例如 add<mode>3)
 * 会被展开成多个 rtx: addsf3 和 adddf3
 * */
while (read_md_rtx(&info)):
    switch (GET_CODE(info.def)):
        case DEFINE_INSN:
        case DEFINE_EXPAND:
            gen_insn(&info);
            break;

static void gen_insn (md_rtx_info *info) {
  optab_pattern p;
  if (find_optab (&p, XSTR (info->def, 0))) {
      patterns.safe_push (p);
  }
}
1.1.2.1.2. find_optab

find_optab 是针对每一个 rtx 去查找 optab.def 中是否有对应的 optab, 目的是为了给 optab 找到底层的 rtx 实现.

例如:

addsf3 这个 rtx 会找到它对应 add_optab 且 mode 为 sf (single float) fadddf3 这个 rtx 会找到它也对应 add_optab 且 mode 为 df (double float)

bool find_optab(optab_pattern *p, const char *name) {
    if (*name == 0 || *name == '*') return false;

    /* 针对 name 遍历所有的 optab */
    for (unsigned int pindex = 0; pindex < ARRAY_SIZE(optabs); pindex++) {
        p->m1 = 0;
        /* 所谓的 match_pattern, 是指 rtx 能否满足 optab pattern 的要求
         * 例如 add_optab 有一个 patten 是 `add$F$a3`, 表示三个操作数的浮点加法,
         * 则 addsf3 和 adddf3 都会和 add_optab match. 即 add_optab 针对浮点模式 sf/df 找到了 rtx 实现
         * 实际上, 上层代码例如生成 gimple 时也会通过 optab_handler 去确认相应的 optab 在底层是否支持.
         * */
        if (match_pattern(p, name, optabs[pindex].pattern)) {
            p->name = name;
            p->op = optabs[pindex].op;
            p->sort_num = (p->op << 16) | p->m1;
            /* p->sort_num 即是 pats 中前面的数:
             * { 0x010405, CODE_FOR_extendqihi2 },
             * */
            return true;
        }
    }
    return false;
}
1.1.2.1.3. match_pattern

optabs.def 中的 add$P$a3 是一个 pattern, genopinit 的 match_pattern 函数会根据这个 pattern 来决定 md 中的哪些 insn 对应这个 optab. pattern 中的 $, P, I, a 等都有特定的含义

static bool match_pattern(optab_pattern *p, const char *name, const char *pat) {
    bool force_float = false;
    bool force_int = false;
    bool force_partial_int = false;
    bool force_fixed = false;

    if (pat == NULL) return false;
    for (;; ++pat) {
        if (*pat != '$') {
            if (*pat != *name++) return false;
            if (*pat == '\0') return true;
            continue;
        }
        /* IPFQ 是 optab.def 中表示格式的特殊字符
         * I 表示 int
         * F 表示 float
         * ...
         * */
        switch (*++pat) {
            case 'I':
                force_int = 1;
                break;
            case 'P':
                force_partial_int = 1;
                break;
            case 'F':
                force_float = 1;
                break;
            case 'Q':
                force_fixed = 1;
                break;

            case 'a':
            case 'b': {
                int i;
                /* NOTE: 遍历 target 支持的所有的 machine_mode: sf, df, si, di,
                 * ... */
                for (i = (MAX_MACHINE_MODE)-1; i >= 0; i--) {
                    const char *p, *q;
                    for (p = GET_MODE_NAME(i), q = name; *p; p++, q++)
                        if (TOLOWER(*p) != *q) break;
                    if (*p == 0 &&
                        (!force_int || mode_class[i] == MODE_INT ||
                         mode_class[i] == MODE_VECTOR_INT) &&
                        (!force_float || mode_class[i] == MODE_FLOAT ||
                         mode_class[i] == MODE_DECIMAL_FLOAT ||
                         mode_class[i] == MODE_COMPLEX_FLOAT ||
                         mode_class[i] == MODE_VECTOR_FLOAT) &&
                        ) || (...);
                        break;
                }

                if (i < 0) return false;

                /* 以 optab.def 中的 OPTAB_NX(add_optab, "add$F$a3") 为例
                 * addsf3 会 match 到 "add$F$a3" 这个 pat
                 */
                name += strlen(GET_MODE_NAME(i));
                p->m1 = i;
            } break;
        }
    }
}
1.1.2.2. optab_libfunc

gcc 通过 optab_handler 查找 optab 对应的 insn_code, 当它找不到 `optab+machmode` 对应的 insn 时, 对于某些简单的 optab (例如 smax_optab), 会在代码中直接生成 rtl 来代替 (例如 cbranch), 例如:

expand_binop:
  case MAX_EXPR:
  case MIN_EXPR:
    expand_operands (treeop0, treeop1,
             target, &op0, &op1, EXPAND_NORMAL);

    /* First try to do it with a special MIN or MAX instruction.
   If that does not win, use a conditional jump to select the proper
   value.  */
    this_optab = optab_for_tree_code (code, type, optab_default);
    temp = expand_binop (mode, this_optab, op0, op1, target, unsignedp,
             OPTAB_WIDEN);

对于复杂的功能, 会通过 optab_libfunc 生成对某个外部函数的调用, 例如 __divsf3. 这些函数是在 libgcc 中实现的.

rtx optab_libfunc(optab optab, machine_mode mode) {
    struct libfunc_entry e;
    struct libfunc_entry **slot;

    e.op = optab;
    e.mode1 = mode;
    e.mode2 = VOIDmode;
    slot = libfunc_hash->find_slot(&e, NO_INSERT);
    if (!slot) {
        const struct optab_libcall_d *d =
            &normlib_def[optab - FIRST_NORM_OPTAB];

        if (d->libcall_gen == NULL) return NULL;

        /* d->libcall_gen 中信息来自 optabs.def, 例如
         * OPTAB_NL(add_optab, "add$P$a3", PLUS, "add", '3', gen_int_fp_fixed_libfunc)
         * 表示 add_optab 的 libcall_basename 为 "add", libcall_gen 为 gen_int_fp_fixed_libfunc,
         * 后者会根据 libcall_basename(add) 和 mode (sf) 生成一个名为 __addsf3 的 libfunc_entry
         * 后通过 set_optab_libfunc 设置到 libfunc_hash, 后续就可以生成对 __addsf3 的调用
         * */

        d->libcall_gen(optab, d->libcall_basename, d->libcall_suffix, mode);
        slot = libfunc_hash->find_slot(&e, NO_INSERT);
        if (!slot) return NULL;
    }
    return (*slot)->libfunc;
}

实际上 soft-float 与 hard-float 的区别就是是通过 optab_libfunc 机制来体现的:

(define_insn "div<mode>3"
  [(set (match_operand:ANYF           0 "register_operand" "=f")
    (div:ANYF (match_operand:ANYF 1 "register_operand" " f")
          (match_operand:ANYF 2 "register_operand" " f")))]
  "TARGET_HARD_FLOAT && TARGET_FDIV"
  "fdiv.<fmt>\t%0,%1,%2"
  [(set_attr "type" "fdiv")
   (set_attr "mode" "<UNITMODE>")])

当 TARGET_HARD_FLOAT 时, optab_handler 会找到这里会产生的 divsf3/divdf3 两条 insn. 否则就会通过 optab_libfunc 产生对 __divsf3/__divdf3 的调用

Backlinks

LLVM Backend (LLVM Backend > instruction selection > SelectionDAG): 其中 DAG 中 node 的类型例如 fadd 定义在 ISDOpcodes.h, 和 gcc 的 optab 有些类似

Backlinks

LLVM Backend (LLVM Backend > instruction selection > legalize > libcall): 类似于 gcc optab_libfunc

1.1.3. machine desc

https://www.cse.iitb.ac.in/grc/slides/cgotut-gcc/topic5-md-intro.pdf

https://gcc.gnu.org/onlinedocs/gccint/Machine-Desc.html#Machine-Desc

optab 要找到 insn_code 和 rtl 需要 machine desc 文件.

写 machine desc(md) 文件是开发 backend 的第一步: 它指明了前端的操作 (例如 PLUS) 需要映射到 backend 的什么操作 (例如 fadd.s), 但它并不是直接由前端的 gimple 翻译成汇编, 而是先翻译成 rtl 代码再翻译成汇编.

md 有两个作用:

  1. 生成 rtl

    insn selection 阶段找到 gimple 对应的 rtl

  2. 匹配 rtl

    code emission 阶段找到 rtl 对应的汇编

1.1.3.1. md 语法
1.1.3.1.1. define_insn

md (例如 riscv.md) 是 backend 最核心的部分, 其中最重要的部分是 define_insn, 例如:

(define_insn "add<mode>3"
  [
  ;; -----------------------------
  ;; rtl 模板
  ;; 这个模板会匹配 (set rd (plus rs1 rs2) 形式的 rtl expression
  ;; 其中 register_operand 是指参数需要是一个寄存器, 在
  ;; predicates.md 中有定义
  (set (match_operand:ANYF 0 "register_operand" "=f")
    (plus:ANYF (match_operand:ANYF 1 "register_operand" "f")
           (match_operand:ANYF 2 "register_operand" "f")))]

  ;; -----------------------------
  ;; 匹配该模板需要的额外条件, 这里要求支持 hard float
  "TARGET_HARD_FLOAT"

  ;; -----------------------------
  ;; 汇编模板
  ;; 表示 rtl 模板匹配后输出的汇编, 可以是一个汇编的 string,
  ;; 也可以是一段产生 string 的 C 代码
  "fadd.<fmt>\t%0,%1,%2"

  ;; -----------------------------
  ;; 这些 attr 和 insn schedule 有关
  [(set_attr "type" "fadd")
   (set_attr "mode" "<UNITMODE>")])

ANYF 是一个包含 [SF, DF] 的 mode_iterator, 所以上面的模板实际上会被展开成两个: addsf3, adddf3

define_insn 有两个用法:

  1. 从 gimple 生成 rtl 时会根据 insn code (例如 CODE_FOR_addsf3) 选择对应的 rtl 模板来生成 rtl. addsf3 这个名字是 gcc 提前定义好的标准名字, 表示一个标准的操作 (更多的标准操作: https://gcc.gnu.org/onlinedocs/gccint/Standard-Names.html#Standard-Names)
  2. 经过各种 rtl pass 之后, 最后 code emission 会通过与 rtl 模板匹配确定对应的汇编

例如 test.c 中的 `float y = x + x` 会先通过 addsf3 转换为下面的 rtl 代码:

(insn# 0 0 (set (reg:SF 48 f16)
        (plus:SF (reg:SF 48 f16)
            (reg:SF 49 f17))) test.c:5# {addsf3}
     (nil))

假设这段代码没有被优化修改, 最终在 final 阶段会匹配前面的 `addsf3` 模板, 最终输出汇编 `fadd.s f16,f16,f17`

1.1.3.1.2. define_expand

与 define_insn 不同, 有些 insn 的功能是通过组合其它 insn 来完成的.

define_expand 的 templte 部分可以只声明 operand, 后面嵌入的 c 代码 (preparation statement) 负责通过 rtx 的 api (例如 emit_insn, gen_xxx) 生成 rtx. define_expand 也可以在 template 中写上完整的 template, 然后 c 代码可以对这个 template 做一些修改, 例如修改某个 operand.

gcc toy 中的 `mov` 为例:

(define_expand "mov<ANYI:mode>"
    [(set (match_operand:ANYI 0 "general_operand")
          (match_operand:ANYI 1 "general_operand"))]
  ""
  {
    if (toy_legitimize_move(operands[0], operands[1]))
        DONE;
  }
  )

由于 mov 可以有多种形式的 operand, 但大部分都是不合法的, 例如 `(set (mem ..) (const_int …))` 无法对应任何 riscv 指令, 因为 store 指令不接受 imm operand.

因此需要通过 expand 生成 rtx, 把 const_int 用 `(set (reg …) (const_int …))` load 到一个寄存器, 后续在 code emission 阶段可以匹配到下面这一条合法的 mov pattern:

(define_insn "*mov<mode>"
    [(set (match_operand:ANYI 0 "register_operand" "=r,r")
          (match_operand:ANYI 1 "arith_operand" "r,I"))]
  ""
  "@
   mv\t%0, %1
   li\t%0, %1"
  )

这个过程和 llvm 的 legalize 有点类似

1.1.3.1.3. define_split

define_split 和 define_expand 功能类似,都是生成 rtl. 但与 define_expand 不同的是, define_split 的 rtl 模板部分负责匹配而不是生成 rtl. define_split 的功能是匹配 rtl, 把它们替换成新的 rtl. 如果某个 optab 的实现比较复杂, 可以用 split 来实现, 例如:

(define_insn "ashlsi3"
   [(set (match_operand:SI 0 "register_operand" "=r")
         (ashift:SI (match_operand:SI 1 "register_operand" "r")
                    (match_operand:SI 2 "arith_operand" "i")))]
   ""
   "#"
)
(define_split
   [(set (match_operand:SI 0 "register_operand" "=r")
         (ashift:SI (match_operand:SI 1 "register_operand" "r")
                    (match_operand:SI 2 "arith_operand" "i")))]
   ""
   [(set (match_dup 0)
         (mult:SI (match_dup 1) (match_dup 2)))]
   {
   operands[2]=force_reg(SImode, GEN_INT (1<<(INTVAL (operands[2]))));
   })

define_insn asm 模板中的 `#` 表示它需要由 split 来处理. 可以把 define_insn 和 define_split 写在一起:

(define_insn_and_split "ashlsi3"
  [(set (match_operand:SI 0 "register_operand")
        (ashift:SI (match_operand:SI 1 "register_operand" "=r,r")
                   (match_operand:SI 2 "arith_operand" "r,i")))]
  ""
  "@
   sll\t%0,%1,%2
   #"
  ""
  [(set (match_dup 0)
        (mult:SI (match_dup 1) (match_dup 2)))]
  {
  operands[2]=force_reg(SImode, GEN_INT (1<<(INTVAL (operands[2]))));
  })

对于 operand 为 `r r`, 它相当于 `define_insn`, 对于 operand 为 `r i`, 它与 `define_expand` 基本相同, 只是不需要在代码中通过 emit_insn 生成的 rtl , 而是直接写在 split 的 new-insn-pattern 中

1.1.3.1.4. define_mode_iterator

某个指令针对不同的 mode (SI, DI, SF, DF,. …) 可能会定义类似的 insn, 例如 gcc-toy 中的:

(define_insn "*mov<mode>"
    [(set (match_operand:ANYI 0 "register_operand" "=r,r")
          (match_operand:ANYI 1 "arith_operand" "r,I"))]
  ""
  "@
   mv\t%0, %1
   li\t%0, %1"
  )

其中 ANYI 是一个包含了 [SI, DI, QI, HI] 的 mode iterator.

1.1.3.1.5. define_mode_attr

以前面的 `*mov<mode>` 为例, 使用 ANYI 这个 mode iterator 时, 当 mode 为 SI 时, insn 的名字应该为 "*movsi", 当 mode 为 DI 时, 相应的 insn 的名字应该为 "*movdi", 这里的 "si", "di" 是由 `mode` 这个 mode attri 定义的, 这个内容大约是:

(define_mode_attr mode [(SI "si") (DI "di") (QI "qi") (HI "hi")])
1.1.3.1.6. define_code_iterator

code iterator 中的 `code` 是指 rtl code. 有许多 rtl code 用起来类似, 例如 `plus`, `and`, `ior`, …

以 gcc-toy 为例:

(define_insn "<optab>si3"
    [(set (match_operand:SI          0 "register_operand")
          (arith:SI (match_operand:SI 1 "register_operand")
                   (match_operand:SI 2 "register_operand")))]
  ""
  "<insn>\t%0,%1,%2"
  [])

其中 arith 是一个 code iteator:

(define_code_iterator arith [minus mult div])

这三个 code 对应的指令在 riscv 中有相同的 template

1.1.3.1.7. define_code_attr

上面的例子中, `optab` 和 `insn` 都是 code attr, 它们保存着 code 对应的 optab 和 insn:

(define_code_attr optab [
  (minus "sub")
  (mult "mul")
  (div "div")
  ])

(define_code_attr insn [
  (minus "sub")
  (mult "mul")
  (div "div")
  ])
1.1.3.1.8. define_c_enum
1.1.3.1.9. define_constants
1.1.3.1.10. define_attr
1.1.3.1.11. define_predicate

predicate 是指 match_operand/match_operator 时可以指定的条件, 例如

(define_insn "adddi3"
  [(set (match_operand:DI 0 "register_operand" "=r,r")
    (plus:DI (match_operand:DI 1 "register_operand" " r,r")
         (match_operand:DI 2 "arith_operand"    " r,I")))]
  ...
)

arith_operand 就是通过 define_predicate 指定的 predicate:

(define_predicate "arith_operand"
  (ior (match_operand 0 "const_arith_operand")
       (match_operand 0 "register_operand")))

另外, register_operand 是 gcc 自带的 predicate, 定义在 recog.c 中, 除了 register_operand, gcc 还定义了:

  1. address_operand
  2. immediate_operand
  3. scratch_operand
  4. const_int_operand
  5. const_double_operand
  6. memory_operand
1.1.3.1.12. define_peephole2
1.1.3.1.13. match

rtl 生成汇编时需要通过 recog 来找到 rtl 匹配的 insn_code, genrecog 通过扫描 match_xxx 生成各种 pattern 函数, 用来检测 rtl 是否 define_insn 匹配

https://gcc.gnu.org/onlinedocs/gccint/Predicates.html#Predicates

  1. match_operand

    match_operand 用来 match operand, 例如:

    (match_operand:SI      0           "register_operand"    "r")
    ----------------------------------------------------------------------
                   <mode>  <operand>   <predicate>           <constraints>
    

    其中 constraints 是针对 predicate 的补充, 例如 r 表示寄存器, m 表示 memory, …

    https://gcc.gnu.org/onlinedocs/gccint/Constraints.html#Constraints

  2. match_operator
  3. match_scratch

    match_scratch 在生成 rtl 时可以用来分配一个 scratch 寄存器, 在匹配 rtl 时和 `(match_operand … "register_operand")` 差不多.

    match_scratch 的典型用法类似这样:

    (define_insn "swapsi3"
    [
     (swap (match_operand:si 1 "register_operand")
       (match_operand:si 2 "register_operand"))
     (clobber (match_scratch:si 0 "=r")
    ]
    ""
    mov %0, %1\nmov %1, %2\nmov %2, %0
    ""
    )
    

    即通过 match_scratch 给 swap 分配一个临时寄存器

  4. match_code
  5. match_test
1.1.3.1.15. define_bypass
Backlinks

LLVM TableGen (LLVM TableGen): target description 和 gcc machine description 基本是类似, 它们都包含:

1.1.4. rtl

https://www.cse.iitb.ac.in/grc/gcc-workshop-13/downloads/slides/Day3/gccw13-rtl-intro.pdf

https://gcc.gnu.org/onlinedocs/gccint/index.html#toc-RTL-Representation

https://github.com/sunwayforever/hello_world/tree/main/hello_gcc_plugin/trace_with_rtl

rtl (Register Transfer Language) 相当于 gcc LIR, 主要涉及到寄存器分配, 汇编代码生成和底层优化.

例如下面的 rtl:

(insn/f 40 39 41 (set (mem/c:DI (plus:DI (reg/f:DI 2 sp)
                    (const_int 24 [0x18])) [2  S8 A64])
            (reg/f:DI 8 s0)) "test.c":1:21)

表示 `把 24(sp) 的值为 s0`

1.1.4.1. rtl.def

和 optab 一样, rtl 也定义了一些基本操作, rtl.def 相当于 rtl 语言的词汇表, 前面提到的 `set`, `const_int`, `reg` 等都定义在 rtl.def 中.

例如:

DEF_RTL_EXPR(PLUS, "plus", "ee", RTX_COMM_ARITH)

表示:

  1. rtx_code 为 PLUS
  2. name 为 "plus"
  3. format 为 "ee", 表示两个 operand 都是 `expression`
  4. class 为 RTX_COMM_ARITH

多个 rtx 可嵌套, 例如 `(set x (plus a b))`, 其中:

  1. `set`, `plus` 是 rtx_code
  2. `(plus…)` 是 `(set …)` 的 operand, operand 类型可以是 expression, int, string 等, 通过 XEXP, XINT, XWINT 和 XSTR 宏可以访问, 例如XEXP(x, 2) 是获得 x 的 operand[2] (假设它是一个 expression)

可以使用两种方式来写 rtl:

  1. 直接在 md 中用纯文本来构造, 然后通过 genemit 生成 rtx, 例如 md 中 `define_insn` 的做法,
  2. 通过 gengenrtl 工具扫描 rtl.def 生成权举类型 (例如 PLUS) 和函数 (例如 gen_rtx_PLUS), 然后通过 c 代码来构造 rtx, 参考 md 中 `define_expand` 的做法
1.1.4.2. constant expression
  • const_int
  • const_string
  • label_ref

    (define_insn "*branch<mode>"
      [(set (pc)
        (if_then_else
         (match_operator 1 "order_operator"
                 [(match_operand:X 2 "register_operand" "r")
                  (match_operand:X 3 "reg_or_0_operand" "rJ")])
         (label_ref (match_operand 0 "" ""))
         (pc)))]
      ""
      "b%C1\t%2,%z3,%0")
    
  • code_label
1.1.4.3. register memory expression
  • reg
  • scratch
  • pc
  • mem
1.1.4.4. arithmetic expression
  • plus/minus/mult/div/mod
  • compare
  • neg
  • not/and/or/xor/ior
  • ashift
  • ss_abs/sqrt
  • clz/ctz
  • eq/ne/gt/gtu/lt
  • if_then_else
  • cond
1.1.4.5. conversion expression
  • sign_extend/zero_extend
  • float_extend
  • float_truncate

    把 df truncate 成 sf:

    (define_insn "truncdfsf2"
      [(set (match_operand:SF     0 "register_operand" "=f")
        (float_truncate:SF
            (match_operand:DF 1 "register_operand" " f")))]
      "TARGET_DOUBLE_FLOAT"
      "fcvt.s.d\t%0,%1")
    
  • truncate
  • float
1.1.4.6. side effect expression
  • set
  • call
  • clobber

    clobber 是指 rtl 会隐式的修改这个寄存器, 例如:

    (define_insn "call_internal"
      [(call (mem:SI (match_operand 0 "call_insn_operand" "l,S,U"))
         (match_operand 1 "" ""))
       (clobber (reg:SI RETURN_ADDR_REGNUM))]
      ""
      "@
       jalr\t%0
       call\t%0
       call\t%0@plt"
    )
    

    call 会隐式给 ra 赋值, 所以用 `(clobber (reg:SI ra))`.

    clobber 与 CALL_USED_REGISTERS 的意义类似.

  • use

    use 是指 rtl 会隐式的读取这个寄存器, 防止前面针对这个寄存器的写操作被优化掉,例如:

    (define_insn "simple_return_internal"
      [(simple_return)
       (use (match_operand 0 "pmode_register_operand" ""))]
      ""
      "jr\t%0"
    )
    

    表示 simple_return_internal 的第一个 operand 需要被隐式使用.

  • parallel
Backlinks

LLVM Backend (LLVM Backend > instruction selection > SelectionDAG): 可以把 SDAG 看做 gcc rtl, 但与 gcc 不同的是, 生成 SDAG 时并不需要 td, td 中定义 的 pattern 只是用来 isel 时做匹配.

1.1.5. example

float foo(float x) {
    if (x > 0.0) {
        return x + 1.0;
    }
    return 0.0;
}

rtl 的 `pass_expand` 用来把 gimple 转换为 rtl, 以 `两个浮点数相加` 为例:

  1. add 对应的 gimple subcode (tree code) 为 PLUS_EXPR, 定义在 tree.def 中
  2. 通过 `optab_for_tree_code` 查找 optab, PLUS_EXPR 对应的 optab 为 add_optab, 定义在 optabs.def. PLUS_EXPR 与 add_optab 的对应关系是预设好的. 除了 `optab_for_tree_code`, 有时也会直接在代码中指定 tree code 与 optab 的映射关系
  3. 通过 `optab_handler` 查找 (add_optab, E_SFmode) 对应的 insn_code 为 CODE_FOR_addsf3, 定义在 md 中. add_optab 与 CODE_FOR_addsf3 的对应关系由 genopinit 根据 optabs.def 生成 ([email protected])
  4. 用 insn_code 做索引在 insn_data 中查找到 `gen_addsf3` 函数. insn_data 由 genoutput 扫描 md 生成. gen_addsf3 由 genemit 生成
  5. gen_addsf3 函数会返回 addsf3 对应的 rtl
1.1.5.1. backtrace
#0  gen_addsf3 (operand0=0x7fffffffb250, operand1=0x7ffff7786bb8,
    operand2=0xf8360d <maybe_legitimize_operands(insn_code, unsigned int, unsigned int, expand_operand*)+46>) at insn-emit.c:45
#1  0x0000000000ef17fa in insn_gen_fn::operator()<rtx_def*, rtx_def*, rtx_def*> (this=0x1ecacb8 <insn_data+56>) at ../.././riscv-gcc/gcc/recog.h:407
#2  0x0000000000f83a1e in maybe_gen_insn (icode=CODE_FOR_addsf3, nops=3, ops=0x7fffffffb250) at ../.././riscv-gcc/gcc/optabs.c:7787
#3  0x0000000000f6ef4a in expand_binop_directly (icode=CODE_FOR_addsf3, mode=E_SFmode, binoptab=add_optab, op0=0x7ffff7786be8, op1=0x7ffff7786c18,
    target=0x7ffff7786bb8, unsignedp=0, methods=OPTAB_LIB_WIDEN, last=0x7ffff7676540) at ../.././riscv-gcc/gcc/optabs.c:1409
#4  0x0000000000f6f417 in expand_binop (mode=E_SFmode, binoptab=add_optab, op0=0x7ffff7786be8, op1=0x7ffff7786c18, target=0x7ffff7786bb8, unsignedp=0,
    methods=OPTAB_LIB_WIDEN) at ../.././riscv-gcc/gcc/optabs.c:1496
#5  0x0000000000c9079a in expand_expr_real_2 (ops=0x7fffffffbb80, target=0x7ffff7786bb8, tmode=E_SFmode, modifier=EXPAND_NORMAL)
    at ../.././riscv-gcc/gcc/expr.c:10007
#6  0x0000000000b197f4 in expand_gimple_stmt_1 (stmt=0x7ffff7784000) at ../.././riscv-gcc/gcc/cfgexpand.c:3947
#7  0x0000000000b199f1 in expand_gimple_stmt (stmt=0x7ffff7784000) at ../.././riscv-gcc/gcc/cfgexpand.c:4008
#8  0x0000000000b20b17 in expand_gimple_basic_block (bb=0x7ffff7761138, disable_tail_calls=false) at ../.././riscv-gcc/gcc/cfgexpand.c:6045
#9  0x0000000000b223d3 in (anonymous namespace)::pass_expand::execute (this=0x23401c0, fun=0x7ffff777e000) at ../.././riscv-gcc/gcc/cfgexpand.c:6729
#10 0x0000000000fdd7d1 in execute_one_pass (pass=0x23401c0) at ../.././riscv-gcc/gcc/passes.c:2567
#11 0x0000000000fddb1e in execute_pass_list_1 (pass=0x23401c0) at ../.././riscv-gcc/gcc/passes.c:2656
#12 0x0000000000fddbab in execute_pass_list (fn=0x7ffff777e000, pass=0x233c270) at ../.././riscv-gcc/gcc/passes.c:2667
#13 0x0000000000b6f617 in cgraph_node::expand (this=0x7ffff7780000) at ../.././riscv-gcc/gcc/cgraphunit.c:1830
#14 0x0000000000b6fd15 in cgraph_order_sort::process (this=0x2333788) at ../.././riscv-gcc/gcc/cgraphunit.c:2069
#15 0x0000000000b6ffce in output_in_order () at ../.././riscv-gcc/gcc/cgraphunit.c:2137
#16 0x0000000000b705a6 in symbol_table::compile (this=0x7ffff7670000) at ../.././riscv-gcc/gcc/cgraphunit.c:2355
#17 0x0000000000b709d2 in symbol_table::finalize_compilation_unit (this=0x7ffff7670000) at ../.././riscv-gcc/gcc/cgraphunit.c:2539
#18 0x000000000110d05f in compile_file () at ../.././riscv-gcc/gcc/toplev.c:482
#19 0x00000000011100c6 in do_compile () at ../.././riscv-gcc/gcc/toplev.c:2201
#20 0x00000000011103e8 in toplev::main (this=0x7fffffffc176, argc=14, argv=0x7fffffffc288) at ../.././riscv-gcc/gcc/toplev.c:2340
#21 0x0000000001bf4bf5 in main (argc=14, argv=0x7fffffffc288) at ../.././riscv-gcc/gcc/main.c:39
1.1.5.2. gimple code
float foo (float x)
{
  float D.1491;
  float _1;
  float _3;
  float _4;

  <bb 2> :
  if (x_2(D) > 0.0)
    goto <bb 3>; [INV]
  else
    goto <bb 4>; [INV]

  <bb 3> :
  _4 = x_2(D) + 1.0e+0;
  // predicted unlikely by early return (on trees) predictor.
  goto <bb 5>; [INV]

  <bb 4> :
  _3 = 0.0;

  <bb 5> :
  # _1 = PHI <_4(3), _3(4)>
<L2>:
  return _1;


1.1.5.3. cbranch_optab

bb2 的第一个 stmt `if (x_2(D) > 0.0)` 的处理过程:

expand_gimple_basic_block()
    if (gimple_code (stmt) == GIMPLE_COND):
        new_bb = expand_gimple_cond (bb, as_a <gcond *> (stmt));

expand_gimple_cond:
   /* tree code 为 GT_EXPR, 定义在 tree.def */
    code = gimple_cond_code (stmt);
    if (true_edge->dest == bb->next_bb):
        jumpifnot_1 (code, op0, op1, label_rtx_for_bb (false_edge->dest), false_edge->probability);

label_rtx_for_bb:
    rtx_code_label *l = gen_label_rtx ();
        /* gen_rtx_CODE_LABEL 生成一个 `code_label` rtx */
        return as_a <rtx_code_label *> (gen_rtx_CODE_LABEL (VOIDmode, NULL_RTX, NULL_RTX,NULL, label_num++, NULL));

jumpifnot_1:
    /* code 为 GT */
    do_compare_rtx_and_jump (op0, op1, code, unsignedp, mode,((mode == BLKmode) ? expr_size (treeop0) : NULL_RTX),
               if_false_label, if_true_label, prob);
        emit_cmp_and_jump_insns (op0, op1, code, size, mode, unsignedp, if_true_label, prob);
            prepare_cmp_insn (op0, op1, comparison, size, unsignedp, OPTAB_LIB_WIDEN, &test, &mode);
                /* cbranchsf4 要求 3 个参数, operand0 是要 test 的条件, comparison 是 GT */
                test = gen_rtx_fmt_ee (comparison, VOIDmode, x, y);
                enum insn_code icode;
                /* NOTE: 映射到 cbranch_optab, 后者又映射为 CODE_FOR_cbranchsf4 */
                icode = optab_handler (cbranch_optab, cmp_mode);
                rtx op0 = prepare_operand (icode, x, 1, mode, cmp_mode, unsignedp);
                rtx op1 = prepare_operand (icode, y, 2, mode, cmp_mode, unsignedp);
                XEXP (test, 0) = op0;
                XEXP (test, 1) = op1;
                /* NOTE: test 是要返回的 rtx  */
                *ptest = test;
        emit_cmp_and_jump_insn_1
            /* NOTE: GEN_FCN(icode) 即 gen_cbranchsf4 */
            insn = emit_jump_insn (GEN_FCN (icode) (test, XEXP (test, 0), XEXP (test, 1), label));
1.1.5.4. add_optab

bb3 的第一个 stmt `_4 = x_2(D) + 1.0e+0` 的处理过程:

expand_gimple_basic_block:
    expand_gimple_stmt(stmt);
       /* stmt->code 为 GIMPLE_ASSIGN, stmt->subcode (tree code) 为 PLUS_EXPR */
       /* target 是需要被 assign 的 rtx (例如 reg) */
       temp = expand_expr_real_2 (&ops, target, GET_MODE (target), EXPAND_NORMAL);
           /* NOTE: code 为 PLUS_EXPR, 返回的 this_optab 为 add_optab */
           this_optab = optab_for_tree_code (code, type, optab_default);
           /* mode 为 E_SFmode */
           temp = expand_binop (mode, this_optab, op0, op1, target, unsignedp, OPTAB_LIB_WIDEN);
               /* NOTE: icode 为 CODE_FOR_addsf3 */
               icode = optab_handler (binoptab, mode);
               create_output_operand (&ops[0], target, tmp_mode);
               create_input_operand (&ops[1], xop0, mode0);
               create_input_operand (&ops[2], xop1, mode1);
               pat = maybe_gen_insn (icode, 3, ops);
                   /* NOTE: 生成 rtx */
                   gen_addsf3();
1.1.5.5. mov_optab

bb4 的第一个 stmt `_3 = 0.0` 的处理过程:

expand_gimple_stmt:
    /* stmt->code 为 GIMPLE_ASSIGN, stmt->subcode 为 REAL_CST (real constant) */
    expand_assignment (lhs, rhs, ...)
      result = store_expr (from, to_rtx, 0, nontemporal, false);
          emit_move_insn (target, temp);
              /* NOTE: mov_optab 对应 CODE_FOR_movsf */
              code = optab_handler (mov_optab, mode);
              /* NOTE: gen_movesf */
              emit_insn (GEN_FCN (code) (x, y));

1.2. rtl optimization

1.3. insn schedule

1.4. register allocation

pass_ira 和 pass_reload

1.5. code emission

rtl 最终由 pass_final 生成 assembly

  1. 通过 recog 函数查找 rtl 对应的 insn_code. recog 由 genrecog 扫描 md 生成. 整个 recog 相当于一个根据 md 生成的巨大的 switch-case
  2. 通过 `get_insn_template` 从 insn_data 以 insn_code 为索引查找汇编模板

1.5.1. backtrace

#0  get_insn_template (code=0, insn=0x7fffffffbc00) at ../.././riscv-gcc/gcc/final.c:2063
#1  0x0000000000ca46cb in final_scan_insn_1 (insn=0x7ffff7676880, file=0x2352480, optimize_p=0, nopeepholes=0, seen=0x7fffffffbdbc)
    at ../.././riscv-gcc/gcc/final.c:3062
#2  0x0000000000ca4b06 in final_scan_insn (insn=0x7ffff7676880, file=0x2352480, optimize_p=0, nopeepholes=0, seen=0x7fffffffbdbc)
    at ../.././riscv-gcc/gcc/final.c:3175
#3  0x0000000000ca2a44 in final_1 (first=0x7ffff77870a8, file=0x2352480, seen=0, optimize_p=0) at ../.././riscv-gcc/gcc/final.c:2022
#4  0x0000000000ca76c5 in rest_of_handle_final () at ../.././riscv-gcc/gcc/final.c:4680
#5  0x0000000000ca792f in (anonymous namespace)::pass_final::execute (this=0x2342260) at ../.././riscv-gcc/gcc/final.c:4758
#6  0x0000000000fdd7d1 in execute_one_pass (pass=0x2342260) at ../.././riscv-gcc/gcc/passes.c:2567
#7  0x0000000000fddb1e in execute_pass_list_1 (pass=0x2342260) at ../.././riscv-gcc/gcc/passes.c:2656
#8  0x0000000000fddb4f in execute_pass_list_1 (pass=0x2341d80) at ../.././riscv-gcc/gcc/passes.c:2657
#9  0x0000000000fddb4f in execute_pass_list_1 (pass=0x2340220) at ../.././riscv-gcc/gcc/passes.c:2657
#10 0x0000000000fddbab in execute_pass_list (fn=0x7ffff777e000, pass=0x233c270) at ../.././riscv-gcc/gcc/passes.c:2667
#11 0x0000000000b6f617 in cgraph_node::expand (this=0x7ffff7780000) at ../.././riscv-gcc/gcc/cgraphunit.c:1830
#12 0x0000000000b6fd15 in cgraph_order_sort::process (this=0x2333788) at ../.././riscv-gcc/gcc/cgraphunit.c:2069
#13 0x0000000000b6ffce in output_in_order () at ../.././riscv-gcc/gcc/cgraphunit.c:2137
#14 0x0000000000b705a6 in symbol_table::compile (this=0x7ffff7670000) at ../.././riscv-gcc/gcc/cgraphunit.c:2355
#15 0x0000000000b709d2 in symbol_table::finalize_compilation_unit (this=0x7ffff7670000) at ../.././riscv-gcc/gcc/cgraphunit.c:2539
#16 0x000000000110d05f in compile_file () at ../.././riscv-gcc/gcc/toplev.c:482
#17 0x00000000011100c6 in do_compile () at ../.././riscv-gcc/gcc/toplev.c:2201
#18 0x00000000011103e8 in toplev::main (this=0x7fffffffc176, argc=14, argv=0x7fffffffc288) at ../.././riscv-gcc/gcc/toplev.c:2340
#19 0x0000000001bf4bf5 in main (argc=14, argv=0x7fffffffc288) at ../.././riscv-gcc/gcc/main.c:39

1.5.2. final_scan_insn_1

final_scan_insn_1:
    /* insn code 在 rtl.def 中定义 */
    switch (GET_CODE (insn)):
        case CODE_LABEL:
            /* internal_label 指向 default_internal_label, 产生 .L6: 形式的 label */
            targetm.asm_out.internal_label (file, "L", CODE_LABEL_NUMBER (insn));
                char *const buf = (char *) alloca (40 + strlen (prefix));
                ASM_GENERATE_INTERNAL_LABEL (buf, prefix, labelno);
                ASM_OUTPUT_INTERNAL_LABEL (stream, buf);

        default:
            insn_code_number = recog_memoized (insn);
            templ = get_insn_template (insn_code_number, insn);
            output_asm_insn (templ, recog_data.operand);

1.5.3. recog_memoized

recog_memoized 的作用是根据 rtl 查找它对应的 insn_code, 查找的依据是 md 中声明的 predicate.

最简单的 recog 实现可以这样:

针对一条 rtl, 与 md 中所有 insn 的 rtl 模板比较, 看哪一条 insn 匹配.

但实际实现上 recog 采用的是类似于字典树 (trie) 的方式:

genrecog 工具会扫描 md 中所有的 insn, 根据 insn 声明的 predicate 构造一棵树, 例如:

  • A 指令的 predicate 为 x, y, z
  • B 指令的 predicate 为 x, y, k

则 genrecog 会生成一棵这样的树:

x--->y--->z
     |
     +--->k

genrecog 生成的 recog 函数会在这棵树上遍历来匹配 insn

1.5.3.1. movdi_64bit

以 movdi_64bit 为例, 要匹配的 insn 为:

(insn/f 40 39 41
        (set (mem/c:DI (plus:DI (reg/f:DI 2 sp) (const_int 24 [0x18])) [2  S8 A64])
        (reg/f:DI 8 s0)) "test.c":1:21
)

recog 的过程为:

static inline int recog_memoized(rtx_insn *insn) {
    if (INSN_CODE(insn) < 0) INSN_CODE(insn) = recog(PATTERN(insn), insn, 0);
    return INSN_CODE(insn);
}

/* x1 为 PATTERN(insn):
 *
 * (set (mem/c:DI (plus:DI (reg/f:DI 2 sp)
 *             (const_int 24 [0x18])) [2  S8 A64])
 *     (reg/f:DI 8 s0))
 *
 */
recog(x1, insn):
    switch (GET_CODE (x1)):
        /* x1->code=SET */
        case SET:
            return recog_19 (x1, insn, pnum_clobbers);

static int recog_19(
    rtx x1 ATTRIBUTE_UNUSED, rtx_insn *insn ATTRIBUTE_UNUSED,
    int *pnum_clobbers ATTRIBUTE_UNUSED) {
    rtx *const operands ATTRIBUTE_UNUSED = &recog_data.operand[0];
    rtx x2, x3, x4, x5, x6, x7;
    int res ATTRIBUTE_UNUSED;
    x2 = XEXP(x1, 1);
    /* NOTE: x2 为 (reg/f:DI 8 s0) */
    switch (GET_CODE(x2)) {
        case REG:
        case SUBREG:
        case MEM:
        case LABEL_REF:
        case SYMBOL_REF:
        case HIGH:
            return recog_7(x1, insn, pnum_clobbers);

    }


static int recog_7 (rtx x1 ATTRIBUTE_UNUSED, rtx_insn *insn ATTRIBUTE_UNUSED, int *pnum_clobbers ATTRIBUTE_UNUSED):
    /* NOTE: x2 为
     *  (mem/c:DI (plus:DI (reg/f:DI 2 sp)
     *   (const_int 24 [0x18])) [2  S8 A64])
     * */
    x2 = XEXP (x1, 0);
    switch (GET_CODE (x2)) {
        case MEM:
            res = recog_2 (x1, insn, pnum_clobbers);
    }

static int recog_2 (rtx x1 ATTRIBUTE_UNUSED, rtx_insn *insn ATTRIBUTE_UNUSED, int *pnum_clobbers ATTRIBUTE_UNUSED):
    x2 = XEXP (x1, 0);
    operands[0] = x2;
    x3 = XEXP (x1, 1);
    operands[1] = x3;
    switch (GET_MODE (operands[0])):
        case E_DImode:
            if ((TARGET_64BIT
                   && (register_operand (operands[0], DImode)
                      || reg_or_0_operand (operands[1], DImode))))
                return 135; /* *movdi_64bit */
            break;

最终匹配的 insn_code 为 135, 对应 riscv.md 中的:

(define_insn "*movdi_64bit"
  [(set (match_operand:DI 0 "nonimmediate_operand" "=r,r,r, m,  *f,*f,*r,*f,*m")
    (match_operand:DI 1 "move_operand"         " r,T,m,rJ,*r*J,*m,*f,*f,*f"))]
  "TARGET_64BIT
   && (register_operand (operands[0], DImode)
       || reg_or_0_operand (operands[1], DImode))"
  { return riscv_output_move (operands[0], operands[1]); }
  [(set_attr "move_type" "move,const,load,store,mtc,fpload,mfc,fmove,fpstore")
   (set_attr "mode" "DI")])
1.5.3.2. addsf3 vs. adddi3

recog 可以看做是一棵决策树, 以 addsf3 和addi3 为例:

(define_insn "addsf3"
  [(set (match_operand:SF            0 "register_operand" "=f")
    (plus:SF (match_operand:SF 1 "register_operand" " f")
           (match_operand:SF 2 "register_operand" " f")))]
  "TARGET_HARD_FLOAT"
  "fadd.s\t%0,%1,%2"
)

(define_insn "addsi3"
  [(set (match_operand:SI          0 "register_operand" "=r,r")
    (plus:SI (match_operand:SI 1 "register_operand" " r,r")
         (match_operand:SI 2 "arith_operand"    " r,I")))]
  ""
  { return TARGET_64BIT ? "add%i2w\t%0,%1,%2" : "add%i2\t%0,%1,%2"; }
)

recog 针对 addsf3 的决策树是: set->plus->GET_MODE(operand[0])==SF recog 针对 adddi3 的决策树是: set->plus->GET_MODE(operand[0])==DI

int recog(
    rtx x1 ATTRIBUTE_UNUSED, rtx_insn *insn ATTRIBUTE_UNUSED,
    int *pnum_clobbers ATTRIBUTE_UNUSED) {
    switch (GET_CODE(x1)):
        case SET:
            /* NOTE: set */
            return recog_19(x1, insn, pnum_clobbers);

static int recog_19(
    rtx x1 ATTRIBUTE_UNUSED, rtx_insn *insn ATTRIBUTE_UNUSED,
    int *pnum_clobbers ATTRIBUTE_UNUSED) {
    x2 = XEXP(x1, 1);
    switch (GET_CODE(x2)) {
        /* NOTE: plus */
        case PLUS:
            x3 = XEXP(x1, 0);
            operands[0] = x3;

            /* 虽然 addsf3 通过 match_oprand 给 plus 的 src 和 target 都指定了
             * mode, 但这里只使用了 target 的 mode, 后续会通过
             * pattern0/pattern10 等检查所有的 mode 都匹配, 这里的 switch mode 相当于一个 */
            switch (GET_MODE(operands[0])) {
                case E_SFmode:
                    if (pattern0(x2, E_SFmode) != 0 || !(TARGET_HARD_FLOAT))
                        return -1;
                    return 1; /* addsf3 */

                case E_DImode:
                    if (pattern10(x2, E_DImode) != 0 || !(TARGET_64BIT))
                        return -1;
                    return 4; /* adddi3 */

static int pattern0 (rtx x1, machine_mode i1) {
  rtx * const operands ATTRIBUTE_UNUSED = &recog_data.operand[0];
  int res ATTRIBUTE_UNUSED;
  if (!register_operand (operands[0], i1)
      || GET_MODE (x1) != i1
      || !register_operand (operands[1], i1)
      || !register_operand (operands[2], i1))
    return -1;
  return 0;
}

1.5.4. get_insn_template

const char *get_insn_template(int code, rtx_insn *insn) {
    const char *ret;
    switch (insn_data[code].output_format) {
        case INSN_OUTPUT_FORMAT_SINGLE:
            ret = insn_data[code].output.single;
            break;
        case INSN_OUTPUT_FORMAT_MULTI:
            ret = insn_data[code].output.multi[which_alternative];
            break;
        /* NOTE: 135 对应的 insn_data 的 output_format 为 INSN_OUTPUT_FORMAT_FUNCTION, 因为 md 文件中指定了
         *   { return riscv_output_move (operands[0], operands[1]); }
         * */
        case INSN_OUTPUT_FORMAT_FUNCTION:
            gcc_assert(insn);
            /* NOTE: 这里的 output.function 就是  riscv_output_move */
            ret = (*insn_data[code].output.function)(recog_data.operand, insn);
            break;
    }
    return ret;
}

riscv_output_move:
    /* dest_code 为 MEM */
    dest_code = GET_CODE (dest);
    /* src_code 为 REG */
    src_code = GET_CODE (src);
    if ((src_code == REG && GP_REG_P (REGNO (src))) || (src == CONST0_RTX (mode))):
        if (dest_code == MEM):
            switch (GET_MODE_SIZE (mode))
            {
                case 1: return "sb\t%z1,%0";
                case 2: return "sh\t%z1,%0";
                case 4: return "sw\t%z1,%0";
                /* NOTE: 最后 match 的是这一句: */
                case 8: return "sd\t%z1,%0";
            }

1.5.5. output_asm_insn

void output_asm_insn (const char *templ, rtx *operands):
    /* NOTE: templ 为 sd\t%z1,%0 */
    /* output_asm_insn 会遍历并打印出 templ, 碰到 % 会特殊处理,
     * % 后面的特殊字符例如 z 决定了 operand 例如 1 如何处理
     * gcc 本身支持的特殊字符包括:
     * a : address
     * c : const address
     * l : label
     * n : int
     * 除此以外, target 可以实现自己的 TARGET_PRINT_OPERAND 来定义
     * 自己的特殊字符, 例如这里的 c 就是 riscv 自己定义的字符
     * %0 这种没有指定特殊字符的 operand 会使用默认的方式处理
     * */

     /* %z1 通过 riscv_print_operand 来处理, 返回 s0 */
     /* %0 通过 output_operand 处理*/
     output_operand (operands[opnum], letter);
         /* x 是 (mem (plus ...)) 这个 rtx, code 为 0, 因为没有 format letter*/
         targetm.asm_out.print_operand (asm_out_file, x, code);

static void riscv_print_operand (FILE *file, rtx op, int letter):
    machine_mode mode = GET_MODE (op);
    enum rtx_code code = GET_CODE (op);

    switch (letter):
        /*
         * ...
         */
        default:

        switch (code):
          case MEM:
            /* XEXP(op,0) 为
             * (plus:DI (reg/f:DI 2 sp)
             *   (const_int 24 [0x18]))
             * */
            output_address (mode, XEXP (op, 0));
          break;

void output_address (machine_mode mode, rtx x) {
  bool changed = false;
  walk_alter_subreg (&x, &changed);
  /* TARGET_PRINT_OPERAND_ADDRESS */
  targetm.asm_out.print_operand_address (asm_out_file, mode, x);
}

static void riscv_print_operand_address (FILE *file, machine_mode mode, rtx x):
    struct riscv_address_info addr;
    if (riscv_classify_address (&addr, x, word_mode, true))
        switch (addr.type) {
            case ADDRESS_REG:
                /* NOTE: 这里会输出 24 */
                riscv_print_operand (file, addr.offset, 0);
                /* NOTE: 这里会接着输出 (sp), 所以 (mem ...) 最终打印出 `24(sp)` */
                fprintf (file, "(%s)", reg_names[REGNO (addr.reg)]);
                return;


所以下面的 rtl:

(insn/f 40 39 41 (set (mem/c:DI (plus:DI (reg/f:DI 2 sp)
                (const_int 24 [0x18])) [2  S8 A64])
        (reg/f:DI 8 s0)) "test.c":1:21)

对应的汇编为:

sd  s0,24(sp)

1.6. misc

1.6.1. target hook

理想情况下 backend 相关代码会依赖 md 文件获得需要的信息, 但实际情况是有些信息无法放在 md 中, 这些信息会放在 target hook 和 target macro 中.

另外, 虽然理论上编译器的前中后端应该是独立的, 但实际上有时前中端也需要 target hook 和 target macro 提供的信息才能工作, 例如:

  • 后端是否支持某种指令 (例如 pass_loop_prefetch 优化依赖 TARGET_HAVE_PREFETCH 这个 target macro 决定是否进行 prefetch)
  • 后端是否支持某个 optab (例如 pass_cse_sincos 优化会依赖 TARGET_OPTAB_SUPPORTED_P 把 pow(x,0.5) 优化成 sqrt(x), 或者 pass_lower_vector 需要根据后端是否支持向量指令决定是否在 gimple 优化阶段把向量操作转换为标量操作)

1.6.2. intrinsic

通过 intrinsic 可以扩展 gcc 以支持 backend 自定义指令. 例如, 假设 backend 包含许多 dsp 指令 (例如 RISC-V P Extension), 那么支持它有两种方式:

  1. 使用 pass_combine 和 pass_peephole2 等让 gcc 自动生成 dsp 指令
  2. 定义 intrinsic 让用户直接调用这些指令

参考 P Extension 的提交:

https://github.com/riscv-collab/riscv-gcc/pull/258

1.6.3. mcpu/mtune/march/mabi

1.6.3.1. mcpu

`-mcpu` 参数是 mtune 和 march 的结合体, 例如

RISCV_CORE("sifive-e31",      "rv32imac",   "sifive-3-series")

表示 `-mcpu=sifive-e31` 等价于 `-march=rv32imac -mtune=sifive-3-series`

1.6.3.2. march

解析 march 的函数主要是 riscv_parse_arch_string, 通过一些宏可以访问解析的结果:

static const riscv_ext_flag_table_t riscv_ext_flag_table[] = {
    {"e", &gcc_options::x_target_flags, MASK_RVE},
    {"m", &gcc_options::x_target_flags, MASK_MUL},
    {"a", &gcc_options::x_target_flags, MASK_ATOMIC},
    {"f", &gcc_options::x_target_flags, MASK_HARD_FLOAT},
    {"d", &gcc_options::x_target_flags, MASK_DOUBLE_FLOAT},
    {"c", &gcc_options::x_target_flags, MASK_RVC},

    {"zicsr", &gcc_options::x_riscv_zi_subext, MASK_ZICSR},
    {"zifencei", &gcc_options::x_riscv_zi_subext, MASK_ZIFENCEI},

    {NULL, NULL, 0}};

/*
 * target_flags 为 global_options.x_target_flags
 */
#define TARGET_64BIT ((target_flags & MASK_64BIT) != 0)
#define TARGET_ATOMIC ((target_flags & MASK_ATOMIC) != 0)
#define TARGET_DOUBLE_FLOAT ((target_flags & MASK_DOUBLE_FLOAT) != 0)
#define TARGET_HARD_FLOAT ((target_flags & MASK_HARD_FLOAT) != 0)
#define TARGET_MUL ((target_flags & MASK_MUL) != 0)
#define TARGET_RVC ((target_flags & MASK_RVC) != 0)
#define TARGET_RVE ((target_flags & MASK_RVE) != 0)

/* 应用 march 设置的主要途径是 md 中会大量使用这些宏做为 match 的 predicate */

(define_insn "mul<mode>3"
  [(set (match_operand:ANYF               0 "register_operand" "=f")
    (mult:ANYF (match_operand:ANYF    1 "register_operand" " f")
              (match_operand:ANYF 2 "register_operand" " f")))]
  "TARGET_HARD_FLOAT"
  "fmul.<fmt>\t%0,%1,%2"
 )

(define_insn "mulsi3"
  [(set (match_operand:SI          0 "register_operand" "=r")
    (mult:SI (match_operand:SI 1 "register_operand" " r")
         (match_operand:SI 2 "register_operand" " r")))]
  "TARGET_MUL"
  { return TARGET_64BIT ? "mulw\t%0,%1,%2" : "mul\t%0,%1,%2"; }
 )

/* 其中在代码中也会有一些针对 TARGET_XXX 的设置, 例如 soft-float 时无法使用 fpr */
static void riscv_conditional_register_usage (void) {
    /* ... */
    if (!TARGET_HARD_FLOAT) {
        for (int regno = FP_REG_FIRST; regno <= FP_REG_LAST; regno++)
            fixed_regs[regno] = call_used_regs[regno] = 1;
    }
    /* ... */
}
Backlinks

TARGET_CPU_CPP_BUILTINS (GCC Target Hook > runtime target 相关 > TARGET_CPU_CPP_BUILTINS): gcc 通过 march 定义了 TARGET_MUL 等 macro, 但它们是给 gcc 使用的. 应用代码如果要 得到 TARGET_MUL 类似的功能, 需要使用 TARGET_CPU_CPP_BUILTINS 通过 builtin_define 定义的 __riscv_mul

1.6.3.3. mtune
1.6.3.4. mabi
static const struct cl_enum_arg cl_enum_abi_type_data[] = {
    {"ilp32", ABI_ILP32, 0},   {"ilp32d", ABI_ILP32D, 0},
    {"ilp32e", ABI_ILP32E, 0}, {"ilp32f", ABI_ILP32F, 0},
    {"lp64", ABI_LP64, 0},     {"lp64d", ABI_LP64D, 0},
    {"lp64f", ABI_LP64F, 0},   {NULL, 0, 0}};

/* 通过这个 define 限制 ILP32/LP64 无法使用 fpr 传递参数 */
#define UNITS_PER_FP_ARG                                                \
  ((riscv_abi == ABI_ILP32 || riscv_abi == ABI_ILP32E                   \
    || riscv_abi == ABI_LP64)                                           \
   ? 0                                                                  \
   : ((riscv_abi == ABI_ILP32F || riscv_abi == ABI_LP64F) ? 4 : 8))

1.6.3.4.1. example
$> cat test.c

extern float hello(float x);

float foo(float x) {
    return x + hello(x);
}

$> /opt/riscv/bin/riscv64-unknown-linux-gnu-gcc test.c -c -O0 -march=rv64i -mabi=lp64 -S -o-

foo:
        addi    sp,sp,-32
        sd      ra,24(sp)
        sd      s0,16(sp)
        addi    s0,sp,32
        sw      a0,-20(s0)
        lw      a0,-20(s0)
        # hello 的参数和返回值通过 gpr (a0)
        call    hello
        mv      a5,a0
        lw      a1,-20(s0)
        mv      a0,a5
        # 使用软浮点
        call    __addsf3
        mv      a5,a0
        mv      a0,a5
        ld      ra,24(sp)
        ld      s0,16(sp)
        addi    sp,sp,32
        jr      ra

$> /opt/riscv/bin/riscv64-unknown-linux-gnu-gcc test.c -c -O0 -march=rv64if -mabi=lp64 -S -o-

foo:
        addi    sp,sp,-32
        sd      ra,24(sp)
        sd      s0,16(sp)
        addi    s0,sp,32
        sw      a0,-20(s0)
        lw      a0,-20(s0)
        # hello 的参数还是用的 gpr
        call    hello
        mv      a4,a0
        lw      a5,-20(s0)
        # 使用 hard float
        fadd.s  a5,a4,a5
        fmv.w.x fa5,a5
        fmv.x.w a0,fa5
        ld      ra,24(sp)
        ld      s0,16(sp)
        addi    sp,sp,32
        jr      ra

$> /opt/riscv/bin/riscv64-unknown-linux-gnu-gcc test.c -c -O0 -march=rv64if -mabi=lp64f -S -o-

foo:
        addi    sp,sp,-32
        sd      ra,24(sp)
        sd      s0,16(sp)
        addi    s0,sp,32
        fsw     fa0,-20(s0)
        flw     fa0,-20(s0)
        # heloo 的参数和返回值使用 fpr (fa0)
        call    hello
        fmv.x.w a4,fa0
        lw      a5,-20(s0)
        # 使用 hard float
        fadd.s  a5,a4,a5
        fmv.w.x fa5,a5
        fmv.s   fa0,fa5
        ld      ra,24(sp)
        ld      s0,16(sp)
        addi    sp,sp,32
        jr      ra

$> /opt/riscv/bin/riscv64-unknown-linux-gnu-gcc test.c -c -O0 -march=rv64i -mabi=lp64f -S -o-
cc1: error: requested ABI requires '-march' to subsume the 'F' extension

$> /opt/riscv/bin/riscv64-unknown-linux-gnu-gcc test.c -c -O0 -march=rv32i -mabi=lp64 -S -o-
cc1: error: ABI requires '-march=rv64'
1.6.3.5. march vs. mabi

https://www.sifive.com/blog/all-aboard-part-1-compiler-args

  • riscv 的 march 可以指定 rv{32,64}{i,f,d,m,a,g,v,c}, 用来指定产生什么样的硬件指令, 例如 `gcc -march=rv32ifd`
  • riscv 的 mabi 是指在与 march 不冲突的基础上, 函数调用时使用什么样的指令/寄存器. riscv mabi 包括:
    1. ilp32{"",f,d}

      ilp32 指 int, long, pointer 是 32 bit, f 指支持 hard single float, d 指支持 hard double float

    2. lp64{"",f,d}

Backlinks

GCC (GCC > Backend): Backend

GCC Target Hook (GCC Target Hook): GCC Backend 的 example 里看到的

Retargeting GCC To RISC-V (Retargeting GCC To RISC-V > gcc > gcc backend): gcc backend

Author: [email protected]
Date: 2022-03-24 Thu 12:17
Last updated: 2024-09-01 Sun 10:04

知识共享许可协议