ARM 内联汇编-1

转自:(72条消息) 10、 ARM 内联汇编学习笔记_天昼AI实验室的博客-CSDN博客_armv8 内联汇编

基本思想:随手记录一下ARM的内联汇编的基础语法,以便更深入的学习NCNN源码~

ARM GCC Inline Assembler Cookbook 参考官网

(1)、基本的汇编语法结构为

asm volatile (
code  代码列表
: output operand list 输出运算符列表
: input operand list 输入运算符列表
: clobber list 被更改资源列表
);
 


 
或者也可以写成这样,因为头文件做了宏定义asm volatile

__asm__ __volatile__ (
code  代码列表
: output operand list 输出运算符列表
: input operand list 输入运算符列表
: clobber list 被更改资源列表
);


(2-1)volatile 表示关键字表示不做任何优化处理;

(2-2) 代码列表,可以写多条指令指令格式如下:

ARMV7 :Documentation – Arm Developer

ARMV7架构包含:



16个通用寄存器(32bit),R0-R15
16个NEON寄存器(128bit),Q0-Q15(同时也可以被视为32个64bit的寄存器,D0-D31)
16个VFP寄存器(32bit),S0-S15

 

NEON和VFP的区别在于VFP是加速浮点计算的硬件不具备数据并行能力,同时VFP更尽兴双精度浮点数(double)的计算,NEON只有单精度浮点计算能力

V{<mod>}<op>{<shape>}{<cond>}{.<dt>}{<dest>}, src1, src2

<mod> 修饰符 (Q, H, D, R)

Q  该指令常用在饱和算法中,如果运算结果发生饱和(超出数据类型,导致溢出),这些状态将被FPSCR寄存器记录,则将结果自动截断,使其避免溢出。VQADD 就是这种指令的一个例子(可通过符号寄存器看出)

H  该指令将使结果减半。它通过向右移动一个位置(实际上是被截断的二分之一)来做到这一点。 VHADD 就是这种指令的一个例子——它可以用来计算两个输入的平均值。

D The instruction doubles the result and saturates. This is commonly required when multiplying numbers in Q15 format, where an additional doubling is required to get the result into the correct form.

R 该指令结果进行四舍五入处理,也就是向上取整操作,否则就将结果数据截断. VRHADD 就是这种指令.

<op> - 操作运算符(如, ADD, SUB, MUL等)

<shape> - Shape (L, W or N, as described in NEON registers)

L 长指令对双字向量操作数执行运算,并生成四字向量结果。 所生成的元素通常是操作数元素宽度的两倍,并属于同一类型。通过将 L 追加到指令助记符来指定长指令。

W 宽指令对一个双字向量操作数和一个四字向量操作数执行运算。 此类指令生成四字向量结果。 所生成的元素和第一个操作数的元素是第二个操作数元素宽度的两倍。通过将 W 追加到指令助记符来指定宽指令。

N 窄指令对四字向量操作数执行运算,并生成双字向量结果。 所生成的元素通常是操作数元素宽度的一半。通过将 N 追加到指令助记符来指定窄指令

<cond> - 条件代码

<.dt> - 数据类型

<dest> - 目的操作数

<src1> - 源操作数 1

<src2> - 源操作数 2.

其中ARM架构的CSPR寄存器中的关键几位N,Z,C,V与8086架构EFLAG中的NF ,SF ,ZF ,CF,OF相对应,主要是用于汇编计算中,使用源操作数计算的目标结果的状态记录和压栈、出栈、跳转状态保存和恢复


ARMV8 移动端arm cpu优化学习笔记第4弹--内联汇编入门 - 知乎

 

Arm v8-A AArch64架构

有31个64位通用目的寄存器,每一个通用寄存器具有64位(X0-X30)或是32位模式(W0-W30)

有32个128位寄存器,也能当作32位Sn寄存器或是64位Dn寄存器使用。

{<prefix>}<op>{<suffix>} Vd.<T>, Vn.<T>, Vm.<T>
这里:
<prefix>——前缀,如S/U/F/P 分别表示 有符号整数/无符号整数/浮点数/布尔数据类型
<op>——操作符。例如ADD,AND等。
<suffix>——后缀,通常是有以下几种

P:将向量按对操作,例如ADDP
V:跨所有的数据通道操作,例如FMAXV
2:在宽指令/窄指令中操作数据的高位部分。例如ADDHN2,SADDL2。
ADDHN2:两个128位矢量相加,得到64位矢量结果,并将结果存到NEON寄存器的高64位部分。
SADDL2: 两个NEON寄存器的高64位部分相加,得到128-位结果。
<T> ——数据类型,通常是8B/16B/4H/8H/2S/4S/2D等。B代表8位数据类型;H代表16位数据宽度;S代表32位数据宽度,可以是32位整数或单精度浮点;D代表64位数据宽度,可以是64位整数或双精度浮点。

arm_neon.h 支持的操作,如果每行指令后面追加“\n\t”,只是为了将neon assembly生成汇编比较美观一些


注)、上表指令后面可以追加一些后缀,比如"B", "H"和"W"分别表示从给定的内存地址依次取1个字节(8位),2个字节和4个字节

 

<高位>32                                             16                               8                       <低位>

4) )约束字段格式 详细见下表 ARM GCC Inline Assembler Cookbook

(2-3)输出运算列表

以逗号分隔,可以写多条指令格式  [助记符名] “约束条件”(变量名)

(2-4)输入运算列表 ARM GCC Inline Assembler Cookbook

1) %0 表示输入运算符列表和输出运算符列表中的第一个值,如果没有输出列表,只有输入列表,那就代表输入列表的值。反之依然如此。如果都有,则依次排之 %0 %1 %2...

2) [{,:}] 指定特定的寄存器,取寄存器里面的内容 指令的寄存器内部存放的是地址 ==>[地址]=内容

3) [{,:}]! 指定特定的寄存器,取寄存器里面的下一个位置内容  ==>[地址]!=下一个内容 

4) {}表示待传送的寄存器列表

5)"!"是表示寄存器自增/自减的

例如:vld1.8 {q1},[r1]!      @v 从r1里面取出第二个参数(v)放到q1寄存器

Constraint    Usage in ARM state    Usage in Thumb state
f    浮点寄存器 f0 .. f7    Not available
h    Not available    Registers r8..r15
G    立即数(浮点数形式)    Not available
H    Same a G, but negated    Not available
I    数据处理指令中的立即数, #operand    Constant in the range 0 .. 255
e.g. SWI operand
J    Indexing constants -4095 .. 4095
e.g. LDR R1, [PC, #operand]    Constant in the range -255 .. -1
e.g. SUB R0, R0, #operand
K    Same as I, but inverted    Same as I, but shifted
L    Same as I, but negated    Constant in the range -7 .. 7
e.g. SUB R0, R1, #operand
l    Same as r    Registers r0..r7
e.g. PUSH operand
M    使用一个内存操作数,内存地址可以是机器支持的范围内    Constant that is a multiple of 4 in the range of 0 .. 1020
e.g. ADD R0, SP, #operand
m    Any valid memory address
N    Not available    一个确定值的立即数,范围一般限制在 0 .. 31
e.g. LSL R0, R1, #operand
O    Not available    使用一个内存操作数,但是要求内存地址范围在在同一段内。例如,加上一个小的偏移量来形成一个可用的地址
r    通用寄存器R0~R15 ,使用r字段可以任意选择    Not available
w    向量寄存器 s0 .. s31    Not available
X    被修饰的操作符只能作为输出
5) 约束字段的修饰符

修饰符    说明
无    被修饰的操作符是只读的
=    被修饰的操作符只写
+    被修饰的操作符具有可读写的属性
&    被修饰的操作符只能作为输出
0    被修饰的操作符既可以作为输入也可以作为输出

6) # 表示立即数   

例如:: [temp] "=r" (tmp)  //输出列表

(2-5)约束列表

:一般是"cc", "memory"开头,然后接着填内联汇编中用到的通用寄存器和向量寄存器

1) "cc"表示内联汇编代码运算过程中,会产生符号变化、数据溢出等问题,这些操作最终会修改了标志寄存器,;
2) "memory"表示汇编代码对输入和输出操作数涉及内存操作,ncnn代码使用arm neon预先将数据从内存拷贝到了寄存器中,这样写汇编指令就不涉及内存操作;

(2)、使用android studio测试ncnn-demo Aarch64 Mix Assembly And Intrinsic - Ncnn - DocsForge

ncnndemo-1 

float computeC(float a,float b,float c){
 
    return a+=b*c;
 
}
float computeNeon(float32_t   a,float32_t   b,float32_t  c){
 
    float32x4_t Aregister;
    float32x4_t Bregister;
    float32x4_t Cregister;
    Aregister = vld1q_f32(&a);
    Bregister = vld1q_f32(&b);
    Cregister = vld1q_f32(&c);
    Aregister = vmlaq_f32(Aregister,Bregister, Cregister);
    float32_t result=0;
    vst1q_f32(&result, Aregister);
    return result;
 
}
float computeAsm(float32_t const  a,float32_t const  b,float32_t const c){
 
    float32x4_t Aregister;
    float32x4_t Bregister;
    float32x4_t Cregister;
    Aregister = vld1q_f32(&a);
    Bregister = vld1q_f32(&b);
    Cregister = vld1q_f32(&c);
    asm volatile(
    "fmla  %0.4s, %2.4s, %3.4s"    //这个地方为啥不能写成v0.4s v2.4s v3.4s 还不是太明白
    :[Aregister0] "=w"(Aregister) // %0
    :[Aregister1] "0"(Aregister),
     [Bregister2] "w"(Bregister), // %2
     [Cregister3] "w"(Cregister) // %3
    :"cc","v0","v1","v2","v3"
    );
    float32_t result=0;
    vst1q_f32(&result, Aregister);
    return result;
}
 
void test() {
    float a=10;
    float b=20;
    float c=30;
    auto start_time=std::chrono::steady_clock::now();
    std::cout<<computeC(a,b,c)<<std::endl;
    auto end_time=std::chrono::steady_clock::now();
    std::cout<<std::chrono::duration<double>(end_time-start_time).count()<<"s"<<std::endl;
    LOGD("computeC %f\n",computeC(a,b,c));
    LOGD("computeC time  %d ms\n",std::chrono::duration<double>(end_time-start_time).count());
    start_time=std::chrono::steady_clock::now();
    std::cout<<computeNeon(a,b,c)<<std::endl;
    end_time=std::chrono::steady_clock::now();
    std::cout<<std::chrono::duration<double>(end_time-start_time).count()<<"s"<<std::endl;
    LOGD("computeNeon %f\n",computeNeon(a,b,c));
    LOGD("computeNeon time  %d ms\n",std::chrono::duration<double>(end_time-start_time).count());
    start_time=std::chrono::steady_clock::now();
    std::cout<<computeAsm(a,b,c)<<std::endl;
    end_time=std::chrono::steady_clock::now();
    std::cout<<std::chrono::duration<double>(end_time-start_time).count()<<"s"<<std::endl;
    LOGD("computeAsm %f \n",computeAsm(a,b,c));
    LOGD("computeAsm time  %d ms\n",std::chrono::duration<double>(end_time-start_time).count());
 
}
测试demo结果

$ adb shell am start -n "com.example.neon/com.example.neon.MainActivity" -a android.intent.action.MAIN -c android.intent.category.LAUNCHER
Connected to process 3247 on device 'rockchip-rk3399-8BPEH3RXVX'.
Capturing and displaying logcat messages from application. This behavior can be disabled in the "Logcat output" section of the "Debugger" settings page.
W/om.example.neo: Accessing hidden method Landroid/graphics/drawable/Drawable;->computeFitSystemWindows(Landroid/graphics/Rect;Landroid/graphics/Rect;)Z (light greylist, reflection)
W/om.example.neo: Accessing hidden method Landroid/view/ViewGroup;->makeOptionalFitsSystemWindows()V (light greylist, reflection)
W/om.example.neo: Accessing hidden method Landroid/widget/TextView;->getTextDirectionHeuristic()Landroid/text/TextDirectionHeuristic; (light greylist, linking)
D/TAG: NO 
D/TEST_NEON: computeC 610.000000
    computeC time  3 s
    computeNeon 610.000000
    computeNeon time  3 s
    computeAsm 610.000000 
    computeAsm time  3 s
D/OpenGLRenderer: Skia GL Pipeline
 算基本入门了~ 开始继续刷ncnn源码 谢谢 @zz大佬、@up、@白学家 解惑

参考:

移动端arm cpu优化学习笔记第4弹--内联汇编入门 - 知乎

Arm NEON programming quick reference guide - Operating Systems blog - Arm Community blogs - Arm Community

ARM NEON指令集总结_JabamiLight的博客-CSDN博客_neon指令集

ARM GCC Inline Assembler Cookbook

ARM NEON 编程系列2 - 基本指令集 - *神气* - 博客园

ARM GCC中内联汇编语法_旭旭旭旭旭的博客-CSDN博客

Armv7 Mix Assembly And Intrinsic - Ncnn - DocsForge

arm汇编基础 - 知否 | nop

ARM汇编语言 - 简介 [一]-电子工程世界

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值