如何让 LLVM 更喜欢一条机器指令而不是另一条机器指令?
How to make LLVM prefer one machine instruction over another?
假设我在目标机器上有两个寄存器计算块:I 和 X。可以只对 I 寄存器应用整数运算,对 X 寄存器应用整数和浮点运算。还有两种类型的指令:
def ADDIi32 : MyInstruction< ..., (outs I:$Rs), (ins I:$Rm, I:$Rn), [(set i32:$Rs, (add i32:$Rm, i32:$Rn)]>;
...
def ADDXi32 : MyInstruction< ..., (outs X:$Rs), (ins X:$Rm, X:$Rn), [(set i32:$Rs, (add i32:$Rm, i32:$Rn)]>;
def ADDXf32 : MyInstruction< ..., (outs X:$Rs), (ins X:$Rm, X:$Rn), [(set f32:$Rs, (fadd f32:$Rm, f32:$Rn)]>;
...
它们的编码方式不同,并具有不同的 asmstrings。所以llvm可以映射
int a, b;
a = a + b;
到 ADDIi32 或 ADDXi32 但
float a, b;
a = a + b;
仅映射到 ADDXf32。
我希望 LLVM 在可能的情况下使用 ADDIi32,但不幸的是我没有办法告诉它一条指令(或寄存器)比另一条多 "costs"。 CostPerUse in Register class 似乎是一个候选者,但它定义了组中其他寄存器的成本,而不是所有寄存器的成本。我看到一些线程声称 AddedComplexity(指令 class 中的字段,描述不明确)控制选择模式,但也什么都不做。
看来是LLVM选择了第一个匹配的指令。当我在 ADDIi32 之前定义 ADDXf32 和 ADDXi32 时,它只对任何类型的数据使用 X 函数。当我在 ADDXf32 和 ADDXi32 之前定义 ADDIi32 时,它对整数数据使用 I 指令,但不匹配浮点数据(奇怪)。我想我可以在 ADDXf32 和 ADDXi32 之间插入 ADDIi32,但它们目前在不同的 .td 文件中,看起来不像是长期解决方案。
有什么想法吗?
请注意,声明顺序不影响指令选择:LLVM 仅通过首先匹配更复杂的模式进行排序(这是 AddedComplexity 影响的——注意增加 AddedComplexity 增加 机会匹配模式),通过将指令与较小的 CodeSize 匹配来打破平局,最终不确定地打破平局。因此你不能依赖声明的顺序。
编辑:我原来回答的以下部分实际上并不适用于您的问题——请参阅 http://lists.llvm.org/pipermail/llvm-dev/2007-September/010833.html。特别是:
ISel patterns are matched against DAGs before register allocation. So
ISel patterns can't take into account register
classes. The register classes in the patterns are a selection
constraint for the register allocator if that instruction is chosen,
not a constraint on choosing that pattern.
除了 I 和 X 寄存器 classes,你应该有一个 "IntRegsRegClass" 或类似的,你可以在其中添加 I 和 X 寄存器。 (这是您调用 addRegisterClass 的 reg class。)
您在此寄存器中添加寄存器的顺序 class 决定了首选分配顺序。
这看起来与带有 split-brain D(ata) 和 A(address) 寄存器的 m68k 非常相似。为了大量使用,D 寄存器靠近 ALU 并用于整数运算,而 A 寄存器靠近 address-calculation 单元。这意味着如果指针在 D 寄存器中,则需要在取消引用之前将其复制到 A 寄存器。
加法是一种对两种寄存器都有意义的操作,因为一种寄存器同时递增计数器和指针,因此有单独的 ADD 和 ADDA 指令。它们在硅中的实现可能完全不同,但唯一的 programmer-visible 区别是 ADD 根据结果更新条件代码,而 ADDA 不影响它们。
其他一些指令也以单独的 address-special 形式出现,包括 MOVE,这就是我如何遇到你在为 m68k 后端实现单独的 MOVE 和 MOVEA 时遇到的相同问题并感到恼火选错了。这就是我最终找到这个 SO post 的方式,Cheng Sun 的回答告诉了我我不想听到但确实需要知道的内容。
我的新方法是定义三个寄存器classes,Dn、An 和Xn(Xn 是m68k 上的现有约定),分别由D、A 以及D 和A 组成。我已经将 ADD/ADDA 合并到一个新的 pseudo-instruction ADDZ(ADDX 已经存在)中,它作用于条件代码未定义的 Xn class。我计划稍后添加一个修复程序,将此类 pseudo-instruction 转换为适当的有效指令,并删除已设置条件代码的情况下的所有冗余 TST 指令。
这已经阻止了 pattern-matching 传球,例如当 ADD 更有意义时,先发出 ADDA,然后发出 MOVE。因为寄存器分配器避免了冗余移动,所以将使用 A 寄存器执行指针运算,即使它们在 Xn class 中最后列出。如果我也支持 floating-point,FADD 指令将被定义为使用单独的 FPx 寄存器 class,因此不会与现有的 ADD 和 ADDZ 模式冲突。
在您的 I 和 X 寄存器示例中,我将分别为 I 和 X 创建 GPR 和 XR classes,以及 X classes。 GPR class 也会首先列出所有 I 寄存器,以便 X 寄存器仅用作最后的手段。然后我会像这样写你的示例模式:
def ADDi32 : MyInstruction< ..., (outs GPR:$Rs), (ins GPR:$Rm, GPR:$Rn), [(set i32:$Rs, (add i32:$Rm, i32:$Rn)]>;
def ADDf32 : MyInstruction< ..., (outs XR:$Rs), (ins XR:$Rm, XR:$Rn), [(set f32:$Rs, (fadd f32:$Rm, f32:$Rn)]>;
假设我在目标机器上有两个寄存器计算块:I 和 X。可以只对 I 寄存器应用整数运算,对 X 寄存器应用整数和浮点运算。还有两种类型的指令:
def ADDIi32 : MyInstruction< ..., (outs I:$Rs), (ins I:$Rm, I:$Rn), [(set i32:$Rs, (add i32:$Rm, i32:$Rn)]>;
...
def ADDXi32 : MyInstruction< ..., (outs X:$Rs), (ins X:$Rm, X:$Rn), [(set i32:$Rs, (add i32:$Rm, i32:$Rn)]>;
def ADDXf32 : MyInstruction< ..., (outs X:$Rs), (ins X:$Rm, X:$Rn), [(set f32:$Rs, (fadd f32:$Rm, f32:$Rn)]>;
...
它们的编码方式不同,并具有不同的 asmstrings。所以llvm可以映射
int a, b;
a = a + b;
到 ADDIi32 或 ADDXi32 但
float a, b;
a = a + b;
仅映射到 ADDXf32。
我希望 LLVM 在可能的情况下使用 ADDIi32,但不幸的是我没有办法告诉它一条指令(或寄存器)比另一条多 "costs"。 CostPerUse in Register class 似乎是一个候选者,但它定义了组中其他寄存器的成本,而不是所有寄存器的成本。我看到一些线程声称 AddedComplexity(指令 class 中的字段,描述不明确)控制选择模式,但也什么都不做。
看来是LLVM选择了第一个匹配的指令。当我在 ADDIi32 之前定义 ADDXf32 和 ADDXi32 时,它只对任何类型的数据使用 X 函数。当我在 ADDXf32 和 ADDXi32 之前定义 ADDIi32 时,它对整数数据使用 I 指令,但不匹配浮点数据(奇怪)。我想我可以在 ADDXf32 和 ADDXi32 之间插入 ADDIi32,但它们目前在不同的 .td 文件中,看起来不像是长期解决方案。
有什么想法吗?
请注意,声明顺序不影响指令选择:LLVM 仅通过首先匹配更复杂的模式进行排序(这是 AddedComplexity 影响的——注意增加 AddedComplexity 增加 机会匹配模式),通过将指令与较小的 CodeSize 匹配来打破平局,最终不确定地打破平局。因此你不能依赖声明的顺序。
编辑:我原来回答的以下部分实际上并不适用于您的问题——请参阅 http://lists.llvm.org/pipermail/llvm-dev/2007-September/010833.html。特别是:
ISel patterns are matched against DAGs before register allocation. So ISel patterns can't take into account register classes. The register classes in the patterns are a selection constraint for the register allocator if that instruction is chosen, not a constraint on choosing that pattern.
除了 I 和 X 寄存器 classes,你应该有一个 "IntRegsRegClass" 或类似的,你可以在其中添加 I 和 X 寄存器。 (这是您调用 addRegisterClass 的 reg class。)
您在此寄存器中添加寄存器的顺序 class 决定了首选分配顺序。
这看起来与带有 split-brain D(ata) 和 A(address) 寄存器的 m68k 非常相似。为了大量使用,D 寄存器靠近 ALU 并用于整数运算,而 A 寄存器靠近 address-calculation 单元。这意味着如果指针在 D 寄存器中,则需要在取消引用之前将其复制到 A 寄存器。
加法是一种对两种寄存器都有意义的操作,因为一种寄存器同时递增计数器和指针,因此有单独的 ADD 和 ADDA 指令。它们在硅中的实现可能完全不同,但唯一的 programmer-visible 区别是 ADD 根据结果更新条件代码,而 ADDA 不影响它们。
其他一些指令也以单独的 address-special 形式出现,包括 MOVE,这就是我如何遇到你在为 m68k 后端实现单独的 MOVE 和 MOVEA 时遇到的相同问题并感到恼火选错了。这就是我最终找到这个 SO post 的方式,Cheng Sun 的回答告诉了我我不想听到但确实需要知道的内容。
我的新方法是定义三个寄存器classes,Dn、An 和Xn(Xn 是m68k 上的现有约定),分别由D、A 以及D 和A 组成。我已经将 ADD/ADDA 合并到一个新的 pseudo-instruction ADDZ(ADDX 已经存在)中,它作用于条件代码未定义的 Xn class。我计划稍后添加一个修复程序,将此类 pseudo-instruction 转换为适当的有效指令,并删除已设置条件代码的情况下的所有冗余 TST 指令。
这已经阻止了 pattern-matching 传球,例如当 ADD 更有意义时,先发出 ADDA,然后发出 MOVE。因为寄存器分配器避免了冗余移动,所以将使用 A 寄存器执行指针运算,即使它们在 Xn class 中最后列出。如果我也支持 floating-point,FADD 指令将被定义为使用单独的 FPx 寄存器 class,因此不会与现有的 ADD 和 ADDZ 模式冲突。
在您的 I 和 X 寄存器示例中,我将分别为 I 和 X 创建 GPR 和 XR classes,以及 X classes。 GPR class 也会首先列出所有 I 寄存器,以便 X 寄存器仅用作最后的手段。然后我会像这样写你的示例模式:
def ADDi32 : MyInstruction< ..., (outs GPR:$Rs), (ins GPR:$Rm, GPR:$Rn), [(set i32:$Rs, (add i32:$Rm, i32:$Rn)]>;
def ADDf32 : MyInstruction< ..., (outs XR:$Rs), (ins XR:$Rm, XR:$Rn), [(set f32:$Rs, (fadd f32:$Rm, f32:$Rn)]>;