P-built-in记录

目标:添加一个builtin函数转换为LLVM IR中的intrinsics,进一步在IR把 call @llvm.riscv.xxx进入DAG,后端根据TableGen文件的生成的描述定义进行模式匹配,选择正确的指令与编码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
C / builtin  定义builtin函数

Clang Sema 范围检测,参数类型和个数检测

Clang CodeGen (CGBuiltin.cpp)

LLVM IR (llvm.riscv.xxx)

LLVM IR 优化

SelectionDAG 构建

RISCVISelLowering (可选)

DAG → DAG 指令选择

RISCV MCInst

Asm / Obj

前置知识:
builtin 和 intrinsic 区别?
builtin是高级语言级别
__builtin_riscv_ksllw(a, 7);
intrinsic 是 IR 级别
i64 @llvm.riscv.ksllw.i64.i32(i64 %a, i32 7)
其中,第一个i64是输出类型,.i64.i32是两个输入类型
在LLVM中,Builtins的TableGen是单独的RISCV作为最顶层单独生成
clang/include/clang/Basic/BuiltinsRISCV.td
—-> include “clang/Basic/BuiltinsRISCV/BuiltinsRISCVAndesP.td”

而Intrinsics则是由总的Target一层一层include下来

1
llvm/include/llvm/Target/Target.td:15  ---->  llvm/include/llvm/IR/Intrinsics.td:2771  ----> include "llvm/IR/IntrinsicsRISCV.td" 

Target:
对builtin function的检查:

1
clang/lib/Basic/BuiltinTargetFeatures.h

这个文件原是fix has_builtin function相关,检查builtin和target的对应情况,可以用来参考正确的格式。
Target:
能够支持’,’(and), ‘|’(or), and ‘()’ 的组合。默认情况下,“,”的优先级高于“|”
E.g:
A,B|C
A,B|C means the builtin function requires (A and B), or (A and C).
格式:FeaturesList不能包含空格,括号必须成对出现,例如

1
2
(lldb) p FeatureList
(llvm::StringRef) (Data = "p095b|zp054b|zp053b|zp052b", Length = 27)

TableGen
TableGen 的语法大致基于 C++ 模板,区别于OOP的是,TableGen字段的值是不可变的。其主要作用有:
描述指令选择规则
描述调度信息
声明IR属性
LLVM 子系统选项 (e.g. Clang’s compiler flags).
TableGen 文件由两个关键部分组成:“classes”和“definitions”,两者都被视为“records”
TableGen definitions是“record”的具体形式。这些通常没有任何未定义的值,并用 ‘ def’ 关键字标记。如:

1
def add64 : RISCVBuiltin<"long int(long int, long int)">;

multiclasses 是一组同时实例化的抽象记录。每个实例化可能导致多个 TableGen 定义。如果multiclass继承自另一个multiclass,则子多类中的定义将成为当前multiclass的一部分,就好像它们是在当前multiclass中声明的一样。
defm ksllw : RVPBinaryAABIntrinsics;
BUILTIN简介
参考文件位置clang/include/clang/Basic/Builtins.def
定义
clang/include/clang/Basic/BuiltinsRISCV.def

1
2
3
4
#if defined(BUILTIN) && !defined(TARGET_BUILTIN)
#define TARGET_BUILTIN(ID, TYPE, ATTRS, FEATURE) BUILTIN(ID, TYPE, ATTRS)
#endif
TARGET_BUILTIN(__builtin_riscv_add8, "ULiULiULi", "", RVZP)

其中TARGET_BUILTIN是一个宏,有多处定义,不同地方的用处不同所使用的东西也不同,所以.td的class builtin会有一个开关要不要取消宏
ID:builtin函数
TYPE:”ULiULiULi”输出,输入,输入。非浮点计算函数可以简单的把i看作分割符区分。此处表示
unsigned long int __builtin_riscv_add8(unsigned long int , unsigned long int )
对应白皮书中的uintXLEN_t __rv__add8(uintXLEN_t a, uintXLEN_t b);
有符号e.g: SLLiSLLiSLLi
需要注意的是,uintXLEN_t 会根据target-triples(What the Hell Is a Target Triple? · mcyoung)变化,可以根据其sizeof选择long int,固定int32只需要单个int,固定int64选择long long int
RV32:
sizeof(long long int) = 8 bytes (64 bits)
sizeof(long int) = 4 bytes (32 bits)
sizeof(int) = 4 bytes (32 bits)
RV64:
sizeof(long long int) = 8 bytes (64 bits)
sizeof(long int) = 8 bytes (64 bits)
sizeof(int) = 4 bytes (32 bits)

1
2
3
4
5
(lldb) p  getAndFeatures(FS.CurFeaturesList)
(clang::Builtin::TargetFeatures::FeatureListStatus) {
HasFeatures = false
CurFeaturesList = (Data = "zp053b|zp052b", Length = 13)
}

ATTRS 函数的签名、类型和属性
具体可查看clang/include/clang/Basic/Builtins.def
例如”nc”为:
n -> nothrow
c -> const
FEATURE let 用于设置 Features 属性的值。

1
2
3
#if defined(BUILTIN) && !defined(TARGET_BUILTIN)
define TARGET_BUILTIN(ID, TYPE, ATTRS, FEATURE) BUILTIN(ID, TYPE, ATTRS)
#endif

第二种方式
clang/include/clang/Basic/BuiltinsRISCV/BuiltinsRISCVAndesP.td

1
2
3
let Features = "zp095b|zp054b|zp053b|zp052b" in {
def add8 : RISCVBuiltin<"unsigned long int(unsigned long int, unsigned long int)">;
} // Features = "zp095b|zp054b|zp053b|zp052b"

Class RISCVBuiltin:
可以看到,使用__builtin_riscv_ + Name 拼接函数名,prototype包括返回类型和参数信息,Feature已在let批量添加

1
2
3
4
5
class RISCVBuiltin<string prototype, string features = ""> : TargetBuiltin {
let Spellings = ["__builtin_riscv_" # NAME];
let Prototype = prototype;
let Features = features;
}

Class TargetBuiltin:

1
2
3
class TargetBuiltin : Builtin {
string Features = "";
}

Class Builtin:

1
2
3
4
5
6
7
8
9
class Builtin {
list<string> Spellings;
list<Attribute> Attributes = [];
string Prototype;
string Namespace;
// On some platforms, some functions are actually macros. In that case we need
// to #undef them.
bit RequiresUndef = 0;
}

这是另一种实现方式,使用.td文件实现,与.def实现的有什么区别?
个人认为
1.功能没有改变
两者同为前端的builtin函数定义,.td文件使用TableGen, .def文件使用宏TARGET_BUILTIN 传入ID, TYPE, ATTRS, FEATURE参数,而RISCVBuiltin已经在 clang/include/clang/Basic/BuiltinsRISCV.td 内定义了一个TableGen类
2.结果也没有改变
TableGen经过llvm-tablegen生成.inc文件后,所有同类riscv builtin函数放置于llvm/tools/clang/include/clang/Basic/BuiltinsRISCV.inc文件,文件内标注List of builtins that Clang recognizes. Automatically generated file, do not edit! 从中可以找到

1
TARGET_BUILTIN(__builtin_riscv_kabs32, "ULiULi", "", "64bit,(zp095b|zp054b|zp053b|zp052b)")

与宏实现一致。两者只是表达差异,如TableGen更声明式、结构化的语法,可以定义继承层次结构,会生成.inc文件include进去,而.def使用宏展开直接编译进llvm,后端见下一篇。

ohters:

1
2
3
4
5
6
7
8
9
10
let Predicates = [HasExtZprvsfextra095b, IsRV64] in {
def KSLLI32 : RVPShiftI5<0b1000010, 0b010, "kslli32">,
Sched<[WritePShift32, ReadPShift32]>;
}

let Predicates = [HasExtZprvsfextra095b, IsRV64] in {
def : RVPBinaryI5Pat<KSLLI32, "kslli32">;
} // Predicates = [HasExtZprvsfextra095b, IsRV64]

def : PatGprpImm<riscv_kslli, KSLLI32, XVEI32VT, uimm5>;

参考:

  1. riscv 基础
    https://suda-morris.github.io/blog/cs/riscv.html
  2. SiFive series tutorials
    https://www.sifive.com/blog/all-aboard-part-0-introduction