Retargeting GCC To RISC-V

Table of Contents

1. Retargeting GCC To RISC-V

1.1. Overview

retargeting 是指让 gcc 在代码生成时支持一种新的后端, 具体的改动涉及到:

  1. gcc, 主要的工作是涉及到 gcc 的 rtl 部分, 包括如何分配寄存器和生成平台相关的汇编
  2. binutils, 主要包括 objdump, as 等, 主要工作是如何把平台相关的汇编翻译成平台相关的机器指令
  3. libc, libc 中并非全都是 c 代码, 有少量涉及到 syscall 等的代码是使用平台相关的汇编写的

1.2. 测试环境

git clone https://github.com/sunwayforever/riscv-gnu-toolchain.git -b initial

configure & make 后的目录结构为:

# riscv 针对 binutils 添加的文件
binutils
# 编译时使用的 build 目录,
# 在 build-binutils-newlib 单独 make install 可以单独编译 binutils
build-binutils-newlib
# 在 build-gcc-newlib 单独 make install 可以单独编译 gcc
build-gcc-newlib
# riscv 针对 gcc 添加的文件
gcc
linux-headers
# riscv 针对 newlib 添加的文件
newlib
# riscv 除了添加文件以外需要的 patch, 主要是修改 makefile 和配置文件
# 以编译新加入的文件
patches
scripts
# make 时会先下载 gcc-4.9.1 以及 binutils, newlib 的相应版本到 src
# 并解压为 src/original-{gcc, binutils, newlib}, 然后
# 把 original-binutils 和 ../binutils 复制到 src/binutils
# 把 original-gcc, original-newlib 和 ../gcc, ../newlib 复制到 src/newlib-gcc
# 最后 apply patches 中的 patch, 所以最终编译时源码都在 src/{newlib-gcc, binutils} 中
src

1.3. binutils

1.4. newlib/glibc

1.4.1. syscall

riscv 通过 scall (最新的规范改成了 ecall) 来发起 syscall, 对应其它平台的 int, svc, syscall, sysenter, … 等

static inline long
__internal_syscall(long n, long _a0, long _a1, long _a2, long _a3)
{
  /* 最新的 riscv abi 里, syscall number 使用 a7, 而不是 v0, 因为 vx 这个名字被
   * 分配给 rv32v 使用 */
  register long v0 asm("v0") = n;
  register long a0 asm("a0") = _a0;
  register long a1 asm("a1") = _a1;
  register long a2 asm("a2") = _a2;
  register long a3 asm("a3") = _a3;

  asm volatile ("scall\n"
        "bltz v0, __syscall_error"
        : "+r"(v0) : "r"(a0), "r"(a1), "r"(a2), "r"(a3));

  /* 最新的 riscv abi 里, function retval 用 a0/a1, 而不是 v0/v1 */
  return v0;
}

1.4.2. setjmp/longjmp

# define REG_S    sw
# define REG_L     lw
# define SZREG 4

/* int setjmp (jmp_buf);  */
  .globl  setjmp
setjmp:
    REG_S ra,  0*SZREG(a0)
    REG_S s0,  1*SZREG(a0)
    REG_S s1,  2*SZREG(a0)
    REG_S s2,  3*SZREG(a0)
    REG_S s3,  4*SZREG(a0)
    REG_S s4,  5*SZREG(a0)
    REG_S s5,  6*SZREG(a0)
    REG_S s6,  7*SZREG(a0)
    REG_S s7,  8*SZREG(a0)
    REG_S s8,  9*SZREG(a0)
    REG_S s9, 10*SZREG(a0)
    REG_S s10,11*SZREG(a0)
    REG_S s11,12*SZREG(a0)
    REG_S sp, 13*SZREG(a0)
    REG_S tp, 14*SZREG(a0)

    li v0, 0
    ret

  .globl  longjmp
longjmp:
    REG_L ra,  0*SZREG(a0)
    REG_L s0,  1*SZREG(a0)
    REG_L s1,  2*SZREG(a0)
    REG_L s2,  3*SZREG(a0)
    REG_L s3,  4*SZREG(a0)
    REG_L s4,  5*SZREG(a0)
    REG_L s5,  6*SZREG(a0)
    REG_L s6,  7*SZREG(a0)
    REG_L s7,  8*SZREG(a0)
    REG_L s8,  9*SZREG(a0)
    REG_L s9, 10*SZREG(a0)
    REG_L s10,11*SZREG(a0)
    REG_L s11,12*SZREG(a0)
    REG_L sp, 13*SZREG(a0)
    REG_L tp, 14*SZREG(a0)

    sltiu v0, a1, 1    # v0 = (a1 == 0)
    add   v0, v0, a1   # v0 = (a1 == 0) ? 1 : a1
    ret

1.4.3. 某些可以优化的函数

  • memcpy
  • memset
  • strcpy
  • strlen
  • strcmp

1.4.4. 直接操作 fcsr 的函数

fcsr 是 floating control and status register

  • fp{set,get}sticky
  • fp{set,get}round
  • fp{get,get}mask

1.4.5. tls 相关

riscv 使用 tp (x4) 做 thread pointer, libc 中和 tls (Thread Local Storage) 相关的代码需要考虑

1.4.6. atomic 相关

例如 riscv 可以用 LR/SC 实现 libc 要求的 atomic_compare_and_exchange_bool_acq 做 CAS.

另外, libc 会使用 CAS 实现 atomicmax, min, …, 但 riscv 可以提供自己的基于 AMO 的实现

1.5. gcc

1.5.1. gcc frontend

1.5.1.1. AST

frontend 的结果是 AST, 它是和语言相关的抽象语法树, 使用 tree 来描述, 其中的节点 以 C 为例, 定义在 c-common.def 中

1.5.1.2. GENERIC

上层 AST 通过 generic 函数转换成通常的 GENERIC, 同样是用 tree 来描述, 其中的节点参数 tree.def

1.5.1.3. gimple

通过 gimplify 函数把 GENERIC 转换成 gimple, 还是用 tree 描述, 其中节点参考 gimple.def.

gimple 相当于 gcc HIR, 主要涉及到 basic block, SSA 等的构造和上层的优化

void foo () {
    float x = 0.0;
    float y = x + x;
    if (y > 0.0) {
        y = 1.0 + y + y;
    } else {
        y = 1.0;
    }
}
$> riscv64-linux-gnu-gcc test.c -O0 -c -fdump-tree-all
$> tree
.
├── test.c
├── test.c.004t.original
├── test.c.005t.gimple
├── test.c.007t.omplower
├── test.c.008t.lower
├── test.c.011t.eh
├── test.c.012t.cfg
├── test.c.013t.ompexp
├── test.c.014t.printf-return-value1
├── test.c.018t.fixup_cfg1
├── test.c.019t.ssa
├── test.c.021t.nothrow
├── test.c.023t.fixup_cfg2
├── test.c.024t.local-fnsummary1
├── test.c.025t.einline
├── test.c.026t.early_optimizations
├── test.c.027t.objsz1
├── test.c.028t.ccp1
├── test.c.029t.forwprop1
....
$> cat test.c.019t.ssa
;; Function foo (foo, funcdef_no=0, decl_uid=1498, cgraph_uid=1, symbol_order=0)

foo ()
{
  float y;
  float x;
  double _1;
  double _2;
  double _3;
  double _4;

  <bb 2> :
  x_5 = 0.0;
  y_6 = x_5 * 2.0e+0;
  if (y_6 > 0.0)
    goto <bb 3>; [INV]
  else
    goto <bb 4>; [INV]

  <bb 3> :
  _1 = (double) y_6;
  _2 = _1 + 1.0e+0;
  _3 = (double) y_6;
  _4 = _2 + _3;
  y_8 = (float) _4;
  goto <bb 5>; [INV]

  <bb 4> :
  y_7 = 1.0e+0;

  <bb 5> :
  return;

}

通过 `-fdump-tree-all` 可以 dump 出某些涉及到 gimple 的中间结果, 以 test.c.019t.ssa 为例:

  • 019 表示 pass 的编号
  • t 表示 tree, 即 gimple 相关
  • ssa 表示 pass 的名字, 例如 .cfg 表示生成 cfg (control flow graph), .ssa 表示转换为 ssa 格式
  • gimple 把 branch 变成了 goto
  • gimple 是三地址形式 (TAC), 所以 y=1.0+y+y 会被拆成两个表达式
Backlinks

GCC Backend (GCC Backend > insn selection > gimple tree code): gimple 定义的 tree code 在 `tree.def` 中, 例如:

1.5.2. gcc backend

Backlinks

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

Author: [email protected]
Date: 2022-02-24 Thu 11:58
Last updated: 2023-09-06 Wed 12:58

知识共享许可协议