AI 移动端框架常用指令·汇总(待续)

2018-08-09  本文已影响458人  十曰立

卷积操作常用的汇编指令(NEON)

前言

首先我们要知道,ios的芯片虽然是ARM内核的,但是后来慢慢地魔改已经跟公版的ARM有所区别了,因此其对应配套的汇编规范也就有些细微的差别了:

arm32 函数调用约定

arm64函数调用约定

ios函数调用约定

因此安卓端的汇编跟iOS端的汇编就得区别对待了!

1. Android端32bit和64bit汇编的区别

先大概看下arm下SIMD汇编指令语法的区别在哪!

数据来源:《Arm® Compiler Version 6.6 armasm User Guide》

1.1 GCC内联汇编模板

模板就是这个样子

__asm__ [__volatile__] (assembler template
    : [output operand list]                          /* optional */
    : [input operand list]                          /* optional */ 
    : [clobbered register list]                       /* optional */ 
    );

真实的例子就是这样:

    asm volatile(
    "0:                         \n"

        "vdup.16 q0, r0         \n"
        "add %0, %0, #10        \n"
        "vst1.s16   {q0}, [%0]  \n"
    :
    "=r"(src) // %0
    :
    "0"(src), //
    "w"(zxl)  //%2
    :
    "cc", "memory", "q0", "r0", "r1"
    );

1.2 armV8和armV7指令集

armV7指令集主要是针对32bit的,armV8指令集则是针对最新的64bit架构;

首先总体来说,v7指令集在操作Q寄存器时,指令喜欢带个V表示,我这是在操作NEON寄存器,而v8的ISA64指令集就把这个给取消了,我只出指令,具体的什么操作细节,你操作数去细化指明。

1.3 预取

v7这样用:从%3处预取192个byte;

  "pld        [%3, #192]          \n"

v8: pld1kepp这个参数是可以改的,改为预取到L2中,不keep,而是流式缓存,也就是不会真放进cache中,具体的可以去看芯片手册。

 "prfm   pldl1keep, [%1, #512]       \n"

1.4 内存加载

** V7: **

"vld1.f32   {q10}, [%3]!        \n"
"vld1.s16 {q0, q1}, [%2]!       \n" 

v8:

"ld1    {v0.4s, v1.4s, v2.4s, v3.4s}, [%2], #64 \n"
"ld1    {v0.8h, v1.8h}, [%2], #32     \n"
"ld1    {v0.4h, v1.4h}, [%2], #32     \n"
"sqshrn  v8.4h, v4.4s, %15            \n" // output int16x4_t
"sqshrn2 v8.8h, v5.4s, %15            \n" // output int16x4_t

1.5 以占位符方式访问Q、D寄存器

关于这部分在NCNN github的wiki页面有详细的解释,可以去那看看哟!

V7:

 const float* bias = _bias;
 
 float32x4_t _bias0 = bias ? vld1q_f32(bias+g*4) : vdupq_n_f32(0.f);
"w"(_bias0)     // %23
"vand.f32     q12, %q23, %q23   \n"
  "vld1.s16 {q0, q1}, [%2]!       \n" // input int16x4_t
  "vmull.s16   q10,  %P6, d0[3]   \n" // 
int16x4_t _k4 = vld1_s16(k0 + 16);

 "vmlal.s16   q13, %P14, d3[3]   \n" //_k4 ==> %14

V8:

  "fmla   v28.4s, %16.4s, v21.s[3]       \n"
  int16x4_t _k0 = vld1_s16(k0     );
  "ld1    {v0.8h, v1.8h}, [%2], #32     \n" // input int16x4_t
  "smull   v4.4s, %6.4h, v0.h[7]        \n" 
  "w"(_k0),    // %6

1.6 小结

v7的浮点操作:
  "vmla.f32   q4, %q6, d0[0]   \n" // q0.s[0]

v8的浮点操作:
  "fmla   v4.4s, %6.4s, v0.s[0]       \n"
  
v7的int16操作:
 "vmull.s16   q10,  %P6, d0[0]   \n" // v0.h[0]
 
v8的int16操作:
 "smull   v4.4s, %6.4h, v0.h[0]        \n" 

2. ios端汇编

In general, iOS adheres to the generic ABI specified by ARM for the ARM64 architecture. However there are some choices to be made within that framework, and some divergences from it. This document describes these issues.

官网的一段申明,指明虽然继承自公版,但其与公版ARM架构仍有所区别;

具体的去这里看吧!ios函数调用约定

2.1 汇编文件

我们先看下如何写汇编文件,并在c++中如何调用它吧~

先大概了解下外围!

这个作者讲的很细致!

知乎专栏

这个作者也讲的很细!

老外总是能把很繁琐的知识(汇编)讲的很简洁!
看看人家的副标题:Learn how to read assembly in iOS – a useful skill when debugging your code or diagnosing why a crash has occurred.

解释下什么是FP(Frame Point)寄存器:
通常在C程序编译过程中,所有函数的局部变量被分配在一个连续的存储区中,一般这个存储区是在堆栈中,这个连续的存储区称为这个函数的存储“帧”,它是通过一个指针访问的。

这里要注意的是Apple所采用的ARM汇编器遵循GNU Assembler规范。

其中,我们可以看到,汇编文件里的注释可以采用C语言标准的注释方式,也可以用C++标准的//注释方式。

GAS规范中表示,可以用.global或.globl来标注全局函数。

在Apple的Assembler中仅支持.globl函数名前要加下划线。

.arm表示后面的函数中的指令都是arm指令。而.thumb表示后面函数中的指令都是thumb或thumb-2指令。其中,如果一个函数是用thumb写的,那么必须用.thumb_func修饰,否则连接器在连接符号时会有问题。

另外,Apple LLVM汇编器中的条件预处理与C语言用的也几乎一样。可以使用#if、#else、#endif、#ifdef、#ifndef、#elif等等。另外,在架构标识上也统一使用了标准的架构标识符,比如:i386表示x86处理器架构;x86_64表示64位的x86处理器;arm表示ARM架构的处理器;arm64表示64位ARM架构处理器。

iOS中ARMv7以及ARM64下的ABI:

ARMv7中,对于通用寄存器,自己写的过程中需要保护R4、R5、R6、R7、R8、R9、R10、R11以及R14寄存器;NEON寄存器需要保存Q4、Q5、Q6、Q7寄存器。

ARM64模式下,通用寄存器X18、X30不能被使用。而需要被自己写的过程所保护的是:X19、X20、X21、X22、X23、X24、X25、X26、X27、X28、X29寄存器;而SIMD寄存器需要保护的是V8、V9、V10、V11、V12、V13、V14、V15。

堆栈的意义在于保存状态。

讲一个最简单的例子吧!如何在工程内添加及使用汇编文件:

.text   //申明是代码区
.align 4 //4字节对齐
.globl _asmTest //申明全局函数名(注:要下划线开头,在外面调用的时候就不需要了)


_asmTest:
// 单纯测试trn转置指令的;

    mov w9, #1
    mov v6.s[0], w9
    mov w9, #2
    mov v6.s[1], w9
    mov w9, #3
    mov v6.s[2], w9
    mov w9, #4
    mov v6.s[3], w9

    mov w9, #4
    mov v7.s[0], w9
    mov w9, #5
    mov v7.s[1], w9
    mov w9, #6
    mov v7.s[2], w9
    mov w9, #7
    mov v7.s[3], w9

    trn1 v10.4s, v6.4s, v7.4s
    trn2 v11.4s, v6.4s, v7.4s

RET
    __asm volatile (
                    "mov w9, #1               \n"
                    "mov v6.s[0], w9          \n"
                    "mov w9, #2               \n"
                    "mov v6.s[1],  w9         \n"
                    "mov w9, #3               \n"
                    "mov v6.s[2],  w9         \n"
                    "mov w9, #4               \n"
                    "mov v6.s[3],  w9         \n"

                    "mov w9, #5               \n"
                    "mov v7.s[0], w9          \n"
                    "mov w9, #6               \n"
                    "mov v7.s[1],  w9         \n"
                    "mov w9, #7               \n"
                    "mov v7.s[2],  w9         \n"
                    "mov w9, #8               \n"
                    "mov v7.s[3],  w9         \n"

                    "trn1 v10.4s, v6.4s, v7.4s\n"
                    "trn2 v11.4s, v6.4s, v7.4s\n"
                    :
                    :
                    : "cc", "memory"
    );

2.2 翻译下老外的篇吧!讲的挺好的!

(汇编)讲的挺简洁!

Calling Conventions讲的是汇编函数调用的规范,用来弄出一个统一的标准。

······
······
······

image

哈哈~发现讲的很基础了!还是不翻译了!你们自己去看吧!

三、 实例

下面是pooling汇编实现的kernel部分,里面有arch32跟arch64的对应版本,对比着看就很明显了:

上一篇 下一篇

猜你喜欢

热点阅读