如题,增加新的指令应该是我最终的目标。有两个方式来实现这个目标,一个是增加通用的DAG,这样所有的ISA都会增加上这条指令,但是比较难,因为DAG有合法化、简化什么的,比较难。所以我选择第二条,就是为某一个特定的ISA增加一个内联函数(intrinsics ),通过这个内联函数来链接到新的指令。
首先,和增加伪指令一样,在clang中增加一条intrinsic function。具体就不写了。有区别的是在Target中,下面描述Target/X86中的修改。
首先,也是在X86ISelLowering.h中增加max_qb,增加一个X86的ISD。
在X86ISelLowering.cpp中增加case,增加这个ISD的对应case X86ISD::max_qb: return "X86ISD::max_qb";
前面都是一样的
在X86IntrinsicsInfo.h中增加自己的intrinsic。因为不需要工具链,所以添加位置需要找好,而且这个比较坑的是要按照字母的顺序进行排序,那时我找了好久错误都找不到,最后还是在论坛求助的。
X86_INTRINSIC_DATA(max_qb, INTR_TYPE_2OP, X86ISD::max_qb, 0),
在X86InstrInfo.td中增加一个SDNode,因为我准备输出和整数乘法格式一样的二进制编码,这样,在读取二级制文件时也简单方便一点。所以SDNode中的参数参照了IMUL。具体定义可以在inlclude/Target中找到。
def X86max_qb : SDNode<"X86ISD::max_qb", SDTBinaryArithWithFlags,[SDNPCommutative]>;
然后在X86InstrArithmetic.td中定义具体输出形式,在这儿定义和在X86InstrInfo.td没区别,只是LLVM对不同指令进行了一个分类。
let Defs = [EFLAGS] in {
let Constraints = "$src1 = $dst" in {
let isCommutable = 1 in {
def max_qb : I<0xF0,MRMSrcReg, (outs GR32:$dst), (ins GR32:$src1,GR32:$src2),
"max_qb{w}\t {$dst, $src1,$src2|$dst, $src2,$src1}", [(set GR32:$dst,EFLAGS,(X86max_qb GR32:$src1, GR32:$src2))]>, Sched<[WriteIMul32Reg]>,
OpSize32 ;
}
}
}
讲解一下,进行了一些限制,比如src1=dst,这是因为X86的基础指令只能有两个寄存器(操作数),所以让一个寄存器等于输出,EFLAGS也是满足EFLAGS。def定义了这条指令,其中F0是操作码,MRMSrcReg是指令类型,后面的定义输出和输入,GR32是32位寄存器的意思。“”号内的是表示汇编的输出形式,在编译后的汇编显示格式。set这句就是二进制的形式。Sched定义了指令在机器中的一些时序,延时、周期数等,最后的OpSize32表示这个是32的操作数,如果遇到64时会自动补全。
对了,得把原来的F0的指令注释掉,不然就会报错说指令覆盖。
下面开始简单测试:
#include <stdlib.h>
#include <stdio.h>
int main()
{
int a;
int b;
int c;
a=2;b=6;
c=__builtin_x86_max_qb (a,b);
printf("%d \n",c);
}
使用命令
clang -emit-llvm -O0 test.c -c -o test.bc
llc test.bc
得到test.s。(节选了一部分),可以看到在汇编中得到了max_qb,所以添加指令成功。但是比较尴尬,我想运行一直失败,3周后我才想起可能是因为运行其实和编译器是无关的,编译器只是负责编译成二进制文件,而运行时看cpu中固有的指令读取方式的,所以下一步,我想修改一下gem5,让指令运行起来
movl $2, -12(%rbp)
movl $6, -8(%rbp)
movl -12(%rbp), %eax
movl -8(%rbp), %ecx
max_qbw %eax, %eax,%ecx
movl %eax, -4(%rbp)
movl -4(%rbp), %esi
movabsq $.L.str, %rdi
movb $0, %al
callq printf