今天我们来简单研究一下ARM的load和store指令。文中的分析都是默认基于A64指令集。如果不明白A64是什么,或者对处理器内部常用寄存器不清楚,建议看看前面的文章。
现在的处理器指令集基本都是寄存器-寄存器型。在寄存器-寄存器型指令系统中,运算指令的操作数只能来自寄存器,不能来自存储器,所有的访存都必须显式的通过load指令和store指令来完成,所以寄存器-寄存器型又被称为load-store型。
Load,顾名思义,就是把外部存储器数据加载到处理器的寄存器中;store就是把寄存器中的数据存储到外部存储器中。需要注意,load/store指令其实是一组指令,除去基本的load/store指令还有一大堆变种指令,总共差不多有几十种。文中以小写字母load/store来统称加载/存储指令,具体的指令用大写字母来表示。
基本的加载和存储指令是LDR和STR,指令的语法格式是:
LDR <Destination>, [<address>]
STR <Destination>, [<address>]
LDR指令就是把内存地址<address>中的数据(用地址加上方括号表示,即[<address>])加载到<Destination>指定的通用寄存器中;STR反过来,把通用寄存器中的数据存储到内存地址中。
现在就涉及到了一个问题,指令中的地址如何确定,或者说指令如何寻址?
A64指令集中的load/store指令的寻址模式需要使用通用寄存器X0-X30或当前堆栈指针SP作为基址,再加上立即数或寄存器偏移量,构成最终的内存地址。A64的寻址模式有以下几种:
- 基址寄存器模式(Base register modes)。这是最简单的寻址形式。基址寄存器是一个X寄存器,包含被访问数据的完整/虚拟地址,如下面的示意图所示,LDR指令把X1寄存器中的值当作内存地址,取出其中的数值,装入W0寄存器。
- 偏移寻址模式(Offset addressing modes)。此模式把寄存器中的地址加上偏移量,相加的结果作为内存地址,如下面的示意图所示,把X1中的地址加上偏移量12作为新的内存地址,然后把新内存地址中的数据加载到W0。
- 前索引寻址模式(Pre-indexed addressing modes)。在这种模式下,先更新地址偏移,然后访问内存地址。在指令语法中,前索引通过在方括号之后添加感叹号来表示。前索引寻址与偏移寻址类似,不同之处在于基指针是根据指令更新的,如下面的示意图所示,先把X1中的内存地址加上偏移量12,存入X1,然后取出新地址中的数据存入W0。
- 后索引寻址模式(Post-indexed addressing modes)。与前索引寻址模式不同,在这种模式下,先访问内存地址,然后更新地址偏移。使用后索引寻址,先从基指针中的地址加载值,然后更新指针,如下面的示意图所示,LDR先把X1内存地址的值加载到W0,然后把内存地址向后偏移12,新的内存地址放入X1。后索引寻址对于出栈很有用。指令从堆栈指针指向的位置加载值,然后将堆栈指针移动到堆栈中的下一个完整位置。
- PC相对寻址模式(Literal)。在这种模式下,基址是64位程序计数器的值,再加上19位有符号字偏移量组成内存地址。这意味着内存地址是一个4字节对齐的地址,地址范围在±1MB内。PC相对寻址只能用于至少32位的加载和预取指令。相对寻址适用于子程序调用。
分析完寻址方式,接下来我们再进一步看看指令的编码格式。A64是固定长指令集,采用32-bit编码。Load/store指令的基本编码格式如下:
其中的op0-op4是操作码,具体编码如下表:
我们具体来分析一条指令,LDR(register)。如前面所说,该指令根据基址寄存器的值和偏移寄存器的值来计算内存地址,并把内存地址中的数据加载进通用寄存器。指令的具体格式如下,其中size=10表示32-bit,size=11表示64-bit。
其中<Xt>是64-bit的通用寄存器名称,放在上图的Rt字段;<Xn>是64-bit的通用寄存器名称,放在Rn字段;如果option<0>=1,<Xm>是64-bit的通用索引寄存器,放在Rm字段;<extend>是索引扩展/移位说明符,默认为LSL,编码在option字段中,见下图;<amount>是索引移位量,仅在<extend>不是LSL时可选用。
前面说的load/store指令都是对一个的寄存器操作。A64还提供了对两个(pair)寄存器操作的load/store指令,也叫成对指令,即LDP(Load Pair)和STP(Store Pair)。成对指令可以在两个寄存器和内存之间传输数据。
下面示例中,LDP指令加载[X0]的数据到W3,加载[X0+4]到W7:
LDP W3, W7, [X0]
下面示例中,STP指令存储D0到[X4],存储D1到[X4+4]:
STP D0, D1, [X4]
LDP和STP指令通常用于出栈和压栈操作。在AArch64中,堆栈指针必须是128-bit对齐的,而通用寄存器是64-bit宽度。下面的示例中,STP指令采用前索引寻址,先把SP指向的地址做偏移,然后把X0和X1的值存入SP指向的地址。LDP指令采用后索引寻址,把SP指向地址的数据加载到X0和X1,并且SP指向的地址偏移16(即128bit对齐)。
STP X0, X1, [SP, #-16]!
LDP X0, X1, [SP], #16
A64中还提供了一些特殊的加载和存储指令。为了支持内存屏障,A64提供了LDAR(Load-Acquire)和STLR(Store-Release)指令。LDAR和STLR是单向的内存屏障指令,可以用于支持释放一致性(Release Consistency)模型。
A64中提供了同步原语指令LDXR(Load Exclusive)和STXR(Store Exclusive),用以支持加载/存储的原子操作。接下来会另开几篇探讨内存屏障和同步,这里就不细说了。
今天就到这里吧,是不是没用的知识又增加了一些呢?
作者:老秦谈芯
文章来源:老秦谈芯
推荐阅读
更多IC设计技术干货请关注IC设计技术专栏。
迎添加极术小姐姐微信(id:aijishu20)加入技术交流群,请备注研究方向。