编译器设计中的内联装配

问题描述 投票:2回答:1

我正在为我自己的C类语言(x86-64)制作自己的编译器。但是我很困惑,如何编译另一种类型的语言的片段,即x86-64汇编,如。

int main() {
   __asm {
       mov rcx, rsp
       call func
   }
}

一旦遇到__asm,它必须以某种方式将标记改为汇编标记,如果我在__asm块之外有一个名为rcx的变量怎么办?有什么好的方法可以将其纳入到类似C语言的编译器设计中?你如何将它标记化,并以一种将它与类 C 代码分离的方式进行解析?__asm 块会首先在解析器层面上被识别,但如果没有将其标记化,你就无法达到这个层面......。

parsing compiler-construction x86-64 inline-assembly language-design
1个回答
5
投票

一种选择是像现代MSVC那样,为每条指令提供固有的功能,包括特权指令,如 invlpg. 因为MSVC不支持32位x86以外的目标内联asm)。 这就是MS还能用它来开发Windows内核的原因。

不过,如果你没有掌握所有你关心的目标ISA中未来的指令集扩展,那就不好用了。


我真的建议使用 GNU C的Extended inline asm语法。 其中操作数约束向编译器描述了asm模板字符串。. 编译器本身不需要理解它。根本,只需用字符串代替它,如 printf 寻找 %conversion. (见 'asm'、'__asm'和'__asm__'之间的区别是什么?)

被访问的C var名是用固定的语法指定的,不依赖于asm语法。 另外: ASM是在一个 "" 作为C语法层面的字符串文字表达式所以像ARM这样的东西 push {r4, lr} 对块作用域解析不可见。 请看 https:/stackoverflow.comtagsinline-assemblyinfo。 以获取更多关于GNU C inline asm工作原理的文档指南。 还需要注意的是,它的模板操作数约束语法和GCC内部在机器定义文件中使用的语法是一样的,GCC将不同目标的可用指令教给编译器。

这就把问题丢给了程序员,让他去写所有的clobber声明来告诉编译器关于每一个寄存器,一个 call 的任意函数进行修改,假设它遵循标准的调用约定。

这也让你可以写一些像 asm("blsi %1, %0" : "=r"(dst) : "r"(src) ) 其中编译器选择实际使用哪些寄存器。 只输出寄存器操作数,只输入寄存器操作数)。 这让编译器尽可能高效地围绕黑盒(asm语句)进行寄存器分配。 它可以选择相同的寄存器进行输入和输出,也可以不选择,因为方便,因为源码没有使用 "早期的clobber" ("=&r"),所以它可以假设所有的输入都是在写入任何输出之前读取的。

它非常适用于封装单条指令,但也可以用于封装多条指令和对指向内存的访问,例如通过一个 "memory" clobber。


你所展示的 MSVC 风格的语法必须对代码块进行解析,以检测 clobber 寄存器,以及对 var 名称的提及。 这就难多了。

现代clang确实支持 asm{} 块,但它的使用效率很差(就像在 MSVC 中一样);它们不能用寄存器代替变量名,所以输入输出必须在内存中跳转。

MSVC 不支持除 32 位 x86 以外的目标使用 asm 块,可能是因为他们的编译器内部处理 asm{} 的方法太乱了,以至于对有寄存器 args 的函数不安全。 这使得它无法用于现代的调用习惯。 这不是语法问题,只是编译器的技术缺陷问题。

但是,在将数据从一个 asm{} 块是一个语法设计问题。 不要重蹈MSVC的覆辙。 或者如果你确实想只让用户提var名,在你的文档中明确说明可以用寄存器或内存来代替,如果你认为可以在你的优化后端工作的话,要留出这个选项。

© www.soinside.com 2019 - 2024. All rights reserved.