MLIR:新建一个Dialect(八),opt

文章来自微信公众号“科文路”,欢迎关注、互动。转发须注明出处。

Multi-Level Intermediate Representation(MLIR)是创建可重用、可扩展编译器基础设施的新途径。本文为第 14 期,介绍如何创建 hello-opt 来应用上一篇文章建立的那些 Pass。

转载请注明出处!

MLIR 项目的核心是 Dialect,MLIR 自身就拥有例如linalgtosaaffine 这些 Dialect。各种不同的 Dialect 使不同类型的优化或转换得以完成。

上一篇文章讲解了 Hello DialectAffine Dialect 的 Lowering,本文继续讲如何将这一 Lowering 实际应用到 hello-opt 中,也即如何完成到汇编文件 .ll 文件的生成。

当然了,前期也提到过,MLIR 生态的目标只在 IR 阶段,所以其实只需要能生成标准的 .mlir 文件。到 .ll 的步骤实际上由 LLVM 后端完成。

复习

工具链、总览等等知识请自行翻看历史 MLIR 标签的相关文章

mlir-hello 项目的目标就是使用自建的 Dialect 通过 MLIR 生态实现一个 hello world,具体做法为:

  1. 创建 hello-opt 将原始 print.mlir (可以理解成 hello world 的 main.cpp)转换为 print.ll 文件
  2. 使用 LLVM 的 lli 解释器直接运行 print.ll 文件

hello-opt

终于,回到了第一步。hello-opt 帮助将 .mlir 文件中的 Hello Dialect 进行 lowering,从而接入 LLVM 后端,生成最终的标准 .ll 汇编文件。

相关文件为,

  • mlir-hello/hello-opt/hello-opt.cpp,完成功能描述、组织
  • mlir-hello/hello-opt/CMakeLists.txt,将 hello-opt 接入到既有生态中
    • 从而可以自动生成很多的通用选项

代码解读

简单讲,opt 的功能就是应用 Dialect 之间的 lowering。

功能上,hello-opt 接受的参数为【可选项】和【文件路径】,例如,

1
./build/bin/hello-opt ./test/Hello/print.mlir > /path/to/print.ll

流程(main函数)为,

  1. 获取 MLIR 相关参数,registerMLIRContextCLOptionsregisterPassManagerCLOptions
  2. 解析参数,ParseCommandLineOptions
  3. 指明相关 Dialect,getOrLoadDialect<hello::HelloDialect>()
  4. 创建 module 并载入文件,loadAndProcessMLIR(context, module)
  5. 转换输入文件到 LLVM IR,dumpLLVMIR(*module)

重要的函数包括,

inputFilename

添加输入文件路径为一个参数项。

loadAndProcessMLIR

载入 MLIR 文件并处理,步骤为,

  • loadMLIR 将文件内容载入已创建的 module
  • module 应用 lowering pass

loadMLIR

加载文件到已创建的 module 中,

  1. 使用 llvm::MemoryBuffer::getFileOrSTDIN 读取文件
  2. 把文件内容添加到llvm::SourceMgrsourceMgr.AddNewSourceBuffer(std::move(*fileOrErr), llvm::SMLoc());
  3. 写进 modulemodule = mlir::parseSourceFile<mlir::ModuleOp>(sourceMgr, &context);

应用 lowering pass

应用上一篇文章中创建的 Lowering pass,

  1. 创建 mlir::PassManager 实例并添加 pass, addPass(hello::createLowerToAffinePass())
  2. module 应用 pass,passManager.run(*module)

dumpLLVMIR

顾名思义,这个函数将前面的输出转换为 LLVM IR。

这是一个利用 LLVM 后端的基本操作,按着代码来就行。

runJit

与本文内容无关。略。

本期结语

可以看出,本文所描述的工作量其实还是集中在上一篇文章所说的 Pass 的实现上。hello-opt 更多的是利用已有的工作接口完成一个 lowering 的流程描述,大多数想起来比较复杂的工作都是由 LLVM 生态完成。

附全部代码

mlir-hello/hello-opt/hello-opt.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

#include "mlir/ExecutionEngine/ExecutionEngine.h"
#include "mlir/ExecutionEngine/OptUtils.h"
#include "mlir/IR/MLIRContext.h"
#include "mlir/InitAllDialects.h"
#include "mlir/InitAllPasses.h"
#include "mlir/Parser/Parser.h"
#include "mlir/Pass/PassManager.h"
#include "mlir/Support/FileUtilities.h"
#include "mlir/Target/LLVMIR/Export.h"
#include "mlir/Target/LLVMIR/Dialect/LLVMIR/LLVMToLLVMIRTranslation.h"

#include "llvm/IR/Module.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/SourceMgr.h"
#include "llvm/Support/TargetSelect.h"
#include "llvm/Support/ToolOutputFile.h"

#include "Hello/HelloDialect.h"
#include "Hello/HelloPasses.h"

namespace cl = llvm::cl;
static cl::opt<std::string> inputFilename(cl::Positional,
cl::desc("<input hello file>"),
cl::init("-"),
cl::value_desc("filename"));

int dumpLLVMIR(mlir::ModuleOp module) {
mlir::registerLLVMDialectTranslation(*module->getContext());
// Convert the module to LLVM IR in a new LLVM IR context.

llvm::LLVMContext llvmContext;
auto llvmModule = mlir::translateModuleToLLVMIR(module, llvmContext);
if (!llvmModule) {
llvm::errs() << "Failed to emit LLVM IR\n";
return -1;
}

// Initialize LLVM targets.
llvm::InitializeNativeTarget();
llvm::InitializeNativeTargetAsmPrinter();
mlir::ExecutionEngine::setupTargetTriple(llvmModule.get());

// Optionally run an optimization pipeline over the llvm module.
auto optPipeline = mlir::makeOptimizingTransformer(0, 0, nullptr);
if (auto err = optPipeline(llvmModule.get())) {
llvm::errs() << "Failed to optimize LLVM IR " << err << "\n";
return -1;
}
llvm::outs() << *llvmModule << "\n";
return 0;
}

int loadMLIR(mlir::MLIRContext &context, mlir::OwningOpRef<mlir::ModuleOp> &module) {
llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> fileOrErr =
llvm::MemoryBuffer::getFileOrSTDIN(inputFilename);
if (std::error_code ec = fileOrErr.getError()) {
llvm::errs() << "Could not open input file: " << ec.message() << "\n";
return -1;
}

llvm::SourceMgr sourceMgr;
sourceMgr.AddNewSourceBuffer(std::move(*fileOrErr), llvm::SMLoc());
module = mlir::parseSourceFile<mlir::ModuleOp>(sourceMgr, &context);
if (!module) {
llvm::errs() << "Error can't load file " << inputFilename << "\n";
return 3;
}
return 0;
}

int loadAndProcessMLIR(mlir::MLIRContext &context, mlir::OwningOpRef<mlir::ModuleOp> &module) {
if (int error = loadMLIR(context, module)) {
return error;
}

// Register passes to be applied in this compile process
mlir::PassManager passManager(&context);
mlir::applyPassManagerCLOptions(passManager);

passManager.addPass(hello::createLowerToAffinePass());
passManager.addPass(hello::createLowerToLLVMPass());

if (mlir::failed(passManager.run(*module))) {
return 4;
}

return 0;
}

int runJit(mlir::ModuleOp module) {
// Initialize LLVM targets.
llvm::InitializeNativeTarget();
llvm::InitializeNativeTargetAsmPrinter();

// Register the translation from MLIR to LLVM IR, which must happen before we
// can JIT-compile.
mlir::registerLLVMDialectTranslation(*module->getContext());

// An optimization pipeline to use within the execution engine.
auto optPipeline = mlir::makeOptimizingTransformer(0, /*sizeLevel=*/0, /*targetMachine=*/nullptr);

// Create an MLIR execution engine. The execution engine eagerly JIT-compiles
// the module.
mlir::ExecutionEngineOptions engineOptions;
engineOptions.transformer = optPipeline;
auto maybeEngine = mlir::ExecutionEngine::create(module, engineOptions);
assert(maybeEngine && "failed to construct an execution engine");
auto &engine = maybeEngine.get();

// Invoke the JIT-compiled function.
auto invocationResult = engine->invokePacked("main");
if (invocationResult) {
llvm::errs() << "JIT invocation failed\n";
return -1;
}

return 0;
}

int main(int argc, char **argv) {
mlir::registerMLIRContextCLOptions();
mlir::registerPassManagerCLOptions();

cl::ParseCommandLineOptions(argc, argv, "Hello compiler\n");
mlir::MLIRContext context;
context.getOrLoadDialect<hello::HelloDialect>();
context.getOrLoadDialect<mlir::func::FuncDialect>();

mlir::OwningOpRef<mlir::ModuleOp> module;
if (int error = loadAndProcessMLIR(context, module)) {
return error;
}

dumpLLVMIR(*module);
// runJit(*module);

return 0;
}

都看到这儿了,不如关注每日推送的“科文路”、互动起来~

至少点个赞再走吧~

MLIR:新建一个Dialect(八),opt

https://xlindo.com/kewenlu2022/posts/d34aee11/

Author

xlindo

Posted on

2022-12-22

Updated on

2023-05-10

Licensed under

Comments