ios

arm-neon

arm-neon 指令

Posted by nomadli on February 28, 2019

简介

  • 是一种SIMD协处理器,可选配置成向量浮点VFPv3
  • 16个128位寄存器Q0-Q15,32个64位寄存器D0-D31
  • 128位与64位寄存器是共用的,小新覆盖

armcc编译器

  • 开启优化等级-O2或者-O3, 开启–vectorize选项来使能向量化编译
  • 指定–cpu 7-A或–cpu Cortex-A8指定指令集架构和CPU类型
  • 指定-mfpu=vfpv3-fp16作为vfp协处理、
  • 指定-mfpu=neon-vfpv4NEON+VFP
  • 指定-mfloat-abi=soft使用软件浮点库不是用VFP或者NEON指令
  • 指定-mfloat-abi=softfp使用软件浮点的调用规则来使用VFP和NEON指令
  • 指定-mfloat-abi=hard使用VFP和NEON指令,改变ABI调用规则提供效率,如用vfp寄存器进行浮点数参数传递减少NEON寄存器和ARM寄存器的拷贝
  • 指定-ffast-math使用浮点数加速

gcc编译器

  • 开启-O3,开启-ftree-vectorize来使能向量化
  • 指定-mfpu=neon和-mcpu=cortex-a5
  • 指定-mfpu=vfpv3-fp16作为vfp协处理、
  • 指定-mfpu=neon-vfpv4 NEON+VFP
  • 指定-mfloat-abi=soft使用软件浮点库不是用VFP或者NEON指令
  • 指定-mfloat-abi=softfp使用软件浮点的调用规则来使用VFP和NEON指令
  • 指定-mfloat-abi=hard使用VFP和NEON指令,改变ABI调用规则提供效率,如用vfp寄存器进行浮点数参数传递减少NEON寄存器和ARM寄存器的拷贝
  • 指定-ffast-math使用浮点数加速

#cpu选择 CPU类型|CPU类型选项|FP选项|FP + SIMD选项|备注 :-|:-|:-|:-|:-| Cortex-A5|-mcpu=cortex-a5|-mfpu=vfpv3-fp16 -mfpu=vfpv3-d16-fp16|-mfpu=neon-fp16|-d16表明只有前16个浮点寄存器可用 Cortex-A7|-mcpu=cortex-a7|-mfpu=vfpv4 -mfpu=vfpv4-d16|-mfpu=neon-vfpv4|-fp16表明支持16bit半精度浮点操作 Cortex-A8|-mcpu=cortex-a8|-mfpu=vfpv3|-mfpu=neon| Cortex-A9|-mcpu=cortex-a9|-mfpu=vfpv3-fp16 -mfpu=vfpv3-d16-fp16|-mfpu=neon-fp16| Cortex-A15|-mcpu=cortex-a15|-mfpu=vfpv4|-mfpu=neon-vfpv4|

NEON 数据类型

  • 无符号整数 U8 U16 U32 U64
  • 有符号整数 S8 S16 S32 S64
  • 未指定类型的整数 I8 I16 I32 I64
  • 浮点数 F32
  • 多项式 P8 P16

NEON 指令

  • 分为正常指令、宽指令、窄指令、饱和指令、长指令
  • 正常指令:返回值与操作数类型及位数相同
  • 宽指令:操作数位数不同,返回值与最长的一样,以W结尾如VADDW指令
  • 窄指令:返回值是操作数的一半,以N结尾如果VMOVN
  • 饱和指令:操作数超过数据长度自动限制到范围,以Q为第二字母如VQSHRUN
  • 长指令:返回值是操作数的一倍,以L结尾如VADDL
  • VMOV 复制指令 ``` //正常指令 VMOV d0, r0, r1 //d0=(r0 « 32) | r1 VMOV r0, r1, d0 //r0=d0 & 0x00000000FFFFFFFF r1=(d0 » 32) & 0x00000000FFFFFFFF VMOV.U32 d0[0], r0 //d0=r0 VMOV.U32 r0, d0[0] //0=d0 & 0x00000000FFFFFFFF VMOV.U16 d0, #1 //d0=0x0001000100010001 VMOV.U32 q0, #1 //d0=0x00000001000000010000000100000001

//长指令 VMOVL.U16 q0, d0 //q0=(d0 & 0x0000000000FFFF)|((d0 & 0x000000FFFF0000) « 32)|… 16->32

//窄指令 VMOVN.I32 d0, q0 //d0=(q0 & 0x000000000000000000FFFFFFFF)|((q0 & 0x0000000000FFFFFFFF00000000) » 32)|.. 32->16

//饱和指令 VQMOVN.S32 d0, q0 //32->16,每部分取值0-65535


## arm-v7
- 32位寄存器, 0-15通用
- R0-R3     指令操作数、函数参数、返回值
- R4-R11    局部变量、函数执行前保留现场返回时恢复
- R12       FP栈帧基地址
- R13       SP栈指针
- R14       LR 链接寄存器 子程序调用返回地址,即执行BL(Branch and Link)时保存当前PC地址
- R15       PC 程序计数器
- R16       CPSR  Current Program Status Register
    - 条件标志: 零标志(Z)、负标志(N)、进位标志(C)、溢出标志(V)
    - 控制标志: 中断禁用(I)、快速中断禁用(F)
    - 模式标志: 用户模式、系统模式、中断模式
- R17-R?    SPSR  Saved Program Status Register
    - 中断发生 保存CPSR到SPSR
    - 每种中断有自己对应的寄存器

## arm-v8
- 64位寄存器, 0-30 通用
- X0        返回值或传参
- X1-X7     传参
- X8        返回值、间接结果地址
- X9-X28    一般寄存器
- X18       平台寄存器 ABI Thumb
- X29       FP栈帧基地址(栈底地址)
- X30       LR返回地址
- X31       SP栈顶地址、ZR零寄存器===0
- X32       PC 程序计数器指令地址
- W0-W30    做为32位寄存器时使用的名字,只使用低32位
- WSP       32位栈帧名字, 64位用SP
- WZR       32位零寄存器、ZR64位零寄存器

## arm 执行模式
- User Mode         用户模式, 正常程序执行的默认模式
- System Mode       系统模式, 用于运行操作系统的内核代码,与用户模式共享相同的寄存器
- FIQ Mode          快速中断模式, 用于处理快速中断请求,有额外的寄存器以减少中断服务例程的执行时间
- IRQ Mode          普通中断模式, 用于处理普通的中断请求
- Supervisor Mode   管理模式, 用于操作系统的内核级别操作,通常在系统启动或系统调用时进入
- Undefined Mode    未定义模式, 当执行了未定义的指令时进入此模式
- Abort Mode        中止模式, 当发生数据或指令预取中止时进入此模式

## arm 函数调用约定
- AAPCS     ARM Architecture Procedure Call Standard 默认函数调用约定
    - R0-R3 参数传递、剩余采用栈传递; 参数从左->右
    - 栈    保存局部变量、临时变量、函数调用状态; 地址从高->低; 
    - 管理  由被调用方管理参数入栈及寄存器状态保存及恢复
- AAPCS-VFP ARM Architecture Procedure Call Standard with the Vector Floating-Point extension 浮点扩展
    - S0-S3 参数传递、剩余采用栈传递;
    - FPSCR 浮点状态寄存器

## 汇编指令
- 寄存器寻址                mov r1, r2              ;R2中的值移动到R1中
- 立即寻址                  mov r0, #0xFF00         ;将0xFF00立即数移动到R0中
- 寄存器移位寻址             mov r0, r1, lsl #3      ;将R1左移3位的结果移动到R0中
- 寄存器间接寻址             ldr r1, [r2]            ;将R2指向的地址中的值加载到R1中r1=*r2
- 基址变址寻址              ldr r1, [r2, #4]         ;将R2加上4所得的地址中的值加载到R1中r1=*(r2+4)
- 多寄存器寻址              ldmia r1!, {r2-r7, r12}  ;将R1中的值加载到R2-R7和R12中,然后将R1加上32
- 堆栈寻址                  stmfd sp!, {r2-r7, lr}  ;将R2-R7、LR的值存储到堆栈中,然后将SP减去24
- 相对寻址                  beq flag '\n' flag:     ;如果条件码为EQ,则跳转到标签flag处
- 强制跳转                  B label                 ;无条件跳转到标签label处
- 带返回的跳转              BL label                 ;跳转到标签label处,并将返回地址存储在LR中
- 带返回和带状态的切换        BLX label               ;跳转到标签label处,并将返回地址存储在LR中;ARM和Thumb之间的切换
- 带状态的跳转               BX R1                   ;跳转到地址存储在R1中的位置,并切换到相应的指令集模式
- 赋值                     MOV R0, R1               ;将R1中的值赋值到R0中
- 加                       ADD R0, R1, R2           ;将R1和R2中的值相加,结果存储到R0中
- 减                       SUB R0, R1, R2           ;将R1-R2,结果存储到R0中
- 位与                     AND R0, R1, R2           ;将R1和R2中的值进行按位与操作,结果存储到R0中
- 位异或                   EOR R0, R1, R2            ;将R1和R2中的值进行按位异或操作,结果存储到R0中
- 位或                     ORR R0, R1, R2            ;将R1和R2中的值进行按位或操作,结果存储到R0中
- 位与非                   BIC R0, R1, #0xf          ;将R1和立即数#0xf进行按位与非操作,将结果存储到R0中;实现的BitClear
- 乘法                     MUL r0 r1,r2              ;r0 = r1 * r2
- 带加法的乘法              MLA r0,r1,r2,r3           ;r0 = r1 * r2 + r3
- 64位乘法                 SMULL r0,r1 ,r2 ,r3       ;r0 = (r2 * r3)的低32位 r1 = (r2 * r3)的高32位
- 64位带加法的乘法          SMLAL r0,r1 ,r2 ,r3        ;r0 = (r2 * r3)的低32位 + r0 r1 = (r2 * r3)的高32位 + r1
- 64位无符号乘法            UMULL r0,r1 ,r2 ,r3        ;r0 = (r2 * r3)的低32位   r1 = (r2 * r3)的高32位
- 64位无符号带加法的乘法     UMLAL r0,r1 ,r2 ,r3        ;r0 = (r2 * r3)的低32位 + r0 r1 = (r2 * r3)的高32位 + r1
- 逻辑左移                 LSL R0, R1, #2             ;将R1中的值左移2位,结果存储到R0中,右侧空出的位补零 
- 逻辑右移                 LSR R0, R1, #3             ;将R1中的值右移3位,结果存储到R0中,左侧空出的位补零
- 循环右移                 ROR R0, R1, #4             ;将R1中的值循环右移4位,结果存储到R0中,左侧空出的位补上右侧移出的位 
- 算术右移                 ASR R0, R1, #5             ;将R1中的值算术右移5位,结果存储到R0中,左侧空出的位补上符号位
- 扩展的循环右移指令         RRX R0, R1                 ;将R1中的值扩展的循环右移1位,结果存储到R0,C标志位的值插入到左侧
- 比较                    CMP r1, #10                 ;比较寄存器r1的值和立即数10;相等N = V && Z = 0
- 比较补码                 CMN r2, #5                  ;比较寄存器r2的值的补码和立即数5
- 小于跳转                 BLE label                   ;如果r2的补码值小于或等于5,则跳转到label
- 掩码位是否全为1           TST r3, #0xFF               ;检查寄存器r3的低八位是否全部为1
- 等于跳转                 BEQ label                   ;如果r3的低八位全为1,则跳转到label;(即Z = 1)
- 按位异或是否为0           TEQ r4, #0x80               ;检查寄存器r4的值和立即数0x80的异或结果不为0
- 不等于跳转               BNE label                    ;如果r4的值与0x80异或后不为零,则跳转到label;(即Z = 0)
- 非负数                   BPL label                   ;结果非负,则跳转到label;(即N = 0)
- 负数                    BMI label                    ;结果为负,则跳转到label;(即N = 1)
- 无进位                   BCC label                    ;结果无进位,则跳转到label;(即C = 0)
- 有进位                   BCS label                    ;结果有进位,则跳转到label;(即C = 1)
- 小于无符号数              BLO label                    ;即C = 1
- 大于等于无符号数           BHS label                    ;即C = 0
- 大于无符号数              BHI label                    ;即C = 0 && Z = 0
- 小于等于无符号数           BLS label                    ;即C = 1 && Z = 1
- 无溢出有符号数             BVC label                    ;即V = 0
- 有溢出有符号数             BVS label                    ;即V = 1
- 大于有符号数              BGT label                     ;即N = V && Z = 0,则跳转到label
- 大于等于有符号数           BGE label                     ;即N = V,则跳转到label
- 小于有符号数               BLT label                    ;即N != V,则跳转到label
- 小于等于有符号数           BLE label                     ;即N != V || Z = 1,则跳转到label
- 内存访问指令  ARM汇编采用RISC架构,CPU不能直接的读取内存,先将内存加载到通用寄存器再由CPU进行计算
- 读取 32位中会读4个字节,在64位中会读8个字节
```arm
    ldr r0,=0x12            ;将0x12赋值给r0
    ldr r0, .lable1         ;获得.lable1的地址,存储在r0之中
    ldr r0,[r3]             ;r0 = *r3
    ldr r0,[r3,#4]          ;r0 = *(r3 + 4)
    ldr r0,[r3,r2,LSL #2]   ;r0 = *(r3+(r2 << 2))
  • 存储 32位中会读4个字节,在64位中会读8个字节
      str R0, [R1], #8       ;将R0中的数据写入以R1为地址的存储器中,并将新地址R1+8写入R1
      str R0, [R1, #8]       ;将R0中的数据写入以R1+8为地址的存储器中
      str R1, [r0]            ;将r1寄存器的值,传送到地址值为r0的内存中
    
  • ldr与 str 指令的后缀与变种在32位中会读4个字节 在64位中会读8个字节
  • ldrb 读一个字节
  • ldrh 读两个字节
  • ldm 批量读
  • strb 写入一个字节
  • strh 写入两个字节
  • stm 批量写

堆栈的指令组合后缀 四种栈的类型:空栈:栈指针指向空位,每次存入时可以直接存入然后栈指针移动一格;而取出时需要先移动一格才能取出。满栈:栈指针指向栈中最后一格数据,每次存入时需要先移动栈指针一格再存入;取出时可以直接取出,然后再移动栈指针。增栈:栈指针移动时向地址增加的方向移动的栈。减栈:栈指针移动时向地址减小的方向移动的栈。 针对此衍生出了8种后缀:ia(increase after:先传输,再地址+4ib(increase before: 先地址+4,再传输da(decrease after: 先传输,再地址-4db(decrease befor: 先地址-4,再传输fd(full decrease):满递减堆栈ed(empty decrease):空递减堆栈fa:满递增堆栈ea:空递增堆栈 在理解的时候我们就可以将其拆开理解,比如LDMIA 就是LDM IA 两部分理解一些例子: 从地址 r0 开始读取 4 个字(word),分别存储到 r1-r4 寄存器中,然后将地址 r0 增加 4。LDMIA r0!, {r1-r4}

将地址 r0 增加 4,然后从新地址开始读取 3 个字,分别存储到 r1-r3 寄存器中。LDMIB r0, {r1-r3}

从地址 r0 开始读取 2 个字,分别存储到 r1-r2 寄存器中,然后将地址 r0 减少 4,并将读取的最后一个字存储到 lr 寄存器中。STMFD sp!, {r1-r3, lr}

将 r1-r3 寄存器中的数据存储到地址 sp 指向的存储器中,然后将 sp 减少 16(4 个字),并将 lr 寄存器中的数据存储到新的地址 sp 指向的存储器中。LDMDB r0!, {r1-r2, lr}

指令中!的作用 一般的,当感叹号 “!” 出现在寄存器名称后面时,就表示在执行指令后会更新该寄存器的值。如果没有!,表示执行指令前不更新该寄存器的值。ldmia r0, {r2 - r3} ldmia r0!, {r2 - r3}

这里感叹号的作用就是r0的值在ldm过程中发生的增加或者减少最后写回到r0去,第二句的ldm时会改变r0的值,而第一句,没有!,运行之后不会更新 r0 的值。 指令中^的作用 ^的作用:在目标寄存器中有pc时,会同时将spsr写入到cpsr,一般用于从异常模式返回。ldmfd sp!, {r0 - r6, pc} ldmfd sp!, {r0 - r6, pc}^

汇编不同写法,对于寄存器的改变(先算括号) 偏移量方法LDR R0,[R1, #4]

实现的操作:r0= *(r1 + 4) 事先更新LDR R0,[R1, #4]!

实现的操作:r0 = *(r1 + 4)r1 = r1 +4 事后更新:LDR R0,[R1], #4

实现的操作:r0=*r1 r1=r1+4