1、参考Instruction scheduling in LLVM - 知乎,在中、后端均存在指令调度
https://www.youtube.com/watch?v=cWLW0aQwFg0&t=922s
GenericScheduler:: 做寄存器压力感知的指令调度
PostGenericScheduler:: 寄存器分配后的指令调度,基于BB的指令调度
2、在llvm12->llvm14 中Machine Instruction Scheduler有个patch af342f7240增强了load/store指令的合并,也就是指令调度不仅仅只是改变指令的顺序,该优化类似gcc中的store-merge优化
注意:后端isPairableLdStInst接口可以指示是否允许寄存器对的优化,一般来说这个合并是有利的,也不能排除个别硬件存在bug
3、指令调度*.P指Pend, *.A为ready的指令,调度时从Bot和Top两端选取,最后将在某个中间位置相遇时结束调度;另外BotQ.A中的指令会被优先考虑发射(P 代表Pending, A 代表Available, 参考SchedBoundary::pickOnlyChoice)
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)
注意:指令调度的模型SchedModel=mf.getSubtarget()和后端架构配置相关
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<AArch64Subtarget>().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
11、将循环中的LoopMicroOpBufferSize值调小一点,参考72a799a6
12、Load/Store的时延分析特别处理,参考D8705,load->store的时延只有1个cycle?
13、指令调度中每个指令被匹配为一个SUnit结点,根据top-down topological order计算两个指令的顺序,也就是A<B代表无B->A的边(即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, #272Resources:
[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.50Resource 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)
19、利用SchedWriteVariant来区分ADD/SUB指令针对是否有shift操作的时延不一致的问题,参考D8043
def : InstRW<[A57WriteISReg], (instregex ".*rs$")>; 也可以用
def : SchedAlias<WriteISReg, A57WriteISReg>; 因为这些指令默认使用WriteISReg描述
注:默认值WriteISReg只能关联一次
注意:SchedAlias和WriteRes的区别参考https://lists.llvm.org/pipermail/llvm-commits/Week-of-Mon-20150406/270546.html
21、在SchedVariant中可以使用正则表达式,注RegShiftedPred是非随意的,rs$匹配的对应WriteISReg时序,未匹配的WriteI时序?疑问:未匹配的比如store指令为何没有用WriteI时序
此定义 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/D140680)
参考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<<MCID::UnmodeledSideEffects)";
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::pickNode及tryLatency
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 = 3; //
let ResourceCycles = [2, 2]; // 分别对应A64FXGI0和A64FXGI56
}A64FX中的注释 LDP only breaks into *one* LS micro-op
每个时钟周期同时占用一个A64FXGI0和一个A64FXGI56执行单元,等价描述
def A64FXWrite_ST2_WD_RI : SchedWriteRes<[A64FXGI0, A64FXGI0, A64FXGI56, A64FXGI56]> {
let Latency = 11;
let NumMicroOps = 3;
}
35、伪指令也是有定义的,比如LOADgot则关注后端对WriteAdr和WriteLD的定义
def LOADgot : Pseudo<(outs GPR64common:$dst), (ins i64imm:$addr),
[(set GPR64common:$dst, (AArch64LOADgot tglobaladdr:$addr))]>,
Sched<[WriteLDAdr]>;def WriteLDAdr : WriteSequence<[WriteAdr, WriteLD]>;
也可以进行显示的定义,参考 AArch64SchedFalkorDetails.td
def : InstRW<[WriteSequence<[FalkorWr_1LD_3cyc, FalkorWr_1XYZ_1cyc]>],
(instrs LOADgot)>;
36、InstRW定义中的顺序对指令的Lantency有影响,参考⚙ D159254 [AArch64] Fix schedmodel pre/post-index loads and stores for Neoverse V2
def : InstRW<[WriteAdr, V2Write_5cyc_1I_3L, WriteLDHi], -- 将地址更新更早发射
37、使用 llvm-mca -mtriple=aarch64 -mcpu=tsv110 --instruction-info=0 --resource-pressure=0 --timeline --timeline-max-iterations=1 可以展示指令流水线调度情况,参考gh #68854
static const char Dispatched = 'D';
static const char Executed = 'E';
static const char Retired = 'R';
static const char Waiting = '='; // Instruction is waiting in the scheduler.
static const char Executing = 'e';
static const char RetireLag = '-'; // The instruction is waiting to retire.
38、 基于PR80178可以看到Latency短的指令发射的优先级一般更低。
39、The main purpose of ReadAdvance is pipeline forwarding,可以通过-debug-only=subtarget-emitter调试?参考https://groups.google.com/g/llvm-dev/c/gbPGlDdT1Pc/m/lhHNPcMRBgAJ
bypass/forward指前后有两个指令,第一个指令的dst在执行完后还没有把结果写到寄存器之前,可以提前给到依赖于它的指令。
def ReadAdr : SchedReadAdvance<3, [WriteLD]> 代表来给写操作使用load的提前3拍发射
通过llvm自带脚本schedcover.py tblGenSubtarget.dbg 'default|NeoverseV1Model' 可以查看每个指令定义后的状态
注:上述tblGenSubtarget.dbg需要在cmake的命令中找到相关的生成命令,类似
llvm-tblgen -DBSPRIV_AARCH64 -DBSPRIV_AARCH64 ... -o lib/Target/AArch64/AArch64GenSubtargetInfo.inc -d lib/Target/AArch64/AArch64GenSubtargetInfo.inc.d >$cwd/tblGenSubtarget.dbg 2>&1
40、指令调度处理融合情况 ,eg: ST.hasCmpBccFusion()
41、可以使用组合来定义Write_6c_1LD_2S
def Write_6c_1LD : SchedWriteRes<[UnitLD]> {
def Write_6c_1LD_2S : SchedWriteRes<[UnitLD, UnitS]> 则使用Write_6c_1LD_2S的地方可以使用def : InstRW<[Write_6c_1LD, WriteAdr]类似的组合替代
42、 commit 4a5b5bf展示对shift不同的常数定义不同Latency的方法,借助IsCheapLSL
43、在PR106371定义了N3指令流水线
N3Write_5c_1M0_1V中的1M0 代表指令需要占用一个M0的uops,1个V的uops, 因此如果V有V0, V1两个端口,则相关指令还可以遇其他占用一个V的uops指令一起并行执行
类似V2Write_9cyc_4L_8V这种将uops展展开的描述更友好?而不推荐使用ReleaseAtCycles
除法指令因为占用整个流水线,可以继续使用ReleaseAtCycles