从一行c代码到0101(LLVM,RISCV)

本文希望力求精简的解释一行c代码是如何变成一段二进制编码,又是如何进入cpu被执行的。

0x00 示例代码

int div(int a, int b, int c) {
    int sum = a + b;
    return sum / c;
}

0x01 词法语法分析,翻译为LLVM-IR

LLVM IR是一门低级语言,语法类似于汇编。任何高级编程语言(如C++)都可以用LLVM IR表示。

define dso_local signext i32 @div(i32 noundef signext %a, i32 noundef signext %b, i32 noundef signext %c) #0 {
entry:
  %a.addr = alloca i32, align 4
  %b.addr = alloca i32, align 4
  %c.addr = alloca i32, align 4
  %sum = alloca i32, align 4
  store i32 %a, ptr %a.addr, align 4
  store i32 %b, ptr %b.addr, align 4
  store i32 %c, ptr %c.addr, align 4
  %0 = load i32, ptr %a.addr, align 4
  %1 = load i32, ptr %b.addr, align 4
  %add = add nsw i32 %0, %1
  store i32 %add, ptr %sum, align 4
  %2 = load i32, ptr %sum, align 4
  %3 = load i32, ptr %c.addr, align 4
  %div = sdiv i32 %2, %3
  ret i32 %div
}

0x02 转为DAG

命令:llc -view-dag-combine1-dags  -march=riscv64 -o instr.s instr.ll,需要自己编译llc为debug版本

此时的DAG有部分是平台无关的代码

0x03 DAG legalization

DAG中平台无关的代码转换为平台相关的代码

比如将上图中的DIV转换为DIVW。

0x04 选择汇编指令

通过DAG中的DIVW节点,映射到指令DIVW。

0x05 指定调度重排,优化执行速度

st i32 %a, i16* %b,  i16 5
st %b, i32* %c, i16 0
%d = ld i32* %c

在riscv cpu上,第3行指令用到了寄存器%c中的值,而第2行使用了寄存器%c,所以要在第2行之后等一个时钟周期,这是cpu的规定。

对指令进行重新排列,交换第1行和第2行,就可以利用等待的时钟周期执行st i32 %a, i16*%b, i16 5这条指令。

0x06 分配寄存器

在此之前LLVM一直使用无限多的寄存器,这样方便代码优化。现在要把无限多的寄存器映射为指定平台的有限多的寄存器,具体过程参考常见的寄存器着色算法。

0x07 输出汇编指令

编译完成,输出汇编指令或者二进制码。

0000000000000002 <div>:
       2: 01 11        	addi	sp, sp, -32
       4: 06 ec        	sd	ra, 24(sp)
       6: 22 e8        	sd	s0, 16(sp)
       8: 00 10        	addi	s0, sp, 32
       a: 23 26 a4 fe  	sw	a0, -20(s0)
       e: 23 24 b4 fe  	sw	a1, -24(s0)
      12: 23 22 c4 fe  	sw	a2, -28(s0)
      16: 03 25 c4 fe  	lw	a0, -20(s0)
      1a: 83 25 84 fe  	lw	a1, -24(s0)
      1e: 2d 9d        	addw	a0, a0, a1
      20: 23 20 a4 fe  	sw	a0, -32(s0)
      24: 03 25 04 fe  	lw	a0, -32(s0)
      28: 83 25 44 fe  	lw	a1, -28(s0)
      2c: 3b 45 b5 02  	divw	a0, a0, a1
      30: e2 60        	ld	ra, 24(sp)
      32: 42 64        	ld	s0, 16(sp)
      34: 05 61        	addi	sp, sp, 32
      36: 82 80        	ret

0x08 CPU执行指令的五大步骤

2c: 3b 45 b5 02  	divw	a0, a0, a1

这条指令的二进制为(参考riscv指令集手册):

0000001        01011                01010                001                 01010                  0111011

funct7            寄存器a1           寄存器a0           funct3              寄存器a0             opcode divw

cpu执行指令的5大步骤为:取指,译码,执行,暂存,写回

  • 取指

cpu拿到3b45b502这个32位指令

  • 译码

查看opcode,发现是divw指令,为执行器准备好寄存器a0和a1

  • 执行

执行器用a0除以a1

  • 存取

如果指令需要读取或者写入数据,那么就访问cpu缓存或者内存。divw指令不需要这一步。

  • 写回

把除法结果写入a0

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值