risc-v GCC内嵌汇编
risc-v GCC内嵌汇编
1 背景
有时候需要对比不同指令的性能,这时候如果会手撸汇编是最方便的,但汇编掌握起来太麻烦,我们可以使用内嵌汇编,在C代码中直接内嵌汇编语句,大大方便了程序的设计。
2 内嵌汇编语法
asm volatile (
"Instruction_l;\
Instruction 2;\
...\
Instruction_n;"
:"=r"(valuel),
"=r"(value2),
"=r"(valuen)
:"r"(valuel),
"r"(value2),
"r"(valuen)
:"r0","r1",..."rn"
);
其中冒号:将括号中的内容分为4部分(用冒号分开):
asm volatile(汇编指令列表
∶ 输出操作数(非必需)
∶ 输入操作数(非必需)
∶ 破坏描述部分(非必需));
-
关键字 asm volatile或(__asm__ __volatile__)
其中:asm 表示是内嵌汇编操作,必需的关键字;
volatile是可选的关键字,使用volatile表示gcc不进行任何优化。
括号中的4部分详细说明如下:
1. 汇编指令列表:
要嵌入的汇编指令,每条指令应该被双引号括起来作为字符串,指令以\n 或者;作为分隔符。
如下4中样式都OK
样式1:
"Instruction_l;\
Instruction 2;\
...\
Instruction_n;"
样式2:
"Instruction_l\n\
Instruction 2\n\
...\n\
Instruction_n\n"
样式3:
"Instruction_l;"
"Instruction 2;"
"...;"
"Instruction_n;"
样式4:
"Instruction_l\n"
"Instruction 2\n"
"...\n"
"Instruction_n\n"
2. 输出操作数:
其格式如下:
:[out1]"=r"(valuel), [out2]"=r"(value2)...
-
新段由冒号:开头,若有多个输出用逗号,间隔
-
方括号[]中的符号名用于将内联汇编程序中使用的操作数。如[out1]"=r"(valuel) 输出是valuel,汇编代码中可以用%[out1]指代。除了
%[str]
中明确的符号命名指定外,还可以使用%数字
的方式进行隐含指定。"数字"从0开始,依次表示输出操作数和输入操作数。 -
双引号"=r" 字母r表示使用编译器自动分配的寄存器来存储该操作数变量。
常见constraints 含义 m 内存操作数 r 寄存器操作数 i 立即数(整数) f 浮点寄存器操作数 F 立即数(浮点) 对于输出操作数来说:“=”表示输出变量,用作输出, “+”表示输出变量不仅作为输出,也作为输入
-
圆括号()中的C/C+变量或者表达式。
3. 输入操作数:
与输出操作数格式相同,差别是没有“=”约束
4. 破坏描述部分:
-
新的段用:开头,若有多个寄存器用逗号,间隔
-
在汇编代码中,我们用到了一些寄存器,需要告知GCC不再信任这些寄存器的值(输入输出操作数指定的寄存器外,这部分由编译器自动分配回收)。
3 ABI接口
ABI接口即二进制接口,其包含许多细节:
- 数据类型的大小、布局和对齐。
- 函数调用的方式,如参数传递是通过栈传递还是寄存器、参数入栈顺序、返回值如何保存等
在写内嵌汇编时,需要了解函数调用输入输出用到了哪些寄存器。
对于RISC-V汇编程序而言,在汇编程序中调用C/C+语言函数,必须遵照ABI所定义的函数调用规则。即:函数参数由寄存器a0-a7传递,函数返回由寄存器a0-a1指定.
4 内嵌汇编示例
4.1 简单形式的内嵌汇编
内嵌汇编共四个部分 汇编指令列表 ∶输出操作数(非必需)∶输入操作数(非必需)∶破坏描述部分(非必需)
各部分使用“:”格开,汇编指令列表必不可少,其他三部分可选。
asm volatile("wfi"); /* 若函数没有输入输出,可直接调用 */
asm volatile("cli": : :"memory") /* 如果使用了后面的部分,而前面部分为空,也需要用“:”格开,相应部分内容为空 */
4.2 扩展内嵌汇编
以下是一个Riscv平台累加和的例子,展示C语言->汇编->内嵌汇编的转换
C版本:
int32_t c_macc(const int32_t *pA, const int32_t *pB, int32_t colCnt)
{
/* init the sum with bias */
int32_t sum = 0;
while (colCnt)
{
sum += *(pA++) * *(pB++);
colCnt--;
}
/* return the new output pointer with offset */
return sum;
}
汇编:
新建一个文件:a_macc.S,将C语言版本的elf文件反汇编,得到汇编代码,加上文件头,稍加修改后贴到a_macc.S中
.file "a_macc.S"
.option nopic
.attribute unaligned_access, 0
.attribute stack_align, 16
.text
.align 1
.globl a_macc
.type a_macc, @function
a_macc:
mv a5,a0
beq a2,zero,.L4
slli a4,a2,32
srli a2,a4,30
add a2,a0,a2
li a0,0
.L3:
lw a4,0(a5)
lw a3,0(a1)
addi a5,a5,4
addi a1,a1,4
mulw a4,a4,a3
addw a0,a4,a0
bne a2,a5,.L3
ret
.L4:
li a0,0
ret
.size a_macc, .-a_macc
.ident "GCC: (GNU) 10.2.0"
调用时,需在C代码里声明,然后使用:
extern int32_t a_macc(const int32_t *pA, const int32_t *pB, int32_t colCnt);
void main(void)
{
int32_t a[100], b[100];
int32_t sum;
sum = a_macc(a, b, 100);
}
内嵌汇编:
int32_t inline_macc(const int32_t *pA, const int32_t *pB, int32_t colCnt)
{
q31_t sum = 0;
colCnt *= 4;
asm volatile(
"sub a7, a7, a7;\
mv s0, %1;\
mv s1, %2;\
add a2, s1, %3;\
mv a5, s1;\
loop:;\
lw a4, 0(a5);\
addi a5,a5,4;\
lw a3,0(s0);\
addi s0,s0,4;\
mulw a4,a4,a3;\
addw a7,a7,a4;\
mv %0,a7;\
bne a2, a5, loop"
:"=r" (sum) // %0
:"r"(pA), // %1
"r"(pB), // %2
"r"(colCnt) // %3
: "s0", "s1"
);
return sum;
}
以上几种写法是等价的。
参考: