引言

image-20240602160356045

现在我们来到了第三层——Machine Language Program

接下来将介绍汇编语言是怎么转化成机器码的

Consequence #1: Everything Has a Memory Address

  • 由于所有的指令和数据都存储在内存中,所以一切都有一个内存地址:指令、数据字
    • 这意味着分支和跳转都使用这些地址
  • C 指针只是内存地址:它们可以指向内存中的任何东西
    • 不受约束的地址使用可能导致严重的错误;在 C 语言中避免错误是你的责任;而在 Java 语言设计中,这种使用受限
  • 一个寄存器保存着当前正在执行的指令的地址:“程序计数器”(Program Counter,简称 PC)
    • 基本上就是一个指向内存的指针
    • 英特尔称之为指令指针(Instruction Pointer,简称 IP)

Consequence #2: 二进制的兼容

  • 程序以二进制形式分发
    • 程序绑定到特定的指令集
    • 手机和个人电脑的版本不同
  • 新机器希望运行旧程序(“二进制”文件)以及编译到新指令的程序
  • 这导致了“向后兼容”的指令集随着时间的推移而演变
  • 选择英特尔8088处理器作为1981年首款IBM PC的处理器是当今最新PC仍使用80x86指令集的主要原因;1981年的PC程序今天仍然可以运行

指令作为数字

大多数我们处理的数据都是以字(32位块)为单位:

  • 每个寄存器是一个字

  • lwsw每次都访问一个字的内存

  • 那么我们如何表示指令呢?

    • 记住:计算机只能理解1和0,所以汇编器字符串“add x10,x11,x0”对硬件来说是无意义的
    • RISC-V追求简洁:既然数据是以字为单位,那么指令也固定为32位的字
    • 同样的32位指令用于RV32、RV64、RV128
  • 一字为32位,因此将指令字分为“字段”

  • 每个字段向处理器传达一些关于指令的信息

  • 我们可以为每条指令定义不同的字段,但RISC-V追求简洁,所以定义了六种基本类型的指令格式:

    • R格式用于寄存器-寄存器算术运算
    • I格式用于寄存器-立即数算术运算和加载
    • S格式用于存储
    • B格式用于分支(S格式的一个小变种)
    • U格式用于20位上位立即数指令
    • J格式用于跳转(U格式的一个小变种)

    image-20240602162715185

R-Format布局

image-20240602163250876

  • 32-bit instruction word divided into six fields of varying numbers of bits each: 7+5+5+3+5+7 = 32

  • opcode: 部分指定了是什么指令

    • 注意:对于所有R型格式的寄存器-寄存器算术指令,该字段等于0110011
  • funct7funct3:结合opcode,这两个字段描述了要执行的操作。

  • rs1Source Register #1):指定包含第一个操作数的寄存器。

  • rs2:指定第二个操作数的寄存器。

  • rdDestination Register):指定将接收计算结果的寄存器。

  • 每个寄存器字段包含一个5位无符号整数(0-31),对应一个寄存器编号(x0-x31)。

例子

image-20240602162249134

所有 RV32 R-format 指令

image-20240602162801591

  • 图片中的“2’s comp of rs2”意思是“rs2的二进制补码”

  • 在表格中,“2’s comp of rs2”标注在funct7字段中的0100000这一行,表示:

    • 对应指令是sub(减法)。

    • 这个funct7字段的具体含义是告诉处理器,这条指令要对寄存器rs2中的值取二进制补码,然后与rs1中的值相加,从而实现减法操作。

  • 具体解释

    • add指令的funct7字段是0000000,表示正常加法操作:rd = rs1 + rs2

    • sub指令的funct7字段是0100000,表示减法操作:rd = rs1 - rs2。在计算机内部,减法操作可以通过加上一个数的二进制补码来实现。因此,这里“2’s comp of rs2”表示对rs2取二进制补码然后再相加。

  • 对于srasrl来说,funct7字段中的第一个1(即0100000中的第2位)在这里作为一个标识符,用于区分这两种不同的右移操作。

I-Format 布局

Register-Immediate Arithmetic Instructions

image-20240602163224051

例子

image-20240602163318632

所有RV32 I-Format 算术指令

image-20240602163423611

Load 指令

image-20240602163947603

例子

image-20240602164004444

所有Load Instructions

image-20240602164056614

S-Format 布局

image-20240602164209926

为什么要分离immediate字段

  • store操作后没有寄存器可以存入
    • 其两个源寄存器
      • 一个是存数据的
      • 一个是存地址的,加上偏移量后把数据移动进去

image-20240602164718733

同时,也是因为为了和其他指令有相同的寄存器字段位置,方便硬件操作

例子

image-20240602164818342

所有Store指令

image-20240602164835016

前三总结

image-20240602164912608

Program Counter(PC) 的更新

PC

image-20240602165451798

对多数指令来说,PC = PC + 4

image-20240602165531632

Branch更新PC到Jump

image-20240602165646693

PC相对寻址法

image-20240602165720574

B-Format布局

image-20240602165909533

偏移的缩放

对于条件分支的 12-bit 的 immediate字段来说:

单位是 2 bytes(16-bit “half-words“)

image-20240602170200033

布局

image-20240602170301535

为什么要把11移到最低位???

image-20240602170501835

  • 对于最高的7位
    • 最高位为符号位
    • B-type这样子做可以和I-type和S-type保持对齐
  • 对于最低的那几位
    • 因为对于B-type我们总是乘2,所以最低位的0我们不存,因此空了一位出来,因此把11移到最低位来保持对齐

例子

image-20240602170743683

image-20240602170753743

16 / 2 = 8,因此是0x100,编码进Imm的时候因为imm的单位是2,所以左移一位,即0x1000,抵消了除的效果

所有B-Format指令

image-20240602172430027

J-Format指令

jal指令和j伪代码

image-20240602200508673

image-20240602200518180

J-Format布局

image-20240602200629331

U-Format布局

image-20240602200751759

lui and addi 实现了Long Immediates

image-20240602200934322

lui的极端情况:addi符号扩展

image-20240602201126883

addi可能会符号位扩展,导致和实际不符

image-20240602201112713

解决方法:lui先加上一再addi

auipc

image-20240602201335833

jalr:I-Format

jalr指令和jr伪代码

image-20240602201547425

jalr布局

image-20240602201605401

jalr指令和jr伪代码

image-20240602202001459