全文 MLIR TOY -- Chapter2: 發出基本的 MLIR——把AST變成SSA的 MLIR Dialect IR

??? 現在我們已經熟悉 Toy 語言和它的AST表示,現在讓我們看看 MLIR 是怎樣幫助編譯 Toy 源程序的。

簡介:多層中間表示

??????? 其他的編譯器,像 LLVM,是提供一個固定的預定義類型和指令(通常是底層的像 RISC的指令)。對于一個給定的語言,在發出 LLVM IR之前,執行任何的語言特定的類型檢查和分析,或者變換等,是由編譯器前端來決定的。例如Clang 將會使用它的 AST 做靜態分析 和 變換,例如 通過對 AST 的克隆和重寫完成C++ 模版實例化。最后,一個比 C/C++ 還高的層級上且帶有構造特性的語言,從它們的 AST 到生成 LLVM IR 可能需要做一個非常重要的下降。因此,多種前端,導致需要重新實現大量的基礎設施部件,以便能夠支持這些前端的分析和轉換。MLIR 通過把 MLIR 設計得具有可擴展性,來應對這個問題。例如,MLIR 中幾乎沒有預定義的指令(用MLIR的術語,這叫做 operations 操作)和預定義的類型。

與 MLIR 的接口

MLIR 語言參考手冊:https://mlir.llvm.org/docs/LangRef/

??????? MLIR 被設計成為一個完全可伸縮的基礎設施,這里沒有封閉的屬性、操作或類型的集合。MLIR 通過 Dialects這個概念來實現其可擴展性。Dialects 為唯一的命名空間下的抽象提供一組機制。

??????? 在MLIR中,Operations 是進行抽象和計算的核心單元,這在很多方面都挺像 LLVM 中的 instructions。 Operations 可以具有應用特定的語義,也可以用來表示 LLVM 中所有的核心的 IR 結構:instruction、globals(類似 functions)、modules等等。

下面是 Toy 語言中的 transpose operations 的 MLIR 匯編:
?

%t_tensor = "toy.transpose"(%tensor) {inplace = true} : (tensor<2x3xf64>) -> tensor<3x2xf64> loc("example/file/path":12:1)


「注:這里只是聲明了transpose操作,具體的實現要在 Chapter5中通過 lowering 來下降到其進一步的實現細節」?

讓我們來解析一下這個 MLIR operations:

. %t_tensor
給這個 operation 的結果所定義的名字(為了避免沖突,它包含一個前綴)。一個 operation 可能定義一個或者多個結果(在 Toy 的上下文中,我們將限制在不超過一個結果的 operations),這些結果都是 SSA 值。這個名字將會在解析時被使用,但是并不是持久的(這個 SSA 值的內存表示不會被追蹤)。


. "toy.transpose"

operation 的名字。在其中“.”前面的前綴所定義的命名空間中,這個字符串被期待是獨一無二的。這個寫法可以讀作:toy dialect 中的 transpose operation。

. (%tensor)

0個或多個輸入的操作數(或者參數)構成的列表,它們是由其他的 operations 或者對參數 block 的引用所定義的 SSA 值。

. { inplace = true }

0個或多個屬性構成的字典,它們是特殊的操作數,永遠是常數。這里我們定義了一個boolean 類型的命名為 inplace 的屬性,它具有常數值 true。

. (tensor<2x3xf64>) -> tensor<3x2xf64>

這個代表了用函數形式表示的操作類型,圓括號中拼寫出了參數的類型,后邊跟著返回值的類型。

. loc("example/file/path":12:1)

這是這個操作的定義開頭在源碼中的位置。

這里展示了一個操作的通常的形式。如上所述,MLIR 中的操作是可擴展的。我們使用了一個最小組的概念來建模 operations,使得 operations 可以被推導和一般性的修改。這些概念如下:

A name for the operation.
A list of SSA operand values.
A list of attributes.
A list of types for result values.
A source location for debugging purposes.
A list of successors blocks (for branches, mostly).
A list of regions (for structural operations like functions).

??????? 在 MLIR中,每一個 operation 都有一個強制性的源代碼位置與其關聯。與 LLVM 不同,debug 使用的 位置信息是元數據,可以背拋棄,在MLIR中,位置信息是一個核心要求,并且又一些API是依賴這些信息的,而且可以修改它們。所以,丟棄位置信息是一個顯式的選擇,它不會因為失誤而發生。


??????? 這里提供一個說明:如果一個變換中替換了一個 operation,那么,新的 operation 必須也附帶有位置信息。這使得追蹤這個 operation 的出處成為可能。


??????? 值得注意的 mlir-tool 這個工具——它是用來測試 編譯器 passes 的工具——在其輸出中,默認并不包含位置信息。而 -mlir-print-debuginfo 這個標志可以指定其輸出中包含位置信息。(運行 mlir-opt --help 可以看到更多的選項)


不透明的 API

??????? MLIR 被設計成為可以允許一切 IR 元素,例如,自定義的屬性,operations,和類型。同時, IR 元素可以總是規約為上述這些基本概念。如此一來,這就允許 MLIR 去對任意 operation 進行解析,表示和遍歷 IR。例如,我們可以把我們的toy 的? operation 從上方移進一個 .mlir 文件,并且通過 mlir-opt 遍歷它,這里不需要注冊任何與 toy 語言關聯的 dialect:

func.func @toy_func(%tensor: tensor<2x3xf64>) -> tensor<3x2xf64> {%t_tensor = "toy.transpose"(%tensor) { inplace = true } : (tensor<2x3xf64>) -> tensor<3x2xf64>return %t_tensor : tensor<3x2xf64>
}

??????? 在 屬性、operations和類型在沒有被注冊的情況下, MLIR 將會強制一些結構性的約束(例如,dominance 等等),但是在其他方面,它們就是完全不透明的。例如,MLIR 關于一個未注冊的 operation 在如下情況中,只知道很少的信息:這個 operation 是否可以作用在一些特別的數據類型上,這個 operation 可以接收多少個操作數,這個 operation 可以產生多少個結果。這種靈活性對于引導性的目的是有用的,但是它通常不建議在成熟的系統中使用。未注冊的 operations 在變換和分析時必須保守對待,并且它們在構造和修改上非常困難。

??????? 通過精心設計什么是給Toy語言的非法的IR,并且通過觀察它不走檢測器時的遍歷,這個處理的意境可以被觀察到:
?

func.func @main() {%0 = "toy.print"() : () -> tensor<2x3xf64>
}

??????? 這里有多個問題:toy.print operation 不是一個終結操作;它應該接收一個操作數;而且,它不應該返回任何值。在下一節中,我們會使用 MLIR 注冊我們 dialect 和 operations,安裝進 verifier中,并且添加更好的 APIs 來修改我們的 operations。

定義 Toy 的一個 Dialect

為了與 MLIR 有效地聯系,我們 將會定義一個新的 Toy dialect。這個 dialect 將會對 Toy 語言的結構進行建模,同時為高級層次的分析和變換提供一個容易的途徑。

/// This is the definition of the Toy dialect. A dialect inherits from
/// mlir::Dialect and registers custom attributes, operations, and types. It can
/// also override virtual methods to change some general behavior, which will be
/// demonstrated in later chapters of the tutorial.
class ToyDialect : public mlir::Dialect {
public:explicit ToyDialect(mlir::MLIRContext *ctx);/// Provide a utility accessor to the dialect namespace.static llvm::StringRef getDialectNamespace() { return "toy"; }/// An initializer called from the constructor of ToyDialect that is used to/// register attributes, operations, types, and more within the Toy dialect.void initialize();
};

??????? 上面為 dialect 的 C++ 定義方式,但是 MLIR 也支持通過 tablegen 聲明性地定義 dialects。使用 td 聲明性的說明是更干凈的,因為在定義一個新的 dialect 時,它移除了對大量樣板文件的需要。它也使得 dialect 文檔的生成變得更容易,它可以直接在 dialect 的旁邊做描述。在這種 聲明性的格式中,toy 的 dialect 應該如下這樣做說明:

// Provide a definition of the 'toy' dialect in the ODS framework so that we
// can define our operations.
def Toy_Dialect : Dialect {// The namespace of our dialect, this corresponds 1-1 with the string we// provided in `ToyDialect::getDialectNamespace`.let name = "toy";// A short one-line summary of our dialect.let summary = "A high-level dialect for analyzing and optimizing the ""Toy language";// A much longer description of our dialect.let description = [{The Toy language is a tensor-based language that allows you to definefunctions, perform some math computation, and print results. This dialectprovides a representation of the language that is amenable to analysis andoptimization.}];// The C++ namespace that the dialect class definition resides in.let cppNamespace = "toy";
}

??????? 為了看到這會生成什么內容,我們可以運行 mlir-tblgen 命令,帶上? gen-dialect-decls 功能,像這樣:

${build_root}/bin/mlir-tblgen -gen-dialect-decls \
${mlir_src_root}/examples/toy/Ch2/include/toy/Ops.td \
-I ${mlir_src_root}/include/

在 dialect 被定義之后,它現在可以被加載進一個 MLIRContext之中:
?

context.loadDialect<ToyDialect>();

默認的話,一個 MLIRContext 只能加載一個 Builtin Dialect,它提供一些核心的 IR 組件,也就是說,其他的 dialects,例如我們自己定義的 Toy dialect,必須被顯式地加載才行。

定義 Toy operations

現在我們已經擁有一個 Toy dialect,我們可以開始定義 operations 了。這將允許提供寓意信息,以便系統的其余部分可以連接進去。作為示例,讓我們一起瀏覽一遍 toy.constant operation 的創建。這個 operation 將會在 Toy 語言中表示一個常數值。

%4 = "toy.constant"() {value = dense<1.0> : tensor<2x3xf64>} : () -> tensor<2x3xf64>


這個 operation 接收0個操作數,一個稠密的元素構成的屬性,稱之為 value,用來表示常熟值,并且返回一個 RankedTensorType 類型的結果。這個操作繼承自 CRTP mlir::Op 類,它還有幾個可選的 trait 屬性來定義它的行為。 Traits 是一個機制,可以用它向 operation 注入額外的行為,例如額外的 訪問器,驗證和其他行為。關于上邊我們講到的常量 operation,讓我們一起看看下邊這個可能的定義:

class ConstantOp : public mlir::Op</// `mlir::Op` is a CRTP class, meaning that we provide the/// derived class as a template parameter.ConstantOp,/// The ConstantOp takes zero input operands.mlir::OpTrait::ZeroOperands,/// The ConstantOp returns a single result.mlir::OpTrait::OneResult,/// We also provide a utility `getType` accessor that/// returns the TensorType of the single result.mlir::OpTrait::OneTypedResult<TensorType>::Impl> {public:/// Inherit the constructors from the base Op class.using Op::Op;/// Provide the unique name for this operation. MLIR will use this to register/// the operation and uniquely identify it throughout the system. The name/// provided here must be prefixed by the parent dialect namespace followed/// by a `.`.static llvm::StringRef getOperationName() { return "toy.constant"; }/// Return the value of the constant by fetching it from the attribute.mlir::DenseElementsAttr getValue();/// Operations may provide additional verification beyond what the attached/// traits provide.  Here we will ensure that the specific invariants of the/// constant operation are upheld, for example the result type must be/// of TensorType and matches the type of the constant `value`.LogicalResult verifyInvariants();/// Provide an interface to build this operation from a set of input values./// This interface is used by the `builder` classes to allow for easily/// generating instances of this operation:///   mlir::OpBuilder::create<ConstantOp>(...)/// This method populates the given `state` that MLIR uses to create/// operations. This state is a collection of all of the discrete elements/// that an operation may contain./// Build a constant with the given return type and `value` attribute.static void build(mlir::OpBuilder &builder, mlir::OperationState &state,mlir::Type result, mlir::DenseElementsAttr value);/// Build a constant and reuse the type from the given 'value'.static void build(mlir::OpBuilder &builder, mlir::OperationState &state,mlir::DenseElementsAttr value);/// Build a constant by broadcasting the given 'value'.static void build(mlir::OpBuilder &builder, mlir::OperationState &state,double value);
};

???????? 然后,我們可以在 ToyDialect 的 initializer 函數中注冊這個 operation:
?

void ToyDialect::initialize() {addOperations<ConstantOp>();
}

Op 與 Operation: 使用 MLIR Operations

??????? 現在我們已經定義了一個 operation,我們想要訪問并變換它。 在 MLIR 中,這里有兩個主要的 class 跟 operations 相關聯:Operation 和 Op。Operation 類用于一般意義上建模所有的 operations。它是不透明的,也就是說,它并不描述 特定 operation 的 properties 和 operations 的 types。相反,Operation class 提供通用的 API 給一個operation 實例。另一方面,operation 的每一個特定的類型是由 Op 的派生類來表示的。例如,ConstantOp 的表示一個0輸入和一個輸出的 operation,它總是被設置成為同樣的值。Op的派生類扮演智能指針封裝著 Operation*,提供 特定 operation 訪問器方法,同時提供operations 的 類型安全的 properties。這意味著,當我們定義我們的Toy operations 時,我們簡單地定義一個干凈的、語義用途的接口來構建和對接 Operation class。這是為什么我們的 ConstantOp 沒有定義class 的字段,這個 operation 的所有數據都存儲在所引用的 Operation class 之中。這個設計的一個副作用是我們總是值傳遞 Op 的派生類,而不是傳遞引用或指針(值傳遞是MLIR的特色,同樣應用于 attributes和types等)。給定一個一般的 Operation*實例,使用 LLVM 的類型變換設施,我們總是可以得到一個特定的 Op 的實例:

void processConstantOp(mlir::Operation *operation) {ConstantOp op = llvm::dyn_cast<ConstantOp>(operation);// This operation is not an instance of `ConstantOp`.if (!op)return;// Get the internal operation instance wrapped by the smart pointer.mlir::Operation *internalOperation = op.getOperation();assert(internalOperation == operation &&"these operation instances are the same");
}


使用 ODS Framework

使用 Operation Definition Specification (ODS) Framework
出了可以特化 mlir::Op 模版,MLIR 還支持使用聲明性的方式定義 operations。這是通過 Operation Definition Specificaiton framework 做到的。
也就是把一個 operaiton 通過一個簡潔的 TableGen 紀錄來特化,在編譯的時候,它將被展開成為等價的 mlir::Op C++模版的特化。
使用 ODS framework 是在 MLIR 中定義operations的提倡的方式,因為這很簡單,簡潔,同時對接 C++ API 的變化表現是穩定的。

讓我們一起看看我們 ConstantOp 的等價的 ODS 定義:
使用 ODS時, operations 是通過繼承 Op class 來定義的。為了簡化我們的 operation 定義,我們將在 Toy dialect中定義一個 operation的基類(因為它們都是 toy dialect 中的 operation,所以這本身是一個共性,故可以存在一個基類。比如它們都會被注冊進 同一個 toy dialect)。
?

// Base class for toy dialect operations. This operation inherits from the base
// `Op` class in OpBase.td, and provides:
//   * The parent dialect of the operation.
//   * The mnemonic for the operation, or the name without the dialect prefix.
//   * A list of traits for the operation.
class Toy_Op<string mnemonic, list<Trait> traits = []> :Op<Toy_Dialect, mnemonic, traits>;

結合這個初步的定義的代碼,我們可以開始定義constant operation。
我們通過繼承上述基類 Toy_Op 來定義一個 toy operation。這里我們給 operation 提供了 mnemonic(助記符) 和一個traits 列表。
這個助記符與 Constant::OperationName 這個成員方法提供的名字相匹配,只是需要去掉 dialect 前綴: toy..
與我們的 C++ 定義少了的部分是 ZeroOperands 和 OneResult traits. 這些將會基于我們稍后定義的 arguments 和 results 字段自動推導出來。

def ConstantOp : Toy_Op<"constant"> {
}

到此為止,你可能想知道 TableGen 生成的代碼看起來會是什么樣子的。帶著 -gen-op-decls 或者 -gen-op-defs 動作,簡單地運行 mlir-tblgen 命令,具體如下所示:
?

${build_root}/bin/mlir-tblgen -gen-op-defs ${mlir_src_root}/examples/toy/Ch2/include/toy/Ops.td -I ${mlir_src_root}/include/

依賴于所選擇的動作,這將會打印出 ConstantOp class 的聲明和它的實現。比較輸出的內容和手寫的實現,對于開始使用 TableGen 是非常有益的。

定義 Arguments 和 Results

結合剛剛定義的這個 operation 的殼,我們可以給我們的 operation 提供輸入和輸出。
一個 operation 的輸入或者 arguments 可能會是 SSA 的操作數值的 attributes 或者 types。
這個結果對應到一組該 operation 產生的值的類型:

def ConstantOp : Toy_Op<"constant"> {// The constant operation takes an attribute as the only input.// `F64ElementsAttr` corresponds to a 64-bit floating-point ElementsAttr.let arguments = (ins F64ElementsAttr:$value);// The constant operation returns a single value of TensorType.// F64Tensor corresponds to a 64-bit floating-point TensorType.let results = (outs F64Tensor);
}

通過提供一個給 arguments 或 results 的名字,例如 $value, ODS 將會自動生成一個對應的訪問器:DenseElementsAttr ConstantOp::value().

添加文檔

定義操作的下一個步驟是文檔化它。Operations 可以提供 summary 和 description 字段來描述這個 operation 的語義。
這些信息對于這個 dialect 的用戶是有益的,甚至可以用來自動生成 Markdown 文檔。

def ConstantOp : Toy_Op<"constant"> {// Provide a summary and description for this operation. This can be used to// auto-generate documentation of the operations within our dialect.let summary = "constant operation";let description = [{Constant operation turns a literal into an SSA value. The data is attachedto the operation as an attribute. For example:%0 = "toy.constant"(){ value = dense<[[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]> : tensor<2x3xf64> }: () -> tensor<2x3xf64>}];// The constant operation takes an attribute as the only input.// `F64ElementsAttr` corresponds to a 64-bit floating-point ElementsAttr.let arguments = (ins F64ElementsAttr:$value);// The generic call operation returns a single value of TensorType.// F64Tensor corresponds to a 64-bit floating-point TensorType.let results = (outs F64Tensor);
}

驗證操作的語義

到此為止,我們已經覆蓋了原先的 C++ 的 operation 的定義的大部分內容。下一個需要定義的部分是 verifier。
幸運的是,很想剛才命名的訪問器,ODS framework 將會基于我們給定的約束自動生成一大堆必要的驗證邏輯。
這意味著我們不需要驗證返回類型的結構,甚至輸入的 attribute value。在大多數情況下,對于 ODS operations,額外的驗證是不需要的。
添加額外的 驗證邏輯,一個 operation 可以重載 verifier 字段。這個 verifier 字段允許定義一個? C++ 代碼塊,作為ConstantOp::verify的一部分來運行。
這個額外的代碼塊可以假設這個 operation 的所有其他的不變量都已經檢查過了:

def ConstantOp : Toy_Op<"constant"> {// Provide a summary and description for this operation. This can be used to// auto-generate documentation of the operations within our dialect.let summary = "constant operation";let description = [{Constant operation turns a literal into an SSA value. The data is attachedto the operation as an attribute. For example:%0 = "toy.constant"(){ value = dense<[[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]> : tensor<2x3xf64> }: () -> tensor<2x3xf64>}];// The constant operation takes an attribute as the only input.// `F64ElementsAttr` corresponds to a 64-bit floating-point ElementsAttr.let arguments = (ins F64ElementsAttr:$value);// The generic call operation returns a single value of TensorType.// F64Tensor corresponds to a 64-bit floating-point TensorType.let results = (outs F64Tensor);// Add additional verification logic to the constant operation. Setting this bit// to `1` will generate a `::llvm::LogicalResult verify()` declaration on the// operation class that is called after ODS constructs have been verified, for// example the types of arguments and results. We implement additional verification// in the definition of this `verify` method in the C++ source file.let hasVerifier = 1;
}

附加 build 方法

跟起初 C++ 定義 operation 的例子中相比,最后還缺少的組件是 build methods。
ODS 可以自動地產生一些簡單的 build 方法,而且在本例子中,它將會產生我們的第一個 build 方法。其余的情況,我們通過定義 builders 字段來定義。這個字段接收一系列 OpBuilder 對象,這些對象會對應地接受一個字符串,作為C++ 參數列表的一個部分,同時可選的 代碼塊可以用來指定這個 builder 的內聯實現。

def ConstantOp : Toy_Op<"constant"> {...// Add custom build methods for the constant operation. These methods populate// the `state` that MLIR uses to create operations, i.e. these are used when// using `builder.create<ConstantOp>(...)`.let builders = [// Build a constant with a given constant tensor value.OpBuilder<(ins "DenseElementsAttr":$value), [{// Call into an autogenerated `build` method.build(builder, result, value.getType(), value);}]>,// Build a constant with a given constant floating-point value. This builder// creates a declaration for `ConstantOp::build` with the given parameters.OpBuilder<(ins "double":$value)>];
}

指定自定義的匯編格式

到此為止,我們可以產生我們的 “Toy IR”。例如如下代碼:

# User defined generic function that operates on unknown shaped arguments.
def multiply_transpose(a, b) {return transpose(a) * transpose(b);
}def main() {var a<2, 3> = [[1, 2, 3], [4, 5, 6]];var b<2, 3> = [1, 2, 3, 4, 5, 6];var c = multiply_transpose(a, b);var d = multiply_transpose(b, a);print(d);
}

可以產生如下的 IR:

module {"toy.func"() ({^bb0(%arg0: tensor<*xf64> loc("test/Examples/Toy/Ch2/codegen.toy":4:1), %arg1: tensor<*xf64> loc("test/Examples/Toy/Ch2/codegen.toy":4:1)):%0 = "toy.transpose"(%arg0) : (tensor<*xf64>) -> tensor<*xf64> loc("test/Examples/Toy/Ch2/codegen.toy":5:10)%1 = "toy.transpose"(%arg1) : (tensor<*xf64>) -> tensor<*xf64> loc("test/Examples/Toy/Ch2/codegen.toy":5:25)%2 = "toy.mul"(%0, %1) : (tensor<*xf64>, tensor<*xf64>) -> tensor<*xf64> loc("test/Examples/Toy/Ch2/codegen.toy":5:25)"toy.return"(%2) : (tensor<*xf64>) -> () loc("test/Examples/Toy/Ch2/codegen.toy":5:3)}) {sym_name = "multiply_transpose", type = (tensor<*xf64>, tensor<*xf64>) -> tensor<*xf64>} : () -> () loc("test/Examples/Toy/Ch2/codegen.toy":4:1)"toy.func"() ({%0 = "toy.constant"() {value = dense<[[1.000000e+00, 2.000000e+00, 3.000000e+00], [4.000000e+00, 5.000000e+00, 6.000000e+00]]> : tensor<2x3xf64>} : () -> tensor<2x3xf64> loc("test/Examples/Toy/Ch2/codegen.toy":9:17)%1 = "toy.reshape"(%0) : (tensor<2x3xf64>) -> tensor<2x3xf64> loc("test/Examples/Toy/Ch2/codegen.toy":9:3)%2 = "toy.constant"() {value = dense<[1.000000e+00, 2.000000e+00, 3.000000e+00, 4.000000e+00, 5.000000e+00, 6.000000e+00]> : tensor<6xf64>} : () -> tensor<6xf64> loc("test/Examples/Toy/Ch2/codegen.toy":10:17)%3 = "toy.reshape"(%2) : (tensor<6xf64>) -> tensor<2x3xf64> loc("test/Examples/Toy/Ch2/codegen.toy":10:3)%4 = "toy.generic_call"(%1, %3) {callee = @multiply_transpose} : (tensor<2x3xf64>, tensor<2x3xf64>) -> tensor<*xf64> loc("test/Examples/Toy/Ch2/codegen.toy":11:11)%5 = "toy.generic_call"(%3, %1) {callee = @multiply_transpose} : (tensor<2x3xf64>, tensor<2x3xf64>) -> tensor<*xf64> loc("test/Examples/Toy/Ch2/codegen.toy":12:11)"toy.print"(%5) : (tensor<*xf64>) -> () loc("test/Examples/Toy/Ch2/codegen.toy":13:3)"toy.return"() : () -> () loc("test/Examples/Toy/Ch2/codegen.toy":8:1)}) {sym_name = "main", type = () -> ()} : () -> () loc("test/Examples/Toy/Ch2/codegen.toy":8:1)
} loc(unknown)

??????? 有一個事情需要注意,我們所有的 Toy operations 都是使用通用的匯編個是打印的。這種格式跟本章開頭分析 toy.transpose 的時候顯示的一樣。MLIR 允許 operations 定義它們獨有的匯編格式,或者是聲明式的,或者是通過 C++ 命令式的定義。用戶自定義的匯編格式允許裁剪調一些通用 IR,使其變得更可讀,這通常是刪除一些通用格式中需要的一些次要的枝椏。讓我們一起瀏覽一個我們將會簡化其 operation 格式的例子。

toy.print

當前的 toy.print 的歌是有點冗長。這里有許多額外的字符是我們想要剝離掉的。讓我們先想一下更好的 toy.print 的格式應該是什么樣的,然后再看看我們如何實現它。看著基本的 toy.print 的格式,我們得到:

toy.print %5 : tensor<*xf64> loc(...)


這里我們已經剝離掉了不是很有必要的成分,而且變得更加可讀了。為了提供一個自定義的匯編格式,一個 operation 可以用 C++ 重寫 hasCustomAssemblyFormat 字段,或者重寫聲明式的 assemblyFormat 字段(tableGen)。我們先看看 C++ 的變體,因為這是聲明式的格式內部映射成的樣子。

/// Consider a stripped definition of `toy.print` here.
def PrintOp : Toy_Op<"print"> {let arguments = (ins F64Tensor:$input);// Divert the printer and parser to `parse` and `print` methods on our operation,// to be implemented in the .cpp file. More details on these methods is shown below.let hasCustomAssemblyFormat = 1;
}

一個 C++ 實現的 printer 和 parser如下所示:

/// The 'OpAsmPrinter' class is a stream that will allows for formatting
/// strings, attributes, operands, types, etc.
void PrintOp::print(mlir::OpAsmPrinter &printer) {printer << "toy.print " << op.input();printer.printOptionalAttrDict(op.getAttrs());printer << " : " << op.input().getType();
}/// The 'OpAsmParser' class provides a collection of methods for parsing
/// various punctuation, as well as attributes, operands, types, etc. Each of
/// these methods returns a `ParseResult`. This class is a wrapper around
/// `LogicalResult` that can be converted to a boolean `true` value on failure,
/// or `false` on success. This allows for easily chaining together a set of
/// parser rules. These rules are used to populate an `mlir::OperationState`
/// similarly to the `build` methods described above.
mlir::ParseResult PrintOp::parse(mlir::OpAsmParser &parser,mlir::OperationState &result) {// Parse the input operand, the attribute dictionary, and the type of the// input.mlir::OpAsmParser::UnresolvedOperand inputOperand;mlir::Type inputType;if (parser.parseOperand(inputOperand) ||parser.parseOptionalAttrDict(result.attributes) || parser.parseColon() ||parser.parseType(inputType))return mlir::failure();// Resolve the input operand to the type we parsed in.if (parser.resolveOperand(inputOperand, inputType, result.operands))return mlir::failure();return mlir::success();
}

「注,parser 部分比較巧妙,根據實際代碼感受true-failure false-success 的效果」

??????? 結合 C++ 定義的實現,讓我們一起看看這將怎樣映射到 聲明式的格式。
??????? 聲明式的格式主要有三個不同的組件構成:
指令(Directives)
??????????????????????????????? 一類內置函數,帶有可選的一組參數。
字面量(Literals)
??????????????????????????????? 一個關鍵字或者符號,用 ‘’包裹。
變量(Variables)
??????????????????????????????? 一個實體,已經通過 operation 自身注冊過的,例如,一個參數(屬性或者操作數),結果,后繼者等。在上述 PrintOp 例子中,一個變量可以是其中的 $input.一個對應 C++ 格式的直接映射是如下這樣:

/// Consider a stripped definition of `toy.print` here.
def PrintOp : Toy_Op<"print"> {let arguments = (ins F64Tensor:$input);// In the following format we have two directives, `attr-dict` and `type`.// These correspond to the attribute dictionary and the type of a given// variable represectively.let assemblyFormat = "$input attr-dict `:` type($input)";
}

??????? 聲明格式中還有很多有趣的特性,在實現一個自定義的 C++ 格式之前,務必要先查看理解一下它們。在對我們的 operation 做了一些格式美化之后,我們現在可以得到一個更可讀的 toy IR:

module {toy.func @multiply_transpose(%arg0: tensor<*xf64>, %arg1: tensor<*xf64>) -> tensor<*xf64> {%0 = toy.transpose(%arg0 : tensor<*xf64>) to tensor<*xf64> loc("test/Examples/Toy/Ch2/codegen.toy":5:10)%1 = toy.transpose(%arg1 : tensor<*xf64>) to tensor<*xf64> loc("test/Examples/Toy/Ch2/codegen.toy":5:25)%2 = toy.mul %0, %1 : tensor<*xf64> loc("test/Examples/Toy/Ch2/codegen.toy":5:25)toy.return %2 : tensor<*xf64> loc("test/Examples/Toy/Ch2/codegen.toy":5:3)} loc("test/Examples/Toy/Ch2/codegen.toy":4:1)toy.func @main() {%0 = toy.constant dense<[[1.000000e+00, 2.000000e+00, 3.000000e+00], [4.000000e+00, 5.000000e+00, 6.000000e+00]]> : tensor<2x3xf64> loc("test/Examples/Toy/Ch2/codegen.toy":9:17)%1 = toy.reshape(%0 : tensor<2x3xf64>) to tensor<2x3xf64> loc("test/Examples/Toy/Ch2/codegen.toy":9:3)%2 = toy.constant dense<[1.000000e+00, 2.000000e+00, 3.000000e+00, 4.000000e+00, 5.000000e+00, 6.000000e+00]> : tensor<6xf64> loc("test/Examples/Toy/Ch2/codegen.toy":10:17)%3 = toy.reshape(%2 : tensor<6xf64>) to tensor<2x3xf64> loc("test/Examples/Toy/Ch2/codegen.toy":10:3)%4 = toy.generic_call @multiply_transpose(%1, %3) : (tensor<2x3xf64>, tensor<2x3xf64>) -> tensor<*xf64> loc("test/Examples/Toy/Ch2/codegen.toy":11:11)%5 = toy.generic_call @multiply_transpose(%3, %1) : (tensor<2x3xf64>, tensor<2x3xf64>) -> tensor<*xf64> loc("test/Examples/Toy/Ch2/codegen.toy":12:11)toy.print %5 : tensor<*xf64> loc("test/Examples/Toy/Ch2/codegen.toy":13:3)toy.return loc("test/Examples/Toy/Ch2/codegen.toy":8:1)} loc("test/Examples/Toy/Ch2/codegen.toy":8:1)
} loc(unknown)

??????? 上述,我們介紹了在 ODS framework中定義 operations 的幾個概念,但是這里還有好幾個概念我們沒有機會涉及到:regions,variadic 操作數,等等。查看一下 全部的說明可以找到更多細節。

完整的 toy 示例

???????? 現在我們可以生成我們的 “Toy IR”。你可以構建 toyc-ch2 然后自己嘗試上邊的示例:

toyc-ch2 test/Examples/Toy/Ch2/codegen.toy -emit=mlir -mlir-print-debuginfo

我們也可以檢查我們的遍歷:

toyc-ch2 test/Examples/Toy/Ch2/codegen.toy -emit=mlir -mlir-print-debuginfo 2> codegen.mlir

你也應該在最終的定義文件上使用 mlir-tblgen,并且仔細研究生成的C++代碼。

??????? 到此為止,MLIR 已經知道了我們的 dialect 和 operations。在下一章中,我們將利用我們的dialect ,為 toy 語言實現一些高級的特定于語言的分析和變換。

注意

「注:根據之前的環境搭建步驟,toyc-ch2 示例中,由 mlir-tblgen 生成于如下文件夾:llvm-project/build_mlir/tools/mlir/examples/toy/Ch2/include/toy/Dialect.cpp.inc

其中 Toy_Dialect 的 td定義:

include "mlir/IR/OpBase.td"
include "mlir/IR/FunctionInterfaces.td"
include "mlir/IR/SymbolInterfaces.td"
include "mlir/Interfaces/SideEffectInterfaces.td"// Provide a definition of the 'toy' dialect in the ODS framework so that we
// can define our operations.
def Toy_Dialect : Dialect {let name = "toy";let cppNamespace = "::mlir::toy";let useFoldAPI = kEmitFoldAdaptorFolder;
}

生成指令大體上如此:


inc := -I /home/hipper/ex_mlir/tmp2/llvm-project/mlir/examples/toy/Ch2/include/toy -I/home/hipper/ex_mlir/tmp2/llvm-project/build_mlir/include -I/home/hipper/ex_mlir/tmp2/llvm-project/llvm/include -I/home/hipper/ex_mlir/tmp2/llvm-project/mlir/include -I/home/hipper/ex_mlir/tmp2/llvm-project/build_mlir/tools/mlir/include 
$(inc)
input := /home/hipper/ex_mlir/tmp2/llvm-project/mlir/examples/toy/Ch2/include/toy/Ops.td
$(input)
output := tools/mlir/examples/toy/Ch2/include/toy/
$(output)
build_dir := /home/hipper/ex_mlir/tmp2/llvm-project/build_mlir
$(build_dir)[1/8] $(build_dir)/bin/mlir-tblgen -gen-dialect-decls $(inc)  $(input) --write-if-changed -o $(output)/Dialect.h.inc    -d $(output)/Dialect.h.inc.d
[2/8] $(build_dir)/bin/mlir-tblgen -gen-op-decls      $(inc)  $(input) --write-if-changed -o $(output)/Ops.h.inc        -d $(output)/Ops.h.inc.d
[3/6] $(build_dir)/bin/mlir-tblgen -gen-dialect-defs  $(inc)  $(input) --write-if-changed -o $(output)t/Dialect.cpp.inc -d $(output)/Dialect.cpp.inc.d
[4/6] $(build_dir)/bin/mlir-tblgen -gen-op-defs       $(inc)  $(input) --write-if-changed -o $(output)/Ops.cpp.inc      -d $(output)/Ops.cpp.inc.d

生成的 decl 代碼如下:(defs 代碼在對應的 Dialect.cpp.inc中)

namespace mlir {
namespace toy {class ToyDialect : public ::mlir::Dialect {explicit ToyDialect(::mlir::MLIRContext *context);void initialize();friend class ::mlir::MLIRContext;
public:~ToyDialect() override;static constexpr ::llvm::StringLiteral getDialectNamespace() {return ::llvm::StringLiteral("toy");}
};
} // namespace toy
} // namespace mlir
MLIR_DECLARE_EXPLICIT_TYPE_ID(::mlir::toy::ToyDialect)

Chapter1: 生成 toy 語言源程序的 AST

Chapter2: 能夠把AST變成SSA的MLIR Dialect IR : toy IR

Chapter3: toy IR 層的優化 opt pass

Chapter4:

Chapter5:

Chapter6:

Chapter7:

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/76212.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/76212.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/76212.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

一個判斷A股交易狀態的python腳本

最近在做股票數據相關的項目&#xff0c;需要用到判斷某一天某個時刻A股的狀態&#xff0c;比如休市&#xff0c;收盤&#xff0c;交易中等&#xff0c;發動腦筋想了一下&#xff0c;這個其實還是比較簡單的&#xff0c;這里我把實現方法分享給大家。 思路 當天是否休市 對于某…

LLaMA Factory微調后的大模型在vLLM框架中對齊對話模版

LLaMA Factory微調后的大模型Chat對話效果&#xff0c;與該模型使用vLLM推理架構中的對話效果&#xff0c;可能會出現不一致的情況。 下圖是LLaMA Factory中的Chat的對話 下圖是vLLM中的對話效果。 模型回答不穩定&#xff1a;有一半是對的&#xff0c;有一半是無關的。 1、未…

004 健身房個性化訓練計劃——金丹期(體態改善)

個人筆記使用。 01 肱骨前移 1.放松肩前束 2.放松肩后束 2.5kg啞鈴側展 泡沫軸上下滾 招財貓 肱二頭 02 溜肩 寬距的坐姿劃船 上頂

【已開源】UniApp+vue3跨端應用從0到1開發指南、uniapp+vue3模板應用

在跨端開發日益成為主流的今天&#xff0c;如何高效構建規范、可維護的企業級應用&#xff1f;本文以UniAppVue3* *TypeScript**為核心技術棧&#xff0c;手把手帶你從零搭建高標準的跨平臺項目。 通過本文&#xff0c;你將系統掌握&#xff1a; ? 環境配置&#xff1a;Node…

線程池設計

線程池實際上也是一個生產者消費者模型&#xff0c;線程池可以讓多個線程去任務隊列中取任務&#xff0c;執行任務&#xff0c;適用于需要大量的線程來完成任務且完成任務的時間較短。 #include "log.hpp" #include <mutex> #include <condition_variable&…

黑盒測試的正交實驗法

背景: 利用因果圖法、判定表法可以幫助我們對于輸入數據的組合情況進行用例設計&#xff0c;但當輸入數據的組合數量巨大時&#xff0c;由于不太可能覆蓋到每個輸入組合的測試情況&#xff0c;因果圖法或判定表法可能就不太適用了&#xff0c;可以采用正交實驗法、來合理地減少…

Linux內核編程

linux 系 統 在 2 4 4 0 上 的 啟 動 過 程 分 三個 階 段 u-boot的啟動 1.先分清寄存器的分類 RAM的分類 ROM的分類 Mini2440開發板的存 儲器配置 Mini2440開發板板載: 1. 64MB sdram; 2. 256MB nand-flash; 3. 2MB nor-flash; 4. s3c2440內部還有4KB iram; Mini2440的啟…

黑盒測試的判定表法(能對多條件依賴關系進行設計測試點)

定義: 判定表是分析和表達多邏輯條件下執行不同操作的工具。就是指把所有的輸入條件、所有可能采取的動作按表格列出來&#xff0c;每一種條件和動作的組合構成一條規則&#xff0c;也即一條用例。 1.判定表法的引用 等價類邊界值分析法主要關注單個輸入類條件的測試并未考慮…

從零構建大語言模型全棧開發指南:第四部分:工程實踐與部署-4.1.2ONNX格式轉換與TensorRT部署

?? 點擊關注不迷路 ?? 點擊關注不迷路 ?? 點擊關注不迷路 文章大綱 從零構建大語言模型全棧開發指南-第四部分:工程實踐與部署4.1.2 ONNX格式轉換與TensorRT部署1. 模型部署的核心挑戰與價值2. ONNX格式轉換技術詳解2.1 ONNX技術棧組成2.2 轉換流程與關鍵技術2.3 轉換常…

免費下載 | 2025年網絡安全報告

報告總結了2024年的網絡安全態勢&#xff0c;并對2025年的安全趨勢進行了預測和分析。報告涵蓋了勒索軟件、信息竊取軟件、云安全、物聯網設備安全等多個領域的安全事件和趨勢&#xff0c;并提供了安全建議和最佳實踐。 一、報告背景與目的 主題&#xff1a;2024企業信息安全峰…

基于Real-Sim-Real循環框架的機器人策略遷移方法

編輯&#xff1a;陳萍萍的公主一點人工一點智能 基于Real-Sim-Real循環框架的機器人策略遷移方法本文通過嚴謹的理論推導和系統的實驗驗證&#xff0c;構建了一個具有普適性的sim-to-real遷移框架。https://mp.weixin.qq.com/s/cRRI2VYHYQUUhHhP3bw4lA 01 摘要 本文提出的Rea…

語義分析(編譯原理)

1.什么是語義分析: 前兩個階段&#xff0c;詞法分析是從字符到單詞的一級識別&#xff0c;保證了每個單詞的形式是正確的&#xff0c; 語法分析是由單詞到語法樹的一級識別&#xff0c;如果不符合語法規則就不能建樹&#xff0c;因此保證了各個語法成分的構成是正確的 詞法分…

藍橋杯備考---》貪心算法之矩陣消除游戲

我們第一次想到的貪心策略一定是找出和最大的行或者列來刪除&#xff0c;每次都更新行和列 比如如圖這種情況&#xff0c;這種情況就不如直接刪除兩行的多&#xff0c;所以本貪心策略有誤 so我們可以枚舉選的行的情況&#xff0c;然后再貪心的選擇列和最大的列來做 #include …

LeetCode hot 100—二叉搜索樹中第K小的元素

題目 給定一個二叉搜索樹的根節點 root &#xff0c;和一個整數 k &#xff0c;請你設計一個算法查找其中第 k 小的元素&#xff08;從 1 開始計數&#xff09;。 示例 示例 1&#xff1a; 輸入&#xff1a;root [3,1,4,null,2], k 1 輸出&#xff1a;1示例 2&#xff1a; …

【Java SE】Arrays類

參考筆記&#xff1a; Java中Arrays類(操作數組的工具)_java arrays-CSDN博客 Java——Arrays 類詳解_java arrays類-CSDN博客 目錄 1.Arrays類簡介 2.Arrays.toString 2.1 使用示例 2.2 源碼 3. Arrays.copyOf 3.1 使用示例 3.2 源碼 4.Arrays.sort 4.1 默認排序使…

git命令簡陋版本

git push git pull 臨時倉庫暫存區 ##############創建提交################ git init #創建git地址 git config --global user.name "***YQ1007" git config --global user.email "***gmail.com" git remote…

6. 王道_網絡協議

1 網絡協議和網絡模型 2 TCP/IP協議族概覽 2.1 四層模型的各層實體 2.2 協議數據單元的轉換 2.3 常見協議以及分層 2.4 ifconfig 2.5 本地環回設備 3 以太網 3.1 以太網和交換機 3.2 以太網幀 MAC地址大小 48位 6字節 IP地址 32位 4字節 port 16位 2字節 3.3 ARP協議 4 IP協…

minecraft.service 文件配置

minecraft.service 文件配置 # /etc/systemd/system/minecraft.service [Unit] DescriptionMinecraft Fabric Server Afternetwork.target Wantsnetwork-online.target[Service] Usermcfabricuser Groupmcfabricuser WorkingDirectory/minecraft/1.21.1-fabric-server ExecStar…

python leetcode簡單練習(2)

20 有效括號 方法思路 要判斷一個僅由括號組成的字符串是否有效&#xff0c;可以使用棧這一數據結構。核心思路是遍歷字符串中的每個字符&#xff0c;遇到左括號時壓入棧中&#xff0c;遇到右括號時檢查棧頂的左括號是否匹配。若匹配則彈出棧頂元素&#xff0c;否則返回false。…

AI 數字人短視頻數字人口播源碼:短視頻內容生產的新引擎?

在當下信息爆炸的時代&#xff0c;短視頻已成為主流的信息傳播與娛樂方式之一。在如此龐大的市場需求下&#xff0c;如何高效、創新地生產短視頻內容成為了行業關注的焦點。AI 數字人短視頻數字人口播源碼應運而生&#xff0c;為短視頻內容生產帶來了全新的變革。? 一、行業背…