Pass Infrastructure基础架构(下)
Pass Infrastructure基础架构(下)
pass注册
PassRegistration该类在示例中简要显示了各种pass类型的定义 。该机制允许注册pass类,以便可以在文本pass管道描述中创建它们 。注册示例如下所示:
void registerMyPass() {
PassRegistration<MyPass>("argument", "description");
}
- MyPass 是派生密码类的名称。
- “参数”是用于以文本格式引用过程的参数。
- “说明”是pass的简短说明。
对于无法默认构造的pass,PassRegistration接受可选的第三个参数,该参数接受回调以创建pass:
void registerMyPass() {
PassRegistration<MyParametricPass>(
"argument", "description",
[]() -> std::unique_ptr<Pass> {
std::unique_ptr<Pass> p = std::make_unique<MyParametricPass>(/*options*/);
/*... non-trivial-logic to configure the pass ...*/;
return p;
});
}
例如,可以使用这种注册变体来接受命令行参数的传递配置,并将其传递给pass构造函数。
注意:请确保该pass以不共享数据的方式是可复制构造的,因为 pass管理器 可能会创建该pass的副本以并行运行。
pass管道注册
上面描述的是用于注册特定派生pass类的机制。最重要的是,MLIR允许以类似的方式注册自定义传递管道。这允许自定义管道以与传递相同的方式提供给mlir-opt之类的工具,这对于封装常见的管道(例如“ -O1”传递系列)很有用。管道pass类似的机制注册,形式为PassPipelineRegistration。与之相比PassRegistration,此类采用管道构造器形式的附加参数,该参数修改提供的参数OpPassManager。
void pipelineBuilder(OpPassManager &pm) {
pm.addPass(std::make_unique<MyPass>());
pm.addPass(std::make_unique<MyOtherPass>());
}
void registerMyPasses() {
// Register an existing pipeline builder function.
PassPipelineRegistration<>(
"argument", "description", pipelineBuilder);
// Register an inline pipeline builder.
PassPipelineRegistration<>(
"argument", "description", [](OpPassManager &pm) {
pm.addPass(std::make_unique<MyPass>());
pm.addPass(std::make_unique<MyOtherPass>());
});
}
文本传递管道规范
前面的部分详细介绍了如何使用特定的参数和说明注册pass和pass管道。一旦注册,就可以使用它们从字符串描述中配置通道管理器。这对于mlir-opt从命令行配置过程管理器的工具,或作为利用动态过程管道的过程的选项的 工具尤其有用 。
为了支持描述传递管道的完整结构的能力,MLIR支持对传递管道的自定义文本描述。文字描述包括嵌套结构,运行的传递和传递管道的参数以及这些传递和管道的任何选项。文本管道定义为一系列名称,每个名称本身都可以递归包含嵌套的管道描述。该规范的语法如下:
pipeline ::= op-name `(` pipeline-element (`,` pipeline-element)* `)`
pipeline-element ::= pipeline | (pass-name | pass-pipeline-name) options?
options ::= '{' (key ('=' value)?)+ '}'
- op-name
- 这对应于要继续运行的算子的助记符名称,例如func或module。
- pass-name | pass-pipeline-name
- 这对应于已注册的pass或pass管道的参数,例如cse或canonicalize。
- options
- 选项是特定的键值对,代表pass或传递管道定义的选项,如 “实例特定的传递选项” 部分所述。有关文本管道中的用法示例,请参见本节。
例如,以下管道:
$ mlir-opt foo.mlir -cse -canonicalize -convert-std-to-llvm='use-bare-ptr-memref-call-conv=1'
也可以指定为(pass-pass-pipeline标志):
$ mlir-opt foo.mlir -pass-pipeline='func(cse,canonicalize),convert-std-to-llvm{use-bare-ptr-memref-call-conv=1}'
为了支持使用来回传递一个通向文本表示的传递 OpPassManager::printAsTextualPipeline(raw_ostream&),请重写StringRef Pass::getArgument()以指定注册传递时使用的参数。
声明式pass规范
可以pass类似于算子的形式声明性地指定pass的某些方面 。该规范简化了定义通道时使用的几种机制。它可用于生成过程注册调用,定义样板过程实用程序以及生成过程文档。
考虑以下在C ++中指定的过程:
struct MyPass : PassWrapper<MyPass, OperationPass<ModuleOp>> {
MyPass() = default;
MyPass(const MyPass &) {}
...
// Specify any options.
Option<bool> option{
*this, "example-option",
llvm::cl::desc("An example option"), llvm::cl::init(true)};
ListOption<int64_t> listOption{
*this, "example-list",
llvm::cl::desc("An example list option"), llvm::cl::ZeroOrMore,
llvm::cl::MiscFlags::CommaSeparated};
// Specify any statistics.
Statistic statistic{this, "example-statistic", "An example statistic"};
};
/// Expose this pass to the outside world.
std::unique_ptr<Pass> foo::createMyPass() {
return std::make_unique<MyPass>();
}
/// Register this pass.
void foo::registerMyPass() {
PassRegistration<MyPass>("my-pass", "My pass summary");
}
此pass可以这样声明地指定:
def MyPass : Pass<"my-pass", "ModuleOp"> {
let summary = "My Pass Summary";
let description = [{
Here we can now give a much larger description of `MyPass`, including all of
its various constraints and behavior.
}];
// A constructor must be provided to specify how to create a default instance
// of MyPass.
let constructor = "foo::createMyPass()";
// Specify any options.
let options = [
Option<"option", "example-option", "bool", /*default=*/"true",
"An example option">,
ListOption<"listOption", "example-list", "int64_t",
"An example list option",
"llvm::cl::ZeroOrMore, llvm::cl::MiscFlags::CommaSeparated">
];
// Specify any statistics.
let statistics = [
Statistic<"statistic", "example-statistic", "An example statistic">
];
}
使用gen-pass-decls生成器,可以自动生成上面的大多数样板。该生成器将一个-name参数作为输入,该参数为正在生成的一组pass提供标签。该生成器产生两个输出块:
第一个是用于在全局注册表中注册声明性传递的代码块。对于每次pass,生成器都会生成一个registerFooPasswhereFoo 是tablegen中指定的定义的名称。它还会生成一个 registerGroupPasses,其中Group是pass-name输入参数提供的标记,该标记会注册所有存在的pass。
// gen-pass-decls -name="Example"
#define GEN_PASS_REGISTRATION
#include "Passes.h.inc"
void registerMyPasses() {
// Register all of the passes.
registerExamplePasses();
// Register `MyPass` specifically.
registerMyPassPass();
}
第二个是每个通道的基类,其中包含与通道定义相关的大多数样板。这些类以的形式命名 MyPassBase,其中MyPass是tablegen中传递定义的名称。可以这样更新原始的C ++传递定义:
/// Include the generated base pass class definitions.
#define GEN_PASS_CLASSES
#include "Passes.h.inc"
/// Define the main class as deriving from the generated base class.
struct MyPass : MyPassBase<MyPass> {
/// The explicit constructor is no longer explicitly necessary when defining
/// pass options and statistics, the base class takes care of that
/// automatically.
...
/// The definitions of the options and statistics are now generated within
/// the base class, but are accessible in the same way.
};
/// Expose this pass to the outside world.
std::unique_ptr<Pass> foo::createMyPass() {
return std::make_unique<MyPass>();
}
使用gen-pass-doc生成器,可以生成每个pass的降价文档。参阅 Passes.md 以获取实际MLIRpass的示例输出。
Tablegen规范
该Pass级用来启动一个新的通行定义。此类将传递给属性的注册表参数以及与传递所算子的算子类型相对应的可选字符串作为参数。该类包含以下字段:
- summary
- 该pass的简短摘要,用作注册pass时的描述。
- description
- pass的更长,更详细的描述。在生成pass文件时使用。
- dependentDialects
- 代表Dialect此过程的类的字符串列表可能会引入实体,属性/算子/类型/等。
- constructor
- 用于创建pass的默认实例的代码块。
- options
- pass使用的pass选项列表。
- statistics
- pass使用的pass统计信息列表。
选项
选项可以passOption和ListOption类指定。该Option 班采用下列模板参数:
- C ++变量名称
- 用于生成的选项变量的名称。
- 论据
- 选项的参数名称。
- 类型
- 选项的C ++类型。
- 默认值
- 默认选项值。
- 描述
- 选项的一行描述。
- 附加选项标志
- 一个字符串,其中包含构造选项所必需的任何其它选项。
def MyPass : Pass<"my-pass"> {
let options = [
Option<"option", "example-option", "bool", /*default=*/"true",
"An example option">,
];
}
该ListOption班采取以下字段:
- C ++变量名称
- 用于生成的选项变量的名称。
- 论据
- 选项的参数名称。
- 元素类型
- 列表元素的C ++类型。
- 描述
- 选项的一行描述。
- 附加选项标志
- 一个字符串,其中包含构造选项所必需的任何其它选项。
def MyPass : Pass<"my-pass"> {
let options = [
ListOption<"listOption", "example-list", "int64_t",
"An example list option",
"llvm::cl::ZeroOrMore, llvm::cl::MiscFlags::CommaSeparated">
];
}
统计
可以pass来指定统计信息Statistic,该参数采用以下模板参数:
- C ++变量名称
- 用于生成的统计变量的名称。
- 显示名称
- 显示统计信息时使用的名称。
- 描述
- 统计信息的一行描述。
def MyPass : Pass<"my-pass"> {
let statistics = [
Statistic<"statistic", "example-statistic", "An example statistic">
];
}
pass检测
MLIRpass该类提供了一个可定制的框架,以pass过程执行和分析计算PassInstrumentation。此类提供了通向PassManager的钩子,用于观察各种事件:
- runBeforePipeline
- 该回调仅在执行传递管道(即传递管理器)之前运行。
- runAfterPipeline
- 成功执行传递管道之后,无论是否成功执行此回调。
- runBeforePass
- 该回调将在执行传递之前运行。
- runAfterPass
- 成功执行传递后立即运行此回调。如果执行此钩子,runAfterPassFailed则不会。
- runAfterPassFailed
- 传递执行失败后立即运行此回调。如果执行此钩子,runAfterPass则不会。
- runBeforeAnalysis
- 该回调在计算分析之前运行。
- runAfterAnalysis
- 在计算分析后立即运行此回调。
PassInstrumentation实例可以 pass方法直接向PassManager实例注册 addInstrumentation。添加到PassManager的工具以类似堆栈的方式运行,即最后一个执行runBefore*钩子的工具将是第一个执行相应runAfter*钩子的工具。PassInstrumentation 保证类的钩子以线程安全的方式执行,因此不需要额外的同步。在下面的示例设备中,该设备对计算DominanceInfo分析的次数进行计数:
struct DominanceCounterInstrumentation : public PassInstrumentation {
/// The cumulative count of how many times dominance has been calculated.
unsigned &count;
DominanceCounterInstrumentation(unsigned &count) : count(count) {}
void runAfterAnalysis(llvm::StringRef, TypeID id, Operation *) override {
if (id == TypeID::get<DominanceInfo>())
++count;
}
};
MLIRContext *ctx = ...;
PassManager pm(ctx);
// Add the instrumentation to the pass manager.
unsigned domInfoCount;
pm.addInstrumentation(
std::make_unique<DominanceCounterInstrumentation>(domInfoCount));
// Run the pass manager on a module operation.
ModuleOp m = ...;
if (failed(pm.run(m)))
...
llvm::errs() << "DominanceInfo was computed " << domInfoCount << " times!\n";
标准设备
MLIR利用pass工具框架提供一些有用的开发人员工具和实用程序。这些工具中的每一个均可直接用于MLIR Pass框架的所有用户。
pass时间
PassTiming设备提供有关pass执行和分析计算的时序信息。这可以快速了解哪些通道花费了最多的时间来执行,以及通道对管道总执行时间的影响。用户可以pass直接在PassManager上启用此检测enableTiming。也可以pass-pass-timing标志在mlir-opt中使用此工具。PassTiming设备为计时结果提供了几种不同的显示模式,下面分别介绍每种模式:
列表显示模式
在这种模式下,结果显示在一个列表中,该列表按总时间排序,每个pass/分析实例汇总为一个唯一的结果。此视图有助于大致了解分析/pass最多花费的时间。此显示模式可在mlir-opt via中使用 -pass-timing-display=list。
$ mlir-opt foo.mlir -mlir-disable-threading -pass-pipeline='func(cse,canonicalize)' -convert-std-to-llvm -pass-timing -pass-timing-display=list
===-------------------------------------------------------------------------===
... Pass execution timing report ...
===-------------------------------------------------------------------------===
Total Execution Time: 0.0203 seconds
---Wall Time--- --- Name ---
0.0047 ( 55.9%) Canonicalizer
0.0019 ( 22.2%) VerifierPass
0.0016 ( 18.5%) LLVMLoweringPass
0.0003 ( 3.4%) CSE
0.0002 ( 1.9%) (A) DominanceInfo
0.0084 (100.0%) Total
管道显示模式
在这种模式下,结果将显示在嵌套管道视图中,该视图镜像在通道管理器中执行的内部通道管道。该视图对于特定地了解流水线中哪个部分花费最多的时间很有用,并且还可用于识别何时使分析无效和重新计算。这是默认显示模式。
$ mlir-opt foo.mlir -mlir-disable-threading -pass-pipeline='func(cse,canonicalize)' -convert-std-to-llvm -pass-timing
===-------------------------------------------------------------------------===
... Pass execution timing report ...
===-------------------------------------------------------------------------===
Total Execution Time: 0.0249 seconds
---Wall Time--- --- Name ---
0.0058 ( 70.8%) 'func' Pipeline
0.0004 ( 4.3%) CSE
0.0002 ( 2.6%) (A) DominanceInfo
0.0004 ( 4.8%) VerifierPass
0.0046 ( 55.4%) Canonicalizer
0.0005 ( 6.2%) VerifierPass
0.0005 ( 5.8%) VerifierPass
0.0014 ( 17.2%) LLVMLoweringPass
0.0005 ( 6.2%) VerifierPass
0.0082 (100.0%) Total
多线程传递时序
在pass管理器中启用多线程后,显示的含义会稍有变化。首先,添加一个新的计时列User Time,显示所有线程花费的总时间。其次,该Wall Time 列显示在所有线程中花费的最长的个人时间。这意味着该Wall Time列将继续提供感知时间或时钟时间的指示符,而User Time则将显示总的cpu时间。
$ mlir-opt foo.mlir -pass-pipeline='func(cse,canonicalize)' -convert-std-to-llvm -pass-timing
===-------------------------------------------------------------------------===
... Pass execution timing report ...
===-------------------------------------------------------------------------===
Total Execution Time: 0.0078 seconds
---User Time--- ---Wall Time--- --- Name ---
0.0177 ( 88.5%) 0.0057 ( 71.3%) 'func' Pipeline
0.0044 ( 22.0%) 0.0015 ( 18.9%) CSE
0.0029 ( 14.5%) 0.0012 ( 15.2%) (A) DominanceInfo
0.0038 ( 18.9%) 0.0015 ( 18.7%) VerifierPass
0.0089 ( 44.6%) 0.0025 ( 31.1%) Canonicalizer
0.0006 ( 3.0%) 0.0002 ( 2.6%) VerifierPass
0.0004 ( 2.2%) 0.0004 ( 5.4%) VerifierPass
0.0013 ( 6.5%) 0.0013 ( 16.3%) LLVMLoweringPass
0.0006 ( 2.8%) 0.0006 ( 7.0%) VerifierPass
0.0200 (100.0%) 0.0081 (100.0%) Total
IR打印
调试时,通常在传递管道的各个阶段转储IR很有用。这是IR打印设备发挥作用的地方。pass选择性地对正在执行的遍历进行过滤,该设备可以在遍历执行之前和之后有条件地打印IR。该工具可以passenableIRPrinting方法直接添加到PassManager 。mlir-opt提供了一些使用此工具的有用标志:
- print-ir-before=(comma-separated-pass-list)
- 在pass列表中提供的每个pass之前打印IR。
- print-ir-before-all
- 在管道中的每一次pass之前都要打印IR。
$ mlir-opt foo.mlir -pass-pipeline='func(cse)' -print-ir-before=cse
*** IR Dump Before CSE ***
func @simple_constant() -> (i32, i32) {
%c1_i32 = constant 1 : i32
%c1_i32_0 = constant 1 : i32
return %c1_i32, %c1_i32_0 : i32, i32
}
- print-ir-after=(comma-separated-pass-list)
- 在pass列表中提供的每个pass之后,打印IR。
- print-ir-after-all
- 每次pass管道后,请打印IR。
$ mlir-opt foo.mlir -pass-pipeline='func(cse)' -print-ir-after=cse
*** IR Dump After CSE ***
func @simple_constant() -> (i32, i32) {
%c1_i32 = constant 1 : i32
return %c1_i32, %c1_i32 : i32, i32
}
- print-ir-after-change
- 如果pass更改了IR,则仅在pass之后打印IR。这有助于减少“无趣”pass的IR转储数量。
- 注意:pass比较传递前后算子的哈希值来检测更改。这增加了额外的运行时间来计算IR的哈希值,在极少数情况下,取决于所使用的哈希算法的冲突率,可能会导致假阳性。
- 注意:此选项应与上面的其它“以后打印”选项一起使用,因为仅此选项无法启用打印。
$ mlir-opt foo.mlir -pass-pipeline='func(cse,cse)' -print-ir-after=cse -print-ir-after-change
*** IR Dump After CSE ***
func @simple_constant() -> (i32, i32) {
%c1_i32 = constant 1 : i32
return %c1_i32, %c1_i32 : i32, i32
}
- print-ir-module-scope
- 无论pass类型或算子嵌套级别如何,始终打印顶层模块算子。
- 注意:仅在禁用多线程(-mlir-disable-threading)时才应使用在模块范围内打印
$ mlir-opt foo.mlir -mlir-disable-threading -pass-pipeline='func(cse)' -print-ir-after=cse -print-ir-module-scope
*** IR Dump After CSE *** ('func' operation: @bar)
func @bar(%arg0: f32, %arg1: f32) -> f32 {
...
}
func @simple_constant() -> (i32, i32) {
%c1_i32 = constant 1 : i32
%c1_i32_0 = constant 1 : i32
return %c1_i32, %c1_i32_0 : i32, i32
}
*** IR Dump After CSE *** ('func' operation: @simple_constant)
func @bar(%arg0: f32, %arg1: f32) -> f32 {
...
}
func @simple_constant() -> (i32, i32) {
%c1_i32 = constant 1 : i32
return %c1_i32, %c1_i32 : i32, i32
}
崩溃和失败复制
MLIR中的 pass管理器包含一个内置的机制,即使发生崩溃或pass失败,也可以生成可复制的内容 。可以passPassManager::enableCrashReproducerGeneration或pass命令行标志 启用此功能 pass-pipeline-crash-reproducer。在任何一种情况下,都提供一个参数,该参数对应于.mlir应将可复制文件写入的输出文件名。可复制的内容包含正在执行的过程管理器的配置,以及运行任何过程之前的初始IR。潜在的可复制性可能具有以下形式:
// configuration: -pass-pipeline='func(cse,canonicalize),inline'
// note: verifyPasses=false
module {
func @foo() {
...
}
}
本地复制器生成
可以向命令行传递一个附加标志PassManager::enableCrashReproducerGeneration,并passpass-pipeline-local-reproducer命令行指定 该附加标志 ,以表明pass管理器应尝试生成“本地”再现器。这将尝试在pass失败之前立即生成包含IR的复制器。这对于已知崩溃是在特定遍内或原始输入依赖于可能并不总是可用的成分(如语言或遍)的情况很有用。
例如,如果先前示例中的失败来自canonicalize,则将生成以下复制器:
// configuration: -pass-pipeline='func(canonicalize)'
// note: verifyPasses=false
module {
func @foo() {
...
}
}