LLVM学习笔记(8)
来源:互联网 发布:大话西游服务端源码 编辑:程序博客网 时间:2024/05/22 06:32
2.3. 汇编处理描述
至于关于读写汇编格式指令信息的封装,TableGen提供了类Target(target.td)作为各目标机器的基类。
1059 classTarget {
1060 // InstructionSet- Instruction set description for this target.
1061 InstrInfo InstructionSet;
1062
1063 // AssemblyParsers- The AsmParser instances available for this target.
1064 list<AsmParser> AssemblyParsers =[DefaultAsmParser];
1065
1066 ///AssemblyParserVariants - The AsmParserVariant instances available for
1067 /// this target.
1068 list<AsmParserVariant>AssemblyParserVariants = [DefaultAsmParserVariant];
1069
1070 //AssemblyWriters - The AsmWriter instances available for this target.
1071 list<AsmWriter> AssemblyWriters =[DefaultAsmWriter];
1072 }
AssemblyParsers与AssemblyParserVariants是目标机器相关的汇编代码解析描述,如果子目标机器需要使用自己的汇编解析描述,这些解析器描述AssemblyParserVariants保存。Target定义中最重要的是InstrInfo,它代表输出汇编所关注的目标机器指令信息:
690 classInstrInfo {
691 // Target canspecify its instructions in either big or little-endian formats.
692 // For instance,while both Sparc and PowerPC are big-endian platforms, the
693 // Sparc manualspecifies its instructions in the format [31..0] (big), while
694 // PowerPCspecifies them using the format [0..31] (little).
695 bit isLittleEndianEncoding = 0;
696
697 // Theinstruction properties mayLoad, mayStore, and hasSideEffects are unset
698 // by default,and TableGen will infer their value from the instruction
699 // pattern whenpossible.
700 //
701 // Normally,TableGen will issue an error it it can't infer the value of a
702 // property thathasn't been set explicitly. When guessInstructionProperties
703 // is set, itwill guess a safe value instead.
704 //
705 // This option isa temporary migration help. It will go away.
706 bit guessInstructionProperties = 1;
707
708 // TableGen'sinstruction encoder generator has support for matching operands
709 // to bit-fieldvariables both by name and by position. While matching by
710 // name ispreferred, this is currently not possible for complex operands,
711 // and sometargets still reply on the positional encoding rules. When
712 // generating adecoder for such targets, the positional encoding rules must
713 // be used by thedecoder generator as well.
714 //
715 // This option istemporary; it will go away once the TableGen decoder
716 // generator hasbetter support for complex operands and targets have
717 // migrated awayfrom using positionally encoded operands.
718 bit decodePositionallyEncodedOperands = 0;
719
720 // When set, thisindicates that there will be no overlap between those
721 // operands thatare matched by ordering (positional operands) and those
722 // matched byname.
723 //
724 // This option istemporary; it will go away once the TableGen decoder
725 // generator hasbetter support for complex operands and targets have
726 // migrated awayfrom using positionally encoded operands.
727 bit noNamedPositionallyEncodedOperands = 0;
728 }
X86目标机器原封不动地从InstrInfo派生了X86InstrInfo。它的Target派生定义则是:
568 def X86 : Target {
569 // Informationabout the instructions...
570 letInstructionSet = X86InstrInfo;
571 letAssemblyParsers = [ATTAsmParser];
572 letAssemblyParserVariants = [ATTAsmParserVariant, IntelAsmParserVariant];
573 letAssemblyWriters = [ATTAsmWriter, IntelAsmWriter];
574 }
正如我们所熟悉的X86的汇编代码分为了AT&T与Intel格式。ATTAsmParserVariant这样的定义给出了对应汇编语言的注释符,分隔符以及寄存器前缀等的定义,而ATTAsmWriter这样的定义则指出LLVM中的哪个类可以输出指定的汇编格式代码。ATTAsmParser则指出LLVM需要使用AsmParser来住持汇编解析。
2.4. 目标机器描述
对目标机器处理器的描述则以Target.td文件中的Processor定义为基类。
1119 classProcessor<string n, ProcessorItineraries pi,list<SubtargetFeature> f> {
1120 // Name - Chipset name. Used by command line (-mcpu=)to determine the
1121 // appropriatetarget chip.
1122 //
1123 string Name = n;
1124
1125 // SchedModel -The machine model for scheduling and instruction cost.
1126 //
1127 SchedMachineModel SchedModel = NoSchedModel;
1128
1129 // ProcItin - Thescheduling information for the target processor.
1130 //
1131 ProcessorItineraries ProcItin = pi;
1132
1133 // Features -list of
1134 list<SubtargetFeature>Features = f;
1135 }
1127行的SchedModel与1131行的ProcItin都可以对调度细节进行描述。SchedModel具体的描述参见下面的定义,ProcItin则是描述各种指令的执行步骤。1127行的NoSchedModel使得Processor定义缺省不使用SchedModel。不过,对于某些处理器,ProcItin并不方便使用,而是使用SchedModel,因此也有缺省禁止ProcItin的Processor定义:
1143 classProcessorModel<string n, SchedMachineModel m,list<SubtargetFeature> f>
1144 : Processor<n, NoItineraries, f> {
1145 letSchedModel = m;
1146 }
下面我们将看到对于Atom这样的顺序流水线机器,使用的是ProcItin,而SandyBridge这样支持乱序执行的机器,则使用SchedModel。我们在下面讨论这些细节。
2.4.1. 特性描述
1134行的Features用于描述处理器所支持的特性,比如是否支持SSE指令集,是否支持TSX指令集等。TableGen将根据这些描述生成需要的谓词判断,SubtargetFeature是一个全字符串的定义:
1077 classSubtargetFeature<string n, string a, string v, string d,
1078 list<SubtargetFeature> i = []> {
1079 // Name - Featurename. Used by command line (-mattr=) todetermine the
1080 // appropriatetarget chip.
1081 //
1082 string Name = n;
1083
1084 // Attribute -Attribute to be set by feature.
1085 //
1086 string Attribute = a;
1087
1088 // Value - Valuethe attribute to be set to by feature.
1089 //
1090 string Value = v;
1091
1092 // Desc - Featuredescription. Used by command line(-mattr=) to display help
1093 // information.
1094 //
1095 string Desc = d;
1096
1097 // Implies -Features that this feature implies are present. If one of those
1098 // features isn'tset, then this one shouldn't be set either.
1099 //
1100 list<SubtargetFeature> Implies = i;
1101 }
1100行的Implies表示,如果具有该特性,该特定隐含包含了哪些特性。
SubTargetFeature具有很好的灵活性以及描述能力,能够描述差异很大的处理器。以Atom处理器为例,这是Intel推出的面向移动设备的处理器。它与传统的Intel处理器相比,更像RISC处理器。TableGen这样定义它:
240 // Atom CPUs.
241 class BonnellProc<string Name> :ProcessorModel<Name, AtomModel, [
242 ProcIntelAtom,
243 FeatureSSSE3,
244 FeatureCMPXCHG16B,
245 FeatureMOVBE,
246 FeatureSlowBTMem,
247 FeatureLeaForSP,
248 FeatureSlowDivide32,
249 FeatureSlowDivide64,
250 FeatureCallRegIndirect,
251 FeatureLEAUsesAG,
252 FeaturePadShortFunctions
253 ]>;
254 def : BonnellProc<"bonnell">;
255 def: BonnellProc<"atom">;// Pin thegeneric name to the baseline.
242~252行就是Atom处理器支持的特性。第一个ProcIntelAtom是对这个处理器族的文字描述,帮助开发人员理解。而接着的FeatureSSSE3表示Atom支持SSSE3指令集,这同时也意味着也支持SSSE3以下的指令集,即SSE3、SSE2、SSE。
54 defFeatureSSSE3: SubtargetFeature<"ssse3","X86SSELevel", "SSSE3",
55 "Enable SSSE3 instructions",
56 [FeatureSSE3]>;
这个定义表示只支持SSSE3及以下的指令集(因此还包括SSE3,SSE2,SSE,MMX等)。
75 defFeatureCMPXCHG16B: SubtargetFeature<"cx16","HasCmpxchg16b", "true",
76 "64-bit with cmpxchg16b",
77 [Feature64Bit]>;
这个定义表示支持64位cmpxchg16b指令及条件move指令。
143 defFeatureMOVBE: SubtargetFeature<"movbe","HasMOVBE", "true",
144 "Support MOVBE instruction">;
这个定义表示支持MOVBE指令。
78 defFeatureSlowBTMem: SubtargetFeature<"slow-bt-mem","IsBTMemSlow", "true",
79 "Bit testing of memory is slow">;
这个定义表示该处理器上的比特测试指令是慢的。
173 defFeatureLeaForSP:SubtargetFeature<"lea-sp", "UseLeaForSP","true",
174 "UseLEA for adjusting the stack pointer">;
这个定义表示使用LEA指令来调整栈指针。
175 defFeatureSlowDivide32:SubtargetFeature<"idivl-to-divb",
176 "HasSlowDivide32", "true",
177 "Use8-bit divide for positive values less than 256">;
这个定义表示对小于256的正数使用8位除法(因为32位除法慢)。
178 defFeatureSlowDivide64:SubtargetFeature<"idivq-to-divw",
179 "HasSlowDivide64", "true",
180 "Use16-bit divide for positive values less than 65536">;
这个定义表示对小于65535的正数使用16位除法(因为64位除法慢)。
184 defFeatureCallRegIndirect:SubtargetFeature<"call-reg-indirect",
185 "CallRegIndirect", "true",
186 "Callregister indirect">;
这个定义表示间接调用寄存器。
187 defFeatureLEAUsesAG:SubtargetFeature<"lea-uses-ag", "LEAUsesAG","true",
188 "LEAinstruction needs inputs at AG stage">;
这个定义表示LEA指令在AG阶段(访问数据缓存的某个阶段)就需要输入。
181 defFeaturePadShortFunctions:SubtargetFeature<"pad-short-functions",
182 "PadShortFunctions", "true",
183 "Padshort functions">;
这个定义表示要填充短的函数。
这些SubtargetFeature的定义由一个专门的TableGen选项“-gen-subtarget”来解析,在构建编译器时,将通过llvm-tblgen执行该选项来生成一个目标机器特定的文件X86GenSubtargetInfo.inc(以X86为例)。这个文件包含的代码将辅助构建一个X86Subtarget类的实例(X86Subtarget.h),这个实例将提供相应的判断方法。
2.4.2. 调度信息
SchedMachineModel是这样描述乱序执行处理器的:
77 classSchedMachineModel {
78 int IssueWidth = -1; //Max micro-ops that may be scheduled per cycle.
79 int MinLatency = -1; //Determines which instructions are allowed in a group.
80 // (-1) inorder (0) ooo, (1): inorder +var latencies.
81 int MicroOpBufferSize = -1;// Max micro-ops that can be buffered.
82 int LoopMicroOpBufferSize = -1;// Max micro-ops that can be buffered for
83 // optimizedloop dispatch/execution.
84 int LoadLatency = -1; // Cycles for loads to access the cache.
85 int HighLatency = -1; // Approximation of cycles for "high latency" ops.
86 int MispredictPenalty = -1;// Extra cycles for a mispredicted branch.
87
88 // Per-cycleresources tables.
89 ProcessorItineraries Itineraries = NoItineraries;
90
91 bit PostRAScheduler = 0;// Enable Post RegAlloc Scheduler pass.
92
93 // Subtargetsthat define a model for only a subset of instructions
94 // that have ascheduling class (itinerary class or SchedRW list)
95 // and mayactually be generated for that subtarget must clear this
96 // bit.Otherwise, the scheduler considers an unmodelled opcode to
97 // be an error.This should only be set during initial bringup,
98 // or there willbe no way to catch simple errors in the model
99 // resulting fromchanges to the instruction definitions.
100 bit CompleteModel = 1;
101
102 bit NoModel = 0; //Special tag to indicate missing machine model.
103 }
属性的缺省值定义在C++类MCSchedModel(MCSchedule.h)中。在SchedMachineModel中出现的值-1表示该目标机器不会改写该属性。
其中IssueWidth缺省为1,它是一个周期内可以调度(发布)的最大指令数。
MicroOpBufferSize缺省是0,它是处理器为乱序执行所缓冲的微操作个数。0表示在本周期未就绪的操作不考虑调度(它们进入挂起队列)。指令时延是最重要的。如果在一次调度中挂起许多指令,可能会更高效。1表示不管在本周期是否就绪,考虑调度所有的指令。指令时延仍然会导致发布暂停,不过我们通过其他启发式平衡这些暂停。>1表示处理器乱序执行。这是高度特定于,比如寄存器重命名池及重排缓冲,这些机器特性的一个机器无关的估计。
LoopMicroOpBufferSize的缺省值是0。它是处理器为了优化循环的执行可能缓冲的微操作个数。更一般地,这代表了一个循环体里最优的微操作数。循环可能被部分展开使得循环体的微操作数更接近这个值。
LoadLatency的缺省值是4。它是读指令的预期时延。如果MinLatency>= 0,对个别读操作,可以通过InstrItinerary的OperandCycles来覆盖它。
HighLatency的缺省值是10。它是“非常高时延”操作的预期时延。通常,这是可能对调度启发式产生一些影响的一个任意高的周期数。如果MinLatency>= 0,可以通过InstrItinerary的OperandCycles来覆盖它。
MispredictPenalty的缺省值是10。它是处理器从一次跳转误判恢复所需的、典型的额外周期数。
NoModel如果是1,表示这个SchedMachineModel定义是没有实际意义的,比如:
105 defNoSchedModel : SchedMachineModel {
106 let NoModel =1;
107 }
看到在SchedMachineModel的定义里也包含了一个ProcessorItineraries类型的成员,注释中提到这是每周期的资源表(缺省是不提供)。同时ProcessorItineraries也是Processor中ProcItin的类型,它的定义是(TargetItinerary.td):
126 classProcessorItineraries<list<FuncUnit> fu,list<Bypass> bp,
127 list<InstrItinData> iid> {
128 list<FuncUnit> FU = fu;
129 list<Bypass> BP = bp;
130 list<InstrItinData> IID = iid;
131 }
其成员都是list列表,列表的每个元素对应一个处理器周期。FuncUnit描述的是处理器的组成单元。因为处理器单元形形色色、各式各样,因此FuncUnit作为基类只是个空的class。类似的,Bypass用于描述流水线旁路,它也是一个空class。
下面我们看一些例子。
2.4.2.1. ATOM的描述
Atom是Intel设计的超低电压IA-32与x86-64微处理器,它基于Bonnell微架构。因此在X86.td的255行,可以看到Atom的ProcessorModel定义正是从BonnellProc派生的。
Bonnell微架构每周期可以最多执行两条指令。像许多其他x86微处理器,在执行前它把x86指令(CISC指令)翻译为更简单的内部操作(有时称为微操作,即实质上RISC形式的指令)。在翻译时,在典型的程序里,大多数指令产生一个微操作,大约4%的指令产生多个微操作。生成多个微操作的指令数显著少于P6及NetBurst微架构。在Bonnell微架构里,内部的微操作可以同时包含与一个ALU操作关联的一个内存读及一个内存写,因此更类似于x86水平,比之前设计中使用的微操作更强大。这使得仅使用两个整数ALU,无需指令重排,推测执行或寄存器重命名,就获得相对好的性能。因此Bonnell微架构代表用在Intel更早期设计的原则,比如P5与i486,的一个部分复兴,唯一的目的是提高每瓦特性能比。不过,超线程以一个简单的方式(即低功耗)实现,通过避免典型的简单线程依赖来提高流水线效率。
要描述这个处理器,首先在X86.td文件里可以看到这些定义(这只是X86处理器定义集中的很小一部分):
203 defProcIntelAtom: SubtargetFeature<"atom","X86ProcFamily", "IntelAtom",
204 "Intel Atom processors">;
205 defProcIntelSLM:SubtargetFeature<"slm", "X86ProcFamily","IntelSLM",
206 "Intel Silvermontprocessors">;
207
208 class Proc<string Name,list<SubtargetFeature> Features>
209 : ProcessorModel<Name, GenericModel,Features>;
210
211 def : Proc<"generic", []>;
212 def : Proc<"i386", []>;
213 def : Proc<"i486", []>;
214 def : Proc<"i586", []>;
215 def : Proc<"pentium", []>;
216 def : Proc<"pentium-mmx", [FeatureMMX]>;
217 def : Proc<"i686", []>;
218 def : Proc<"pentiumpro", [FeatureCMOV]>;
219 def : Proc<"pentium2", [FeatureMMX, FeatureCMOV]>;
220 def : Proc<"pentium3", [FeatureSSE1]>;
221 def : Proc<"pentium3m", [FeatureSSE1, FeatureSlowBTMem]>;
222 def : Proc<"pentium-m", [FeatureSSE2, FeatureSlowBTMem]>;
223 def : Proc<"pentium4", [FeatureSSE2]>;
224 def : Proc<"pentium4m", [FeatureSSE2, FeatureSlowBTMem]>;
225
226 // Intel Core Duo.
227 def : ProcessorModel<"yonah",SandyBridgeModel,
228 [FeatureSSE3,FeatureSlowBTMem]>;
229
230 // NetBurst.
231 def : Proc<"prescott", [FeatureSSE3,FeatureSlowBTMem]>;
232 def : Proc<"nocona", [FeatureSSE3, FeatureCMPXCHG16B,FeatureSlowBTMem]>;
233
234 // Intel Core 2 Solo/Duo.
235 def : ProcessorModel<"core2", SandyBridgeModel,
236 [FeatureSSSE3,FeatureCMPXCHG16B, FeatureSlowBTMem]>;
237 def : ProcessorModel<"penryn",SandyBridgeModel,
238 [FeatureSSE41,FeatureCMPXCHG16B, FeatureSlowBTMem]>;
239
240 // Atom CPUs.
241 class BonnellProc<string Name> :ProcessorModel<Name,AtomModel, [
242 ProcIntelAtom,
243 FeatureSSSE3,
244 FeatureCMPXCHG16B,
245 FeatureMOVBE,
246 FeatureSlowBTMem,
247 FeatureLeaForSP,
248 FeatureSlowDivide32,
249 FeatureSlowDivide64,
250 FeatureCallRegIndirect,
251 FeatureLEAUsesAG,
252 FeaturePadShortFunctions
253 ]>;
254 def : BonnellProc<"bonnell">;
255 def : BonnellProc<"atom">;// Pin the generic name to the baseline.
256
257 class SilvermontProc<string Name> :ProcessorModel<Name, SLMModel, [
258 ProcIntelSLM,
259 FeatureSSE42,
260 FeatureCMPXCHG16B,
261 FeatureMOVBE,
262 FeaturePOPCNT,
263 FeaturePCLMUL,
264 FeatureAES,
265 FeatureSlowDivide64,
266 FeatureCallRegIndirect,
267 FeaturePRFCHW,
268 FeatureSlowLEA,
269 FeatureSlowIncDec,
270 FeatureSlowBTMem,
271 FeatureFastUAMem
272 ]>;
273 def : SilvermontProc<"silvermont">;
274 def: SilvermontProc<"slm">;// Legacyalias.
首先203与205行定义了两个SubtargetFeature的派生定义,分别用于表示Atom与Silvermont型号处理器的特性。208行Proc定义使用GenericModel作为描述模型。这个处理器模型用于粗略描述处理器(比如没有提供处理器完整的指令执行细节文档的情形)。
637 defGenericModel: SchedMachineModel{
638 letIssueWidth = 4;
639 letMicroOpBufferSize = 32;
640 letLoadLatency = 4;
641 letHighLatency = 10;
642 letPostRAScheduler = 0;
643 }
GenericModel不包含每周期的资源表。IssueWidth类似于解码单元的数量。Core与其子代,包括Nehalem及SandyBridge有4个解码器。解码器之外的资源执行微操作并被缓冲,因此相邻的微操作不会直接竞争。
MicroOpBufferSize > 1表示未经处理的依赖性可以在同一个周期里解码。对运行中的指令,值32是一个合理的主观值。
HighLatency = 10是乐观的。X86InstrInfo::isHighLatencyDef标志高时延的操作码。或者,在这里包含InstrItinData项来定义特定的操作数时延。因为这些时延不用于流水线冲突(pipelinehazard),它们不需要精确。
这里可以看到,即使GenericModel也是对Core及之后的处理器优化的,这个设置对老旧的Intel处理器并不适合(它们没有这么多解码单元,也没有微操作缓冲)。不过对指令调度来说,没有什么太大问题,只是会使老旧处理器的效率不高而已。
上面的处理器定义用到了许多特性描述,其中FeatureMMX表示处理器支持符合MMX标准的指令与寄存器:
41 def FeatureMMX:SubtargetFeature<"mmx","X86SSELevel", "MMX",
42 "Enable MMXinstructions">;
而Atom的SchedMachineModel派生定义是这样的:
537 defAtomModel : SchedMachineModel{
538 letIssueWidth = 2; //Allows 2 instructions per scheduling group.
539 let MicroOpBufferSize= 0; // In-order execution, always hide latency.
540 letLoadLatency = 3; // Expected cycles, may beoverriden by OperandCycles.
541 letHighLatency = 30;// Expected, may be overriden byOperandCycles.
542
543 // On the Atom,the throughput for taken branches is 2 cycles. For small
544 // simple loops,expand by a small factor to hide the backedge cost.
545 letLoopMicroOpBufferSize = 10;
546 letPostRAScheduler = 1;
547
548 letItineraries = AtomItineraries;
549 }
548行的AtomItineraries调度路线图是一组庞大的定义(X86ScheduleAtom.td)。Atom依赖它来描述各种指令对资源(功能单元)占用的情况。能这么做,是因为Atom使用顺序流水线,而且只有两个Port口(这是Intel的处理器手册定义的功能单元,手册上就这么定义的)。
20 def Port0 : FuncUnit; //ALU: ALU0, shift/rotate, load/store
21 // SIMD/FP: SIMD ALU,Shuffle,SIMD/FP multiply, divide
22 def Port1 :FuncUnit; // ALU: ALU1, bit processing, jump, andLEA
23 // SIMD/FP: SIMD ALU, FP Adder
24
25 defAtomItineraries : ProcessorItineraries<
26 [ Port0, Port1 ],
27 [], [
28 // P0 only
29 //InstrItinData<class, [InstrStage<N, [P0]>] >,
30 // P0 or P1
31 //InstrItinData<class, [InstrStage<N, [P0, P1]>] >,
32 // P0 and P1
33 //InstrItinData<class, [InstrStage<N, [P0], 0>, InstrStage<N, [P1]>] >,
34 //
35 // Default is 1cycle, port0 or port1
36 InstrItinData<IIC_ALU_MEM,[InstrStage<1, [Port0]>] >,
37 InstrItinData<IIC_ALU_NONMEM, [InstrStage<1,[Port0, Port1]>] >,
38 InstrItinData<IIC_LEA, [InstrStage<1,[Port1]>] >,
39 InstrItinData<IIC_LEA_16,[InstrStage<2, [Port0, Port1]>] >,
40 // mul
…
238 InstrItinData<IIC_SSE_MASKMOV,[InstrStage<2, [Port0, Port1]>] >,
239
240 InstrItinData<IIC_SSE_PEXTRW,[InstrStage<4, [Port0, Port1]>] >,
241 InstrItinData<IIC_SSE_PINSRW,[InstrStage<1, [Port0]>] >,
242
243 InstrItinData<IIC_SSE_PABS_RR,[InstrStage<1, [Port0, Port1]>] >,
244 InstrItinData<IIC_SSE_PABS_RM,[InstrStage<1, [Port0]>] >,
245
246 InstrItinData<IIC_SSE_MOV_S_RR,[InstrStage<1, [Port0, Port1]>] >,
247 InstrItinData<IIC_SSE_MOV_S_RM,[InstrStage<1, [Port0]>] >,
248 InstrItinData<IIC_SSE_MOV_S_MR,[InstrStage<1, [Port0]>] >,
249
250 InstrItinData<IIC_SSE_MOVA_P_RR,[InstrStage<1, [Port0, Port1]>] >,
251 InstrItinData<IIC_SSE_MOVA_P_RM,[InstrStage<1, [Port0]>] >,
252 InstrItinData<IIC_SSE_MOVA_P_MR,[InstrStage<1, [Port0]>] >,
253
254 InstrItinData<IIC_SSE_MOVU_P_RR,[InstrStage<1, [Port0, Port1]>] >,
255 InstrItinData<IIC_SSE_MOVU_P_RM,[InstrStage<3, [Port0, Port1]>] >,
256 InstrItinData<IIC_SSE_MOVU_P_MR,[InstrStage<2, [Port0, Port1]>] >,
257
258 InstrItinData<IIC_SSE_MOV_LH,[InstrStage<1, [Port0]>] >,
259
260 InstrItinData<IIC_SSE_LDDQU,[InstrStage<3, [Port0, Port1]>] >,
261
262 InstrItinData<IIC_SSE_MOVDQ,[InstrStage<1, [Port0]>] >,
263 InstrItinData<IIC_SSE_MOVD_ToGP,[InstrStage<3, [Port0]>] >,
264 InstrItinData<IIC_SSE_MOVQ_RR,[InstrStage<1, [Port0, Port1]>] >,
…
357 InstrItinData<IIC_FILD, [InstrStage<5,[Port0], 0>, InstrStage<5, [Port1]>] >,
358 InstrItinData<IIC_FLD, [InstrStage<1, [Port0]>] >,
359 InstrItinData<IIC_FLD80, [InstrStage<4,[Port0, Port1]>] >,
360
361 InstrItinData<IIC_FST, [InstrStage<2, [Port0, Port1]>] >,
362 InstrItinData<IIC_FST80, [InstrStage<5,[Port0, Port1]>] >,
363 InstrItinData<IIC_FIST, [InstrStage<6, [Port0, Port1]>] >,
…
533 InstrItinData<IIC_NOP, [InstrStage<1,[Port0, Port1]>] >
534 ]>;
X86ScheduleAtom.td文件开头的注释提到,这部分定义来自“Intel 64 andIA32 Architectures Optimization Reference Manual”的第13章,第4节(2016年版则在第14章,第4节)。这份文档网上可以下载。文档中将这两个ALU命名为Port0与Port1,LLVM也遵循它的命名,在20与22行给出这两个定义。文档在第13章,第4节给出了一张表各种指令与ALU绑定及执行时延等信息,AtomItineraries正是根据这张表来构建的。比如,第36、37行的定义来自表的以下两项:
Instruction
Ports
Latency
Throughput
ADD/AND/CMP/OR/SUB/XOR/TEST mem, reg;
ADD/AND/CMP/OR/SUB/XOR2 reg, mem;
0
1
1
ADD/AND/CMP/OR/SUB/XOR reg, Imm8
ADD/AND/CMP/OR/SUB/XOR reg, imm
(0, 1)
1
0.5
现在我们看一下具体的例子。比如下面的指令定义(X86InstrCompiler.td):
551 multiclassLOCK_ArithBinOp<bits<8> RegOpc,bits<8> ImmOpc, bits<8> ImmOpc8,
552 Format ImmMod,string mnemonic> {
553 let Defs = [EFLAGS], mayLoad = 1, mayStore = 1,isCodeGenOnly = 1,
554 SchedRW = [WriteALULd, WriteRMW]in {
555
556 def NAME#8mr :I<{RegOpc{7}, RegOpc{6}, RegOpc{5}, RegOpc{4},
557 RegOpc{3}, RegOpc{2},RegOpc{1}, 0 },
558 MRMDestMem, (outs), (insi8mem:$dst, GR8:$src2),
559 !strconcat(mnemonic,"{b}\t",
560 "{$src2,$dst|$dst, $src2}"),
561 [], IIC_ALU_NONMEM>, LOCK;
562 def NAME#16mr :I<{RegOpc{7}, RegOpc{6}, RegOpc{5}, RegOpc{4},
563 RegOpc{3}, RegOpc{2}, RegOpc{1},1 },
564 MRMDestMem, (outs), (insi16mem:$dst, GR16:$src2),
565 !strconcat(mnemonic,"{w}\t",
566 "{$src2,$dst|$dst, $src2}"),
567 [], IIC_ALU_NONMEM>,OpSize16, LOCK;
568 def NAME#32mr :I<{RegOpc{7}, RegOpc{6}, RegOpc{5}, RegOpc{4},
569 RegOpc{3}, RegOpc{2},RegOpc{1}, 1 },
570 MRMDestMem, (outs), (insi32mem:$dst, GR32:$src2),
571 !strconcat(mnemonic,"{l}\t",
572 "{$src2,$dst|$dst, $src2}"),
573 [], IIC_ALU_NONMEM>,OpSize32, LOCK;
574 def NAME#64mr :RI<{RegOpc{7}, RegOpc{6}, RegOpc{5}, RegOpc{4},
575 RegOpc{3}, RegOpc{2},RegOpc{1}, 1 },
576 MRMDestMem, (outs), (insi64mem:$dst, GR64:$src2),
577 !strconcat(mnemonic,"{q}\t",
578 "{$src2,$dst|$dst, $src2}"),
579 [], IIC_ALU_NONMEM>,LOCK;
580
581 def NAME#8mi :Ii8<{ImmOpc{7}, ImmOpc{6}, ImmOpc{5}, ImmOpc{4},
582 ImmOpc{3}, ImmOpc{2},ImmOpc{1}, 0 },
583 ImmMod, (outs), (ins i8mem:$dst, i8imm :$src2),
584 !strconcat(mnemonic,"{b}\t",
585 "{$src2,$dst|$dst, $src2}"),
586 [], IIC_ALU_MEM>, LOCK;
587
588 def NAME#16mi :Ii16<{ImmOpc{7}, ImmOpc{6}, ImmOpc{5}, ImmOpc{4},
589 ImmOpc{3}, ImmOpc{2},ImmOpc{1}, 1 },
590 ImmMod, (outs), (ins i16mem:$dst, i16imm :$src2),
591 !strconcat(mnemonic,"{w}\t",
592 "{$src2,$dst|$dst, $src2}"),
593 [], IIC_ALU_MEM>,OpSize16, LOCK;
594
595 def NAME#32mi :Ii32<{ImmOpc{7}, ImmOpc{6}, ImmOpc{5}, ImmOpc{4},
596 ImmOpc{3}, ImmOpc{2},ImmOpc{1}, 1 },
597 ImmMod, (outs), (ins i32mem:$dst, i32imm :$src2),
598 !strconcat(mnemonic,"{l}\t",
599 "{$src2,$dst|$dst, $src2}"),
600 [], IIC_ALU_MEM>,OpSize32, LOCK;
601
602 def NAME#64mi32 :RIi32S<{ImmOpc{7}, ImmOpc{6}, ImmOpc{5}, ImmOpc{4},
603 ImmOpc{3}, ImmOpc{2},ImmOpc{1}, 1 },
604 ImmMod, (outs), (ins i64mem:$dst, i64i32imm :$src2),
605 !strconcat(mnemonic, "{q}\t",
606 "{$src2, $dst|$dst, $src2}"),
607 [], IIC_ALU_MEM>, LOCK;
608
609 def NAME#16mi8 :Ii8<{ImmOpc8{7}, ImmOpc8{6}, ImmOpc8{5}, ImmOpc8{4},
610 ImmOpc8{3}, ImmOpc8{2},ImmOpc8{1}, 1 },
611 ImmMod, (outs), (ins i16mem:$dst, i16i8imm :$src2),
612 !strconcat(mnemonic,"{w}\t",
613 "{$src2,$dst|$dst, $src2}"),
614 [], IIC_ALU_MEM>,OpSize16, LOCK;
615 def NAME#32mi8 :Ii8<{ImmOpc8{7}, ImmOpc8{6}, ImmOpc8{5}, ImmOpc8{4},
616 ImmOpc8{3}, ImmOpc8{2},ImmOpc8{1}, 1 },
617 ImmMod, (outs), (ins i32mem:$dst, i32i8imm :$src2),
618 !strconcat(mnemonic,"{l}\t",
619 "{$src2,$dst|$dst, $src2}"),
620 [], IIC_ALU_MEM>,OpSize32, LOCK;
621 def NAME#64mi8 :RIi8<{ImmOpc8{7}, ImmOpc8{6}, ImmOpc8{5}, ImmOpc8{4},
622 ImmOpc8{3}, ImmOpc8{2},ImmOpc8{1}, 1 },
623 ImmMod, (outs),(ins i64mem :$dst, i64i8imm :$src2),
624 !strconcat(mnemonic, "{q}\t",
625 "{$src2,$dst|$dst, $src2}"),
626 [], IIC_ALU_MEM>,LOCK;
627
628 }
629
630 }
631
632 defm LOCK_ADD : LOCK_ArithBinOp<0x00,0x80, 0x83, MRM0m, "add">;
633 defm LOCK_SUB : LOCK_ArithBinOp<0x28, 0x80, 0x83,MRM5m, "sub">;
634 defm LOCK_OR :LOCK_ArithBinOp<0x08, 0x80, 0x83, MRM1m, "or">;
635 defm LOCK_AND : LOCK_ArithBinOp<0x20, 0x80, 0x83,MRM4m, "and">;
636 defmLOCK_XOR : LOCK_ArithBinOp<0x30, 0x80, 0x83, MRM6m, "xor">;
在上面的定义里,RegOpc{7}表示访问RegOpc的第7位(RegOpc是一个bits<8>)。而像559行的!strconcat(mnemonic,"{b}\t", "{$src2, $dst|$dst, $src2}")则是这样的语义,比如632行的定义,mnemonic是add,那么!strconcat执行后得到:add{b},{$src2, $dst|$dst, $src2},这包含了AT&T及Intel汇编形式,LLVM将根据需要输出:addb,$src2, $dst或add $dst, $src2。
LOCK_ArithBinOp是一个multiclass,从派生它将展开为多个定义,比如LOCK_ADD将展开为这些指令定义:LOCK_ADD8mr,LOCK_ADD16mr,LOCK_ADD32mr,LOCK_ADD64mr,LOCK_ADD8mi,LOCK_ADD16mi,LOCK_ADD32mi,LOCK_ADD64mi32,LOCK_ADD64mi8,LOCK_ADD16mi8,LOCK_ADD32mi8,LOCK_ADD64mi8。
这些指令定义的SchedRW被定义为[WriteALULd,WriteRMW],WriteALULd与WriteRMW都是SchedWrite的空派生def。它们表示这些指令所占用的资源,其中WriteALULd表示这些是寄存器-内存间简单的整形ALU操作,WriteRMW表示这些是执行读-修改-写的指令。但在Atom的定义里,资源的映射实际上是由IIC_ALU_MEM,IIC_ALU_NONMEM这样的InstrItinClass派生定义来实现的。指令的SchedRW定义,对Atom处理器是没有意义的。
2.4.2.2. Sandy Bridge的描述
采用Intel SandyBridge架构的处理器要比Atom复杂得多,下图显示处理器的流水线与主要组件。
其流水线包括:
· 一个获取指令并将它们解码为微操作的顺序发布前端。前端向下一个流水线阶段提供来自程序执行最可能路径的连续微操作流。
· 一个乱序的,能每周期分发执行最多6个微操作的超标量执行引擎。分配/重命名块将微操作重排为“数据流”序,使得它们可以在资源就绪后尽快执行。
· 一个确保微操作的执行结果,包括它们可能遇到任何异常,以原有程序顺序出现的顺序回收单元。
指令在流水线里的流动可以被总结为以下的动作:
1. 分支预测单元从程序选择下一个要执行的代码块。处理器在以下资源中以这个次序查找代码:
a. 已解码 ICache
b. 指令Cache,通过激活遗留解码流水线
c. 如果需要,L2 Cache,末级缓存(llc)以及内存
2. 代码对应的微操作被发送到重命名/回收单元。它们以程序序进入调度器,但根据数据流序从调度器执行与释放。至于同时就绪微操作,几乎总是维持FIFO序。
由安排在三个栈上的执行资源执行微操作。每个栈上的执行单元与指令的数据类型相关。
分支执行触发分支预测。它将发布微操作的前端重导向到正确的路径。处理器可以用后面正确路径的操作覆盖掉在分支误判之前的操作。
3. 管理及重排内存操作以实现并发性及最大的性能。L1数据Cache不命中的数据将进入L2Cache。数据Cache是非阻塞的,可以处理多个并发的不命中。
4. 在出错指令退出时(或尝试退出时)触发异常(Fault,Trap)。
Sandy Bridge微架构的整体情况由下面的SchedMachineModel派生定义来描述。比如一周期能发布、执行4个微操作,微操作重排队列增大到168,而且还有循环微操作缓冲(用于循环流检测)。
15 defSandyBridgeModel : SchedMachineModel{
16 // All x86instructions are modeled as a single micro-op, and SB can decode 4
17 // instructionsper cycle.
18 // FIXME:Identify instructions that aren't a single fused micro-op.
19 let IssueWidth= 4;
20 letMicroOpBufferSize = 168; // Based on the reorderbuffer.
21 letLoadLatency = 4;
22 letMispredictPenalty = 16;
23
24 // Based on theLSD (loop-stream detector) queue size.
25 letLoopMicroOpBufferSize = 28;
26
27 // FIXME: SSE4and AVX are unimplemented. This flag is set to allow
28 // the schedulerto assign a default model to unrecognized opcodes.
29 letCompleteModel = 0;
30 }
实际上,Sandy Bridge微架构相当复杂,难以在处理器层次对指令的执行进行详细描述。因此,它更多是通过定义WriteRes与ReadAdvance将处理器资源及时延关联到每个SchedReadWrite定义,来描述指令的执行。
2.4.2.2.1. 资源的描述
在LLVM里对Sandy Bridge执行资源的描述在X86SchedSandyBridge.td。在进入具体定义之前,我们先看一下描述资源所需的定义。首先是ProcResourceUnits:
165 classProcResourceUnits<ProcResourceKind kind, intnum> {
166 ProcResourceKind Kind = kind;
167 int NumUnits = num;
168 ProcResourceKind Super = ?;
169 int BufferSize = -1;
170 SchedMachineModel SchedModel = ?;
171 }
它定义了若干可互换的处理器资源。NumUnits确定要求该资源指令的吞吐率。
ProcResourceUnits通常在一个乱序引擎里构建几个被缓冲的资源。被缓冲的资源可以被持有几个时钟周期,但调度器不会把它们固定在相对于指令分发的一个特定时钟周期。设置BufferSize为0表示一个顺序发布的资源。在这个情形下,调度器从顺序发出指令的周期倒数,一旦后续指令要求相同的资源,强制暂停,直到WriteRes中指定的ResourceCyles个周期过去。设置BufferSize为1表示一个顺序时延资源。在这个情形下,调度器在使用这个资源的指令之间建立生产者/消费者暂停。
例子(都假定一个乱序引擎):
对由一个一体式保留站(unified reservation station)填充的“发布端口”使用BufferSize= -1(保留站的作用是排队微操作,直到所有源操作数就绪,将就绪的微操作调度并分发到可用的执行单元)。这里保留站的尺寸由MicroOpBufferSize给出,MicroOpBufferSize应该是寄存器重命名池、一体式保留站、或重排缓冲中的最小尺寸。
对强制“成组发布(dispatch/issue groups)”的资源使用BufferSize= 0(不同的处理器定义以不同的方式定义发布。这里指解码为微操作以及移入保留站之间的阶段)。通常NumMicroOps足以限制组发布。不过,一些处理器可以构成仅有特定指令类型组合的组,比如POWER7。
对顺序执行单元使用BufferSize = 1。这用于一个乱序核内的一个顺序流水线,在那里依赖调度的操作保证靠在一起,并生成一个空泡,比如Cortex-a9浮点单元。
对带有独立保留站的乱序执行单元使用BufferSize > 1。这只是模仿了该保留站的尺寸。
为了模仿发布组与顺序执行单元,创建两个单元类型,一个BufferSize=0,另一个BufferSize=1。
179行的SchedModel用于将资源单元绑定到处理器(参考下面SandyBridge的定义)。
165行的参数ProcResourceKind只是一个空的class,类似于C++里的抽象基类,代表处理器资源类型。因此处理器资源的定义是这样的,num表示资源的个数:
180 classProcResource<int num> : ProcResourceKind,
181 ProcResourceUnits<EponymousProcResourceKind,num>;
其中EponymousProcResourceKind是一个空的def:
176 def EponymousProcResourceKind : ProcResourceKind;
据说它主要是为了能让ProcResourceUnits定义能援引它自己,不过我看不出这是为什么。
多种资源还能构成资源组。
183 classProcResGroup<list<ProcResource>resources> : ProcResourceKind {
184 list<ProcResource> Resources =resources;
185 SchedMachineModel SchedModel = ?;
186 int BufferSize = -1;
187 }
另外,对写操作专门定义了一个基类ProcWriteResources:
233 classProcWriteResources<list<ProcResourceKind>resources> {
234 list<ProcResourceKind> ProcResources =resources;
235 list<int> ResourceCycles = [];
236 int Latency = 1;
237 int NumMicroOps = 1;
238 bit BeginGroup = 0;
239 bit EndGroup = 0;
240 // Allow aprocessor to mark some scheduling classes as unsupported
241 // for strongerverification.
242 bit Unsupported = 0;
243 SchedMachineModel SchedModel = ?;
244 }
注意,资源一定属于某个处理器,因此定义中总是有SchedMachineModel来将它们绑定到指定的处理器。
2.4.2.2.2. 执行的描述
首先是SchedReadWrite的定义,同样作为类似于抽象基类的成分,它是一个空的class。对SandyBridge这样具有乱序执行微操作能力的处理器来说,原则上每个SchedReadWrite派生定义对应一个微操作执行。
207 // Define a schedulerresource associated with a def operand.
208 classSchedWrite :SchedReadWrite;
209 def NoWrite : SchedWrite;
210
211 // Define a scheduler resource associated with a useoperand.
212 classSchedRead: SchedReadWrite;
将ProcWriteResources派生定义绑定到资源有两种方式,分别是WriteRes与SchedWriteRes。
ProcWriteResources是WriteRes与SchedWriteRes的基类。
280 classWriteRes<SchedWrite write,list<ProcResourceKind> resources>
281 : ProcWriteResources<resources>{
282 SchedWrite WriteType = write;
283 }
288 classSchedWriteRes<list<ProcResourceKind>resources> : SchedWrite,
289 ProcWriteResources<resources>;
WriteRes定义了资源以及一个SchedWrite(微操作)的时延。这将由没有itinerary类的目标机器直接使用。在这个情形下,SchedWrite由目标机器定义,而resources由次级目标机器定义,并且将SchedWrite映射到处理器资源(参见SandyBridge的定义)。
如果目标机器已经有itinerary类,可以使用SchedWriteRes来定义子目标机器特定的SchedWrites,并将它们一起映射到处理器资源。然后ItinRW可以将itinerary类映射到次级目标机器的SchedWrites。
ProcResources(类ProcWriteResources的成员,后面的ResourceCycles也是)代表由写操作消耗的资源组。可选地,ResourceCycles用于表示资源被消费的周期数。每个ResourceCycles项与ProcResources列表里相同位置的ProcResource项结对。因为逐一列举ResourceCycles的情况很少,这个列表可能是不完整的。默认的,不管时延,只消费资源一个周期,这模仿了一个全流水线化的单元。ResourceCycles值为0意味着资源必须可用但不消耗,这仅与非缓冲资源相关。
缺省的每个SchedWrite耗费一个微操作,这根据处理器的IssueWidth限制来计数。如果一条指令可以在单个微操作里写多个寄存器,次级目标机器应该将其中一个写定义为零个微操作。如果次级目标机器要求多个微操作写同一个结果,应该要么将写的NumMicroOps改写为大于1,或要求另外的写操作。可以通过定义一个WriteSequence来定义额外的写,或只是在指令的写入者列表里列出“def”操作数以外的额外写。调度器假定所有的微操作必须在同一个周期里发布。可以要求这些微操作来启动或终止当前的发布组。
SchedWriteRes的作用与WriteRes类似,不过它是SchedWrite的派生定义,因此它可以直接用在SchedReadWrite定义能使用的地方。SchedWriteRes定义了一个新的SchedWrite类型,与此同时将它关联到一组WriteResources。这个新的SchedWrite类不知道它的SchedModel,所以必须由InstRW或ItinRW来引用它。
最后一个与写相关的定义是WriteSequence。它将一个SchedWrite构建为一组带有累计时延的SchedWrite。这相当于将一个操作数映射到由前面定义的一组SchedWrite组成的资源。
224 classWriteSequence<list<SchedWrite> writes, int rep= 1> : SchedWrite {
225 list<SchedWrite> Writes = writes;
226 int Repeat = rep;
227 SchedMachineModel SchedModel = ?;
228 }
如果这个序列中最后的写被标记为Variadic,在解析了最后写的谓词之后,前面的写分散在所有的操作数上。
对于读操作,则有这些资源相关定义。首先是ProcReadAdvance:
294 classProcReadAdvance<int cycles, list<SchedWrite> writes = []> {
295 int Cycles = cycles;
296 list<SchedWrite> ValidWrites = writes;
297 // Allow aprocessor to mark some scheduling classes as unsupported
298 // for strongerverification.
299 bit Unsupported = 0;
300 SchedMachineModel SchedModel = ?;
301 }
其中的SchedModel将这些定义的资源绑定到处理器。从ProcReadAdvance派生出ReadAdvance与SchedReadAdvance。
313 classReadAdvance<SchedRead read,int cycles, list<SchedWrite> writes = []>
314 : ProcReadAdvance<cycles,writes> {
315 SchedRead ReadType = read;
316 }
处理器可以定义与一个SchedRead关联的ReadAdvance来将前面一个写的时延减少N个周期。一个负的进展实际上增加了时延,这可能用于跨域暂停。
ReadAdvance可与一组SchedWrite关联以实现流水线旁路。写列表可以是空的来表示总是比普通寄存器读迟cycles周期的读入操作数,允许读的父指令相对于写入者更早地发布它。
320 classSchedReadAdvance<int cycles, list<SchedWrite> writes = []> : SchedRead,
321 ProcReadAdvance<cycles,writes>;
SchedReadAdvance则直接将一个新的SchedRead类型与一个时延及可选的流水线旁路关联。与InstRW或ItinRW一起使用。
我们已经知道在Instruction的定义里,SchedRW(类型list<SchedReadWrite>)给出了每个操作数所占用的资源的描述。另外,对那些子目标机器间存在较大差异的目标机器,比如ARM,LLVM提供了一个语法糖果InstRW用以将一组指令重新绑定到另一组SchedReadWrite定义。
391 classInstRW<list<SchedReadWrite> rw, dag instrlist> {
392 list<SchedReadWrite> OperandReadWrites= rw;
393 dag Instrs =instrlist;
394 SchedMachineMode lSchedModel = ?;
395 }
这是ARM的一个例子(AArch64SchedA53.td):
204 def : InstRW<[A53WriteVLD1], (instregex"LD1i(8|16|32|64)$")>;
在这个定义里以LD1i8,LD1i16,LD1i32及LD1i64开头的指令被重新绑定到A53WriteVLD1。同样,instregex是另一个TableGen支持的内置关键字,是用于描述指令的正则表达式。它也是由TableGen根据表达式自己展开。
另一个语法糖果目前也是给ARM家族的。ItinRW将一组InstrItinClass映射为一组SchedReadWrite。
402 classItinRW<list<SchedReadWrite> rw,list<InstrItinClass> iic> {
403 list<InstrItinClass> MatchedItinClasses= iic;
404 list<SchedReadWrite> OperandReadWrites= rw;
405 SchedMachineModel SchedModel = ?;
406 }
这是ARM的一个例子(ARMScheduleA9.td)
2264 def :ItinRW<[WriteALU, A9ReadALU],[IIC_iMVNr]>;
表示步骤IIC_iMVNr使用资源WriteALU与A9ReadALU。
最后一颗糖果是SchedAlias,子目标机器可以通过它将一个SchedReadWrite映射到一个WriteSequence,SchedWriteVariant或者SchedReadVariant。
415 classSchedAlias<SchedReadWrite match, SchedReadWritealias> {
416 SchedReadWrite MatchRW = match;
417 SchedReadWrite AliasRW = alias;
418 SchedMachineModel SchedModel = ?;
419 }
这是ARM的一个例子(AArch64SchedA53.td):
169 def : SchedAlias<ReadISReg, A53ReadISReg>;
其中A53ReadISReg是一个SchedReadVariant派生定义:
166 def A53ReadISReg : SchedReadVariant<[
167 SchedVar<RegShiftedPred,[A53ReadShifted]>,
168 SchedVar<NoSchedPred,[A53ReadNotShifted]>]>;
SchedReadVariant派生自SchedRead与SchedVariant。它将一个SchedRead映射到一组SchedRead,以这组SchedRead给出的谓词为条件,从中选择符合条件的SchedRead定义。
383 classSchedReadVariant<list<SchedVar>variants> : SchedRead,
384 SchedVariant<variants>{
385 }
其中基类SchedVariant的定义为:
361 classSchedVariant<list<SchedVar>variants> {
362 list<SchedVar> Variants = variants;
363 bit Variadic = 0;
364 SchedMachineModel SchedModel = ?;
365 }
定义中的SchedVar列表定义了这个选择过程。默认的,选中的SchedReadWrites仍然与一个操作数关联并假定以累计的时延顺序执行。不过,如果父SchedWriteVariant或SchedReadVariant被标记为“Variadic”,那么每个选中SchedReadWrite被适当地映射到该指令的可变操作数。在这个情形下,时延不是累加的。如果当前变量已经是一个Sequence的部分,那么在该变量之前的使用部分应用在可变操作数上。
355 classSchedVar<SchedPredicate pred, list<SchedReadWrite> selected> {
356 SchedPredicate Predicate = pred;
357 list<SchedReadWrite> Selected =selected;
358 }
另外356行的SchedPredicate是这样定义的(344行是它的一个特殊派生def,表示没有谓词,即永远选中):
340 classSchedPredicate<code pred> {
341 SchedMachineModel SchedModel = ?;
342 code Predicate = pred;
343 }
344 defNoSchedPred: SchedPredicate<[{true}]>;
看到A53ReadISReg给出了两个SchedRead。在满足谓词RegShiftedPred时,选中A53ReadShifted,否则选中A53ReadNotShifted。
而ReadISReg则是一个平凡的定义:
29 def ReadISReg: SchedRead; // ALU ofShifted-Reg
事实上,关键的是它本身代表的操作。
那么现在有了A53ReadISReg定义,如果某条指令使用了ReadISReg,那么在执行ReadISReg时,是从A53ReadISReg中根据谓词选择A53ReadShifted或A53ReadNotShifted来执行。更多关于ItinRW以及SchedVariant,SchedVar的内容,参考“从ItinRW定义推导”一节。
2.4.2.2.3. Sandy Bridge的定义
SandyBridgeModel是没有Itinerary的(而Atom则主要通过Itinerary来描述),因为SandyBridge先将指令翻译为一系列微操作,并具有乱序执行微操作的能力,可同时执行不同指令的微操作。因此SandyBridge(及其后续CPU)使用一系列SchedReadWrite派生定义来描述对操作数的各种操作(大致可视为微操作的执行),并通过各种WriteRes派生定义关联这些操作所需的资源。
Sandy Bridge的相关定义如下。在所有这些定义里用到的SchedWrite都是X86FoldableSchedWrite的派生定义。X86FoldableSchedWrite是这样定义的:
27 classX86FoldableSchedWrite : SchedWrite{
28 // The SchedWriteto use when a load is folded into the instruction.
29 SchedWrite Folded;
30 }
大多数指令有可以直接操作内存的版本,因此几乎每个SchedWrite都有两个变种:操作寄存器或操作内存(操作内存的版本带有Ld后缀)。X86SchedWritePair是定义这些操作对的一个方便的基类:
33 multiclassX86SchedWritePair {
34 //Register-Memory operation.
35 def Ld :SchedWrite;
36 //Register-Register operation.
37 def NAME : X86FoldableSchedWrite {
38 let Folded= !cast<SchedWrite>(NAME#"Ld");
39 }
40 }
37行的NAME是TableGen中一个特殊的标识符,它代表X86SchedWritePair派生定义的名字。比如下面43行的WriteALU,这时NAME就是WriteALU。但35行的定义则会生成WriteALULd这个名字的定义(在前面LOCK_ArithBinOP定义可以看到其中的SchedRW被定义为 [WriteALULd, WriteRMW])。使用X86SchedWritePair的定义集中在X86Schedule.td,比如:
42 // Arithmetic.
43 defm WriteALU: X86SchedWritePair;// Simple integer ALU op.
44 defm WriteIMul : X86SchedWritePair;// Integer multiplication.
45 def WriteIMulH: SchedWrite; // Integer multiplication, high part.
46 defm WriteIDiv : X86SchedWritePair;// Integer division.
47 def WriteLEA : SchedWrite; // LEAinstructions can't fold loads.
48
49 // Integer shifts and rotates.
50 defm WriteShift : X86SchedWritePair;
51
52 // Loads, stores, and moves, not folded with otheroperations.
53 def WriteLoad : SchedWrite;
54 def WriteStore : SchedWrite;
55 defWriteMove : SchedWrite;
类似于X86SchedWritePair,下面72行的SBWriteResPair是封装类似WriteALU与WriteALULd这样的操作对与相关资源的一个便利方法。从定义可以看出,执行内存操作的版本比寄存器版本的时延要多4处理器周期(下面72行SBWriteResPair的定义)。
在下面首先定义了Sandy Bridge的资源。与Atom将Port0、Port1都定义为功能单元(FuncUnit)不同,SandyBridge将Port1~Port5都定义为资源,并根据使用情况定义了资源组(ProcResource后面的数字表示资源的数目,一个ProcResource派生def同时也是一个新的ProcResourceKind)。这是必然的,因为在SandyBridge里,指令只要使用不同的资源,且没有依赖关系,就能并发执行,指令能否调度很大程度上取决于是否有可用资源。从资源角度描述处理器能更好地支持指令调度。57行的BufferSize实际上是SandyBridge保留站(reservationstation )的深度。
32 let SchedModel = SandyBridgeModelin{
33
34 // Sandy Bridge can issue micro-ops to 6 different portsin one cycle.
35
36 // Ports 0, 1, and 5 handle all computation.
37 def SBPort0 : ProcResource<1>;
38 def SBPort1 : ProcResource<1>;
39 def SBPort5 : ProcResource<1>;
40
41 // Ports 2 and 3 are identical. They handle loads and theaddress half of
42 // stores.
43 def SBPort23 : ProcResource<2>;
44
45 // Port 4 gets the data half of stores. Store data can beavailable later than
46 // the store address, but since we don't model thelatency of stores, we can
47 // ignore that.
48 def SBPort4 : ProcResource<1>;
49
50 // Many micro-ops are capable of issuing on multipleports.
51 def SBPort05 :ProcResGroup<[SBPort0, SBPort5]>;
52 def SBPort15 :ProcResGroup<[SBPort1, SBPort5]>;
53 def SBPort015 : ProcResGroup<[SBPort0, SBPort1,SBPort5]>;
54
55 // 54 Entry Unified Scheduler
56 def SBPortAny : ProcResGroup<[SBPort0, SBPort1,SBPort23, SBPort4, SBPort5]> {
57 letBufferSize=54;
58 }
59
60 // Integer division issued on port 0.
61 def SBDivider : ProcResource<1>;
62
63 // Loads are 4 cycles, so ReadAfterLd registers needn'tbe available until 4
64 // cycles after the memory operand.
65 def : ReadAdvance<ReadAfterLd,4>;
66
67 // Many SchedWrites are defined in pairs with and withouta folded load.
68 // Instructions with folded loads are usually micro-fused,so they only appear
69 // as two micro-ops when queued in the reservationstation.
70 // This multiclass defines the resource usage forvariants with and without
71 // folded loads.
72 multiclass SBWriteResPair<X86FoldableSchedWrite SchedRW,
73 ProcResourceKindExePort,
74 int Lat> {
75 // Registervariant is using a single cycle on ExePort.
76 def : WriteRes<SchedRW, [ExePort]> { let Latency = Lat; }
77
78 // Memory variantalso uses a cycle on port 2/3 and adds 4 cycles to the
79 // latency.
80 def :WriteRes<SchedRW.Folded, [SBPort23, ExePort]> {
81 letLatency = !add(Lat, 4);
82 }
83 }
84
85 // A folded store needs a cycle on port 4 for the storedata, but it does not
86 // need an extra port 2/3 cycle to recompute the address.
87 def : WriteRes<WriteRMW, [SBPort4]>;
88
89 def : WriteRes<WriteStore, [SBPort23,SBPort4]>;
90 def : WriteRes<WriteLoad, [SBPort23]> {letLatency = 4; }
91 def : WriteRes<WriteMove, [SBPort015]>;
92 def : WriteRes<WriteZero, []>;
93
94 defm : SBWriteResPair<WriteALU, SBPort015, 1>;
95 defm : SBWriteResPair<WriteIMul, SBPort1, 3>;
96 def :WriteRes<WriteIMulH, []> {let Latency =3; }
97 defm : SBWriteResPair<WriteShift, SBPort05, 1>;
98 defm : SBWriteResPair<WriteJump, SBPort5, 1>;
99
100 // This is for simple LEAs with one or two inputoperands.
101 // The complex ones can only execute on port 1, and theyrequire two cycles on
102 // the port to read all inputs. We don't model that.
103 def : WriteRes<WriteLEA, [SBPort15]>;
104
105 // This is quite rough, latency depends on the dividend.
106 def : WriteRes<WriteIDiv, [SBPort0, SBDivider]>{
107 let Latency =25;
108 letResourceCycles = [1, 10];
109 }
110 def : WriteRes<WriteIDivLd, [SBPort23, SBPort0,SBDivider]> {
111 let Latency =29;
112 letResourceCycles = [1, 1, 10];
113 }
114
115 // Scalar and vector floating point.
116 defm : SBWriteResPair<WriteFAdd, SBPort1, 3>;
117 defm : SBWriteResPair<WriteFMul, SBPort0, 5>;
118 defm : SBWriteResPair<WriteFDiv, SBPort0, 12>;//10-14 cycles.
119 defm : SBWriteResPair<WriteFRcp, SBPort0, 5>;
120 defm : SBWriteResPair<WriteFRsqrt, SBPort0, 5>;
121 defm : SBWriteResPair<WriteFSqrt, SBPort0, 15>;
122 defm : SBWriteResPair<WriteCvtF2I, SBPort1, 3>;
123 defm : SBWriteResPair<WriteCvtI2F, SBPort1, 4>;
124 defm : SBWriteResPair<WriteCvtF2F, SBPort1, 3>;
125 defm : SBWriteResPair<WriteFShuffle, SBPort5, 1>;
126 defm : SBWriteResPair<WriteFBlend, SBPort05, 1>;
127 def : WriteRes<WriteFVarBlend, [SBPort0,SBPort5]> {
128 let Latency =2;
129 letResourceCycles = [1, 1];
130 }
131 def : WriteRes<WriteFVarBlendLd, [SBPort0,SBPort5, SBPort23]> {
132 let Latency =6;
133 letResourceCycles = [1, 1, 1];
134 }
135
136 // Vector integer operations.
137 defm : SBWriteResPair<WriteVecShift,SBPort05, 1>;
138 defm : SBWriteResPair<WriteVecLogic, SBPort015,1>;
139 defm : SBWriteResPair<WriteVecALU, SBPort15, 1>;
140 defm : SBWriteResPair<WriteVecIMul, SBPort0, 5>;
141 defm : SBWriteResPair<WriteShuffle, SBPort15, 1>;
142 defm : SBWriteResPair<WriteBlend, SBPort15, 1>;
143 def : WriteRes<WriteVarBlend, [SBPort1,SBPort5]> {
144 let Latency =2;
145 letResourceCycles = [1, 1];
146 }
147 def : WriteRes<WriteVarBlendLd, [SBPort1, SBPort5,SBPort23]> {
148 let Latency =6;
149 letResourceCycles = [1, 1, 1];
150 }
151 def : WriteRes<WriteMPSAD, [SBPort0, SBPort1,SBPort5]> {
152 let Latency =6;
153 letResourceCycles = [1, 1, 1];
154 }
155 def : WriteRes<WriteMPSADLd, [SBPort0, SBPort1,SBPort5, SBPort23]> {
156 let Latency =6;
157 letResourceCycles = [1, 1, 1, 1];
158 }
159
160 // String instructions.
161 // Packed Compare Implicit Length Strings, Return Mask
162 def : WriteRes<WritePCmpIStrM, [SBPort015]> {
163 let Latency =11;
164 letResourceCycles = [3];
165 }
166 def : WriteRes<WritePCmpIStrMLd, [SBPort015,SBPort23]> {
167 let Latency =11;
168 letResourceCycles = [3, 1];
169 }
170
171 // Packed Compare Explicit Length Strings, Return Mask
172 def : WriteRes<WritePCmpEStrM, [SBPort015]> {
173 let Latency =11;
174 letResourceCycles = [8];
175 }
176 def : WriteRes<WritePCmpEStrMLd, [SBPort015,SBPort23]> {
177 let Latency =11;
178 letResourceCycles = [7, 1];
179 }
180
181 // Packed Compare Implicit Length Strings, Return Index
182 def : WriteRes<WritePCmpIStrI, [SBPort015]> {
183 let Latency =3;
184 letResourceCycles = [3];
185 }
186 def : WriteRes<WritePCmpIStrILd, [SBPort015,SBPort23]> {
187 let Latency =3;
188 letResourceCycles = [3, 1];
189 }
190
191 // Packed Compare Explicit Length Strings, Return Index
192 def : WriteRes<WritePCmpEStrI, [SBPort015]> {
193 let Latency =4;
194 letResourceCycles = [8];
195 }
196 def : WriteRes<WritePCmpEStrILd, [SBPort015,SBPort23]> {
197 let Latency =4;
198 letResourceCycles = [7, 1];
199 }
200
201 // AES Instructions.
202 def : WriteRes<WriteAESDecEnc, [SBPort015]> {
203 let Latency =8;
204 let ResourceCycles= [2];
205 }
206 def : WriteRes<WriteAESDecEncLd, [SBPort015,SBPort23]> {
207 let Latency =8;
208 letResourceCycles = [2, 1];
209 }
210
211 def : WriteRes<WriteAESIMC, [SBPort015]> {
212 let Latency =8;
213 letResourceCycles = [2];
214 }
215 def : WriteRes<WriteAESIMCLd, [SBPort015,SBPort23]> {
216 let Latency =8;
217 letResourceCycles = [2, 1];
218 }
219
220 def : WriteRes<WriteAESKeyGen, [SBPort015]> {
221 let Latency =8;
222 letResourceCycles = [11];
223 }
224 def : WriteRes<WriteAESKeyGenLd, [SBPort015,SBPort23]> {
225 let Latency =8;
226 letResourceCycles = [10, 1];
227 }
228
229 // Carry-less multiplication instructions.
230 def : WriteRes<WriteCLMul, [SBPort015]> {
231 let Latency =14;
232 let ResourceCycles= [18];
233 }
234 def : WriteRes<WriteCLMulLd, [SBPort015,SBPort23]> {
235 let Latency =14;
236 letResourceCycles = [17, 1];
237 }
238
239
240 def : WriteRes<WriteSystem, [SBPort015]> {let Latency = 100; }
241 def : WriteRes<WriteMicrocoded, [SBPort015]> {let Latency = 100; }
242 def : WriteRes<WriteFence, [SBPort23,SBPort4]>;
243 def : WriteRes<WriteNop, []>;
244
245 // AVX2 is not supported on that architecture, but weshould define the basic
246 // scheduling resources anyway.
247 defm : SBWriteResPair<WriteFShuffle256,SBPort0, 1>;
248 defm : SBWriteResPair<WriteShuffle256,SBPort0, 1>;
249 defm : SBWriteResPair<WriteVarVecShift,SBPort0, 1>;
250 } //SchedModel
在上面的定义中Sandy Bridge将各种读写操作绑定到资源。比如87行WriteRes定义将WriteRMW(代表读-修改-写)绑定到SBPort4,表示该操作时延为一个周期,占用SBPort4一个周期。在94行将WriteALULd(代表寄存器内存间简单的整形ALU操作)绑定到资源SBPort015,表示该操作时延为一个周期,占用SBPort0,SBPort1或SBPort5。
现在需要有一个方法将指令的操作数与表示其相关操作的SchedReadWrite定义关联起来。这就是下面的Sched类(注意Instruction定义中有相同的SchedRW域,如果指令从Sched派生,TableGen在派生类中只会保留Sched中的SchedRW,即两者合二为一)。
203 classSched<list<SchedReadWrite>schedrw> {
204 list<SchedReadWrite> SchedRW = schedrw;
205 }
在Sched定义里(包括Instruction的SchedRW),每个显式写入的操作数必须依次列出一个SchedWrite类型。对隐含写入的操作数列出额外的SchedWrite类型则是可选的。对读操作数列出SchedRead类型也是可选的。写相对于读的次序是无关重要的。这样,相同的SchedReadWrite序列可用于一个操作的多个形式。例如,一条双地址指令可以具有两个绑定的操作数或同时读写一个寄存器的单个操作数。在这两种情形下,有任意次序的单个SchedWrite与单个SchedRead。
以AVX类型的指令VCVTSS2SDrm为例(X86InstrSSE.td):
1891 let mayLoad = 1in
1892 def VCVTSS2SDrm :I<0x5A,MRMSrcMem, (outs FR64:$dst),
1893 (insFR32:$src1, f32mem:$src2),
1894 "vcvtss2sd\t{$src2,$src1, $dst|$dst, $src1, $src2}",
1895 [],IIC_SSE_CVT_Scalar_RM>,
1896 XS, VEX_4V, VEX_LIG,Requires<[HasAVX, OptForSize]>,
1897 Sched<[WriteCvtF2FLd,ReadAfterLd]>;
1898 }
1897行的Sched定义将WriteCvtF2FLd绑定到输出操作数FR64:$dst上,WriteCvtF2FLd代表一个浮点到浮点的转换。而ReadAfterLd则绑定到两个输入操作数,ReadAfterLd用于标记那些从内存读入的操作数(实际上直接设置Instruction中的SchedRW效果是相同的,参考前面LOCK_ArithBinOp的例子,它就是直接设置Instruction的SchedRW)。
同样注意1895行的IIC_SSE_CVT_Scalar_RM,它是为Atom这样的处理器准备的。
2.4.2.2.4. 总结
根据上面的讨论与例子。我们可以得出这些结论(这些内容实际来自LLVM代码中的注释)。通过定义SchedMachineModel,子目标机器可以给出下面三组数据之一:
1. 粗粒度指令代价模型的基本属性。Target hooks允许子目标机器将操作码关联到这些属性。
2. 用于简单每操作码代价模型调度器的读/写资源。可以下述方式的任意组合实现它:
A. 通过修改指令定义使其继承自Sched,将每操作数的SchedReadWrite类型关联到指令。对于每个子目标机器,定义WriteRes与ReadAdvance将处理器资源与时延关联到每个SchedReadWrite类型。
B. 在每条指令定义里,命名一个InstrItinClass。对于每个子目标机器,定义ItinRW项将InstrItinClass映射到每操作数的SchedReadWrite类型。不像方法A,这些类型可能是子目标机器特定的,可以通过定义SchedWriteRes及SchedReadAdvance,直接关联到资源。
C. 在子目标机器里映射SchedReadWrite类型到特定的操作码。这覆盖了指令定义的任何SchedReadWrite类型或InstrItinClass。类似方法B,子目标机器通过定义SchedWriteRes及SchedReadAdvance,可以将SchedReadWrite类型直接关联到资源。
D. 在目标机器或子目标机器里,定义SchedWriteVariant或SchedReadVariant,将一个SchedReadWrite类型映射到另一个SchedReadWrite类型序列。这允许通过定制的C++代码动态地选择一条指令的机器模型。它还允许将一个机器无关的SchedReadWrite类型映射到一个机器相关类型序列。
3. 用于具体保留表的指令路线(Instruction itineraries)。可以通过提供Itineraries,外加将指令映射到InstrItinClass,实现一个每流水线步骤机器模型。
- LLVM学习笔记(8)
- llvm学习笔记(1)
- llvm学习笔记(2)
- llvm学习笔记(3)
- llvm学习笔记(4)
- llvm学习笔记(5)
- LLVM学习笔记(6)
- LLVM学习笔记(7)
- LLVM学习笔记(9)
- LLVM学习笔记(10)
- LLVM学习笔记(11)
- LLVM学习笔记(12)
- LLVM学习笔记(13)
- LLVM学习笔记(14)
- LLVM学习笔记(15)
- LLVM学习笔记(20)
- LLVM学习笔记(16)
- LLVM学习笔记(17)
- 初窥Linux 之 数据流重定向
- Intellij IDEA打开就闪退或关闭
- 动态规划---最长公共子序列
- c Get方式请求网络接口
- 机器学习----贝叶斯分类器(贝叶斯决策论和极大似然估计)
- LLVM学习笔记(8)
- 关于出现php -m和phpinfo不一致的问题
- 奇技淫巧系列
- SQLServer提示评估期已过解决方案
- idea_life_2
- 从操作数据库谈到Template模式
- Android开发中多点触摸的实现方法
- 仿美团loading加载中的动画
- ucenter的实现原理简单讲解