ARM指令

  • 采用Load-Store结构
  • 固定长度(32bit)指令
  • 三操作数指令格式
  • 条件执行所有指令
  • 一条指令可以装载或存储多个寄存器
  • ALU操作支持单周期n-bit移位

Load-Store结构

  • 指令集中的数据运算:
    • 不能对存储器中的数据直接进行操作
    • 仅能处理寄存器中的值,而且总是将处理结果放回到寄存器中
  • 对存储器中数据的操作:
    • 仅能将存储器的值 加载(load) 到寄存器中
    • 或将寄存器的值 存储(store) 到存储器中
  • ARM指令不支持“存储器-存储器”操作

汇编格式

  • 寄存器传送:
    • <opcode1>{<cond>}{S} <Rd>, <shifter_operand>
    • <opcode1> := MOV | MVN
  • 比较:
    • <opcode2>{<cond>} <Rn>, <shifter_operand>
    • <opcode2> := CMP | CMN | TST | TEQ
  • 运算:
    • <opcode3>{<cond>}{S} <Rd>, <Rn>, <shifter_operand>
    • <opcode3> := ADD | SUB | RSB | ADC | SBC | RSC | AND | BIC | EOR | ORR

CMP | CMN | TST | TEQ 没有目标寄存器

MOV | MVN 没有第一个源操作数

立即数

必考的

立即数的范围:在32位指令字内编码

<immediate> = immed_8 循环右移(2*rotate_imm)

  • <immediate>为32位的立即数
  • immed_8为8位的立即数
  • rotate_imm为4位的循环右移值

合法立即数:0xff0xff00xff00

非合法立即数:0xff10x1fe

如何判断立即数?

0x0001_0000_0001    因立即数是八位循环右移
0x0001_0000_0010 且仅能循环右移偶数位
0x0001_0000_0011 0x0001_0000_0010需要由0x1000_0001右移奇数位
0x0001_0000_0100 0x0001_0000_0100需要由0x0100_0001右移偶数位
1_2345_678

比较

CMP CMN TST TEQ

如上比较操作中的“减,加,与和异或”的结果不存储于任何目标寄存器。

乘法

这是ARM中罕见的四操作数指令——MLA!

乘法:MUL r4, r3, r2 ; r4 := (r3×r2)[31:0]

乘累加:MLA r4, r3, r2, r1 ; r4 := (r3×r2+r1)[31:0]
考虑指令执行的效率,所有操作数都放在寄存器中。

移位

LSL LSR ASL ASR

  • 前两个为逻辑移位(Logical),空出的位用0填充;
  • 后两个为算术移位(Arithmetic),如果源操作数是正数,则空出的最高有效位用0填充,反之则用1填充。

ROR ROX

这俩是循环移位。

  • ROX:循环右移1~31位,移出的字的最低有效位依次填入空出的最高有效位
  • RRX:扩展1位的循环右移,空位(31)是用原来的标志位C填充,操作数右移1位

似乎左移都是0~31位,但右移是1~32位。应该是为了区分吧。

控制流指令

控制流指令确定程序下一步执行哪条指令,包含转移指令和条件转移指令。

B与BL为转移指令,带L的话将转移后下一条指令的地址传送到当前处理器模式下的链接寄存器(r14)。这一般用于实现子程序的调用,在子程序返回时将链接寄存器的内容拷贝回PC。

条件执行

条件域(condition field) 占据32位指令域的高4位。条件域共有16个值,每个值都根据CPSR寄存器中的标志位N、Z、C和V的值来确定指令是执行还是跳过。

条件域代码,注意1111不应当被使用

ARM寻址

ARM中的寻址方式有九种,常用的有如下几种:

  • 立即数寻址
  • 寄存器寻址
  • 寄存器间接寻址
  • 寄存器偏移寻址
  • 寄存器基址变址寻址

ARM没有直接寻址。

立即数寻址

立即数寻址就是直接将内存中的数据发给CPU作为操作数。ARM是32位指令集,因此立即数范围在0至28-1之间(八位立即数)。

LDR r0, #254   ; 将254写入r0寄存器
ADD r1, r2, #3 ; 将r2寄存器中的值与3相加后,再写入r1寄存器

寄存器寻址

寄存器寻址就是直接将寄存器中的数值作为操作数。

LDR r1, r0      ; 将r0寄存器中的值写到r0
ADD r3, r2, r1 ; 将r1、r2寄存器的值相加,结果写入r3寄存器

寄存器间接寻址

寄存器间接寻址也用到了寄存器,但是操作数不是寄存器里的值了,而是寄存器内地址所对应的操作数。操作数在内存里。

和寄存器寻址相比,在提供操作数地址的寄存器上加上[ ],比如[r0]

graph LR
寄存器 --> 地址 --> 操作数
MOV r0, #0X54000032    ; 将0X54000032的地址赋给r0
LDR r1, [r0] ; 将地址为0X54000032的数据写入r1寄存器中

寄存器偏移寻址

以寄存器寻址为本,将寄存器中的数移位后作为操作数。

LDR r0, r1, LSL #3   ; 将r1的值逻辑左移3位后写入r0
LDR r0, r1, ROR r2 ; 将r1的值循环右移r2中的值对应位后,写入r0

寄存器基址变址寻址

多看

基址变址寻址是基于寄存器间接寻址的,只不过地址不再是寄存器中的值了,而是偏移后的值,这里的偏移值可以理解为地址相加值。

有时候会在第二个操作数后面加上!,这代表加上自动变址的功能。前变址不会改变,因此有时候要加上!

前变址

前变址不改变r1寄存器的值,在有些情况下不方便。

!就成为前变址加自动变址(auto-indexed)。

LDR r0, [r1, #3]     ; 地址为:r1值+3字节,指令执行完r1不变
; 未加! 则r1不变
LDR r0, [r1, #3]! ; 地址为:r1值+3字节,指令执行完r1=r1+3
; 加了! 则r1自动变址
LDR r0, [r1, #-1] ; 地址为:r1值-1字节,指令执行完r1不变

LDR r0, [r1, r2] ; 地址为:r1值+r2值

后变址

后变址允许基地址不加偏移即作为数据传送地址使用,而后再自动变址。

LDR r0, [r1], #4     ; 地址为:r1值,但指令执行完后r1=r1+4

说白了就是:前后变址的“前后”是相对于基地址的偏移而言的。前变址是先变再用,后变址是先用再变(自带自动变址)。

练习

  1. 在ARM指令系统的各种寻址方式中,获取操作数最快的方式是( )
  2. 若操作数在内存中的地址包含在指令中,则属于( )方式。
  • A、直接寻址
  • B、立即数寻址
  • C、寄存器寻址
  • D、寄存器间接寻址
答案
  1. B
  2. D

ADR

ADR:小范围的地址读取伪指令

ADR指令将基于PC相对偏移的地址值读取到寄存器中。在汇编编译源程序时,ADR被编译器替换成一条合适的指令(ADD or SUB)。

  • 程序计数器PC(r15)的内容通常接近所需数据地址。
  • ADR r1, table1指令被转换成PC(r15)加减一个常数。
  • 程序计数器相对寻址(PC relative addressing),PC值实际为当前指令地址+8

PC相对寻址

重点,理解计算的方法!

  • 程序计数器相对寻址(PC relative addressing):PC值=当前程序执行位置+8
  • ARM7采用取指、译码、执行三级流水线结构。
  • 程序计数器PC(r15)总是指向 “正在取指” 的指令,而不是指向“正在译码”的指令或“正在执行”的指令。一般来说,人们习惯性约定将“正在执行的指令作为参考点”,称之为当前第一条指令,因此PC总是指向第三条指令。
  • 在ARM状态下,每条指令为4字节长,所以PC始终指向该指令地址加8字节的地址。

例:根据ARM的3级流水线结构,分析以下程序执行完成后 PC寄存器的内容。

Mem[0x4000]: ADD PC, PC, #4
Mem[0x4004]: ADD r0, PC, #4
答案

因为是三级流水线:

Mem[0x4000]: ADD PC, PC, #4    F D E
Mem[0x4004]: ADD r0, PC, #4 F D E
Mem[0x4008]: ... F D E
在第一条指令执行时,PC指向取值的指令地址,即4008
在执行完第一条(即译码完第二条)之后,PC变为400C
随后执行完第二条(不再对新的指令进行取指),则PC不变。

帧机制

ARM64架构栈帧以及帧指针FP_arm64 帧指针-CSDN博客

汇编语言程序的基本结构

  • 顺序结构
  • 分支结构
  • 循环结构

“程序设计理论已经证明,这三种结构是完备的,用它们可以写出任何功能的程序。”

证明自 Bohm C., Jacopini G. “Flow diagrams, Turing machines and languages with only two formation rules.” Communications of the Association for Computing Machinery, Vol.9, pp. 366–371. 1966.

bohm-jac.pdf

顺序结构

y = a * (b + c);

ADR r4, b		; get address for b
LDR r0, [r4] ; get value of b
ADR r4, c ; get address for c
LDR r1, [r4] ; get value of c
ADD r2, r0, r1 ; compute partial result
ADR r4, a ; get address for a
LDR r0, [r4] ; get value of a
MUL r3, r2, r0 ; compute final value for y
ADR r4, y ; get address for y
STR r3, [r4] ; store y

分支结构

if-else

if (a < b) x = 5; else x = c;

答案
  ADR r4, a		; get address for a
LDR r0, [r4] ; get value of a
ADR r4, b ; get address for b
LDR r1, [r4] ; get value for b
CMP r0, r1 ; compare a < b, test condition
BGE fblock ; if a >= b, branch to false block
tblock MOV r0, #5 ; generate value for x
ADR r4, x ; get address for x
STR r0, [r4] ; store x
B … ; skip the fblock
fblock ADR r4, c ; get address for c
LDR r0, [r4] ; get value of c
ADR r4, x ; get address for x
STR r0, [r4] ; store value of x

switch

DCD伪操作用于分配一片连续的字存储单元。

switch (test) { case 0: … ; case 1: … ; ……}

答案
ADR r2, test 		; get address for test
LDR r0, [r2] ; load value of test, e.g., 0\1\2\3
ADR r1, switchtab ; load address for switch table
LDR r15, [r1, r0, LSL #2] ; index switch table, r15=pc

循环结构

1+2+3+...+100

答案
		MOV r0, #0
MOV r1, #0
SUM: ADD r1, r1, #1
ADD r0,r0, r1
CMP r1, #100
BNE SUM

FIR滤波器

答案
		MOV r0, #0		; use r0 for i
MOV r8, #0 ; use word-aligned index for arrays
ADR r2, N ; get address for N
LDR r1, [r2] ; get value of N
MOV r2, #0 ; use r2 for f
ADR r3, c ; load r3 with base pointer of c
ADR r5, x ; load r5 with base pointer of x
loop LDR r4, [r3, r8] ; get c[i]
LDR r6, [r5, r8] ; get x[i]
MUL r7, r4, r6 ; compute c[i]*x[i]
ADD r2, r2, r7 ; add into running sum
ADD r8, r8, #4 ; add one word offset
ADD r0, r0, #1 ; add 1 to i
CMP r0, r1 ; exit?
BLT loop ; if i < N, continue

下次会讲讲多寄存器的L/S指令。