https://developer.arm.com/
http://infocenter.arm.com
32位ARM优化可以参考文档:
https://developer.arm.com/products/architecture/a-profile/docs/ddi0406/latest/arm-architecture-reference-manual-armv7-a-and-armv7-r-edition
64位ARM优化可以参考文档:
https://developer.arm.com/products/architecture/a-profile/docs/ddi0487/latest/arm-architecture-reference-manual-armv8-for-armv8-a-architecture-profile
其他资源(均来源于http://infocenter.arm.com):
RealView编译工具《编译器参考指南》:
http://infocenter.arm.com/help/topic/com.arm.doc.dui0348bc/DUI0348BC_rvct_comp_ref_guide.pdf
RealView编译工具《汇编器指南》:
http://infocenter.arm.com/help/topic/com.arm.doc.dui0204ic/DUI0204IC_rvct_assembler_guide.pdf
RealView编译工具《链接器用户指南》:
http://infocenter.arm.com/help/topic/com.arm.doc.dui0206ic/DUI0206IC_rvct_linker_user_guide.pdf
https://developer.arm.com/technologies/neon/intrinsics
http://infocenter.arm.com/help/topic/com.arm.doc.ihi0073a/IHI0073A_arm_neon_intrinsics_ref.pdf
http://infocenter.arm.com/help/topic/com.arm.doc.ihi0073b/IHI0073B_arm_neon_intrinsics_ref.pdf
ARM是三大CPU架构(X86、ARM、MIPS)之一,是精简指令集计算机的代表,其功耗低、功能强的特点使之广泛应用于移动设备。目前常见的是ARM32和ARM64,下面对其分别简单介绍:
(1)ARM32以ARMv7为主要架构,是32位的,常见的设备有:iphone5、Cortex-A15;
(2)ARM64以ARMv8为主要架构,是64位的,常见的设备有:Cortex-A53、Cortex-A57、iphone5s的A7、iphone6和iphone6Plus的A8等。
Thumb指令、ARM指令和NEON指令的区别和联系:Thumb指令是ARM指令中一种16位的指令集,具有16bit的代码密度;ARM指令集包含ARM32和ARM64,分别适用于ARM32位优化和ARM64位优化;NEON指令集同样包含NEON32和NEON64,分别适用于ARM32位优化和ARM64位优化。不管ARM指令集还是NEON指令集,虽然都包含适用于ARM32位优化和ARM64位优化的指令集,但是它们的指令名称、个数和使用方法均是不一样的,需要特别注意,尤其是在ARM32位优化改写为ARM64位优化时。
ARM32位寄存器共有16个(R0~R15),均是32位宽的,ARM32遵循ATPCS(ARM-THUMB procedure call standard,ARM-Thumb过程调用标准)规则:
a. R0~R3寄存器用于子程序间传递参数,当参数大于4时,将多余的参数用数据栈进行传递,入栈的顺序与参数顺序恰好相反,被调用子程序返回前无需恢复寄存器R0~R3的值,也就是说R0~R3无需出栈和入栈;
b. R4~R11寄存器用于子程序间保存变量,这些寄存器在子程序进入时必须保存,返回时必须恢复,也就是说R4~R11使用时必须出栈和入栈;
c. R13是堆栈指针寄存器SP(在进入子程序时的值和退出子程序时的值必须相等),R14是链接寄存器LR,R15是一个程序计数器PC;
d. R12时一般的通用寄存器,使用时不需要保存;
e. 子程序返回32位的整数,使用R0返回;返回64位的整数时,使用R0返回低位、R1返回高位。
NEON32位寄存器主要包含如下寄存器,由于NEON寄存器是有重叠部分物理地址的,所以使用时需要特别注意:
32个单字(32bits)的S寄存器(S0~S31);
32个双字(64bits)的D寄存器(D0~D31);
16个四字(128bits)的Q寄存器(Q0~Q15)。
neon的S/D/Q寄存器之间的关系举例如下:
S0 | S1 | S2 | S3 | S4 | S5 | S6 | S7 | S8 | S9 | S10 | S11 | S12 | S13 | S14 | S15 |
D0 | D1 | D2 | D3 | D4 | D5 | D6 | D7 |
Q0 | Q1 | Q2 | Q3 |
在使用neon32位寄存器时,如果用到d8~d15寄存器,需要先入栈vpush {d8~d15},使用完后要出栈vpop {d8~d15};若对应于S寄存器,则是S16~S31;对应于Q寄存器,则是Q4~Q7。
假设我们有如下三个文件:
arm32_opt_c.c :待优化的c源码和demo模板
arm32_opt_arm.s :arm32汇编优化源码
makefile :优化模板的makefile,编译出可执行文件测试出汇编性能提升倍数
arm32_opt_c.c内容如下:
#include
#incldue
#include
#include
#include
extern void arm32_opt_arm(unsigned short *output_asm,unsigned short *input,int width,int height,int blocksize,int stride);
void arm32_opt_c(unsigned short *output_c,unsigned short *input,int width,int height,int blocksize);
{
int x,y;
for(x=0;x
arm32_opt_arm.s内容如下:
.text
.arm
.align 4
.global arm32_opt_asm
arm32_opt_asm:
PUSH {R4-R12,LR} @ 入栈
ADD R11,SP,#40
LDM R11,{R4,R5} @ 加载多个寄存器 blcoksize stride
MOV R8,R0 @ output_asm
MOV R9,R1 @ input
MOV R6,R2 @ width
loop_x:
MOV R7,R4
MOV R10,R9
VEOR Q1,Q1 @ 按位异或
loop_y:
VLD1.16 {Q1},[R10] @ 向量加载
VADD.U16 Q1,Q0,Q1 @ 向量加法
ADD R10,R5,LSL#1
SUBS R7,#1
BGT loop_y @ branch greater than
VST1.16 {Q1},[R8]! @ 向量存储
ADD R9,#16
SUBS R6,#8
BGT loop_x
POP {R4-R12,PC}
.end
makefile内容如下:
ROOTSRC = .
PLATFORM ?= 666888
ifeq ($(PLATFORM),666888)
CORSS = arm-hisiv500-linux-
CFLAGS += -march=armv7-a -mfloat-abi=softfp
SFLAGS += -march=armv7-a -mfpu=neon
endif
CC = $(CROSS)gcc
CPP = $(CROSS)g++
LD = $(CROSS)ld
AR = $(CROSS)ar
AS = $(CROSS)as
CFALGS += -O3
CFLAGS += -I$(ROOTSRC)
TARGET_APP = test
OBJS = $(ROOTSRC)/arm32_opt_c.o \
$(ROOTSRC)/arm32_opt_asm.o
all:$(TARGET_APP)
$(TARGET_APP):$(OBJS)
$(CC) -O $@ $(OBJS)
rm -f $(OBJS)
rm -f *.o
%.o:%.c
$(CC) -c $< $(CFLAGS) -o $@
%.o:%.cpp
$(CPP) -c $< $(CFLAGS) -o $@
%.o:%.S
$(CC) -c $< $(CFLAGS) -o $@
%.o:%.s
$(AS) -c $< $(SFLAGS) -o $@
clean:
rm -rf $(OBJS)
rm -rf $(TARGET_APP)
ARM64有32个寄存器(X0~X31),均是64位宽的,ARM64遵循AAPCS64(ARM Archtecture Procedure Call Standard)规则,其中:
a. 其中w0~w30分别是x0~x30的低32位;
b. x0~x7寄存器用于子程序间传递参数,当参数大于8时,将多余的参数用数据栈进行传递,入栈的顺序与参数顺序恰好相反,被调用子程序返回前无需恢复寄存器x0~x7的值
c. x8是XR,用于保存子程序返回地址;
d. x9~x15是临时寄存器,使用时不需要保存;
e. x16(IP0)、x17(IP1)是子程序内部调用寄存器,使用时不需要保存;
f. x18(PR)是平台寄存器,它的使用和平台有关;
g. x19~x28是临时寄存器,使用必须保存(入栈);
h. x29(FP)是帧指针寄存器,用于链接栈帧,使用时需要保存;
i. x30是链接寄存器LR,x31是堆栈指针寄存器SP;
j. PC是程序寄存器。
Neon64寄存器主要包含如下:
32个B寄存器(B0~B31),8bit;
32个H寄存器(H0~H31),16bit;
32个S寄存器(S0~S31),单字(32bit);
32个D寄存器(D0~D31),双字(64bit);
32个Q寄存器(V0~V31),四字(128bit)。
它们之间的关系举例如下:
S0是32位的,是D0(64位)的低半部分,D0是V0(128位)的低半部分;S1是32位的,是D1(64位)的低半部分,D1是V1(128位)的低半部分,这一点与NEON32是不同的,需要注意!!!
除此之外,仍需要特别注意的是NEON寄存器v0~v31的使用方法:
a. v0~v7:用于参数传递和返回值,callee(被调用者或者子程序)时不需要保存,即不需要出栈和入栈;
b. v8~v15:callee子程序调用时必须入栈保存(低64位进行保存即可);
c. v16~v31:callee不需要保存;
d. caller可能需要保存的是v0~v7,v16~v31。
假设我们有如下三个文件:
arm64_opt_c.c :待优化的c源码和demo模板
arm64_opt_arm.s :arm64汇编优化源码
makefile :优化模板的makefile,编译出可执行文件测试出汇编性能提升倍数
arm64_opt_c.c与arm32_opt_c.c一样;
arm64_opt_arm.s 如下:
.text
.align 4
.global arm64_opt_asm
arm64_opt_asm:
mov x9,x0 // output_asm
mov x10,x1 // input
mov x11,x2 // width
loop_x:
mov x12,x4
mov x13,x10
eor v1.16B,v1.16B,v1.16B // 按位异或
loop_y:
ldl {v0.8H},[x13] // 向量加载
add v1.8H,v0.8H,v1.8H // 向量加法
add x13,x13,x5,lsl #1
subs x12,x12,#1
bgt loop_y // branch greater than
st1 {v1.8H},[x9],#16 // 向量存储
add x10,x10,#16
subs x11,x11,#8
bgt loop_x
ret // 汇编调用的子程序返回
makefile与ARM32的异同点如下:
CORSS = aarch64-himix100-linux-
CFLAGS += -march=armv8-a -mfpu=cortex-a73
SFLAGS += -march=armv8-a -mfpu=cortex-a73
或者
CORSS = aarch64-himix100v630-linux-
CFLAGS += -march=armv8-a
SFLAGS += -march=armv8-a
ARM32位的inline汇编与ARM64位的inline汇编格式一样,唯一不同的是里面使用的寄存器等,这也是ARM32位纯汇编和ARM64纯汇编的区别。除此之外,Inline汇编是由编译器管理入栈和出栈,而纯汇编则是由被调用者(子函数)管理寄存器入栈和出栈。还需注意的一点是汇编分“AT&T”和“Intel”格式。
模板如下:
__asm volatile
(
"汇编指令1\n\t"
"汇编指令1\n\t"
"汇编指令1\n\t"
:输出变量列表【可选】
:输入变量列表【可选】
:被破坏的寄存器列表【可选】
);
举例如下:
__asm volatile
(
"mov r0,%[dst] \n\t"
"mov r1,%[src] \n\t"
"mov r2,%[stride] \n\t"
"mov r3,#16 \n\t"
"指令 \n\t"
:
:
[dst] "r" (dst),
[src] "r" (src),
[stride] "r" (stride)
:"r0","r1","r2","memory"
);
Intrinsic作为内联函数,直接在调用的地方插入代码,即避免了函数调用的额外开销,又能够使用比较高级的机器指令对函数进行优化。
需要说明的是:intrinsics下,arm32和aarch64下的代码是一致的。
(1)无论是ARM32位优化还是ARM64位优化,均可混合使用ARM寄存器和NEON寄存器;
(2)ARM32位优化的注释方法为“@”,而ARM64位优化的注释方法为“//”或者“/**/”。
后续补充。。。