ARM(Android NDK)混编C/C++汇编优化

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/feelinghappy/article/details/87101555

在进行Android开发时,一般考虑加速某些算法处理速率时,需要使用NDK进行开发,

为了进一步加速程序执行速率,还可以进行汇编级别的优化。

比如采用 NEON 技术进行代码的优化,以实现模块处理效率的成倍增长。

在C/C++中使用内联汇编的用法如下:

asm(
"ADD R0,R0,#1 \n\t"       // 使R0寄存器值增加1,后面加换行符和制表符是为了汇编代码的美观,其中有多条指令时 换行符是必须的
"SUB R0,R0,#1 "           // 使R0寄存器的值减少1
);

参考的文章有如下几篇,可能跟我的平台有所不同,所以我都不能完全照搬使用:

http://blog.csdn.net/wuzhong325/article/details/8277703

      读了一些网上找的文章,所讲述的内容大体比较零散,我在此做一个整理,方便后来者的学习和使用。

       开发Arm程序的时候,大多数时候使用C/C++语言就可以了,但汇编语言在某些情况下能够实现一些C语言无法实现的功能,这时候就要调用一些汇编语言的程序.我们需要大概了解一下在C语言中如何嵌入汇编语言.
    1.内嵌汇编语言的语法:

    __asm
    {
        指令[;指令]  
        ......
        [指令]
    } 

    2.举例:使能/禁止IRQ中断

    __inline void enable_IRQ(void)
    {
        int tmp;
        __asm                        //嵌入汇编代码
        {
            MRS  tmp,CPSR            //读取CPSR的值
            BIC  tmp,tmp,#0x80       //将IRQ中断禁止位I清零,即允许IRQ中断
            MSR  CPSR_c,tmp          //设置CPSR的值
        }
    }
    __inline void disable_IRQ(void)
    {
        int tmp;
        __asm
        {
            MRS  tmp,CPSR
            ORR  tmp,tmp,#Ox80
            MSR  CPSR_c,tmp
        }
    } 
 

    3.举例:字符串复制

    void my-strcpy(const char *src,char *dst)
    {
        int ch;
        __asm
        {
        loop:
    #ifndef __thumb
        LDRB ch,[src],#1
        STRB ch,[dst],#1
    #else
        LDRB ch,[src]
        ADD  src,#1
        STRB ch,[dst]
        ADD  dst,#1
    #endif
        CMP ch,#0
        BNE loop
        }
    }
 
    int main(void)
    {
        const char *a="Hello world!";
        char b[20];
        __asm
        {
             MOV  R0,a
             MOV  R1,b
             BL my_strcpy,{R0,R1}
        }
        return(0);
    }


    4.内嵌汇编的指令用法:

    .操作书: 内嵌的汇编指令中作为操作数的寄存器和常量可以是C表达式.这些表达式可以是char,short或int等类型,而且这些表达式都是作为无符号数进行操作的.若要有符号数,用户需要自己处理与符号有关的操作.编译器将会计算这些表达式的值,并为其分配寄存器.
    .物理寄存器:内嵌汇编中使用物理寄存器是有限制的:
        _ 不能直接向PC(程序计数器)寄存器中赋值,程序跳转只能通过B或BL指令来实现.
        _ 使用物理寄存器的指令中,不要使用过于复杂的C表达式
        _ 尽可能少的使用物理寄存器
    .常量: 在内嵌汇编指令中,常量前面的"#"可以省略
    .标号: C程序中的标号可以被内嵌的汇编指令使用.但是只有指令B可以使用C程序中的标号,而指令BL则不能使用.

    .内存单元的分配:所有内存分配均由C编译器完成,分配的内存单元通过变量供内嵌汇编器使用.内嵌汇编器不支持内嵌汇编程序中用于内存分配的伪指令.    

5.内嵌汇编注意事项:

    .必须小心使用物理寄存器,如R0~R3,IP,LR,CPSR中的标志位,避免发生冲突.
    例如:
        __asm
        {
            MOV  R0,x
            ADD  y,R0,x/y
        }
        改成下面代码会比较妥当:
        __asm
        {
            MOV var,x
            ADD y,var,x/y
        }
    .不要使用寄存器代替变量.
    .使用内嵌汇编无需保存和恢复寄存器.事实上,除了CPSR,SPSR寄存器,对物理寄存器先读后写都会引起汇编报错.
    .汇编语言中","号作为操作数分隔符.如果有C表达式作为操作数,若表达式中包含有",",则必须使用()将其规约为一个汇编操作数,例如:

    __asm
    {
        ADD x,y,(f(),z)    //"f(),z"为带有","的C表达式.
    }
    6.不同数据类型对应的汇编指令:
    unsigned char    LDRB/STRB
    unsigned short   LDRH/STRH
    unsigned int       LDR/STR
    char                   LDRSB/STRSB
    short                  LDRSH/STRSH 
    7.访问C程序的变量:
    AREA    globals,CODE,READONLY
    EXPORT  asmsubroutine
    IMPORT  globalvar        ;声明的外部变量
asmsubroutine
    LDR  R1,=blobalval
    LDR  R0,[R1]
    ADD  R0,R0,#1
    STR  R0,[R1]
    MOV  PC,LR
    END


http://blog.csdn.net/wuzhong325/article/details/8277718

一、格式
    asm volatile (“asm code”:output:input:changed);    //必须以‘;’结尾,不管有多长对C都只是一条语句
    asm                 内嵌汇编关键字
    volatile            告诉编译器不要优化内嵌汇编,如果想优化可以不加
    ANSI C规范的关键字:    (ANSI C把asm用于其它用途,不能用于内嵌汇编语句,GCC可以)
        __asm__ 
        __volatile__        //前面和后面都有两个下划线,它们之间没有空格 
    如果后面部分没有内容,‘:’可以省略,前面或中间的不能省略‘:’
    没有asm code也不可以省略‘“”’,没有changed必须省略‘:’
    
二、asm code
    
    asm code必须放在一个字符串内,但是字符串中间是不能直接按回车键换行的
    可以写成多个字符串,只要字符串之间不加任何符号编译完后就会变成一个字符串
         
"mov r0,r0\n\t"        //指令之间必须要换行,/t可以不加,只是为了在汇编文件中的指令格式对齐
"mov r1,r1\n\t"
"mov r2,r2"
  
    字符串内不是只能放指令,可以放一些标签、变量、循环、宏等等  
    还可以把内嵌汇编放在C函数外面,用内嵌汇编定义函数、变量、段等汇编有的东东,总之就跟直接在写汇编文件一样
    在C函数外面定义内嵌汇编时不能加volatile:output:input:changed
    注意:编译器不检查asm code的内容是否合法,直接交给汇编器
    
三、output(ASM --> C)和input(C --> ASM)
    1、    指定输出值
__asm__ __volatile__ (
        "asm code"
            :“constraint”(variable)
 );
// constraint定义variable的存放位置:
   r            使用任何可用的通用寄存器
   m            使用变量的内存地址
// output修饰符:
   +            可读可写
   =            只写
   &            该输出操作数不能使用输入部分使用过的寄存器,只能 +& 或 =& 方式使用


    2、    指定输入值
 __asm__ __volatile__ (
        "asm code"
        :
       :"constraint"(variable / immediate)
);
constraint定义variable / immediate的存放位置:
    r            使用任何可用的通用寄存器(变量和立即数都可以)
    m            使用变量的内存地址(不能用立即数)
    i             使用立即数(不能用变量)
    3、    使用占位符
            int a = 100,b = 200;
            int result;
//  注意:原文中引号是中文的,换行符也打印成了 /n/t,我在此作了替换。
// 下面的代码同样有类似的错误,我也进行了替换
__asm__ __volatile__ (
  "mov    %0,%3  \n\t"  // mov r3,#123           %0代表result,%3代表123(编译器会自动加 # 号)
  "ldr    r0,%1  \n\t"  // ldr r0,[fp, #-12]     %1代表 a 的地址
  "ldr    r1,%2  \n\t"  // ldr r1,[fp, #-16]     %2代表 b 的地址
  "str    r0,%2  \n\t"  // str r0,[fp, #-16]     因为%1和%2是地址所以只能用ldr或str指令
  "str    r1,%1  \n\t'  // str r1,[fp, #-12]     如果用错指令编译时不会报错,要到汇编时才会
  :“=r”(result),“+m”(a),“+m”(b) //  out1是%0,out2是%1,...,outN是%N-1
  :“i”(123)                        //   in1是%N,in2是%N+1,...
);
  4、引用占位符
   int num = 100;
 __asm__ __volatile__ (
 "add    %0,%1,#100\n\t"
 : "=r"(a)
 : "0"(a)         //"0"是零,即%0,引用时不可以加 %,只能input引用output,
);                //引用是为了更能分清输出输入部分
   5、    & 修饰符
  int num;
  __asm__ __volatile__ (                //mov     r3, #123            //编译器自动加的指令
        "mov    %0,%1\n\t"            //mov     r3,r3                //输入和输出使用相同的寄存器
     : "=r"(num)
      : "r"(123)
   );
   
   int num;
__asm__ __volatile__ (                //mov     r3, #123
  "mov    %0,%1\n\t"            //mov     r2,r3                //加了&后输入和输出的寄存器不一样了
  : "=&r"(num)                    //mov     r3, r2                //编译器自动加的指令
  : "r"(123)
);
   
四、changed   
    告诉编译器你修改过的寄存器,编译器会自动把保存这些寄存器值的指令加在内嵌汇编之前,再把恢复寄存器值的指令加在内嵌汇编之后   
void test()                                  test:
{                                                str     fp, [sp, #-4]!
       __asm__ __volatile__ (                    add     fp, sp, #0
           "mov    r4,#123"                      mov     r4,#123
     );                                          add     sp, fp, #0
 }                                               ldmfd   sp!, {fp}
                                                 bx      lr
                                                                             
void test()                              test:
   {                                             stmfd   sp!, {r4, fp}
      __asm__ __volatile__ (                     add     fp, sp, #0
         "mov    r4,#123"                        mov     r4,#123
            :                                    add     sp, fp, #0
                :                                ldmfd   sp!, {r4, fp}
                 :"r4"                           bx      lr
           );
}
    汇编的第 2 行与第 6 行没有保存和恢复 R4(R4是通用寄存器变量必须保护,见APCS),第 10 行与第 14 行有保存和恢复 R4
    如果修改了没有在输入或输出中定义的任何内存位置,必须在changed列表里加上“memory”
http://blog.chinaunix.net/uid-20715001-id-1234726.html

 在嵌入式系统开发中,目前使用的主要编程语言是C 和汇编,虽然C++已经有相应的编译器,但是现在使用还是比较少的。
    在稍大规模的嵌入式程序设计中,大部分的代码都是用C来编写的,主要是因为C语言具有较强的结构性,便于人的理解,并且具有大量的库支持。但对于一写硬件上的操作,很多地方还是要用到汇编语言,例如硬件系统的初始化中的CPU 状态的设定,中断的使能,主频的设定,RAM控制参数等。另外在一些对性能非常敏感的代码块,基于汇编与机器码一一对应的关系,这时不能依靠C编译器的生成代码,而要手工编写汇编,从而达到优化的目的。汇编语言是和CPU的指令集紧密相连的,作为涉及底层的嵌入式系统开发,熟练对应汇编语言的使用也是必须的。
    单纯的C或者汇编编程请参考相关的书籍或者手册,这里主要讨论C和汇编的混合编程,包括相互之间的函数调用。下面分四种情况来进行讨论,不涉及C++语言。

一、在C语言中内嵌汇编
    在C中内嵌的汇编指令包含大部分的ARM和Thumb指令,不过使用与单纯的汇编程序使用的指令略有不同,存在一些限制,主要有下面几个方面:
    a 不能直接向PC 寄存器赋值,程序跳转要使用B或者BL指令;
    b 在使用物理寄存器时,不要使用过于复杂的C表达式,避免物理寄存器冲突;
    c R12和R13可能被编译器用来存放中间编译结果,计算表达式值时可能把R0-R3、R12及R14用于子程序调用,因此避免直接使用这些物理寄存器;
    d 一般不要直接指定物理寄存器;
    e 让编译器进行分配内嵌汇编使用的标记是__asm或asm关键字,用法如下:

__asm{instruction [; instruction]}或 asm("instruction[; instruction]")。
    下面是一个例子来说明如何在C中内嵌汇编语言:


//C语言文件*.c http://hi.baidu.com/procatlaw/
#include <stdio.h>
void my_strcpy(const char *src, char *dest){
char ch;
       __asm{
              loop:
                     ldrb ch, [src], #1
                     strb ch, [dest], #1
                     cmp ch, #0
                     bne loop
       }
}
int main(){
       char *a="forget it and move on!";
       char b[64];
       my_strcpy(a, b);
       printf("original: %s", a);
       printf("copyed: %s", b);
       return 0;

在此例子中C语言和汇编之间的值传递是用C语言的指针来实现的,因为指针对应的是地址,所以汇编中也可以访问。

二、在汇编中使用C定义的全局变量
    内嵌汇编不用单独编辑汇编语言文件,比较简洁,但是有很多的限制。当汇编的代码较多时一般放在单独的汇编文件中,这时就需要在汇

编文件和C文件之间进行一些数据的传递,最简便的办法就是使用全局变量。


//C语言文件*.c http://hi.baidu.com/procatlaw/
#include <stdio.h>
int gVar=12;
extern asmDouble(void);
int main(){
       printf("original value of gVar is: %d", gVar_1);
       asmDouble();
       printf(" modified value of gVar is: %d", gVar_1);
       return 0;

    下面是一个C语言和汇编语言共享全局变量的例子:
;汇编语言文件*.S http://hi.baidu.com/procatlaw/
       AREA asmfile, CODE, READONLY EXPORT asmDouble
       IMPORT gVar
asmDouble
       ldr r0, =gVar
       ldr r1, [r0]
       mov r2, #2
       mul r3, r1, r2
       str r3, [r0]
       mov pc, lr
       END

    在此例中,汇编文件与C文件之间相互传递了全局变量gVar和函数asmDouble,留意声明的关键字extern和IMPORT

三、在C中调用汇编的函数
    有一些对机器要求高的敏感函数,通过C语言编写再通过C编译器翻译有时会出现误差,因此这样的函数一般采用汇编语言来编写,然后供C

语言调用。在C文件中调用汇编文件中的函数,要注意的有两点,一是要在C文件中声明所调用的汇编函数原型,并加入extern关键字作为引入

函数的声明;二是在汇编文件中对对应的汇编代码段标识用EXPORT关键字作为导出函数的声明,函数通过mov pc, lr指令返回。这样,就可以

在C文件中使用该函数了。从C语言的角度的角度,并不知道调用的函数的实现是用C语言还是汇编汇编语言,原因C语言的函数名起到表明函数

代码起始地址的作用,而这个作用和汇编语言的代码段标识符是一致的。
    下面是一个C语言调用汇编函数例子:

//C语言文件*.c http://hi.baidu.com/procatlaw/
#include <stdio.h>
extern void asm_strcpy(const char *src, char *dest);
int main(){
       const char *s="seasons in the sun"; char d[32];
       asm_strcpy(s, d);
       printf("source: %s", s);
       printf(" destination: %s",d);
       return 0;
}
 
;汇编语言文件*.S http://hi.baidu.com/procatlaw/
       AREA asmfile, CODE, READONLY
       EXPORT asm_strcpy
asm_strcpy
       loop
       ldrb r4, [r0], #1
       cmp r4, #0
       beq over
       strb r4, [r1], #1
       b loop
       over
       mov pc, lr
       END 


    在此例中,C语言和汇编语言之间的参数传递是通过对应的用R0-R3来进行传递,即R0传递第一个参数,R1传递第二个参数,多于4个时借助栈完成,函数的返回值通过R0来传递。这个规定叫作ATPCS(ARM Thumb Procedure Call Standard),具体见ATPCS规范。

四、在汇编中调用C的函数 
    在汇编语言中调用C语言的函数,需要在汇编中IMPORT对应的C函数名,然后将C的代码放在一个独立的C文件中进行编译,剩下的工作由连接器来处理。
    下面是一个汇编语言调用C语言函数例子:

//C语言文件*.c http://hi.baidu.com/procatlaw/
 
int cFun(int a, int b, int c){
       return a+b+c;
}
 
;汇编语言文件*.S http://hi.baidu.com/procatlaw/
       AREA asmfile, CODE, READONLY
       EXPORT cFun
start
       mov r0, #0x1
       mov r1, #0x2
       mov r2, #0x3
       bl cFun
       nop
       nop
       b start
       END 

    在汇编语言中调用C语言的函数,参数的传递也是按照ATPCS规范来实现的。
    在这里简单介绍一下部分ATPCS规范:

    子程序间通过寄存器R0~R3来传递参数。 
    A.在子程序中,使用寄存器R4~R11来保存局部变量。 
    B.寄存器R12用于子程序间scratch寄存器(用于保存SP,在函数返回时使用该寄存器出桟),记作IP。 
    C.寄存器R13用于数据栈指针,记作SP。寄存器SP在进入子程序时的值和退出子程序时的值必须相等。  
    D.寄存器R14称为链接寄存器,记作LR。它用于保存子程序的返回地址。
    E.寄存器R15是程序计数器,记作PC 
    F.参数不超过4个时,可以使用寄存器R0~R3来传递参数,当参数超过4个时,还可以使用数据栈来传递参数。
    G.结果为一个32位整数时,可以通过寄存器R0返回 
    H.结果为一个64位整数时,可以通过寄存器R0和R1返回,依次类推。

    以上通过几个简单的例子演示了嵌入式开发中常用的C 和汇编混合编程的一些方法和基本的思路,其实最核心的问题就是如何在C 和汇编之间传值,剩下的问题就是各自用自己的方式来进行处理。以上只是抛砖引玉,更详细和复杂的使用方法要结合实际应用并参考相关的资料。
--------------------- 
作者:luofl_ 
来源:CSDN 
原文:https://blog.csdn.net/luofl1992/article/details/8756423 
版权声明:本文为博主原创文章,转载请附上博文链接!

猜你喜欢

转载自blog.csdn.net/feelinghappy/article/details/87101555
今日推荐