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,实现一个每流水线步骤机器模型。


0 0
原创粉丝点击