Physical Address:
ChongQing,China.
WebSite:
首先需要明确的一点是,为什么我们需要在C/C++中嵌入汇编代码,这通常是出于三个目的:性能优化、特定指令需求以及对资源的精细化管理。
这是因为汇编语言可以实现对CPU寄存器的直接操作,以及对内存的精确控制,无需从C/C++代码进行转译,能够提升我们的软件运行效率从而实现性能优化,同时某些CPU特有的指令集在C/C++中没有相关的支持,此时也只有通过汇编来实现相关操作。
在Android系统C/C++开发中嵌入汇编代码,通常采用两种方式:内嵌汇编、外部汇编文件。
内嵌汇编(Inline Assembly):这是最直接的方式,在C代码中嵌入汇编代码片段。语法和具体实现依赖于编译器,通常使用特定的关键字或者语法来标识汇编代码。例如,在GCC和Clang编译器中,可以使用asm关键字来标识内嵌汇编代码。如下所示:
// frameworks/av/media/libaudioprocessing/AudioResamplerSinc.cpp
static inline
int32_t mulAdd(int16_t in, int32_t v, int32_t a)
{
#if USE_INLINE_ASSEMBLY
int32_t out;
asm( "smlawb %[out], %[v], %[in], %[a] \n"
: [out]"=r"(out)
: [in]"%r"(in), [v]"r"(v), [a]"r"(a)
: );
return out;
#else
return a + int32_t((int64_t(v) * in) >> 16);
#endif
}
除了内嵌汇编,还经常用到外部汇编文件文件来进行嵌入。
外部汇编文件(External Assembly Files):将汇编代码单独存放在一个汇编文件中,然后通过C语言的函数调用来调用这些汇编函数。在汇编文件中,需要按照特定的调用约定来定义函数接口以及参数传递方式。C语言代码通过函数调用来触发对应的汇编代码执行。如下所示:
void SwapContext(char **src_sp, char **dest_sp)
{
ctx_swap(reinterpret_cast<void **>(src_sp), reinterpret_cast<void **>(dest_sp));
}
其中ctx_swap的调用对应汇编源文件中提供的symbol,一般我们会将汇编文件以.S或者.asm结尾,如下所示:
//swap_aarch64.S source code
.globl ctx_swap
.type ctx_swap, @function
ctx_swap:
push %edi
push %ebx
push %ebp
movl %esp, (%edi)
movl (%esi), %esp
pop %ebp
pop %ebx
pop %edi
在编译链接完成后就能实现在C代码中调用汇编代码。
在Android系统中我们一般推荐使用外部汇编文件的方式进行嵌入,这样能够具有更好的灵活性,因为汇编代码与CPU架构是强相关的。为了多平台的适配,我们可以在Android.bp中区分不同的CPU架构从而引入不同的外部汇编文件,如下所示:
//Android.bp
cc_library_shared {
srcs:[
example.cc
],
…..
arch:{
arm64:{
srcs:["croutine/detail/swap_aarch64.S"],
},
arm:{
srcs:["croutine/detail/swap_aarch64.S"],
},
x86:{
srcs:["croutine/detail/swap_x86_32.S"],
},
x86_64:{
srcs:["croutine/detail/swap_x86_64.S"],
}
},
…..
}
这里我们通过arch字段分别区分了arm64、arm、x86以及x86_64不同的架构,进而导入不同的汇编文件。
如果我们不考虑平台兼容性,可以使用asm内嵌汇编,不过这里有一个小细节是我们的程序默认会编译32+64位的,此时asm内嵌汇编可能会报错,一般我们需要在Android.bp中通过compile_multilib字段进行配置,编译指定类型的产物,如下所示:
cc_library_shared {
compile_multilib: "64",
}
这里我们通过compile_multilib字段指定仅编译64位程序,从而确保asm嵌入汇编可以正常编译通过。