RISC-V MCU中文社区

【分享】 inline ASM(内联汇编)的细节分享

发表于 全国大学生集成电路创新创业大赛 2021-04-19 11:40:31
0
183
0
内联汇编

基本的语法
asm volatile ("instruction list":OUTPUT:INPUT:(clobber/modify));
如果 clobber| Modify 为空,其前冒号必须省略。
如果 Output, Input, Clobber|Modify 都为空, Output, INPUT之前的冒号既可省略,也可不省略,如果都省略,则是化为内联汇编,否则,仍然是带有C++表达式的内联江编,此时"instructionlist"中的寄存器的写法需要遵守相关的规定: 寄存器前必须使用两个百分号(%%),而不是像基本汇编格式样只加一个%;
如果INPUT clobber/modify 为空, output不为空, input前的冒号可省略也可不省略;__asm__( " mov %%eax, %%ebx" : "=b"(foo) : );__asm__( " mov %%eax, %%ebx" : "=b"(foo) )都是正确的。
如果后边的部分不为空,前面的部分为空,则前面的冒号必须保留;
output
输出,指定内联汇编语句的输出:
_asm_("movl %%cr0 %0" :"=a"(cr0) );
此内联汇编语句的输出部分 "=a"(cr0),输出操作由两部分组成: 括号部分为C/C++表达式,,用来保存内联汇编的一个输出值: 其操作就等于C中的相等赋值; cr0 = outputvalue;, 因此,括号中的输出表达式只能是C/C++的左值表达式, 右边的值来自左边引号部分的操作约束: (operation constraints) ,上例中的操作约束"=a" ,它包含两个约束: = 和 a,其中 = 表示括号中的左值表达式cr0是一个 write-only的,只能够当做当前内联汇编的输入,而字母a 是寄存器EAX\AX\AL 的简写,说明cr0的值需要从 EAX中读取;,也就是 cr0 = eax ; 最终转化为汇编指令就是: movl %eax, address_of_cr0;
有些文档说明,输出操作的操作约束必须包含一个=;
GCC文档中清楚地声明, =表示表达式是一个write-only的,使用+表示当前表达式是write-read,如果一个操作约束没有给出这两个符号,则说明当前表达式是 read-only的,对于一个输出操作而言,肯定是必须是可写的,而=/+都表示可写;
input
input 域表示内联汇编的输入;__asm__("movl %0, %%db7" : : "a" (cpu->db7));
输入表达式同样分两部分: "a" (cpu->db7), ()内叫表达式, ""内叫操作约束; 与输出表达式稍有不同, ()内表达式不必须为一个左值表达式,还可以放在赋值操作的右边的表达式.所以他可以是一个变量,一个数字,还可以是一个复杂的表达式,
##### operation condition
r I,O 表示使用一个通用寄存器,由GCC在%eax/%ax/%al, %ebx/%bx/%bl, %ecx/%cx/%cl, %edx/%dx/%dl中选取一个GCC认为合适的。
q I,O 表示使用一个通用寄存器,和r的意义相同。
a I,O 表示使用%eax / %ax / %al
b I,O 表示使用%ebx / %bx / %bl
c I,O 表示使用%ecx / %cx / %cl
d I,O 表示使用%edx / %dx / %dl
D I,O 表示使用%edi / %di
S I,O 表示使用%esi / %si
f I,O 表示使用浮点寄存器
t I,O 表示使用第一个浮点寄存器
u I,O 表示使用第二个浮点寄存器

内存约束

如果INPUT OUTPUT的操作数表达式的C/C++操作表达式表现为一个内存地址,不想借助于其他寄存器,则可以使用内存约束,
m I,O 表示使用系统所支持的任何一种内存方式,不需要借助寄存器

立即数约束

i I 表示输入表达式是一个立即数(整数),不需要借助任何寄存器
F I 表示输入表达式是一个立即数(浮点数),不需要借助任何寄存器
#### 操作表达式编号
其操作表达式被按照被列出的顺序编号,第一个是0,第2个是1,依次类推,GCC最多允许有10个操作表达式
__asm__ ("popl %0 \n\t"
"movl %1, %%esi \n\t"
"movl %2, %%edi \n\t"
: "=a"(__out)
: "r" (__in1), "r" (__in2));
_out所在的OUTPUT操做表达式被编号为0, "r"(_in1)被编号为2 "r"(_in2)被编号为2;
如果某个Input操作表达式使用数字0到9中的一个数字(假设为1)作为它的操作约束,则等于向GCC声明:“我要使用和编号为1的Output操作表达 式相同的寄存器(如果Output操作表达式1使用的是寄存器),或相同的内存地址(如果Output操作表达式1使用的是内存)”。上面的描述包含两个 限定:数字0到数字9作为操作约束只能用在Input操作表达式中,被指定的操作表达式(比如某个Input操作表达式使用数字1作为约束,那么被指定的 就是编号为1的操作表达式)只能是Output操作表达式。
由于GCC规定最多只能有10个Input/Output操作表达式,所以事 实上数字9作为操作约束永远也用不到,因为Output操作表达式排在Input操作表达式的前面,那么如果有一个Input操作表达式指定了数字9作为 操作约束的话,那么说明Output操作表达式的数量已经至少为10个了,那么再加上这个Input操作表达式,则至少为11个了,以及超出GCC的限制。

modifier characters 修饰符

= or +
#### 占位符
__asm__ ("addl %1, %0\n\t"
: "=a"(__out)
: "m" (__in1), "a" (__in2));
在上述例子中 %1 %0 就是占位符: 每一个占位符对应一个INPUT\OUTPUT操作表达式, 占位符中的数字对应 INPUT/OUTPUT操作表达式的列出顺序编号0-9;
每个占位符前使用一个%;
GCC在对其进行编译时,会将每个占位符替换为对应的INPUT/OUTPUT操作表达式所指定的寄存器/内存地址/立即数.在上例中,占位符%0对应output的操作表达式"=a"(_out),其指定的寄存器为 %eax,所以把%0 替换为%eax, 占位符%1 对应INPUT的操作表达式 "m"(_in), 被指定为内存操作,所以把%1 替换为变量的_in1的内存地址;
#### clobber/modify
有时候你想通知GCC当前的内联汇编语句可能对某些寄存器或者内存进行修改,希望GCC在编译时 能够将这一点考虑进去, 那么你就可以在clobber/modify 域声明这些寄存器或者内存;
这种情况一般发生在一个寄存器出现在"instructionlist" 但却不是有INPUT/OUTPUT操作表达式所指定的;,也不是在一些INPUT/OUTPUT表达式中使用R/g约束时由 GCC为其选择的, 同时此寄存器被"instructionlist"的指令修改,而这个寄存器只是供当前的内联汇编临时使用的情况;
__asm__ ("movl %0, %%ebx" : : "a"(__foo) : "bx");
上述内联汇编指令, 寄存器%ebx 出现在instructionlist中 并且被movl指令修改, 但没有被INPUT/OUTPUT 的操作表达式指定, 所以在 clobber/modify 指定此寄存器, 让GCC知道这一点;
在Clobber/Modify域中指定这些寄存器的方法很简单,你只需要将寄存器的名字使用双引号(" ")引起来。如果有多个寄存器需要声明,你需要在任意两个声明之间用逗号隔开。
声明的串 代表的寄存器
"al","ax","eax" %eax
"bl","bx","ebx" %ebx
"cl","cx","ecx" %ecx
"dl","dx","edx" %edx
"si","esi" %esi
"di", "edi" %edi
如果你在一个内联汇编语句的Clobber/Modify域向GCC声明某个寄存器内容发生了改变,GCC在编译时,如果发现这个被声明的寄存器的内容在此 内联汇编语句之后还要继续使用,那么GCC会首先将此寄存器的内容保存起来,然后在此内联汇编语句的相关生成代码之后,再将其内容恢复。我们来看两个例 子,然后对比一下它们之间的区别。

volatile 关键字

一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:
1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。

2)禁止进行指令重排序。 
喜欢0
用户评论
关于作者
明洋

明洋 未通过人工认证

懒的都不写签名

问答
粉丝
0
关注
0
  • 早春营|RISC-V处理器嵌入式开发
  • RV-STAR 开发板
  • RISC-V处理器设计系列课程
  • 培养RISC-V大学土壤 共建RISC-V教育生态
早春营|RISC-V处理器嵌入式开发