组译器与连结器 (上)

本文目标

  • 理解 RISC-V 基础指令集
  • 假指令 (pseudo instruction)、扩展指令集
  • 组译器与组合语言

组译器

组译器 (Assembler) 能够将组合语言转换为目标代码(机器语言或接近於机器语言的程序码)。
在 Unix 系统中,组译器的输入为 .s 後缀的档案,像是: foo.s
此外,组译器还有一项非常重要的功能: 它可以将扩展指令转为基础指令。

基础指令集

在谈扩展指令集之前,我们先来了解一下指令集的定义。
指令集顾名思义代表着指令的集合, RISC-V 有多个基本指令集:

  • RV32I
  • RV32E
  • RV64I
  • RV128I

以 RV32I 指令集来看,该指令集一共有 47 个 32 位地址的指令集并且支持 32 个通用整数寄存器。

RV32I

从上图可以了解不同 Type 的指令如何利用 32 位元的空间,以 I-Type 来看:

  • Bit 20 - Bit 31 用来表示立即数
  • Bit 15 - Bit 19 表示 rs1 (Input 暂存器)
  • Bit 12 - Bit 14 表示 funct (在确定指令属於哪一个 Type 後,观察 Funct 来判断该指令是哪一个操作)
  • Bit 7 - Bit 11 表示 rd (目标暂存器)
  • Bit 0 - Bit 6 为 opcode (用於辨别 Type)

addi 指令为例,该指令会将 rs1 存放的数值加上立即数後将结果存放到 rd 中。I-Type 指令的 opcode 统一为 0010011,而 addi 的 funct3 为 000
再以 xori 为例,该指令会将 rs1 存放的数值与立即数进行按位元的 xor 操作,完成後再将结果存放到 rd 中。而 xori 的 funct3 会是 100

扩展指令集

谈完基础指令集後,再回到扩展指令集上面。
扩展指令集的诞生与人类赋予计算机的任务逐渐加重有很大的关系,像是在现代应用中,我们利用电脑处理音频、影像的处理。纵使现代处理器具有极强的运算能力,但基础指令一次也仅能处理一个数据,在处理 RGB 或是座标问题时就需要拆成多项指令才能完成任务。因此,势必需要增加特殊指令处理这些问题,这些新增的指令便成 了扩展指令集。
由於 RISC-V 是属於精减指令架构 (RISC) ,所以就连常见的乘除法操作也会透过扩展指令实作。

好奇计算机怎麽做加减乘除吗?
可以参考先前的透过数位逻辑电路学习 Bitwise 操作一文唷!

在 RISC-V 中,有以下常见的指令集:

扩展指令集 指令数 描述
M 8 整数乘法与除法指令
A 11 储存器原子操作指令以及 Load-Reserved/Store-Conditional 指令
F 26 单精度(32 bits)浮点数指令
D 26 双精度(64 bits)浮点数指令
C 46 压缩指令,指令长度为 16 bits

以上 IMAFD 指令集组合又被称为通用组合,在英文中以 G (General) 表示,所以 RV32G 等於 RV32IMAFD

如何解析扩展指令

要解析扩展指令有两种直观的做法:

  • 设计相关硬体使 CPU 能够对指令进行硬处理
  • 利用多个指令构成新的指令 (假指令)

在 RISC 架构的处理器中,多数应属於後者,这个结果也呼应到本文一开始所提到组译器主要的功能。

组合语言

在 RISC-V 中,我们编写组合语言时会在开头使用组译指示符 (assemble directives):

  • .text
  • .align
  • .globl
  • .section
  • ...

在实际档案中会长这样:

    .text
    .align 2
    .globl main
main:
    addi sp,sp,-16
    sw ra,12(sp)
    # ...
    ret
    # ...

其中, ret 指令并不是基础指令,而是假指令的一种。
假指令

根据上图,可以知道 ret 指令是由基础指令 jalr x0, 0(x1) 扩展而成。

本文并没有介绍全部的假指令,更多的资讯还是需要读者自行去翻阅 RISC-V 规格书。

再以 C 语言程序码为例:

#include <stdio.h>
int main()
{
    printf("Hello, %s\n", "world");
    return 0;
}

我们可以将 C 程序码使用 RISC-V 的 C 语言编译器进行编译得到组译档案。
结果如下:

    .text
    .align 2
    .globl main
main:
    addi sp,sp,-16
    sw   ra,12(sp)
    lui  a0,%hi(string1)
    addi a0,a0,%lo(string1)
    lui  a0,%hi(string2)
    addi a0,a0,%lo(string2)
    call printf
    lw   ra,12(sp)
    addi sp,sp,16
    li   a0,0
    ret
    .secton rodata
    .balign 4
string1:
    .string "Hello, %s!\n"
string2:
    .string "world"

导读

  • 第 7 - 8 行:
    lui 指令可以将 unsigned 20-bit放到 rd暂存器的最高 20-bit,并将剩余的 12-bit补 0 ,而 %hi(string1) 则是用来取 string1 的前 20 位地址值(一共 32 位)。

    参考一

我们将组合语言丢到组译器编译後就能得到 RISC-V 的机器语言了。

00000000 <main>:
 0: ff010113 addi  sp,sp,-16
 4: 00112623 sw    ra,12(sp)
 8: 00000537 lui   a0,0x0
 c: 00050513 mv    a0,a0
10: 000005b7 lui   a1,0x0
14: 00058593 mv    a1,a1
18: 00000097 auipc ra,0x0
1c: 000080e7 jalr  ra
20: 00c12083 lw    ra,12(sp)
24: 01010113 addi  sp,sp,16
28: 00000513 li    a0,0
2c: 00008067 ret

总结

组译器的介绍在今天告一段落,下一篇会接着介绍连接器。
看完本篇与下一篇後就能了解 C 语言从编译到执行到底精过了哪些阶段,是不是很有趣呢?

Reference


<<:  Day 18 ( 中级 ) 阵列点灯 ( 显示图形 )

>>:  [Day03] TS:泛型就。很。泛!用 extends 来加上一点限制吧!

Day 02: ML基础第二步 Anaconda开发环境

前言 Python虽然可以直接使用Windows的Console直接执行程序,但是不只对於笔者,对於...

DAY 18 - 九尾狐妹妹 (2) 线稿

大家好~ 我是五岁~ 今天来继续改善昨天的九尾狐草图吧~ 首先把耳朵厚度加厚了,并且加上毛茸茸的前饰...

【Day 27】Google Apps Script - API Blueprint 篇 - Apiary 建立专案与版本控制

Apiary 结合 Github 版本控制很方便。 今日要点: 》Apiary 建立专案介绍 》A...

[第十四只羊] 迷雾森林舞会VII 开完房间後走进房间

天亮了 昨晚是平安夜 关於迷雾森林故事 焦虑抑制剂 4号:我跟全场站不同边耶,我站7耶,我跟7号玩家...

Day-1 : Hello Wali 起手式

说故事之前,不免俗先自我介绍一下,我叫瓦力, 我的本行是一位插画家,也是一位斜杠青年, 我来自一个很...