本文章为LLVM DFAPacketizer的源码分析。
相关文件路径
- llvm/include/llvm/CodeGen/DFAPacketizer.h
- llvm/lib/CodeGen/DFAPacketizer.cpp
一个确定性有限自动机(DFA)由三个主要元素组成:状态(states)、输入(inputs)和转换(transitions)。对于打包机制来说,输入是目标指令类集合。状态模拟了在给定指令包中所有可能的功能单元消耗组合。转换模拟了将指令添加到指令包中的过程。在这个类构建的确定性有限自动机中,如果一个指令可以被添加到指令包中,那么就存在一个有效的从相应状态出发的转换。无效的转换表明该指令不能被添加到当前的指令包中。
DFAPacketizer定义三个类DefaultVLIWScheduler
、DFAPacketizer
、VLIWPacketizerList
。
DefaultVLIWScheduler
构建依赖图。
类定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| // 这个类扩展了 ScheduleDAGInstrs,并覆盖了 schedule 方法以构建依赖图。
class DefaultVLIWScheduler : public ScheduleDAGInstrs {
private:
AAResults *AA;
// DAG 后处理步骤的有序列表。
std::vector<std::unique_ptr<ScheduleDAGMutation>> Mutations;
public:
DefaultVLIWScheduler(MachineFunction &MF, MachineLoopInfo &MLI,
AAResults *AA);
// 调度。
void schedule() override;
// DefaultVLIWScheduler拥有突变对象的所有权。
void addMutation(std::unique_ptr<ScheduleDAGMutation> Mutation) {
Mutations.push_back(std::move(Mutation));
}
protected:
void postProcessDAG();
};
|
变量
AA
别名分析。
AAResults *AA
Mutations
存储DAG后处理阶段的有序列表。
std::vector<std::unique_ptr<ScheduleDAGMutation>> Mutations
- ScheduleDAGMutation在DAG构建后为针对调度的依赖图的目标特定变异(llvm/include/llvm/CodeGen/ScheduleDAGMutation.h)。
目标无关函数
DefaultVLIWScheduler
构造函数。
schedule
调度工作。
1
2
3
4
5
6
| void DefaultVLIWScheduler::schedule() {
// 构建调度图
// llvm/lib/CodeGen/ScheduleDAGInstrs.cpp
buildSchedGraph(AA);
postProcessDAG();
}
|
addMutation
添加异变对象。
postProcessDAG
DAG后处理。
DFAPacketizer
该类进行资源管理以及自动机操作。
类定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
| class DFAPacketizer {
private:
const InstrItineraryData *InstrItins;
// 自动机
Automaton<uint64_t> A;
// 对于每条调度,都有一个应用于自动机的“行为”。这消除了行程类别之间动作的冗余。
ArrayRef<unsigned> ItinActions;
public:
DFAPacketizer(const InstrItineraryData *InstrItins, Automaton<uint64_t> a,
ArrayRef<unsigned> ItinActions)
: InstrItins(InstrItins), A(std::move(a)), ItinActions(ItinActions) {
// 开始时禁用资源跟踪功能。
A.enableTranscription(false);
}
// 重置当前状态,另所有资源可获取
void clearResources() {
A.reset();
}
// 设置这个打包器是否不仅应该跟踪指令是否可以打包,
// 而且还要跟踪在打包之后每条指令最终使用哪些功能单元。
void setTrackResources(bool Track) {
A.enableTranscription(Track);
}
// 检查当前状态下MCInstrDesc占用的资源是否可用。
bool canReserveResources(const MCInstrDesc *MID);
// 预留MCInstrDesc占用的资源,并改变当前状态以反映这一变化。
void reserveResources(const MCInstrDesc *MID);
// 检查机器指令占用的资源在当前状态下是否可用。
bool canReserveResources(MachineInstr &MI);
// 预留机器指令占用的资源,并更新当前状态以反映这一变化。
void reserveResources(MachineInstr &MI);
// 返回添加到这个数据包中的第InstIdx条指令所使用的资源。
// 资源以功能单元的位向量形式返回。
// 注意,一个指令束可能有多种有效的打包方式。这个函数返回一种任意有效的打包。
// 需要先调用setTrackResources(true)。
unsigned getUsedResources(unsigned InstIdx);
// 获取子目标提供的供目标使用的调度数据。
const InstrItineraryData *getInstrItins() const { return InstrItins; }
};
|
变量
InstrItins
调度数据。
const InstrItineraryData *InstrItins
A
自动机。
Automaton<uint64_t> A
ItinActions
调度行为。
ArrayRef<unsigned> ItinActions
目标无关函数
DFAPacketizer
构造函数。
开启自动机A的转录功能(Transcription)。
clearResources
重置自动机A,使所有资源可获取。
setTrackResources
根据Track设置自动机A是否转录,表示除考虑打包外还应考虑功能单元的应用。
canReserveResources
1.canReserveResources(const MCInstrDesc *MID)
: 检查被一个MCInstrDesc占用的资源是否可获取。
1
2
3
4
5
6
| bool DFAPacketizer::canReserveResources(const MCInstrDesc *MID) {
unsigned Action = ItinActions[MID->getSchedClass()];
if (MID->getSchedClass() == 0 || Action == 0)
return false;
return A.canAdd(Action);
}
|
1
2
3
4
5
| // llvm/include/llvm/Support/Automaton.h
bool canAdd(const ActionT &A) {
auto I = M->find({State, A});
return I != M->end();
}
|
- 根据MID获取调度类别。
- 根据调度类别获取调度行为。
- 如果没有调度类别或调度行为,结束。
- 否则使用自动机A确定调度行为是否可以被转换。
getSchedClass获取该指令的调度类别,其结果为InstrItineraryData表的索引。
2.canReserveResources(MachineInstr &MI)
: 检查MachineInstr占用的资源在当前状态下是否可用。
该方法调用canReserveResources(const MCInstrDesc *MID)
。
reserveResources
1.reserveResources(const MCInstrDesc *MID)
: 预留MCInstrDesc占用的资源,并改变当前状态以反映这一变化。
1
2
3
4
5
6
| void DFAPacketizer::reserveResources(const MCInstrDesc *MID) {
unsigned Action = ItinActions[MID->getSchedClass()];
if (MID->getSchedClass() == 0 || Action == 0)
return;
A.add(Action);
}
|
1
2
3
4
5
6
7
8
9
10
| // llvm/include/llvm/Support/Automaton.h
bool add(const ActionT &A) {
auto I = M->find({State, A});
if (I == M->end())
return false;
if (Transcriber && Transcribe)
Transcriber->transition(I->second.second);
State = I->second.first;
return true;
}
|
- 根据MID获取调度类别。
- 根据调度类别获取调度行为。
- 如果没有调度类别或调度行为,结束。
- 否则将该行为添加到自动机A中。
2.reserveResources(MachineInstr &MI)
: 预留MachineInstr占用的资源,并更新当前状态以反映这一变化。
该方法调用reserveResources(const MCInstrDesc *MID)
。
getUsedResources
返回添加到这个数据包中的第InstIdx条指令所使用的资源,资源以功能单元的位向量形式返回。注意,一个指令束可能有多种有效的打包方式。这个函数返回一种任意有效的打包。需要先调用setTrackResources(true)。
1
2
3
4
5
6
7
8
9
10
11
12
13
| unsigned DFAPacketizer::getUsedResources(unsigned InstIdx) {
ArrayRef<NfaPath> NfaPaths = A.getNfaPaths();
assert(!NfaPaths.empty() && "Invalid bundle!");
const NfaPath &RS = NfaPaths.front();
// RS stores the cumulative resources used up to and including the I'th
// instruction. The 0th instruction is the base case.
if (InstIdx == 0)
return RS[0];
// Return the difference between the cumulative resources used by InstIdx and
// its predecessor.
return RS[InstIdx] ^ RS[InstIdx - 1];
}
|
getInstrItins
获取子目标提供的供目标使用的调度数据
VLIWPacketizerList
VLIWPacketizerList 实现了一个使用 DFA(确定性有限自动机)的简单 VLIW 指令打包器。该打包器在机器基本块上工作。对于 BB(基本块)中的每条指令 I,打包器会查询 DFA 来看是否有足够的机器资源来执行 I。如果是这样,打包器会检查 I 是否依赖当前数据包中的任何指令。如果没有发现依赖关系,I 就会被添加到当前数据包中,并且相应的机器资源会被标记为已占用。如果发现了依赖关系,就会进行目标 API 调用以剪枝依赖。
类定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
| class VLIWPacketizerList {
protected:
MachineFunction &MF;
const TargetInstrInfo *TII;
AAResults *AA;
// VLIW调度器
DefaultVLIWScheduler *VLIWScheduler;
// 当前数据包包含的指令。
std::vector<MachineInstr*> CurrentPacketMIs;
// DFA资源追踪器。
DFAPacketizer *ResourceTracker;
// 指令到功能单元的映射。
std::map<MachineInstr*, SUnit*> MIToSUnit;
public:
// 构造函数,AAResults参数可以为空指针。
VLIWPacketizerList(MachineFunction &MF, MachineLoopInfo &MLI,
AAResults *AA);
virtual ~VLIWPacketizerList();
// 指令打包接口。
void PacketizeMIs(MachineBasicBlock *MBB,
MachineBasicBlock::iterator BeginItr,
MachineBasicBlock::iterator EndItr);
// 返回ResourceTracker。
DFAPacketizer *getResourceTracker() {return ResourceTracker;}
// 添加指令到当前包。
virtual MachineBasicBlock::iterator addToPacket(MachineInstr &MI) {
CurrentPacketMIs.push_back(&MI);
ResourceTracker->reserveResources(MI);
return MI;
}
// 结束当前打包并且重置打包器的状态。
// 覆盖当前函数允许确切目标打包器执行自定义最终处理。
virtual void endPacket(MachineBasicBlock *MBB,
MachineBasicBlock::iterator MI);
// 在将指令打包之前执行初始化。该函数应由依赖于目标的打包器覆盖。
virtual void initPacketizerState() {}
// 检查是否给定的指令应当被打包器忽视。
virtual bool ignorePseudoInstruction(const MachineInstr &I,
const MachineBasicBlock *MBB) {
return false;
}
// 若当前指令不能和任意一个指令打包,则返回true,这意味着当前指令独自为一个包。
virtual bool isSoloInstruction(const MachineInstr &MI) { return true; }
// 检查打包器是否应该尝试将给定的指令添加到当前数据包中。
// 可能不希望将指令包含在当前数据包中的原因之一是,它可能导致停滞。
// 如果这个函数返回 "false",则当前数据包将结束,并且该指令将被添加到下一个数据包中。
virtual bool shouldAddToPacket(const MachineInstr &MI) { return true; }
// 检查将 SUI 和 SUJ 打包在一起是否合法。
virtual bool isLegalToPacketizeTogether(SUnit *SUI, SUnit *SUJ) {
return false;
}
// 检查在 SUI 和 SUJ 之间剪枝是否合法。
virtual bool isLegalToPruneDependencies(SUnit *SUI, SUnit *SUJ) {
return false;
}
// 在打包开始前,增加一次 DAG 突变。
void addMutation(std::unique_ptr<ScheduleDAGMutation> Mutation);
bool alias(const MachineInstr &MI1, const MachineInstr &MI2,
bool UseTBAA = true) const;
private:
bool alias(const MachineMemOperand &Op1, const MachineMemOperand &Op2,
bool UseTBAA = true) const;
};
|
变量
MF
MachineFunction &MF
TII
目标指令信息。
const TargetInstrInfo *TII
AA
别名分析。
AAResults *AA
VLIWScheduler
VLIW调度器。
DefaultVLIWScheduler *VLIWScheduler
CurrentPacketMIs
当前数据包包含的指令
std::vector<MachineInstr*> CurrentPacketMIs
ResourceTracker
DFA资源追踪器。
DFAPacketizer *ResourceTracker
MIToSUnit
指令到功能单元的映射关系。
std::map<MachineInstr*, SUnit*> MIToSUnit
目标无关函数
VLIWPacketizerList
构造函数,AAResults参数可以为空指针。
PacketizeMIs
指令打包接口。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
| void VLIWPacketizerList::PacketizeMIs(MachineBasicBlock *MBB,
MachineBasicBlock::iterator BeginItr,
MachineBasicBlock::iterator EndItr) {
assert(VLIWScheduler && "VLIW Scheduler is not initialized!");
// 准备调度执行。
VLIWScheduler->startBlock(MBB);
// 为新调度区域初始化 DAG 和通用调度器状态。
// 这实际上并不创建 DAG,只是清除它。
// 调度驱动程序可在每个调度区域多次调用 BuildSchedGraph。
VLIWScheduler->enterRegion(MBB, BeginItr, EndItr,
std::distance(BeginItr, EndItr));
VLIWScheduler->schedule();
LLVM_DEBUG({
dbgs() << "Scheduling DAG of the packetize region\n";
VLIWScheduler->dump();
});
// 生成机器指令到调度单元的映射。
MIToSUnit.clear();
for (SUnit &SU : VLIWScheduler->SUnits)
MIToSUnit[SU.getInstr()] = &SU;
bool LimitPresent = InstrLimit.getPosition();
// 打包。
for (; BeginItr != EndItr; ++BeginItr) {
if (LimitPresent) {
if (InstrCount >= InstrLimit) {
EndItr = BeginItr;
break;
}
InstrCount++;
}
MachineInstr &MI = *BeginItr;
initPacketizerState();
// 如果需要结束当前打包。
if (isSoloInstruction(MI)) {
endPacket(MBB, MI);
continue;
}
// 忽视伪指令。
if (ignorePseudoInstruction(MI, MBB))
continue;
SUnit *SUI = MIToSUnit[&MI];
assert(SUI && "Missing SUnit Info!");
// 询问DFA该机器指令所需资源是否可获取。
LLVM_DEBUG(dbgs() << "Checking resources for adding MI to packet " << MI);
bool ResourceAvail = ResourceTracker->canReserveResources(MI);
LLVM_DEBUG({
if (ResourceAvail)
dbgs() << " Resources are available for adding MI to packet\n";
else
dbgs() << " Resources NOT available\n";
});
if (ResourceAvail && shouldAddToPacket(MI)) {
// 检查当前指令和包中指令依赖关系。
for (auto *MJ : CurrentPacketMIs) {
SUnit *SUJ = MIToSUnit[MJ];
assert(SUJ && "Missing SUnit Info!");
LLVM_DEBUG(dbgs() << " Checking against MJ " << *MJ);
// 打包SUI和SUJ是否合法。
if (!isLegalToPacketizeTogether(SUI, SUJ)) {
LLVM_DEBUG(dbgs() << " Not legal to add MI, try to prune\n");
// 如果依赖可以被剪枝则打包。
if (!isLegalToPruneDependencies(SUI, SUJ)) {
// 如果依赖不能剪枝则结束打包。
LLVM_DEBUG(dbgs()
<< " Could not prune dependencies for adding MI\n");
endPacket(MBB, MI);
break;
}
LLVM_DEBUG(dbgs() << " Pruned dependence for adding MI\n");
}
}
} else {
LLVM_DEBUG(if (ResourceAvail) dbgs()
<< "Resources are available, but instruction should not be "
"added to packet\n "
<< MI);
// 如果资源不可获取或指令不能加入当前包则结束打包。
endPacket(MBB, MI);
}
// 添加MI到当前包。
LLVM_DEBUG(dbgs() << "* Adding MI to packet " << MI << '\n');
BeginItr = addToPacket(MI);
} // 对于打包范围的所有指令。
// 结束任何遗留的数据包。
endPacket(MBB, EndItr);
VLIWScheduler->exitRegion();
VLIWScheduler->finishBlock();
}
|
getResourceTracker
返回ResourceTracker。
addMutation
在打包开始前,增加一次 DAG 突变。
alias
public
alias
private
目标相关函数
~VLIWPacketizerList()
析构函数
addToPacket
添加指令到当前包。(存在默认实现)
#Todo
endPacket
结束当前打包并且重置打包器的状态,覆盖当前函数允许确切目标打包器执行自定义最终处理。(存在默认实现)
#Todo
initPacketizerState
在将指令打包之前执行初始化。该函数应由依赖于目标的打包器覆盖。
#Todo
ignorePseudoInstruction
检查是否给定的指令应当被打包器忽视。
#Todo
isSoloInstruction
若当前指令不能和任意一个指令打包,则返回true,这意味着当前指令独自为一个包。
#Todo
shouldAddToPacket
检查打包器是否应该尝试将给定的指令添加到当前数据包中。
可能不希望将指令包含在当前数据包中的原因之一是,它可能导致停滞。
如果这个函数返回 “false”,则当前数据包将结束,并且该指令将被添加到下一个数据包中。
#Todo
isLegalToPacketizeTogether
检查将 SUI 和 SUJ 打包在一起是否合法。
#Todo
isLegalToPruneDependencies
检查剪枝 SUI 和 SUJ 之间的依赖是否合法。
#Todo