一:概述
任何后端的核心都是指令选择。LLVM 实现了几种方法;在本篇文章中,我们将通过选择有向无环图(DAG)和全局指令选择来实现指令选择。
在本篇文章中,我们将学习以下主题:
• 定义调用约定规则:本节展示如何在目标描述中设置调用约定的规则。
• 通过选择 DAG 进行指令选择:本节介绍如何使用图数据结构实现指令选择。
• 添加寄存器和指令信息:本节解释如何访问目标描述中的信息,以及一些额外提供的信息。
• 设置堆栈空间:本节介绍堆栈布局和函数调用栈。
• 生成机器指令:本节介绍机器指令如何最终写入目标文件或汇编文本。
• 创建目标机器和子目标:本节介绍如何配置后端。
• 全局指令选择:本节演示指令选择的另一种方法。
• 如何进一步完善后端:本节提供有关进行下一步工作的一些指导。
在本篇文章结束时,我们将知道如何创建一个可以翻译简单指令的LLVM后端。我们还将获得关于指令选择的知识,并将熟悉实现指令选择所需的所有重要辅助类。
二:定义调用约定规则
实现调用约定规则是将LLVM中间表示(IR)转换为机器代码的重要部分。基本规则可以在目标描述中定义。让我们来看看。
大多数调用约定遵循基本模式:它们定义了一组用于参数传递的寄存器。如果这一子集没有用尽,下一个参数将通过下一个空闲寄存器传递。如果没有空闲寄存器,则该值将通过栈传递。这可以通过循环参数并决定如何将每个参数传递给被调用函数,同时跟踪已使用的寄存器来实现。在LLVM中,这个循环在框架内部实现,状态保存在一个名为CCState的类中。此外,规则在目标描述中定义。
规则作为一系列条件给出。如果条件成立,则执行一个动作。根据该动作的结果,要么找到参数的位置,要么评估下一个条件。例如,32位整数通过寄存器传递。条件是类型检查,动作是将寄存器分配给该参数。在目标描述中,这写作如下:
CCIfType<[i32],
CCAssignToReg<[R2, R3, R4, R5, R6, R7, R8, R9]>>,
当然,如果被调用的函数有超过八个参数,则寄存器列表将被用尽&#x