LLVM学习笔记(36)

栏目: 服务器 · 编程工具 · 发布时间: 6年前

内容简介:前面,我们已经各种相关的定义通过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行将重复的谓词去除,就像我们例子显示的,谓词可能会重复多个。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

用数据讲故事

用数据讲故事

[美] Cole Nussbaumer Knaflic / 陆 昊、吴梦颖 / 人民邮电出版社 / 2017-8 / 59.00元

本书通过大量案例研究介绍数据可视化的基础知识,以及如何利用数据创造出吸引人的、信息量大的、有说服力的故事,进而达到有效沟通的目的。具体内容包括:如何充分理解上下文,如何选择合适的图表,如何消除杂乱,如何聚焦受众的视线,如何像设计师一样思考,以及如何用数据讲故事。一起来看看 《用数据讲故事》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码