1. 简介
MLIR是Multi-layer IR的缩写,它是基于LLVM IR发展的一个中间语言形式,是Clang/LLVM的发明者Chris Lattner在加盟谷歌后又一个重要的发明。MLIR是较之LLVM IR更为灵活的深度学习编译器架构。
其他编译器,像LLVM(参考Kaleidoscope tutorial),提供一组固定的预定义的类型以及(通常低级/类RISC)指令。在发布LLVM IR之前,由特定语言的前端来执行所有语言特定的类型检查、分析或转换。例如,Clang的AST不仅用来执行静态分析,还用于转换,比如使用AST克隆与重写的C++模板具现。最后,具有比C/C++更高级结构的语言可能要求从它们的AST经过重要的(non-trivial)降级来产生LLVM IR。
因此,多个前端最终重新实现基础架构重要的部分,以支持这些分析与转换。而MLIR通过可扩展性的设计来应对这些情况。因此,只有少数几个预定义指令(MLIR术语里的操作,operation)以及类型。
LLVM本身有一整套相当复杂的命令行选项解析机制,通过llvm::cl::AddLiteralOption(),可以向LLVM的命令行解析器注册新的命令行选项,让它为我们提供一整套选项处理功能。这正是MLIR命令行选项着手的地方!注意,它是与LLVM本身的命令行选项解析分开的,LLVM虽然也是调用这个方法,但LLVM组织的方式是不一样的。
为了解析与MLIR相关的ODS定义,首先MLIR提供了一个GenRegistration定义,这个结构体提供的唯一方法就是构造函数:
31 mlir::GenRegistration::GenRegistration(StringRef arg, StringRef description,
32 GenFunction function) {
33 generatorRegistry->emplace_back(arg, description, function);
34 }
这里generatorRegistry是一个静态变量(由ManagedStatic类提供封装):
29 static llvm::ManagedStatic<std::vector<GenInfo>> generatorRegistry;
其中,GenInfo用于封装各种代码生成器,它只有3个域:arg(选项名,字符串类型),description(选项描述,字符串类型),generator(代码生成器,一个可执行的对象)。
因此在OpDefinitionsGen.cpp里,我们可以看到这样的GenRegistration对象声明:
2255 static mlir::GenRegistration
2256 genOpDecls("gen-op-decls", "Generate op declarations",
2257 [](const RecordKeeper &records, raw_ostream &os) {
2258 return emitOpDecls(records, os);
2259 });
2260
2261 static mlir::GenRegistration genOpDefs("gen-op-defs", "Generate op definitions",
2262 [](const RecordKeeper &records,
2263 raw_ostream &os) {
2264 return emitOpDefs(records, os);
2265 });
这样,程序在初始化时会向generatorRegistry添加这些GenInfo对象。那么generatorRegistry怎么样被调动起来的呢?我们看一下mlri-tblgen.cpp,这是TableGen语言代码生成器的源代码所在:
75 int main(int argc, char **argv) {
76 llvm::InitLLVM y(argc, argv);
77 llvm::cl::opt<const mlir::GenInfo *, false, mlir::GenNameParser> generator(
78 "", llvm::cl::desc("Generator to run"));
79 cl::ParseCommandLineOptions(argc, argv);
80 ::generator = generator.getValue();
81
82 return TableGenMain(argv[0], &MlirTableGenMain);
83 }
LLVM有一套极其复杂的命令行选项处理机制,这里我们只能简要说一下。
首先,76行的llvm::InitLLVM类型的局部变量y是初始化LLVM的必要模块,与命令行解析的关系不大。77行的generator就是命令行选项解析机制的一部分,它的类型是cl::opt,我们看一下这个类型定义开头的几行(CommandLine.h):
1404 template <class DataType, bool ExternalStorage = false,
1405 class ParserClass = parser<DataType>>
1406 class opt : public Option,
1407 public opt_storage<DataType, ExternalStorage,
1408 std::is_class<DataType>::value> {
1409 ParserClass Parser;
它的构造函数是这样定义的:
1482 template <class... Mods>
1483 explicit opt(const Mods &... Ms)
1484 : Option(Optional, NotHidden), Parser(*this) {
1485 apply(this, Ms...);
1486 done();
1487 }
基类Option描述了选项属性,而opt_storage则保存了命令行上出现选项的具体信息(它是一个模板类,这里的特化以所服务的信息类型为基类,在这个上下文里就是GenInfo的派生类),这里因为在声明opt_storage基类时把ExternalStorage指定为false,因此generator的opt_storage部分将用于保存命令行上出现的选项所对应的GenInfo实例。
同时,构造函数里指定opt使用的Parser是mlir::Gen