生成x86处理器的汇编代码

7

我正在学习Andrew Appel的《Java编译器实现》一书,目前正在构建低级中间表示法。起初,我决定针对JVM进行开发,忽略所有底层机器内容,但为了学习不熟悉的知识,我改变了想法。这改变了我的IR,因为针对JVM使我能够(或多或少地)忽略方法调用或对象构造。

Appel的书并没有详细介绍任何特定的机器架构,所以我想知道在哪里可以找到我需要了解的所有信息,以便进一步深入学习。

我目前意识到需要了解的事情有:

  • 使用哪个指令集。我有两台笔记本电脑可以进行开发;两者都有Core 2 Duo处理器。我目前的理解是,x86处理器大多使用相同的指令集,但它们并不完全相同。

  • 操作系统是否影响编译的代码生成步骤,或者它完全依赖于处理器。例如,我知道在32位和64位平台上运行代码的生成方式有所不同。

  • 如何组织堆栈帧等。何时使用寄存器而不是将参数放在堆栈上,调用者保存还是被调用者保存,所有这些内容。我本来以为这将与指令集一起描述,但到目前为止,我还没有在任何地方看到这些特定信息。也许我在这里有什么误解?

欢迎提供相关资源的链接。


投票关闭,原因是范围过于宽泛。 - Ciro Santilli OurBigBook.com
3个回答

5

大多数x86指令集对所有处理器都是通用的 - 可以合理地确定您的处理器具有相同的指令集,除了SIMD指令可能在实现简单编译器时对您没有太大用处(这些指令通常用于加速多媒体应用程序等)。 指令集列在Intel手册中 - 特别是2A和2B提供了指令及其行为的完整列表,但其他卷也值得一看。

生成用户空间代码时,操作系统的选择在系统调用方面很重要。例如,如果您想让一个程序在64位Linux上输出一些东西到终端,您需要通过以下方式进行系统调用:

  • 将值1加载到寄存器rax中,以指示这是一个写入系统调用。
  • 将值1加载到寄存器rdi中,以指示应该使用stdout(1是stdout的文件描述符)
  • 将要打印的内容的起始地址加载到寄存器rsi
  • 将要打印的内容的长度加载到寄存器rdx
  • 设置好寄存器(和内存)后执行syscall指令。

write的返回值存储在rax中。

不同的操作系统可能对write有不同的系统调用号码,可能有不同的参数传递方式(x86-64 Linux系统调用总是按照rdirsirdxr10r8r9的顺序传递参数,并在rax中使用系统调用号),也可能有完全不同的系统调用。

在Linux上,普通函数调用的约定类似--寄存器的顺序是rdi,rsi,rdx,rcx,r8和r9(所以完全相同,只是使用rcx而不是r10),进一步的参数在堆栈中,并且返回值在rax中。根据this page,应该在函数调用中保留寄存器rbp,rbx和r12到r15。当然,您可以自由地制定自己的约定(除非进行系统调用),但这使得从其他人生成或编写的代码中调用变得更加困难。

谢谢,Michael - 这个答案也非常有帮助。我希望我也能接受它;这是我的错,因为我把太多的问题结合在一起了。不过还是点了个赞。 - danben
实际上,第二次阅读后我认为这个回答非常详尽地解答了我所有的问题。 - danben

3
如何组织堆栈帧等内容。何时使用寄存器而不是将参数放在堆栈上,调用者保存和被调用者保存,所有这些都需要考虑。我本以为这会随着指令集一起描述,但到目前为止,我还没有在任何地方看到这个特定的信息。也许我在这里有什么误解?
总的来说,这些问题没有标准答案。你可以使用任何调用约定……除非你想与其他人的代码相互操作。为了实现互操作性,编译器会标准化应用���序二进制接口。我的理解是Itanium C++ ABI已成为近年来流行的标准。可以从那里开始。

谢谢,Nathan。我不太理解Itanium C++ ABI与我的目的有何关系(例如,在为另一种语言开发编译器时,C++扮演了什么角色?);然而,这个链接最终引导我了解到各种x86调用约定(cdecl等),这正是我所需要的。 - danben

1

我无法回答你所有的问题;但是

  • 基本的x86指令集在x86系列处理器中是兼容的。你没有计划实现任何特定的扩展,对吧?
  • 我认为你的操作系统或架构对代码生成并不重要。
  • 与编译器相关的任何问题的默认答案都是龙书。你看过它了吗?

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接