内容简介:前面,我们已经各种相关的定义通过CodeGenSchedModels中的各式容器关联起来。现在我们将根据这些定义推导出所有的调度类型。在840行遍历所有的调度类型。注意,这个遍历的终止条件SchedClasses.size()是动态的,包括了新添加的类型,因为在下面的处理里,会向该容器添加新的调度类型。
3.5.1.6. 推导调度类型
前面,我们已经各种相关的定义通过CodeGenSchedModels中的各式容器关联起来。现在我们将根据这些定义推导出所有的调度类型。在840行遍历所有的调度类型。注意,这个遍历的终止条件SchedClasses.size()是动态的,包括了新添加的类型,因为在下面的处理里,会向该容器添加新的调度类型。
836 void CodeGenSchedModels::inferSchedClasses (){
837 DEBUG(dbgs() << NumInstrSchedClasses<< " instr sched classes.\n");
838
839 // Visit allexisting classes and newly created classes.
840 for (unsignedIdx = 0; Idx != SchedClasses.size(); ++Idx) {
841 assert (SchedClasses[Idx].Index== Idx && "bad SCIdx");
842
843 if (SchedClasses[Idx].ItinClassDef)
844 inferFromItinClass (SchedClasses[Idx].ItinClassDef,Idx);
845 if (!SchedClasses[Idx].InstRWs.empty())
846 inferFromInstRWs (Idx);
847 if (!SchedClasses[Idx].Writes.empty()) {
848 inferFromRW (SchedClasses[Idx].Writes,SchedClasses[Idx].Reads,
849 Idx, SchedClasses[Idx].ProcIndices);
850 }
851 assert (SchedClasses.size()< (NumInstrSchedClasses*6) &&
852 "too many SchedVariants");
853 }
854 }
3.5.1.6.1. 从ItinRW定义推导
首先处理将一组InstrItinClass(执行步骤)映射到一组SchedReadWrite(资源使用)的ItinRW定义(843行)。为了方便起见,我们将涉及的定义列出如下:
402 class ItinRW<list<SchedReadWrite> rw, list<InstrItinClass> iic> {
403 list<InstrItinClass> MatchedItinClasses= iic;
404 list<SchedReadWrite> OperandReadWrites= rw;
405 SchedMachineModel SchedModel = ?;
406 }
其中OperandReadWrites的成员可以是SchedWriteVariant与的SchedReadVariant定义,两者的基类都是SchedVariant。SchedVariant包含了一组Variants,每个Variant(SchedVar)都有一个谓词,在谓词条件满足时,Selected援引的SchedReadWrite对象适用。
361 class SchedVariant<list<SchedVar> variants> {
362 list<SchedVar> Variants = variants;
363 bit Variadic = 0;
364 SchedMachineModel SchedModel = ?;
365 }
355 class SchedVar<SchedPredicate pred,list<SchedReadWrite> selected> {
356 SchedPredicate Predicate = pred;
357 list<SchedReadWrite> Selected =selected;
358 }
374 class SchedWriteVariant<list<SchedVar> variants>: SchedWrite,
375 SchedVariant<variants> {
376 }
383 class SchedReadVariant<list<SchedVar>variants> : SchedRead,
384 SchedVariant<variants> {
385 }
另外,SchedReadWrite定义(match)可能被SchedAlias修改为另一个(alias)。
415 class SchedAlias<SchedReadWrite match, SchedReadWrite alias> {
416 SchedReadWrite MatchRW = match;
417 SchedReadWrite AliasRW = alias;
418 SchedMachineModel SchedModel = ?;
419 }
OperandReadWrites的成员还可以是WriteSequence:
224 class WriteSequence<list<SchedWrite> writes, int rep = 1> : SchedWrite {
225 list<SchedWrite> Writes = writes;
226 int Repeat = rep;
227 SchedMachineModel SchedModel = ?;
228 }
更为复杂的是,上述定义中的SchedWrite及SchedReadWrite仍然可以是SchedWriteVariant,SchedReadVariant,WriteSequence,还有相关SchedAlias(当然,很复杂定义出现的可能性很小)。举一个ARM的例子,下面是ARMCortexA9处理器指令调度定义的一个片段(ARMScheduleA9.td):
1912 let SchedModel =CortexA9Model in {
…
1926 def A9WriteIssue : SchedWriteRes<[]> { let Latency = 0; }
…
2037 def A9WriteLMAdr : SchedWriteVariant<[
2038 SchedVar<A9LMAdr1Pred, [A9WriteAdr1]>,
2039 SchedVar<A9LMAdr2Pred, [A9WriteAdr2]>,
2040 SchedVar<A9LMAdr3Pred, [A9WriteAdr3]>,
2041 SchedVar<A9LMAdr4Pred, [A9WriteAdr4]>,
2042 SchedVar<A9LMAdr5Pred, [A9WriteAdr5]>,
2043 SchedVar<A9LMAdr6Pred, [A9WriteAdr6]>,
2044 SchedVar<A9LMAdr7Pred, [A9WriteAdr7]>,
2045 SchedVar<A9LMAdr8Pred, [A9WriteAdr8]>,
2046 // For unknownLDM/VLDM/VSTM, assume 2 32-bit registers.
2047 SchedVar<A9LMUnknownPred,[A9WriteAdr2]>]>;
…
2086 def A9WriteLM : SchedWriteVariant<[
2087 SchedVar<A9LMAdr1Pred,A9WriteLMOpsList.Writes[0-1]>,
2088 SchedVar<A9LMAdr2Pred,A9WriteLMOpsList.Writes[0-3]>,
2089 SchedVar<A9LMAdr3Pred, A9WriteLMOpsList.Writes[0-5]>,
2090 SchedVar<A9LMAdr4Pred,A9WriteLMOpsList.Writes[0-7]>,
2091 SchedVar<A9LMAdr5Pred,A9WriteLMOpsList.Writes[0-9]>,
2092 SchedVar<A9LMAdr6Pred,A9WriteLMOpsList.Writes[0-11]>,
2093 SchedVar<A9LMAdr7Pred,A9WriteLMOpsList.Writes[0-13]>,
2094 SchedVar<A9LMAdr8Pred,A9WriteLMOpsList.Writes[0-15]>,
2095 // For unknownLDMs, define the maximum number of writes, but only
2096 // make the firsttwo consume resources.
2097 SchedVar<A9LMUnknownPred, [A9WriteL1,A9WriteL1Hi,
2098 A9WriteL2,A9WriteL2Hi,
2099 A9WriteL3Hi,A9WriteL3Hi,
2100 A9WriteL4Hi,A9WriteL4Hi,
2101 A9WriteL5Hi,A9WriteL5Hi,
2102 A9WriteL6Hi, A9WriteL6Hi,
2103 A9WriteL7Hi,A9WriteL7Hi,
2104 A9WriteL8Hi,A9WriteL8Hi]>]> {
2105 let Variadic= 1;
2106 }
…
2339 def :ItinRW<[A9WriteLM, A9WriteLMAdr,A9WriteIssue], [IIC_iLoad_m, IIC_iPop]>;
…
2529 }
我们将使用这个例子来帮助学习下面的处理代码。这个例子将原来,比如使用IIC_iLoad_m来描述调度特征的,指令定义LDM的调度特征,细分为9类。每一类由A9WriteLM,A9WriteLMAdr及A9WriteIssue中由指定的谓词选择的一个SchedReadWrite定义组成。
首先,在CodeGenSchedModels::inferSchedClasses的844行,如果调度类型的ItinClassDef不为空(来自指令定义的Itinerary),需要检查这个Itinerary定义是否对某些处理器改写了。
857 void CodeGenSchedModels::inferFromItinClass (Record*ItinClassDef,
858 unsigned FromClassIdx) {
859 for (unsignedPIdx = 0, PEnd = ProcModels.size(); PIdx != PEnd; ++PIdx) {
860 const CodeGenProcModel &PM = ProcModels[PIdx];
861 // For allItinRW entries.
862 bool HasMatch = false;
863 for (RecIter II = PM.ItinRWDefs.begin(), IE = PM.ItinRWDefs.end();
864 II != IE; ++II) {
865 RecVec Matched =(*II)->getValueAsListOfDefs("MatchedItinClasses");
866 if (!std::count(Matched.begin(),Matched.end(), ItinClassDef))
867 continue ;
868 if (HasMatch)
869 PrintFatalError((*II)->getLoc(),"Duplicate itinerary class "
870 +ItinClassDef->getName()
871 + " in ItinResourcesfor " + PM.ModelName);
872 HasMatch = true;
873 IdxVec Writes, Reads;
874 findRWs ((*II)->getValueAsListOfDefs("OperandReadWrites"),Writes, Reads);
875 IdxVec ProcIndices(1, PIdx);
876 inferFromRW (Writes,Reads, FromClassIdx, ProcIndices);
877 }
878 }
879 }
ItinRW的定义都保存在它们所援引处理器CodeGenSchedModels对象的ItinRWDefs容器里。因此,859行循环遍历所有的CodeGenSchedModels实例,找出与ItinClassDef相关的对象,记录其序号。对同一个处理器,不能存在映射同一个InstrItinClass的多个ItinRW定义,因为这样带来了二义性。在874行获取该ItinRW匹配的那组SchedReadWrite,然后据此推导新调度类型。
对上面的例子,在874行得到Writes的内容是:[A9WriteLM,A9WriteLMAdr, A9WriteIssue]。
1325 void CodeGenSchedModels::inferFromRW ( const IdxVec &OperWrites,
1326 const IdxVec &OperReads,
1327 unsignedFromClassIdx,
1328 const IdxVec &ProcIndices) {
1329 DEBUG(dbgs() << "INFER RWproc("; dumpIdxVec(ProcIndices); dbgs() << ") ");
1330
1331 // Create a seedtransition with an empty PredTerm and the expanded sequences
1332 // of SchedWritesfor the current SchedClass.
1333 std::vector<PredTransition>LastTransitions;
1334 LastTransitions.resize(1);
1335 LastTransitions.back().ProcIndices.append(ProcIndices.begin(),
1336 ProcIndices.end());
1337
1338 for (IdxIterI = OperWrites.begin(), E = OperWrites.end(); I != E; ++I) {
1339 IdxVec WriteSeq;
1340 expandRWSequence (*I, WriteSeq, /*IsRead=*/ false);
1341 unsigned Idx =LastTransitions[0].WriteSequences.size();
1342 LastTransitions[0].WriteSequences.resize(Idx + 1);
1343 SmallVectorImpl<unsigned> &Seq =LastTransitions[0].WriteSequences[Idx];
1344 for (IdxIter WI = WriteSeq.begin(), WE = WriteSeq.end(); WI != WE; ++WI)
1345 Seq.push_back(*WI);
1346 DEBUG(dbgs() << "(";dumpIdxVec(Seq); dbgs() << ") ");
1347 }
1348 DEBUG(dbgs() << " Reads: ");
1349 for (IdxIterI = OperReads.begin(), E = OperReads.end(); I != E; ++I) {
1350 IdxVec ReadSeq;
1351 expandRWSequence (*I, ReadSeq, /*IsRead=*/ true);
1352 unsigned Idx = LastTransitions[0].ReadSequences.size();
1353 LastTransitions[0].ReadSequences.resize(Idx+ 1);
1354 SmallVectorImpl<unsigned> &Seq =LastTransitions[0].ReadSequences[Idx];
1355 for (IdxIter RI = ReadSeq.begin(), RE = ReadSeq.end(); RI != RE; ++RI)
1356 Seq.push_back(*RI);
1357 DEBUG(dbgs() << "(";dumpIdxVec(Seq); dbgs() << ") ");
1358 }
1359 DEBUG(dbgs() << '\n');
1360
1361 // Collect allPredTransitions for individual operands.
1362 // Iterate untilno variant writes remain.
1363 while ( hasVariant (LastTransitions, * this )){
1364 PredTransitions Transitions(* this );
1365 for (std::vector<PredTransition>::const_iterator
1366 I = LastTransitions.begin(), E =LastTransitions.end();
1367 I != E; ++I) {
1368 Transitions. substituteVariants (*I);
1369 }
1370 DEBUG(Transitions.dump());
1371 LastTransitions.swap(Transitions.TransVec);
1372 }
1373 // If the first transitionhas no variants, nothing to do.
1374 if (LastTransitions[0].PredTerm.empty())
1375 return ;
1376
1377 // WARNING: Weare about to mutate the SchedClasses vector. Do not refer to
1378 // OperWrites,OperReads, or ProcIndices after calling inferFromTransitions.
1379 inferFromTransitions (LastTransitions,FromClassIdx, * this );
1380 }
上面的代码首先在1333行准备一个临时容器LastTransitions,它一开始只有一个项。随着其中SchedVariant定义的处理,它将扩大为其中不冲突项的数目。元素类型PredTransition的定义如下。
927 struct PredTransition {
928 // A predicateterm is a conjunction of PredChecks.
929 SmallVector<PredCheck, 4> PredTerm;
930 SmallVector<SmallVector<unsigned,4>,16> WriteSequences;
931 SmallVector<SmallVector<unsigned,4>,16> ReadSequences;
932 SmallVector<unsigned, 4> ProcIndices;
933 };
因为SchedVariant定义包含SchedVar列表,而SchedVar又包含SchedReadWrite列表,为了辅助SchedVariant的展开,需要如930及931行定义的WriteSequences与ReadSequences容器。另外,容器PredTerm将用于保存SchedVariant中SchedVar的谓词定义,其中的PredCheck类型如下。
918 struct PredCheck {
919 bool IsRead;
920 unsigned RWIdx;
921 Record *Predicate;
922
922 PredCheck(bool r, unsigned w, Record *p):IsRead(r), RWIdx(w), Predicate(p) {}
923 };
920行的RWIdx用于关联该谓词所选择的SchedReadWrite定义。
在inferFromRW的1340与1351行,对ItinRW的OperandReadWrites中的SchedWrite与SchedRead定义调用expandRWSequence方法展开可能存在的WriteSequence定义。不是从WriteSequence派生的定义,在406行就返回了。至于WriteSequence的派生定义,其Repeat成员指出Writes成员的重复次数,因此410行的循环将该Writes成员展开这个次数。
401 void CodeGenSchedModels::expandRWSequence (unsignedRWIdx, IdxVec &RWSeq,
402 boolIsRead) const {
403 const CodeGenSchedRW &SchedRW = getSchedRW(RWIdx, IsRead);
404 if (!SchedRW.IsSequence) {
405 RWSeq.push_back(RWIdx);
406 return ;
407 }
408 int Repeat =
409 SchedRW.TheDef ?SchedRW.TheDef->getValueAsInt("Repeat") : 1;
410 for (int i =0; i < Repeat; ++i) {
411 for (IdxIter I = SchedRW.Sequence.begin(), E = SchedRW.Sequence.end();
412 I != E; ++I) {
413 expandRWSequence(*I, RWSeq, IsRead);
414 }
415 }
416 }
从expandRWSequence方法返回到inferFromRW,注意1343与1354行,这些展开的结果只在当前PredTransition对象(当前上下文是LastTransitions[0])的WriteSequences及ReadSequences里占据一项。接着在1363行循环处理LastTransitions,直到它不再包含SchedWriteVariant或SchedReadVariant。
其中使用的hasVariant方法定义如下。它遍历LastTransitions,在上面展开的结果中,查找是否存在SchedWriteVariant或SchedReadVariant。
1016 static bool hasVariant (ArrayRef<PredTransition> Transitions,
1017 CodeGenSchedModels&SchedModels) {
1018 for (ArrayRef<PredTransition>::iterator
1019 PTI = Transitions.begin(), PTE =Transitions.end();
1020 PTI != PTE; ++PTI) {
1021 for (SmallVectorImpl<SmallVector<unsigned,4> >::const_iterator
1022 WSI =PTI->WriteSequences.begin(), WSE = PTI->WriteSequences.end();
1023 WSI != WSE; ++WSI) {
1024 for (SmallVectorImpl<unsigned>::const_iterator
1025 WI = WSI->begin(), WE =WSI->end(); WI != WE; ++WI) {
1026 if ( hasAliasedVariants (SchedModels.getSchedWrite(*WI),SchedModels))
1027 return true;
1028 }
1029 }
1030 for (SmallVectorImpl<SmallVector<unsigned,4> >::const_iterator
1031 RSI = PTI->ReadSequences.begin(),RSE = PTI->ReadSequences.end();
1032 RSI != RSE; ++RSI) {
1033 for (SmallVectorImpl<unsigned>::const_iterator
1034 RI = RSI->begin(), RE =RSI->end(); RI != RE; ++RI) {
1035 if ( hasAliasedVariants (SchedModels.getSchedRead(*RI),SchedModels))
1036 return true;
1037 }
1038 }
1039 }
1040 return false;
1041 }
尽管方法hasAliasedVariants的名字中有aliased这个字眼,实际上在函数开头的993行却是判断传入的SchedReadWrite是否为SchedWriteVariant或SchedReadVariant。如果不是,继续检查它是否具有别名,如果该别名是一个SchedWriteVariant或SchedReadVariant定义,这个SchedWriteVariant或SchedReadVariant定义就是需要处理的对象。
991 static bool hasAliasedVariants ( const CodeGenSchedRW &RW,
992 CodeGenSchedModels &SchedModels) {
993 if (RW.HasVariants)
994 return true;
995
996 for (RecIterI = RW.Aliases.begin(), E = RW.Aliases.end(); I != E; ++I) {
997 const CodeGenSchedRW &AliasRW =
998 SchedModels.getSchedRW((*I)->getValueAsDef("AliasRW"));
999 if (AliasRW.HasVariants)
1000 return true;
1001 if (AliasRW.IsSequence) {
1002 IdxVec ExpandedRWs;
1003 SchedModels.expandRWSequence(AliasRW.Index, ExpandedRWs, AliasRW.IsRead);
1004 for (IdxIter SI = ExpandedRWs.begin(), SE = ExpandedRWs.end();
1005 SI != SE; ++SI) {
1006 if(hasAliasedVariants(SchedModels.getSchedRW(*SI, AliasRW.IsRead),
1007 SchedModels)) {
1008 return true;
1009 }
1010 }
1011 }
1012 }
1013 return false;
1014 }
如果存在需要处理的SchedWriteVariant或SchedReadVariant定义,在inferFromRW的1364行首先生成一个PredTransitions实例,它封装了处理所需的方法。
937 class PredTransitions {
938 CodeGenSchedModels &SchedModels;
939
940 public :
941 std::vector< PredTransition >TransVec;
942
943 PredTransitions(CodeGenSchedModels &sm):SchedModels(sm) {}
944
945 void substituteVariantOperand( const SmallVectorImpl<unsigned> &RWSeq,
946 bool IsRead,unsigned StartIdx);
947
948 void substituteVariants( const PredTransition &Trans);
949
950 #ifndef NDEBUG
951 void dump() const ;
952 #endif
953
954 private :
955 bool mutuallyExclusive(Record *PredDef,ArrayRef<PredCheck> Term);
956 void getIntersectingVariants(
957 const CodeGenSchedRW &SchedRW, unsigned TransIdx,
958 std::vector<TransVariant>&IntersectingVariants);
959 void pushVariant( const TransVariant &VInfo, bool IsRead);
960 };
其中941行的容器TransVec与容器LastTransitions类型相同。
如果存在从SchedVariant派生的定义,在1368行对LastTransitions容器的每个项调用下面的方法,将其中SchedVariant定义包含的SchedVar列表,根据它们的Predicate,对Selected成员进行筛选。
1247 void PredTransitions::substituteVariants ( const PredTransition &Trans) {
1248 // Build up a setof partial results starting at the back of
1249 //PredTransitions. Remember the first new transition.
1250 unsigned StartIdx = TransVec.size();
1251 TransVec.resize(TransVec.size() + 1);
1252 TransVec.back().PredTerm = Trans.PredTerm;
1253 TransVec.back().ProcIndices =Trans.ProcIndices;
1254
1255 // Visit eachoriginal write sequence.
1256 for (SmallVectorImpl<SmallVector<unsigned,4> >::const_iterator
1257 WSI = Trans.WriteSequences.begin(),WSE = Trans.WriteSequences.end();
1258 WSI != WSE; ++WSI) {
1259 // Push a new(empty) write sequence onto all partial Transitions.
1260 for (std::vector<PredTransition>::iterator I =
1261 TransVec.begin() + StartIdx, E =TransVec.end(); I != E; ++I) {
1262 I->WriteSequences.resize(I->WriteSequences.size() + 1);
1263 }
1264 substituteVariantOperand (*WSI, /*IsRead=*/ false, StartIdx);
1265 }
1266 // Visit eachoriginal read sequence.
1267 for (SmallVectorImpl<SmallVector<unsigned,4> >::const_iterator
1268 RSI = Trans.ReadSequences.begin(), RSE= Trans.ReadSequences.end();
1269 RSI != RSE; ++RSI) {
1270 // Push a new(empty) read sequence onto all partial Transitions.
1271 for (std::vector<PredTransition>::iterator I =
1272 TransVec.begin() + StartIdx, E =TransVec.end(); I != E; ++I) {
1273 I->ReadSequences.resize(I->ReadSequences.size()+ 1);
1274 }
1275 substituteVariantOperand (*RSI, /*IsRead=*/ true, StartIdx);
1276 }
1277 }
首先在PredTransitions的TransVec容器里开辟一个项用于当前的SchedVariant的处理。在1256与1267行循环,依次处理前面展开的结果。以前面的例子来说,WSI将依次援引A9WriteLM,A9WriteLMAdr,A9WriteIssue展开的结果。1260与1271行循环是必要的,因为在1264与1275行执行的substituteVariantOperand方法会扩大TransVec容器来保存SchedVariant中不冲突的展开结果。
因此,substituteVariantOperand方法执行将SchedVariant展开的工作。参数RWSeq就是ItinRW的OperandReadWrites中某个SchedReadWrite展开的结果,对上面的例子,它是A9WriteLM,A9WriteLMAdr,A9WriteIssue其中之一。
对于其中既不是SchedWriteVariant或SchedReadVariant,别名也不是SchedWriteVariant或SchedReadVariant的定义,直接记录在当前正在处理的展开的ReadSequences或WriteSequences容器里,无需特别处理(1216行循环)。
1206 void PredTransitions::substituteVariantOperand (
1207 const SmallVectorImpl<unsigned> &RWSeq, bool IsRead, unsigned StartIdx) {
1208
1209 // Visit eachoriginal RW within the current sequence.
1210 for (SmallVectorImpl<unsigned>::const_iterator
1211 RWI = RWSeq.begin(), RWE =RWSeq.end(); RWI != RWE; ++RWI) {
1212 const CodeGenSchedRW &SchedRW = SchedModels.getSchedRW(*RWI, IsRead);
1213 // Push this RWon all partial PredTransitions or distribute variants.
1214 // NewPredTransitions may be pushed within this loop which should not be
1215 // revisited(TransEnd must be loop invariant).
1216 for (unsigned TransIdx = StartIdx, TransEnd = TransVec.size();
1217 TransIdx != TransEnd; ++TransIdx) {
1218 // In thecommon case, push RW onto the current operand's sequence.
1219 if (! hasAliasedVariants (SchedRW,SchedModels)) {
1220 if (IsRead)
1221 TransVec[TransIdx].ReadSequences.back().push_back(*RWI);
1222 else
1223 TransVec[TransIdx].WriteSequences.back().push_back(*RWI);
1224 continue ;
1225 }
1226 // Distributethis partial PredTransition across intersecting variants.
1227 // This willpush a copies of TransVec[TransIdx] on the back of TransVec.
1228 std::vector<TransVariant>IntersectingVariants;
1229 getIntersectingVariants (SchedRW,TransIdx, IntersectingVariants);
1230 // Now expandeach variant on top of its copy of the transition.
1231 for (std::vector<TransVariant>::const_iterator
1232 IVI =IntersectingVariants.begin(),
1233 IVE = IntersectingVariants.end();
1234 IVI != IVE; ++IVI) {
1235 pushVariant (*IVI,IsRead);
1236 }
1237 }
1238 }
1239 }
至于SchedWriteVariant或SchedReadVariant或者别名为SchedWriteVariant或SchedReadVariant的定义,在1228行首先准备一个std::vector<TransVariant>类型的容器。它用于保存Variant与展开项的关联结果,其中TransVariant的定义如下:
906 struct TransVariant {
907 Record *VarOrSeqDef; // Variant orsequence.
908 unsigned RWIdx; // Index ofthis variant or sequence's matched type.
909 unsigned ProcIdx; // Processormodel index or zero for any.
910 unsigned TransVecIdx; // Index into PredTransitions::TransVec.
911
912 TransVariant (Record*def, unsigned rwi, unsigned pi, unsigned ti):
913 VarOrSeqDef(def), RWIdx(rwi), ProcIdx(pi),TransVecIdx(ti) {}
914 };
其中907行的VarOrSeqDef是SchedVariant中Variant的某一项,910行的TransVecIdx是该SchedVar或WriteSequence(来自SchedAlias)在TransVec容器里的索引。
然后调用getIntersectingVariants方法,注意这个调用在1216行开始循环内部,因此对某一个SchedRW,对每个正在处理的展开,都调用这个方法一遍。
1053~1090行,将SchedVariant中Variant的每一项,或SchedAlias中AliasRW的每一项记录到Variants里。然后在1091行循环遍历Variants容器,VarProcIdx与AliasProcIdx都是CodeGenProcModel实例的索引。这个域可以不设置的(即定义里那个问号),表示是一个通用设置。但如果设置了,我们必须使用处理器索引匹配的定义。
1046 void PredTransitions::getIntersectingVariants (
1047 const CodeGenSchedRW &SchedRW, unsigned TransIdx,
1048 std::vector< TransVariant >&IntersectingVariants) {
1049
1050 bool GenericRW = false;
1051
1052 std::vector<TransVariant> Variants;
1053 if (SchedRW.HasVariants) {
1054 unsigned VarProcIdx = 0;
1055 if(SchedRW.TheDef->getValueInit("SchedModel")->isComplete()) {
1056 Record *ModelDef =SchedRW.TheDef->getValueAsDef("SchedModel");
1057 VarProcIdx =SchedModels.getProcModel(ModelDef).Index;
1058 }
1059 // Push eachvariant. Assign TransVecIdx later.
1060 const RecVec VarDefs = SchedRW.TheDef->getValueAsListOfDefs("Variants");
1061 for (RecIter RI = VarDefs.begin(), RE = VarDefs.end(); RI != RE; ++RI)
1062 Variants.push_back( TransVariant (*RI, SchedRW.Index,VarProcIdx, 0));
1063 if (VarProcIdx == 0)
1064 GenericRW = true;
1065 }
1066 for (RecIterAI = SchedRW.Aliases.begin(), AE = SchedRW.Aliases.end();
1067 AI != AE; ++AI) {
1068 // If eitherthe SchedAlias itself or the SchedReadWrite that it aliases
1069 // to isdefined within a processor model, constrain all variants to
1070 // thatprocessor.
1071 unsigned AliasProcIdx = 0;
1072 if((*AI)->getValueInit("SchedModel")->isComplete()) {
1073 Record *ModelDef =(*AI)->getValueAsDef("SchedModel");
1074 AliasProcIdx =SchedModels.getProcModel(ModelDef).Index;
1075 }
1076 const CodeGenSchedRW &AliasRW =
1077 SchedModels.getSchedRW((*AI)->getValueAsDef("AliasRW"));
1078
1079 if (AliasRW.HasVariants) {
1080 const RecVec VarDefs = AliasRW.TheDef->getValueAsListOfDefs("Variants");
1081 for (RecIter RI = VarDefs.begin(), RE = VarDefs.end(); RI != RE; ++RI)
1082 Variants.push_back( TransVariant (*RI, AliasRW.Index,AliasProcIdx, 0));
1083 }
1084 if (AliasRW.IsSequence) {
1085 Variants.push_back(
1086 TransVariant(AliasRW.TheDef,SchedRW.Index, AliasProcIdx, 0));
1087 }
1088 if (AliasProcIdx == 0)
1089 GenericRW = true;
1090 }
1091 for (unsignedVIdx = 0, VEnd = Variants.size(); VIdx != VEnd; ++VIdx) {
1092 TransVariant &Variant = Variants[VIdx];
1093 // Don't expandvariants if the processor models don't intersect.
1094 // A zeroprocessor index means any processor.
1095 SmallVectorImpl<unsigned>&ProcIndices = TransVec[TransIdx].ProcIndices;
1096 if (ProcIndices[0] &&Variants[VIdx].ProcIdx) {
1097 unsigned Cnt =std::count(ProcIndices.begin(), ProcIndices.end(),
1098 Variant.ProcIdx);
1099 if (!Cnt)
1100 continue ;
1101 if (Cnt > 1) {
1102 const CodeGenProcModel &PM =
1103 *(SchedModels.procModelBegin() +Variant.ProcIdx);
1104 PrintFatalError(Variant.VarOrSeqDef->getLoc(),
1105 "Multiple variantsdefined for processor " +
1106 PM.ModelName +
1107 " Ensure only oneSchedAlias exists per RW.");
1108 }
1109 }
1110 if(Variant.VarOrSeqDef->isSubClassOf("SchedVar")) {
1111 Record *PredDef =Variant.VarOrSeqDef->getValueAsDef("Predicate");
1112 if ( mutuallyExclusive (PredDef,TransVec[TransIdx].PredTerm))
1113 continue ;
1114 }
1115 if (IntersectingVariants.empty()) {
1116 // The firstvariant builds on the existing transition.
1117 Variant.TransVecIdx = TransIdx;
1118 IntersectingVariants.push_back(Variant);
1119 }
1120 else {
1121 // Pushanother copy of the current transition for more variants.
1122 Variant.TransVecIdx = TransVec.size();
1123 IntersectingVariants.push_back(Variant);
1124 TransVec.push_back(TransVec[TransIdx]);
1125 }
1126 }
1127 if (GenericRW &&IntersectingVariants.empty()) {
1128 PrintFatalError(SchedRW.TheDef->getLoc(), "No variant of thistype has "
1129 "a matching predicateon any processor");
1130 }
1131 }
对Variant包含SchedVar定义,首先通过mutuallyExclusive方法检查其谓词与当前PredTransition对象记录的谓词是否兼容。
972 bool PredTransitions::mutuallyExclusive (Record*PredDef,
973 ArrayRef<PredCheck> Term) {
974
975 for (ArrayRef<PredCheck>::iterator I = Term.begin(), E = Term.end();
976 I != E; ++I) {
977 if (I->Predicate == PredDef)
978 return false;
979
980 const CodeGenSchedRW &SchedRW = SchedModels.getSchedRW(I->RWIdx,I->IsRead);
981 assert (SchedRW.HasVariants&& "PredCheck must refer to a SchedVariant");
982 RecVec Variants =SchedRW.TheDef->getValueAsListOfDefs("Variants");
983 for (RecIter VI = Variants.begin(), VE = Variants.end(); VI != VE; ++VI) {
984 if((*VI)->getValueAsDef("Predicate") == PredDef)
985 return true;
986 }
987 }
988 return false;
989 }
PredTransition对象的PredTerm容器一开始是空的,一旦处理了一个SchedVar定义,就会保存它的谓词定义以及包含这个SchedVar的SchedVariant的索引(pushVariant的1148行)。
上面代码所表达的逻辑是这样的:与一个给定SchedVariant对象相关的谓词都视为彼此互斥,即使这些谓词所表示的条件不是互斥的。这没问题,因为选择给定SchedWrite的谓词总是以它们在.td文件里定义是次序来检查。后面的条件隐含地否定了任何前面的条件。
如果不互斥,在IntersectingVariants中记录这个Variant对象,并扩大TransVec记录这些Variant。
以上面的例子来说,首先处理A9WriteLM,Variants的内容是:
TransIdx = 0
[0]: SchedVar<A9LMAdr1Pred,A9WriteLMOpsList.Writes[0-1]>
[1]: SchedVar<A9LMAdr2Pred,A9WriteLMOpsList.Writes[0-3]>
[2]: SchedVar<A9LMAdr3Pred,A9WriteLMOpsList.Writes[0-5]>
[3]: SchedVar<A9LMAdr4Pred, A9WriteLMOpsList.Writes[0-7]>
[4]: SchedVar<A9LMAdr5Pred,A9WriteLMOpsList.Writes[0-9]>
[5]: SchedVar<A9LMAdr6Pred,A9WriteLMOpsList.Writes[0-11]>
[6]: SchedVar<A9LMAdr7Pred,A9WriteLMOpsList.Writes[0-13]>
[7]: SchedVar<A9LMAdr8Pred, A9WriteLMOpsList.Writes[0-15]>
[8]: SchedVar<A9LMUnknownPred, [A9WriteL1,A9WriteL1Hi, A9WriteL2, A9WriteL2Hi,
A9WriteL3Hi,A9WriteL3Hi, A9WriteL4Hi, A9WriteL4Hi,
A9WriteL5Hi,A9WriteL5Hi, A9WriteL6Hi, A9WriteL6Hi,
A9WriteL7Hi,A9WriteL7Hi, A9WriteL8Hi, A9WriteL8Hi]>
IntersectingVariants中每一项包含Variants对应的一项。同时TransVec中也有9项。
回到substituteVariantOperand方法,现在容器IntersectingVariants记录了这个SchedVariant定义所对应的若干TransVariant实例。在1231行遍历这些实例,并对它们调用下面的pushVariant方法记录被选中的SchedReadWrite定义。
1135 void PredTransitions::
1136 pushVariant( const TransVariant &VInfo, bool IsRead) {
1137
1138 PredTransition &Trans =TransVec[VInfo.TransVecIdx];
1139
1140 // If thisoperand transition is reached through a processor-specific alias,
1141 // then the wholetransition is specific to this processor.
1142 if (VInfo.ProcIdx != 0)
1143 Trans.ProcIndices.assign(1, VInfo.ProcIdx);
1144
1145 IdxVec SelectedRWs;
1146 if(VInfo.VarOrSeqDef->isSubClassOf("SchedVar")) {
1147 Record *PredDef =VInfo.VarOrSeqDef->getValueAsDef("Predicate");
1148 Trans.PredTerm.push_back( PredCheck (IsRead, VInfo.RWIdx,PredDef));
1149 RecVec SelectedDefs =VInfo.VarOrSeqDef->getValueAsListOfDefs("Selected");
1150 SchedModels.findRWs(SelectedDefs,SelectedRWs, IsRead);
1151 }
1152 else {
1153 assert (VInfo.VarOrSeqDef->isSubClassOf("WriteSequence")&&
1154 "variant must be a SchedVariantor aliased WriteSequence");
1155 SelectedRWs.push_back(SchedModels.getSchedRWIdx(VInfo.VarOrSeqDef,IsRead));
1156 }
1157
1158 const CodeGenSchedRW &SchedRW = SchedModels.getSchedRW(VInfo.RWIdx, IsRead);
1159
1160 SmallVectorImpl<SmallVector<unsigned,4> > &RWSequences =IsRead
1161 ? Trans.ReadSequences :Trans.WriteSequences;
1162 if (SchedRW.IsVariadic) {
1163 unsigned OperIdx = RWSequences.size()-1;
1164 // Make N-1copies of this transition's last sequence.
1165 for (unsigned i = 1, e = SelectedRWs.size(); i != e; ++i) {
1166 // Create atemporary copy the vector could reallocate.
1167 RWSequences.reserve(RWSequences.size() +1);
1168 RWSequences.push_back(RWSequences[OperIdx]);
1169 }
1170 // Push each ofthe N elements of the SelectedRWs onto a copy of the last
1171 // sequence(split the current operand into N operands).
1172 // Note thatwrite sequences should be expanded within this loop--the entire
1173 // sequencebelongs to a single operand.
1174 for (IdxIter RWI = SelectedRWs.begin(), RWE = SelectedRWs.end();
1175 RWI != RWE; ++RWI, ++OperIdx) {
1176 IdxVec ExpandedRWs;
1177 if (IsRead)
1178 ExpandedRWs.push_back(*RWI);
1179 else
1180 SchedModels. expandRWSequence (*RWI,ExpandedRWs, IsRead);
1181 RWSequences[OperIdx].insert(RWSequences[OperIdx].end(),
1182 ExpandedRWs.begin(),ExpandedRWs.end());
1183 }
1184 assert (OperIdx== RWSequences.size() && "missed a sequence");
1185 }
1186 else {
1187 // Push thistransition's expanded sequence onto this transition's last
1188 // sequence(add to the current operand's sequence).
1189 SmallVectorImpl<unsigned> &Seq =RWSequences.back();
1190 IdxVec ExpandedRWs;
1191 for (IdxIter RWI = SelectedRWs.begin(), RWE = SelectedRWs.end();
1192 RWI != RWE; ++RWI) {
1193 if (IsRead)
1194 ExpandedRWs.push_back(*RWI);
1195 else
1196 SchedModels.expandRWSequence(*RWI,ExpandedRWs, IsRead);
1197 }
1198 Seq.insert(Seq.end(), ExpandedRWs.begin(),ExpandedRWs.end());
1199 }
1200 }
TransVariant实例要么来自SchedVariant的Variants(list<SchedVar>),要么来自SchedAlias的AliasRW(WriteSequence或SchedVariant),这样就不难理解1153行的断言了。对于由SchedVar生成的TransVariant对象,它的谓词记录在相关的PredTransition实例(这就是getIntersectingVariants1112行TransVec[TransIdx].PredTerm的来源)。
容器SelectedRWs记录相关SchedReadWrite定义的索引(SchedVar是Selected,WriteSequence则是它本身)。在1158行从VInfo.RWIdx得到包含这个SchedVar或WriteSequence的SchedVariant定义的CodeGenSchedRW实例。
如果这个SchedVariant定义不包含可变参数(不满足1162行条件),1191行遍历选中SchedVar定义中的Selected或SchedAlias定义中的AliasRW,展开可能出现的WriteSequence,在TransVec[VInfo.TransVecIdx]的ReadSequences或WriteSequences容器里记录这些CodeGenSchedRW对象序号。这些CodeGenSchedRW对象全部保存在容器的最后一个单元,这是与可变参数时最大的区别。
如果这个SchedVariant定义包含可变参数,首先在TransVec[VInfo.TransVecIdx]的ReadSequences或WriteSequences容器里拷贝最后一个单元,使得未用单元与SelectedRWs的成员可以一一对应。然后,将SelectedRWs中的成员(SchedReadWrite)分别一一对应地记录在这些未用单元里。
这样对于我们的例子,TransVec的内容是这样的:
TransVec[0].WriteSequences[0]:A9WriteLMOpsList.Writes[0-1], Pred: A9LMAdr1Pred
TransVec[1].WriteSequences[0]:A9WriteLMOpsList.Writes[0-3], Pred: A9LMAdr2Pred
TransVec[2].WriteSequences[0]:A9WriteLMOpsList.Writes[0-5], Pred: A9LMAdr3Pred
TransVec[3].WriteSequences[0]:A9WriteLMOpsList.Writes[0-7], Pred: A9LMAdr4Pred
TransVec[4].WriteSequences[0]:A9WriteLMOpsList.Writes[0-9], Pred: A9LMAdr5Pred
TransVec[5].WriteSequences[0]:A9WriteLMOpsList.Writes[0-11], Pred: A9LMAdr6Pred
TransVec[6].WriteSequences[0]:A9WriteLMOpsList.Writes[0-13], Pred: A9LMAdr7Pred
TransVec[7].WriteSequences[0]:A9WriteLMOpsList.Writes[0-15], Pred: A9LMAdr8Pred
TransVec[8]: Pred: A9LMUnknownPred
WriteSequences[0]: A9WriteL1
WriteSequences[1]: A9WriteL1Hi
WriteSequences[2]: A9WriteL2
WriteSequences[3]: A9WriteL2Hi
WriteSequences[4]: A9WriteL3Hi
WriteSequences[5]: A9WriteL3Hi
WriteSequences[6]: A9WriteL4Hi
WriteSequences[7]: A9WriteL4Hi
WriteSequences[8]: A9WriteL5Hi
WriteSequences[9]: A9WriteL5Hi
WriteSequences[10]: A9WriteL6Hi
WriteSequences[11]: A9WriteL6Hi
WriteSequences[12]: A9WriteL7Hi
WriteSequences[13]: A9WriteL7Hi
WriteSequences[14]: A9WriteL8Hi
WriteSequences[15]: A9WriteL8Hi
从substituteVariantOperand返回,再次进入substituteVariants在1256行的循环,处理ItinRW的OperandReadWrites中下一个SchedReadWrite。以上面的例子来说,是A9WriteLMAdr。在1262行,上面所有TransVec的WriteSequences容器都扩大一项。处理后,得到(可以对照mutuallyExclusive代码看一下):
TransVec[0] A9LMAdr1Pred x 2 :
WriteSequences[0]: A9WriteLMOpsList.Writes[0-1]
WriteSequences[1]: A9WriteAdr1
TransVec[1] A9LMAdr2Pred x 2 :
WriteSequences[0]: A9WriteLMOpsList.Writes[0-3]
WriteSequences[1]: A9WriteAdr2
TransVec[2] A9LMAdr3Pred x 2 :
WriteSequences[0]: A9WriteLMOpsList.Writes[0-5]
WriteSequences[1]: A9WriteAdr3
TransVec[3] A9LMAdr4Pred x 2 :
WriteSequences[0]: A9WriteLMOpsList.Writes[0-7]
WriteSequences[1]: A9WriteAdr4
TransVec[4] A9LMAdr5Pred x 2 :
WriteSequences[0]: A9WriteLMOpsList.Writes[0-9]
WriteSequences[1]: A9WriteAdr5
TransVec[5] A9LMAdr6Pred x 2 :
WriteSequences[0]:A9WriteLMOpsList.Writes[0-11]
WriteSequences[1]:A9WriteAdr6
TransVec[6] A9LMAdr7Pred x 2 :
WriteSequences[0]: A9WriteLMOpsList.Writes[0-13]
WriteSequences[1]: A9WriteAdr7
TransVec[7] A9LMAdr8Pred x 2 :
WriteSequences[0]: A9WriteLMOpsList.Writes[0-15]
WriteSequences[1]: A9WriteAdr8
TransVec[8] A9LMUnknownPred x2 :
WriteSequences[0]: A9WriteL1
WriteSequences[1]: A9WriteL1Hi
WriteSequences[2]: A9WriteL2
WriteSequences[3]: A9WriteL2Hi
WriteSequences[4]: A9WriteL3Hi
WriteSequences[5]: A9WriteL3Hi
WriteSequences[6]: A9WriteL4Hi
WriteSequences[7]: A9WriteL4Hi
WriteSequences[8]: A9WriteL5Hi
WriteSequences[9]: A9WriteL5Hi
WriteSequences[10]: A9WriteL6Hi
WriteSequences[11]: A9WriteL6Hi
WriteSequences[12]: A9WriteL7Hi
WriteSequences[13]: A9WriteL7Hi
WriteSequences[14]: A9WriteL8Hi
WriteSequences[15]: A9WriteL8Hi
WriteSequences[16]: A9WriteAdr2
同样,在处理了最后的A9WriteIssue之后,TransVec变成:
TransVec[0] A9LMAdr1Pred x 3 :
WriteSequences[0]: A9WriteLMOpsList.Writes[0-1]
WriteSequences[1]: A9WriteAdr1
WriteSequences[2]: A9WriteIssue
TransVec[1] A9LMAdr2Pred x 3 :
WriteSequences[0]: A9WriteLMOpsList.Writes[0-3]
WriteSequences[1]: A9WriteAdr2
WriteSequences[2]: A9WriteIssue
TransVec[2] A9LMAdr3Pred x 3 :
WriteSequences[0]: A9WriteLMOpsList.Writes[0-5]
WriteSequences[1]: A9WriteAdr3
WriteSequences[2]: A9WriteIssue
TransVec[3] A9LMAdr4Pred x 3 :
WriteSequences[0]: A9WriteLMOpsList.Writes[0-7]
WriteSequences[1]: A9WriteAdr4
WriteSequences[2]: A9WriteIssue
TransVec[4] A9LMAdr5Pred x 3 :
WriteSequences[0]: A9WriteLMOpsList.Writes[0-9]
WriteSequences[1]: A9WriteAdr5
WriteSequences[2]: A9WriteIssue
TransVec[5] A9LMAdr6Pred x 3 :
WriteSequences[0]:A9WriteLMOpsList.Writes[0-11]
WriteSequences[1]: A9WriteAdr6
WriteSequences[2]:A9WriteIssue
TransVec[6] A9LMAdr7Pred x 3 :
WriteSequences[0]: A9WriteLMOpsList.Writes[0-13]
WriteSequences[1]: A9WriteAdr7
WriteSequences[2]: A9WriteIssue
TransVec[7] A9LMAdr8Pred x 3 :
WriteSequences[0]: A9WriteLMOpsList.Writes[0-15]
WriteSequences[1]: A9WriteAdr8
WriteSequences[2]: A9WriteIssue
TransVec[8] A9LMUnknownPred x 3 :
WriteSequences[0]: A9WriteL1
WriteSequences[1]: A9WriteL1Hi
WriteSequences[2]: A9WriteL2
WriteSequences[3]: A9WriteL2Hi
WriteSequences[4]: A9WriteL3Hi
WriteSequences[5]: A9WriteL3Hi
WriteSequences[6]: A9WriteL4Hi
WriteSequences[7]: A9WriteL4Hi
WriteSequences[8]: A9WriteL5Hi
WriteSequences[9]: A9WriteL5Hi
WriteSequences[10]: A9WriteL6Hi
WriteSequences[11]: A9WriteL6Hi
WriteSequences[12]: A9WriteL7Hi
WriteSequences[13]: A9WriteL7Hi
WriteSequences[14]: A9WriteL8Hi
WriteSequences[15]: A9WriteL8Hi
WriteSequences[16]: A9WriteAdr2
WriteSequences[17]: A9WriteIssue
回到CodeGenSchedModels::inferFromRW。在1371行,Transitions.TransVec与LastTransitions交互内容,重新进入1363行循环,直到 LastTransitions中不包含SchedVariant为止。
一旦有SchedVariant被处理,就需要定义inferFromTransitions方法来构建与之对应的新调度类型。以我们的例子来说,现在LastTransitions的内容与TransVec一致。
1280 static void inferFromTransitions (ArrayRef<PredTransition>LastTransitions,
1281 unsignedFromClassIdx,
1282 CodeGenSchedModels &SchedModels) {
1283 // For eachPredTransition, create a new CodeGenSchedTransition, which usually
1284 // requires creatinga new SchedClass.
1285 for (ArrayRef<PredTransition>::iterator
1286 I = LastTransitions.begin(), E =LastTransitions.end(); I != E; ++I) {
1287 IdxVec OperWritesVariant;
1288 for (SmallVectorImpl<SmallVector<unsigned,4> >::const_iterator
1289 WSI = I->WriteSequences.begin(),WSE = I->WriteSequences.end();
1290 WSI != WSE; ++WSI) {
1291 // Create anew write representing the expanded sequence.
1292 OperWritesVariant.push_back(
1293 SchedModels.findOrInsertRW(*WSI, /*IsRead=*/ false));
1294 }
1295 IdxVec OperReadsVariant;
1296 for (SmallVectorImpl<SmallVector<unsigned,4> >::const_iterator
1297 RSI = I->ReadSequences.begin(),RSE = I->ReadSequences.end();
1298 RSI != RSE; ++RSI) {
1299 // Create anew read representing the expanded sequence.
1300 OperReadsVariant.push_back(
1301 SchedModels.findOrInsertRW(*RSI, /*IsRead=*/ true));
1302 }
1303 IdxVecProcIndices(I->ProcIndices.begin(), I->ProcIndices.end());
1304 CodeGenSchedTransition SCTrans;
1305 SCTrans.ToClassIdx =
1306 SchedModels. addSchedClass (/*ItinClassDef=*/ nullptr, OperWritesVariant,
1307 OperReadsVariant, ProcIndices);
1308 SCTrans.ProcIndices = ProcIndices;
1309 // The finalPredTerm is unique set of predicates guarding the transition.
1310 RecVec Preds;
1311 for (SmallVectorImpl<PredCheck>::const_iterator
1312 PI = I->PredTerm.begin(), PE =I->PredTerm.end(); PI != PE; ++PI) {
1313 Preds.push_back(PI->Predicate);
1314 }
1315 RecIter PredsEnd =std::unique(Preds.begin(), Preds.end());
1316 Preds.resize(PredsEnd - Preds.begin());
1317 SCTrans.PredTerm = Preds;
1318 SchedModels.getSchedClass(FromClassIdx).Transitions.push_back(SCTrans);
1319 }
1320 }
也就是说,上面的每个TransVec项都会产生一个调度类型。除此之外,对每个新生成的调度类型,还会生成一个CodeGenSchedTransition对象,这个对象的定义是:
97 struct CodeGenSchedTransition {
98 unsigned ToClassIdx;
99 IdxVec ProcIndices;
100 RecVec PredTerm;
101 };
对指定的处理器而言,FromClassIdx所代表的调度类型被当前的ItinRW定义修改为ToClassIdx所代表的调度类型。为了记录这个事实,CodeGenSchedClass定义了Transitions容器。注意在1306行构建的CodeGenSchedClass对象,它的ItinClassDef是NULL。这样在inferSchedClasses的840行循环里,不会再对该CodeGenSchedClass实例调用inferFromRW方法。
另外,注意1315行将重复的谓词去除,就像我们例子显示的,谓词可能会重复多个。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 【每日笔记】【Go学习笔记】2019-01-04 Codis笔记
- 【每日笔记】【Go学习笔记】2019-01-02 Codis笔记
- 【每日笔记】【Go学习笔记】2019-01-07 Codis笔记
- Golang学习笔记-调度器学习
- Vue学习笔记(二)------axios学习
- 算法/NLP/深度学习/机器学习面试笔记
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。