新建MLIR一个Dialect,lowering,opt

新建MLIR一个Dialect,lowering,opt
Multi-Level Intermediate Representation(MLIR)是创建可重用、可扩展编译器基础设施的新途径。介绍一个简单的 MLIR Dialect。
MLIR:新建一个Dialect,lowering
MLIR 项目的核心是 Dialect,MLIR 自身就拥有例如linalgtosaaffine 这些 Dialect。各种不同的 Dialect 使不同类型的优化或转换得以完成。
讲解MLIR 的 Dialect 的 Lowering,即由 MLIR 代码逐级转换为机器代码的过程。
MLIR 生态的目标只在中间阶段,所以其 lowering 本质上并不涉及太多最终的 IR 生成,这一部分更依赖 LLVM 的基石。
mlir-hello[1] 项目的目标就是使用自建的 Dialect 通过 MLIR 生态实现一个 hello world,具体做法为:
创建 hello-opt 将原始 print.mlir (可以理解成 hello world 的 main.cpp)转换为 print.ll 文件
使用 LLVM 的 lli 解释器直接运行 print.ll 文件
前文主要介绍了如何通过 ODS[2] 实现新的 Dialect/Op 的定义。
2Lowering
MLIR 看似清爽,但相关 Pass 的实现一样工作量巨大。
在定义和编写了 HelloDialect 的方方面面后,最终还是要使它们回归 LLVM MLIR “标准库” Dialect,从而再做面向硬件的代码生成。因为标准库中的 Dialect 的剩余工作可以“无痛”衔接到 LLVM 的基础组件上。
具体到 mlir-hello,HelloDialect 到 LLVM 标准库 Dialect,例如 affine dialect,llvm dialect 的 lowering 将手工编码完成。
这一部分可能是 MLIR 相关任务工作量最大的地方。
作为 lowering 相关内容,解读如何通过 C++ 实现 HelloDialect 到 affine dialect 的 lowering。
相关文件如下:
mlir-hello/include/Hello/HelloDialect.h,主要内容通过 ODS 自动生成,略
mlir-hello/include/Hello/HelloOps.h,主要内容通过ODS 自动生成,略
mlir-hello/include/Hello/HelloPasses.h,注册本不存在的 lowering pass,比如 Hello 到 Affine 的 pass
mlir-hello/lib/Hello/LowerToAffine.cpp,lowering pass 的实现
3代码解读
简单讲,Dialect 到 Dialect 是一个 match and rewrite 的过程。
注意,有一个之前介绍过的、在 MLIR 中被大量应用的 C++ 编程技巧可能需要巩固一下:C++:CRTP,传入继承。
Pass registration
mlir-hello/include/Hello/HelloPasses.h
通过 std::unique_ptr<mlir::Pass> 在 MLIR 中注册两条 lowering pass。
注册的这个函数钩子将会在下一节的 cpp 中得到具体的实现的函数。
// mlir-hello/include/Hello/HelloPasses.h
// 该文件在 MLIR 中注册两条 lowering pass,没啥特别的
#ifndef MLIR_HELLO_PASSES_H
#define MLIR_HELLO_PASSES_H
#include <memory>
#include "mlir/Pass/Pass.h"
namespace hello {
std::unique_ptr<mlir::Pass> createLowerToAffinePass();
std::unique_ptr<mlir::Pass> createLowerToLLVMPass();
}
#endif // MLIR_HELLO_PASSES_H
Lowering implementation
mlir-hello/lib/Hello/LowerToAffine.cpp
负责 hello 到 affine 的 lowering 实现,本质上分为各 Op lowering 的前置工作和Dialect to Dialect实现两个部分。最终的实现 createLowerToAffinePass 将作为 Pass 注册时函数钩子的返回。
1. Op lowering
例如对于某 Xxx 算子,共性为
定义为 class XxxOpLowering
继承自 mlir::OpRewritePattern<hello::XxxOp>
重载 matchAndRewrite 函数,做具体实现
XxxOpLowering 最终将作为模板参数传入新 pass 的 mlir::RewritePatternSet<XxxOpLowering>
例如 class ConstantOpLowering 的实现如下:它会将 ConstantOp 所携带的信息最终转储到 mlir::AffineStoreOp 中。
class ConstantOpLowering : public mlir::OpRewritePattern<hello::ConstantOp> {
using OpRewritePattern<hello::ConstantOp>::OpRewritePattern;
mlir::LogicalResult matchAndRewrite(hello::ConstantOp op, mlir::PatternRewriter &rewriter) const final {
// 捕获 ConstantOp 的元信息:值、位置
mlir::DenseElementsAttr constantValue = op.getValue();
mlir::Location loc = op.getLoc();
// lowering 时,需要将 constant 的参数转存为 memref
auto tensorType = op.getType().cast<mlir::TensorType>();
auto memRefType = convertTensorToMemRef(tensorType);
auto alloc = insertAllocAndDealloc(memRefType, loc, rewriter);
// 预先声明一个“最高维”的变量
auto valueShape = memRefType.getShape();
mlir::SmallVector<mlir::Value, 8> constantIndices;
if (!valueShape.empty()) {
for (auto i : llvm::seq<int64_t>(
0, *std::max_element(valueShape.begin(), valueShape.end())))
constantIndices.push_back(rewriter.create<mlir::arith::ConstantIndexOp>(loc, i));
} else {
// rank 为 0 时
constantIndices.push_back(rewriter.create<mlir::arith::ConstantIndexOp>(loc, 0));
}
// ConstantOp 将作为一个“多维常量”被使用,它可能包含下面这些隐含信息(结构、值),
// [4, 3] (1, 2, 3, 4, 5, 6, 7, 8)
// storeElements(0)
// indices = [0]
// storeElements(1)
// indices = [0, 0]
// storeElements(2)
// store (const 1) [0, 0]
// indices = [0]
// indices = [0, 1]
// storeElements(2)
// store (const 2) [0, 1]
// ...
 
// 于是,可以通过定义一个递归 functor (中文一般译为仿函数)去捕获这些信息。
// functor 的基本思路为,从第一个维度开始,向第 2, 3,...个维度递归取回每个维度上的元素。
mlir::SmallVector<mlir::Value, 2> indices;
auto valueIt = constantValue.getValues<mlir::FloatAttr>().begin();
std::function<void(uint64_t)> storeElements = [&](uint64_t dimension "&") {
// 递归边界情况:到了最后一维,直接存下整组值
if (dimension == valueShape.size()) {
rewriter.create<mlir::AffineStoreOp>(
loc, rewriter.create<mlir::arith::ConstantOp>(loc, *valueIt++), alloc,
llvm::makeArrayRef(indices));
return;
}
// 未到递归边界:在当前维度上挨个儿递归,存储结构信息
for (uint64_t i = 0, e = valueShape[dimension]; i != e; ++i) {
indices.push_back(constantIndices[i]);
storeElements(dimension + 1);
indices.pop_back();
}
};
// 使用上面的 functor
storeElements(/*dimension=*/0);
// 将 insertAllocAndDealloc 替换为当前 op
rewriter.replaceOp(op, alloc);
return mlir::success();
}
};
2. Dialect to Dialect
定义好 op 的 lowering 后,就可以通过点对点的 lowering pass 说明如何进行 Dialect 之间的转换了。
这里的 class HelloToAffineLowerPass 主要需要实现 runOnOperation 函数。
namespace {
// 继承 PassWrapper,定义 HelloToAffineLowerPass,它将作为函数钩子的实现返回到上面的 pass 注册
class HelloToAffineLowerPass : public mlir::PassWrapper<HelloToAffineLowerPass, mlir::OperationPass<mlir::ModuleOp>> {
public:
MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(HelloToAffineLowerPass)
// 依赖哪些标准库里的 Dialect
void getDependentDialects(mlir::DialectRegistry &registry) const override {
registry.insert<mlir::AffineDialect, mlir::func::FuncDialect, mlir::memref::MemRefDialect>();
}
void runOnOperation() final;
};
}
// 需要实现的函数,它来说明如何做 lowering
void HelloToAffineLowerPass::runOnOperation() {
// 获取上下文
mlir::ConversionTarget target(getContext());
// 在 addIllegalDialect 中将 HelloDialect 置为不合法(需要被lowering)
target.addIllegalDialect<hello::HelloDialect>();
// 说明哪些 Dialect 是合法(lowering目标,通常是标准库中的 Dialect)的
target.addLegalDialect<mlir::AffineDialect, mlir::BuiltinDialect,
mlir::func::FuncDialect, mlir::arith::ArithDialect, mlir::memref::MemRefDialect>();
// 可通过 `isDynamicallyLegal` 决定其是否合法,这里具体表现为“当 PrintOp 的参数合法时,它才合法”
target.addDynamicallyLegalOp<hello::PrintOp>([](hello::PrintOp op "") {
return llvm::none_of(op->getOperandTypes(),
[](mlir::Type type "") { return type.isa<mlir::TensorType>(); });
});
// 说明如何 lowering,只需要把 illegal 的 op 的 lowering 实现作为模板参数传入 RewritePatternSet
mlir::RewritePatternSet patterns(&getContext());
patterns.add<ConstantOpLowering, PrintOpLowering>(&getContext());
if (mlir::failed(mlir::applyPartialConversion(getOperation(), target, std::move(patterns)))) {
signalPassFailure();
}
}
Pass 的实现确实工作量比较大,但是又不可避免,因为新的 Dialect 到标准库 Dialect 的过程还是必定需要手工实现。这也是很多反对 MLIR 的声音的来源。
5附全部代码
mlir-hello/lib/Hello/LowerToAffine.cpp
// 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
//
//
// 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 "Hello/HelloDialect.h"
#include "Hello/HelloOps.h"
#include "Hello/HelloPasses.h"
#include "mlir/Dialect/Affine/IR/AffineOps.h"
#include "mlir/Dialect/Arith/IR/Arith.h"
#include "mlir/Dialect/Func/IR/FuncOps.h"
#include "mlir/Dialect/MemRef/IR/MemRef.h"
#include "mlir/Pass/Pass.h"
#include "mlir/Transforms/DialectConversion.h"
#include "llvm/ADT/Sequence.h"
static mlir::MemRefType convertTensorToMemRef(mlir::TensorType type) {
assert(type.hasRank() && "expected only ranked shapes");
return mlir::MemRefType::get(type.getShape(), type.getElementType());
}
static mlir::Value insertAllocAndDealloc(mlir::MemRefType type, mlir::Location loc,
mlir::PatternRewriter &rewriter) {
auto alloc = rewriter.create<mlir::memref::AllocOp>(loc, type);
// Make sure to allocate at the beginning of the block.
auto *parentBlock = alloc->getBlock();
alloc->moveBefore(&parentBlock->front());
// Make sure to deallocate this alloc at the end of the block. This is fine
// as toy functions have no control flow.
auto dealloc = rewriter.create<mlir::memref::DeallocOp>(loc, alloc);
dealloc->moveBefore(&parentBlock->back());
return alloc;
}
class ConstantOpLowering : public mlir::OpRewritePattern<hello::ConstantOp> {
using OpRewritePattern<hello::ConstantOp>::OpRewritePattern;
mlir::LogicalResult matchAndRewrite(hello::ConstantOp op, mlir::PatternRewriter &rewriter) const final {
mlir::DenseElementsAttr constantValue = op.getValue();
mlir::Location loc = op.getLoc();
// When lowering the constant operation, we allocate and assign the constant
// values to a corresponding memref allocation.
auto tensorType = op.getType().cast<mlir::TensorType>();
auto memRefType = convertTensorToMemRef(tensorType);
auto alloc = insertAllocAndDealloc(memRefType, loc, rewriter);
// We will be generating constant indices up-to the largest dimension.
// Create these constants up-front to avoid large amounts of redundant
// operations.
auto valueShape = memRefType.getShape();
mlir::SmallVector<mlir::Value, 8> constantIndices;
if (!valueShape.empty()) {
for (auto i : llvm::seq<int64_t>(
0, *std::max_element(valueShape.begin(), valueShape.end())))
constantIndices.push_back(rewriter.create<mlir::arith::ConstantIndexOp>(loc, i));
} else {
// This is the case of a tensor of rank 0.
constantIndices.push_back(rewriter.create<mlir::arith::ConstantIndexOp>(loc, 0));
}
// The constant operation represents a multi-dimensional constant, so we
// will need to generate a store for each of the elements. The following
// functor recursively walks the dimensions of the constant shape,
// generating a store when the recursion hits the base case.
// [4, 3] (1, 2, 3, 4, 5, 6, 7, 8)
// storeElements(0)
// indices = [0]
// storeElements(1)
// indices = [0, 0]
// storeElements(2)
// store (const 1) [0, 0]
// indices = [0]
// indices = [0, 1]
// storeElements(2)
// store (const 2) [0, 1]
// ...
//
mlir::SmallVector<mlir::Value, 2> indices;
auto valueIt = constantValue.getValues<mlir::FloatAttr>().begin();
std::function<void(uint64_t)> storeElements = [&](uint64_t dimension "&") {
// The last dimension is the base case of the recursion, at this point
// we store the element at the given index.
if (dimension == valueShape.size()) {
rewriter.create<mlir::AffineStoreOp>(
loc, rewriter.create<mlir::arith::ConstantOp>(loc, *valueIt++), alloc,
llvm::makeArrayRef(indices));
return;
}
// Otherwise, iterate over the current dimension and add the indices to
// the list.
for (uint64_t i = 0, e = valueShape[dimension]; i != e; ++i) {
indices.push_back(constantIndices[i]);
storeElements(dimension + 1);
indices.pop_back();
}
};
// Start the element storing recursion from the first dimension.
storeElements(/*dimension=*/0);
// Replace this operation with the generated alloc.
rewriter.replaceOp(op, alloc);
return mlir::success();
}
};
class PrintOpLowering : public mlir::OpConversionPattern<hello::PrintOp> {
using OpConversionPattern<hello::PrintOp>::OpConversionPattern;
mlir::LogicalResult matchAndRewrite(hello::PrintOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const final {
// We don't lower "hello.print" in this pass, but we need to update its
// operands.
rewriter.updateRootInPlace(op,
[&] { op->setOperands(adaptor.getOperands()); });
return mlir::success();
}
};
namespace {
class HelloToAffineLowerPass : public mlir::PassWrapper<HelloToAffineLowerPass, mlir::OperationPass<mlir::ModuleOp>> {
public:
MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(HelloToAffineLowerPass)
void getDependentDialects(mlir::DialectRegistry &registry) const override {
registry.insert<mlir::AffineDialect, mlir::func::FuncDialect, mlir::memref::MemRefDialect>();
}
void runOnOperation() final;
};
}
void HelloToAffineLowerPass::runOnOperation() {
mlir::ConversionTarget target(getContext());
target.addIllegalDialect<hello::HelloDialect>();
target.addLegalDialect<mlir::AffineDialect, mlir::BuiltinDialect,
mlir::func::FuncDialect, mlir::arith::ArithDialect, mlir::memref::MemRefDialect>();
target.addDynamicallyLegalOp<hello::PrintOp>([](hello::PrintOp op "") {
return llvm::none_of(op->getOperandTypes(),
[](mlir::Type type "") { return type.isa<mlir::TensorType>(); });
});
mlir::RewritePatternSet patterns(&getContext());
patterns.add<ConstantOpLowering, PrintOpLowering>(&getContext());
if (mlir::failed(mlir::applyPartialConversion(getOperation(), target, std::move(patterns)))) {
signalPassFailure();
}
}
std::unique_ptr<mlir::Pass> hello::createLowerToAffinePass() {
return std::make_unique<HelloToAffineLowerPass>();
}
 
参考资料
MLIR:新建一个Dialect,opt
Multi-Level Intermediate Representation(MLIR)是创建可重用、可扩展编译器基础设施的新途径。介绍如何创建 hello-opt 来应用建立的那些 Pass。
MLIR 项目的核心是 Dialect,MLIR 自身就拥有例如linalg,tosa,affine 这些 Dialect。各种不同的 Dialect 使不同类型的优化或转换得以完成。
前面讲解了 Hello Dialect 到 Affine Dialect 的 Lowering,现在继续讲如何将这一 Lowering 实际应用到 hello-opt 中,也即如何完成到汇编文件 .ll 文件的生成。
当然了,MLIR 生态的目标只在 IR 阶段,所以其实只需要能生成标准的 .mlir 文件。到 .ll 的步骤实际上由 LLVM 后端完成。
工具链、总览等等知识请自行翻看历史 MLIR 标签的相关文章
mlir-hello[1] 项目的目标就是使用自建的 Dialect 通过 MLIR 生态实现一个 hello world,具体做法为:
创建 hello-opt 将原始 print.mlir (可以理解成 hello world 的 main.cpp)转换为 print.ll 文件
使用 LLVM 的 lli 解释器直接运行 print.ll 文件
Part2hello-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 接受的参数为【可选项】和【文件路径】,例如,
./build/bin/hello-opt ./test/Hello/print.mlir > /path/to/print.ll
流程(main函数)为,
获取 MLIR 相关参数,registerMLIRContextCLOptions,registerPassManagerCLOptions
解析参数,ParseCommandLineOptions
指明相关 Dialect,getOrLoadDialect<hello::HelloDialect>()
创建 module 并载入文件,loadAndProcessMLIR(context, module)
转换输入文件到 LLVM IR,dumpLLVMIR(*module)
重要的函数包括,
1inputFilename
添加输入文件路径为一个参数项。
2loadAndProcessMLIR
载入 MLIR 文件并处理,步骤为,
loadMLIR 将文件内容载入已创建的 module
对 module 应用 lowering pass
loadMLIR
加载文件到已创建的 module 中,
使用 llvm::MemoryBuffer::getFileOrSTDIN 读取文件
把文件内容添加到llvm::SourceMgr,sourceMgr.AddNewSourceBuffer(std::move(*fileOrErr), llvm::SMLoc());
写进 module,module = mlir::parseSourceFile<mlir::ModuleOp>(sourceMgr, &context);
应用 lowering pass
应用上一篇文章中创建的 Lowering pass,
创建 mlir::PassManager 实例并添加 pass, addPass(hello::createLowerToAffinePass())
对 module 应用 pass,passManager.run(*module)
3dumpLLVMIR
顾名思义,这个函数将前面的输出转换为 LLVM IR。
这是一个利用 LLVM 后端的基本操作,按着代码来就行。
4runJit
与本文内容无关。略。
可以看出,所描述的工作量其实还是集中Pass 的实现上。hello-opt 更多的是利用已有的工作接口完成一个 lowering 的流程描述,大多数想起来比较复杂的工作都是由 LLVM 生态完成。
附全部代码
mlir-hello/hello-opt/hello-opt.cpp
// 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
//
//
// 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;
}
参考资料
 
 
参考文献链接
https://mp.weixin.qq.com/s/JLgcl4b_hesy1A9QZ09p9A
posted @ 2023-04-23 04:29  吴建明wujianming  阅读(193)  评论(0编辑  收藏  举报