├── .clangd ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── ex1-io ├── ex1-io.cpp └── ex1.mlir ├── ex2-build └── ex2-build.cpp ├── ex3-dialect ├── CMakeLists.txt ├── ex3-cse.mlir ├── ex3.mlir ├── include │ └── toy │ │ ├── CMakeLists.txt │ │ ├── Toy.td │ │ ├── ToyDialect.h │ │ ├── ToyDialect.td │ │ ├── ToyOps.h │ │ └── ToyOps.td ├── lib │ ├── CMakeLists.txt │ └── toy.cpp └── tools │ └── toy-opt │ ├── CMakeLists.txt │ └── toy-opt.cpp ├── ex4-beautiful-dialect ├── CMakeLists.txt ├── ex4.mlir ├── include │ └── toy │ │ ├── CMakeLists.txt │ │ ├── Toy.td │ │ ├── ToyDialect.h │ │ ├── ToyDialect.td │ │ ├── ToyOps.h │ │ └── ToyOps.td ├── lib │ ├── CMakeLists.txt │ └── toy.cpp └── tools │ └── toy-opt │ ├── CMakeLists.txt │ └── toy-opt.cpp ├── ex5-pass ├── CMakeLists.txt ├── ex5.mlir ├── include │ └── toy │ │ ├── CMakeLists.txt │ │ ├── Toy.td │ │ ├── ToyDialect.h │ │ ├── ToyDialect.td │ │ ├── ToyOps.h │ │ ├── ToyOps.td │ │ ├── ToyPasses.h │ │ └── ToyPasses.td ├── lib │ ├── CMakeLists.txt │ ├── Transforms │ │ ├── CMakeLists.txt │ │ ├── ConvertToyToArith.cpp │ │ └── DCE.cpp │ └── toy.cpp └── tools │ └── toy-opt │ ├── CMakeLists.txt │ └── toy-opt.cpp ├── ex6-pattern ├── CMakeLists.txt ├── ex6.mlir ├── include │ └── toy │ │ ├── CMakeLists.txt │ │ ├── Toy.td │ │ ├── ToyDialect.h │ │ ├── ToyDialect.td │ │ ├── ToyOps.h │ │ ├── ToyOps.td │ │ ├── ToyPasses.h │ │ └── ToyPasses.td ├── lib │ ├── CMakeLists.txt │ ├── Transforms │ │ ├── CMakeLists.txt │ │ ├── ConvertToyToArith.cpp │ │ └── DCE.cpp │ └── toy.cpp └── tools │ └── toy-opt │ ├── CMakeLists.txt │ └── toy-opt.cpp ├── ex7-convert ├── CMakeLists.txt ├── ex7.mlir ├── include │ └── toy │ │ ├── CMakeLists.txt │ │ ├── Toy.td │ │ ├── ToyDialect.h │ │ ├── ToyDialect.td │ │ ├── ToyOps.h │ │ ├── ToyOps.td │ │ ├── ToyPasses.h │ │ ├── ToyPasses.td │ │ ├── ToyTypes.h │ │ └── ToyTypes.td ├── lib │ ├── CMakeLists.txt │ ├── Transforms │ │ ├── CMakeLists.txt │ │ ├── ConvertToyToArith.cpp │ │ └── DCE.cpp │ └── toy.cpp └── tools │ └── toy-opt │ ├── CMakeLists.txt │ └── toy-opt.cpp └── fig ├── .gitignore ├── DefUseChains.svg ├── MLIR Dialects.jpg ├── Use-list.svg ├── op.drawio └── op.svg /.clangd: -------------------------------------------------------------------------------- 1 | CompileFlags: 2 | Remove: 3 | - -fno-lifetime-dse 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.cache 2 | /build 3 | /.vscode -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13.4) 2 | 3 | project(mlir-toy VERSION 0.0.0) 4 | 5 | # 防止 cmake 生成一堆 warning 6 | cmake_policy(SET CMP0116 NEW) 7 | 8 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 9 | set(CMAKE_CXX_STANDARD 17) 10 | set(CMAKE_CXX_STANDARD_REQUIRED YES) 11 | 12 | # 设置输出目录 13 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) 14 | 15 | add_compile_options(-g) 16 | 17 | find_package(MLIR REQUIRED CONFIG) 18 | list(APPEND CMAKE_MODULE_PATH "${MLIR_CMAKE_DIR}") 19 | list(APPEND CMAKE_MODULE_PATH "${LLVM_CMAKE_DIR}") 20 | 21 | include(TableGen) 22 | include(AddLLVM) 23 | include(AddMLIR) 24 | include(HandleLLVMOptions) 25 | 26 | include_directories(${LLVM_INCLUDE_DIRS} ${MLIR_INCLUDE_DIRS}) 27 | 28 | add_executable(ex1-io ex1-io/ex1-io.cpp) 29 | target_link_libraries( 30 | ex1-io 31 | MLIRIR 32 | MLIRParser 33 | MLIRFuncDialect 34 | MLIRArithDialect 35 | ) 36 | 37 | add_executable(ex2-build ex2-build/ex2-build.cpp) 38 | target_link_libraries( 39 | ex2-build 40 | MLIRIR 41 | MLIRParser 42 | MLIRFuncDialect 43 | MLIRArithDialect 44 | ) 45 | 46 | # add_subdirectory(ex3-dialect) 47 | # add_subdirectory(ex4-beautiful-dialect) 48 | # add_subdirectory(ex5-pass) 49 | add_subdirectory(ex7-convert) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hands-On Practical MLIR Tutorial 2 | 3 | Kexing Zhou(周可行) 4 | 5 | 北京大学 6 | 7 | zhoukexing@pku.edu.cn 8 | 9 | 10 | * 1. [MLIR 简介](#mlir-简介) 11 | * 1.1. [MLIR 编译管线](#mlir-编译管线) 12 | * 1.2. [常见的 Dialect](#常见的-dialect) 13 | * 1.3. [insight:“及时做优化”](#insight:“及时做优化”) 14 | * 1.4. [MLIR 的用处](#mlir-的用处) 15 | * 1.5. [MLIR 的缺点](#mlir-的缺点) 16 | * 2. [MLIR 基本用法](#mlir-基本用法) 17 | * 2.1. [IR 基本结构](#ir-基本结构) 18 | * 2.2. [MLIR 基本工程模板](#mlir-基本工程模板) 19 | * 2.2.1. [配置 clangd](#配置-clangd) 20 | * 2.3. [MLIR 的读入、输出](#mlir-的读入、输出) 21 | * 2.4. [用代码生成 MLIR](#用代码生成-mlir) 22 | * 3. [MLIR Op 的结构](#mlir-op-的结构) 23 | * 3.1. [Attribute 和 Operand](#attribute-和-operand) 24 | * 3.2. [Attribute, Value 和 Type](#attribute,-value-和-type) 25 | * 4. [MLIR 的类型转换](#mlir-的类型转换) 26 | * 4.1. [Op 的类型转换](#op-的类型转换) 27 | * 4.2. [Type / Attribute 的类型转换](#type-/-attribute-的类型转换) 28 | * 5. [MLIR 的图结构](#mlir-的图结构) 29 | * 5.1. [MLIR 数据流图结构](#mlir-数据流图结构) 30 | * 5.2. [MLIR 数据流图的遍历与修改](#mlir-数据流图的遍历与修改) 31 | * 5.3. [MLIR 控制流图的遍历与修改](#mlir-控制流图的遍历与修改) 32 | * 6. [基本的 Dialect 工程](#基本的-dialect-工程) 33 | * 6.1. [TableGen 工程模板](#tablegen-工程模板) 34 | * 6.2. [Tablegen Language Server](#tablegen-language-server) 35 | * 6.3. [IR 的默认定义与实现](#ir-的默认定义与实现) 36 | * 6.3.1. [TableGen 文件](#tablegen-文件) 37 | * 6.3.2. [头文件](#头文件) 38 | * 6.3.3. [库文件](#库文件) 39 | * 6.3.4. [程序入口](#程序入口) 40 | * 7. [TableGen Op 定义详解](#tablegen-op-定义详解) 41 | * 7.1. [Attribute、Type、Constraint](#attribute、type、constraint) 42 | * 7.1.1. [内置 Attribute](#内置-attribute) 43 | * 7.1.2. [内置的 Type](#内置的-type) 44 | * 7.1.3. [为什么 Attribute 和 Type 都是 Constraint](#为什么-attribute-和-type-都是-constraint) 45 | * 7.2. [Verifier:发现IR错误](#verifier:发现ir错误) 46 | * 7.2.1. [emitError](#emiterror) 47 | * 7.2.2. [LogicalResult](#logicalresult) 48 | * 7.3. [Variadic:可变参数](#variadic:可变参数) 49 | * 7.3.1. [多个可变参数:AttrSizedOperandSegments](#多个可变参数:attrsizedoperandsegments) 50 | * 7.4. [AssemblyFormat:更易读的输出](#assemblyformat:更易读的输出) 51 | * 7.4.1. [常用关键字](#常用关键字) 52 | * 7.4.2. [额外 attr 字典](#额外-attr-字典) 53 | * 7.4.3. [输出 type](#输出-type) 54 | * 7.4.4. [可选输出:Optional、UnitAttr](#可选输出:optional、unitattr) 55 | * 7.5. [Builder:自定义 create 函数](#builder:自定义-create-函数) 56 | * 7.5.1. [默认Builder](#默认builder) 57 | * 7.5.2. [自定义builder](#自定义builder) 58 | * 7.6. [自定义函数](#自定义函数) 59 | * 7.6.1. [header target](#header-target) 60 | * 7.7. [使用 Trait](#使用-trait) 61 | * 7.7.1. [内存副作用:SideEffectInterfaces](#内存副作用:sideeffectinterfaces) 62 | * 7.7.2. [类型推断:InferTypeOpInterface](#类型推断:infertypeopinterface) 63 | * 7.8. [函数:FunctionOpTrait](#函数:functionoptrait) 64 | * 7.8.1. [定义 Return](#定义-return) 65 | * 7.8.2. [定义 Function](#定义-function) 66 | * 7.8.3. [定义 Call](#定义-call) 67 | * 8. [添加 Pass](#添加-pass) 68 | * 8.1. [Pass 工程模板](#pass-工程模板) 69 | * 8.2. [Pass 定义详解](#pass-定义详解) 70 | * 8.2.1. [指定 Pass 在哪个 Op 上运行](#指定-pass-在哪个-op-上运行) 71 | * 8.2.2. [带参数的 Pass](#带参数的-pass) 72 | * 8.3. [简单的 DCE Pass 实现](#简单的-dce-pass-实现) 73 | * 8.3.1. [定义](#定义) 74 | * 8.3.2. [实现](#实现) 75 | * 9. [Pattern Rewrite](#pattern-rewrite) 76 | * 9.1. [Pattern Rewrite](#pattern-rewrite-1) 77 | * 9.1.1. [描述 Pattern](#描述-pattern) 78 | * 9.1.2. [调用 Pattern](#调用-pattern) 79 | * 9.1.3. [Depedent Dialect & Linking](#depedent-dialect-&-linking) 80 | * 9.2. [Dialect Convertion (Type Conversion)](#dialect-convertion-(type-conversion)) 81 | * 9.2.1. [TypeConverter](#typeconverter) 82 | * 9.2.2. [Conversion Pattern:自动做 Operand 的类型转换](#conversion-pattern:自动做-operand-的类型转换) 83 | * 9.2.3. [类型转换的细节与 Debug](#类型转换的细节与-debug) 84 | * 9.2.4. [使用自己的 materialization](#使用自己的-materialization) 85 | * 9.3. [使用 MLIR 里已有的 Pattern 做多步转换](#使用-mlir-里已有的-pattern-做多步转换) 86 | * 10. [自定义 Type](#自定义-type) 87 | * 11. [TIPS](#tips) 88 | * 11.1. [如何找头文件、找想要的函数](#如何找头文件、找想要的函数) 89 | * 11.2. [如何找需要连接的库](#如何找需要连接的库) 90 | * 11.3. [如何加快编译速度](#如何加快编译速度) 91 | * 11.4. [去 MLIR 里抄代码](#去-mlir-里抄代码) 92 | * 12. [MLIR 的批判:C++ v.s. Rust](#mlir-的批判:c++-v.s.-rust) 93 | * 13. [Issue & Reply](#issue-&-reply) 94 | 95 | 99 | 100 | 101 | 102 | ## 1. MLIR 简介 103 | 104 | ### 1.1. MLIR 编译管线 105 | 106 | MLIR 在于设计一套可复用的编译管线,包括可复用的 IR、Pass 和 IO 系统。在 IR 中,多个 Dialect 可以混合存在。MLIR 已经定义好了一套 Dialect Translation Graph: 107 | 108 | ![](fig/MLIR%20Dialects.jpg) 109 | 110 | ### 1.2. 常见的 Dialect 111 | 112 | MLIR 的 Dialect 是相对独立的,下面列举一些常见的 dialect: 113 | 114 | 1. **func**:处理函数的dialect,包含的函数定义、调用、返回等基本操作 115 | 2. **arith**:处理加减乘除移位等各种运算 116 | * **math**:更复杂的运算,如 log, exp, tan 等 117 | 3. **affine**:处理循环嵌套,实现了循环展开、多面体变换等一些算法 118 | 4. **scf**:(structured control flow) 结构化控制流,保留 for,if 等语句 119 | * **cf**:无结构控制流,只有条件跳转命令 120 | 5. **llvm**:LLVM IR 的 binding,可以直接翻译给 LLVM 做后续编译 121 | 122 | MLIR的编译从高层次的 tensor 到 低层次的 scf,cf,每个阶段都是多个 dialect 的混合体,每次 lowering 往往只针对一个 dialect 进行。 123 | 124 | 163 | 164 | ### 1.3. insight:“及时做优化” 165 | 166 | 这里简单举例,dialect 是如何混合的。 167 | 168 | 例如,我是 pytorch,生成了一些神经网络,我想要表示这些运算: 169 | 170 | * Tensor 是一块带 shape 的指针:使用 **tensor** dialect 171 | * 简单的 elementwise 加减乘除:使用 **arith** dialect 172 | * 复杂的 log、exp 等运算:使用 **math** dialect 173 | * 矩阵线性代数运算:使用 **linalg** dialect 174 | * 可能有一些控制流:使用 **scf** dialect 175 | * 整个网络是一个函数:使用 **func** dialect 176 | 177 | 接下来,将其逐渐 lower 到 LLVM: 178 | 179 | * 调用 Lowering Pass,把 **tensor** lowering 到 **linalg**,而其他的 dialect 不会改变。 180 | * 继续调用 pass,直到把 **linalg** 转换到 **affine** -> **scf** -> **cf**,其他表示运算的 dialect 保留不变。 181 | * 继续 Lowering,把 **memref** 转换为裸指针、**arith** 和 **func** 转换为 llvm 内置运算。 182 | * 最后,所有非 **llvm** dialect 都被转换为了 **llvm** dialect,现在可以导出为 llvm ir 交给 llvm 继续编译。 183 | 184 | **可见,MLIR 编译有一个特点**:不同 dialect 是独立的。 185 | 186 | * 例如,在做循环展开等优化的时候,我不需要关心加法和减法可以合并;而在做算数表达式优化的时候,也不需要关心当前在哪个函数里边。 187 | 188 | 189 | **MLIR 可以从各个层次优化 IR**:例如: 190 | 191 | * 在 **affine** 层面,可以根据循环大小做展开,向量化 192 | * 在 **scf** 层面,可以发现循环不变量 193 | * 在 **arith** 层面,可以用算数恒等式优化代码 194 | 195 | MLIR 的 insight 在于“**及时做优化**”。很明显,linalg 层次,我们很容易发现矩阵被转置了两次,但一旦 lower 到 scf,所有转置操作都变成循环,优化就很难进行了。 196 | 197 | ### 1.4. MLIR 的用处 198 | 199 | 我们使用 MLIR,主要也是想要复用别人已经写好的代码,一般包括: 200 | 201 | * 复用已有 dialect 作为 **输入**,不用自己写前端。 202 | * 如 Polygeist 能把 C 翻译成 Affine Dialect,这样我们就不用写 C Parser 203 | * 将已有 dialect **混入**或**作为输出**。 204 | * 如 arith 等 dialect,可以直接集成起来,不需要自己写。 205 | * 要生成 binary 的时候,可以直接生成 LLVM Dialect,复用后端 LLVM 编译管线 206 | * 复用已有的 Pass。 207 | * 常见的 Pass 如 CSE,DCE 可以复用 208 | * Dialect 专用 Pass,如循环展开,也可以复用 209 | 210 | ### 1.5. MLIR 的缺点 211 | 212 | MLIR 也有缺点: 213 | 214 | * 太过笨重,编译、链接时间长(可能会连接出上百M的文件) 215 | * 可以用 lld 来加快链接速度,但依然很慢 [见TIPS](#113-如何加快编译速度) 216 | * Dialect 定义极不灵活,定义较复杂 Op 时非常麻烦 217 | 218 | ## 2. MLIR 基本用法 219 | 220 | ### 2.1. IR 基本结构 221 | 222 | MLIR 是 树形结构,每个节点是 Operation,Op 可以组成 Block,Block 组成 Region,而 Region 又可以嵌套在 Op 内部。 223 | 224 | * **Operation** 指单个运算,运算内可以嵌套 **Region** 225 | * **Block** 指基本块,基本块包含一个或多个 **Operation** 226 | * **Region** 指区域,类似于循环体或函数体,包含若干 **Block** 227 | 228 | MLIR 的基本块使用 **“基本块参数”** 来取代“phi函数”,如下面的例子: 229 | 230 | * **块参数**:每个基本块都带有参数,块内可以使用 231 | * **终止符**:每个基本块的一般为跳转或返回,跳转时需要附上块参数 232 | 233 | ```mlir 234 | module { 235 | func.func @foo(%a: i32, %b: i32, %c: i32) -> i32 { 236 | %cmp = arith.cmpi "sge", %a, %b : i32 237 | cf.cond_br %cmp, ^add(%a: i32), ^add(%b: i32) 238 | ^add(%1: i32): 239 | %ret = llvm.add %1, %c : i32 240 | cf.br ^ret 241 | ^ret: 242 | func.return %ret : i32 243 | } 244 | } 245 | ``` 246 | 247 | **module**: 默认情况下,mlir 最外层是 `builtin.module`,作为 IR 的根。 248 | 249 | ### 2.2. MLIR 基本工程模板 250 | 251 | 构建第一个 mlir 项目往往非常困难,下面给一个我常用的工程模板: 252 | 253 | ``` 254 | mlir-tutorial 255 | ├── install # Install Prefix,把 MLIR 编译后安装到这里 256 | ├── llvm-project # MLIR 项目 257 | └── mlir-toy # 自己的 MLIR 工程 258 | ``` 259 | 260 | 首先,按照 MLIR [getting started](https://mlir.llvm.org/getting_started/) 的方法,安装 MLIR。注意,安装的时候要设置 PREFIX 为 install 目录,如下面所示,和 getting start 上的略有区别: 261 | 262 | ```bash 263 | git clone https://github.com/llvm/llvm-project.git 264 | cd llvm-project 265 | git checkout 186a4b3b657878ae2aea23caf684b6e103901162 # 本教程使用的版本 266 | mkdir build && cd build 267 | cmake -G Ninja ../llvm \ 268 | -DCMAKE_INSTALL_PREFIX=/mlir-tutorial/install \ 269 | -DLLVM_ENABLE_PROJECTS=mlir \ 270 | -DLLVM_BUILD_EXAMPLES=ON \ 271 | -DLLVM_TARGETS_TO_BUILD="Native;NVPTX;AMDGPU" \ 272 | -DCMAKE_BUILD_TYPE=Release \ 273 | -DLLVM_ENABLE_ASSERTIONS=ON 274 | ``` 275 | 276 | 在 build 完之后,安装到 prefix 277 | 278 | ```bash 279 | ninja install 280 | ``` 281 | 282 | 现在,mlir 把所有二进制文件、库文件都装到了 install 目录下。现在可以 `export PATH=/mlir-tutorial/install/bin:$PATH`,方便调用 `bin` 里面的 `mlir-opt` 等程序。 283 | 284 | ```bash 285 | install 286 | ├── bin 287 | ├── examples 288 | ├── include 289 | ├── lib 290 | └── share 291 | ``` 292 | 293 | 接下来,在 mlir-toy 里面建立一个简单的工程 294 | 295 | ```bash 296 | mlir-toy 297 | ├── CMakeLists.txt 298 | └── main.cpp 299 | ``` 300 | 301 | 其中 CMakeLists.txt 文件写法比较固定: 302 | 303 | ```cmake 304 | cmake_minimum_required(VERSION 3.13.4) 305 | 306 | project(mlir-toy VERSION 0.0.0) 307 | 308 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # 生成 compile_commands.json 便于代码高亮 309 | set(CMAKE_CXX_STANDARD 17) 310 | set(CMAKE_CXX_STANDARD_REQUIRED YES) 311 | 312 | find_package(MLIR REQUIRED CONFIG) 313 | list(APPEND CMAKE_MODULE_PATH "${MLIR_CMAKE_DIR}") 314 | list(APPEND CMAKE_MODULE_PATH "${LLVM_CMAKE_DIR}") 315 | 316 | include(TableGen) 317 | include(AddLLVM) 318 | include(AddMLIR) 319 | include(HandleLLVMOptions) 320 | 321 | include_directories(${LLVM_INCLUDE_DIRS} ${MLIR_INCLUDE_DIRS}) 322 | 323 | add_executable(mlir-toy main.cpp) 324 | ``` 325 | 326 | 在 main.cpp 里面写一个 main 函数,然后先 build 一下。注意,必须要写上 `CMAKE_INSTALL_PREFIX`,这样cmake 可以自动找到 MLIR。 327 | 328 | ```bash 329 | cd build 330 | cmake .. -GNinja -DCMAKE_INSTALL_PREFIX=/mlir-tutorial/install 331 | ninja 332 | ``` 333 | 334 | #### 2.2.1. 配置 clangd 335 | 336 | 使用 vscode 默认的 lint 工具跑 mlir 会非常卡,建议使用 clangd。 337 | 338 | * 在扩展里安装 clangd 插件 339 | * cmake 的时候加上 `-DCMAKE_EXPORT_COMPILE_COMMANDS=ON`(上面的CMakeLists.txt 已经加上了) 340 | * 有时候要吧 compile_commands.json 拷贝到工程根目录,或者在 vscode 设置里配置一下 341 | * 一旦发现高亮炸了,vscode里 Ctrl + Shift + P,输入 clangd: restart language server 342 | * 有时候,mlir 的编译选项与 clangd 冲突,在 mlir-toy 目录下建立 .clangd 文件,去掉相关的选项: 343 | ```yaml 344 | CompileFlags: 345 | Remove: 346 | - -fno-lifetime-dse 347 | ``` 348 | 349 | ### 2.3. MLIR 的读入、输出 350 | 351 | 测试用 mlir: 352 | 353 | ```mlir 354 | func.func @test(%a: i32, %b: i32) -> i32 { 355 | %c = arith.addi %a, %b : i32 356 | func.return %c : i32 357 | } 358 | ``` 359 | 360 | 最简单的读入输出: 361 | 362 | ```cpp 363 | #include "mlir/IR/AsmState.h" 364 | #include "mlir/IR/BuiltinOps.h" 365 | #include "mlir/IR/MLIRContext.h" 366 | #include "mlir/Parser/Parser.h" 367 | #include "mlir/Support/FileUtilities.h" 368 | #include "mlir/Dialect/Func/IR/FuncOps.h" 369 | #include "mlir/Dialect/Arith/IR/Arith.h" 370 | #include "llvm/Support/raw_ostream.h" 371 | 372 | using namespace mlir; 373 | 374 | int main(int argc, char ** argv) { 375 | MLIRContext ctx; 376 | // 首先,注册需要的 dialect 377 | ctx.loadDialect(); 378 | // 读入文件 379 | auto src = parseSourceFile(argv[1], &ctx); 380 | // 输出dialect,也可以输出到 llvm::errs(), llvm::dbgs() 381 | src->print(llvm::outs()); 382 | // 简单的输出,在 debug 的时候常用 383 | src->dump(); 384 | return 0; 385 | } 386 | ``` 387 | 388 | 需要连接上所有依赖的文件: 389 | 390 | ```cmake 391 | target_link_libraries( 392 | ex1-io 393 | MLIRIR 394 | MLIRParser 395 | MLIRFuncDialect 396 | MLIRArithDialect 397 | ) 398 | ``` 399 | 400 | 测试方式: 401 | 402 | ```bash 403 | ./ex1-io ../ex1-io/ex1.mlir 404 | ``` 405 | 406 | ### 2.4. 用代码生成 MLIR 407 | 408 | ```cpp 409 | #include "mlir/IR/AsmState.h" 410 | #include "mlir/IR/Builders.h" 411 | #include "mlir/IR/BuiltinOps.h" 412 | #include "mlir/IR/MLIRContext.h" 413 | #include "mlir/Parser/Parser.h" 414 | #include "mlir/Support/FileUtilities.h" 415 | 416 | #include "mlir/Dialect/Func/IR/FuncOps.h" 417 | #include "mlir/Dialect/Arith/IR/Arith.h" 418 | #include "llvm/Support/raw_ostream.h" 419 | 420 | using namespace mlir; 421 | 422 | int main(int argc, char ** argv) { 423 | MLIRContext ctx; 424 | ctx.loadDialect(); 425 | 426 | // 创建 OpBuilder 427 | OpBuilder builder(&ctx); 428 | auto mod = builder.create(builder.getUnknownLoc()); 429 | 430 | // 设置插入点 431 | builder.setInsertionPointToEnd(mod.getBody()); 432 | 433 | // 创建 func 434 | auto i32 = builder.getI32Type(); 435 | auto funcType = builder.getFunctionType({i32, i32}, {i32}); 436 | auto func = builder.create(builder.getUnknownLoc(), "test", funcType); 437 | 438 | // 添加基本块 439 | auto entry = func.addEntryBlock(); 440 | auto args = entry->getArguments(); 441 | 442 | // 设置插入点 443 | builder.setInsertionPointToEnd(entry); 444 | 445 | // 创建 arith.addi 446 | auto addi = builder.create(builder.getUnknownLoc(), args[0], args[1]); 447 | 448 | // 创建 func.return 449 | builder.create(builder.getUnknownLoc(), ValueRange({addi})); 450 | mod->print(llvm::outs()); 451 | return 0; 452 | } 453 | ``` 454 | 455 | **如何寻找 builder.create 的参数**:builder.create 内部是调用 `Op::build` 函数的,你可以 Ctrl + 鼠标点击找到 `func::FuncOp` 的定义,然后找里面的 build 函数,看参数表。 456 | 457 | ## 3. MLIR Op 的结构 458 | 459 | MLIR 的一个 Operation 里可以包含下面的一些东西: 460 | 461 | * Operand:这个 Op 接受的操作数 462 | * Result:这个 Op 生成的新 Value 463 | * Attribute:可以理解为编译器常量 464 | * Region:这个 Op 内部的 Region 465 | 466 | MLIR 中,Attribute 是高度灵活的,允许插入原来不存在的 attr,允许不同 dialect 互相插入 attribute。 467 | 468 | ### 3.1. Attribute 和 Operand 469 | 470 | Attribute 和 Operand 有一些区别。Attribute 指的编译器已知的量,而 Operand 指只有运行时才能知道的量。 471 | 472 | 如下面的这个Op,0 是一个 Attribute 而不是一个 Operand 473 | 474 | ```mlir 475 | %c0 = arith.constant 0 : i32 476 | ``` 477 | 478 | ### 3.2. Attribute, Value 和 Type 479 | 480 | Value 必然包含 Type,Type 也可以作为 Attribute 附加在 Operation 上。 481 | 482 | 例如函数 Op,虽然 %a, %b 出现在了参数表里,但它们实际上是函数类型的一部分,算是 Type Attribute。 483 | 484 | ```mlir 485 | func.func @test(%a: i32, %b: i32) -> i32 { 486 | %c = arith.addi %a, %b : i32 487 | func.return %c : i32 488 | } 489 | ``` 490 | 491 | * `mlir-opt --mlir-print-op-generic` 来打印这里的代码,得到下面的代码。参数名被隐去,只有 function_type 作为 attribute 保留了下来。 492 | ```mlir 493 | "builtin.module"() ({ 494 | "func.func"() <{function_type = (i32, i32) -> i32, sym_name = "test"}> ({ 495 | ^bb0(%arg0: i32, %arg1: i32): 496 | %0 = "arith.addi"(%arg0, %arg1) : (i32, i32) -> i32 497 | "func.return"(%0) : (i32) -> () 498 | }) : () -> () 499 | }) : () -> () 500 | ``` 501 | 502 | ## 4. MLIR 的类型转换 503 | 504 | ### 4.1. Op 的类型转换 505 | 506 | MLIR 的所有 Op 都有一个统一的储存格式,叫 `Operation`。`Operation` 里面存了 OpName 和所有的 operands, results, attributes 和其它的东西。 507 | 508 | 用户定义的 `arith.addi` 等等 Op,本质上都是 `Operation` 的指针。但与 `Operation*` 不同的是,`AddIOp` 定义了 `Operation` 里储存的数据的解释方式。如 AddOp,自己是一个 `Operation` 的指针,也定义了一个函数 `getLhs` 用来返回第一个值,当作 lhs。 509 | 510 |
511 | 512 | **DownCast**:如何在拿到 `Operation*` 的情况下,将其转换为 `AddOp` 呢?llvm 提供了一些转换函数,这些函数会检查 Operation 的 OpName,并进行转换。 513 | 514 | ```cpp 515 | using namespace llvm; 516 | void myCast(Operation * op) { 517 | auto res = cast(op); // 直接转换,失败报错 518 | auto res = dyn_cast(op); // 尝试转换,失败返回 null,op为null时报错 519 | auto res = dyn_cast_if_present(op); // 类似 dyn_cast,op为null时返回null 520 | } 521 | ``` 522 | 523 | **相等关系**:两个 Operation* 相等,指的是它们指向同一个 Operation 实例,而不是这个 Operation 的 operand,result,attr 相等。 524 | 525 | **Hashing**:在不修改 IR 的情况下,每个 `Operation` 有唯一地址。于是,可以直接用 `Operation*` 当作值建立哈系表,用来统计 IR 中数据或做分析: 526 | 527 | ```cpp 528 | #include "llvm/ADT/DenseMap.h" 529 | llvm::DenseMap numberOfReference; 530 | ``` 531 | 532 | ### 4.2. Type / Attribute 的类型转换 533 | 534 | MLIR 的 Type 和 Attribute 与 Op 类似。Type 是到 TypeStorage 的指针,Attribute 也是到 AttributeStorage 的指针。 535 | 536 | * TypeStorage 里面会存 Type 的参数,如 Integer 会存 width,Array 会存 Shape。 537 | 538 | | 专用指针 | 通用指针 | 值(存在 Context中)| 539 | |:------------|:------------|:--------------------| 540 | | AddOp | Operation* | Operation | 541 | | IntegerType | Type | TypeStorage | 542 | | IntegerAttr | Attribute | AttrStorage | 543 | 544 | **全局单例**:与 Op 不同的是,MLIR Context 会完成 Type 和 Attribute 的去重工作。**Type相等,它们的TypeStorage也一定相等。** 545 | 546 | **DownCast**:Type 的 DownCast 与 Op 相同。 547 | 548 | **Hashing**:与 Op 类似,Type 也可以作为 Key 来建哈系表,但不那么常用。 549 | 550 | ## 5. MLIR 的图结构 551 | 552 | MLIR 里,有两个层次的图: 553 | 554 | * 第一个是 Region 嵌套构成的树,这个图表示 **控制流** 555 | * 第二个是 Op/Value 构成的图,这个图表示 **数据流** 556 | 557 | ### 5.1. MLIR 数据流图结构 558 | 559 | MLIR 的数据流图是由 Operation 和 Value 构成的。MLIR 官网上,IR Structure 里面的 [两幅图](https://mlir.llvm.org/docs/Tutorials/UnderstandingTheIRStructure/#traversing-the-def-use-chains) 将 MLIR 的图结构解释得非常清楚: 560 | 561 | 首先是 **Operation 的连接**: 562 | 563 | * Value 要么来自于 Operation 的 Result 要么来自于 BlockArgument 564 | * 每个 Operation 的 Operand 都是到 Value 的指针 565 | * 要修改 Operand 的时候,实际修改的应该是 OpOperand 566 | 567 |
568 | 569 | 然后,是 **Value 的 use-chain**: 570 | 571 | * 每个 Value 都将其 User 连接在一起了 572 | 573 |
574 | 575 | 可见,MLIR 的图是一个双向的图结构,在遍历尤其是修改的时候需要特别小心。 576 | 577 | * 在修改 OpOpeand 的时候,对应 value 的 use-chain 会暗中被 MLIR 改掉 578 | * 在调用 `value->getDefiningOp()` 的时候,BlockArgument 会返回 null 579 | 580 | ### 5.2. MLIR 数据流图的遍历与修改 581 | 582 | MLIR 数据流图的遍历往往遵循一种模式:Operation 调用函数找 Value,再用 Value 调用函数找 Operation,交替进行。 583 | 584 | 其中,**Operation 找 Value 的方法**有: 585 | 586 | * **getOperands**、**getResults**:这两个非常常用,如下面的代码可以用来 Op 找 Op 587 | ```cpp 588 | for(auto operand: op->getOperands()) { 589 | if(auto def = operand.getDefiningOp()) { 590 | // do something 591 | } 592 | else { 593 | // block argument 594 | } 595 | } 596 | ``` 597 | * **getOpOperands**:这个在需要更改 operands 的时候非常有用,例如下面的代码将 value 做替换: 598 | ```cpp 599 | IRMapping mapping; 600 | // 将 op1 的 results 映射到 op2 的 results 601 | mapping.map(op1->getResults(), op2->getResults()); 602 | for(auto &opOperand: op3->getOpOperands()) { 603 | // 将 op3 的参数里含有 op1 results 的替换为 op2 的 604 | // lookupOrDefault 指找不到 mapping 就用原来的 605 | opOperand.set(mapping.lookupOrDefault(opOperand.get())); 606 | } 607 | ``` 608 | 609 | **Value 找 Op 的方法**有: 610 | 611 | * **getDefiningOp**:可能返回 null 612 | * **getUses**:返回 OpOperand 的迭代器 613 | * **getUsers**:返回 Operation 的迭代器 614 | 615 | **Op的getUses和getUser**:operation 也有 getUses 和 getUsers 函数,等价于把这个 op 的所有 result 的 Uses 或 Users 拼在一起。 616 | 617 | **Value的修改**:Value 支持 **replaceAllUseWith** 修改,一种*看起来*等价的代码是: 618 | ```cpp 619 | for(auto & uses: value.getUses()) { 620 | uses.set(new_value); 621 | } 622 | ``` 623 | 但需要注意,上面的代码是**非常危险**的。因为在 uses.set 的时候,会修改 value 的 use chain,而 value 的 use-chain 正在被遍历,可能一修改就挂了。于是,最好用 mlir 提供好的 `replaceAllUseWith` 来修改。 624 | 625 | ### 5.3. MLIR 控制流图的遍历与修改 626 | 627 | 与数据流图相比,控制流图遍历更简单,常用的一些函数: 628 | 629 | * **op.getParentOp**, **op.getParentOfType**:获取父亲Op 630 | * **op.getBlock**:注意是返回父亲block,而不是函数block 631 | * **op.getBody**:这个才是返回内部 block / region 632 | 633 | 遍历儿子的方法: 634 | 635 | * **op.walk**:递归地遍历所有子孙op: 636 | 637 | ```cpp 638 | // 递归遍历所有儿子 639 | func.walk([](Operation * child) { 640 | // do something 641 | }); 642 | // 递归遍历所有是 `ReturnOp` 类型的儿子 643 | func.walk([](ReturnOp ret) { 644 | // do something 645 | }) 646 | ``` 647 | 648 | * **block**:直接就是一个 iterator,可以直接遍历: 649 | 650 | ```cpp 651 | Block * block = xxx 652 | for(auto & item: *block) { 653 | // do something 654 | } 655 | ``` 656 | 657 | 其他遍历方法如 `getOps` 可以自行尝试。 658 | 659 | 控制流图的修改主要用 `OpBuilder` 完成。强烈建议把找到 `OpBuilder` 的代码,把里面有什么函数都看一看,常见的: 660 | 661 | * **builder.create**:创建op 662 | * **builder.insert**:插入remove的op 663 | * **op->remove()**:从当前块移除,但不删除,可以插入到其他块内 664 | * **op->erase()**:从当前块移除,并且删除 665 | 666 | **删除顺序**:在删除一个 op 的时候,这个 op 不能存在 user,否则会报错。 667 | 668 | ## 6. 基本的 Dialect 工程 669 | 670 | 这一节会讲如何用 tablegen 定义自己的 dialect,使用 mlir 自带的通用程序入口 `MlirOptMain`,生成 `toy-opt`。 671 | 672 | 这只是一个简单的工程模板,`toy-opt` 只能识别 Op 的 generic 格式。但先不着急,我们先构建出工程的骨架,在往上不断添加 feature。 673 | 674 | ```mlir 675 | %c = "toy.add"(%a, %b): (i32, i32) -> i32 // 可以读取 676 | %c = toy.add %a, %b : i32 // 无法读取 677 | ``` 678 | 679 | ### 6.1. TableGen 工程模板 680 | 681 | 这个过于复杂,请参考附带例子 `ex3-dialect`: 682 | 683 | 文件结构: 684 | 685 | ```bash 686 | ex3-dialect 687 | ├── CMakeLists.txt # 控制其他各个部分的 CMakeList 688 | ├── include 689 | │ └── toy 690 | │ ├── CMakeLists.txt # 控制 Dialect 定义的 CMakeList 691 | │ ├── ToyDialect.h # Dialect 头文件 692 | │ ├── ToyDialect.td # Dialect TableGen 文件 693 | │ ├── ToyOps.h # Op 头文件 694 | │ ├── ToyOps.td # Op TableGen 文件 695 | │ └── Toy.td # 把 ToyDialect.td 和 ToyOps.td include 到一起,用于 tablegen 696 | ├── lib 697 | │ ├── CMakeLists.txt 698 | │ └── toy.cpp # Dialect library 699 | └── tools 700 | └── toy-opt 701 | ├── CMakeLists.txt 702 | └── toy-opt.cpp # Executable Tool 703 | ``` 704 | 705 | ### 6.2. Tablegen Language Server 706 | 707 | vscode 提供 mlir 扩展,可以为我们写 tablegen 文件提供帮助。在 `/mlir-tutorial/install/bin` 里面,有 `mlir-lsp-server`。在 vscode 的设置里找到 mlir-lsp-server 的设置,设好绝对路径,还有 database 的路径。 708 | 709 | 注意,lsp-server 很容易突然崩溃,炸了的时候用 Ctrl+Shift+P,"mlir: restart language server"。 710 | 711 | ### 6.3. IR 的默认定义与实现 712 | 713 | #### 6.3.1. TableGen 文件 714 | 715 | 1. `include/ToyDialect.td`:定义 Dialect 名字和cpp命名空间 716 | 717 | ```tablegen 718 | include "mlir/IR/OpBase.td" 719 | def ToyDialect : Dialect { 720 | let name = "toy"; 721 | let cppNamespace = "::toy"; 722 | let summary = "Toy Dialect"; 723 | } 724 | ``` 725 | 726 | 2. `include/ToyOps.td`:定义 Operation 727 | 728 | ```tablegen 729 | include "mlir/IR/OpBase.td" 730 | include "toy/ToyDialect.td" 731 | include "mlir/Interfaces/SideEffectInterfaces.td" 732 | 733 | // mnemonic 指名字 734 | class ToyOp traits = []> : 735 | Op; 736 | 737 | // Pure 是 Trait,表示没有 SideEffect 的纯函数 738 | def AddOp : ToyOp<"add", [Pure]> { 739 | let summary = "add operation"; 740 | let arguments = (ins AnyInteger:$lhs, AnyInteger:$rhs); 741 | let results = (outs AnyInteger:$result); 742 | } 743 | ``` 744 | 745 | 3. `include/Toy.td`:把其他的 td include 到一起,用于交给 tablegen 生成 746 | 747 | ```tablegen 748 | include "toy/ToyDialect.td" 749 | include "toy/ToyOps.td" 750 | ``` 751 | 752 | 注意添加 include 目录。 753 | ```cmake 754 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) 755 | ``` 756 | 757 | 4. `include/CMakeLists.txt`:调用 tablegen 生成代码,其中,第一个 Toy 是 Dialect 的名字,第二个 toy 指的是 `toy.td` 758 | 759 | ```cmake 760 | add_mlir_dialect(Toy toy) 761 | ``` 762 | 763 | #### 6.3.2. 头文件 764 | 765 | 5. tablegen 生成的文件放在 `build/include/toy` 里,包括默认的定义和实现 766 | 767 | * `ToyDialect.{h,cpp}.inc`:存 Dialect 的定义和实现 768 | * `Toy.{h,cpp}.inc`:存 Op 的定义和实现 769 | 770 | tablegen 生成到了 `build` 目录,需要额外添加 include 771 | ```cmake 772 | include_directories(${CMAKE_CURRENT_BINARY_DIR}/include) 773 | ``` 774 | 775 | 6. `include/ToyDialect.h`:把 Dialect 的定义加载进来 776 | 777 | ```cpp 778 | #pragma once 779 | #include "mlir/IR/BuiltinDialect.h" 780 | #include "toy/ToyDialect.h.inc" // include 进来就可以了 781 | ``` 782 | 783 | 7. `include/ToyOps.h`:把 Op 的定义加载进来 784 | 785 | ```cpp 786 | #pragma once 787 | #include "mlir/IR/BuiltinOps.h" 788 | #include "mlir/IR/Builders.h" 789 | // td 里面 include 的,这里也要 include 对应的 h 文件 790 | #include "toy/ToyDialect.h" 791 | #include "mlir/Interfaces/SideEffectInterfaces.h" 792 | #define GET_OP_CLASSES 793 | #include "toy/Toy.h.inc" 794 | ``` 795 | 796 | #### 6.3.3. 库文件 797 | 798 | 8. `lib/toy.cpp`:把默认 Dialect 和 Op 的默认实现加载进来 799 | 800 | ```cpp 801 | #include "toy/ToyDialect.h" 802 | #include "toy/ToyOps.h" 803 | #include "toy/ToyDialect.cpp.inc" 804 | #define GET_OP_CLASSES 805 | #include "toy/Toy.cpp.inc" 806 | using namespace toy; 807 | void ToyDialect::initialize() { 808 | // 下面的代码会生成 Op 的列表,专门用来初始化 809 | addOperations< 810 | #define GET_OP_LIST 811 | #include "toy/Toy.cpp.inc" 812 | >(); 813 | } 814 | ``` 815 | 816 | 9. `lib/CMakeLists.txt`:前面的 tablegen 会生成一个 `MLIRxxxIncGen` 的 Target,library 需要依赖这个 Target,才能先生成头文件,再编译 toy.cpp。一般 Library 取名为 `MLIRToy` 或者 `Toy`。 817 | ```cmake 818 | add_mlir_library(Toy toy.cpp DEPENDS MLIRToyIncGen) 819 | ``` 820 | 821 | #### 6.3.4. 程序入口 822 | 823 | 10. `tools/toy-opt/toy-opt.cpp`:mlir 提供了一个可复用的通用的程序入口,我们可以在 `MlirOptMain` 前面注册我们想要的 Dialect 和 Pass,接下来调用 `MlirOptMain`,就可以使用一些默认提供的功能。 824 | 825 | ```cpp 826 | #include "mlir/IR/DialectRegistry.h" 827 | #include "mlir/Tools/mlir-opt/MlirOptMain.h" 828 | // 导入 Func Dialect 829 | #include "mlir/Dialect/Func/IR/FuncOps.h" 830 | // 导入 MLIR 自带 Pass 831 | #include "mlir/Transforms/Passes.h" 832 | // 导入我们新建的 Dialect 833 | #include "toy/ToyDialect.h" 834 | using namespace mlir; 835 | using namespace llvm; 836 | 837 | int main(int argc, char ** argv) { 838 | DialectRegistry registry; 839 | // 注册 Dialect 840 | registry.insert(); 841 | // 注册两个 Pass 842 | registerCSEPass(); 843 | registerCanonicalizerPass(); 844 | return asMainReturnCode(MlirOptMain(argc, argv, "toy-opt", registry)); 845 | } 846 | ``` 847 | 848 | 注意,此时需要连接上 `MLIROptLib` 库:在 `tools/toy-opt/CMakeLists.txt` 里 849 | 850 | ```cmake 851 | add_mlir_tool(toy-opt toy-opt.cpp) 852 | target_link_libraries(toy-opt 853 | PRIVATE 854 | MLIRIR MLIRParser MLIRSupport 855 | Toy # 对应 #include "toy/ToyDialect.h" 856 | MLIROptLib # 对应 #include "mlir/Tools/mlir-opt/MlirOptMain.h" 857 | MLIRFuncDialect # 对应 #include "mlir/Dialect/Func/IR/FuncOps.h" 858 | MLIRTransforms # 对应 #include "mlir/Transforms/Passes.h" 859 | ) 860 | ``` 861 | 862 | 11. 简单使用 `ninja toy-opt` 863 | * `./toy-opt --help` 可以打文档,里面应该有 cse 和 canonicalize 两个 pass 864 | * `./toy-opt ../ex3-dialect/ex3.mlir` 读文件 865 | * `./toy-opt -canonicalize ../ex3-dialect/ex3-cse.mlir`,可以做 dce 866 | * `./toy-opt -cse ../ex3-dialect/ex3-cse.mlir`,可以做 cse 867 | 868 | 为什么 mlir 知道我们的 Op 可以被 CSE 和 DCE 呢,因为我们给 Op 标记了 `Pure` Trait,这表示这个 Op 是纯函数。`Pure` Trait 会自动帮我们注册对应 Op 的 CSE 和 DCE 模式。 869 | 870 | ## 7. TableGen Op 定义详解 871 | 872 | 上一节介绍了 MLIR 工程的骨架,现在我们为其添砖加瓦,让 IR 的定义、输入、输出更简单。 873 | 874 | ### 7.1. Attribute、Type、Constraint 875 | 876 | 添加 Attribute 的方法与 Operand 类似,都写在 arguments 里面。 877 | 878 | ```tablegen 879 | def ConstantOp : ToyOp<"const", [Pure]> { 880 | let summary = "const operation"; 881 | let arguments = (ins APIntAttr:$value); 882 | let results = (outs AnyInteger:$result); 883 | } 884 | ``` 885 | 886 | #### 7.1.1. 内置 Attribute 887 | 888 | 见 `mlir/IR/CommonAttrConstraints.td`,常用的: 889 | 890 | * `TypeAttrOf`:将一个 Type 作为 Attr 891 | * `FlatSymbolRefAttr`:call 函数的时候,函数名的 Attr 892 | * `SymbolNameAttr`: 定义函数的时候,函数名的 Attr 893 | * `UnitAttr`:表示一个bool,为true的时候,它在 attr 表里面,为 false 的时候不在 894 | * `I64SmallVectorArrayAttr`:整数数组 Attr,与其他的整数数组区别的是,它用 SmallVector,会好用一些 895 | 896 | #### 7.1.2. 内置的 Type 897 | 898 | 见 `mlir/IR/CommonTypeConstraint.td`,常用的: 899 | 900 | * `I1`, `I8`, `I16`, `I32`, `I64`, `I128` 901 | * `AnyType`:表示任何类型 902 | * `AnyInteger`:表示任何整数 903 | 904 | #### 7.1.3. 为什么 Attribute 和 Type 都是 Constraint 905 | 906 | 为 Op 定义一个 Attribute 的时候,实际上是指定了 [Operation](#41-op-的类型转换) 里面 operands, results, attributes 等等 的解释方式。 907 | 908 | 像 Attribute、Type 这样的 表示了 “第 i 个位置的 operand 只能被解释为整数”、“第 j 个位置的 attr 只能被解释为Symbol” 的约定,算是限制了各个 field 的解释方式,被看作是 “Constraint”。 909 | 910 | ### 7.2. Verifier:发现IR错误 911 | 912 | 在 tablegen 里面加上 `hasVerifier=true` 913 | 914 | ```mlir 915 | def SubOp : ToyOp<"sub", [Pure]> { 916 | let summary = "sub operation"; 917 | let arguments = (ins AnyInteger:$lhs, AnyInteger:$rhs); 918 | let results = (outs AnyInteger:$result); 919 | let hasVerifier = true; 920 | } 921 | ``` 922 | 923 | 然后在 `toy.cpp` 里写 verifier 的实现: 924 | 925 | ```cpp 926 | using namespace mlir; 927 | LogicalResult SubOp::verify() { 928 | if (getLhs().getType() != getRhs().getType()) 929 | return this->emitError() << "Lhs Type " << getLhs().getType() 930 | << " not equal to rhs " << getRhs().getType(); 931 | return success(); 932 | } 933 | ``` 934 | 935 | #### 7.2.1. emitError 936 | 937 | emitError 是 Op 带有的函数。MLIR里面 Op 都会带 `emitError` 函数,用来区分是哪个Op发生了错误。在这里,我们 verify 的是自己,就只需要调用自己的 `emitError` 函数。 938 | 939 | * 还有 `emitWarning`,可以输出 Warning。 940 | 941 | #### 7.2.2. LogicalResult 942 | 943 | MLIR 用 LogicalResult 用来表示类似 bool 的值,它的特点是: 944 | 945 | * mlir的一些其他类型可以自动转换为 LogicalResult,如上面 emitError 就可以自动转换 946 | * 用 success(), failure() 生成 true 和 false 947 | * 用 succeed(x), failed(x) 来判读是否为 true, false 948 | 949 | ### 7.3. Variadic:可变参数 950 | 951 | 使用 `Variadic` 来描述可变参数: 952 | 953 | ```tablegen 954 | def AddOp : ToyOp<"add", [Pure]> { 955 | let summary = "add operation"; 956 | let arguments = (ins Variadic:$inputs); 957 | let results = (outs AnyInteger:$result); 958 | } 959 | ``` 960 | 961 | 使用 `Option` 来描述可选参数: 962 | 963 | ```tablegen 964 | def ReturnOp : ToyOp<"return", [Terminator, ReturnLike]> { 965 | let summary = "return operation" 966 | let arguments = (ins Optional:$data); 967 | } 968 | ``` 969 | 970 | #### 7.3.1. 多个可变参数:AttrSizedOperandSegments 971 | 972 | 当一个函数只有一个 `Variadic` 或 `Optional` 的时候,可以根据参数总数量推断有多少个可变参数。但如果有多个 `Variadic` 或 `Optional`,需要增加 `AttrSizedOperandSegments` Trait,这个 trait 会为 Op 添加一个 attribute 用来记录每个可变参数是否存在,如果存在有多少个。 973 | 974 | 与之相关的还有 `AttrSizedResultSegments` 用于返回了多个可变参数的情况,它们都在 `OpBase.td` 里面。 975 | 976 | ### 7.4. AssemblyFormat:更易读的输出 977 | 978 | 例子: 979 | 980 | ```tablegen 981 | def AddOp : ToyOp<"add", [Pure]> { 982 | let summary = "add operation"; 983 | let arguments = (ins Variadic:$inputs); 984 | let results = (outs AnyInteger:$result); 985 | let assemblyFormat = "$inputs attr-dict `:` type($inputs) `->` type($result)"; 986 | } 987 | ``` 988 | 989 | 这样会生成下面的更可读的代码: 990 | 991 | ```mlir 992 | %0 = toy.add %a, %b : i32, i32 -> i32 993 | ``` 994 | 995 | #### 7.4.1. 常用关键字 996 | 997 | * `$xxx` 用来表示 operand 或者 attribute 998 | * `type($xxx)` 用来表示 xxx 的类型。 999 | * ``` `keyword` ```: 插入 keyword 1000 | * `functional-type($inputs, results)`,生成形如 `(i32, i32) -> i32` 的函数类型 1001 | * `attr-dict`:表示额外的 attr 字典。 1002 | 1003 | #### 7.4.2. 额外 attr 字典 1004 | 1005 | mlir 允许为 OP 插入任意的 attribute,允许跨 dialect 插入 attribute。所以,在定义 op 的时候,总是要把 `attr-dict` 加上,这样其他人插入的 attr 也能存下来。 1006 | 1007 | #### 7.4.3. 输出 type 1008 | 1009 | 所有没有限制死(AnyXXX,如 AnyInteger)的 operand,都需要写清楚 type,要么用 `type($xxx)`,要么用 `functional-type`。 1010 | 1011 | #### 7.4.4. 可选输出:Optional、UnitAttr 1012 | 1013 | 针对 Optional 和 UnitAttr,MLIR 提供了一种 条件分组 的语法:如下面的 HWReg 1014 | 1015 | ```tablegen 1016 | def HWRegOp : ToyOp<"reg"> { 1017 | let summary = "hardware register"; 1018 | let arguments = (ins I1:$clock, AnyInteger:$input, Optional:$reset, UnitAttr:$is_public); 1019 | let results = (outs AnyInteger:$result); 1020 | let assemblyFormat = [{ 1021 | (`public` $is_public^)? $input 1022 | `clock` $clock 1023 | (`reset` $reset^)? 1024 | attr-dict `:` functional-type($input, results) 1025 | }]; 1026 | } 1027 | ``` 1028 | 1029 | 可以有下面的效果: 1030 | 1031 | ```mlir 1032 | %a = toy.reg public %b clock %clk reset %reset : (i32) -> i32 1033 | %a = toy.reg %b clock %clk reset %reset : (i32) -> i32 1034 | %a = toy.reg %b clock %clk : (i32) -> i32 1035 | ``` 1036 | 1037 | * `[{xxx}]`,MLIR中的长文本可以用 `[{}]` 括起来。 1038 | * ``(`reset` $reset^)?``,其中 `(...)?` 表示分组,`^` 表示判断依据。只有对应的 `Optional` 或 `UnitAttr` 存在的时候,才会输出这个分组。 1039 | 1040 | ### 7.5. Builder:自定义 create 函数 1041 | 1042 | Builder 会在 `builder.create()` 的时候被调用,一个更简单的 builder 可以让创建 Op 更快捷。 1043 | 1044 | #### 7.5.1. 默认Builder 1045 | 1046 | MLIR 会默认生成一些builder。默认 builder 会要求先传入 result 的类型,再传入 operand,attribute 的值。 1047 | 1048 | ```cpp 1049 | build( 1050 | $_builder, $_state, 1051 | mlir::Type Res1Type, mlir::Type Res2Type, ..., 1052 | mlir::Value arg1, mlir::Value arg2, ... 1053 | ) 1054 | ``` 1055 | 1056 | 有时候,一些常用的 Attr,如 `StringAttr`,MLIR 会自动生成以 `StringRef` 为参数的 builder,用来方便调用。用户就不需要使用 `builder.getStringAttr(xxx)` 先把 `StringRef` 转换为 `StringAttr` 再来传参数了。 1057 | 1058 | [之前](#711-内置-attribute) 提到的 `I64SmallVectorArrayAttr` 就可以直接传一个 `SmallVector`,而不需要传一个 Attr 进去,会非常方便。 1059 | 1060 | #### 7.5.2. 自定义builder 1061 | 1062 | 例如,我们可以在创建 Op 的时候,推断出结果的类型: 1063 | 1064 | ```mlir 1065 | def SubOp : ToyOp<"sub", [Pure]> { 1066 | let summary = "sub operation"; 1067 | let arguments = (ins AnyInteger:$lhs, AnyInteger:$rhs); 1068 | let results = (outs AnyInteger:$result); 1069 | let builders = [ 1070 | OpBuilder< 1071 | (ins "mlir::Value":$lhs, "mlir::Value":$rhs), 1072 | "build($_builder, $_state, lhs.getType(), lhs, rhs);" 1073 | > 1074 | ]; 1075 | let hasVerifier = true; 1076 | } 1077 | ``` 1078 | 1079 | * 首先,mlir 会自动为我们生成 `build($_builder, $_state, ResultType, LhsValue, RhsValue)` 的 builder 1080 | * 我们的 builder 通过 `lhs.getType()` 推断 result 的类型,并调用 mlir 生成好的 builder,实现自动推断类型 1081 | 1082 | 如果只是为了推断类型,建议使用 MLIR 为类型推断专门实现的 Trait: InferTypeOpInterface [后面有介绍](#772-类型推断infertypeopinterface)。 1083 | 1084 | ### 7.6. 自定义函数 1085 | 1086 | tablegen 允许用户为 Op 添加自定义函数,例如,我想直接获取 ConstantOp 的类型的位宽: 1087 | 1088 | ```tablegen 1089 | def ConstantOp : ToyOp<...> { 1090 | let extraClassDeclaration = [{ 1091 | int64_t getBitWidth() { 1092 | return getResult().getType().getWidth(); 1093 | } 1094 | }]; 1095 | } 1096 | ``` 1097 | 1098 | 这样,之后想要获取位宽的时候,就可以更简洁了: 1099 | 1100 | ```cpp 1101 | auto w = op.getResult().getType().getWidth(); 1102 | auto w = op.getBitWidth(); 1103 | ``` 1104 | 1105 | 由于 tablegen 里面没有 cpp 的语法补全,可以只在 tablegen 里写一个方法定义,然后在 `toy.cpp` 里面写实现(使用 `ninja MLIRToyIncGen` 生成头文件) 1106 | 1107 | ```tablegen 1108 | def ConstantOp : ToyOp<...> { 1109 | let extraClassDeclaration = [{ 1110 | int64_t getBitWidth(); 1111 | }]; 1112 | } 1113 | ``` 1114 | 1115 | #### 7.6.1. header target 1116 | 1117 | 一个 trick 是在 `CMakeLists.txt` 里面添加一个 target,这样每次改了 tablegen 文件,只需要 `ninja header` 就能生成头文件。 1118 | 1119 | ```cmake 1120 | add_custom_target(header DEPENDS MLIRToyIncGen) 1121 | ``` 1122 | 1123 | ### 7.7. 使用 Trait 1124 | 1125 | [前面](#634-程序入口) 介绍到,在给 Op 标记上 `Pure` 之后,就会自动被 cse, dce Pass 理解。除了 Pure Trait 之外,MLIR 为我们提供了很多好用的 Trait,这里介绍常用的 SideEffect,InferType 和 比较复杂的和函数相关的 Trait。 1126 | 1127 | 使用 Trait 的时候要注意: 1128 | 1129 | 1. Interface 可能会要求用户实现一些固定的接口,trait 里一些 `InterfaceMethod` 是没有默认实现的。 1130 | 2. 在 td 里要 include trait 的 td 文件,在 h 里也要 include 对应的 h 文件 1131 | 1132 | #### 7.7.1. 内存副作用:SideEffectInterfaces 1133 | 1134 | `mlir/Interfaces/SideEffectInterfaces.{td,h}` 文件里定义了内存副作用的 interface 1135 | 1136 | * **Pure**:纯函数,使用后可以自动 cse,dce 1137 | * `MemRead`, `MemWrite`, `MemAlloc`, `MemFree`:内存作用 1138 | 1139 | #### 7.7.2. 类型推断:InferTypeOpInterface 1140 | 1141 | `mlir/Interfaces/InferTypeOpInterface.{td,h}` 文件里定义了类型推断的 Interface,使用类型推断,你可以: 1142 | 1143 | * 在 `assemblyFormat` 里可以少写几个 type,写出来的格式更漂亮 1144 | * 自动生成带类型推断的 builder,只需要传参数就能推断返回值类型 1145 | 1146 | 类型推断主要有下面几个常用的 Trait: 1147 | 1148 | * **SameOperandsAndResultType**:操作数和返回值有相同的类型,使用后 assemblyFormat 里就只需要写任何某一个操作数的类型 1149 | * **InferTypeOpInterface**:通过输入和 attr 的类型推断返回值类型,自己写推断函数 1150 | * **InferTypeOpAdaptor**:与上一个相似,但封装了一个 Adaptor,写起来会更简单 1151 | 1152 | 推荐使用 **InferTypeOpAdaptor**: 1153 | * 在 tablegen 里面 1154 | ```tablegen 1155 | def ConstantOp : ToyOp<"const", [Pure, InferTypeOpAdaptor]> { 1156 | let summary = "const operation"; 1157 | let arguments = (ins APIntAttr:$value); 1158 | let results = (outs AnyInteger:$result); 1159 | let assemblyFormat = "$value attr-dict"; // 这里不需要写 type($result) 了 1160 | } 1161 | ``` 1162 | * 在 `toy.cpp` 里面 1163 | ```cpp 1164 | mlir::LogicalResult ConstantOp::inferReturnTypes( 1165 | mlir::MLIRContext * context, 1166 | std::optional location, 1167 | Adaptor adaptor, 1168 | llvm::SmallVectorImpl & inferedReturnType 1169 | ) { 1170 | // adaptor 是 “incomplete op”,表示只知道输入,不知道返回值的 Op 1171 | auto type = adaptor.getValueAttr().getType(); 1172 | inferedReturnType.push_back(type); 1173 | return mlir::success(); 1174 | } 1175 | ``` 1176 | 1177 | ### 7.8. 函数:FunctionOpTrait 1178 | 1179 | 这里着重介绍 func、call、return,参考 `ex4-beautiful-dialect`。函数的这一套代码非常固定,每次照搬就好,没有太多的解释。按照下面的说明设置好函数Op后,应该就可以用 `./ex4-opt ../ex4-beautiful-dialect/ex4.mlir` 来读取函数了。 1180 | 1181 | #### 7.8.1. 定义 Return 1182 | 1183 | Return 是一个终止符,需要使用 `Terminator`。同时,我们为其加上 `ReturnLike`。 1184 | 1185 | ```tablegen 1186 | def ReturnOp : ToyOp<"ret", [Terminator, ReturnLike]> { 1187 | let summary = "return operation"; 1188 | let arguments = (ins AnyType:$data); 1189 | let assemblyFormat = "$data attr-dict `:` type($data)"; 1190 | } 1191 | ``` 1192 | 1193 | #### 7.8.2. 定义 Function 1194 | 1195 | 定义函数需要实现 `FunctionOpInterface`,它依赖于 `Symbol` 和 `CallableOpInterface`。 1196 | 同时,因为我们定义了 Region,最好还加上 `RegionKindInterface`,它会自动为我们检查 Region 的格式是否正确。 1197 | 1198 | 我们的 Function 是一个全局的函数,需要加上 `IsolatedFromAbove` Trait,这表示它不会使用它所在的 Region 之前的任何值。与之相对的是 `for`,就不是 `IsolatedFromAbove`,因为它要使用上下文的值。 1199 | 1200 | 这里使用 `AnyRegion`,MLIR 还为我们提供了一些其他的 Region 选项,如 `SizedRegion<1>` 表示只有一个基本块的 `Regin`。 1201 | 1202 | 为了能正确地打印函数,我们需要调用 MLIR 提供给我们的 parser 和 printer,这里选 `hasCustomAssemblyFormat=true`。 1203 | 1204 | 随后,要实现好每个 Interface 需要的函数,如 `extraClassDeclaration` 里面的一样。 1205 | 1206 | ```tablegen 1207 | def FuncOp : ToyOp<"func", [ 1208 | IsolatedFromAbove, 1209 | FunctionOpInterface, 1210 | /* Symbol, */ /* Symbol 会自动被 FunctionOpInterface 加上 */ 1211 | /* CallableOpInterface, */ /* CallOpInterface 会自动被 FunctionOpInterface 加上 */ 1212 | RegionKindInterface]> { 1213 | let summary = "function"; 1214 | let arguments = (ins 1215 | SymbolNameAttr:$sym_name, 1216 | TypeAttrOf:$function_type, 1217 | // FunctionOpInterface 需要两个 Attr 来记录 arg 和 res 的名字 1218 | OptionalAttr:$arg_attrs, 1219 | OptionalAttr:$res_attrs 1220 | ); 1221 | dag regions = (region AnyRegion:$body); 1222 | let hasCustomAssemblyFormat = true; 1223 | let extraClassDeclaration = [{ 1224 | // Method of FunctionOpInterface 1225 | mlir::Region * getCallableRegion() {return &getBody();} 1226 | // getFunctionType 函数会自动生成 1227 | // mlir::FunctionType getFunctionType(); 1228 | 1229 | // Method of CallableOpInterface 1230 | llvm::ArrayRef getArgumentTypes() {return getFunctionType().getInputs();} 1231 | llvm::ArrayRef getResultTypes() {return getFunctionType().getResults();} 1232 | 1233 | // Method of RegionKindInterface 1234 | static mlir::RegionKind getRegionKind(unsigned idx) { return mlir::RegionKind::SSACFG; } 1235 | }]; 1236 | } 1237 | ``` 1238 | 1239 | 然后在 `toy.cpp`,使用 MLIR 自带的 function interface 来 parse 和 print。 1240 | 1241 | ```cpp 1242 | #include "mlir/Interfaces/FunctionImplementation.h" 1243 | using namespace mlir; 1244 | 1245 | ParseResult FuncOp::parse(OpAsmParser &parser, OperationState &result) { 1246 | auto buildFuncType = [](auto & builder, auto argTypes, auto results, auto, auto) { 1247 | return builder.getFunctionType(argTypes, results); 1248 | }; 1249 | return function_interface_impl::parseFunctionOp( 1250 | parser, result, false, 1251 | getFunctionTypeAttrName(result.name), buildFuncType, 1252 | getArgAttrsAttrName(result.name), getResAttrsAttrName(result.name) 1253 | ); 1254 | } 1255 | 1256 | void FuncOp::print(OpAsmPrinter &p) { 1257 | function_interface_impl::printFunctionOp( 1258 | p, *this, false, getFunctionTypeAttrName(), 1259 | getArgAttrsAttrName(), getResAttrsAttrName()); 1260 | } 1261 | ``` 1262 | 1263 | #### 7.8.3. 定义 Call 1264 | 1265 | 使用 CallOpInterface 就行了,需要写一下 Interface 函数。 1266 | 1267 | ```tablegen 1268 | def CallOp : ToyOp<"call", [CallOpInterface]> { 1269 | let summary = "call operation"; 1270 | let arguments = (ins SymbolRefAttr:$callee, Variadic:$arg_operands); 1271 | let results = (outs AnyType:$result); 1272 | let assemblyFormat = "$callee `(` $arg_operands `)` attr-dict `:` functional-type($arg_operands, results)"; 1273 | let extraClassDeclaration = [{ 1274 | mlir::CallInterfaceCallable getCallableForCallee() { 1275 | return getCalleeAttr(); 1276 | } 1277 | void setCalleeFromCallable(mlir::CallInterfaceCallable callee) { 1278 | setCalleeAttr(callee.get()); 1279 | } 1280 | }]; 1281 | } 1282 | ``` 1283 | 1284 | ## 8. 添加 Pass 1285 | 1286 | 上一节讲述了 IR 的定义、输入、输出、成员函数等等。但一个编译器只有 IR 还不够,需要有在 IR 上运行的 Pass。我们这一节介绍如何使用方便快捷的 tablegen 来定义 Pass。 1287 | 1288 | ### 8.1. Pass 工程模板 1289 | 1290 | 1. `include/ToyPasses.td`:描述 Pass 文件 1291 | 1292 | ```tablegen 1293 | include "mlir/Pass/PassBase.td" 1294 | 1295 | def ConvertToyToArith : Pass<"convert-toy-to-arith"> { 1296 | let summary = "Convert Toy To Arith"; 1297 | let constructor = "toy::createConvertToyToArithPass()"; 1298 | } 1299 | ``` 1300 | 1301 | 2. `include/CMakeLists.txt`:添加 tablegen 1302 | 1303 | ```cmake 1304 | set(LLVM_TARGET_DEFINITIONS ToyPasses.td) 1305 | mlir_tablegen(ToyPasses.h.inc -gen-pass-decls) 1306 | add_public_tablegen_target(MLIRToyTransformsIncGen) 1307 | ``` 1308 | 1309 | 3. `include/ToyPasses.h`:Pass 的头文件 1310 | ```cpp 1311 | namespace toy { 1312 | // 先生成定义 1313 | #define GEN_PASS_DECL 1314 | #include "toy/ToyPasses.h.inc" 1315 | 1316 | // 在写 create 函数表 1317 | std::unique_ptr createConvertToyToArithPass(); 1318 | 1319 | // 生成注册函数 1320 | #define GEN_PASS_REGISTRATION 1321 | #include "toy/ToyPasses.h.inc" 1322 | } 1323 | ``` 1324 | 1325 | 4. `lib/Transforms/CMakeLists.txt`:添加 library 1326 | ```cmake 1327 | add_mlir_library( 1328 | ToyTransforms 1329 | ConvertToyToArith.cpp 1330 | DEPENDS MLIRToyTransformsIncGen 1331 | ) 1332 | ``` 1333 | 1334 | 5. `lib/Transforms/ConvertToyToArith.cpp`:一个基本实现 1335 | ```cpp 1336 | #define GEN_PASS_DEF_CONVERTTOYTOARITH 1337 | #include "toy/ToyPasses.h" 1338 | #include "llvm/Support/raw_ostream.h" 1339 | 1340 | struct ConvertToyToArithPass : 1341 | toy::impl::ConvertToyToArithBase 1342 | { 1343 | // 使用父类的构造函数 1344 | using toy::impl::ConvertToyToArithBase::ConvertToyToArithBase; 1345 | void runOnOperation() final { 1346 | getOperation()->print(llvm::errs()); 1347 | } 1348 | }; 1349 | 1350 | std::unique_ptr toy::createConvertToyToArithPass() { 1351 | return std::make_unique(); 1352 | } 1353 | ``` 1354 | 1355 | 6. 注册 Pass:`tools/toy-opt/toy-opt.cpp` 1356 | 1357 | ```cpp 1358 | toy::registerPasses(); 1359 | return asMainReturnCode(MlirOptMain(argc, argv, "toy-opt", registry)); 1360 | ``` 1361 | 1362 | 7. 测试:`./ex5-opt -convert-toy-to-arith ../ex5-pass/ex5.mlir` 1363 | 1364 | ### 8.2. Pass 定义详解 1365 | 1366 | #### 8.2.1. 指定 Pass 在哪个 Op 上运行 1367 | 1368 | 参考 [Pass Infrastructure](https://mlir.llvm.org/docs/PassManagement/),MLIR 的 Pass 有下面几种: 1369 | 1370 | * OperationPass:在某个固定类型的 Op 上运行的 Pass 1371 | * InterfacePass:在特定的 OpInterface 上运行的 Pass 1372 | 1373 | 它们两个的区别是,Pass 里面 `getOperation()` 返回的是 Operation 还是 Interface。 1374 | 1375 | 默认情况下,`Pass<"convert-toy-to-arith">` 定义了一个在任意 `Operation*` 上都能运行的 Pass,如果要定义在指定 Operation 上运行的 Pass,可以使用下面的定义。注意,此时使用了`toy::FuncOp`,在 `ToyPasses.h` 文件里需要 include 对应的头文件,防止找不到名字。 1376 | 1377 | ```tablegen 1378 | def ConvertToyToArith : Pass<"convert-toy-to-arith", "toy::FuncOp"> { 1379 | let summary = "Convert Toy To Arith"; 1380 | let constructor = "toy::createConvertToyToArithPass()"; 1381 | } 1382 | ``` 1383 | 1384 | #### 8.2.2. 带参数的 Pass 1385 | 1386 | 首先需要在 tablgen 文件里写上参数的定义: 1387 | 1388 | ```tablegen 1389 | def IsolateTopModule : Pass<"convert-toy-to-arith"> { 1390 | let summary = "Convert Toy To Arith"; 1391 | let constructor = "toy::createConvertToyToArithPass()"; 1392 | let options = [ 1393 | // 代码里的名字 命令行的名字 类型 默认值 帮助 1394 | Option<"name", "name", "std::string", "", "help"> 1395 | ]; 1396 | } 1397 | ``` 1398 | 1399 | 然后,在 `ToyPasses.h` 文件里,要修改 create 函数的定义: 1400 | 1401 | ```cpp 1402 | std::unique_ptr createConvertToyToArithPass( 1403 | ConvertToyToArithOptions options={} 1404 | ); 1405 | ``` 1406 | 1407 | 在实现 create 函数的时候,也要带上参数: 1408 | ```cpp 1409 | struct ConvertToyToArithPass : 1410 | toy::impl::ConvertToyToArithBase 1411 | { 1412 | // 使用父类的构造函数 1413 | using toy::impl::ConvertToyToArithBase::ConvertToyToArithBase; 1414 | void runOnOperation() final { 1415 | llvm::errs() << "get name: " << name << "\n"; 1416 | } 1417 | }; 1418 | 1419 | std::unique_ptr toy::createConvertToyToArithPass( 1420 | ConvertToyToArithOptions options) { 1421 | return std::make_unique(options); 1422 | } 1423 | ``` 1424 | 1425 | 配置参数的方法:`ex5-opt -convert-toy-to-arith="name=xxx" ../ex5-pass/ex5.mlir` 1426 | 1427 | ### 8.3. 简单的 DCE Pass 实现 1428 | 1429 | Pass 的实现,就是灵活使用 IR 的遍历与修改。我们弄一个简单 DCE Pass 作为 Example 1430 | 1431 | #### 8.3.1. 定义 1432 | 1433 | ```tablegen 1434 | def DCE : Pass<"toy-dce", "toy::FuncOp"> { 1435 | let summary = "dce"; 1436 | let constructor = "toy::createDCEPass()"; 1437 | } 1438 | ``` 1439 | 1440 | #### 8.3.2. 实现 1441 | 1442 | ```cpp 1443 | struct DCEPass : toy::impl::DCEBase { 1444 | void visitAll(llvm::DenseSet &visited, Operation * op) { 1445 | if(visited.contains(op)) return; 1446 | visited.insert(op); 1447 | for(auto operand: op->getOperands()) 1448 | if(auto def = operand.getDefiningOp()) 1449 | visitAll(visited, def); 1450 | } 1451 | void runOnOperation() final { 1452 | llvm::DenseSet visited; 1453 | // 遍历所有 Return,把 Return 可达的加入 visited 集合 1454 | getOperation()->walk([&](toy::ReturnOp op) { 1455 | visitAll(visited, op); 1456 | }); 1457 | llvm::SmallVector opToRemove; 1458 | // 将不可达的加入 opToRemove 集合 1459 | getOperation().walk([&](Operation * op) { 1460 | if(op == getOperation()) return; 1461 | if(!visited.contains(op)) opToRemove.push_back(op); 1462 | }); 1463 | // 反向 erase 1464 | for(auto v: reverse(opToRemove)) { 1465 | v->erase(); 1466 | } 1467 | } 1468 | }; 1469 | ``` 1470 | 1471 | ## 9. Pattern Rewrite 1472 | 1473 | pattern rewrite 是 MLIR 的一大特色。Pattern 会匹配 IR 的一个子图,然后将其更改为新的格式。MLIR 会为我们自动调度 pattern,让 IR 的变换更加简单。 1474 | 1475 | 很多 IR 的操作都可以看作是 Pattern Rewrite: 1476 | * 算术优化,如 x*2 优化为 x+x ,可以看作是对表达式做模式替换 1477 | * 表达式 Lowering,可以看作是把 HighLevel Op 替换为 LowLevel Op 1478 | 1479 | 在这一节,我们使用 Pattern Rewrite 来把 toy 里的 Op 转换为 Arith 里的 Op。 1480 | 1481 | ### 9.1. Pattern Rewrite 1482 | 1483 | #### 9.1.1. 描述 Pattern 1484 | 1485 | `matchAndRewrite` 返回 success 表示能够 match,返回 failure 表示不能 match。如果能 match,就通过 rewriter 改写。rewriter 实现了一套完整的改写 API。 1486 | 1487 | ```c++ 1488 | struct AddOpPat: OpRewritePattern { 1489 | using OpRewritePattern::OpRewritePattern; 1490 | LogicalResult matchAndRewrite(AddOp op, PatternRewriter & rewriter) const { 1491 | auto inputs = to_vector(op.getInputs()); 1492 | auto result = inputs[0]; 1493 | for(size_t i = 1; i< inputs.size(); i++) { 1494 | result = rewriter.create(op->getLoc(), result, inputs[i]); 1495 | } 1496 | rewriter.replaceOp(op, ValueRange(result)); 1497 | return success(); 1498 | } 1499 | }; 1500 | ``` 1501 | 1502 | #### 9.1.2. 调用 Pattern 1503 | 1504 | 在使用 conversion 的时候,首先要定义 `ConversionTarget`,然后要配置好 `PatternSet`,最后调用 `applyXXX` 驱动函数: 1505 | 1506 | ```c++ 1507 | ConversionTarget target(getContext()); 1508 | target.addLegalDialect(); 1509 | RewritePatternSet patterns(&getContext()); 1510 | patterns.add(&getContext()); 1511 | if(failed(applyPartialConversion(getOperation(), target, std::move(patterns)))) 1512 | signalPassFailure(); 1513 | ``` 1514 | 1515 | 这里我们使用了 `partialConversion`,MLIR 支持三种 Conversion 模式: 1516 | * `partialConversion`:**如果 Pattern 转换结果是 Legal,则保留转换结果**。如果输入存在 IllegalOp 或 IllegalDialect,立刻报错。 1517 | * `fullConversion`:开始时可能是Illegal的。**调用 Pattern 将其转换,直到全部 Legal 为止**。 1518 | * `greedyPatternRewrite`:**不需要提供 Target,贪心地尝试尽量多次修改**。 1519 | 1520 | 前两个常用于 Dialect Lowering 之中。而`geedyPatternRewrie` 很适合用来写优化,比如我可以写一个把形如 `toy.sub %a, %a` 替换为 `const 0: i32` 的 pattern,希望 MLIR 尽量多优化它。 1521 | 1522 | #### 9.1.3. Depedent Dialect & Linking 1523 | 1524 | 注意,我们将 toy dialect 转换为了 arith dialect,这说明我们的 pass 依赖 arith ,要添加依赖: 1525 | 1526 | ```c++ 1527 | void getDependentDialects(DialectRegistry ®istry) const final { 1528 | registry.insert(); 1529 | } 1530 | ``` 1531 | 1532 | 同时,在 `opt` 程序里还要注册 arith, 1533 | 1534 | ```c++ 1535 | registry.insert(); 1536 | ``` 1537 | 1538 | 以及连接上 arith,这是我们的 Transform 依赖 arith,所以 arith 应该加载 transform 的连接列表中。 1539 | 1540 | ```cmake 1541 | add_mlir_library( 1542 | ToyTransforms 1543 | ConvertToyToArith.cpp 1544 | DCE.cpp 1545 | DEPENDS MLIRToyTransformsIncGen 1546 | LINK_LIBS MLIRArithDialect # here 1547 | ) 1548 | ``` 1549 | 1550 | 使用下面的命令来验证结果: 1551 | 1552 | ``` 1553 | ./ex6-opt --convert-toy-to-arith --toy-dce ../ex6-pattern/ex6.mlir 1554 | ``` 1555 | 1556 | 1557 | ##### debug 的方法 1558 | 1559 | 可以用 `--debug` 来启动程序,程序会打印出转换的详细过程。 1560 | 1561 | ``` 1562 | ./ex6-opt --debug --convert-toy-to-arith ../ex6-pattern/ex6.mlir 1563 | ``` 1564 | 1565 | ### 9.2. Dialect Convertion (Type Conversion) 1566 | 1567 | Dialect 除了 Op 之外,还有 Type。在进行 Dialect 之间的转换的时候,对 Type 的改写也很重要。 1568 | 1569 | MLIR 对 Type 做改写的方法是用 `TypeConverter` 完成的, `TypeConverter` 有三个功能: 1570 | 1571 | 1. `addConversion`:添加一个 Type 的转换规则 1572 | 2. `addTargetMaterialization`:生成将 SourceType 转换为 TargetType 的代码块 1573 | 3. `addSourceMaterialization`:生成将 TargetType 转换回 SourceType 的代码块 1574 | 1575 | 这三个里面最重要的是 1,剩下两个一般不需要自己实现。 1576 | 1577 | 为了做示范,我们定义一个自己的 `toy.int` 类型,它可以被转换为 `Integer` 类型。这里略过类型定义的部分,详细请看 [自定义类型](#10-自定义-type)。 1578 | 1579 | #### 9.2.1. TypeConverter 1580 | 1581 | 首先,我们要声明一个 DialectConverter,然后我们要为其添加类型转换规则。下面的代码添加了 ToyIntegerType 到 IntegerType 的转换。MLIR 会使用神奇的模板元编程的方法,获取传入函数的参数和返回值类型,来判断是什么类型到什么类型的转换。 1582 | 1583 | ```c++ 1584 | TypeConverter converter; 1585 | converter.addConversion([&](ToyIntegerType t) -> std::optional { 1586 | return IntegerType::get(&getContext(), t.getWidth()); 1587 | }); 1588 | ``` 1589 | 1590 | #### 9.2.2. Conversion Pattern:自动做 Operand 的类型转换 1591 | 1592 | 我们用 ConversionPattern 来自动做类型转换。ConversionPattern 与 RewritePattern 不同的是,它多了一个 `Adaptor`。`Adaptor` 在前面 [InferTypeOpInterface](#772-类型推断infertypeopinterface) 介绍到,`Adaptor` 是只有 operands 没有 results 的中间态。 1593 | 1594 | MLIR 在调用 ConversionPattern 之前,会先尝试将 op 的 Operand 全部转换为目标格式,如果不能转换就保留原来的。并且将转换之后的 operand 储存在 Adaptor 里面。 1595 | 1596 | 在 replace 的时候,类型也可以和原来的不一样(但必须能转换过去),MLIR 会自动处理类型转换的问题。 1597 | 1598 | ```c++ 1599 | struct AddOpPat: OpConversionPattern { 1600 | using OpConversionPattern::OpConversionPattern; 1601 | LogicalResult matchAndRewrite(AddOp op, AddOpAdaptor adaptor, ConversionPatternRewriter & rewriter) const { 1602 | auto inputs = to_vector(adaptor.getInputs()); 1603 | auto result = inputs[0]; 1604 | for(size_t i = 1; i< inputs.size(); i++) { 1605 | assert(inputs[i]); 1606 | result = rewriter.create(op->getLoc(), result, inputs[i]); 1607 | } 1608 | rewriter.replaceOp(op, ValueRange(result)); 1609 | return success(); 1610 | } 1611 | }; 1612 | ``` 1613 | 1614 | ##### 使用 MLIR 自带的 FuncOpConversion 1615 | 1616 | 在对函数做类型转换的时候,既需要对 Region 的参数表做转换,还需要对函数类型做转换。MLIR为我们提供了默认的 Pattern: 1617 | 1618 | ```c++ 1619 | populateFunctionOpInterfaceTypeConversionPattern(patterns, converter); 1620 | ``` 1621 | 1622 | #### 9.2.3. 类型转换的细节与 Debug 1623 | 1624 | 在使用类型转换时候,可以用 `--debug` 来启动程序,程序会打印出转换的详细过程。 1625 | 1626 | 一般情况下,mlir 做类型转换是通过一个特殊的 Op 来实现的: `builtin.unrealized_conversion_cast` 1627 | 1628 | 例如,mlir 要对下面的代码做类型转换: 1629 | 1630 | ```mlir 1631 | %a = toy.constant 0: !toy.int<32> 1632 | %b = toy.add %a, %a: !toy.int<32> 1633 | ``` 1634 | 1635 | 当然,mlir 一般都是从前往后做替换的,这样更不容易插入 unrealized。可以在 apply 的时候配置 config 改变顺序,为了展示,我们假设 mlir 从%b开始匹配。 1636 | `toy.add`,它的输入是 `%a`,类型为 `!toy.int<32>`,转换为 `i32`,需要做 TargetMaterialization,由于用户没有注册 materialization,就插入一个 unrealized 的转换: 1637 | 1638 | ```mlir 1639 | %a = toy.constant 0: !toy.int<32> 1640 | %a_1 = builtin.unrealized_conversion_cast %a : !toy.int<32> to i32 1641 | %b = arith.add %a_1, %a_1 : i32 1642 | ``` 1643 | 1644 | 接下来,再匹配 `toy.constant`,被替换为了 `arith.constant`,mlir 发现输出类型发生了变化,做 SourceMaterialization,同样插入 unrealized 1645 | 1646 | ```mlir 1647 | %a_2 = arith.constant 0: i32 1648 | %a = builtin.unrealized_conversion_cast %a_2 : i32 to !toy.int<32> 1649 | %a_1 = builtin.unrealized_conversion_cast %a : !toy.int<32> to i32 1650 | %b = arith.add %a_1, %a_1 : i32 1651 | ``` 1652 | 1653 | 最后,mlir 会尝试把所有的 unrealized 转换移除。上面 `i32` 被转换成了 `!toy.int<32>`,又被转换了回去,是无效转换,需要移除,最后变成: 1654 | 1655 | ```mlir 1656 | %a = arith.constant 0: i32 1657 | %b = arith.add %a, %a : i32 1658 | ``` 1659 | 1660 | #### 9.2.4. 使用自己的 materialization 1661 | 1662 | 如果用户注册了自己的 materialization 方法,MLIR 就会使用用户注册的 materilzation。 1663 | 1664 | 一个使用自己 materialization 的场景:我们自己定义了 `float32` 复数类型,转换的时候要把它转换成 `float8`。我们肯定希望尽量少地调用函数做类型转换 `float8`。但如果`float32`是函数参数,别人要调用这个函数不能随便改,就只能强行转换了。 1665 | 1666 | 例如,我们可以直接把 `unrealized_conversion_cast` 注册为默认 materialization,这样在 debug 的时候很方便。 1667 | 1668 | ```c++ 1669 | converter.addTargetMaterialization([](OpBuilder& builder, Type /* 对所有 SourceType 注册 */ resultType, ValueRange inputs, Location loc) -> std::optional { 1670 | return builder.create(loc, resultType, inputs).getResult(0); 1671 | }); 1672 | ``` 1673 | 1674 | 把关于函数的 target 注释掉,可以观察程序的结果: 1675 | 1676 | ```c++ 1677 | // target.addDynamicallyLegalOp([](FuncOp f) { 1678 | // return llvm::all_of(f.getArgumentTypes(), 1679 | // [](Type t) {return !isa(t);}); 1680 | // }); 1681 | ``` 1682 | 1683 | ```c++ 1684 | ./ex7-opt --convert-toy-to-arith ../ex7-convert/ex7.mlir 1685 | ``` 1686 | 1687 | ```mlir 1688 | toy.func @add(%arg0: !toy.int<32>, %arg1: !toy.int<32>) -> !toy.int<32> { 1689 | %0 = builtin.unrealized_conversion_cast %arg0 : !toy.int<32> to i32 1690 | %1 = builtin.unrealized_conversion_cast %arg1 : !toy.int<32> to i32 1691 | %2 = arith.addi %0, %1 : i32 1692 | toy.ret %2 : i32 1693 | } 1694 | ``` 1695 | 1696 | ### 9.3. 使用 MLIR 里已有的 Pattern 做多步转换 1697 | 1698 | MLIR 为我们提供了模块化的 PatternRewrite API。几乎所有的 Conversion 都有对应的 populateXXXPatterns 函数。 1699 | 1700 | 例如,我们想要一次性将 `toy` 转换到 `llvm`,可以先自己写 pattern 把 `toy` 转换到 `arith`,再添加 `arith` 的 pattern 将其转换为 `LLVM`: 1701 | 1702 | ```c++ 1703 | #include "mlir/Conversion/ArithToLLVM/ArithToLLVM.h" 1704 | #include "mlir/Conversion/LLVMCommon/TypeConverter.h" 1705 | #include "mlir/Dialect/LLVMIR/LLVMDialect.h" 1706 | 1707 | ConversionTarget target(getContext()); 1708 | target.addLegalDialect(); 1709 | LLVMTypeConverter converter(&getContext()); 1710 | RewritePatternSet patterns(&getContext()); 1711 | patterns.add(&getContext()); 1712 | arith::populateArithToLLVMConversionPatterns(converter, patterns); // 使用已有 pattern 1713 | if(failed(applyPartialConversion(getOperation(), target, std::move(patterns)))) 1714 | signalPassFailure(); 1715 | ``` 1716 | 1717 | 其他头文件,需要连接的库文件,请看 `ex6` 里的代码。 1718 | 1719 | ## 10. 自定义 Type 1720 | 1721 | 参考 `ex7`,自定义类型的方法: 1722 | 1723 | ```tablegen 1724 | // ToyDialect.td 1725 | def ToyDialect : Dialect { 1726 | let name = "toy"; 1727 | let cppNamespace = "::toy"; 1728 | let summary = "Toy Dialect"; 1729 | let useDefaultTypePrinterParser = true; // 新增 1730 | let extraClassDeclaration = [{ 1731 | void registerTypes(); 1732 | }]; 1733 | } 1734 | 1735 | 1736 | // ToyTypes.td 1737 | class ToyType traits=[]>: TypeDef; 1738 | def ToyInteger: ToyType<"ToyInteger"> { 1739 | let mnemonic = "int"; 1740 | let parameters = (ins "uint64_t":$width); 1741 | let assemblyFormat = "`<` $width `>`"; 1742 | } 1743 | ``` 1744 | 1745 | ```c++ 1746 | #include "toy/ToyTypes.h" 1747 | #include "mlir/IR/DialectImplementation.h" 1748 | #include "llvm/ADT/StringExtras.h" 1749 | #include "llvm/ADT/StringSwitch.h" 1750 | #include "llvm/ADT/TypeSwitch.h" 1751 | #define GET_TYPEDEF_CLASSES 1752 | #include "toy/ToyTypes.cpp.inc" 1753 | 1754 | void ToyDialect::initialize() { 1755 | addOperations< 1756 | #define GET_OP_LIST 1757 | #include "toy/Toy.cpp.inc" 1758 | >(); 1759 | registerTypes(); // 增加 1760 | } 1761 | 1762 | void ToyDialect::registerTypes() { 1763 | addTypes< 1764 | #define GET_TYPEDEF_LIST 1765 | #include "toy/ToyTypes.cpp.inc" 1766 | >(); 1767 | } 1768 | ``` 1769 | 1770 | ## 11. TIPS 1771 | 1772 | ### 11.1. 如何找头文件、找想要的函数 1773 | 1774 | 首先,对于常用的头文件,可以都过目一下函数列表,包括: 1775 | 1776 | * `llvm/ADT/*` 里面的数据结构 1777 | * `mlir/IR/CommonAttrConstraints.td` 1778 | * `mlir/IR/CommonTypeConstraints.td` 1779 | 1780 | MLIR 的 Dialect 文件结构都比较整齐,`mlir/Dialect/XXX/IR/XXX.h` 1781 | 1782 | 其他的函数/头文件,建议开个 vscode 到 mlir 源码目录,使用全局搜索来找。 1783 | 1784 | ### 11.2. 如何找需要连接的库 1785 | 1786 | 首先,找到你 include 的头文件,如 `mlir/Dialect/Func/IR/FuncOps.h`。 1787 | 1788 | 然后,找到这个头文件对应的 cpp 文件,`lib/Dialect/Func/IR/FuncOps.cpp`。 1789 | 1790 | 从 cpp 文件逐步往上找 `CMakeLists.txt`,检查里面的 `add_mlir_dialect_library` 里的库文件名。 1791 | 1792 | ### 11.3. 如何加快编译速度 1793 | 1794 | MLIR 经常会连接出上百 M 甚至上 G 的文件,不同的链接器对性能有很大影响,使用 `lld` (llvm 链接器) 似乎会比 `ld` 快非常多,下面的命令可以让 CMAKE 强制使用 lld(你需要先安装 llvm 编译工具包)。 1795 | 1796 | ```bash 1797 | cmake .. -DCMAKE_CXX_FLAGS="-fuse-ld=lld" 1798 | ``` 1799 | 1800 | ### 11.4. 去 MLIR 里抄代码 1801 | 1802 | MLIR 为我们写好了大量的 Dialect,我们想要的功能,那些 dialect 多半都已经实现过了。 1803 | 1804 | 可以用 `mlir-opt --help`,`mlir-opt --help-hidden` 看看有那些 dialect 哪些选项,找到可能是和自己想要做的相似的,然后过去看代码,边看边抄大概就能实现好了。 1805 | 1806 | ## 12. MLIR 的批判:C++ v.s. Rust 1807 | 1808 | > 这一段都是我的个人想法,可能会比较偏激。 1809 | 1810 | MLIR 只能用 g++ 编译,用 clang++ 编译会 Runtime Error。这充分地说明了一个事实:MLIR 这座庞大的大厦,是建立在脆弱的 ub (undefined behavior) 之上的。 1811 | 1812 | MLIR Context,在 rust 眼里,实际上就是一个 Interner,把类型转换为指针,来去重和加速类型比较。但而为了实现它,MLIR 使用了晦涩难懂又麻烦的 Pointer, Storage 的模式。 1813 | 1814 | Tablegen 可以快速地描述一段 IR,前提是去掉 debug 需要的时间。 1815 | 1816 | 多返回值 Op 虽然增加了 IR 的表达力,但让数据流分析变得复杂。实际上,多返回值 Op 几乎不存在。但我依然不得不为罕见的部分 Op 做特判,处理多返回值的情况。 1817 | 1818 | 另外,作为 C++ 的通病,MLIR 指针混沌得让人绝望。而更让人绝望的是,MLIR 要求任意操作过程中,IR 总是合法的。我们不能插入空指针,也不能随意删除一个变量。可以说,当你走出了 PatternRewrite 的舒适区域,想要做一些复杂的 Inline, Group, Partition 操作时,Segmentation Fault 总是与你形影不离。 1819 | 1820 | mlir 创新地把 Op 同构地看作 operand, attribute, result 的集合,具体的 Op 只是这个集合的解释方法。但其本质,就是定制的序列化和反序列化系统。而困惑的是,这样一个输入输出系统,在运行过程中一直存在,我们随时都在反序列化 Op 来获取 operand,更新 operand 之后又序列化到通用表示上。为了完成这样的序列化、反序列化,mlir 创造了令人惊叹的冗余代码,巨大的二进制文件,令人困惑的函数定义,无处不在的 Segmentation Fault 陷阱,和未知的性能提升。 1821 | 1822 | 我觉得,一个更好的 IR System 应该是: 1823 | 1824 | * 严格 SSA 的:所有操作都有返回值,多返回值看作 Tuple,无返回值看作空 Tuple 1825 | * 异构Op:Op 不存在 operand, attr, result 的统一形式,而是异构的,序列化按需进行。 1826 | * 无状态的:不需要 Interner。Interner 是为了处理大量的复制。用 Rc 来处理复制,实现专门的 Pass 来去重。 1827 | * 控制流、数据流分离的:控制流和数据流用不同的结构来储存,可以做分离的分析,而不是存在一个指针表里面 1828 | 1829 | ## 13. Issue & Reply 1830 | 1831 | 本文档仅作教学,本人不负责解决使用 mlir 中遇到的任何问题。作为一个要使用 mlir 的人,应该做好遭遇玄学 bug 的觉悟。 1832 | -------------------------------------------------------------------------------- /ex1-io/ex1-io.cpp: -------------------------------------------------------------------------------- 1 | #include "mlir/IR/AsmState.h" 2 | #include "mlir/IR/BuiltinOps.h" 3 | #include "mlir/IR/MLIRContext.h" 4 | #include "mlir/Parser/Parser.h" 5 | #include "mlir/Support/FileUtilities.h" 6 | 7 | #include "mlir/Dialect/Func/IR/FuncOps.h" 8 | #include "mlir/Dialect/Arith/IR/Arith.h" 9 | #include "llvm/Support/raw_ostream.h" 10 | 11 | using namespace mlir; 12 | 13 | int main(int argc, char ** argv) { 14 | MLIRContext ctx; 15 | // 首先,注册需要的 dialect 16 | ctx.loadDialect(); 17 | // 读入dialect 18 | auto src = parseSourceFile(argv[1], &ctx); 19 | // 输出dialect 20 | src->print(llvm::outs()); 21 | // 简单的输出,在 debug 的时候常用 22 | src->dump(); 23 | return 0; 24 | } -------------------------------------------------------------------------------- /ex1-io/ex1.mlir: -------------------------------------------------------------------------------- 1 | func.func @test(%a: i32, %b: i32) -> i32 { 2 | %c = arith.addi %a, %b : i32 3 | func.return %c : i32 4 | } -------------------------------------------------------------------------------- /ex2-build/ex2-build.cpp: -------------------------------------------------------------------------------- 1 | #include "mlir/IR/AsmState.h" 2 | #include "mlir/IR/Builders.h" 3 | #include "mlir/IR/BuiltinAttributes.h" 4 | #include "mlir/IR/BuiltinOps.h" 5 | #include "mlir/IR/BuiltinTypes.h" 6 | #include "mlir/IR/MLIRContext.h" 7 | #include "mlir/IR/Visitors.h" 8 | #include "mlir/Parser/Parser.h" 9 | #include "mlir/Support/FileUtilities.h" 10 | 11 | #include "mlir/Dialect/Func/IR/FuncOps.h" 12 | #include "mlir/Dialect/Arith/IR/Arith.h" 13 | #include "llvm/Support/Casting.h" 14 | #include "llvm/Support/raw_ostream.h" 15 | 16 | #include "llvm/ADT/DenseMap.h" 17 | 18 | using namespace mlir; 19 | using namespace llvm; 20 | 21 | int main(int argc, char ** argv) { 22 | MLIRContext ctx; 23 | 24 | ctx.loadDialect(); 25 | 26 | // 创建 OpBuilder 27 | OpBuilder builder(&ctx); 28 | auto mod = builder.create(builder.getUnknownLoc()); 29 | 30 | // 设置插入点 31 | builder.setInsertionPointToEnd(mod.getBody()); 32 | 33 | // 创建 func 34 | auto i32 = builder.getI32Type(); 35 | auto funcType = builder.getFunctionType({i32, i32}, {i32}); 36 | auto func = builder.create(builder.getUnknownLoc(), "test", funcType); 37 | 38 | // 添加基本块 39 | auto entry = func.addEntryBlock(); 40 | auto args = entry->getArguments(); 41 | 42 | // 设置插入点 43 | builder.setInsertionPointToEnd(entry); 44 | 45 | // 创建 arith.addi 46 | auto addi = builder.create(builder.getUnknownLoc(), args[0], args[1]); 47 | 48 | // 创建 func.return 49 | builder.create(builder.getUnknownLoc(), ValueRange({addi})); 50 | mod->print(llvm::outs()); 51 | return 0; 52 | } -------------------------------------------------------------------------------- /ex3-dialect/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(LLVM_RUNTIME_OUTPUT_INTDIR ${CMAKE_BINARY_DIR}) 2 | 3 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) 4 | include_directories(${CMAKE_CURRENT_BINARY_DIR}/include) 5 | add_subdirectory(include/toy) 6 | add_subdirectory(lib) 7 | add_subdirectory(tools/toy-opt) -------------------------------------------------------------------------------- /ex3-dialect/ex3-cse.mlir: -------------------------------------------------------------------------------- 1 | func.func @test(%a: i32, %b: i32) -> i32 { 2 | %c = "toy.add"(%a, %b): (i32, i32) -> i32 3 | %d = "toy.add"(%a, %b): (i32, i32) -> i32 4 | %e = "toy.add"(%c, %d): (i32, i32) -> i32 5 | %f = "toy.add"(%e, %e): (i32, i32) -> i32 6 | func.return %e : i32 7 | } -------------------------------------------------------------------------------- /ex3-dialect/ex3.mlir: -------------------------------------------------------------------------------- 1 | func.func @test(%a: i32, %b: i32) -> i32 { 2 | %c = "toy.add"(%a, %b): (i32, i32) -> i32 3 | func.return %c : i32 4 | } -------------------------------------------------------------------------------- /ex3-dialect/include/toy/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_mlir_dialect(Toy toy) -------------------------------------------------------------------------------- /ex3-dialect/include/toy/Toy.td: -------------------------------------------------------------------------------- 1 | #ifndef TOY_TD 2 | #define TOY_TD 3 | 4 | include "toy/ToyDialect.td" 5 | include "toy/ToyOps.td" 6 | 7 | #endif 8 | -------------------------------------------------------------------------------- /ex3-dialect/include/toy/ToyDialect.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mlir/IR/BuiltinDialect.h" 4 | #include "toy/ToyDialect.h.inc" 5 | -------------------------------------------------------------------------------- /ex3-dialect/include/toy/ToyDialect.td: -------------------------------------------------------------------------------- 1 | #ifndef TOY_DIALECT_TD 2 | #define TOY_DIALECT_TD 3 | 4 | include "mlir/IR/OpBase.td" 5 | 6 | def ToyDialect : Dialect { 7 | let name = "toy"; 8 | let cppNamespace = "::toy"; 9 | let summary = "Toy Dialect"; 10 | } 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /ex3-dialect/include/toy/ToyOps.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mlir/IR/BuiltinOps.h" 4 | #include "mlir/IR/Builders.h" 5 | 6 | #include "toy/ToyDialect.h" 7 | #include "mlir/Interfaces/SideEffectInterfaces.h" 8 | 9 | #define GET_OP_CLASSES 10 | #include "toy/Toy.h.inc" -------------------------------------------------------------------------------- /ex3-dialect/include/toy/ToyOps.td: -------------------------------------------------------------------------------- 1 | #ifndef TOY_OPS_TD 2 | #define TOY_OPS_TD 3 | 4 | include "mlir/IR/OpBase.td" 5 | include "toy/ToyDialect.td" 6 | include "mlir/Interfaces/SideEffectInterfaces.td" 7 | 8 | // mnemonic 指名字 9 | class ToyOp traits = []> : 10 | Op; 11 | 12 | // Pure 是 Trait,表示没有 SideEffect 的纯函数 13 | def AddOp : ToyOp<"add", [Pure]> { 14 | let summary = "add operation"; 15 | let arguments = (ins Variadic:$inputs); 16 | let results = (outs AnyInteger:$result); 17 | } 18 | 19 | def SubOp : ToyOp<"sub", [Pure]> { 20 | let summary = "sub operation"; 21 | let arguments = (ins AnyInteger:$lhs, AnyInteger:$rhs); 22 | let results = (outs AnyInteger:$result); 23 | } 24 | 25 | def ConstantOp : ToyOp<"const", [Pure]> { 26 | let summary = "const operation"; 27 | let arguments = (ins APIntAttr:$value); 28 | let results = (outs AnyInteger:$result); 29 | } 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /ex3-dialect/lib/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_mlir_library(Toy toy.cpp DEPENDS MLIRToyIncGen) -------------------------------------------------------------------------------- /ex3-dialect/lib/toy.cpp: -------------------------------------------------------------------------------- 1 | #include "toy/ToyDialect.h" 2 | #include "toy/ToyOps.h" 3 | 4 | #include "toy/ToyDialect.cpp.inc" 5 | #define GET_OP_CLASSES 6 | #include "toy/Toy.cpp.inc" 7 | 8 | using namespace mlir; 9 | using namespace toy; 10 | 11 | void ToyDialect::initialize() { 12 | addOperations< 13 | #define GET_OP_LIST 14 | #include "toy/Toy.cpp.inc" 15 | >(); 16 | } 17 | -------------------------------------------------------------------------------- /ex3-dialect/tools/toy-opt/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_mlir_tool(ex3-opt toy-opt.cpp) 2 | target_link_libraries(ex3-opt 3 | PRIVATE Toy 4 | MLIRIR MLIRParser MLIRSupport 5 | MLIROptLib # 对应 #include "mlir/Tools/mlir-opt/MlirOptMain.h" 6 | MLIRFuncDialect # 对应 #include "mlir/Dialect/Func/IR/FuncOps.h" 7 | MLIRTransforms # 对应 #include "mlir/Transforms/Passes.h" 8 | ) -------------------------------------------------------------------------------- /ex3-dialect/tools/toy-opt/toy-opt.cpp: -------------------------------------------------------------------------------- 1 | #include "mlir/IR/DialectRegistry.h" 2 | #include "mlir/Tools/mlir-opt/MlirOptMain.h" 3 | // 导入 Func Dialect 4 | #include "mlir/Dialect/Func/IR/FuncOps.h" 5 | // 导入 MLIR 自带 Pass 6 | #include "mlir/Transforms/Passes.h" 7 | // 导入我们新建的 Dialect 8 | #include "toy/ToyDialect.h" 9 | using namespace mlir; 10 | using namespace llvm; 11 | 12 | int main(int argc, char ** argv) { 13 | DialectRegistry registry; 14 | // 注册 Dialect 15 | registry.insert(); 16 | // 注册两个 Pass 17 | registerCSEPass(); 18 | registerCanonicalizerPass(); 19 | return asMainReturnCode(MlirOptMain(argc, argv, "toy-opt", registry)); 20 | } -------------------------------------------------------------------------------- /ex4-beautiful-dialect/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | set(LLVM_RUNTIME_OUTPUT_INTDIR ${CMAKE_BINARY_DIR}) 4 | 5 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) 6 | include_directories(${CMAKE_CURRENT_BINARY_DIR}/include) 7 | add_subdirectory(include/toy) 8 | add_subdirectory(lib) 9 | add_subdirectory(tools/toy-opt) -------------------------------------------------------------------------------- /ex4-beautiful-dialect/ex4.mlir: -------------------------------------------------------------------------------- 1 | toy.func @add(%a: i32, %b: i32) -> i32 { 2 | %c = toy.add %a, %b : i32 3 | toy.ret %c : i32 4 | } 5 | 6 | toy.func @test(%a: i32, %b: i32) -> i32 { 7 | %c = toy.call @add(%a, %b) : (i32, i32) -> i32 8 | toy.ret %c : i32 9 | } -------------------------------------------------------------------------------- /ex4-beautiful-dialect/include/toy/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_mlir_dialect(Toy toy) 2 | add_custom_target(header DEPENDS MLIRToyIncGen) -------------------------------------------------------------------------------- /ex4-beautiful-dialect/include/toy/Toy.td: -------------------------------------------------------------------------------- 1 | #ifndef TOY_TD 2 | #define TOY_TD 3 | 4 | include "toy/ToyDialect.td" 5 | include "toy/ToyOps.td" 6 | 7 | #endif 8 | -------------------------------------------------------------------------------- /ex4-beautiful-dialect/include/toy/ToyDialect.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mlir/IR/BuiltinDialect.h" 4 | #include "toy/ToyDialect.h.inc" 5 | -------------------------------------------------------------------------------- /ex4-beautiful-dialect/include/toy/ToyDialect.td: -------------------------------------------------------------------------------- 1 | #ifndef TOY_DIALECT_TD 2 | #define TOY_DIALECT_TD 3 | 4 | include "mlir/IR/OpBase.td" 5 | 6 | def ToyDialect : Dialect { 7 | let name = "toy"; 8 | let cppNamespace = "::toy"; 9 | let summary = "Toy Dialect"; 10 | } 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /ex4-beautiful-dialect/include/toy/ToyOps.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mlir/IR/BuiltinOps.h" 4 | #include "mlir/IR/Builders.h" 5 | 6 | #include "toy/ToyDialect.h" 7 | #include "mlir/Interfaces/InferTypeOpInterface.h" 8 | #include "mlir/Interfaces/SideEffectInterfaces.h" 9 | #include "mlir/Interfaces/ControlFlowInterfaces.h" 10 | #include "mlir/Interfaces/FunctionInterfaces.h" 11 | #include "mlir/IR/RegionKindInterface.h" 12 | 13 | #define GET_OP_CLASSES 14 | #include "toy/Toy.h.inc" -------------------------------------------------------------------------------- /ex4-beautiful-dialect/include/toy/ToyOps.td: -------------------------------------------------------------------------------- 1 | #ifndef TOY_OPS_TD 2 | #define TOY_OPS_TD 3 | 4 | include "mlir/IR/OpBase.td" 5 | include "toy/ToyDialect.td" 6 | include "mlir/Interfaces/InferTypeOpInterface.td" 7 | include "mlir/Interfaces/SideEffectInterfaces.td" 8 | include "mlir/Interfaces/FunctionInterfaces.td" 9 | include "mlir/Interfaces/ControlFlowInterfaces.td" 10 | include "mlir/IR/RegionKindInterface.td" 11 | 12 | // mnemonic 指名字 13 | class ToyOp traits = []> : 14 | Op; 15 | 16 | // Pure 是 Trait,表示没有 SideEffect 的纯函数 17 | def AddOp : ToyOp<"add", [Pure, SameOperandsAndResultType]> { 18 | let summary = "add operation"; 19 | let arguments = (ins Variadic:$inputs); 20 | let results = (outs AnyInteger:$result); 21 | let assemblyFormat = "$inputs attr-dict `:` type($result)"; 22 | } 23 | 24 | def SubOp : ToyOp<"sub", [Pure, SameOperandsAndResultType]> { 25 | let summary = "sub operation"; 26 | let arguments = (ins AnyInteger:$lhs, AnyInteger:$rhs); 27 | let results = (outs AnyInteger:$result); 28 | let assemblyFormat = "$lhs `,` $rhs attr-dict `:` type($result)"; 29 | } 30 | 31 | def ConstantOp : ToyOp<"const", [Pure, InferTypeOpAdaptor]> { 32 | let summary = "const operation"; 33 | let arguments = (ins APIntAttr:$value); 34 | let results = (outs AnyInteger:$result); 35 | let assemblyFormat = "$value attr-dict"; 36 | let extraClassDeclaration = [{ 37 | int64_t getBitWidth() { 38 | return getResult().getType().getWidth(); 39 | } 40 | }]; 41 | } 42 | 43 | // 另一种实现 44 | // 45 | // def ConstantOp : ToyOp<"const", [Pure, InferTypeOpInterface]> { 46 | // let summary = "const operation"; 47 | // let arguments = (ins APIntAttr:$value); 48 | // let results = (outs AnyInteger:$result); 49 | // let assemblyFormat = "$value attr-dict"; 50 | // let extraClassDeclaration = [{ 51 | // static mlir::LogicalResult inferReturnTypes( 52 | // mlir::MLIRContext * context, 53 | // std::optional<::mlir::Location> location, 54 | // mlir::ValueRange operands, 55 | // mlir::DictionaryAttr attributes, 56 | // mlir::OpaqueProperties properties, 57 | // mlir::RegionRange regions, 58 | // llvm::SmallVectorImpl<::mlir::Type>& inferredReturnTypes 59 | // ); 60 | // }]; 61 | // } 62 | 63 | def ReturnOp : ToyOp<"ret", [Terminator, ReturnLike]> { 64 | let summary = "return operation"; 65 | let arguments = (ins AnyType:$data); 66 | let assemblyFormat = "$data attr-dict `:` type($data)"; 67 | } 68 | 69 | def FuncOp : ToyOp<"func", [ 70 | FunctionOpInterface, 71 | /* Symbol, */ /* Symbol 会自动被 FunctionOpInterface 加上 */ 72 | /* CallableOpInterface, */ /* CallOpInterface 会自动被 FunctionOpInterface 加上 */ 73 | RegionKindInterface]> { 74 | let summary = "function"; 75 | let arguments = (ins 76 | SymbolNameAttr:$sym_name, 77 | TypeAttrOf:$function_type, 78 | // FunctionOpInterface 需要两个 Attr 来记录 arg 和 res 的名字 79 | OptionalAttr:$arg_attrs, 80 | OptionalAttr:$res_attrs 81 | ); 82 | dag regions = (region AnyRegion:$body); 83 | let hasCustomAssemblyFormat = true; 84 | let extraClassDeclaration = [{ 85 | // Method of FunctionOpInterface 86 | mlir::Region * getCallableRegion() {return &getBody();} 87 | // getFunctionType 函数会自动生成 88 | // mlir::FunctionType getFunctionType(); 89 | 90 | // Method of CallableOpInterface 91 | llvm::ArrayRef getArgumentTypes() {return getFunctionType().getInputs();} 92 | llvm::ArrayRef getResultTypes() {return getFunctionType().getResults();} 93 | 94 | // Method of RegionKindInterface 95 | static mlir::RegionKind getRegionKind(unsigned idx) { return mlir::RegionKind::SSACFG; } 96 | }]; 97 | } 98 | 99 | def CallOp : ToyOp<"call", [CallOpInterface]> { 100 | let summary = "call operation"; 101 | let arguments = (ins SymbolRefAttr:$callee, Variadic:$arg_operands); 102 | let results = (outs AnyType:$result); 103 | let assemblyFormat = "$callee `(` $arg_operands `)` attr-dict `:` functional-type($arg_operands, results)"; 104 | let extraClassDeclaration = [{ 105 | mlir::CallInterfaceCallable getCallableForCallee() { return getCalleeAttr(); } 106 | void setCalleeFromCallable(mlir::CallInterfaceCallable callee) { setCalleeAttr(callee.get()); } 107 | }]; 108 | } 109 | 110 | #endif 111 | -------------------------------------------------------------------------------- /ex4-beautiful-dialect/lib/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_mlir_library(Toy toy.cpp DEPENDS MLIRToyIncGen) -------------------------------------------------------------------------------- /ex4-beautiful-dialect/lib/toy.cpp: -------------------------------------------------------------------------------- 1 | #include "mlir/IR/BuiltinAttributes.h" 2 | #include "mlir/IR/BuiltinTypes.h" 3 | #include "mlir/Interfaces/CallInterfaces.h" 4 | #include "mlir/Support/LogicalResult.h" 5 | #include "mlir/Interfaces/FunctionImplementation.h" 6 | #include "toy/ToyDialect.h" 7 | #include "toy/ToyOps.h" 8 | 9 | #include "toy/ToyDialect.cpp.inc" 10 | #define GET_OP_CLASSES 11 | #include "toy/Toy.cpp.inc" 12 | 13 | using namespace mlir; 14 | using namespace toy; 15 | 16 | void ToyDialect::initialize() { 17 | addOperations< 18 | #define GET_OP_LIST 19 | #include "toy/Toy.cpp.inc" 20 | >(); 21 | } 22 | 23 | // mlir::LogicalResult ConstantOp::inferReturnTypes( 24 | // mlir::MLIRContext * context, 25 | // std::optional<::mlir::Location> location, 26 | // mlir::ValueRange operands, 27 | // mlir::DictionaryAttr attributes, 28 | // mlir::OpaqueProperties properties, 29 | // mlir::RegionRange regions, 30 | // llvm::SmallVectorImpl<::mlir::Type>& inferredReturnTypes 31 | // ) { 32 | // ConstantOp::Adaptor adaptor(operands, attributes, properties, regions); 33 | // inferredReturnTypes.push_back(adaptor.getValueAttr().getType()); 34 | // return success(); 35 | // } 36 | 37 | mlir::LogicalResult ConstantOp::inferReturnTypes( 38 | mlir::MLIRContext * context, 39 | std::optional location, 40 | Adaptor adaptor, 41 | llvm::SmallVectorImpl & inferedReturnType 42 | ) { 43 | inferedReturnType.push_back(adaptor.getValueAttr().getType()); 44 | return mlir::success(); 45 | } 46 | 47 | mlir::ParseResult FuncOp::parse(::mlir::OpAsmParser &parser, ::mlir::OperationState &result) { 48 | auto buildFuncType = [](auto & builder, auto argTypes, auto results, auto, auto) { 49 | return builder.getFunctionType(argTypes, results); 50 | }; 51 | return function_interface_impl::parseFunctionOp( 52 | parser, result, false, 53 | getFunctionTypeAttrName(result.name), buildFuncType, 54 | getArgAttrsAttrName(result.name), getResAttrsAttrName(result.name) 55 | ); 56 | } 57 | 58 | void FuncOp::print(mlir::OpAsmPrinter &p) { 59 | // Dispatch to the FunctionOpInterface provided utility method that prints the 60 | // function operation. 61 | mlir::function_interface_impl::printFunctionOp( 62 | p, *this, /*isVariadic=*/false, getFunctionTypeAttrName(), 63 | getArgAttrsAttrName(), getResAttrsAttrName()); 64 | } 65 | -------------------------------------------------------------------------------- /ex4-beautiful-dialect/tools/toy-opt/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_mlir_tool(ex4-opt toy-opt.cpp) 2 | target_link_libraries(ex4-opt 3 | PRIVATE Toy 4 | MLIRIR MLIRParser MLIRSupport 5 | MLIROptLib # 对应 #include "mlir/Tools/mlir-opt/MlirOptMain.h" 6 | MLIRFuncDialect # 对应 #include "mlir/Dialect/Func/IR/FuncOps.h" 7 | MLIRTransforms # 对应 #include "mlir/Transforms/Passes.h" 8 | ) -------------------------------------------------------------------------------- /ex4-beautiful-dialect/tools/toy-opt/toy-opt.cpp: -------------------------------------------------------------------------------- 1 | #include "mlir/IR/DialectRegistry.h" 2 | #include "mlir/Tools/mlir-opt/MlirOptMain.h" 3 | // 导入 Func Dialect 4 | #include "mlir/Dialect/Func/IR/FuncOps.h" 5 | // 导入 MLIR 自带 Pass 6 | #include "mlir/Transforms/Passes.h" 7 | // 导入我们新建的 Dialect 8 | #include "toy/ToyDialect.h" 9 | using namespace mlir; 10 | using namespace llvm; 11 | 12 | int main(int argc, char ** argv) { 13 | DialectRegistry registry; 14 | // 注册 Dialect 15 | registry.insert(); 16 | // 注册两个 Pass 17 | registerCSEPass(); 18 | registerCanonicalizerPass(); 19 | return asMainReturnCode(MlirOptMain(argc, argv, "toy-opt", registry)); 20 | } 21 | -------------------------------------------------------------------------------- /ex5-pass/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | set(LLVM_RUNTIME_OUTPUT_INTDIR ${CMAKE_BINARY_DIR}) 4 | 5 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) 6 | include_directories(${CMAKE_CURRENT_BINARY_DIR}/include) 7 | add_subdirectory(include/toy) 8 | add_subdirectory(lib) 9 | add_subdirectory(tools/toy-opt) -------------------------------------------------------------------------------- /ex5-pass/ex5.mlir: -------------------------------------------------------------------------------- 1 | toy.func @add(%a: i32, %b: i32) -> i32 { 2 | %c = toy.add %a, %b : i32 3 | toy.ret %c : i32 4 | } 5 | 6 | toy.func @test(%a: i32, %b: i32) -> i32 { 7 | %c = toy.call @add(%a, %b) : (i32, i32) -> i32 8 | %d = toy.add %a, %b : i32 9 | %f = toy.add %d, %d : i32 10 | toy.ret %c : i32 11 | } -------------------------------------------------------------------------------- /ex5-pass/include/toy/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_mlir_dialect(Toy toy) 2 | 3 | 4 | set(LLVM_TARGET_DEFINITIONS ToyPasses.td) 5 | mlir_tablegen(ToyPasses.h.inc -gen-pass-decls) 6 | add_public_tablegen_target(MLIRToyTransformsIncGen) 7 | 8 | add_custom_target(header DEPENDS MLIRToyIncGen MLIRToyTransformsIncGen) -------------------------------------------------------------------------------- /ex5-pass/include/toy/Toy.td: -------------------------------------------------------------------------------- 1 | #ifndef TOY_TD 2 | #define TOY_TD 3 | 4 | include "toy/ToyDialect.td" 5 | include "toy/ToyOps.td" 6 | 7 | #endif 8 | -------------------------------------------------------------------------------- /ex5-pass/include/toy/ToyDialect.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mlir/IR/BuiltinDialect.h" 4 | #include "toy/ToyDialect.h.inc" 5 | -------------------------------------------------------------------------------- /ex5-pass/include/toy/ToyDialect.td: -------------------------------------------------------------------------------- 1 | #ifndef TOY_DIALECT_TD 2 | #define TOY_DIALECT_TD 3 | 4 | include "mlir/IR/OpBase.td" 5 | 6 | def ToyDialect : Dialect { 7 | let name = "toy"; 8 | let cppNamespace = "::toy"; 9 | let summary = "Toy Dialect"; 10 | } 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /ex5-pass/include/toy/ToyOps.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mlir/IR/BuiltinOps.h" 4 | #include "mlir/IR/Builders.h" 5 | 6 | #include "toy/ToyDialect.h" 7 | #include "mlir/Interfaces/InferTypeOpInterface.h" 8 | #include "mlir/Interfaces/SideEffectInterfaces.h" 9 | #include "mlir/Interfaces/ControlFlowInterfaces.h" 10 | #include "mlir/Interfaces/FunctionInterfaces.h" 11 | #include "mlir/IR/RegionKindInterface.h" 12 | 13 | #define GET_OP_CLASSES 14 | #include "toy/Toy.h.inc" -------------------------------------------------------------------------------- /ex5-pass/include/toy/ToyOps.td: -------------------------------------------------------------------------------- 1 | #ifndef TOY_OPS_TD 2 | #define TOY_OPS_TD 3 | 4 | include "mlir/IR/OpBase.td" 5 | include "toy/ToyDialect.td" 6 | include "mlir/Interfaces/InferTypeOpInterface.td" 7 | include "mlir/Interfaces/SideEffectInterfaces.td" 8 | include "mlir/Interfaces/FunctionInterfaces.td" 9 | include "mlir/Interfaces/ControlFlowInterfaces.td" 10 | include "mlir/IR/RegionKindInterface.td" 11 | 12 | // mnemonic 指名字 13 | class ToyOp traits = []> : 14 | Op; 15 | 16 | // Pure 是 Trait,表示没有 SideEffect 的纯函数 17 | def AddOp : ToyOp<"add", [Pure, SameOperandsAndResultType]> { 18 | let summary = "add operation"; 19 | let arguments = (ins Variadic:$inputs); 20 | let results = (outs AnyInteger:$result); 21 | let assemblyFormat = "$inputs attr-dict `:` type($result)"; 22 | } 23 | 24 | def SubOp : ToyOp<"sub", [Pure, SameOperandsAndResultType]> { 25 | let summary = "sub operation"; 26 | let arguments = (ins AnyInteger:$lhs, AnyInteger:$rhs); 27 | let results = (outs AnyInteger:$result); 28 | let assemblyFormat = "$lhs `,` $rhs attr-dict `:` type($result)"; 29 | } 30 | 31 | def ConstantOp : ToyOp<"const", [Pure, InferTypeOpAdaptor]> { 32 | let summary = "const operation"; 33 | let arguments = (ins APIntAttr:$value); 34 | let results = (outs AnyInteger:$result); 35 | let assemblyFormat = "$value attr-dict"; 36 | let extraClassDeclaration = [{ 37 | int64_t getBitWidth() { 38 | return getResult().getType().getWidth(); 39 | } 40 | }]; 41 | } 42 | 43 | // 另一种实现 44 | // 45 | // def ConstantOp : ToyOp<"const", [Pure, InferTypeOpInterface]> { 46 | // let summary = "const operation"; 47 | // let arguments = (ins APIntAttr:$value); 48 | // let results = (outs AnyInteger:$result); 49 | // let assemblyFormat = "$value attr-dict"; 50 | // let extraClassDeclaration = [{ 51 | // static mlir::LogicalResult inferReturnTypes( 52 | // mlir::MLIRContext * context, 53 | // std::optional<::mlir::Location> location, 54 | // mlir::ValueRange operands, 55 | // mlir::DictionaryAttr attributes, 56 | // mlir::OpaqueProperties properties, 57 | // mlir::RegionRange regions, 58 | // llvm::SmallVectorImpl<::mlir::Type>& inferredReturnTypes 59 | // ); 60 | // }]; 61 | // } 62 | 63 | def ReturnOp : ToyOp<"ret", [Terminator, ReturnLike]> { 64 | let summary = "return operation"; 65 | let arguments = (ins AnyType:$data); 66 | let assemblyFormat = "$data attr-dict `:` type($data)"; 67 | } 68 | 69 | def FuncOp : ToyOp<"func", [ 70 | IsolatedFromAbove, 71 | FunctionOpInterface, 72 | /* Symbol, */ /* Symbol 会自动被 FunctionOpInterface 加上 */ 73 | /* CallableOpInterface, */ /* CallOpInterface 会自动被 FunctionOpInterface 加上 */ 74 | RegionKindInterface]> { 75 | let summary = "function"; 76 | let arguments = (ins 77 | SymbolNameAttr:$sym_name, 78 | TypeAttrOf:$function_type, 79 | // FunctionOpInterface 需要两个 Attr 来记录 arg 和 res 的名字 80 | OptionalAttr:$arg_attrs, 81 | OptionalAttr:$res_attrs 82 | ); 83 | dag regions = (region AnyRegion:$body); 84 | let hasCustomAssemblyFormat = true; 85 | let extraClassDeclaration = [{ 86 | // Method of FunctionOpInterface 87 | mlir::Region * getCallableRegion() {return &getBody();} 88 | // getFunctionType 函数会自动生成 89 | // mlir::FunctionType getFunctionType(); 90 | 91 | // Method of CallableOpInterface 92 | llvm::ArrayRef getArgumentTypes() {return getFunctionType().getInputs();} 93 | llvm::ArrayRef getResultTypes() {return getFunctionType().getResults();} 94 | 95 | // Method of RegionKindInterface 96 | static mlir::RegionKind getRegionKind(unsigned idx) { return mlir::RegionKind::SSACFG; } 97 | }]; 98 | } 99 | 100 | def CallOp : ToyOp<"call", [CallOpInterface]> { 101 | let summary = "call operation"; 102 | let arguments = (ins SymbolRefAttr:$callee, Variadic:$arg_operands); 103 | let results = (outs AnyType:$result); 104 | let assemblyFormat = "$callee `(` $arg_operands `)` attr-dict `:` functional-type($arg_operands, results)"; 105 | let extraClassDeclaration = [{ 106 | mlir::CallInterfaceCallable getCallableForCallee() { return getCalleeAttr(); } 107 | void setCalleeFromCallable(mlir::CallInterfaceCallable callee) { setCalleeAttr(callee.get()); } 108 | }]; 109 | } 110 | 111 | #endif 112 | -------------------------------------------------------------------------------- /ex5-pass/include/toy/ToyPasses.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mlir/Pass/Pass.h" 4 | #include "mlir/Pass/PassRegistry.h" 5 | #include "toy/ToyOps.h" 6 | #include 7 | 8 | namespace toy { 9 | 10 | #define GEN_PASS_DECL 11 | #include "toy/ToyPasses.h.inc" 12 | 13 | std::unique_ptr createConvertToyToArithPass(ConvertToyToArithOptions options={}); 14 | std::unique_ptr createDCEPass(); 15 | 16 | #define GEN_PASS_REGISTRATION 17 | #include "toy/ToyPasses.h.inc" 18 | 19 | } -------------------------------------------------------------------------------- /ex5-pass/include/toy/ToyPasses.td: -------------------------------------------------------------------------------- 1 | #ifndef TOY_PASSES_TD 2 | #define TOY_PASSES_TD 3 | 4 | include "mlir/Pass/PassBase.td" 5 | 6 | def ConvertToyToArith : Pass<"convert-toy-to-arith"> { 7 | let summary = "Convert Toy To Arith"; 8 | let constructor = "toy::createConvertToyToArithPass()"; 9 | let options = [ 10 | Option<"name", "name", "std::string", "", "help"> 11 | ]; 12 | } 13 | 14 | def DCE : Pass<"toy-dce", "toy::FuncOp"> { 15 | let summary = "dce"; 16 | let constructor = "toy::createDCEPass()"; 17 | } 18 | #endif 19 | -------------------------------------------------------------------------------- /ex5-pass/lib/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_mlir_library(Toy toy.cpp DEPENDS MLIRToyIncGen) 2 | add_subdirectory(Transforms) -------------------------------------------------------------------------------- /ex5-pass/lib/Transforms/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_mlir_library( 2 | ToyTransforms 3 | ConvertToyToArith.cpp 4 | DCE.cpp 5 | DEPENDS 6 | MLIRToyTransformsIncGen 7 | ) -------------------------------------------------------------------------------- /ex5-pass/lib/Transforms/ConvertToyToArith.cpp: -------------------------------------------------------------------------------- 1 | #include "llvm/Support/raw_ostream.h" 2 | #define GEN_PASS_DEF_CONVERTTOYTOARITH 3 | #include "toy/ToyPasses.h" 4 | 5 | struct ConvertToyToArithPass : toy::impl::ConvertToyToArithBase { 6 | using toy::impl::ConvertToyToArithBase::ConvertToyToArithBase; 7 | void runOnOperation() final { 8 | llvm::errs() << "get name: " << name << "\n"; 9 | } 10 | }; 11 | 12 | std::unique_ptr toy::createConvertToyToArithPass(ConvertToyToArithOptions options) { 13 | return std::make_unique(options); 14 | } 15 | -------------------------------------------------------------------------------- /ex5-pass/lib/Transforms/DCE.cpp: -------------------------------------------------------------------------------- 1 | #include "llvm/ADT/STLExtras.h" 2 | #include "llvm/ADT/SmallVector.h" 3 | #include "llvm/Support/raw_ostream.h" 4 | #define GEN_PASS_DEF_DCE 5 | #include "toy/ToyPasses.h" 6 | 7 | using namespace mlir; 8 | using namespace llvm; 9 | using namespace toy; 10 | 11 | struct DCEPass : toy::impl::DCEBase { 12 | void visitAll(llvm::DenseSet &visited, Operation * op) { 13 | if(visited.contains(op)) return; 14 | visited.insert(op); 15 | for(auto operand: op->getOperands()) 16 | if(auto def = operand.getDefiningOp()) 17 | visitAll(visited, def); 18 | } 19 | void runOnOperation() final { 20 | llvm::DenseSet visited; 21 | getOperation()->walk([&](toy::ReturnOp op) { 22 | visitAll(visited, op); 23 | }); 24 | llvm::SmallVector opToRemove; 25 | getOperation().walk([&](Operation * op) { 26 | if(op == getOperation()) return; 27 | if(!visited.contains(op)) opToRemove.push_back(op); 28 | }); 29 | for(auto v: reverse(opToRemove)) { 30 | v->erase(); 31 | } 32 | } 33 | }; 34 | 35 | std::unique_ptr toy::createDCEPass() { 36 | return std::make_unique(); 37 | } 38 | -------------------------------------------------------------------------------- /ex5-pass/lib/toy.cpp: -------------------------------------------------------------------------------- 1 | #include "mlir/IR/BuiltinAttributes.h" 2 | #include "mlir/IR/BuiltinTypes.h" 3 | #include "mlir/Interfaces/CallInterfaces.h" 4 | #include "mlir/Support/LogicalResult.h" 5 | #include "mlir/Interfaces/FunctionImplementation.h" 6 | #include "toy/ToyDialect.h" 7 | #include "toy/ToyOps.h" 8 | 9 | #include "toy/ToyDialect.cpp.inc" 10 | #define GET_OP_CLASSES 11 | #include "toy/Toy.cpp.inc" 12 | 13 | using namespace mlir; 14 | using namespace toy; 15 | 16 | void ToyDialect::initialize() { 17 | addOperations< 18 | #define GET_OP_LIST 19 | #include "toy/Toy.cpp.inc" 20 | >(); 21 | } 22 | 23 | // mlir::LogicalResult ConstantOp::inferReturnTypes( 24 | // mlir::MLIRContext * context, 25 | // std::optional<::mlir::Location> location, 26 | // mlir::ValueRange operands, 27 | // mlir::DictionaryAttr attributes, 28 | // mlir::OpaqueProperties properties, 29 | // mlir::RegionRange regions, 30 | // llvm::SmallVectorImpl<::mlir::Type>& inferredReturnTypes 31 | // ) { 32 | // ConstantOp::Adaptor adaptor(operands, attributes, properties, regions); 33 | // inferredReturnTypes.push_back(adaptor.getValueAttr().getType()); 34 | // return success(); 35 | // } 36 | 37 | mlir::LogicalResult ConstantOp::inferReturnTypes( 38 | mlir::MLIRContext * context, 39 | std::optional location, 40 | Adaptor adaptor, 41 | llvm::SmallVectorImpl & inferedReturnType 42 | ) { 43 | inferedReturnType.push_back(adaptor.getValueAttr().getType()); 44 | return mlir::success(); 45 | } 46 | 47 | mlir::ParseResult FuncOp::parse(::mlir::OpAsmParser &parser, ::mlir::OperationState &result) { 48 | auto buildFuncType = [](auto & builder, auto argTypes, auto results, auto, auto) { 49 | return builder.getFunctionType(argTypes, results); 50 | }; 51 | return function_interface_impl::parseFunctionOp( 52 | parser, result, false, 53 | getFunctionTypeAttrName(result.name), buildFuncType, 54 | getArgAttrsAttrName(result.name), getResAttrsAttrName(result.name) 55 | ); 56 | } 57 | 58 | void FuncOp::print(mlir::OpAsmPrinter &p) { 59 | // Dispatch to the FunctionOpInterface provided utility method that prints the 60 | // function operation. 61 | mlir::function_interface_impl::printFunctionOp( 62 | p, *this, /*isVariadic=*/false, getFunctionTypeAttrName(), 63 | getArgAttrsAttrName(), getResAttrsAttrName()); 64 | } 65 | -------------------------------------------------------------------------------- /ex5-pass/tools/toy-opt/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_mlir_tool(ex5-opt toy-opt.cpp) 2 | target_link_libraries(ex5-opt 3 | PRIVATE Toy 4 | MLIRIR MLIRParser MLIRSupport 5 | MLIROptLib # 对应 #include "mlir/Tools/mlir-opt/MlirOptMain.h" 6 | MLIRFuncDialect # 对应 #include "mlir/Dialect/Func/IR/FuncOps.h" 7 | MLIRTransforms # 对应 #include "mlir/Transforms/Passes.h" 8 | ToyTransforms 9 | ) -------------------------------------------------------------------------------- /ex5-pass/tools/toy-opt/toy-opt.cpp: -------------------------------------------------------------------------------- 1 | #include "mlir/IR/DialectRegistry.h" 2 | #include "mlir/Tools/mlir-opt/MlirOptMain.h" 3 | // 导入 Func Dialect 4 | #include "mlir/Dialect/Func/IR/FuncOps.h" 5 | // 导入 MLIR 自带 Pass 6 | #include "mlir/Transforms/Passes.h" 7 | // 导入我们新建的 Dialect 8 | #include "toy/ToyDialect.h" 9 | #include "toy/ToyPasses.h" 10 | using namespace mlir; 11 | using namespace llvm; 12 | 13 | int main(int argc, char ** argv) { 14 | DialectRegistry registry; 15 | // 注册 Dialect 16 | registry.insert(); 17 | // 注册两个 Pass 18 | registerCSEPass(); 19 | registerCanonicalizerPass(); 20 | toy::registerPasses(); 21 | return asMainReturnCode(MlirOptMain(argc, argv, "toy-opt", registry)); 22 | } 23 | -------------------------------------------------------------------------------- /ex6-pattern/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | set(LLVM_RUNTIME_OUTPUT_INTDIR ${CMAKE_BINARY_DIR}) 4 | 5 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) 6 | include_directories(${CMAKE_CURRENT_BINARY_DIR}/include) 7 | add_subdirectory(include/toy) 8 | add_subdirectory(lib) 9 | add_subdirectory(tools/toy-opt) -------------------------------------------------------------------------------- /ex6-pattern/ex6.mlir: -------------------------------------------------------------------------------- 1 | toy.func @add(%a: i32, %b: i32) -> i32 { 2 | %c = toy.add %a, %b : i32 3 | toy.ret %c : i32 4 | } 5 | 6 | toy.func @test(%a: i32, %b: i32) -> i32 { 7 | %c = toy.call @add(%a, %b) : (i32, i32) -> i32 8 | %d = toy.add %a, %b : i32 9 | %f = toy.add %d, %d : i32 10 | toy.ret %f : i32 11 | } -------------------------------------------------------------------------------- /ex6-pattern/include/toy/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_mlir_dialect(Toy toy) 2 | 3 | 4 | set(LLVM_TARGET_DEFINITIONS ToyPasses.td) 5 | mlir_tablegen(ToyPasses.h.inc -gen-pass-decls) 6 | add_public_tablegen_target(MLIRToyTransformsIncGen) 7 | 8 | add_custom_target(header DEPENDS MLIRToyIncGen MLIRToyTransformsIncGen) -------------------------------------------------------------------------------- /ex6-pattern/include/toy/Toy.td: -------------------------------------------------------------------------------- 1 | #ifndef TOY_TD 2 | #define TOY_TD 3 | 4 | include "toy/ToyDialect.td" 5 | include "toy/ToyOps.td" 6 | 7 | #endif 8 | -------------------------------------------------------------------------------- /ex6-pattern/include/toy/ToyDialect.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mlir/IR/BuiltinDialect.h" 4 | #include "toy/ToyDialect.h.inc" 5 | -------------------------------------------------------------------------------- /ex6-pattern/include/toy/ToyDialect.td: -------------------------------------------------------------------------------- 1 | #ifndef TOY_DIALECT_TD 2 | #define TOY_DIALECT_TD 3 | 4 | include "mlir/IR/OpBase.td" 5 | 6 | def ToyDialect : Dialect { 7 | let name = "toy"; 8 | let cppNamespace = "::toy"; 9 | let summary = "Toy Dialect"; 10 | } 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /ex6-pattern/include/toy/ToyOps.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mlir/IR/BuiltinOps.h" 4 | #include "mlir/IR/Builders.h" 5 | 6 | #include "toy/ToyDialect.h" 7 | #include "mlir/Interfaces/InferTypeOpInterface.h" 8 | #include "mlir/Interfaces/SideEffectInterfaces.h" 9 | #include "mlir/Interfaces/ControlFlowInterfaces.h" 10 | #include "mlir/Interfaces/FunctionInterfaces.h" 11 | #include "mlir/IR/RegionKindInterface.h" 12 | 13 | #define GET_OP_CLASSES 14 | #include "toy/Toy.h.inc" -------------------------------------------------------------------------------- /ex6-pattern/include/toy/ToyOps.td: -------------------------------------------------------------------------------- 1 | #ifndef TOY_OPS_TD 2 | #define TOY_OPS_TD 3 | 4 | include "mlir/IR/OpBase.td" 5 | include "toy/ToyDialect.td" 6 | include "mlir/Interfaces/InferTypeOpInterface.td" 7 | include "mlir/Interfaces/SideEffectInterfaces.td" 8 | include "mlir/Interfaces/FunctionInterfaces.td" 9 | include "mlir/Interfaces/ControlFlowInterfaces.td" 10 | include "mlir/IR/RegionKindInterface.td" 11 | 12 | // mnemonic 指名字 13 | class ToyOp traits = []> : 14 | Op; 15 | 16 | // Pure 是 Trait,表示没有 SideEffect 的纯函数 17 | def AddOp : ToyOp<"add", [Pure, SameOperandsAndResultType]> { 18 | let summary = "add operation"; 19 | let arguments = (ins Variadic:$inputs); 20 | let results = (outs AnyInteger:$result); 21 | let assemblyFormat = "$inputs attr-dict `:` type($result)"; 22 | } 23 | 24 | def SubOp : ToyOp<"sub", [Pure, SameOperandsAndResultType]> { 25 | let summary = "sub operation"; 26 | let arguments = (ins AnyInteger:$lhs, AnyInteger:$rhs); 27 | let results = (outs AnyInteger:$result); 28 | let assemblyFormat = "$lhs `,` $rhs attr-dict `:` type($result)"; 29 | } 30 | 31 | def ConstantOp : ToyOp<"const", [Pure, InferTypeOpAdaptor]> { 32 | let summary = "const operation"; 33 | let arguments = (ins APIntAttr:$value); 34 | let results = (outs AnyInteger:$result); 35 | let assemblyFormat = "$value attr-dict"; 36 | let extraClassDeclaration = [{ 37 | int64_t getBitWidth() { 38 | return getResult().getType().getWidth(); 39 | } 40 | }]; 41 | } 42 | 43 | // 另一种实现 44 | // 45 | // def ConstantOp : ToyOp<"const", [Pure, InferTypeOpInterface]> { 46 | // let summary = "const operation"; 47 | // let arguments = (ins APIntAttr:$value); 48 | // let results = (outs AnyInteger:$result); 49 | // let assemblyFormat = "$value attr-dict"; 50 | // let extraClassDeclaration = [{ 51 | // static mlir::LogicalResult inferReturnTypes( 52 | // mlir::MLIRContext * context, 53 | // std::optional<::mlir::Location> location, 54 | // mlir::ValueRange operands, 55 | // mlir::DictionaryAttr attributes, 56 | // mlir::OpaqueProperties properties, 57 | // mlir::RegionRange regions, 58 | // llvm::SmallVectorImpl<::mlir::Type>& inferredReturnTypes 59 | // ); 60 | // }]; 61 | // } 62 | 63 | def ReturnOp : ToyOp<"ret", [Terminator, ReturnLike]> { 64 | let summary = "return operation"; 65 | let arguments = (ins AnyType:$data); 66 | let assemblyFormat = "$data attr-dict `:` type($data)"; 67 | } 68 | 69 | def FuncOp : ToyOp<"func", [ 70 | IsolatedFromAbove, 71 | FunctionOpInterface, 72 | /* Symbol, */ /* Symbol 会自动被 FunctionOpInterface 加上 */ 73 | /* CallableOpInterface, */ /* CallOpInterface 会自动被 FunctionOpInterface 加上 */ 74 | RegionKindInterface]> { 75 | let summary = "function"; 76 | let arguments = (ins 77 | SymbolNameAttr:$sym_name, 78 | TypeAttrOf:$function_type, 79 | // FunctionOpInterface 需要两个 Attr 来记录 arg 和 res 的名字 80 | OptionalAttr:$arg_attrs, 81 | OptionalAttr:$res_attrs 82 | ); 83 | dag regions = (region AnyRegion:$body); 84 | let hasCustomAssemblyFormat = true; 85 | let extraClassDeclaration = [{ 86 | // Method of FunctionOpInterface 87 | mlir::Region * getCallableRegion() {return &getBody();} 88 | // getFunctionType 函数会自动生成 89 | // mlir::FunctionType getFunctionType(); 90 | 91 | // Method of CallableOpInterface 92 | llvm::ArrayRef getArgumentTypes() {return getFunctionType().getInputs();} 93 | llvm::ArrayRef getResultTypes() {return getFunctionType().getResults();} 94 | 95 | // Method of RegionKindInterface 96 | static mlir::RegionKind getRegionKind(unsigned idx) { return mlir::RegionKind::SSACFG; } 97 | }]; 98 | } 99 | 100 | def CallOp : ToyOp<"call", [CallOpInterface]> { 101 | let summary = "call operation"; 102 | let arguments = (ins SymbolRefAttr:$callee, Variadic:$arg_operands); 103 | let results = (outs AnyType:$result); 104 | let assemblyFormat = "$callee `(` $arg_operands `)` attr-dict `:` functional-type($arg_operands, results)"; 105 | let extraClassDeclaration = [{ 106 | mlir::CallInterfaceCallable getCallableForCallee() { return getCalleeAttr(); } 107 | void setCalleeFromCallable(mlir::CallInterfaceCallable callee) { setCalleeAttr(callee.get()); } 108 | }]; 109 | } 110 | 111 | #endif 112 | -------------------------------------------------------------------------------- /ex6-pattern/include/toy/ToyPasses.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mlir/Pass/Pass.h" 4 | #include "mlir/Pass/PassRegistry.h" 5 | #include "toy/ToyOps.h" 6 | #include 7 | 8 | namespace toy { 9 | 10 | #define GEN_PASS_DECL 11 | #include "toy/ToyPasses.h.inc" 12 | 13 | std::unique_ptr createConvertToyToArithPass(ConvertToyToArithOptions options={}); 14 | std::unique_ptr createDCEPass(); 15 | 16 | #define GEN_PASS_REGISTRATION 17 | #include "toy/ToyPasses.h.inc" 18 | 19 | } -------------------------------------------------------------------------------- /ex6-pattern/include/toy/ToyPasses.td: -------------------------------------------------------------------------------- 1 | #ifndef TOY_PASSES_TD 2 | #define TOY_PASSES_TD 3 | 4 | include "mlir/Pass/PassBase.td" 5 | 6 | def ConvertToyToArith : Pass<"convert-toy-to-arith"> { 7 | let summary = "Convert Toy To Arith"; 8 | let constructor = "toy::createConvertToyToArithPass()"; 9 | let options = [ 10 | Option<"name", "name", "std::string", "", "help"> 11 | ]; 12 | } 13 | 14 | def DCE : Pass<"toy-dce", "toy::FuncOp"> { 15 | let summary = "dce"; 16 | let constructor = "toy::createDCEPass()"; 17 | } 18 | #endif 19 | -------------------------------------------------------------------------------- /ex6-pattern/lib/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_mlir_library(Toy toy.cpp DEPENDS MLIRToyIncGen) 2 | add_subdirectory(Transforms) -------------------------------------------------------------------------------- /ex6-pattern/lib/Transforms/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_mlir_library( 2 | ToyTransforms 3 | ConvertToyToArith.cpp 4 | DCE.cpp 5 | 6 | DEPENDS MLIRToyTransformsIncGen 7 | 8 | LINK_LIBS MLIRArithDialect 9 | MLIRArithToLLVM 10 | # MLIRLLVMDialect 11 | ) -------------------------------------------------------------------------------- /ex6-pattern/lib/Transforms/ConvertToyToArith.cpp: -------------------------------------------------------------------------------- 1 | #include "mlir/IR/BuiltinDialect.h" 2 | #include "mlir/IR/PatternMatch.h" 3 | #include "mlir/Support/LogicalResult.h" 4 | #include "toy/ToyDialect.h" 5 | #include "toy/ToyOps.h" 6 | #include "mlir/Dialect/Arith/IR/Arith.h" 7 | // #include "mlir/Conversion/ArithToLLVM/ArithToLLVM.h" 8 | // #include "mlir/Conversion/LLVMCommon/TypeConverter.h" 9 | // #include "mlir/Dialect/LLVMIR/LLVMDialect.h" 10 | #include "llvm/ADT/SmallVector.h" 11 | #include "llvm/Support/raw_ostream.h" 12 | #include "mlir/Transforms/GreedyPatternRewriteDriver.h" 13 | #define GEN_PASS_DEF_CONVERTTOYTOARITH 14 | #include "toy/ToyPasses.h" 15 | 16 | #include "mlir/Transforms/DialectConversion.h" 17 | 18 | using namespace mlir; 19 | using namespace llvm; 20 | using namespace toy; 21 | 22 | struct AddOpPat: OpRewritePattern { 23 | using OpRewritePattern::OpRewritePattern; 24 | LogicalResult matchAndRewrite(AddOp op, PatternRewriter & rewriter) const { 25 | auto inputs = to_vector(op.getInputs()); 26 | auto result = inputs[0]; 27 | for(size_t i = 1; i< inputs.size(); i++) { 28 | result = rewriter.create(op->getLoc(), result, inputs[i]); 29 | } 30 | rewriter.replaceOp(op, ValueRange(result)); 31 | return success(); 32 | } 33 | }; 34 | 35 | struct SubOpPat: OpRewritePattern { 36 | using OpRewritePattern::OpRewritePattern; 37 | LogicalResult matchAndRewrite(SubOp op, PatternRewriter & rewriter) const { 38 | rewriter.replaceOpWithNewOp(op, op.getLhs(), op.getRhs()); 39 | return success(); 40 | } 41 | }; 42 | 43 | struct ConstantOpPat: OpRewritePattern { 44 | using OpRewritePattern::OpRewritePattern; 45 | LogicalResult matchAndRewrite(ConstantOp op, PatternRewriter & rewriter) const { 46 | rewriter.replaceOpWithNewOp(op, op.getValueAttr()); 47 | return success(); 48 | } 49 | }; 50 | 51 | struct ConvertToyToArithPass : toy::impl::ConvertToyToArithBase { 52 | using toy::impl::ConvertToyToArithBase::ConvertToyToArithBase; 53 | void getDependentDialects(DialectRegistry ®istry) const final { 54 | registry.insert(); 55 | // registry.insert(); 56 | } 57 | void runOnOperation() final { 58 | ConversionTarget target(getContext()); 59 | target.addLegalDialect(); 60 | // target.addLegalDialect(); 61 | // LLVMTypeConverter converter(&getContext()); 62 | RewritePatternSet patterns(&getContext()); 63 | patterns.add(&getContext()); 64 | // arith::populateArithToLLVMConversionPatterns(converter, patterns); 65 | if(failed(applyPartialConversion(getOperation(), target, std::move(patterns)))) 66 | signalPassFailure(); 67 | } 68 | }; 69 | 70 | std::unique_ptr toy::createConvertToyToArithPass(ConvertToyToArithOptions options) { 71 | return std::make_unique(options); 72 | } 73 | -------------------------------------------------------------------------------- /ex6-pattern/lib/Transforms/DCE.cpp: -------------------------------------------------------------------------------- 1 | #include "llvm/ADT/STLExtras.h" 2 | #include "llvm/ADT/SmallVector.h" 3 | #include "llvm/Support/raw_ostream.h" 4 | #define GEN_PASS_DEF_DCE 5 | #include "toy/ToyPasses.h" 6 | 7 | using namespace mlir; 8 | using namespace llvm; 9 | using namespace toy; 10 | 11 | struct DCEPass : toy::impl::DCEBase { 12 | void visitAll(llvm::DenseSet &visited, Operation * op) { 13 | if(visited.contains(op)) return; 14 | visited.insert(op); 15 | for(auto operand: op->getOperands()) 16 | if(auto def = operand.getDefiningOp()) 17 | visitAll(visited, def); 18 | } 19 | void runOnOperation() final { 20 | llvm::DenseSet visited; 21 | getOperation()->walk([&](toy::ReturnOp op) { 22 | visitAll(visited, op); 23 | }); 24 | llvm::SmallVector opToRemove; 25 | getOperation().walk([&](Operation * op) { 26 | if(op == getOperation()) return; 27 | if(!visited.contains(op)) opToRemove.push_back(op); 28 | }); 29 | for(auto v: reverse(opToRemove)) { 30 | v->erase(); 31 | } 32 | } 33 | }; 34 | 35 | std::unique_ptr toy::createDCEPass() { 36 | return std::make_unique(); 37 | } 38 | -------------------------------------------------------------------------------- /ex6-pattern/lib/toy.cpp: -------------------------------------------------------------------------------- 1 | #include "mlir/IR/BuiltinAttributes.h" 2 | #include "mlir/IR/BuiltinTypes.h" 3 | #include "mlir/Interfaces/CallInterfaces.h" 4 | #include "mlir/Support/LogicalResult.h" 5 | #include "mlir/Interfaces/FunctionImplementation.h" 6 | #include "toy/ToyDialect.h" 7 | #include "toy/ToyOps.h" 8 | 9 | #include "toy/ToyDialect.cpp.inc" 10 | #define GET_OP_CLASSES 11 | #include "toy/Toy.cpp.inc" 12 | 13 | using namespace mlir; 14 | using namespace toy; 15 | 16 | void ToyDialect::initialize() { 17 | addOperations< 18 | #define GET_OP_LIST 19 | #include "toy/Toy.cpp.inc" 20 | >(); 21 | } 22 | 23 | // mlir::LogicalResult ConstantOp::inferReturnTypes( 24 | // mlir::MLIRContext * context, 25 | // std::optional<::mlir::Location> location, 26 | // mlir::ValueRange operands, 27 | // mlir::DictionaryAttr attributes, 28 | // mlir::OpaqueProperties properties, 29 | // mlir::RegionRange regions, 30 | // llvm::SmallVectorImpl<::mlir::Type>& inferredReturnTypes 31 | // ) { 32 | // ConstantOp::Adaptor adaptor(operands, attributes, properties, regions); 33 | // inferredReturnTypes.push_back(adaptor.getValueAttr().getType()); 34 | // return success(); 35 | // } 36 | 37 | mlir::LogicalResult ConstantOp::inferReturnTypes( 38 | mlir::MLIRContext * context, 39 | std::optional location, 40 | Adaptor adaptor, 41 | llvm::SmallVectorImpl & inferedReturnType 42 | ) { 43 | inferedReturnType.push_back(adaptor.getValueAttr().getType()); 44 | return mlir::success(); 45 | } 46 | 47 | mlir::ParseResult FuncOp::parse(::mlir::OpAsmParser &parser, ::mlir::OperationState &result) { 48 | auto buildFuncType = [](auto & builder, auto argTypes, auto results, auto, auto) { 49 | return builder.getFunctionType(argTypes, results); 50 | }; 51 | return function_interface_impl::parseFunctionOp( 52 | parser, result, false, 53 | getFunctionTypeAttrName(result.name), buildFuncType, 54 | getArgAttrsAttrName(result.name), getResAttrsAttrName(result.name) 55 | ); 56 | } 57 | 58 | void FuncOp::print(mlir::OpAsmPrinter &p) { 59 | // Dispatch to the FunctionOpInterface provided utility method that prints the 60 | // function operation. 61 | mlir::function_interface_impl::printFunctionOp( 62 | p, *this, /*isVariadic=*/false, getFunctionTypeAttrName(), 63 | getArgAttrsAttrName(), getResAttrsAttrName()); 64 | } 65 | -------------------------------------------------------------------------------- /ex6-pattern/tools/toy-opt/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_mlir_tool(ex6-opt toy-opt.cpp) 2 | target_link_libraries(ex6-opt 3 | PRIVATE Toy 4 | MLIRIR MLIRParser MLIRSupport 5 | MLIROptLib # 对应 #include "mlir/Tools/mlir-opt/MlirOptMain.h" 6 | MLIRFuncDialect # 对应 #include "mlir/Dialect/Func/IR/FuncOps.h" 7 | MLIRTransforms # 对应 #include "mlir/Transforms/Passes.h" 8 | ToyTransforms 9 | ) -------------------------------------------------------------------------------- /ex6-pattern/tools/toy-opt/toy-opt.cpp: -------------------------------------------------------------------------------- 1 | #include "mlir/Dialect/Arith/IR/Arith.h" 2 | #include "mlir/Dialect/LLVMIR/LLVMTypes.h" 3 | #include "mlir/IR/DialectRegistry.h" 4 | #include "mlir/Tools/mlir-opt/MlirOptMain.h" 5 | // #include "mlir/Dialect/LLVMIR/LLVMDialect.h" 6 | // 导入 Func Dialect 7 | #include "mlir/Dialect/Func/IR/FuncOps.h" 8 | // 导入 MLIR 自带 Pass 9 | #include "mlir/Transforms/Passes.h" 10 | // 导入我们新建的 Dialect 11 | #include "toy/ToyDialect.h" 12 | #include "toy/ToyPasses.h" 13 | using namespace mlir; 14 | using namespace llvm; 15 | 16 | int main(int argc, char ** argv) { 17 | DialectRegistry registry; 18 | // 注册 Dialect 19 | registry.insert(); 20 | // registry.insert(); 21 | // 注册两个 Pass 22 | registerCSEPass(); 23 | registerCanonicalizerPass(); 24 | toy::registerPasses(); 25 | return asMainReturnCode(MlirOptMain(argc, argv, "toy-opt", registry)); 26 | } 27 | -------------------------------------------------------------------------------- /ex7-convert/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | set(LLVM_RUNTIME_OUTPUT_INTDIR ${CMAKE_BINARY_DIR}) 4 | 5 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) 6 | include_directories(${CMAKE_CURRENT_BINARY_DIR}/include) 7 | add_subdirectory(include/toy) 8 | add_subdirectory(lib) 9 | add_subdirectory(tools/toy-opt) -------------------------------------------------------------------------------- /ex7-convert/ex7.mlir: -------------------------------------------------------------------------------- 1 | !t32 = !toy.int<32> 2 | 3 | toy.func @add(%a: !t32, %b: !t32) -> !t32 { 4 | %c = toy.add %a, %b : !t32 5 | toy.ret %c : !t32 6 | } 7 | 8 | toy.func @test(%a: !t32, %b: !t32) -> !t32 { 9 | %c = toy.call @add(%a, %b) : (!t32, !t32) -> !t32 10 | %d = toy.add %a, %b : !t32 11 | %f = toy.add %d, %d : !t32 12 | toy.ret %f : !t32 13 | } -------------------------------------------------------------------------------- /ex7-convert/include/toy/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_mlir_dialect(Toy toy) 2 | 3 | 4 | set(LLVM_TARGET_DEFINITIONS ToyPasses.td) 5 | mlir_tablegen(ToyPasses.h.inc -gen-pass-decls) 6 | add_public_tablegen_target(MLIRToyTransformsIncGen) 7 | 8 | add_custom_target(header DEPENDS MLIRToyIncGen MLIRToyTransformsIncGen) -------------------------------------------------------------------------------- /ex7-convert/include/toy/Toy.td: -------------------------------------------------------------------------------- 1 | #ifndef TOY_TD 2 | #define TOY_TD 3 | 4 | include "toy/ToyDialect.td" 5 | include "toy/ToyOps.td" 6 | include "toy/ToyTypes.td" 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /ex7-convert/include/toy/ToyDialect.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mlir/IR/BuiltinDialect.h" 4 | #include "toy/ToyDialect.h.inc" 5 | -------------------------------------------------------------------------------- /ex7-convert/include/toy/ToyDialect.td: -------------------------------------------------------------------------------- 1 | #ifndef TOY_DIALECT_TD 2 | #define TOY_DIALECT_TD 3 | 4 | include "mlir/IR/OpBase.td" 5 | 6 | def ToyDialect : Dialect { 7 | let name = "toy"; 8 | let cppNamespace = "::toy"; 9 | let summary = "Toy Dialect"; 10 | let useDefaultTypePrinterParser = true; 11 | let extraClassDeclaration = [{ 12 | void registerTypes(); 13 | }]; 14 | } 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /ex7-convert/include/toy/ToyOps.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mlir/IR/BuiltinOps.h" 4 | #include "mlir/IR/Builders.h" 5 | 6 | #include "toy/ToyDialect.h" 7 | #include "toy/ToyTypes.h" 8 | #include "mlir/Interfaces/InferTypeOpInterface.h" 9 | #include "mlir/Interfaces/SideEffectInterfaces.h" 10 | #include "mlir/Interfaces/ControlFlowInterfaces.h" 11 | #include "mlir/Interfaces/FunctionInterfaces.h" 12 | #include "mlir/IR/RegionKindInterface.h" 13 | 14 | #define GET_OP_CLASSES 15 | #include "toy/Toy.h.inc" -------------------------------------------------------------------------------- /ex7-convert/include/toy/ToyOps.td: -------------------------------------------------------------------------------- 1 | #ifndef TOY_OPS_TD 2 | #define TOY_OPS_TD 3 | 4 | include "mlir/IR/OpBase.td" 5 | include "toy/ToyDialect.td" 6 | include "toy/ToyTypes.td" 7 | include "mlir/Interfaces/InferTypeOpInterface.td" 8 | include "mlir/Interfaces/SideEffectInterfaces.td" 9 | include "mlir/Interfaces/FunctionInterfaces.td" 10 | include "mlir/Interfaces/ControlFlowInterfaces.td" 11 | include "mlir/IR/RegionKindInterface.td" 12 | 13 | // mnemonic 指名字 14 | class ToyOp traits = []> : 15 | Op; 16 | 17 | // Pure 是 Trait,表示没有 SideEffect 的纯函数 18 | def AddOp : ToyOp<"add", [Pure, SameOperandsAndResultType]> { 19 | let summary = "add operation"; 20 | let arguments = (ins Variadic:$inputs); 21 | let results = (outs ToyInteger:$result); 22 | let assemblyFormat = "$inputs attr-dict `:` type($result)"; 23 | } 24 | 25 | def SubOp : ToyOp<"sub", [Pure, SameOperandsAndResultType]> { 26 | let summary = "sub operation"; 27 | let arguments = (ins ToyInteger:$lhs, ToyInteger:$rhs); 28 | let results = (outs ToyInteger:$result); 29 | let assemblyFormat = "$lhs `,` $rhs attr-dict `:` type($result)"; 30 | } 31 | 32 | def ConstantOp : ToyOp<"const", [Pure, InferTypeOpAdaptor]> { 33 | let summary = "const operation"; 34 | let arguments = (ins APIntAttr:$value); 35 | let results = (outs ToyInteger:$result); 36 | let assemblyFormat = "$value attr-dict"; 37 | let extraClassDeclaration = [{ 38 | int64_t getBitWidth() { 39 | return getResult().getType().getWidth(); 40 | } 41 | }]; 42 | } 43 | 44 | // 另一种实现 45 | // 46 | // def ConstantOp : ToyOp<"const", [Pure, InferTypeOpInterface]> { 47 | // let summary = "const operation"; 48 | // let arguments = (ins APIntAttr:$value); 49 | // let results = (outs AnyInteger:$result); 50 | // let assemblyFormat = "$value attr-dict"; 51 | // let extraClassDeclaration = [{ 52 | // static mlir::LogicalResult inferReturnTypes( 53 | // mlir::MLIRContext * context, 54 | // std::optional<::mlir::Location> location, 55 | // mlir::ValueRange operands, 56 | // mlir::DictionaryAttr attributes, 57 | // mlir::OpaqueProperties properties, 58 | // mlir::RegionRange regions, 59 | // llvm::SmallVectorImpl<::mlir::Type>& inferredReturnTypes 60 | // ); 61 | // }]; 62 | // } 63 | 64 | def ReturnOp : ToyOp<"ret", [Terminator, ReturnLike]> { 65 | let summary = "return operation"; 66 | let arguments = (ins AnyType:$data); 67 | let assemblyFormat = "$data attr-dict `:` type($data)"; 68 | } 69 | 70 | def FuncOp : ToyOp<"func", [ 71 | IsolatedFromAbove, 72 | FunctionOpInterface, 73 | /* Symbol, */ /* Symbol 会自动被 FunctionOpInterface 加上 */ 74 | /* CallableOpInterface, */ /* CallOpInterface 会自动被 FunctionOpInterface 加上 */ 75 | RegionKindInterface]> { 76 | let summary = "function"; 77 | let arguments = (ins 78 | SymbolNameAttr:$sym_name, 79 | TypeAttrOf:$function_type, 80 | // FunctionOpInterface 需要两个 Attr 来记录 arg 和 res 的名字 81 | OptionalAttr:$arg_attrs, 82 | OptionalAttr:$res_attrs 83 | ); 84 | dag regions = (region AnyRegion:$body); 85 | let hasCustomAssemblyFormat = true; 86 | let extraClassDeclaration = [{ 87 | // Method of FunctionOpInterface 88 | mlir::Region * getCallableRegion() {return &getBody();} 89 | // getFunctionType 函数会自动生成 90 | // mlir::FunctionType getFunctionType(); 91 | 92 | // Method of CallableOpInterface 93 | llvm::ArrayRef getArgumentTypes() {return getFunctionType().getInputs();} 94 | llvm::ArrayRef getResultTypes() {return getFunctionType().getResults();} 95 | 96 | // Method of RegionKindInterface 97 | static mlir::RegionKind getRegionKind(unsigned idx) { return mlir::RegionKind::SSACFG; } 98 | }]; 99 | } 100 | 101 | def CallOp : ToyOp<"call", [CallOpInterface]> { 102 | let summary = "call operation"; 103 | let arguments = (ins SymbolRefAttr:$callee, Variadic:$arg_operands); 104 | let results = (outs AnyType:$result); 105 | let assemblyFormat = "$callee `(` $arg_operands `)` attr-dict `:` functional-type($arg_operands, results)"; 106 | let extraClassDeclaration = [{ 107 | mlir::CallInterfaceCallable getCallableForCallee() { return getCalleeAttr(); } 108 | void setCalleeFromCallable(mlir::CallInterfaceCallable callee) { setCalleeAttr(callee.get()); } 109 | }]; 110 | } 111 | 112 | #endif 113 | -------------------------------------------------------------------------------- /ex7-convert/include/toy/ToyPasses.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mlir/Pass/Pass.h" 4 | #include "mlir/Pass/PassRegistry.h" 5 | #include "toy/ToyOps.h" 6 | #include 7 | 8 | namespace toy { 9 | 10 | #define GEN_PASS_DECL 11 | #include "toy/ToyPasses.h.inc" 12 | 13 | std::unique_ptr createConvertToyToArithPass(ConvertToyToArithOptions options={}); 14 | std::unique_ptr createDCEPass(); 15 | 16 | #define GEN_PASS_REGISTRATION 17 | #include "toy/ToyPasses.h.inc" 18 | 19 | } -------------------------------------------------------------------------------- /ex7-convert/include/toy/ToyPasses.td: -------------------------------------------------------------------------------- 1 | #ifndef TOY_PASSES_TD 2 | #define TOY_PASSES_TD 3 | 4 | include "mlir/Pass/PassBase.td" 5 | 6 | def ConvertToyToArith : Pass<"convert-toy-to-arith"> { 7 | let summary = "Convert Toy To Arith"; 8 | let constructor = "toy::createConvertToyToArithPass()"; 9 | let options = [ 10 | Option<"name", "name", "std::string", "", "help"> 11 | ]; 12 | } 13 | 14 | def DCE : Pass<"toy-dce", "toy::FuncOp"> { 15 | let summary = "dce"; 16 | let constructor = "toy::createDCEPass()"; 17 | } 18 | #endif 19 | -------------------------------------------------------------------------------- /ex7-convert/include/toy/ToyTypes.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "toy/ToyDialect.h" 4 | #include "mlir/IR/BuiltinTypes.h" 5 | #include "mlir/IR/Types.h" 6 | 7 | #include "llvm/Support/MathExtras.h" 8 | 9 | #define GET_TYPEDEF_CLASSES 10 | #include "toy/ToyTypes.h.inc" -------------------------------------------------------------------------------- /ex7-convert/include/toy/ToyTypes.td: -------------------------------------------------------------------------------- 1 | #ifndef TOY_TYPES_TD 2 | #define TOY_TYPES_TD 3 | 4 | include "toy/ToyDialect.td" 5 | include "mlir/IR/AttrTypeBase.td" 6 | 7 | class ToyType traits=[]>: TypeDef; 8 | 9 | def ToyInteger: ToyType<"ToyInteger"> { 10 | let mnemonic = "int"; 11 | let parameters = (ins "uint64_t":$width); 12 | let assemblyFormat = "`<` $width `>`"; 13 | } 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /ex7-convert/lib/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_mlir_library(Toy toy.cpp DEPENDS MLIRToyIncGen) 2 | add_subdirectory(Transforms) -------------------------------------------------------------------------------- /ex7-convert/lib/Transforms/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_mlir_library( 2 | ToyTransforms 3 | ConvertToyToArith.cpp 4 | DCE.cpp 5 | 6 | DEPENDS MLIRToyTransformsIncGen 7 | 8 | LINK_LIBS MLIRArithDialect 9 | MLIRArithToLLVM 10 | # MLIRLLVMDialect 11 | ) -------------------------------------------------------------------------------- /ex7-convert/lib/Transforms/ConvertToyToArith.cpp: -------------------------------------------------------------------------------- 1 | #include "mlir/IR/BuiltinDialect.h" 2 | #include "mlir/IR/BuiltinOps.h" 3 | #include "mlir/IR/BuiltinTypes.h" 4 | #include "mlir/IR/PatternMatch.h" 5 | #include "mlir/Support/LogicalResult.h" 6 | #include "toy/ToyDialect.h" 7 | #include "toy/ToyOps.h" 8 | #include "toy/ToyTypes.h" 9 | #include "mlir/Dialect/Arith/IR/Arith.h" 10 | // #include "mlir/Conversion/ArithToLLVM/ArithToLLVM.h" 11 | // #include "mlir/Conversion/LLVMCommon/TypeConverter.h" 12 | // #include "mlir/Dialect/LLVMIR/LLVMDialect.h" 13 | #include "llvm/ADT/STLExtras.h" 14 | #include "llvm/ADT/SmallVector.h" 15 | #include "llvm/Support/raw_ostream.h" 16 | #include "mlir/Transforms/GreedyPatternRewriteDriver.h" 17 | #define GEN_PASS_DEF_CONVERTTOYTOARITH 18 | #include "toy/ToyPasses.h" 19 | 20 | #include "mlir/Transforms/DialectConversion.h" 21 | 22 | using namespace mlir; 23 | using namespace llvm; 24 | using namespace toy; 25 | 26 | struct AddOpPat: OpConversionPattern { 27 | using OpConversionPattern::OpConversionPattern; 28 | LogicalResult matchAndRewrite(AddOp op, AddOpAdaptor adaptor, ConversionPatternRewriter & rewriter) const { 29 | auto inputs = to_vector(adaptor.getInputs()); 30 | auto result = inputs[0]; 31 | for(size_t i = 1; i< inputs.size(); i++) { 32 | assert(inputs[i]); 33 | result = rewriter.create(op->getLoc(), result, inputs[i]); 34 | } 35 | rewriter.replaceOp(op, ValueRange(result)); 36 | return success(); 37 | } 38 | }; 39 | 40 | struct SubOpPat: OpConversionPattern { 41 | using OpConversionPattern::OpConversionPattern; 42 | LogicalResult matchAndRewrite(SubOp op, SubOpAdaptor adaptor, ConversionPatternRewriter & rewriter) const { 43 | rewriter.replaceOpWithNewOp(op, adaptor.getLhs(), adaptor.getRhs()); 44 | return success(); 45 | } 46 | }; 47 | 48 | struct ConstantOpPat: OpConversionPattern { 49 | using OpConversionPattern::OpConversionPattern; 50 | LogicalResult matchAndRewrite(ConstantOp op, ConstantOpAdaptor adaptor, ConversionPatternRewriter & rewriter) const { 51 | rewriter.replaceOpWithNewOp(op, op.getValueAttr()); 52 | return success(); 53 | } 54 | }; 55 | 56 | struct ReturnOpPat: OpConversionPattern { 57 | using OpConversionPattern::OpConversionPattern; 58 | LogicalResult matchAndRewrite(ReturnOp op, ReturnOpAdaptor adaptor, ConversionPatternRewriter & rewriter) const { 59 | auto data = adaptor.getData(); 60 | rewriter.startRootUpdate(op); 61 | op.getDataMutable().assign(data); 62 | rewriter.finalizeRootUpdate(op); 63 | return success(); 64 | } 65 | }; 66 | 67 | struct CallOpPat: OpConversionPattern { 68 | using OpConversionPattern::OpConversionPattern; 69 | LogicalResult matchAndRewrite(CallOp op, CallOpAdaptor adaptor, ConversionPatternRewriter & rewriter) const { 70 | SmallVector resTypes; 71 | assert(succeeded(getTypeConverter()->convertTypes(op->getResultTypes(), resTypes))); 72 | rewriter.replaceOpWithNewOp(op, resTypes, op.getCallee(), adaptor.getOperands()); 73 | return success(); 74 | } 75 | }; 76 | 77 | struct ConvertToyToArithPass : toy::impl::ConvertToyToArithBase { 78 | using toy::impl::ConvertToyToArithBase::ConvertToyToArithBase; 79 | void getDependentDialects(DialectRegistry ®istry) const final { 80 | registry.insert(); 81 | } 82 | void runOnOperation() final { 83 | ConversionTarget target(getContext()); 84 | target.addLegalDialect(); 85 | // target.addDynamicallyLegalOp([](FuncOp f) { 86 | // return llvm::all_of(f.getArgumentTypes(), [](Type t) {return !isa(t);}); 87 | // }); 88 | auto checkValid = [](Operation* f) { 89 | return llvm::all_of(f->getOperandTypes(), [](Type t) {return !isa(t);}); 90 | }; 91 | target.addDynamicallyLegalOp(checkValid); 92 | TypeConverter converter; 93 | converter.addConversion([&](ToyIntegerType t) -> std::optional { 94 | return IntegerType::get(&getContext(), t.getWidth()); 95 | }); 96 | converter.addTargetMaterialization([](OpBuilder& builder, Type resultType, ValueRange inputs, Location loc) -> std::optional { 97 | return builder.create(loc, resultType, inputs).getResult(0); 98 | }); 99 | RewritePatternSet patterns(&getContext()); 100 | patterns.add(converter, &getContext()); 101 | populateFunctionOpInterfaceTypeConversionPattern(patterns, converter); 102 | if(failed(applyPartialConversion(getOperation(), target, std::move(patterns)))) 103 | signalPassFailure(); 104 | } 105 | }; 106 | 107 | std::unique_ptr toy::createConvertToyToArithPass(ConvertToyToArithOptions options) { 108 | return std::make_unique(options); 109 | } 110 | -------------------------------------------------------------------------------- /ex7-convert/lib/Transforms/DCE.cpp: -------------------------------------------------------------------------------- 1 | #include "llvm/ADT/STLExtras.h" 2 | #include "llvm/ADT/SmallVector.h" 3 | #include "llvm/Support/raw_ostream.h" 4 | #define GEN_PASS_DEF_DCE 5 | #include "toy/ToyPasses.h" 6 | 7 | using namespace mlir; 8 | using namespace llvm; 9 | using namespace toy; 10 | 11 | struct DCEPass : toy::impl::DCEBase { 12 | void visitAll(llvm::DenseSet &visited, Operation * op) { 13 | if(visited.contains(op)) return; 14 | visited.insert(op); 15 | for(auto operand: op->getOperands()) 16 | if(auto def = operand.getDefiningOp()) 17 | visitAll(visited, def); 18 | } 19 | void runOnOperation() final { 20 | llvm::DenseSet visited; 21 | getOperation()->walk([&](toy::ReturnOp op) { 22 | visitAll(visited, op); 23 | }); 24 | llvm::SmallVector opToRemove; 25 | getOperation().walk([&](Operation * op) { 26 | if(op == getOperation()) return; 27 | if(!visited.contains(op)) opToRemove.push_back(op); 28 | }); 29 | for(auto v: reverse(opToRemove)) { 30 | v->erase(); 31 | } 32 | } 33 | }; 34 | 35 | std::unique_ptr toy::createDCEPass() { 36 | return std::make_unique(); 37 | } 38 | -------------------------------------------------------------------------------- /ex7-convert/lib/toy.cpp: -------------------------------------------------------------------------------- 1 | #include "mlir/IR/BuiltinAttributes.h" 2 | #include "mlir/IR/BuiltinTypes.h" 3 | #include "mlir/Interfaces/CallInterfaces.h" 4 | #include "mlir/Support/LogicalResult.h" 5 | #include "mlir/Interfaces/FunctionImplementation.h" 6 | #include "toy/ToyDialect.h" 7 | #include "toy/ToyOps.h" 8 | 9 | #include "toy/ToyDialect.cpp.inc" 10 | #define GET_OP_CLASSES 11 | #include "toy/Toy.cpp.inc" 12 | 13 | #include "toy/ToyTypes.h" 14 | #include "mlir/IR/DialectImplementation.h" 15 | #include "llvm/ADT/StringExtras.h" 16 | #include "llvm/ADT/StringSwitch.h" 17 | #include "llvm/ADT/TypeSwitch.h" 18 | #define GET_TYPEDEF_CLASSES 19 | #include "toy/ToyTypes.cpp.inc" 20 | 21 | using namespace mlir; 22 | using namespace toy; 23 | 24 | void ToyDialect::initialize() { 25 | addOperations< 26 | #define GET_OP_LIST 27 | #include "toy/Toy.cpp.inc" 28 | >(); 29 | registerTypes(); 30 | } 31 | 32 | void ToyDialect::registerTypes() { 33 | addTypes< 34 | #define GET_TYPEDEF_LIST 35 | #include "toy/ToyTypes.cpp.inc" 36 | >(); 37 | } 38 | 39 | // mlir::LogicalResult ConstantOp::inferReturnTypes( 40 | // mlir::MLIRContext * context, 41 | // std::optional<::mlir::Location> location, 42 | // mlir::ValueRange operands, 43 | // mlir::DictionaryAttr attributes, 44 | // mlir::OpaqueProperties properties, 45 | // mlir::RegionRange regions, 46 | // llvm::SmallVectorImpl<::mlir::Type>& inferredReturnTypes 47 | // ) { 48 | // ConstantOp::Adaptor adaptor(operands, attributes, properties, regions); 49 | // inferredReturnTypes.push_back(adaptor.getValueAttr().getType()); 50 | // return success(); 51 | // } 52 | 53 | mlir::LogicalResult ConstantOp::inferReturnTypes( 54 | mlir::MLIRContext * context, 55 | std::optional location, 56 | Adaptor adaptor, 57 | llvm::SmallVectorImpl & inferedReturnType 58 | ) { 59 | inferedReturnType.push_back(adaptor.getValueAttr().getType()); 60 | return mlir::success(); 61 | } 62 | 63 | mlir::ParseResult FuncOp::parse(::mlir::OpAsmParser &parser, ::mlir::OperationState &result) { 64 | auto buildFuncType = [](auto & builder, auto argTypes, auto results, auto, auto) { 65 | return builder.getFunctionType(argTypes, results); 66 | }; 67 | return function_interface_impl::parseFunctionOp( 68 | parser, result, false, 69 | getFunctionTypeAttrName(result.name), buildFuncType, 70 | getArgAttrsAttrName(result.name), getResAttrsAttrName(result.name) 71 | ); 72 | } 73 | 74 | void FuncOp::print(mlir::OpAsmPrinter &p) { 75 | // Dispatch to the FunctionOpInterface provided utility method that prints the 76 | // function operation. 77 | mlir::function_interface_impl::printFunctionOp( 78 | p, *this, /*isVariadic=*/false, getFunctionTypeAttrName(), 79 | getArgAttrsAttrName(), getResAttrsAttrName()); 80 | } 81 | -------------------------------------------------------------------------------- /ex7-convert/tools/toy-opt/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_mlir_tool(ex7-opt toy-opt.cpp) 2 | target_link_libraries(ex7-opt 3 | PRIVATE Toy 4 | MLIRIR MLIRParser MLIRSupport 5 | MLIROptLib # 对应 #include "mlir/Tools/mlir-opt/MlirOptMain.h" 6 | MLIRFuncDialect # 对应 #include "mlir/Dialect/Func/IR/FuncOps.h" 7 | MLIRTransforms # 对应 #include "mlir/Transforms/Passes.h" 8 | ToyTransforms 9 | ) -------------------------------------------------------------------------------- /ex7-convert/tools/toy-opt/toy-opt.cpp: -------------------------------------------------------------------------------- 1 | #include "mlir/Dialect/Arith/IR/Arith.h" 2 | #include "mlir/Dialect/LLVMIR/LLVMTypes.h" 3 | #include "mlir/IR/DialectRegistry.h" 4 | #include "mlir/Tools/mlir-opt/MlirOptMain.h" 5 | // #include "mlir/Dialect/LLVMIR/LLVMDialect.h" 6 | // 导入 Func Dialect 7 | #include "mlir/Dialect/Func/IR/FuncOps.h" 8 | // 导入 MLIR 自带 Pass 9 | #include "mlir/Transforms/Passes.h" 10 | // 导入我们新建的 Dialect 11 | #include "toy/ToyDialect.h" 12 | #include "toy/ToyPasses.h" 13 | using namespace mlir; 14 | using namespace llvm; 15 | 16 | int main(int argc, char ** argv) { 17 | DialectRegistry registry; 18 | // 注册 Dialect 19 | registry.insert(); 20 | // registry.insert(); 21 | // 注册两个 Pass 22 | registerCSEPass(); 23 | registerCanonicalizerPass(); 24 | toy::registerPasses(); 25 | return asMainReturnCode(MlirOptMain(argc, argv, "toy-opt", registry)); 26 | } 27 | -------------------------------------------------------------------------------- /fig/.gitignore: -------------------------------------------------------------------------------- 1 | .$* -------------------------------------------------------------------------------- /fig/MLIR Dialects.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KEKE046/mlir-tutorial/833cd57278d92ba1bb0b627db7cf4e6acc669144/fig/MLIR Dialects.jpg -------------------------------------------------------------------------------- /fig/op.drawio: -------------------------------------------------------------------------------- 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 | 156 | 157 | 158 | 159 | 160 | 161 | -------------------------------------------------------------------------------- /fig/op.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 |
Operation
Operation
OperandsVec
OperandsVec
ResultsVec
ResultsVec
AttributesVec
AttributesVec
Operation*
Operation*
AddOp
AddOp
ConstantOp
ConstantOp
op.getLhs() = this->p->operands[0]
op.getLhs() = this->p->operands[0]
op.getRhs() = this->p->operands[1]
op.getRhs() = this->p->operands[1]
op.getResult() = this->p->results[0]
op.getResult() = this->p->results[0]
op.getValue() = this->p->attributes[0]
op.getValue() = this->p->attributes[0]
op.getResult() = this->p->results[0]
op.getResult() = this->p->results[0]
(*op).getOperands()
(*op).getOperands()
(*op).getResults()
(*op).getResults()
(*op).getAttrDictionary()
(*op).getAttrDictionary()
OpName
OpName
Text is not SVG - cannot display
--------------------------------------------------------------------------------