想象你走进一家面包工厂。面粉、水、酵母堆在仓库里,但它们不会自动变成面包——需要经过一道道工序:和面、发酵、整形、烘烤、冷却、包装。每一道工序只做一件事,但所有工序必须按照精确的顺序衔接,前一道没完成,后一道就不能开始。如果有两道工序互相依赖,比如“烘烤”必须等“整形”完成,工厂主管就得在排产表上仔细安排。 MLIR编译器里的Pass流水线,就是这座面包工厂的排产系统。在MLIR框架中,代码(中间表示,即IR)就像刚从仓库拿出的原料,而每个Pass就是一道加工工序——有的负责化简表达式(相当于把面粉过筛),有的负责消除冗余计算(相当于把面团里的气泡压掉)。Pass流水线的作用,就是把这一道道Pass按正确顺序串起来,让编译器自动执行,最终产出高效的机器码。这个过程就像工厂主管拿着排产表,指挥各道工序有条不紊地完成面包生产。

岗位关联

掌握Pass流水线管理是AI编译器开发者的核心技能。无论是为深度学习模型定制优化流程,还是调试某个Pass为何没生效,都离不开对Pass注册、依赖和调度机制的理解。学会它,你就能像工厂主管一样,灵活编排各种优化“工序”。

目录
  1. 可视化演示
  2. 流水线概念
  3. Pass注册管理
  4. 流水线构建
  5. 依赖关系管理
  6. 执行调度机制
  7. Pipeline工具
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

展示Pass流水线执行流程、嵌套流水线作用域隔离、以及Pass调度与分析缓存机制。切换模式理解编译器优化变换管理。

本节配有交互动效(进入视口后自动加载,约 3KB)。

Pass流水线执行流程 PassManager按序执行Pass序列,每个Pass对IR做变换:规范化→内联→CSE→ lowering逐步优化 OperationPass针对单Operation,ModulePass针对整个模块,通过PassInfo声明依赖确保正确执行 IR → Pass1 → Pass2 → ... → optimized IR 流水线管理是编译器优化的核心

流水线概念

面包工厂的排产表上,每一道工序都写得清清楚楚:和面必须在发酵之前,发酵必须在整形之前。MLIR的Pass流水线,就是编译器世界里的排产表。这里的“原料”是MLIR中间表示(IR),每一道“工序”就是一个Pass——它接收一份IR,做一次特定变换,然后输出新的IR给下一道工序。

为什么需要流水线?因为单个Pass的能力是有限的。就像一道“和面”工序不可能同时完成发酵和烘烤,一个Pass也只专注于一类优化——有的负责常量折叠(把能预先算好的表达式直接算掉),有的负责死代码消除(删掉永远用不到的代码)。把这些Pass串成流水线,就能积少成多,让代码一步步趋于高效。

流水线的核心特征是串行:Pass按顺序执行,前一个Pass的输出就是后一个Pass的输入。这个设计让每个Pass都保持简单、可复用,就像工厂里每个工人都只精通一道工序,但整条生产线却能做出完美的面包。

回到面包工厂的场景:排产表上每个工序就是一个Pass,从原料到成品要经过完整链条。理解了这个概念,你就掌握了Pass流水线的核心直觉——串行接力,各司其职。

IR0→Pass1IR1→Pass2IR2→Pass3⋯→PassnIRn\text{IR}_0 \xrightarrow{\text{Pass}_1} \text{IR}_1 \xrightarrow{\text{Pass}_2} \text{IR}_2 \xrightarrow{\text{Pass}_3} \cdots \xrightarrow{\text{Pass}_n} \text{IR}_n
这个链条表示一份IR从进入流水线到产出的完整旅程,每个箭头代表一个Pass的变换。
  1. 初始IR表示未经优化的中间表示,相当于从仓库领出的原始面粉。
    IR0\text{IR}_0
  2. 第一个Pass对IR施加第一次变换,相当于和面工序对原料的第一次加工。
    →Pass1\xrightarrow{\text{Pass}_1}
  3. 变换后的IR第一个Pass的输出,是第二个Pass的输入,体现流水线的接力关系。
    IR1\text{IR}_1
  4. 持续变换流水线不断重复“施加变换、产生新IR”的过程,直到所有Pass执行完毕。
    →Pass2IR2→Pass3⋯→PassnIRn\xrightarrow{\text{Pass}_2} \text{IR}_2 \xrightarrow{\text{Pass}_3} \cdots \xrightarrow{\text{Pass}_n} \text{IR}_n

Pass注册管理

工厂里每个工序都要登记在册,主管才能知道厂里有哪些加工能力。MLIR里,每个Pass都要经过注册才能在流水线上使用。注册的过程,就是把Pass的“名字”和“构造函数”登记到一个全局注册表(Global Registry)中。

在代码层面,MLIR提供了一个宏 PassRegistration 来完成注册。当程序启动时,所有注册过的Pass会自动加入全局注册表,就像新设备启用时自动列入工厂资产清单。这样做的直接好处是:通过命令行工具输入Pass的名字,就能找到对应的Pass并执行它——不需要改动工具源码。

工厂主管查看登记册就知道有哪些工序可用;编译器开发者在命令行敲 mlir-opt --help,就能看到所有已注册的Pass清单。注册是Pass进入流水线的“准生证”,没有注册的Pass在流水线上无从谈起。

Pass→PassRegistrationRegistry\text{Pass} \xrightarrow{\text{PassRegistration}} \text{Registry}
注册操作把一个Pass实例的创建能力注册到全局注册表,与使用者打招呼:“我在这里,随时可以调度我。”
  1. Pass类定义一个继承自Pass基类的自定义Pass,完成具体变换逻辑。
    MyPass\text{MyPass}
  2. 注册声明通过PassRegistration模板声明Pass的名称和描述信息。
    →PassRegistration\xrightarrow{\text{PassRegistration}}
  3. 全局注册表MLIR框架维护的一个全局映射表,把Pass名称映射到其构造函数。
    Registry\text{Registry}
cpp3 行
void registerMyPass() {  PassRegistration<MyPass> registration("my-pass", "My custom optimization pass");}
cpp2 行
// 注册的Pass可以通过命令行工具查询和使用mlir-opt input.mlir --my-pass

流水线构建

有了注册好的Pass,接下来就是排产:把它们编排成一条流水线。MLIR使用 OpPassManager 来管理这条流水线,它负责按照给定的顺序调度Pass作用于IR。

构建流水线有两种典型方式。一种是在C++代码中手动向 OpPassManager 添加Pass;另一种更常用——通过--pass-pipeline 命令行参数,用一段字符串描述整条流水线。语法简单直观,就像在排产表上按顺序写工序名。

这条命令的意思:在 builtin.func(函数级操作)上依次执行 canonicalize(规范化)、fold-constants(常量折叠)和 cse(公共子表达式消除)三个Pass。外层 builtin.func(...) 表示这些Pass作用在函数级别,这就是嵌套流水线的雏形——在模块级流水线下,每个函数又有自己的子流水线,类似工厂里每条产线又有多个工位集群。

回到排产表:主管按工艺要求排出工序顺序,并划分成不同车间(嵌套流水线)。流水线构建的本质就是选择哪些Pass、按什么顺序执行,这个选择直接影响最终的优化效果。

Pipeline=Pass1+Pass2+⋯+Passn\text{Pipeline} = \text{Pass}_1 + \text{Pass}_2 + \cdots + \text{Pass}_n
流水线就是Pass的有序组合。顺序至关重要,不同的排列会产生不同的优化效果。
  1. Pass集合从注册表中选择需要的Pass,构成流水线的候选集。
    {Pass1,Pass2,… }\{\text{Pass}_1, \text{Pass}_2, \dots\}
  2. 排序根据优化目标和依赖关系确定Pass的执行顺序。
    Order(Passi)\text{Order}(\text{Pass}_i)
  3. 流水线生成将排序好的Pass序列封装为OpPassManager,供编译器执行引擎调用。
    OpPassManager=Pass1∘Pass2∘⋯∘Passn\text{OpPassManager} = \text{Pass}_1 \circ \text{Pass}_2 \circ \cdots \circ \text{Pass}_n
text1 行
mlir-opt input.mlir --pass-pipeline="builtin.func(canonicalize,fold-constants,cse)"

依赖关系管理

工厂里“烘烤”依赖“整形”完成,“包装”依赖“冷却”完成,工序之间有严格的先后依赖。MLIR的Pass之间也存在依赖关系——有的Pass必须在另一个Pass之后运行才能发挥作用。

比如,一个“循环展开”Pass可能依赖之前的“循环规范化”Pass把循环整理成统一格式。如果顺序颠倒,后面的Pass可能无法识别输入,或者产生错误结果。MLIR通过在Pass类中声明依赖来管理这种关系:定义Pass时可以声明它依赖哪些Pass或哪些IR特性。

MLIR会分析依赖关系,并提供一个验证器检查流水线是否满足依赖约束。如果构建的流水线违反了依赖关系(比如把依赖者放在被依赖者之前),验证器就会报错,就像工厂主管发现“烘烤”排在“整形”前面,立刻叫停产线一样。

A≺B表示A must run before BA \prec B \quad \text{表示} \quad A \text{ must run before } B

1. 依赖声明:Pass在定义时声明自己依赖的其他Pass或IR特性。

Dependencies(B)={A}\text{Dependencies}(B) = \{A\}

2. 依赖分析:框架分析流水线中所有Pass的依赖关系,构建有向依赖图。

DAG=(V,E)\text{DAG} = (V, E)

3. 顺序验证:验证器检查流水线顺序是否与依赖图一致。

Valid  ⟺  ∀(A,B)∈E,pos(A)<pos(B)\text{Valid} \iff \forall (A, B) \in E, \text{pos}(A) < \text{pos}(B)

依赖管理就像是工厂的工艺规范:主管排产时必须先查工艺卡,确保每道工序的前置条件已满足。依赖管理保障了Pass流水线的正确性——顺序对了,每道工序才能在自己需要的条件下工作。

执行调度机制

排产表定好了,接下来就是让流水线跑起来。MLIR的执行调度机制负责控制Pass如何被依次执行,包括何时停下来。这其中最关键的概念是收敛(convergence)。

很多Pass是“迭代式”的:比如常量折叠——折叠完一层常量后,可能又暴露出新的常量折叠机会。就像面包需要反复揉压才能把气泡排干净。因此,MLIR的PassManager支持重复执行直到IR不再发生变化(即达到“不动点”)。每次执行后,框架会比较IR是否变化,如果没变化就停止,避免无限循环。

调度时,PassManager按流水线的顺序逐站执行,每个Pass作用于对应的IR单元。有些Pass作用在整个模块上(module-level),有些作用在单个函数上(function-level),调度器会根据Pass的粒度分配合适的IR单元,就像生产线上的分配器把半成品分配到不同的工位。

调度机制确保流水线既不会提前停产(优化未充分),也不会无限空转(已收敛还继续跑)。调度是流水线的时间轴——它决定了每一步何时发生、何时结束。

FixPoint:IRk+1=Pass(IRk)一旦IRk+1=IRk停止\text{FixPoint}: \text{IR}_{k+1} = \text{Pass}(\text{IR}_k) \quad \text{一旦} \quad \text{IR}_{k+1} = \text{IR}_k \quad \text{停止}
不动点原理:当一轮Pass执行后IR没有变化,说明优化已经饱和,继续执行不会带来新收益,因此停止迭代。
  1. Pass应用将当前Pass作用于IR的某个操作单元。
    IR′=Pass(IR)\text{IR}' = \text{Pass}(\text{IR})
  2. 变化检测比较执行前后IR是否发生变化。
    Δ=IR′−IR\Delta = \text{IR}' - \text{IR}
  3. 收敛判断如果$\Delta$为零,即IR无变化,说明收敛,执行下一Pass;否则可能重复执行。
    Δ=0⇒Converged⇒next pass\Delta = 0 \Rightarrow \text{Converged} \Rightarrow \text{next pass}

Pipeline工具

要管理好流水线,必须能“看见”流水线的运行过程。MLIR提供了 mlir-opt 工具,这是编译器开发者的“工厂监控台”——通过它执行任意Pass流水线,并观察每一步IR的变化。

使用 mlir-opt 的典型做法是:输入一份 .mlir 文件(包含待优化的IR),通过命令行参数指定要执行的流水线,工具会输出变换后的IR。开发者可以对比变换前后的IR,确认每个Pass是否按预期工作。如果某个Pass优化效果不佳,可以直接调整流水线顺序,或者增删Pass,立即看到结果。

mlir-opt 还支持 --mlir-print-ir-after-all 这样的大杀器——在流水线中每个Pass执行后都打印一次IR,让开发者像看监控回放一样观察IR的逐步演变。性能分析时,还可以结合 --mlir-timing 查看每个Pass耗时,找出流水线的性能瓶颈(就像工厂主管统计每道工序的耗时,找出拖慢产线的环节)。

Input IR+Pipeline=Output IR\text{Input IR} + \text{Pipeline} = \text{Output IR}

1. 输入读取:解析 .mlir 文件,将其加载为内存中的IR表示。

Input:file.mlir→IR\text{Input}: \text{file.mlir} \rightarrow \text{IR}

2. 流水线应用:按照命令行指定的流水线依次执行Pass。

IR→pipelineIR′\text{IR} \xrightarrow{\text{pipeline}} \text{IR}'

3. 输出生成:将结果写为标准输出或文件,供开发者检查或进一步处理。

Output:IR′→stdout\text{Output}: \text{IR}' \rightarrow \text{stdout}

工厂主管通过监控台观察产线运行,AI编译器工程师通过 mlir-opt 观察IR变换。pipeline工具是调试流水线的眼睛,没有它,Pass的执行过程就像黑箱一样难以捉摸。

text5 行
# 查看每个Pass执行后的IR(逐段打印)mlir-opt input.mlir --pass-pipeline="canonicalize,cse" --mlir-print-ir-after-all # 只运行某个Pass并打印变换前IRmlir-opt input.mlir --pass-pipeline="canonicalize" --mlir-print-ir-before-all

AI 应用场景

AI编译器优化Pipeline定制

阶段:模型编译优化

解决的问题:深度学习模型(如BERT、ResNet)在部署到AI芯片时,默认的优化流程并不能充分挖掘硬件性能。传统做法是手工尝试各种优化组合,费时费力且容易出错。利用MLIR的Pass流水线,开发者可以针对目标硬件的特性定制优化流水线,将通用优化与硬件特定优化灵活编排。

具体案例:某AI芯片公司需要将PyTorch导出的ONNX模型部署到自研NPU上。编译器团队用MLIR构建了一条定制流水线:先执行 canonicalize 规范化IR,再做算子融合(把多个小算子合并成大算子),然后做硬件特定的内存布局转换,最后执行循环展开和向量化。通过反复调整流水线顺序(比如先算子融合再做常量折叠),模型推理性能提升了3倍。整个过程使用 mlir-opt 加 --pass-pipeline 参数快速迭代验证,每次调整只需几十秒就能看到效果。

编译器Pass性能瓶颈诊断

阶段:编译器性能调优

解决的问题:在大型AI模型编译过程中,某些Pass耗时异常高,拖累整个编译周期。传统做法是靠日志和人工猜测定位瓶颈,效率极低。MLIR提供的 --mlir-timing 功能可以统计每个Pass的耗时,帮助开发者快速定位瓶颈环节。

具体案例:某团队在编译一个大规模Transformer模型时发现编译时间过长。通过 mlir-opt --mlir-timing 查看各Pass耗时分布,发现某个循环向量化Pass耗时占总编译时间60%。进一步分析发现该Pass作用在超大函数上,产生了过多次迭代。团队通过在流水线前面增加一个超大函数拆分Pass,将函数拆小后再做向量化,编译时间从2小时降至20分钟,模型质量不变。

Python 演示

python124 行
"""Simplified Pass Pipeline ManagerA toy example inspired by MLIR PassPipeline management."""import time  class Pass:    """Base class for all passes."""    def __init__(self, name):        self.name = name     def run(self, ir):        """Apply transformation to IR. Subclass should override."""        return ir  class ConstantFoldPass(Pass):    def __init__(self):        super().__init__("constant-fold")     def run(self, ir):        new_ir = ir.replace("3 + 4", "7")        return new_ir  class DeadCodeElimPass(Pass):    def __init__(self):        super().__init__("dce")     def run(self, ir):        lines = [l for l in ir.split("\n") if "unused" not in l]        return "\n".join(lines)  class CSEPass(Pass):    def __init__(self):        super().__init__("cse")     def run(self, ir):        # Remove duplicate lines (naive CSE)        seen = set()        result = []        for line in ir.split("\n"):            line_stripped = line.strip()            if line_stripped and line_stripped in seen:                continue            seen.add(line_stripped)            result.append(line)        return "\n".join(result)  class PassRegistry:    """Global pass registry."""    def __init__(self):        self.registry = {}     def register(self, pass_instance):        self.registry[pass_instance.name] = pass_instance     def create(self, name):        if name not in self.registry:            raise KeyError(f"Pass '{name}' not registered!")        return self.registry[name]  class PassPipeline:    """A pipeline that runs passes sequentially."""    def __init__(self, pass_names, registry):        self.passes = [registry.create(n) for n in pass_names]     def run(self, ir, max_iter=3):        """Run pipeline to fixpoint or max_iter."""        cur_ir = ir        for iteration in range(max_iter):            changed = False            for p in self.passes:                before = cur_ir                cur_ir = p.run(cur_ir)                if cur_ir != before:                    changed = True                    print(f"  [iter {iteration+1}] {p.name}: IR changed")            if not changed:                print(f"  Converged after {iteration+1} iteration(s).")                break        return cur_ir  def main():    # Build global registry (like PassRegistration)    registry = PassRegistry()    registry.register(ConstantFoldPass())    registry.register(DeadCodeElimPass())    registry.register(CSEPass())     # Sample IR    sample_ir = """func @main() {  %0 = constant 3 + 4  %1 = constant 3 + 4  %2 = addi %0, %0  %unused = muli %2, %2  return %2}"""     # Build pipeline from string    pipeline_desc = "constant-fold,cse,dce"    pipeline = PassPipeline(pipeline_desc.split(","), registry)     # Run    print("=== Running pipeline ===")    opt_ir = pipeline.run(sample_ir)    print("\n=== Optimized IR ===")    print(opt_ir)     # Show all registered passes (like mlir-opt --help)    print("\n=== Registered passes ===")    for name in registry.registry:        print(f"  {name}")  if __name__ == "__main__":    main()
目的:用纯Python模拟MLIR Pass流水线的核心机制——注册表管理Pass、按字符串描述构建流水线、迭代执行直到收敛。
  1. registry.register(pass_instance)注册Pass实例,相当于MLIR中的PassRegistration登记步骤。
  2. pass_names.split(",")用字符串描述流水线,模仿MLIR的--pass-pipeline参数风格。
  3. for p in self.passes按顺序逐个执行Pass,体现流水线的串行特征。
  4. if cur_ir != before: changed = True检测IR是否有变化,这是收敛判断的核心逻辑。
  5. if not changed: breakIR无变化即收敛,停止迭代,模拟MLIR的不动点检测。

常见误区

误区流水线里的Pass随便排都能达到同样的优化效果,反正都是做优化。

正解Pass顺序至关重要。就像面包工厂必须先和面再烘烤,颠倒工序会做出“烤面块”。在MLIR中,canonicalize 经常需要放在其他优化之前,因为它把IR整理成规范形式,后续Pass才能识别和匹配。依赖关系验证器存在的意义,就是防止这种顺序错误。

为何常见:初学时看到的示例流水线往往只有两三个Pass,顺序问题不明显。但真实项目中十多个Pass交错编排,顺序错误可能导致Pass完全失效甚至产生错误代码。

误区写完 `PassRegistration` 注册代码,就以为这个Pass会自动运行。

正解注册只是把Pass“登记在册”,相当于工厂设备入库,不代表它已在生产线上运转。要让Pass生效,必须在流水线中显式指定它(通过 --pass-pipeline 或代码手动添加),并且确保它被编排进去。

为何常见:“注册”这个术语容易让人联想到“激活”或“开始”,实际上它只是完成了“可被发现”这一步,离“被执行”还有流水线编排的距离。

误区把能用上的优化Pass全塞进流水线,总能把代码优化到极致。

正解流水线的长度与效果并不成正比。冗余的Pass会增加编译时间,某些Pass之间可能产生冲突(一个Pass的变换破坏了另一个Pass已优化的结果),甚至在某些场景下引入负优化。正确的做法是根据目标硬件和模型特性,精选Pass并反复测试调优。

为何常见:直观感觉“多就是好”,但优化领域讲究“恰到好处”,一个不必要的Pass可能让编译时间翻倍而收益为零。

误区PassManager反复执行Pass直到收敛,会不会因为每次都有微小变化而永远跑不完?

正解理论上确实需要考虑这一点。MLIR的PassManager通过收敛检测来终止循环——当一轮执行后IR无变化就停止。但对于设计不良的Pass(比如每次执行都给常量加1),理论上可能不收敛。因此依赖开发者的良好设计+框架的收敛检测,双保险确保终止。

为何常见:初学者对“不动点”概念不熟悉,担心迭代机制失控。实际上框架的收敛检测和最大迭代次数限制,让这个担心在正常使用中不会发生。

面试题

Q1. MLIR中Pass流水线的基本执行方式是?

单选题 · easy

  • A.并行执行所有Pass
  • B.按顺序串行执行每个Pass← 正确答案
  • C.随机选择Pass执行
  • D.只执行第一个Pass

答案:B。Pass流水线模仿工厂生产线,Pass按预先编排的顺序依次执行,前一个Pass的输出是后一个Pass的输入,形成串行执行链。并行(A)不是流水线的基本模式,随机(C)会破坏依赖关系,只执行第一个(D)则无法完成完整优化。

Q2. 在MLIR中注册Pass使用的机制是?

单选题 · easy

  • A.PassRegistration注册← 正确答案
  • B.手动修改编译器源码
  • C.写配置文件加载
  • D.通过环境变量指定

答案:A。MLIR通过PassRegistration模板将Pass注册到全局注册表,程序启动后即可被识别和使用。修改源码(B)不便于扩展,配置文件(C)和环境变量(D)都不是MLIR的Pass注册机制。

Q3. 检查流水线中Pass顺序是否满足依赖关系的组件是?

单选题 · easy

  • A.编译器前端
  • B.Pass验证器← 正确答案
  • C.链接器
  • D.调试器

答案:B。MLIR提供验证器(verifier)检查流水线的依赖约束,类似工厂主管对照工艺卡检查工序顺序。编译器前端(A)负责解析源码,链接器(C)负责链接目标文件,调试器(D)用于排查程序错误。

Q4. Pass执行到IR不再变化时,框架会?

单选题 · mid

  • A.继续执行直到超时
  • B.立即报错终止
  • C.判定收敛并停止迭代← 正确答案
  • D.自动增加新Pass再执行

答案:C。当一轮Pass执行后IR无变化,说明优化已达到不动点,框架判定收敛并停止迭代。这模仿了工厂中“揉面到面团不再变化就停止”的直觉,避免无限循环。

Q5. 下面哪种方式可以指定MLIR流水线?

单选题 · mid

  • A.--pass-pipeline 命令行参数← 正确答案
  • B.修改BIOS设置
  • C.调整操作系统内核参数
  • D.改变CPU主频

答案:A。--pass-pipeline 是MLIR标准做法,用字符串描述流水线并传给mlir-opt等工具。BIOS(B)、内核参数(C)、CPU主频(D)都是系统层面配置,与编译器Pass编排无关。

Q6. canonicalize Pass通常放在流水线什么位置?

单选题 · mid

  • A.放在任意位置都没关系
  • B.放在流水线开头先规范化IR← 正确答案
  • C.放在流水线末尾做收尾
  • D.单独运行不能与其他Pass组合

答案:B。canonicalize 把IR整理成规范形式,为后续Pass匹配和识别创造条件,所以通常放在流水线靠前位置。类似工厂先整理原料再加工。

Q7. 嵌套流水线在MLIR中如何表示?

单选题 · hard

  • A.无法实现嵌套流水线
  • B.在操作类型后用括号包含子流水线← 正确答案
  • C.需要修改编译器内核
  • D.只能通过编写C++插件实现

答案:B。MLIR用builtin.func(canonicalize,cse)类似语法表示在函数级别嵌套子流水线。外层括号指定作用范围,内层列出子流水线中的Pass,类似工厂中的车间下又有工位分组。

Q8. 以下哪些是Pass流水线的组成部分?

多选题 · mid

  • A.Pass注册表← 正确答案
  • B.OpPassManager管理器← 正确答案
  • C.流水线语法描述← 正确答案
  • D.操作系统调度器

答案:ABC。Pass注册表负责管理Pass注册信息,OpPassManager负责编排执行,流水线语法(如--pass-pipeline字符串)用于描述流水线结构。操作系统调度器(D)是系统资源管理层面的,与编译器内部Pass流水线无关。

Q9. Pass依赖管理的主要作用有哪些?

多选题 · mid

  • A.确保Pass执行顺序正确← 正确答案
  • B.检查流水线的合法性← 正确答案
  • C.提高编译器的编译速度
  • D.防止违反依赖关系的调度← 正确答案

答案:ABD。依赖管理(A)明确Pass的先后关系,(B)通过验证器检查流水线合法性,(D)防止错误顺序导致问题。但依赖管理与编译速度(C)无直接关系,编译速度主要取决于流水线的规模和实现效率。

Q10. mlir-opt工具的--mlir-print-ir-after-all参数用途是?

单选题 · hard

  • A.在流水线中每个Pass执行后打印IR← 正确答案
  • B.打印所有注册Pass的列表
  • C.输出编译时间统计信息
  • D.显示IR的抽象语法树

答案:A。--mlir-print-ir-after-all 在每个Pass执行后打印IR快照,让开发者像看监控回放一样观察IR演变。打印Pass列表是--help,输出编译时间是--mlir-timing,显示抽象语法树是其他工具的功能。