LLVM 编译器学习笔记之三十六-- 指令调度Instruction scheduling

1、参考Instruction scheduling in LLVM - 知乎,在中、后端均存在指令调度

GenericScheduler:: 做寄存器压力感知的指令调度

PostGenericScheduler:: 寄存器分配后的指令调度,基于BB的指令调度

LLVM 编译器学习笔记之三十六-- 指令调度Instruction scheduling_第1张图片

 2、在llvm12->llvm14 中Machine Instruction Scheduler有个patch af342f7240增强了load/store指令的合并,也就是指令调度不仅仅只是改变指令的顺序,该优化类似gcc中的store-merge优化

LLVM 编译器学习笔记之三十六-- 指令调度Instruction scheduling_第2张图片

注意:后端isPairableLdStInst接口可以指示是否允许寄存器对的优化,一般来说这个合并是有利的,也不能排除个别硬件存在bug

3、指令调度*.P指Pend, *.A为ready的指令,调度时从Bot和Top两端选取,最后将在某个中间位置相遇时结束调度;另外BotQ.A中的指令会被优先考虑发射(P 代表Pending, A 代表Available, 参考SchedBoundary::pickOnlyChoice)

LLVM 编译器学习笔记之三十六-- 指令调度Instruction scheduling_第3张图片

4、指令中隐式的定义需要被列在let Defs = []中,参考⚙ D123578 [RISCV] Add sched to pseudo function call instructions

let isCall = 1, Defs = [LR, X0, X1], hasSideEffects = 1, Size = 16,
    isCodeGenOnly = 1 in
def TLSDESC_CALLSEQ
    : Pseudo<(outs), (ins i64imm:$sym),
             [(AArch64tlsdesc_callseq tglobaltlsaddr:$sym)]>,
      Sched<[WriteI, WriteLD, WriteI, WriteBrReg]>;

AArch64::TLSDESC_CALLSEQ lower to

adrp  x0, :tlsdesc:var

ldr   x1, [x0, #:tlsdesc_lo12:var]

add   x0, x0, #:tlsdesc_lo12:var

blr   x1 # bl 指令会将程序的返回地址放到LR寄存器中 

5、 乱序执行OOO也需要进行指令调度优化,不是任意顺序性能都一样的

硬件有一个instruction window, 假如这个window无限大,compiler不管怎么写指令都无所谓。但实际上这个window只有80-100个指令,所以当有很深的parallel loop时,compiler如何写指令就会对性能有影响了

6、实现指令调度,确保stp指令按照升序排列输出(寄存器分配后的指令调度简单从ReadyQueue队列中获取一个指令,然后和当前Cand进行比较,如果需要调整即更新Cand, 因此pickNodeFromQueue函数返回后Cand值即为最优先需要发射的指令)

⚙ D125377 [AArch64] Order STP Q's by ascending address (llvm.org)

LLVM 编译器学习笔记之三十六-- 指令调度Instruction scheduling_第4张图片

注意:指令调度的模型SchedModel=mf.getSubtarget()和后端架构配置相关LLVM 编译器学习笔记之三十六-- 指令调度Instruction scheduling_第5张图片

7、 在-mattr=+slow-paired-128选项说明128位的loadstore不好可能是一个共性问题,commit 7784cacd9,涉及AArch64InstrInfo::isCandidateToMergeOrPair函数

 203 def FeatureSlowPaired128 : SubtargetFeature<"slow-paired-128",
 204     "Paired128IsSlow", "true", "Paired 128 bit loads and stores are slow">;

usage:clang  -Xclang -target-feature -Xclang +slow-paired-128 或者llc -mattr=+slow-paired-128

代码中选项使用方法,参考MF.getSubtarget().hardenSlsBlr() (注:需要头文件#include "AArch64Subtarget.h")

8、指令调度模型在 llvm/lib/Target/AArch64/AArch64.td中设置,参考D89972 (之后在D120906中进行了重构),也就是当前指定使用ProcTSV110对应的模型配置

当仅仅使用 -march=armv8.2-a 未指定tsv110时使用默认"generic"对应的CortexA55Model

:def : ProcessorModel<"generic", CortexA55Model, ProcessorFeatures.Generic
+def : ProcessorModel<"tsv110", TSV110Model, [ProcTSV110]>;

9、GenericSchedulerBase::shouldReduceLatency  确认指令的时序是否要减少?(已经大于关键路径)

10、in-order和out-of-order的关键配置(相关处理CodeGen/MachineScheduler.cpp)

Target/RISCV/RISCVSchedSiFive7.td:13:  let MicroOpBufferSize = 0; // Explicitly set to zero since SiFive7 is in-order

LLVM 编译器学习笔记之三十六-- 指令调度Instruction scheduling_第6张图片

11、将循环中的LoopMicroOpBufferSize值调小一点,参考72a799a6

12、Load/Store的时延分析特别处理,参考D8705,load->store的时延只有1个cycle?LLVM 编译器学习笔记之三十六-- 指令调度Instruction scheduling_第7张图片

13、指令调度中每个指令被匹配为一个SUnit结点,根据top-down topological order计算两个指令的顺序,也就是AA的边(即A可以在B前发射),参考ScheduleDAGInstrs::initSUnits

SU->Latency = SchedModel.computeInstrLatency(SU->getInstr())

14、通过ARMTargetLowering::getSchedulingPreference设置特殊指令的调度模式(用于寄存器分配前的指令调度)

enum Preference {
  None,        // No preference
  Source,      // Follow source order.
  RegPressure, // Scheduling for lowest register pressure.
  Hybrid,      // Scheduling for both latency and register pressure.
  ILP,         // Scheduling for ILP in low register pressure mode.
  VLIW,        // Scheduling for VLIW targets.
  Fast,        // Fast suboptimal list scheduling
  Linearize    // Linearize DAG, no scheduling
};

15、实现模型的定义,比如将 WriteV进一步区分WriteVd和WriteVq的描述,参考D108766

16、使用llvm-mca -mtriple=aarch64 -mcpu=tsv110 -instruction-tables < test.s可以显示编译器定义的时序信息,方便和文档做比较,参考D128631 (Gcc 侧的定义commit 25095d1ef8)

llvm/utils/update_mca_test_checks.py --llvm-mca-binary=build/bin/llvm-mca xx.s

Instruction Info:
[1]: #uOps
[2]: Latency
[3]: RThroughput
[4]: MayLoad
[5]: MayStore
[6]: HasSideEffects (U)

[1]    [2]    [3]    [4]    [5]    [6]    Instructions:
 2      5     1.00    *                   ldp    s7, s16, [x24, #-8]
 1      1     0.50                        add    x13, sp, #272

Resources:
[0]   - A57UnitB
[1.0] - A57UnitI
[1.1] - A57UnitI
[2]   - A57UnitL
[3]   - A57UnitM
[4]   - A57UnitS
[5]   - A57UnitW
[6]   - A57UnitX


Resource pressure per iteration:
[0]    [1.0]  [1.1]  [2]    [3]    [4]    [5]    [6]    
 -     3.00   3.00   7.00    -      -     20.50  3.50   

Resource pressure by instruction:
[0]    [1.0]  [1.1]  [2]    [3]    [4]    [5]    [6]    Instructions:
 -      -      -     1.00    -      -      -      -     ldp    s7, s16, [x24, #-8]
 -     0.50   0.50    -      -      -      -      -     add    x13, sp, #272

17、调试 --misched-only-func=foo --misched-only-block=9 指定函数foo的第9个BB不调度

18、 throughput是根據資源數量和指令時延算出來,参考M5WriteVSTI,如果没有定义则默认根据指令类型获取默认值(MCSchedule.h

SDIV的時延是 6-12 cycles, ProcResource 數量只有1, 所以throughput就該是 1/12 - 1/6
注意:mca可以通过如下方式设置分数,参考 https://lists.llvm.org/pipermail/llvm-dev/2015-April/084470.html
LLVM 编译器学习笔记之三十六-- 指令调度Instruction scheduling_第8张图片

19、利用SchedWriteVariant来区分ADD/SUB指令针对是否有shift操作的时延不一致的问题,参考D8043

注意:SchedAlias和WriteRes的区别参考https://lists.llvm.org/pipermail/llvm-commits/Week-of-Mon-20150406/270546.htmlLLVM 编译器学习笔记之三十六-- 指令调度Instruction scheduling_第9张图片

21、在SchedVariant中可以使用正则表达式,注RegShiftedPred是非随意的,rs$匹配的对应WriteISReg时序,未匹配的WriteI时序?疑问:未匹配的比如store指令为何没有用WriteI时序

LLVM 编译器学习笔记之三十六-- 指令调度Instruction scheduling_第10张图片

此定义 llvm-tblgen AArch64.td -class=Instruction -I../../../include/ &> test.dump 对应的record

22、SchedWrite和SchedWriteRes的区别,参考TargetSchedule.td中的介绍

SchedWrite can be used inside AArch64InstrFormats.td for generalizing over a class of instructions.
SchedWriteRes allows you to fine-tune and override the pipeline and latency for a given instruction, or instructions matching a regex.(即只能用于InstRW或ItinRW的匹配)

23、shouldScheduleAdjacent定义两个指令需要被调度排布在一起,不定义是指令调度结束后分裂,参考D120104

24、D110480上有AMD等指令时序的描述

25、指令的Lantency在addVRegDefDeps中调用SchedModel.computeOperandLatency计算

26、finalizeBundle->prepend->..->bundleWithSucc设置了hasUnmodeledSideEffects(),从而访存指令不能跨过bundle( 可以为一类指令设置默认属性默认设置的属性仅对单指令的的tablegen pattern生效,参考D37097)

参考LLVM笔记(7) - 指令的side effect - Five100Miles - 博客园,对应side effect属性,指令默认存在副作用,需要let hasSideEffects = 0显示的调整),也可以参考⚙ D139637 [AArch64][SVE][ISel] Combine dup of load to replicating load

utils/TableGen/InstrInfoEmitter.cpp:993:  if (Inst.hasSideEffects)     OS << "|(1ULL<";

class SME_Switch_Intrinsic  --> 也可以pattern中设置
    : DefaultAttrsIntrinsic<[],[ llvm_i32_ty],[IntrNoMem, IntrHasSideEffects]>;

27、是否out of order模型的接口函数isOutOfOrder ==> 关键:MicroOpBufferSize

注意:在out-of-order 模型中,也可以通过let BufferSize = 0来设置确保Out Latency=1, 详见computeOutputLatency

def AnyLdSt : ProcResGroup<[UnitLdSt1, UnitLdSt2]> { let BufferSize = 0; }

28、Depth +  Height 在寄存器分配的调度后是一个常数?在寄存器分配前的调度中非常数;指令调度默认是misched-bottomup(因此无依赖的指令先调度,但实际是后发射),需要-mllvm -misched-topdown=true调整方向, 参考GenericScheduler::pickNodetryLatency

SU(0):   renamable $q1, renamable $q2 = LDPQi renamable $x9, -1 :: (load (s128) from %ir.scevgep10, align 8), (load (s128) from %ir.lsr.iv79, align 8)
  # preds left       : 0
  # succs left       : 4
  # rdefs left       : 0
  Latency            : 6  -> 
getCriticalCount(), SchedBoundary::countResource
  Depth              : 0  -> 
SU->getDepth(), tryLatency
  Height             : 17 -> 
SU->getHeight(), tryLatency

29、in-order时,Pending指已经消除依赖,但是时序未达ReadyCycle的指令?而Out-of-order时不需要考虑时序,仅考虑依赖?参考SchedBoundary::releaseNode

SchedBoundary::pickOnlyChoice中,如果checkHazard中检查发现指令发射数目超过uops, 也会重新将相关指令从Ready list中删除,放到Pend中==> 即使乱序模式下,也有Pend (dump中SU(1) uops=3

30、指令调度中dump信息解析,参考GenericScheduler::tryCandidate

** ScheduleDAGMILive::schedule picking next node  --> 开始应该新的group调度
Queue BotQ.P: 
Queue BotQ.A: 29 30 
  Cand SU(29) ORDER          --> 优选29,原因order, 即原来的指令顺序                    
  Cand SU(30) ORDER          --> 更新为30更优,同样原因是order                 
Pick Bot ORDER     
Scheduling SU(30) %52:zpr = LD1RD_IMM %16:ppr_3b,%51:gpr64common, 0

31、检查确认是否资源是瓶颈,参考SchedBoundary::countResource

  A57UnitL +2x6u  --> 前面2是Cycles,后面6是Factor

*** Critical resource A57UnitL: 2c

def : InstRW<[A57Write_6cyc_2L, WriteLDHi], (instrs LDPQi)>; ==> uops = 2+1=3,参考resolveSchedClass->getSchedClassDesc

34、 典型的SchedWriteRes描述,参考AArch64SchedA64FX.td

def A64FXWrite_ST2_WD_RI : SchedWriteRes<[A64FXGI0, A64FXGI56]> {
  let Latency = 11;
  let NumMicroOps = 2;
  let ResourceCycles = [2, 2];
}

A64FX中的注释 LDP only breaks into *one* LS micro-op

你可能感兴趣的:(LLVM,技术文章,后端)