LLVM后端 td文件 tablegen 模式匹配 寄存器 指令集 calling convention

目录

一、寄存器

1.1 寄存器定义

1.2 寄存器分类

二、指令集

2.1 指令集定义

2.2 模式匹配

2.2.1 PatFrags与PatFrag

2.2.2 OutPatFrag

2.2.3 PatLeaf

2.2.4 ImmLeaf

2.2.5 IntImmLeaf和FPImmLeaf

2.2.6 Pat

2.2.7 ComplexPattern

2.3 指令合法化

2.3.1 Promote

2.3.2 Expand

2.3.3 Custom

2.3.4 Legal

2.4 模式匹配的底层实现(先记录,待扩展)

2.5 总结

三、calling convention


一、寄存器

1.1 寄存器定义

class X86Reg<string n, bits<16> Enc, list<Register> subregs = []> : Register<n> {
  let Namespace = "X86";
  let HWEncoding = Enc;
  let SubRegs = subregs;
}
def AL : X86Reg<"al", 0>;
def DL : X86Reg<"dl", 2>;
def CL : X86Reg<"cl", 1>;
def BL : X86Reg<"bl", 3>;

这是LLVM后端td文件中定义寄存器的例子,继承自Target.td文件中的Register父类,这里我们介绍一下父类Register中的两个结构,SubRegs和RegAltNameIndices,SubRegs指的是子寄存器,RegAltNameIndices表示寄存器的备用名称索引,这么解释可能不太好理解,我们看了例子就明白了。

// Target.td
class SubRegIndex<int size, int offset = 0> {
...
}

// AArch64RegisterInfo.td
class AArch64Reg<bits<16> enc, string n, list<Register> subregs = [],
               list<string> altNames = []>
        : Register<n, altNames> {
  let HWEncoding = enc;
  let Namespace = "AArch64";
  let SubRegs = subregs;
}
def bsub : SubRegIndex<8>;
def dsub : SubRegIndex<64>;
def B0    : AArch64Reg<0,   "b0">, DwarfRegNum<[64]>;
let SubRegIndices = [bsub] in {
def H0    : AArch64Reg<0,   "h0", [B0]>, DwarfRegAlias<B0>;
...
}
...
let SubRegIndices = [dsub], RegAltNameIndices = [vreg, vlist1] in {
def Q0    : AArch64Reg<0,   "q0", [D0], ["v0", ""]>, DwarfRegAlias<B0>;
...
}

我们在文章AArch64 ARM64 寄存器介绍 简单介绍过AArch64架构的寄存器,我们看一下AArch64上的浮点寄存器的一个例子。我们先定义了8位的B系列寄存器,然后定义了H系列寄存器,且指定了subregs集合是对应的B系列寄存器,这个有什么效果呢?H寄存器用一个let SubRegIndices = [bsub]包了起来,意思就是H系列寄存器的SubRegIndices属性都为bsub,然后我们再看bsub的定义,是一个SubRegIndex类型的对象,SubRegIndex对象有两个参数第一个是size第二个是offset偏移(默认是0),因此H0与B0寄存器的关系就是,H0寄存器中从偏移0的位置开始8size的内容为B0寄存器,这个与我们之前介绍的B系列与H系列寄存器的关系也相符合。

DwarfRegAlias是gdb等调试器识别的寄存器编码,这里的意思是H0与B0的编码是别名关系,也就相当于H0与B0使用一样的编码,也就是B0定义中的DwarfRegNum<[64]>的64。名字中带有Dwarf的多数都是与调试有关的一些信息。

然后我们再看Q0寄存器,我们知道AArch64上Q系列是128位的寄存器,D系列是64位的寄存器,根据上边的分析很好理解定义了Q的前64位为对应的D系列寄存器,这里还使用了AArch64Reg的第四个参数altNames属性,这个是干什么的呢?根绝之前对于AArch64寄存器的介绍,其实Q系列和V系列的寄存器都是128位的,其实是相同的寄存器,就是有时候用的不同表示,这个就是备用名的意思。

在Aarch64架构中,q0v0寄存器实际上是同一个寄存器的不同表示方式。在Aarch64中,寄存器q0v0都代表了128位的寄存器,用于存储128位的向量数据。q0Quad Register的缩写,表示一个128位的寄存器,而v0则表示一个向量寄存器。因此,q0v0在Aarch64架构中是等效的,都指代同一个128位的向量寄存器,只是在不同的上下文中可能会使用不同的命名方式。

这里是GPT对于Q和V的解释,也可以进一步参考一下。AArch64上只有1个子寄存器,我们再看一个X86寄存器的例子。

let Namespace = "X86" in {
  def sub_8bit     : SubRegIndex<8>;
  def sub_8bit_hi  : SubRegIndex<8, 8>;
  def sub_8bit_hi_phony  : SubRegIndex<8, 8>;
  def sub_16bit    : SubRegIndex<16>;
  def sub_16bit_hi : SubRegIndex<16, 16>;
  def sub_32bit    : SubRegIndex<32>;
  ...
}
// 8-bit registers, low registers
def AL : X86Reg<"al", 0>;
// 8-bit registers, high registers
def AH : X86Reg<"ah", 4>;
// 16-bit registers
let SubRegIndices = [sub_8bit, sub_8bit_hi], CoveredBySubRegs = 1 in {
def AX : X86Reg<"ax", 0, [AL,AH]>;
...
}
let isArtificial = 1 in {
...
// High word of the low 32 bits of the super-register:
def HAX   : X86Reg<"", -1>;
}
let SubRegIndices = [sub_16bit, sub_16bit_hi], CoveredBySubRegs = 1 in {
def EAX : X86Reg<"eax", 0, [AX, HAX]>, DwarfRegNum<[-2, 0, 0]>;
...
}
let SubRegIndices = [sub_32bit] in {
def RAX : X86Reg<"rax", 0, [EAX]>, DwarfRegNum<[0, -2, -2]>;

...
}

X86_64 寄存器介绍 文章中有对X86寄存器的大体的介绍。根据上边的分析,我们很容易知道,AX寄存器的低8位是AL,高8位是AH,然后这里还有一个CoveredBySubRegs = 1的属性,这个属性指的是AX的子寄存器是否完全覆盖了AX,这里AX是16位寄存器,高低8位都有对应的子寄存器,因此完全覆盖了,所以CoveredBySubRegs为true,上面AArch64的例子中只使用了低位部分,因此没有完全覆盖,CoveredBySubRegs默认为false。

然后EAX的定义中低16位为AX,高16位为HAX,HAX定义中有一个isArtificial=1的属性,这个指的是这个寄存器是“人工”的,寄存器可以被指定为“人工”,这意味着它在关联架构中没有对应的物理寄存器。这对于创建与实际(非人工)寄存器的无法寻址部分对应的寄存器可能很有用。

DwarfRegNum定义了debug信息,用于gcc、gdb或者一个调试信息输入来标识一个寄存器。对于寄存器EAX,DwarfRegNum携带三个值的数组表示三种不同的模式:第一个元素用来表示X86-64,第二个用来在X86-32上的异常处理(EH),第三个是一般用途。-1是个表示gcc数字没有定义,-2表示该模式下寄存器号无效。

1.2 寄存器分类

// RegisterClass - Now that all of the registers are defined, and aliases
// between registers are defined, specify which registers belong to which
// register classes.  This also defines the default allocation order of
// registers by register allocators.
//
class RegisterClass<string namespace, list<ValueType> regTypes, int alignment,
                    dag regList, RegAltNameIndex idx = NoRegAltName>
  : DAGOperand {
...
}
  • 参数一:namespace的名字,一般指的是架构名
  • 参数二:第二个参数是在include/llvm/CodeGen/ValueTypes.td中定义的ValueType寄存器类型值的列表。定义的值包括整数类型(例如i16、i32和i1)、浮点类型(f32、f64)和向量类型(例如v8i16表示8 x i16向量)。RegisterClass中的所有寄存器必须具有相同的ValueType,但某些寄存器可能以不同的配置存储矢量数据。例如,一个能处理128位矢量的寄存器可能能够处理16个8位整数元素、8个16位整数、4个32位整数等。
  • 参数三:指定了将寄存器存储或加载到内存时所需的对齐方式。
  • 参数四:最后一个参数regList指定了属于这个类的寄存器。如果未指定替代的分配顺序方法,则regList还定义了寄存器分配器使用的分配顺序。除了简单地列出寄存器(如(add R0, R1, ...)),还可以使用更高级的集合运算符。
def FPR64 : RegisterClass<"AArch64", [f64, i64, v2f32, v1f64, v8i8, v4i16, v2i32,
                                      v1i64, v4f16, v4bf16],
                                     64, (sequence "D%u", 0, 31)>;
// For tail calls, we can't use callee-saved registers, as they are restored
// to the saved value before the tail call, which would clobber a call address.
// This is for indirect tail calls to store the address of the destination.
def tcGPR64 : RegisterClass<"AArch64", [i64], 64, (sub GPR64common, X19, X20, X21,
                                                     X22, X23, X24, X25, X26,
                                                     X27, X28, FP, LR)>;

例如AArch64中的FPR64和tcGPR64这两个类,FPR64是一个64位浮点类型的寄存器类,sequence "D%u", 0, 31表示了D0-D31这32个寄存器,然后支持多种类型,我们能看到这些类型都是64位的各种整形、浮点及向量类型,因此寄存器可以支持不同的类型。

tcGPR64是个用于尾调用的寄存器类,这里做了一个集合的运算,将GPR64common寄存器类中的寄存器减去后边列出来的那些寄存器(callee saved寄存器)就是tcGPR64里边的寄存器。集合运算的话也可以做多次,例如我们sub之后可以再add,这些都是允许的操作。

二、指令集

2.1 指令集定义

  • Target.td — 定义了指令、操作数、InstrInfo等基本类的地方。
  • TargetSelectionDAG.td — 用于SelectionDAG指令选择生成器,包含SDTC*类(选择DAG类型约束)、SelectionDAG节点的定义(如imm、cond、bb、add、fadd、sub)以及模式支持(Pattern、Pat、PatFrag、PatLeaf、ComplexPattern)。
  • XXXInstrFormats.td — 用于定义特定目标指令的模式。
  • XXXInstrInfo.td — 指定了特定目标的指令模板、条件码和指令集的指令。对于架构修改,可能会使用不同的文件名。例如,对于带有SSE指令的奔腾处理器,该文件名为X86InstrSSE.td,对于带有MMX的奔腾处理器,该文件名为X86InstrMMX.td。

上述是指令集定义涉及到的几个文件,Target.td和TargetSelectionDAG.td是指令集相关接口的父类所在的文件,我们的工作是继承所需的功能然后实现我们要定制化的指令。

class Instruction {
  string Namespace = "";
  dag OutOperandList;    // A dag containing the MI def operand list.
  dag InOperandList;     // A dag containing the MI use operand list.
  string AsmString = ""; // The .s format to print the instruction with.
  list<dag> Pattern;     // Set to the DAG pattern for this instruction.
  list<Register> Uses = [];
  list<Register> Defs = [];
  list<Predicate> Predicates = [];  // predicates turned into isel match code
  ... remainder not shown for space ...
}

这是Instruction父类的定义,架构手册中的单条指令通常被建模为多个目标指令,取决于其操作数。例如,手册可能描述一个加法指令,它接受寄存器或立即数操作数。LLVM目标可以使用两个指令ADDri和ADDrr来模拟这种情况。

// Class specifying the SSE execution domain, used by the SSEDomainFix pass.
// Keep in sync with tables in X86InstrInfo.cpp.
class Domain<bits<2> val> {
  bits<2> Value = val;
}
def GenericDomain   : Domain<0>;
def SSEPackedSingle : Domain<1>;
def SSEPackedDouble : Domain<2>;
def SSEPackedInt    : Domain<3>;
class X86Inst<bits<8> opcod, Format f, ImmType i, dag outs, dag ins,
              string AsmStr, Domain d = GenericDomain>
  : Instruction {
  let Namespace = "X86";

  bits<8> Opcode = opcod;
  Format Form = f;
...
}
class I<bits<8> o, Format f, dag outs, dag ins, string asm,
        list<dag> pattern, Domain d = GenericDomain>
  : X86Inst<o, f, NoImm, outs, ins, asm, d> {
  let Pattern = pattern;
  let CodeSize = 3;
}
def MOV8rr  : I<0x88, MRMDestReg, (outs GR8 :$dst), (ins GR8 :$src),
                "mov{b}\t{$src, $dst|$dst, $src}", []>;
def MOV16rr : I<0x89, MRMDestReg, (outs GR16:$dst), (ins GR16:$src),
                "mov{w}\t{$src, $dst|$dst, $src}", []>, OpSize16;
def MOV32rr : I<0x89, MRMDestReg, (outs GR32:$dst), (ins GR32:$src),
                "mov{l}\t{$src, $dst|$dst, $src}", []>, OpSize32;
def MOV64rr : RI<0x89, MRMDestReg, (outs GR64:$dst), (ins GR64:$src),
                 "mov{q}\t{$src, $dst|$dst, $src}", []>;

第一个参数表示了操作数,第二个参数表示了指令类型,第三个参数表示了指令输出,第四个参数表示了指令输入,第五个参数表示了匹配模式,第五个参数是X86定一的表示是否是SSE指令集,默认不是。

这里是X86架构上对于各个size的mov指令的定义,MOV8rr等是mov指令在Machine code中的表现形式,movb等是输出到汇编中的形式,我们能看到指令定义的第二行的汇编表示形式用|符号链接了两句,这不是要输出两句,这是定义了两种汇编输出形式,AT&T和Intel形式。

2.2 模式匹配

当前存在的其中模式匹配的类型:

2.2.1 PatFrags与PatFrag

//===----------------------------------------------------------------------===//
// Selection DAG Node Transformation Functions.
//
// This mechanism allows targets to manipulate nodes in the output DAG once a
// match has been formed.  This is typically used to manipulate immediate
// values.
//
class SDNodeXForm<SDNode opc, code xformFunction> {
  SDNode Opcode = opc;
  code XFormFunction = xformFunction;
}
def NOOP_SDNodeXForm : SDNodeXForm<imm, [{}]>;
// Selection DAG Pattern Operations
class SDPatternOperator {
  list<SDNodeProperty> Properties = [];
}
/// PatFrags - Represents a set of pattern fragments.  Each single fragment
/// can match something on the DAG, from a single node to multiple nested other
/// fragments.   The whole set of fragments matches if any of the single
/// fragments match.  This allows e.g. matching and "add with overflow" and
/// a regular "add" with the same fragment set.
///
class PatFrags<dag ops, list<dag> frags, code pred = [{}],
               SDNodeXForm xform = NOOP_SDNodeXForm> : SDPatternOperator {
  dag Operands = ops;
  list<dag> Fragments = frags;
  code PredicateCode = pred;
  code GISelPredicateCode = [{}];
  code ImmediateCode = [{}];
  SDNodeXForm OperandTransform = xform;
...
}
// PatFrag - A version of PatFrags matching only a single fragment.
class PatFrag<dag ops, dag frag, code pred = [{}],
              SDNodeXForm xform = NOOP_SDNodeXForm>
  : PatFrags<ops, [frag], pred, xform>;

PatFrags与PatFrag的区别是很好区分的,PatFrags是多模式匹配,多个匹配规则中只要有一个匹配上就算匹配成功,PatFrag是单模式匹配,只有一个匹配规则。他们的参数都完全一样的。第一个参数指的是指令的操作数,第二个参数指的是匹配规则,PatFrags的这个参数类型是个list,表示可以有多个匹配规则,第三个参数是code类型,指的是调用cpp代码里边生成的一些谓词条件,第四个参数主要是用于表示立即数的。比较重要的是前两个参数,也是较常用的两个参数。

def AArch64sabd     : PatFrags<(ops node:$lhs, node:$rhs),
                               [(abds node:$lhs, node:$rhs),
                                (int_aarch64_neon_sabd node:$lhs, node:$rhs)]>;
defm SABD     : SIMDThreeSameVectorBHS<0,0b01110,"sabd", AArch64sabd>;

这是AArch64中对于一个PatFrags使用的例子,SABD指令在AArch64架构中代表"Signed Absolute Difference",用于计算两个操作数的绝对差值,并保留符号。AArch64sabd匹配模式中有两条匹配规则,只要有一个匹配上了,就认为匹配成功,然后生成对应的sabd指令。

2.2.2 OutPatFrag

// OutPatFrag is a pattern fragment that is used as part of an output pattern
// (not an input pattern). These do not have predicates or transforms, but are
// used to avoid repeated subexpressions in output patterns.
class OutPatFrag<dag ops, dag frag>
 : PatFrag<ops, frag, [{}], NOOP_SDNodeXForm>;

// PPCInstr64Bit.td
def i64not : OutPatFrag<(ops node:$in),
                        (NOR8 $in, $in)>;
def        : Pat<(not i64:$in),
                 (i64not $in)>;

OutPatFrag主要是用于输出匹配的,经常作为Pat的result项,如果not i64的形式匹配上了,那么在PPC架构下展开成NOR8指令。

2.2.3 PatLeaf

// PatLeaf's are pattern fragments that have no operands.  This is just a helper
// to define immediates and other common things concisely.
class PatLeaf<dag frag, code pred = [{}], SDNodeXForm xform = NOOP_SDNodeXForm>
 : PatFrag<(ops), frag, pred, xform>;

// X86InstrInfo.td
def MOV8mi  : Ii8 <0xC6, MRM0m, (outs), (ins i8mem :$dst, i8imm :$src),
                   "mov{b}\t{$src, $dst|$dst, $src}",
                   [(store (i8 imm_su:$src), addr:$dst)]>;
def imm_su : PatLeaf<(imm), [{
    return !shouldAvoidImmediateInstFormsForSize(N);
}]>;

PatLeaf继承自PatFrag,操作数项为空,主要是通过frag和pred来进行匹配。例如,对于X86中的这个指令和匹配规则,MOV8mi的匹配规则是匹配一条store指令,然后store指令的src项要满足imm_su这条匹配规则。imm_su这条匹配的是一个不满足cpp内定义的shouldAvoidImmediateInstFormsForSize这个函数结果的立即数(shouldAvoidImmediateInstFormsForSize是X86ISelDAGToDAG.cpp内的函数),如果全部匹配成功就会生成MOV8mi指令。

2.2.4 ImmLeaf

// ImmLeaf is a pattern fragment with a constraint on the immediate.  The
// constraint is a function that is run on the immediate (always with the value
// sign extended out to an int64_t) as Imm.  For example:
//
//  def immSExt8 : ImmLeaf<i16, [{ return (char)Imm == Imm; }]>;
//
// this is a more convenient form to match 'imm' nodes in than PatLeaf and also
// is preferred over using PatLeaf because it allows the code generator to
// reason more about the constraint.
//
// If FastIsel should ignore all instructions that have an operand of this type,
// the FastIselShouldIgnore flag can be set.  This is an optimization to reduce
// the code size of the generated fast instruction selector.
class ImmLeaf<ValueType vt, code pred, SDNodeXForm xform = NOOP_SDNodeXForm,
              SDNode ImmNode = imm>
  : PatFrag<(ops), (vt ImmNode), [{}], xform> {
  let ImmediateCode = pred;
  bit FastIselShouldIgnore = false;

  // Is the data type of the immediate an APInt?
  bit IsAPInt = false;

  // Is the data type of the immediate an APFloat?
  bit IsAPFloat = false;
}

// X86InstrInfo.td
def i16immSExt8  : ImmLeaf<i16, [{ return isInt<8>(Imm); }]>;

ImmLeaf是专门用于立即数的匹配模式,它继承自PatFrag类,操作数为空,第一个参数是一个类型,第二个参数是code代码,也就是主要的匹配规则。例如X86这里的匹配规则就是判断一个i16类型的立即数是不是Int8范围内的数。

2.2.5 IntImmLeaf和FPImmLeaf

// An ImmLeaf except that Imm is an APInt. This is useful when you need to
// zero-extend the immediate instead of sign-extend it.
//
// Note that FastISel does not currently understand IntImmLeaf and will not
// generate code for rules that make use of it. As such, it does not make sense
// to replace ImmLeaf with IntImmLeaf. However, replacing PatLeaf with an
// IntImmLeaf will allow GlobalISel to import the rule.
class IntImmLeaf<ValueType vt, code pred, SDNodeXForm xform = NOOP_SDNodeXForm>
    : ImmLeaf<vt, pred, xform> {
  let IsAPInt = true;
  let FastIselShouldIgnore = true;
}

// An ImmLeaf except that Imm is an APFloat.
//
// Note that FastISel does not currently understand FPImmLeaf and will not
// generate code for rules that make use of it.
class FPImmLeaf<ValueType vt, code pred, SDNodeXForm xform = NOOP_SDNodeXForm>
  : ImmLeaf<vt, pred, xform, fpimm> {
  let IsAPFloat = true;
  let FastIselShouldIgnore = true;
}

IntImmLeaf和FPImmLeaf是继承自ImmLeaf类的,分别用整数立即数和浮点立即数的匹配模式,用法上与ImmLeaf相同。

2.2.6 Pat

class Pattern<dag patternToMatch, list<dag> resultInstrs> {
  dag             PatternToMatch  = patternToMatch;
  list<dag>       ResultInstrs    = resultInstrs;
  list<Predicate> Predicates      = [];  // See class Instruction in Target.td.
  int             AddedComplexity = 0;   // See class Instruction in Target.td.
}

// Pat - A simple (but common) form of a pattern, which produces a simple result
// not needing a full list.
class Pat<dag pattern, dag result> : Pattern<pattern, [result]>;

// X86InstrInfo.td
def : Pat<(i32 relocImm:$src), (MOV32ri relocImm:$src)>;

Pat使用起来很简单,一共就是两个操作数,第一个是要匹配的形式,第二个是要输出的结果。X86的这个例子就是如果匹配到满足relocImm规则的i32的立即数,那么就将其转化成对应的MOV32ri指令。

Pat匹配模式使用起来比较简单,但是千万不要低估他,很有用。它可以匹配多条指令复合的形式,来生成对应结果的一条指令,做一些架构上的指令优化。它还可以匹配一些当前架构不支持的指令,然后生成当前架构支持的对应功能的指令(可能是多条指令)。

2.2.7 ComplexPattern

// Complex patterns, e.g. X86 addressing mode, requires pattern matching code
// in C++. NumOperands is the number of operands returned by the select function;
// SelectFunc is the name of the function used to pattern match the max. pattern;
// RootNodes are the list of possible root nodes of the sub-dags to match.
// e.g. X86 addressing mode - def addr : ComplexPattern<4, "SelectAddr", [add]>;
//
class ComplexPattern<ValueType ty, int numops, string fn,
                     list<SDNode> roots = [], list<SDNodeProperty> props = [],
                     int complexity = -1> {
  ValueType Ty = ty;
  int NumOperands = numops;
  string SelectFunc = fn;
  list<SDNode> RootNodes = roots;
  list<SDNodeProperty> Properties = props;
  int Complexity = complexity;
}

// X86InstrInfo.td
// Define X86-specific addressing mode.
def addr      : ComplexPattern<iPTR, 5, "selectAddr", [], [SDNPWantParent]>;
def lea32addr : ComplexPattern<i32, 5, "selectLEAAddr",
                               [add, sub, mul, X86mul_imm, shl, or, xor, frameindex],
                               []>;

// X86ISelDAGToDAG.cpp
bool selectAddr(SDNode *Parent, SDValue N, SDValue &Base,
                    SDValue &Scale, SDValue &Index, SDValue &Disp,
                    SDValue &Segment);
bool selectLEAAddr(SDValue N, SDValue &Base,
                       SDValue &Scale, SDValue &Index, SDValue &Disp,
                       SDValue &Segment);

我们上边介绍的各种匹配模式的功能总归是有限的,有时候可能不能满足我们的需求,这个时候我们可以考虑用ComplexPattern匹配模式。第一个参数是类型第二个参数是操作数的数目,第三个函数是匹配函数,第四个参数是根节点,第五个参数是SDNode的一些属性,第六个参数是复杂度。

addr的匹配规则是selectAddr这个函数,我们能看到操作数是5,但是函数的实际操作数是7,这是为什么呢?这是因为复杂模式匹配自带一个SDValue N这个操作数,然后addr又指定了需要父节点的这个属性,因此会多一个SDNode *Parent的参数,因此共有7个参数。下方的lea32addr就只有6个参数。

2.3 指令合法化

LLVM中如何检查目标架构原生支持的操作,并为没有原生支持的操作添加回调函数到XXXTargetLowering类的构造函数中,以便指令选择过程知道如何处理。TargetLowering类的回调方法(声明在llvm/Target/TargetLowering.h中)包括:

  • setOperationAction — 通用操作。
  • setLoadExtAction — 带扩展的加载。
  • setTruncStoreAction — 截断存储。
  • setIndexedLoadAction — 索引加载。
  • setIndexedStoreAction — 索引存储。
  • setConvertAction — 类型转换。
  • setCondCodeAction — 支持给定条件码。

这些回调用于确定操作是否适用于指定类型(或类型)。在所有情况下,第三个参数是LegalAction类型的枚举值:Promote、Expand、Custom或Legal。

2.3.1 Promote

对于没有对给定类型进行本地支持的操作,指定的类型可能会被提升为一个更大的支持的类型。例如,SPARC不支持对布尔值(i1类型)进行符号扩展加载,因此在SparcISelLowering.cpp中,下面的第三个参数,Promote,会在加载之前将i1类型的值更改为一个Sparc架构所支持的更大的类型。

setLoadExtAction(ISD::SEXTLOAD, MVT::i1, Promote);

2.3.2 Expand

对于没有本地支持的类型,一个值可能需要进一步分解,而不是提升。对于没有本地支持的操作,可能需要使用其他操作的组合来达到类似的效果。在SPARC中,浮点正弦和余弦三角函数操作通过扩展到其他操作来实现支持,这由setOperationAction的第三个参数Expand指示。

setOperationAction(ISD::FSIN, MVT::f32, Expand);
setOperationAction(ISD::FCOS, MVT::f32, Expand);

2.3.3 Custom

对于某些操作,简单的类型提升或操作扩展可能不足够。在某些情况下,必须实现特殊的内部函数。

例如,常量值可能需要特殊处理,或者某个操作可能需要在堆栈中溢出和恢复寄存器,并与寄存器分配器一起工作。

如下所示,在X86ISelLowering.cpp代码中,要将处理绝对值运算,首先应该调用setOperationAction,将Custom作为第三个参数:

// Integer absolute.
  if (Subtarget.canUseCMOV()) {
    setOperationAction(ISD::ABS            , MVT::i16  , Custom);
    setOperationAction(ISD::ABS            , MVT::i32  , Custom);
    if (Subtarget.is64Bit())
      setOperationAction(ISD::ABS          , MVT::i64  , Custom);
  }

在LowerOperation方法中,对于每个Custom操作,应添加一个case语句来指示调用哪个函数。在下面的代码中,ABS操作码将调用LowerABS方法:

case ISD::ABS:                return LowerABS(Op, Subtarget, DAG);

最后,实现LowerABS方法。

2.3.4 Legal

在SparcISelLowering.cpp中,LegalizeAction枚举值Legal表示操作具有本地支持的默认条件,因此很少使用。对于CTPOP(用于计算整数中设置的位数的操作),仅在SPARC v9中本地支持。以下代码为非v9 SPARC实现启用了Expand转换技术。

setOperationAction(ISD::CTPOP, MVT::i32, Expand);
...
if (TM.getSubtarget<SparcSubtarget>().isV9())
  setOperationAction(ISD::CTPOP, MVT::i32, Legal);

2.4 模式匹配的底层实现(先记录,待扩展)

LLVM模式匹配的代码主要在CodeGenDAGPatterns.cpp这个文件里。

LLVM模式匹配通过模式匹配树来实现的,具体实现逻辑待日后进一步补充,这里先记录下。

2.5 总结

以上就是对后端指令集和匹配模式等的简单介绍。td文件的功能总归是有限的,llvm希望将所有架构相关的内容都在td文件里实现,不过目前还没有彻底实现这个想法,因此对于一些复杂的需求的话我们需要在XXXInstrInfo.cpp文件里进行进一步实现。

三、calling convention

def CC_AArch64_AAPCS : CallingConv<[
  CCIfType<[iPTR], CCBitConvertToType<i64>>,
  CCIfType<[v2f32], CCBitConvertToType<v2i32>>,
  CCIfType<[v2f64, v4f32], CCBitConvertToType<v2i64>>,

  // Big endian vectors must be passed as if they were 1-element vectors so that
  // their lanes are in a consistent order.
  CCIfBigEndian<CCIfType<[v2i32, v2f32, v4i16, v4f16, v4bf16, v8i8],
                         CCBitConvertToType<f64>>>,
  CCIfBigEndian<CCIfType<[v2i64, v2f64, v4i32, v4f32, v8i16, v8f16, v8bf16, v16i8],
                         CCBitConvertToType<f128>>>,

  // In AAPCS, an SRet is passed in X8, not X0 like a normal pointer parameter.
  // However, on windows, in some circumstances, the SRet is passed in X0 or X1
  // instead.  The presence of the inreg attribute indicates that SRet is
  // passed in the alternative register (X0 or X1), not X8:
  // - X0 for non-instance methods.
  // - X1 for instance methods.

  // The "sret" attribute identifies indirect returns.
  // The "inreg" attribute identifies non-aggregate types.
  // The position of the "sret" attribute identifies instance/non-instance
  // methods.
  // "sret" on argument 0 means non-instance methods.
  // "sret" on argument 1 means instance methods.

  CCIfInReg<CCIfType<[i64],
    CCIfSRet<CCIfType<[i64], CCAssignToReg<[X0, X1]>>>>>,

  CCIfSRet<CCIfType<[i64], CCAssignToReg<[X8]>>>,

  // Put ByVal arguments directly on the stack. Minimum size and alignment of a
  // slot is 64-bit.
  CCIfByVal<CCPassByVal<8, 8>>,

  // The 'nest' parameter, if any, is passed in X18.
  // Darwin uses X18 as the platform register and hence 'nest' isn't currently
  // supported there.
  CCIfNest<CCAssignToReg<[X18]>>,

  // Pass SwiftSelf in a callee saved register.
  CCIfSwiftSelf<CCIfType<[i64], CCAssignToReg<[X20]>>>,

  // A SwiftError is passed in X21.
  CCIfSwiftError<CCIfType<[i64], CCAssignToReg<[X21]>>>,

  // Pass SwiftAsync in an otherwise callee saved register so that it will be
  // preserved for normal function calls.
  CCIfSwiftAsync<CCIfType<[i64], CCAssignToReg<[X22]>>>,

  CCIfConsecutiveRegs<CCCustom<"CC_AArch64_Custom_Block">>,

  CCIfType<[nxv16i8, nxv8i16, nxv4i32, nxv2i64, nxv2f16, nxv4f16, nxv8f16,
            nxv2bf16, nxv4bf16, nxv8bf16, nxv2f32, nxv4f32, nxv2f64],
           CCAssignToReg<[Z0, Z1, Z2, Z3, Z4, Z5, Z6, Z7]>>,
  CCIfType<[nxv16i8, nxv8i16, nxv4i32, nxv2i64, nxv2f16, nxv4f16, nxv8f16,
            nxv2bf16, nxv4bf16, nxv8bf16, nxv2f32, nxv4f32, nxv2f64],
           CCPassIndirect<i64>>,

  CCIfType<[nxv1i1, nxv2i1, nxv4i1, nxv8i1, nxv16i1],
           CCAssignToReg<[P0, P1, P2, P3]>>,
  CCIfType<[nxv1i1, nxv2i1, nxv4i1, nxv8i1, nxv16i1],
           CCPassIndirect<i64>>,

  // Handle i1, i8, i16, i32, i64, f32, f64 and v2f64 by passing in registers,
  // up to eight each of GPR and FPR.
  CCIfType<[i1, i8, i16], CCPromoteToType<i32>>,
  CCIfType<[i32], CCAssignToReg<[W0, W1, W2, W3, W4, W5, W6, W7]>>,
  // i128 is split to two i64s, we can't fit half to register X7.
  CCIfType<[i64], CCIfSplit<CCAssignToRegWithShadow<[X0, X2, X4, X6],
                                                    [X0, X1, X3, X5]>>>,

  // i128 is split to two i64s, and its stack alignment is 16 bytes.
  CCIfType<[i64], CCIfSplit<CCAssignToStackWithShadow<8, 16, [X7]>>>,

  CCIfType<[i64], CCAssignToReg<[X0, X1, X2, X3, X4, X5, X6, X7]>>,
  CCIfType<[f16], CCAssignToReg<[H0, H1, H2, H3, H4, H5, H6, H7]>>,
  CCIfType<[bf16], CCAssignToReg<[H0, H1, H2, H3, H4, H5, H6, H7]>>,
  CCIfType<[f32], CCAssignToReg<[S0, S1, S2, S3, S4, S5, S6, S7]>>,
  CCIfType<[f64], CCAssignToReg<[D0, D1, D2, D3, D4, D5, D6, D7]>>,
  CCIfType<[v1i64, v2i32, v4i16, v8i8, v1f64, v2f32, v4f16, v4bf16],
           CCAssignToReg<[D0, D1, D2, D3, D4, D5, D6, D7]>>,
  CCIfType<[f128, v2i64, v4i32, v8i16, v16i8, v4f32, v2f64, v8f16, v8bf16],
           CCAssignToReg<[Q0, Q1, Q2, Q3, Q4, Q5, Q6, Q7]>>,

  // If more than will fit in registers, pass them on the stack instead.
  CCIfType<[i1, i8, i16, f16, bf16], CCAssignToStack<8, 8>>,
  CCIfType<[i32, f32], CCAssignToStack<8, 8>>,
  CCIfType<[i64, f64, v1f64, v2f32, v1i64, v2i32, v4i16, v8i8, v4f16, v4bf16],
           CCAssignToStack<8, 8>>,
  CCIfType<[f128, v2i64, v4i32, v8i16, v16i8, v4f32, v2f64, v8f16, v8bf16],
           CCAssignToStack<16, 16>>
]>;

这是AArch64CallingConvention.td文件内一条AArch64的calling convention的定义,我们简单来分析下。

/// CCIfType - If the current argument is one of the specified types, apply
/// Action A.
class CCIfType<list<ValueType> vts, CCAction A> : CCPredicateAction<A> {
  list<ValueType> VTs = vts;
}
/// CCBitConvertToType - If applied, this bitconverts the specified current
/// value to the specified type.
class CCBitConvertToType<ValueType destTy> : CCAction {
  ValueType DestTy = destTy;
}

CCIfType<[iPTR], CCBitConvertToType<i64>>
CCIfType<[v2f32], CCBitConvertToType<v2i32>>
CCIfType<[v2f64, v4f32], CCBitConvertToType<v2i64>>

CCBitConvertToType就是当做目的类型来处理,这三条的意思是对于地址类型当做i64来处理,对于v2f32向量类型当做v2i32向量类型来处理,第三条同理。

/// CCAssignToReg - This action matches if there is a register in the specified
/// list that is still available.  If so, it assigns the value to the first
/// available register and succeeds.
class CCAssignToReg<list<Register> regList> : CCAction {
  list<Register> RegList = regList;
}
/// CCIfSRet - If this argument is marked with the 'sret' attribute, apply
/// the specified action.
class CCIfSRet<CCAction A> : CCIf<"ArgFlags.isSRet()", A> {}

CCIfSRet<CCIfType<[i64], CCAssignToReg<[X8]>>>

这里的意思是,如果这是个sret的参数的话,又是个i64类型的话,如果X8寄存器目前可用的话我们就将其存到X8寄存器中,不可用的话一般会存到栈中。注意,这里不是严格的i64类型,因为前边我们对于指针类型的也是当做i64来处理的。

/// CCIfByVal - If the current argument has ByVal parameter attribute, apply
/// Action A.
class CCIfByVal<CCAction A> : CCIf<"ArgFlags.isByVal()", A> {
}
/// CCPassByVal - This action always matches: it assigns the value to a stack
/// slot to implement ByVal aggregate parameter passing. Size and alignment
/// specify the minimum size and alignment for the stack slot.
class CCPassByVal<int size, int align> : CCAction {
  int Size = size;
  int Align = align;
}


// Put ByVal arguments directly on the stack. Minimum size and alignment of a
// slot is 64-bit.
CCIfByVal<CCPassByVal<8, 8>>

这里的意思是,对于一个ByVal的参数我们将其存到栈上。

/// CCCustom - Calls a custom arg handling function.
class CCCustom<string fn> : CCAction {
  string FuncName = fn;
}
/// CCIfConsecutiveRegs - If the current argument has InConsecutiveRegs
/// parameter attribute, apply Action A.
class CCIfConsecutiveRegs<CCAction A> : CCIf<"ArgFlags.isInConsecutiveRegs()", A> {
}

CCIfConsecutiveRegs<CCCustom<"CC_AArch64_Custom_Block">>

这里的意思是如果当前的参数满足这个条件的话,那么我们会调用CC_AArch64_Custom_Block这个参数处理函数,CC_AArch64_Custom_Block这个函数在AArch64CallingConvention.cpp文件内。

/// CCPromoteToType - If applied, this promotes the specified current value to
/// the specified type.
class CCPromoteToType<ValueType destTy> : CCAction {
  ValueType DestTy = destTy;
}

CCIfType<[i1, i8, i16], CCPromoteToType<i32>>,
CCIfType<[i32], CCAssignToReg<[W0, W1, W2, W3, W4, W5, W6, W7]>>,

这里的意思是将i1、i8、i16扩展成i32来处理,对于i32类型,将其按顺序存到可用的W0-W7寄存器中,W0-W7也就是AArch64中32位的整形参数寄存器。

CallingConvention.td文件中还有个比较常用的语法CCDelegateTo,我们也简单介绍下。

/// CCDelegateTo - This action invokes the specified sub-calling-convention.  It
/// is successful if the specified CC matches.
class CCDelegateTo<CallingConv cc> : CCAction {
  CallingConv CC = cc;
}

def CC_AArch64_Win64_VarArg : CallingConv<[
  CCIfType<[f16, bf16], CCBitConvertToType<i16>>,
  CCIfType<[f32], CCBitConvertToType<i32>>,
  CCIfType<[f64], CCBitConvertToType<i64>>,
  CCDelegateTo<CC_AArch64_AAPCS>
]>;

对于这个calling convention,将f16和bf16当做i16来处理,将f32当做i32来处理,将f64当做i64来处理,做完这些之后我们再使用CC_AArch64_AAPCS这个calling convention。

def RetCC_X86_32 : CallingConv<[
  // If FastCC, use RetCC_X86_32_Fast.
  CCIfCC<"CallingConv::Fast", CCDelegateTo<RetCC_X86_32_Fast>>,
  CCIfCC<"CallingConv::Tail", CCDelegateTo<RetCC_X86_32_Fast>>,
  // CFGuard_Check never returns a value so does not need a RetCC.
  // If HiPE, use RetCC_X86_32_HiPE.
  CCIfCC<"CallingConv::HiPE", CCDelegateTo<RetCC_X86_32_HiPE>>,
  CCIfCC<"CallingConv::X86_VectorCall", CCDelegateTo<RetCC_X86_32_VectorCall>>,
  CCIfCC<"CallingConv::X86_RegCall", CCDelegateTo<RetCC_X86_32_RegCall>>,

  // Otherwise, use RetCC_X86_32_C.
  CCDelegateTo<RetCC_X86_32_C>
]>;

这是X86CallingConvention.td文件内的,CCDelegateTo也可以这样使用。对于获取到的不同的calling convention我们使用对应的calling convention策略,都没匹配上的话,就使用最后这个策略,有点类似于switch case default。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/714670.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

异常向量表的设置

1、Linux Kernel中对异常向量表的填充 linux/arch/arm64/kernel/entry.S kernel_ventry 是一个定义异常向量的宏&#xff1b; 在该宏中&#xff0c;程序跳转到了b el\el\ht()\regsize()\label; 以为异常向量的第6行为例&#xff0c;其实就是跳转到了bl el1h_64_irq; 然后你去搜…

YOLOv10涨点改进SPPF创新结构,重新设计全局平均池化层和全局最大池化层,增强全局视角信息和不同尺度大小的特征

本文改进:SPPF_improve利用全局平均池化层和全局最大池化层,加入一些全局背景信息和边缘信息,从而获取全局视角信息并减轻不同尺度大小所带来的影响,强烈推荐,适合直接使用,paper创新级。 目录 1,YOLOv10介绍 1.1 C2fUIB介绍 1.2 PSA介绍 1.3 SCDown 2.SPP &SP…

哇塞,超好吃的麻辣片,一口就爱上

最近&#xff0c;我发现了一款让人欲罢不能的美食——食家巷麻辣片&#xff01;&#x1f60d; 一打开包装&#xff0c;那浓郁的麻辣香气就扑鼻而来&#xff0c;瞬间刺激着我的嗅觉神经。&#x1f603;食家巷麻辣片的外观色泽鲜艳&#xff0c;红通通的一片&#xff0c;看着就特…

计算机组成原理之定点除法

文章目录 定点除法运算原码恢复余数法原码不恢复余数法&#xff08;加减交替法&#xff09;运算规则 习题 定点除法运算 注意 &#xff08;1&#xff09;被除数小于除数的时候&#xff0c;商0 &#xff08;2&#xff09;接下来&#xff0c;有一个除数再原来的基础上&#xff0c…

Linux2-系统自有服务防火墙与计划任务

一、什么是防火墙 防火墙主要用于防范网络攻击&#xff0c;防火墙一般分为软件防火墙、硬件防火墙 1、Windows中的防护墙设置 2、防火墙的作用 3、Linux中的防火墙分类 Centos6、Centos6>防火墙>iptables防火墙 防火墙系统管理工具 Centos7>防火墙>firewalld防火…

单目标应用:基于三角拓扑聚合优化算法TTAO的微电网优化(MATLAB代码)

一、微电网模型介绍 微电网多目标优化调度模型简介_vmgpqv-CSDN博客 参考文献&#xff1a; [1]李兴莘,张靖,何宇,等.基于改进粒子群算法的微电网多目标优化调度[J].电力科学与工程, 2021, 37(3):7 二、三角拓扑聚合优化算法求解微电网 2.1算法简介 三角拓扑聚合优化算法&…

USB2.0高速转接芯片CH347应用开发手册

CH347应用开发手册 V1.3 一、简介 CH347是一款USB2.0高速转接芯片&#xff0c;以实现USB-UART(HID串口/VCP串口)、USB-SPI、USB-I2C、USB-JTAG以及USB-GPIO等接口&#xff0c;分别包含在芯片的四种工作模式中。 CH347DLL用于为CH347芯片提供操作系统端的UART/SPI/I2C/JTAG/B…

Windows11和Ubuntu22双系统安装指南

一、需求描述 台式机电脑&#xff0c;已有Windows11操作系统&#xff0c;想要安装Ubuntu22系统&#xff08;版本任意&#xff09;。其中Windows安装在Nvme固态上&#xff0c;Ubuntu安装在Sata固态上&#xff0c;双盘双系统。开机时使用Grub控制进入哪个系统&#xff0c;效果图…

Win10“始终使用此应用打开”不见了怎么办?

问题背景 真是服了&#xff0c;昨天家里停电把我电脑系统盘固态烧掉了&#xff0c;于是换了个新的固态给电脑装上新系统。结果这个版本的Win10系统居然无法修改默认应用。具体问题见下面两个图&#xff0c;以py文件为例。 图一&#xff1a;“选择打开方式时没有始终使用此应用…

大模型-人类病理学的语言视觉AI助手

论文摘要翻译与评论 论文标题&#xff1a; A Multimodal Generative AI Copilot for Human Pathology 摘要翻译&#xff1a; 计算病理学领域已经在任务特定的预测模型和任务无关的自监督视觉编码器的发展方面取得了显著进展。然而&#xff0c;尽管生成性人工智能快速增长&a…

史上最详细的轨迹优化教程-机器人避障及轨迹平滑实现(干货满满)

有一些朋友问我到底如何用优化方法实现轨迹优化&#xff08;避障轨迹平滑等&#xff09;&#xff0c;今天就出一个干货满满的教程&#xff0c;绝对是面向很多工业化场景的讲解&#xff0c;为了便于理解&#xff0c;我选用二维平面并给出详细代码实现&#xff0c;三维空间原理相…

MySQL数据操作与查询- 聚合函数和分组查询

一、聚合函数 聚合函数主要用来进行数据 汇总 。 1、sum 返回选取的某列的总和。 语法&#xff1a; select sum(字段名) from 表名 where 条件表达式 2、max 返回选取的某列的最大值。 语法&#xff1a; select max(字段名) from 表名 where 条件表达式 3、min 返…

LoadBalance客户端负载均衡

1. 前言Ribbon Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端 负载均衡的工具。简单的说&#xff0c;Ribbon是Netflix发布的开源项目&#xff0c;主要功能是提供客户端的软件负载均衡算法和服务调用。Ribbon客户端组件提供一系列完善的配置项如连接超时&#xff0…

VMware虚拟机linux无法使用ifconfig的解决方法

在有些linux系统中&#xff0c;输入ifconfig会报错&#xff0c;这是为什么呢&#xff1f; 如果出现 那是说明&#xff0c;你的linux内没有对应的命令。 具体可输入 ls /sbin 查看,发现其中确实没有ifconfig命令 这个解决很简单&#xff0c;在命令行输入 sudo apt-get inst…

DDPAI盯盯拍记录仪删除后的恢复方法(前后双路)

DDPAI盯盯拍行车记录仪的口碑相当不错&#xff0c;其产品一直以行车记录仪为主&#xff0c;曾经使用过比较早的产品&#xff0c;体验还不错。下面来看下这个DDPAI的视频恢复方法。 故障存储: 64G存储卡 /文件系统&#xff1a;FAT32 故障现象: 在发生事故后在记录仪上看到了…

OpenCV目标识别

一 图像轮廓 具有相同颜色或强度的连续点的曲线。 图像轮廓的作用 可以用于图像分析 物体的识别与检测 注意 为了检测的准确性&#xff0c;需要先对图像进行二值化或Canny操作。 画轮廓时会修改输入的图像。 轮廓查找的API findContours(img,mode,ApproximationMode,...)…

北航第六次数据结构与程序设计作业(查找与排序)选填题

一、 顺序查找的平均查找长度ASL&#xff08;1 2 …… n&#xff09;/ n (n 1&#xff09;/ 2 二、 这半查找法的平均查找次数和判定树的深度有关系。若查找一个不存在的元素&#xff0c;说明进行了深度次比较。 注意&#xff0c;判定树不是满二叉树&#xff0c;因此深…

创新案例 | 3个关键策略:乳制品品牌认养一头牛如何通过私域流量运营获取1400万会员

探索认养一头牛如何运用创新的私域流量运营策略&#xff0c;在竞争激烈的乳制品市场中脱颖而出&#xff0c;实现会员数量的飞速增长至1400万。本文深入分析了其数据驱动的广告投放、高效的会员运营体系和创新的用户互动机制&#xff0c;为企业提供提升用户粘性和品牌忠诚度的宝…

Postgre 调优工具pgBadger部署

一&#xff0c;简介&#xff1a; pgBadger&#xff08;日志分析器&#xff09;类似于oracle的AWR报告&#xff08;基于1小时&#xff0c;一天&#xff0c;一周&#xff0c;一月的报告&#xff09;&#xff0c;以图形化的方式帮助DBA更方便的找到隐含问题。 pgbadger是为了提高…

嵌入式数据库的一般架构

嵌入式数据库的架构与应用对象紧密相关&#xff0c;其架构是以内存、文件和网络等三种方式为主。 1.基于内存的数据库系统 基于内存的数据库系统中比较典型的产品是每个McObject公司的eXtremeDB嵌入式数据库&#xff0c;2013年3月推出5.0版&#xff0c;它采用内存数据结构&…