深入解析 MLIR Toy Tutorial(Chapter 2):AST to IR

概述

MLIR Toy Tutorial 的目標(biāo)是通過構(gòu)建一門編程語言編譯器的完整過程(包括前端和后端技術(shù)),教授如何使用 MLIR 的各個(gè)組件來實(shí)現(xiàn)語言的解析峭拘、轉(zhuǎn)換和代碼生成等功能。

Chapter2 介紹了如何為 Toy 語言開發(fā) MLIR 方言(Dialect),并將 Chapter1 生成的 AST 轉(zhuǎn)換成 MLIR IR瞳腌。
源碼:https://github.com/llvm/llvm-project/tree/main/mlir/examples/toy/Ch2

AST -> IR

// Ch2/toyc.cpp@dumpMLIR()
mlir::MLIRContext context;
context.getOrLoadDialect<mlir::toy::ToyDialect>();

auto moduleAST = parseInputFile(inputFilename);
mlir::OwningOpRef<mlir::ModuleOp> module = mlirGen(context, *moduleAST);
module->dump();

Ch2 的 example 演示了如何將基于 toy 語言寫的源碼轉(zhuǎn)換成 MLIR IR:先通過 parseInputFile() 構(gòu)建源碼的抽象語法樹 AST暂衡,再通過 mlirGen() 將 AST 轉(zhuǎn)換成方言 mlir::toy::ToyDialect 定義的 IR暇屋,最后將 IR 打印出來(AST 的生成過程可以參考: 深入解析 MLIR Toy Tutorial(Chapter 1))炫隶。

方言 Dialect

MLIR 是一個(gè)設(shè)計(jì)完全可擴(kuò)展的基礎(chǔ)設(shè)施存璃,它的可擴(kuò)展性體現(xiàn)在 IR 的各個(gè)元素仑荐,包括 operations、types 和 attributes 等都是可以進(jìn)行擴(kuò)展的纵东。這種可擴(kuò)展性是通過方言(dialect)來實(shí)現(xiàn)的粘招,方言為 IR 提供了一種通過 namespace 分組的機(jī)制,可以賦予操作(operation)新的語義偎球,實(shí)現(xiàn)自定義的行為洒扎。

在 IR 中,如果想要為操作賦予新的語義以實(shí)現(xiàn)自定義行為衰絮,通常需要添加新的屬性或者重新定義輸入輸出袍冷。這時(shí),IR 的可擴(kuò)展性就變得非常重要猫牡。

舉例來說胡诗,考慮一個(gè) matmul 算子,如果想要為它添加一個(gè)屬性以進(jìn)行后端圖優(yōu)化淌友,就需要查看該算子的 attributes 字段是否可擴(kuò)展煌恢,以及 op builder 是否允許傳入該屬性。只有兩者都支持亩进,才能滿足需求症虑。否則,通常需要?jiǎng)?chuàng)建一個(gè)自定義的 matmul 算子归薛,并將原有的 matmul 算子替換為自定義算子谍憔。

而在 MLIR 中,你可以定義一個(gè)特定的方言主籍,比如 toy dialect习贫,然后在該方言中定義一個(gè) matmul 算子,這樣就可以獲得 toy.matmul 算子千元。在圖優(yōu)化過程中苫昌,可以根據(jù)方言的不同對算子進(jìn)行分層優(yōu)化,使用不同方言的轉(zhuǎn)換來實(shí)現(xiàn)多層次的優(yōu)化幸海。

MLIR 提供了許多內(nèi)置的 Dialects祟身,可以組合使用它們,以滿足在不同語義下對操作進(jìn)行圖優(yōu)化的需求物独。通過利用方言的能力袜硫,MLIR 實(shí)現(xiàn)了靈活且可擴(kuò)展的編譯器基礎(chǔ)設(shè)施。

定義 Toy 方言

方言可以被理解為一組操作(op)挡篓,因此定義方言就意味著定義操作婉陷。操作的定義通常包括以下內(nèi)容:

  • 定義操作的靜態(tài)信息帚称,包括輸入輸出、屬性和類型等秽澳,這些信息描述了操作的語義闯睹。
  • 創(chuàng)建操作的構(gòu)造方法,用于創(chuàng)建一個(gè)操作并將其添加到IR中担神。
  • 創(chuàng)建操作的實(shí)現(xiàn)方法楼吃,用于在IR執(zhí)行時(shí)調(diào)用。

MLIR提供了一種領(lǐng)域特定語言(DSL)杏瞻,使得我們可以通過聲明的方式描述和定義操作的輸入輸出所刀、屬性、類型和行為捞挥。利用MLIR提供的 tablegen 工具(mlir-tblgen)浮创,我們可以基于 Operation Definition Specification(ODS)框架自動(dòng)生成操作類的聲明和實(shí)現(xiàn)代碼。這種聲明式的定義風(fēng)格使得操作的定義更加清晰易懂砌函,我們只需要關(guān)注操作的語義定義即可斩披。

在Ch2中,我們分別在 include/toy/Ops.tdmlir/Dialect.cpp 中定義了 add讹俊、mul垦沉、call 等操作的靜態(tài)信息和方法。以 call 操作(GenericCallOp)為例仍劈,我們可以進(jìn)一步分析如何創(chuàng)建 MLIR 的操作(op):

def Toy_Dialect : Dialect {
  let name = "toy";
  let cppNamespace = "::mlir::toy";
} 

class Toy_Op<string mnemonic, list<Trait> traits = []> :
    Op<Toy_Dialect, mnemonic, traits>;

//===----------------------------------------------------------------------===//
// GenericCallOp
//===----------------------------------------------------------------------===//

def GenericCallOp : Toy_Op<"generic_call"> {
  let summary = "generic call operation";
  let description = [{
    ......
  }];

  // The generic call operation takes a symbol reference attribute as the
  // callee, and inputs for the call.
  let arguments = (ins FlatSymbolRefAttr:$callee, Variadic<F64Tensor>:$inputs);

  // The generic call operation returns a single value of TensorType.
  let results = (outs F64Tensor);

  // Specialize assembly printing and parsing using a declarative format.
  let assemblyFormat = [{
    $callee `(` $inputs `)` attr-dict `:` functional-type($inputs, results)
  }];

  // Add custom build methods for the generic call operation.
  let builders = [
    OpBuilder<(ins "StringRef":$callee, "ArrayRef<Value>":$arguments)>
  ];
}

首先厕倍,我們需要?jiǎng)?chuàng)建一個(gè)方言(Toy_Dialect)和一個(gè)基類操作(Toy_Op)。GenericCallOp 是繼承自 Toy_Op 的操作贩疙,它屬于 toy 方言讹弯。

然后,我們定義操作(op)的具體內(nèi)容:

  • arguments 和 results:定義操作的輸入和輸出这溅。
  • builders:操作的構(gòu)造方法组民,用于創(chuàng)建操作并將其添加到 IR 中。
  • assemblyFormat:定義操作在打印時(shí)的文本格式悲靴,這在對 IR 進(jìn)行輸出時(shí)非常有用臭胜。這里的 `` 表示雙引號(hào)。

在編譯時(shí)癞尚,我們使用 mlir-tblgen 工具生成操作類的聲明和實(shí)現(xiàn)代碼耸三。生成的代碼位于 llvm-project/build/tools/mlir/examples/toy/Ch2/include/toy/*.inc 文件中,并被 mlir/Dialect.cpp 引用浇揩。這些文件是通過使用 -gen-dialect-decls吕晌、-gen-op-decls 和 -gen-op-defs 參數(shù)生成的。例如:mlir-tblgen -gen-dialect-decls llvm-project/mlir/examples/toy/Ch2/include/toy/Ops.td -Illvm-project/mlir/include临燃。

瀏覽這些 *.inc 文件,我們可以看到生成的內(nèi)容不僅包括操作類和一些通用方法,還包括輔助類(如 OpAdaptor膜廊、OpGenericAdaptor 等)乏沸,用于簡化操作的使用和參數(shù)傳遞。借助 tablegen 的輔助功能爪瓜,我們只需在 Dialect.cpp 中定義少量的方法蹬跃,就能完成操作的定義:

// mlir/Dialect.cpp

//===----------------------------------------------------------------------===//
// GenericCallOp
//===----------------------------------------------------------------------===//

void GenericCallOp::build(mlir::OpBuilder &builder, mlir::OperationState &state,
                          StringRef callee, ArrayRef<mlir::Value> arguments) {
  // Generic call always returns an unranked Tensor initially.
  state.addTypes(UnrankedTensorType::get(builder.getF64Type()));
  state.addOperands(arguments);
  state.addAttribute("callee",
                     mlir::SymbolRefAttr::get(builder.getContext(), callee));
}

GenericCallOp::build() 用于在 IR 中構(gòu)建 call op。

mlirGen(): AST -> IR

在完成方言的定義之后铆铆,下一步是將抽象語法樹(AST)轉(zhuǎn)換為中間表示(IR)蝶缀。讓我們回顧一下 AST 的層次結(jié)構(gòu):

  • 程序(ModuleAST)由函數(shù)(FunctionAST)組成。
  • 函數(shù)由原型(PrototypeAST)和代碼塊(block薄货,即花括號(hào)中的內(nèi)容)組成翁都。
  • 原型由函數(shù)名和參數(shù)列表組成。
  • 代碼塊由表達(dá)式列表(ExprASTList)組成谅猾。

IR 的轉(zhuǎn)換是通過 mlirGen() 函數(shù)完成的柄慰,它會(huì)遍歷 AST,并根據(jù) AST 節(jié)點(diǎn)生成相應(yīng)的操作(op)税娜。例如坐搔,ModuleAST 會(huì)被轉(zhuǎn)換為 ModuleOp,F(xiàn)unctionAST 會(huì)被轉(zhuǎn)換為 FuncOp敬矩,CallExprAST 會(huì)被轉(zhuǎn)換為 GenericCallOp 等概行。

我們以 GenericCallOp 為例,來看看它是如何進(jìn)行轉(zhuǎn)換的:

  /// Emit a call expression. It emits specific operations for the `transpose`
  /// builtin. Other identifiers are assumed to be user-defined functions.
  mlir::Value mlirGen(CallExprAST &call) {
    llvm::StringRef callee = call.getCallee();
    auto location = loc(call.loc());

    // Codegen the operands first.
    SmallVector<mlir::Value, 4> operands;
    for (auto &expr : call.getArgs()) {
      auto arg = mlirGen(*expr);
      if (!arg)
        return nullptr;
      operands.push_back(arg);
    }

    ......

    // Otherwise this is a call to a user-defined function. Calls to
    // user-defined functions are mapped to a custom call that takes the callee
    // name as an attribute.
    return builder.create<GenericCallOp>(location, callee, operands);
}

在轉(zhuǎn)換過程中弧岳,mlirGen() 函數(shù)從 AST 的調(diào)用節(jié)點(diǎn)中提取了它在源碼中的位置信息(location)凳忙、被調(diào)用函數(shù)名(callee)以及要傳遞的參數(shù)(operands)。然后缩筛,通過調(diào)用 builder.create<GenericCallOp>(location, callee, operands) 創(chuàng)建了調(diào)用操作消略,并將其添加到 IR 中。

location 是創(chuàng)建 MLIR IR 所必需的元素瞎抛,它在錯(cuò)誤報(bào)告艺演、調(diào)試和優(yōu)化等方面提供了更準(zhǔn)確和有用的信息,這是 MLIR 與其他編譯器的區(qū)別之一桐臊。

mlir::Value 表示操作的輸入和輸出值胎撤,它是一種通用的值類型,可以表示多種不同的數(shù)據(jù)類型断凶,例如標(biāo)量伤提、向量和張量等。mlirGen() 函數(shù)通過遞歸的方式找到輸入的葉子節(jié)點(diǎn)(LiteralExprAST)认烁,并為其構(gòu)建 ConstantOp肿男,從而生成相應(yīng)的 mlir::Value介汹。

Module dump

在完成 IR 轉(zhuǎn)換后,Ch2 示例通過調(diào)用 module->dump() 將 IR 打印出來舶沛。這個(gè)過程涉及到操作(op)的 parse()print() 方法的調(diào)用嘹承。使用 tablegen 生成的操作類不僅包含聲明和實(shí)現(xiàn),還生成了一些通用的方法如庭,用于校驗(yàn)叹卷、打印和轉(zhuǎn)換等操作。也就是說坪它,parse() 和 print() 方法默認(rèn)會(huì)由 tablegen 自動(dòng)生成骤竹。如果在操作定義中聲明了 let hasCustomAssemblyFormat = 1,則需要在 Dialect.cpp 中自己實(shí)現(xiàn)這些方法往毡。

總結(jié)

Chapter 2 介紹了如何為 Toy 語言開發(fā) MLIR 方言蒙揣,并將生成的抽象語法樹(AST)轉(zhuǎn)換為 MLIR 中的自定義方言。方言允許為操作賦予新的語義卖擅,擴(kuò)展了 IR 的功能鸣奔。使用聲明的方式定義操作,并通過 tablegen 自動(dòng)生成基于Operation Definition Specification (ODS) 框架的操作類的聲明和實(shí)現(xiàn)惩阶。最后通過 mlirGen() 將 AST 轉(zhuǎn)換為 MLIR IR挎狸。

END

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市断楷,隨后出現(xiàn)的幾起案子锨匆,更是在濱河造成了極大的恐慌,老刑警劉巖冬筒,帶你破解...
    沈念sama閱讀 211,948評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件恐锣,死亡現(xiàn)場離奇詭異,居然都是意外死亡舞痰,警方通過查閱死者的電腦和手機(jī)土榴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,371評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來响牛,“玉大人玷禽,你說我怎么就攤上這事⊙酱颍” “怎么了矢赁?”我有些...
    開封第一講書人閱讀 157,490評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長贬丛。 經(jīng)常有香客問我撩银,道長,這世上最難降的妖魔是什么豺憔? 我笑而不...
    開封第一講書人閱讀 56,521評(píng)論 1 284
  • 正文 為了忘掉前任额获,我火速辦了婚禮够庙,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘咪啡。我一直安慰自己首启,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,627評(píng)論 6 386
  • 文/花漫 我一把揭開白布撤摸。 她就那樣靜靜地躺著,像睡著了一般褒纲。 火紅的嫁衣襯著肌膚如雪准夷。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,842評(píng)論 1 290
  • 那天莺掠,我揣著相機(jī)與錄音衫嵌,去河邊找鬼。 笑死彻秆,一個(gè)胖子當(dāng)著我的面吹牛楔绞,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播唇兑,決...
    沈念sama閱讀 38,997評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼酒朵,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了扎附?” 一聲冷哼從身側(cè)響起蔫耽,我...
    開封第一講書人閱讀 37,741評(píng)論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎留夜,沒想到半個(gè)月后匙铡,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,203評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡碍粥,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,534評(píng)論 2 327
  • 正文 我和宋清朗相戀三年鳖眼,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嚼摩。...
    茶點(diǎn)故事閱讀 38,673評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡钦讳,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出低斋,到底是詐尸還是另有隱情蜂厅,我是刑警寧澤,帶...
    沈念sama閱讀 34,339評(píng)論 4 330
  • 正文 年R本政府宣布膊畴,位于F島的核電站掘猿,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏唇跨。R本人自食惡果不足惜稠通,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,955評(píng)論 3 313
  • 文/蒙蒙 一衬衬、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧改橘,春花似錦滋尉、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,770評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至碌识,卻和暖如春碾篡,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背筏餐。 一陣腳步聲響...
    開封第一講書人閱讀 32,000評(píng)論 1 266
  • 我被黑心中介騙來泰國打工开泽, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人魁瞪。 一個(gè)月前我還...
    沈念sama閱讀 46,394評(píng)論 2 360
  • 正文 我出身青樓穆律,卻偏偏與公主長得像,于是被迫代替她去往敵國和親导俘。 傳聞我的和親對象是個(gè)殘疾皇子峦耘,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,562評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容