对于load/store指令,有必要再深入研究一下。由于load和store指令是相对应的,我们今天重点看load。
ARM提供了一组指令,包括加载获取(Load Acquire)语义和存储释放(Store Release)语义,以支持Release Consistency sequentially consistent(RCsc)模型。另外,FEAT_LRCPC(Armv8.3中的扩展功能)提供Load AcquirePC指令。Load AcquirePC和Store Release的组合可用于支持Release Consistency processor consistent(RCpc)模型。RCpc相较RCsc,则更加弱化。
ARM采用的是弱一致性模型。释放一致性模型(Release Consistency,RC)是对弱一致性模型的改进,它把同步操作进一步分成获取操作(Acquire)和释放操作(Release)。Acquire用于获取对某些共享存储单元的独占性访问权;Release用于释放该访问权。执行的顺序为:acquire-> load/store ->release。
对于获取语义:该原语之后的任何读写操作都不能重新排序到此原语之前执行
对于释放语义:该源于之前的任何读写操作都不能重新排序到此原语之后执行
Acquire通常与加载指令结合;而Release通常与存储指令结合,所以在文档中经常会看到load-acquire和store-release这样的描述。
对于数据大小,有如下的约定:
字节(Byte),8-bit
半字(Half-word),16-bit
字(Word),32-bit
双字(Double-word),64-bit
铺垫完这些基本概念,回到加载指令。
1 LD64B,单拷贝原子64字节加载指令。该指令从基址寄存器中得到内存地址,从内存位置连续加载八个64-bit的双字,写入连续寄存器Xt到X(t+7),整个过程是原子操作。
LD64B指令的编码格式如下:
LD64B指令的语法格式如下:
LD64B <Xt>, [<Xn|SP> {,
2 LDADD指令,原子加法指令。该指令从内存中加载一个32-bit字或64-bit双字,与寄存器里面的值相加,把结果写回到内存中;最初从内存加载的值返回到目标寄存器中。
LDADD指令的编码格式如下:
LDADD指令的语法格式如下:
LDADD <Xs>, <Xt>, [<Xn|SP>]
另外还有三条指令,LDADDA,LDADDL和LDADDAL。这四条指令的区别是,LDADDA和LDADDAL的加载访问有获取语义;LDADDL和LDADDAL的存储访问有释放语义;LDADD既没有获取语义也没有释放语义。所以,指令中最后的A代表acquire,L代表release。针对字节和半字操作,相应的指令是LDADDB, LDADDAB, LDADDALB, LDADDLB和LDADDH, LDADDAH, LDADDALH, LDADDLH,最后的B表示byte,H表示halfword。
3 LDAR,加载获取指令(Load-Acquire)。该指令从基址寄存器得到内存地址,从内存位置加载一个32-bit字或64-bit双字,写入寄存器。
LDAR指令的编码格式如下:
LDAR指令的语法格式如下:
LDAR <Xt>, [<Xn|SP>{,
后面讲内存屏障的时候会着重解释一下LDAR指令。
LDAR的变体指令有LDARB和LDARH。
4 LDAPR,加载获取指令(Load-Acquire RCpc)。该指令从基址寄存器得到内存地址,从内存位置加载一个32-bit或64-bit,写入寄存器。
LDAPR指令的编码格式如下:
LDAPR指令的语法格式如下:
LDAPR <Xt>, [<Xn|SP> {,
与LDAR指令的不同之处在于,LDAPR指令支持Load-AcquirePC。LDAPR的变体指令有LDAPRB和LDAPRH。
5 LDAPUR,不扩展加载获取指令(Load-Acquire RCpc,unscaled)。该指令从基址寄存器得到内存地址,并加上一个立即数偏移组成新的地址,从新地址加载一个32-bit字或64-bit双字,不扩展,写入寄存器。
LDAPUR指令的编码格式如下:
LDAPUR指令的语法格式如下:
LDAPUR <Xt>, [<Xn|SP>{, #<simm>}]
LDAPUR指令中的U表示unscaled。这里的unscaled指的是偏移量不可扩展。LDR指令中的偏移量是可扩展的,也就是偏移量是8字节对齐的。以imm9为例,这是9-bit的偏移量,如果是可扩展,地址偏移量范围是0 ~ 4088B(2^9 * 8)。如果不可扩展,偏移量是字节对齐的,范围是-256 ~ 255B。
LDAPUR的变体指令有LDAPURB和LDAPURH。
6 LDAPURSW,有符号加载获取指令(Load-Acquire RCpc,Signed Word)。该指令从基址寄存器和立即偏移量计算地址,从内存加载有一个符号字,对其进行符号扩展,并将其写入寄存器。
LDAPURSW指令的编码格式如下:
LDAPURSW指令的语法格式如下:
LDAPURSW <Xt>, [<Xn|SP>{, #<simm>}]
LDAPURSW指令中的SW表示signed word。其变体指令有LDAPURSB和LDAPURH。
7 LDAXR,加载获取独占指令(Load-Acquire Exclusive)。该指令从基址寄存器得到内存地址,从内存位置加载一个32-bit字或64-bit双字,写入寄存器。内存访问是原子性的。PE将正在访问的物理地址标记为独占访问,此独占访问标记由存储独占指令检查。
LDAXR指令的编码格式如下:
LDAXR指令的语法格式如下:
LDAXR <Xt>, [<Xn|SP>{,
LDAXR指令中的X表示exclusive。其变体指令有LDAXRB和LDAXRH。
8 LDAXP,加载获取独占指令(Load-Acquire Exclusive Pair)。该指令从基址寄存器得到内存地址,从内存位置加载两个(一对)32-bit字或64-bit双字,并将其写入两个寄存器。PE将正在访问的物理地址标记为独占访问,此独占访问标记由存储独占指令检查。
LDAXP指令的编码格式如下:
LDAXP指令的语法格式如下:
LDAXP <Xt1>, <Xt2>, [<Xn|SP>{,#0}]
LDAXP指令中的P表示pair,参考下面的LDP指令。
9 LDXP,加载独占指令(Load Exclusive Pair)。该指令从基址寄存器值得到内存地址,从内存加载两个32-bit字或两个64-bit双字,并将其写入两个寄存器。PE将正在访问的物理地址标记为独占访问,此独占访问标记由存储独占指令检查。
LDXP指令的编码格式如下:
LDXP指令的语法格式如下:
LDXP <Xt1>, <Xt2>, [<Xn|SP>{,#0}]
10 LDXR,加载独占指令(Load Exclusive)。该指令从基址寄存器值得到内存地址,从内存加载32-bit字或64-bit双字,并将其写入寄存器。内存访问是原子性的。PE将正在访问的物理地址标记为独占访问,此独占访问标记由存储独占指令检查。
LDXR指令的编码格式如下:
LDXR指令的语法格式如下:
LDXR <Xt>, [<Xn|SP>{,
其变体指令有LDXRB和LDXRH。
11 LDP,加载指令(Pair)。该指令通过基址寄存器和立即数偏移计算出内存地址,从内存地址加载两个32-bit字或64-bit双字,写入到两个寄存器。
LDP指令的编码格式如下:
其变体指令LDTRB,LDTRH。
当PSTATE寄存器中的UAO字段为1,在EL1和EL2执行非特权加载指令的效果和执行特权加载指令的效果一样。
13 LDTRSW,非特权有符号数加载,该指令从内存中加载一个字,将其扩展为64位有符号数,并将结果写入寄存器。用于加载的地址由基址寄存器和立即数偏移量计算得出。
其变体指令有LDTRSB,LDTRSH.
14 LDUR,不扩展加载指令(unscaled),该指令根据基址寄存器和立即数偏移量计算地址,从内存加载32-bit字或64-bit双字,零扩展,并将其写入寄存器。
其变体指令有LDURB,LDURH。
15 LDURSW,有符号不扩展加载指令,该指令根据基址寄存器和立即数偏移量计算地址,从内存加载有符号字,对其进行符号扩展,并将其写入寄存器。
其变体指令有LDURSB和LDURSH
16 LDG,加载分配标记(Allocation Tag)指令,该指令从内存地址加载分配标记,从分配标记生成逻辑地址标记,并将其合并到目标寄存器中。用于加载的地址根据基址寄存器和立即数有符号偏移量(通过Tag粒度缩放)计算。
其变体指令有LDGM,M表示multiple。等讲内存标签(Memory Tagging)的时候在具体介绍LDG指令。
17 LDLAR,加载指令(Load LOAcquire)。该指令从内存中加载一个32-bit字或64-bit双字,并将其写入寄存器。该指令还具有内存排序语义(在手册的LoadLOAcquire, StoreLORelease章节)。
LDLAR指令的编码格式如下:
18 LDCLR,原子位清除指令。该指令以原子方式从内存加载一个32-bit字或64-bit双字,执行按位与运算,并将值的补码保存在寄存器中,然后将结果存储回内存。最初从内存加载的值返回到目标寄存器中。
其变体指令有LDCLRA,LDCLRL和LDCLRAL,A和L的含义参考前面。同样,对于8-bit数据操作,有LDCLRB,LDCLRAB,LDCLRALB,LDCLRLB;对16-bit数据操作,有LDCLRH,LDCLRAH,LDCLRALH,LDCLRLH。
19 LDEOR,原子异或指令。该指令以原子方式从内存中加载32-bit字或64-bit双字,使用其寄存器中保存的值执行异或,并将结果存储回内存。最初从内存加载的值返回到目标寄存器中。
LDEOR指令的编码格式如下:
其变体指令有LDEORA, LDEORAL, LDEORL,LDEORB, LDEORAB, LDEORALB, LDEORLB,LDEORH, LDEORAH, LDEORALH, LDEORLH。不再赘述。
20 LDSET,原子位设置指令。该指令以原子方式从内存加载一个32-bit字或64-bit双字,执行按位或运算,并将值保存在寄存器中,然后将结果存储回内存。最初从内存加载的值返回到目标寄存器中。
其变体指令有LDSETA,LDSETAL,LDSETL;LDSETB,LDSETAB,LDSETALB,LDSETLB;LDSETH,LDSETAH,LDSETALH,LDSETLH。
21 LDSMAX,原子有符号数取大值指令。该指令以原子方式从内存中加载一个32-bit字或64-bit双字,将其与寄存器中保存的值进行比较,并将较大的值存储回内存。最初从内存加载的值返回到目标寄存器中。
LDSMAX指令的编码格式如下:
其指令变体有LDSMAXA,LDSMAXAL,LDSMAXL;LDSMAXB,LDSMAXAB,LDSMAXALB,LDSMAXLB;LDSMAXH,LDSMAXAH,LDSMAXALH,LDSMAXLH。
对应的还有原子无符号数取大值指令,LDUMAX,LDUMAXA,LDUMAXAL,LDUMAXL;LDUMAXB,LDUMAXAB,LDUMAXALB,LDUMAXLB;LDUMAXH,LDUMAXAH,LDUMAXALH,LDUMAXLH。
22 LDSMIN,原子有符号数取小值指令。该指令从内存中加载一个32-bit字或64-bit双字,将其与寄存器中保存的值进行比较,并将较小的值存储回内存,将这些值视为有符号数。最初从内存加载的值返回到目标寄存器中。
LDSMIN指令的编码格式如下:
其变体指令LDSMINA,LDSMINAL,LDSMINL;LDSMINB,LDSMINAB,LDSMINALB,LDSMINLB;LDSMINH,LDSMINAH,LDSMINALH,LDSMINLH。
无符号数指令LDUMIN,LDUMINA,LDUMINAL,LDUMINL;LDUMINB,LDUMINAB,LDUMINALB,LDUMINLB;LDUMINH,LDUMINAH,LDUMINALH,LDUMINLH。
本篇主要是介绍各种特殊的加载指令,至于具体的用法,会放在后面的同步原语(Synchronization Rrimitive),内存屏障(Memory Barrier),内存标签(Memory Tagging)等文章中。
这篇文章整理起来十分繁琐,指令过多,而且指令名字差别不大,几个字母颠来倒去,很容易就看串行或者敲错了。
与加载指令对应的是存储指令,就不再整理了。
如果大家不是做处理器逻辑设计,或者底层软件开发,编译器开发等等(比如我,哈哈),就不需要太关注具体的指令编码和语法。但是从系统架构角度,需要对这些指令有所了解。
原作者:老秦谈芯