vimer linux kernel 爱好者

纯手工打造rv编译器

2022-07-21

这个教程是PLCT推出的,强烈推荐。在这篇文章中,我们就简单记录下这个学习过程。

资料

https://gist.github.com/sunshaoce/90216c19591581f8997df0109b65c154 这个是RISC-V环境的构建脚本,需要先下载有相应的代码仓库。

riscv elfapi url: https://github.com/riscv-non-isa/riscv-elf-psabi-doc

Docker 环境也有了:

大家好,RVCC 开发环境的 Docker 镜像已经配置好,欢迎大家使用。详情可以参考:https://github.com/ksco/rvcc-env-docker。

提供的是国内阿里云的 registry,下载飞快!

[完结] 循序渐进,学习开发一个RISC-V上的操作系统 - 汪辰 - 2021春

https://www.bilibili.com/video/BV1Q5411w7z5

可以看看前面一部分的公共知识课,包含了RISC-V的基础指令集的详细的介绍和一些程序执行堆栈的知识

01

01视频地址 首先我是在rv硬件上执行这个程序的,所以命令稍微不同,但是效果是一样的。我只是没用交叉编译器嘛。

#cat tmp.s
        .global main # 声明一个main的段,程序的入口
main:                # 段开始的标志 
        li a0, 42    # a0寄存器就是 返回值
        ret
# compiler cmd:
gcc -s tmp.s
 ./a.out
vimer@unmatched:~/build/rvcc$ echo $?
42

接下来,我们再进一步,使用一个 .c 文件:

vimer@unmatched:~/build/rvcc/02$ cat main.c
#include <stdio.h>
#include <stdlib.h>

int main (int argc, char ** argv){
        if (argc != 2){
                // argc is right
                fprintf(stderr, "%s: invalid number of argcs\n", argv[0]);
                return 1;
        }

        printf("        .global main\n");

        printf("main:\n");
        printf("        li a0, %d\n", atoi(argv[1]));
        printf("        ret\n");
}

然后Makefile:

vimer@unmatched:~/build/rvcc/02$ cat Makefile
# C编译器参数:使用C11标准,生成debug信息,禁止将未初始化的全局变量放入到common段
CFLAGS=-std=c11 -g -fno-common
# 指定C编译器,来构建项目
CC=clang

# rvcc标签,表示如何构建最终的二进制文件,依赖于main.o文件
rvcc: main.o
# 将多个*.o文件编译为rvcc
        $(CC) -o rvcc $(CFLAGS) main.o

# 测试标签,运行测试脚本
test: rvcc
        ./test.sh

# 清理标签,清理所有非源代码文件
clean:
        rm -f rvcc *.o *.s tmp* a.out

# 伪目标,没有实际的依赖文件
.PHONY: test clean

这里还有一个test.sh:

#!/bin/bash

# 声明一个函数
assert() {
  # 程序运行的 期待值 为参数1
  expected="$1"
  # 输入值 为参数2
  input="$2"

  # 运行程序,传入期待值,将生成结果写入tmp.s汇编文件。
  # 如果运行不成功,则会执行exit退出。成功时会短路exit操作
  ./rvcc "$input" > tmp.s || exit
  # 编译rvcc产生的汇编文件
  clang -o tmp tmp.s
  # $RISCV/bin/riscv64-unknown-linux-gnu-gcc -static -o tmp tmp.s

  # 运行生成出来目标文件
  ./tmp
  # $RISCV/bin/qemu-riscv64 -L $RISCV/sysroot ./tmp
  # $RISCV/bin/spike --isa=rv64gc $RISCV/riscv64-unknown-linux-gnu/bin/pk ./tmp

  # 获取程序返回值,存入 实际值
  actual="$?"

  # 判断实际值,是否为预期值
  if [ "$actual" = "$expected" ]; then
    echo "$input => $actual"
  else
    echo "$input => $expected expected, but got $actual"
    exit 1
  fi
}

# assert 期待值 输入值
# [1] 返回指定数值
assert 0 0
assert 42 42

# 如果运行正常未提前退出,程序将显示OK
echo OK

执行 make test

vimer@unmatched:~/build/rvcc/02$ make test
clang -std=c11 -g -fno-common   -c -o main.o main.c
clang -o rvcc -std=c11 -g -fno-common main.o
./test.sh
0 => 0
42 => 42
OK

其实,程序的注释已经非常清晰明了,除了一些特性,基本的流程我们总结一下。

首先,项目的主程序是main.c,也就是我们通过c程序把目前的汇编代码重定向输出到汇编文件,这个过程转换完成之后, 再利用一个简易的脚本把汇编文件编译成可执行文件,基本上模拟了一个编译器的整体流程。

02 支持+ -运算符

徒手写一个RISC-V编译器 - 第002课

https://www.bilibili.com/video/BV1KS4y1E79u


下一篇 gcc-12 ftbfs

Comments

Content