Ch2 编译原理 整数运算

Arithmetics & Information

Posted by R1NG on October 27, 2020 Viewed Times

Ch2 整数运算

Motivation:

  1. 使用 ARM 指令集编写简单的汇编程序
  2. 了解信息的保存方式

1. 编写简单的 ARM 汇编程序

我们以一个简单的程序为例:

1
2
3
4
5
6
7
8
9
10
11
A shop wishes to give a 100 pound discount to anyone who buys goods for ?2000 or more.

Decision: Compare the total price to 2000 and see if the total is larger. Depending on the result take action or not.

Action: Subtract 100 pounds from the total. Computers have no intelligence, so spell out details:

Formalise: if total ≥ 2000 pounds then subtract 100 pounds from total
Rewrite: if total < 2000 pounds then don’t subtract 100 pounds from total   
;remember the characteristic of insrtuction 'CMP': computer do way better determining whether the output operand is negative or not

Encode: as ARM instructions

1.1 内存分配

下面, 我们将要着手编写程序. 首先, 我们需要为变量分配存储单元 (Memory Location): 通过使用 DEFW 命令, 编译器可以为我们做到这一点:

1
label  DEFW  value

需要注意的是:

  1. DEFW 是一个伪指令, 而非真正的 ARM 指令.
  2. 伪指令看上去很像真实的指令, 但它们并不会是要被执行的. 如果尝试令处理器执行它, 处理器并不能正确地将其识别并产生所预期的结果.
  3. 对于 DEFW, 编译器将被分配的值放入下一个可供使用的空存储单元, 而这一切都发生在 程序运行之前.
  4. 标签使编译器能够找到它所被分配的存储单元.

程序编写如下:

20201105191846

而该程序的内存分配如下:

20201105191913

1.2 内存映射

在内存中, 一些存储单元所存储的是我们为程序定义的值, 但更多的内存单元都是未经定义的. 实际上, 内存映射的某些部分可能并不实际存在. 存储单元所存储值的含义 (Meaning) 经过转译 (Interpretation)后即可得到体现. 使用不同的指令, 我们可以将特定的存储单元中所存储的值映射为某个指令或映射为数据类型. 需要注意的是, 程序计数器中所存储的值, 能且只能是内存地址.


2. 信息表示

我们通常使用位来表示和存储信息. 每一个位有两种状态: 0, 1. 它们分别可以指示一种状态, 若有 $n$ 个位, 最多可以表示 $2^n$ 种状态.

以字符编码标准: ASCII 标准为例, 在该标准中, 每一个字符都由 7 个位表示, 它共可以编码 128 个不同的字符, 包括全部数字, 大小写字母, 一些符号以及控制符. 为了表示更多的符号, 还制定了其他的标准.

其余被广泛使用的进制系统还有二进制, 十六进制, 在一些特殊场合还会使用八进制. 关于进制间的转换, 此处不再赘述.

关于二进制整数表示法与浮点数表示法, 详见 COMP12111 计算机组成与结构: 逻辑门电路.


3. 整数运算

3.1 寄存器

如前文所述, ARM 架构定义了 $16$ 个寄存器: $R_0, \cdots, R_{15}$, 并且我们不能一次使用多个寄存器. 程序的高效运行需要涉及多个寄存器, 在编程时必须谨慎考虑寄存器的重复使用.

由于 32 位指令集长度有限, 且受到指令集分配耗时限制和芯片大小的限制, ARM 架构不提供更多的寄存器.

在进行汇编语言编程时, 我们可以优化指令或其执行顺序, 以减少寄存器的使用, 缩减程序执行时间:

可见, 通过优化, 我们成功地将涉及三个数的加法运算所需要使用的寄存器数量减少了 $5$ 个, 并且所执行指令的数量并没有增加. 这意味着, 程序运行时间和程序寄存器占用都得到了优化.

下面再看一个例子: 20201105192253

在该案例中, 经过优化后的程序运算指令数没有变化. 虽然使用的寄存器数量有所减少, 但存储操作指令的数量有所增加. 存储操作指令耗时较长, 这一般也意味着执行这一条指令需要消耗更多的电量. 因此, 从运行时间的角度考量, 这并不是一个成功的优化.

以寄存器复用位为单一导向的程序优化是有局限的. 对于执行流程短的程序, 使用更多的存储操作指令以减少寄存器的使用对程序运行时间的影响微乎其微, 但对于规模较大的程序而言并不是这样. 在实际优化中, 我们必须综合考虑多种因素, 结合实际进行优化操作.

在评价程序的执行效率 (性能) 时, 我们一般考虑:

  1. 执行指令数
  2. 寄存器利用数
  3. 存储操作指令数 以上三者的评价标准都是越小越好.

注:

  1. 对程序的优化不得导致运算的结果出现可能的变化. 有限的寄存器大小意味着, 能存储入寄存器的数值是有范围的. 如果我们将两个大数相加后的结果存入寄存器, 则可能出现溢出!
  2. 此外, 涉及浮点数的计算也需要特别注意顺序. 寄存器内只能存取有限位浮点数的尾数, 因此计算顺序的变化可能导致计算结果的变化.

ARM 架构为我们提供了名为 “文本” (literal) 的预设数值以避免数值运算中对寄存器的冗余使用. 加法, 减法和比较 (ADD, SUB, CMP) 的第二个输入操作数可以被 “文本” 定义:

1
2
3
ADD R0, R1, #100
;#100 is a literal: fixed value embedded in the instruction
;it's a constant: it doesn't change during the program execution!

需要注意的是, MUL, STR, LDR 都不能使用 “文本”.