[llvm] r364964 - [SLP] Recommit: Look-ahead operand reordering heuristic.
Benjamin Kramer via llvm-commits
llvm-commits at lists.llvm.org
Mon Jul 22 10:39:01 PDT 2019
At this point I'm relatively certain that this change just happened to
tickle an AVX512 bug somewhere else. I can still reproduce my floating
point issues (there should be no fast math) at head after the revert, so I
don't think my test case is going to be useful to you.
On Mon, Jul 15, 2019 at 1:43 PM Eric Christopher <echristo at gmail.com> wrote:
> Actually Jorge is... I'm not sure what Ben's found as far as miscompiles
> though.
>
> -eric
>
> On Mon, Jul 15, 2019 at 10:30 AM Eric Christopher <echristo at gmail.com>
> wrote:
> >
> > If I recall correctly the regression was in one of the Eigen
> > benchmarks which are publically available conveniently :) Jordan is
> > going to work on tracking down which one it is, etc.
> >
> > Thanks!
> >
> > -eric
> >
> > On Mon, Jul 15, 2019 at 10:19 AM Porpodas, Vasileios
> > <vasileios.porpodas at intel.com> wrote:
> > >
> > > Sure, go ahead. But please try to share some code examples where you
> are getting these performance differences. There is a good chance that the
> vectorizer was picking a better solution by pure luck, so we need to
> address those cases properly and add tests for them.
> > >
> > > -----Original Message-----
> > > From: Eric Christopher [mailto:echristo at gmail.com]
> > > Sent: Monday, July 15, 2019 9:39 AM
> > > To: Porpodas, Vasileios <vasileios.porpodas at intel.com>
> > > Cc: Benjamin Kramer <benny.kra at gmail.com>; llvm-commits <
> llvm-commits at lists.llvm.org>
> > > Subject: Re: [llvm] r364964 - [SLP] Recommit: Look-ahead operand
> reordering heuristic.
> > >
> > > We're actually seeing some significant performance results around this
> as well, given Ben's miscompiles and that can we revert this while we
> follow up?
> > >
> > > -eric
> > >
> > > On Wed, Jul 3, 2019 at 9:59 AM Porpodas, Vasileios via llvm-commits <
> llvm-commits at lists.llvm.org> wrote:
> > > >
> > > > This patch updates the heuristic that triggers operand reordering of
> commutative operations to improve vectorization. Therefore, there may be
> more instructions that get their operands reordered. Operand reordering for
> floating point instructions will only take place under unsafe math, just
> like before this patch.
> > > >
> > > >
> > > >
> > > > Yes, please share a small test case so that I can investigate
> further.
> > > >
> > > >
> > > >
> > > > From: Benjamin Kramer [mailto:benny.kra at gmail.com]
> > > > Sent: Wednesday, July 3, 2019 8:42 AM
> > > > To: Porpodas, Vasileios <vasileios.porpodas at intel.com>
> > > > Cc: llvm-commits <llvm-commits at lists.llvm.org>
> > > > Subject: Re: [llvm] r364964 - [SLP] Recommit: Look-ahead operand
> reordering heuristic.
> > > >
> > > >
> > > >
> > > > I'm seeing miscompiles with AVX512 after this change (wrong floating
> point results). Any ideas? I'll try getting a small test case.
> > > >
> > > >
> > > >
> > > > On Tue, Jul 2, 2019 at 10:20 PM Vasileios Porpodas via llvm-commits <
> llvm-commits at lists.llvm.org> wrote:
> > > >
> > > > Author: vporpo
> > > > Date: Tue Jul 2 13:20:28 2019
> > > > New Revision: 364964
> > > >
> > > > URL: http://llvm.org/viewvc/llvm-project?rev=364964&view=rev
> > > > Log:
> > > > [SLP] Recommit: Look-ahead operand reordering heuristic.
> > > >
> > > > Summary: This patch introduces a new heuristic for guiding operand
> reordering. The new "look-ahead" heuristic can look beyond the immediate
> predecessors. This helps break ties when the immediate predecessors have
> identical opcodes (see lit test for an example).
> > > >
> > > > Reviewers: RKSimon, ABataev, dtemirbulatov, Ayal, hfinkel, rnk
> > > >
> > > > Reviewed By: RKSimon, dtemirbulatov
> > > >
> > > > Subscribers: hiraditya, phosek, rnk, rcorcs, llvm-commits
> > > >
> > > > Tags: #llvm
> > > >
> > > > Differential Revision: https://reviews.llvm.org/D60897
> > > >
> > > > Modified:
> > > > llvm/trunk/lib/Transforms/Vectorize/SLPVectorizer.cpp
> > > > llvm/trunk/test/Transforms/SLPVectorizer/X86/lookahead.ll
> > > >
> > > > Modified: llvm/trunk/lib/Transforms/Vectorize/SLPVectorizer.cpp
> > > > URL:
> > > >
> http://llvm.org/viewvc/llvm-project/llvm/trunk/lib/Transforms/Vectoriz
> > > > e/SLPVectorizer.cpp?rev=364964&r1=364963&r2=364964&view=diff
> > > >
> ======================================================================
> > > > ========
> > > > --- llvm/trunk/lib/Transforms/Vectorize/SLPVectorizer.cpp (original)
> > > > +++ llvm/trunk/lib/Transforms/Vectorize/SLPVectorizer.cpp Tue Jul 2
> > > > +++ 13:20:28 2019
> > > > @@ -147,6 +147,20 @@ static cl::opt<unsigned> MinTreeSize(
> > > > "slp-min-tree-size", cl::init(3), cl::Hidden,
> > > > cl::desc("Only vectorize small trees if they are fully
> > > > vectorizable"));
> > > >
> > > > +// The maximum depth that the look-ahead score heuristic will
> explore.
> > > > +// The higher this value, the higher the compilation time overhead.
> > > > +static cl::opt<int> LookAheadMaxDepth(
> > > > + "slp-max-look-ahead-depth", cl::init(2), cl::Hidden,
> > > > + cl::desc("The maximum look-ahead depth for operand reordering
> > > > +scores"));
> > > > +
> > > > +// The Look-ahead heuristic goes through the users of the bundle to
> > > > +calculate // the users cost in getExternalUsesCost(). To avoid
> > > > +compilation time increase // we limit the number of users visited
> to this value.
> > > > +static cl::opt<unsigned> LookAheadUsersBudget(
> > > > + "slp-look-ahead-users-budget", cl::init(2), cl::Hidden,
> > > > + cl::desc("The maximum number of users to visit while visiting
> the "
> > > > + "predecessors. This prevents compilation time
> > > > +increase."));
> > > > +
> > > > static cl::opt<bool>
> > > > ViewSLPTree("view-slp-tree", cl::Hidden,
> > > > cl::desc("Display the SLP trees with Graphviz")); @@
> > > > -708,6 +722,7 @@ public:
> > > >
> > > > const DataLayout &DL;
> > > > ScalarEvolution &SE;
> > > > + const BoUpSLP &R;
> > > >
> > > > /// \returns the operand data at \p OpIdx and \p Lane.
> > > > OperandData &getData(unsigned OpIdx, unsigned Lane) { @@ -733,6
> > > > +748,215 @@ public:
> > > > std::swap(OpsVec[OpIdx1][Lane], OpsVec[OpIdx2][Lane]);
> > > > }
> > > >
> > > > + // The hard-coded scores listed here are not very important.
> When computing
> > > > + // the scores of matching one sub-tree with another, we are
> basically
> > > > + // counting the number of values that are matching. So even if
> all scores
> > > > + // are set to 1, we would still get a decent matching result.
> > > > + // However, sometimes we have to break ties. For example we may
> have to
> > > > + // choose between matching loads vs matching opcodes. This is
> what these
> > > > + // scores are helping us with: they provide the order of
> preference.
> > > > +
> > > > + /// Loads from consecutive memory addresses, e.g. load(A[i]),
> load(A[i+1]).
> > > > + static const int ScoreConsecutiveLoads = 3;
> > > > + /// Constants.
> > > > + static const int ScoreConstants = 2;
> > > > + /// Instructions with the same opcode.
> > > > + static const int ScoreSameOpcode = 2;
> > > > + /// Instructions with alt opcodes (e.g, add + sub).
> > > > + static const int ScoreAltOpcodes = 1;
> > > > + /// Identical instructions (a.k.a. splat or broadcast).
> > > > + static const int ScoreSplat = 1;
> > > > + /// Matching with an undef is preferable to failing.
> > > > + static const int ScoreUndef = 1;
> > > > + /// Score for failing to find a decent match.
> > > > + static const int ScoreFail = 0;
> > > > + /// User exteranl to the vectorized code.
> > > > + static const int ExternalUseCost = 1;
> > > > + /// The user is internal but in a different lane.
> > > > + static const int UserInDiffLaneCost = ExternalUseCost;
> > > > +
> > > > + /// \returns the score of placing \p V1 and \p V2 in
> consecutive lanes.
> > > > + static int getShallowScore(Value *V1, Value *V2, const
> DataLayout &DL,
> > > > + ScalarEvolution &SE) {
> > > > + auto *LI1 = dyn_cast<LoadInst>(V1);
> > > > + auto *LI2 = dyn_cast<LoadInst>(V2);
> > > > + if (LI1 && LI2)
> > > > + return isConsecutiveAccess(LI1, LI2, DL, SE)
> > > > + ? VLOperands::ScoreConsecutiveLoads
> > > > + : VLOperands::ScoreFail;
> > > > +
> > > > + auto *C1 = dyn_cast<Constant>(V1);
> > > > + auto *C2 = dyn_cast<Constant>(V2);
> > > > + if (C1 && C2)
> > > > + return VLOperands::ScoreConstants;
> > > > +
> > > > + auto *I1 = dyn_cast<Instruction>(V1);
> > > > + auto *I2 = dyn_cast<Instruction>(V2);
> > > > + if (I1 && I2) {
> > > > + if (I1 == I2)
> > > > + return VLOperands::ScoreSplat;
> > > > + InstructionsState S = getSameOpcode({I1, I2});
> > > > + // Note: Only consider instructions with <= 2 operands to
> avoid
> > > > + // complexity explosion.
> > > > + if (S.getOpcode() && S.MainOp->getNumOperands() <= 2)
> > > > + return S.isAltShuffle() ? VLOperands::ScoreAltOpcodes
> > > > + : VLOperands::ScoreSameOpcode;
> > > > + }
> > > > +
> > > > + if (isa<UndefValue>(V2))
> > > > + return VLOperands::ScoreUndef;
> > > > +
> > > > + return VLOperands::ScoreFail;
> > > > + }
> > > > +
> > > > + /// Holds the values and their lane that are taking part in the
> look-ahead
> > > > + /// score calculation. This is used in the external uses cost
> calculation.
> > > > + SmallDenseMap<Value *, int> InLookAheadValues;
> > > > +
> > > > + /// \Returns the additinal cost due to uses of \p LHS and \p
> RHS that are
> > > > + /// either external to the vectorized code, or require
> shuffling.
> > > > + int getExternalUsesCost(const std::pair<Value *, int> &LHS,
> > > > + const std::pair<Value *, int> &RHS) {
> > > > + int Cost = 0;
> > > > + SmallVector<std::pair<Value *, int>, 2> Values = {LHS, RHS};
> > > > + for (int Idx = 0, IdxE = Values.size(); Idx != IdxE; ++Idx) {
> > > > + Value *V = Values[Idx].first;
> > > > + // Calculate the absolute lane, using the minimum relative
> lane of LHS
> > > > + // and RHS as base and Idx as the offset.
> > > > + int Ln = std::min(LHS.second, RHS.second) + Idx;
> > > > + assert(Ln >= 0 && "Bad lane calculation");
> > > > + unsigned UsersBudget = LookAheadUsersBudget;
> > > > + for (User *U : V->users()) {
> > > > + if (const TreeEntry *UserTE = R.getTreeEntry(U)) {
> > > > + // The user is in the VectorizableTree. Check if we
> need to insert.
> > > > + auto It = llvm::find(UserTE->Scalars, U);
> > > > + assert(It != UserTE->Scalars.end() && "U is in UserTE");
> > > > + int UserLn = std::distance(UserTE->Scalars.begin(), It);
> > > > + assert(UserLn >= 0 && "Bad lane");
> > > > + if (UserLn != Ln)
> > > > + Cost += UserInDiffLaneCost;
> > > > + } else {
> > > > + // Check if the user is in the look-ahead code.
> > > > + auto It2 = InLookAheadValues.find(U);
> > > > + if (It2 != InLookAheadValues.end()) {
> > > > + // The user is in the look-ahead code. Check the lane.
> > > > + if (It2->second != Ln)
> > > > + Cost += UserInDiffLaneCost;
> > > > + } else {
> > > > + // The user is neither in SLP tree nor in the
> look-ahead code.
> > > > + Cost += ExternalUseCost;
> > > > + }
> > > > + }
> > > > + // Limit the number of visited uses to cap compilation
> time.
> > > > + if (--UsersBudget == 0)
> > > > + break;
> > > > + }
> > > > + }
> > > > + return Cost;
> > > > + }
> > > > +
> > > > + /// Go through the operands of \p LHS and \p RHS recursively
> until \p
> > > > + /// MaxLevel, and return the cummulative score. For example:
> > > > + /// \verbatim
> > > > + /// A[0] B[0] A[1] B[1] C[0] D[0] B[1] A[1]
> > > > + /// \ / \ / \ / \ /
> > > > + /// + + + +
> > > > + /// G1 G2 G3 G4
> > > > + /// \endverbatim
> > > > + /// The getScoreAtLevelRec(G1, G2) function will try to match
> the nodes at
> > > > + /// each level recursively, accumulating the score. It starts
> from matching
> > > > + /// the additions at level 0, then moves on to the loads (level
> 1). The
> > > > + /// score of G1 and G2 is higher than G1 and G3, because
> {A[0],A[1]} and
> > > > + /// {B[0],B[1]} match with VLOperands::ScoreConsecutiveLoads,
> while
> > > > + /// {A[0],C[0]} has a score of VLOperands::ScoreFail.
> > > > + /// Please note that the order of the operands does not matter,
> as we
> > > > + /// evaluate the score of all profitable combinations of
> operands. In
> > > > + /// other words the score of G1 and G4 is the same as G1 and
> G2. This
> > > > + /// heuristic is based on ideas described in:
> > > > + /// Look-ahead SLP: Auto-vectorization in the presence of
> commutative
> > > > + /// operations, CGO 2018 by Vasileios Porpodas, Rodrigo C. O.
> Rocha,
> > > > + /// LuÃs F. W. Góes
> > > > + int getScoreAtLevelRec(const std::pair<Value *, int> &LHS,
> > > > + const std::pair<Value *, int> &RHS, int
> CurrLevel,
> > > > + int MaxLevel) {
> > > > +
> > > > + Value *V1 = LHS.first;
> > > > + Value *V2 = RHS.first;
> > > > + // Get the shallow score of V1 and V2.
> > > > + int ShallowScoreAtThisLevel =
> > > > + std::max((int)ScoreFail, getShallowScore(V1, V2, DL, SE) -
> > > > + getExternalUsesCost(LHS,
> RHS));
> > > > + int Lane1 = LHS.second;
> > > > + int Lane2 = RHS.second;
> > > > +
> > > > + // If reached MaxLevel,
> > > > + // or if V1 and V2 are not instructions,
> > > > + // or if they are SPLAT,
> > > > + // or if they are not consecutive, early return the current
> cost.
> > > > + auto *I1 = dyn_cast<Instruction>(V1);
> > > > + auto *I2 = dyn_cast<Instruction>(V2);
> > > > + if (CurrLevel == MaxLevel || !(I1 && I2) || I1 == I2 ||
> > > > + ShallowScoreAtThisLevel == VLOperands::ScoreFail ||
> > > > + (isa<LoadInst>(I1) && isa<LoadInst>(I2) &&
> ShallowScoreAtThisLevel))
> > > > + return ShallowScoreAtThisLevel;
> > > > + assert(I1 && I2 && "Should have early exited.");
> > > > +
> > > > + // Keep track of in-tree values for determining the
> external-use cost.
> > > > + InLookAheadValues[V1] = Lane1;
> > > > + InLookAheadValues[V2] = Lane2;
> > > > +
> > > > + // Contains the I2 operand indexes that got matched with I1
> operands.
> > > > + SmallSet<unsigned, 4> Op2Used;
> > > > +
> > > > + // Recursion towards the operands of I1 and I2. We are trying
> all possbile
> > > > + // operand pairs, and keeping track of the best score.
> > > > + for (unsigned OpIdx1 = 0, NumOperands1 = I1->getNumOperands();
> > > > + OpIdx1 != NumOperands1; ++OpIdx1) {
> > > > + // Try to pair op1I with the best operand of I2.
> > > > + int MaxTmpScore = 0;
> > > > + unsigned MaxOpIdx2 = 0;
> > > > + bool FoundBest = false;
> > > > + // If I2 is commutative try all combinations.
> > > > + unsigned FromIdx = isCommutative(I2) ? 0 : OpIdx1;
> > > > + unsigned ToIdx = isCommutative(I2)
> > > > + ? I2->getNumOperands()
> > > > + : std::min(I2->getNumOperands(),
> OpIdx1 + 1);
> > > > + assert(FromIdx <= ToIdx && "Bad index");
> > > > + for (unsigned OpIdx2 = FromIdx; OpIdx2 != ToIdx; ++OpIdx2) {
> > > > + // Skip operands already paired with OpIdx1.
> > > > + if (Op2Used.count(OpIdx2))
> > > > + continue;
> > > > + // Recursively calculate the cost at each level
> > > > + int TmpScore =
> getScoreAtLevelRec({I1->getOperand(OpIdx1), Lane1},
> > > > +
> {I2->getOperand(OpIdx2), Lane2},
> > > > + CurrLevel + 1,
> MaxLevel);
> > > > + // Look for the best score.
> > > > + if (TmpScore > VLOperands::ScoreFail && TmpScore >
> MaxTmpScore) {
> > > > + MaxTmpScore = TmpScore;
> > > > + MaxOpIdx2 = OpIdx2;
> > > > + FoundBest = true;
> > > > + }
> > > > + }
> > > > + if (FoundBest) {
> > > > + // Pair {OpIdx1, MaxOpIdx2} was found to be best. Never
> revisit it.
> > > > + Op2Used.insert(MaxOpIdx2);
> > > > + ShallowScoreAtThisLevel += MaxTmpScore;
> > > > + }
> > > > + }
> > > > + return ShallowScoreAtThisLevel;
> > > > + }
> > > > +
> > > > + /// \Returns the look-ahead score, which tells us how much the
> sub-trees
> > > > + /// rooted at \p LHS and \p RHS match, the more they match the
> higher the
> > > > + /// score. This helps break ties in an informed way when we
> cannot decide on
> > > > + /// the order of the operands by just considering the immediate
> > > > + /// predecessors.
> > > > + int getLookAheadScore(const std::pair<Value *, int> &LHS,
> > > > + const std::pair<Value *, int> &RHS) {
> > > > + InLookAheadValues.clear();
> > > > + return getScoreAtLevelRec(LHS, RHS, 1, LookAheadMaxDepth);
> > > > + }
> > > > +
> > > > // Search all operands in Ops[*][Lane] for the one that matches
> best
> > > > // Ops[OpIdx][LastLane] and return its opreand index.
> > > > // If no good match can be found, return None.
> > > > @@ -750,9 +974,6 @@ public:
> > > > // The linearized opcode of the operand at OpIdx, Lane.
> > > > bool OpIdxAPO = getData(OpIdx, Lane).APO;
> > > >
> > > > - const unsigned BestScore = 2;
> > > > - const unsigned GoodScore = 1;
> > > > -
> > > > // The best operand index and its score.
> > > > // Sometimes we have more than one option (e.g., Opcode and
> Undefs), so we
> > > > // are using the score to differentiate between the two.
> > > > @@ -781,41 +1002,19 @@ public:
> > > > // Look for an operand that matches the current mode.
> > > > switch (RMode) {
> > > > case ReorderingMode::Load:
> > > > - if (isa<LoadInst>(Op)) {
> > > > - // Figure out which is left and right, so that we can
> check for
> > > > - // consecutive loads
> > > > - bool LeftToRight = Lane > LastLane;
> > > > - Value *OpLeft = (LeftToRight) ? OpLastLane : Op;
> > > > - Value *OpRight = (LeftToRight) ? Op : OpLastLane;
> > > > - if (isConsecutiveAccess(cast<LoadInst>(OpLeft),
> > > > - cast<LoadInst>(OpRight), DL,
> SE))
> > > > - BestOp.Idx = Idx;
> > > > - }
> > > > - break;
> > > > - case ReorderingMode::Opcode:
> > > > - // We accept both Instructions and Undefs, but with
> different scores.
> > > > - if ((isa<Instruction>(Op) && isa<Instruction>(OpLastLane)
> &&
> > > > - cast<Instruction>(Op)->getOpcode() ==
> > > > - cast<Instruction>(OpLastLane)->getOpcode()) ||
> > > > - (isa<UndefValue>(OpLastLane) && isa<Instruction>(Op))
> ||
> > > > - isa<UndefValue>(Op)) {
> > > > - // An instruction has a higher score than an undef.
> > > > - unsigned Score = (isa<UndefValue>(Op)) ? GoodScore :
> BestScore;
> > > > - if (Score > BestOp.Score) {
> > > > - BestOp.Idx = Idx;
> > > > - BestOp.Score = Score;
> > > > - }
> > > > - }
> > > > - break;
> > > > case ReorderingMode::Constant:
> > > > - if (isa<Constant>(Op)) {
> > > > - unsigned Score = (isa<UndefValue>(Op)) ? GoodScore :
> BestScore;
> > > > - if (Score > BestOp.Score) {
> > > > - BestOp.Idx = Idx;
> > > > - BestOp.Score = Score;
> > > > - }
> > > > + case ReorderingMode::Opcode: {
> > > > + bool LeftToRight = Lane > LastLane;
> > > > + Value *OpLeft = (LeftToRight) ? OpLastLane : Op;
> > > > + Value *OpRight = (LeftToRight) ? Op : OpLastLane;
> > > > + unsigned Score =
> > > > + getLookAheadScore({OpLeft, LastLane}, {OpRight,
> Lane});
> > > > + if (Score > BestOp.Score) {
> > > > + BestOp.Idx = Idx;
> > > > + BestOp.Score = Score;
> > > > }
> > > > break;
> > > > + }
> > > > case ReorderingMode::Splat:
> > > > if (Op == OpLastLane)
> > > > BestOp.Idx = Idx;
> > > > @@ -946,8 +1145,8 @@ public:
> > > > public:
> > > > /// Initialize with all the operands of the instruction vector
> \p RootVL.
> > > > VLOperands(ArrayRef<Value *> RootVL, const DataLayout &DL,
> > > > - ScalarEvolution &SE)
> > > > - : DL(DL), SE(SE) {
> > > > + ScalarEvolution &SE, const BoUpSLP &R)
> > > > + : DL(DL), SE(SE), R(R) {
> > > > // Append all the operands of RootVL.
> > > > appendOperandsOfVL(RootVL);
> > > > }
> > > > @@ -1169,7 +1368,8 @@ private:
> > > > SmallVectorImpl<Value
> *> &Left,
> > > > SmallVectorImpl<Value
> *> &Right,
> > > > const DataLayout &DL,
> > > > - ScalarEvolution &SE);
> > > > + ScalarEvolution &SE,
> > > > + const BoUpSLP &R);
> > > > struct TreeEntry {
> > > > using VecTreeTy = SmallVector<std::unique_ptr<TreeEntry>, 8>;
> > > > TreeEntry(VecTreeTy &Container) : Container(Container) {} @@
> > > > -2371,7 +2571,7 @@ void BoUpSLP::buildTree_rec(ArrayRef<Val
> > > > // Commutative predicate - collect + sort operands of the
> instructions
> > > > // so that each side is more likely to have the same opcode.
> > > > assert(P0 == SwapP0 && "Commutative Predicate mismatch");
> > > > - reorderInputsAccordingToOpcode(VL, Left, Right, *DL, *SE);
> > > > + reorderInputsAccordingToOpcode(VL, Left, Right, *DL, *SE,
> > > > + *this);
> > > > } else {
> > > > // Collect operands - commute if it uses the swapped
> predicate.
> > > > for (Value *V : VL) {
> > > > @@ -2416,7 +2616,7 @@ void BoUpSLP::buildTree_rec(ArrayRef<Val
> > > > // have the same opcode.
> > > > if (isa<BinaryOperator>(VL0) && VL0->isCommutative()) {
> > > > ValueList Left, Right;
> > > > - reorderInputsAccordingToOpcode(VL, Left, Right, *DL, *SE);
> > > > + reorderInputsAccordingToOpcode(VL, Left, Right, *DL, *SE,
> > > > + *this);
> > > > buildTree_rec(Left, Depth + 1, {TE, 0});
> > > > buildTree_rec(Right, Depth + 1, {TE, 1});
> > > > return;
> > > > @@ -2585,7 +2785,7 @@ void BoUpSLP::buildTree_rec(ArrayRef<Val
> > > > // Reorder operands if reordering would enable vectorization.
> > > > if (isa<BinaryOperator>(VL0)) {
> > > > ValueList Left, Right;
> > > > - reorderInputsAccordingToOpcode(VL, Left, Right, *DL, *SE);
> > > > + reorderInputsAccordingToOpcode(VL, Left, Right, *DL, *SE,
> > > > + *this);
> > > > buildTree_rec(Left, Depth + 1, {TE, 0});
> > > > buildTree_rec(Right, Depth + 1, {TE, 1});
> > > > return;
> > > > @@ -3302,13 +3502,15 @@ int BoUpSLP::getGatherCost(ArrayRef<Valu
> > > >
> > > > // Perform operand reordering on the instructions in VL and return
> > > > the reordered // operands in Left and Right.
> > > > -void BoUpSLP::reorderInputsAccordingToOpcode(
> > > > - ArrayRef<Value *> VL, SmallVectorImpl<Value *> &Left,
> > > > - SmallVectorImpl<Value *> &Right, const DataLayout &DL,
> > > > - ScalarEvolution &SE) {
> > > > +void BoUpSLP::reorderInputsAccordingToOpcode(ArrayRef<Value *> VL,
> > > > + SmallVectorImpl<Value
> *> &Left,
> > > > + SmallVectorImpl<Value
> *> &Right,
> > > > + const DataLayout &DL,
> > > > + ScalarEvolution &SE,
> > > > + const BoUpSLP &R) {
> > > > if (VL.empty())
> > > > return;
> > > > - VLOperands Ops(VL, DL, SE);
> > > > + VLOperands Ops(VL, DL, SE, R);
> > > > // Reorder the operands in place.
> > > > Ops.reorder();
> > > > Left = Ops.getVL(0);
> > > >
> > > > Modified: llvm/trunk/test/Transforms/SLPVectorizer/X86/lookahead.ll
> > > > URL:
> > > >
> http://llvm.org/viewvc/llvm-project/llvm/trunk/test/Transforms/SLPVect
> > > > orizer/X86/lookahead.ll?rev=364964&r1=364963&r2=364964&view=diff
> > > >
> ======================================================================
> > > > ========
> > > > --- llvm/trunk/test/Transforms/SLPVectorizer/X86/lookahead.ll
> > > > (original)
> > > > +++ llvm/trunk/test/Transforms/SLPVectorizer/X86/lookahead.ll Tue Jul
> > > > +++ 2 13:20:28 2019
> > > > @@ -27,22 +27,19 @@ define void @lookahead_basic(double* %ar
> > > > ; CHECK-NEXT: [[IDX5:%.*]] = getelementptr inbounds double,
> double* [[ARRAY]], i64 5
> > > > ; CHECK-NEXT: [[IDX6:%.*]] = getelementptr inbounds double,
> double* [[ARRAY]], i64 6
> > > > ; CHECK-NEXT: [[IDX7:%.*]] = getelementptr inbounds double,
> double* [[ARRAY]], i64 7
> > > > -; CHECK-NEXT: [[A_0:%.*]] = load double, double* [[IDX0]], align
> 8
> > > > -; CHECK-NEXT: [[A_1:%.*]] = load double, double* [[IDX1]], align
> 8
> > > > -; CHECK-NEXT: [[B_0:%.*]] = load double, double* [[IDX2]], align
> 8
> > > > -; CHECK-NEXT: [[B_1:%.*]] = load double, double* [[IDX3]], align
> 8
> > > > -; CHECK-NEXT: [[C_0:%.*]] = load double, double* [[IDX4]], align
> 8
> > > > -; CHECK-NEXT: [[C_1:%.*]] = load double, double* [[IDX5]], align
> 8
> > > > -; CHECK-NEXT: [[D_0:%.*]] = load double, double* [[IDX6]], align
> 8
> > > > -; CHECK-NEXT: [[D_1:%.*]] = load double, double* [[IDX7]], align
> 8
> > > > -; CHECK-NEXT: [[SUBAB_0:%.*]] = fsub fast double [[A_0]], [[B_0]]
> > > > -; CHECK-NEXT: [[SUBCD_0:%.*]] = fsub fast double [[C_0]], [[D_0]]
> > > > -; CHECK-NEXT: [[SUBAB_1:%.*]] = fsub fast double [[A_1]], [[B_1]]
> > > > -; CHECK-NEXT: [[SUBCD_1:%.*]] = fsub fast double [[C_1]], [[D_1]]
> > > > -; CHECK-NEXT: [[ADDABCD_0:%.*]] = fadd fast double [[SUBAB_0]],
> [[SUBCD_0]]
> > > > -; CHECK-NEXT: [[ADDCDAB_1:%.*]] = fadd fast double [[SUBCD_1]],
> [[SUBAB_1]]
> > > > -; CHECK-NEXT: store double [[ADDABCD_0]], double* [[IDX0]],
> align 8
> > > > -; CHECK-NEXT: store double [[ADDCDAB_1]], double* [[IDX1]],
> align 8
> > > > +; CHECK-NEXT: [[TMP0:%.*]] = bitcast double* [[IDX0]] to <2 x
> double>*
> > > > +; CHECK-NEXT: [[TMP1:%.*]] = load <2 x double>, <2 x double>*
> [[TMP0]], align 8
> > > > +; CHECK-NEXT: [[TMP2:%.*]] = bitcast double* [[IDX2]] to <2 x
> double>*
> > > > +; CHECK-NEXT: [[TMP3:%.*]] = load <2 x double>, <2 x double>*
> [[TMP2]], align 8
> > > > +; CHECK-NEXT: [[TMP4:%.*]] = bitcast double* [[IDX4]] to <2 x
> double>*
> > > > +; CHECK-NEXT: [[TMP5:%.*]] = load <2 x double>, <2 x double>*
> [[TMP4]], align 8
> > > > +; CHECK-NEXT: [[TMP6:%.*]] = bitcast double* [[IDX6]] to <2 x
> double>*
> > > > +; CHECK-NEXT: [[TMP7:%.*]] = load <2 x double>, <2 x double>*
> [[TMP6]], align 8
> > > > +; CHECK-NEXT: [[TMP8:%.*]] = fsub fast <2 x double> [[TMP1]],
> [[TMP3]]
> > > > +; CHECK-NEXT: [[TMP9:%.*]] = fsub fast <2 x double> [[TMP5]],
> [[TMP7]]
> > > > +; CHECK-NEXT: [[TMP10:%.*]] = fadd fast <2 x double> [[TMP8]],
> [[TMP9]]
> > > > +; CHECK-NEXT: [[TMP11:%.*]] = bitcast double* [[IDX0]] to <2 x
> double>*
> > > > +; CHECK-NEXT: store <2 x double> [[TMP10]], <2 x double>*
> [[TMP11]], align 8
> > > > ; CHECK-NEXT: ret void
> > > > ;
> > > > entry:
> > > > @@ -164,22 +161,23 @@ define void @lookahead_alt2(double* %arr
> > > > ; CHECK-NEXT: [[IDX5:%.*]] = getelementptr inbounds double,
> double* [[ARRAY]], i64 5
> > > > ; CHECK-NEXT: [[IDX6:%.*]] = getelementptr inbounds double,
> double* [[ARRAY]], i64 6
> > > > ; CHECK-NEXT: [[IDX7:%.*]] = getelementptr inbounds double,
> double* [[ARRAY]], i64 7
> > > > -; CHECK-NEXT: [[A_0:%.*]] = load double, double* [[IDX0]], align
> 8
> > > > -; CHECK-NEXT: [[A_1:%.*]] = load double, double* [[IDX1]], align
> 8
> > > > -; CHECK-NEXT: [[B_0:%.*]] = load double, double* [[IDX2]], align
> 8
> > > > -; CHECK-NEXT: [[B_1:%.*]] = load double, double* [[IDX3]], align
> 8
> > > > -; CHECK-NEXT: [[C_0:%.*]] = load double, double* [[IDX4]], align
> 8
> > > > -; CHECK-NEXT: [[C_1:%.*]] = load double, double* [[IDX5]], align
> 8
> > > > -; CHECK-NEXT: [[D_0:%.*]] = load double, double* [[IDX6]], align
> 8
> > > > -; CHECK-NEXT: [[D_1:%.*]] = load double, double* [[IDX7]], align
> 8
> > > > -; CHECK-NEXT: [[ADDAB_0:%.*]] = fadd fast double [[A_0]], [[B_0]]
> > > > -; CHECK-NEXT: [[SUBCD_0:%.*]] = fsub fast double [[C_0]], [[D_0]]
> > > > -; CHECK-NEXT: [[ADDCD_1:%.*]] = fadd fast double [[C_1]], [[D_1]]
> > > > -; CHECK-NEXT: [[SUBAB_1:%.*]] = fsub fast double [[A_1]], [[B_1]]
> > > > -; CHECK-NEXT: [[ADDABCD_0:%.*]] = fadd fast double [[ADDAB_0]],
> [[SUBCD_0]]
> > > > -; CHECK-NEXT: [[ADDCDAB_1:%.*]] = fadd fast double [[ADDCD_1]],
> [[SUBAB_1]]
> > > > -; CHECK-NEXT: store double [[ADDABCD_0]], double* [[IDX0]],
> align 8
> > > > -; CHECK-NEXT: store double [[ADDCDAB_1]], double* [[IDX1]],
> align 8
> > > > +; CHECK-NEXT: [[TMP0:%.*]] = bitcast double* [[IDX0]] to <2 x
> double>*
> > > > +; CHECK-NEXT: [[TMP1:%.*]] = load <2 x double>, <2 x double>*
> [[TMP0]], align 8
> > > > +; CHECK-NEXT: [[TMP2:%.*]] = bitcast double* [[IDX2]] to <2 x
> double>*
> > > > +; CHECK-NEXT: [[TMP3:%.*]] = load <2 x double>, <2 x double>*
> [[TMP2]], align 8
> > > > +; CHECK-NEXT: [[TMP4:%.*]] = bitcast double* [[IDX4]] to <2 x
> double>*
> > > > +; CHECK-NEXT: [[TMP5:%.*]] = load <2 x double>, <2 x double>*
> [[TMP4]], align 8
> > > > +; CHECK-NEXT: [[TMP6:%.*]] = bitcast double* [[IDX6]] to <2 x
> double>*
> > > > +; CHECK-NEXT: [[TMP7:%.*]] = load <2 x double>, <2 x double>*
> [[TMP6]], align 8
> > > > +; CHECK-NEXT: [[TMP8:%.*]] = fsub fast <2 x double> [[TMP5]],
> [[TMP7]]
> > > > +; CHECK-NEXT: [[TMP9:%.*]] = fadd fast <2 x double> [[TMP5]],
> [[TMP7]]
> > > > +; CHECK-NEXT: [[TMP10:%.*]] = shufflevector <2 x double>
> [[TMP8]], <2 x double> [[TMP9]], <2 x i32> <i32 0, i32 3>
> > > > +; CHECK-NEXT: [[TMP11:%.*]] = fadd fast <2 x double> [[TMP1]],
> [[TMP3]]
> > > > +; CHECK-NEXT: [[TMP12:%.*]] = fsub fast <2 x double> [[TMP1]],
> [[TMP3]]
> > > > +; CHECK-NEXT: [[TMP13:%.*]] = shufflevector <2 x double>
> [[TMP11]], <2 x double> [[TMP12]], <2 x i32> <i32 0, i32 3>
> > > > +; CHECK-NEXT: [[TMP14:%.*]] = fadd fast <2 x double> [[TMP13]],
> [[TMP10]]
> > > > +; CHECK-NEXT: [[TMP15:%.*]] = bitcast double* [[IDX0]] to <2 x
> double>*
> > > > +; CHECK-NEXT: store <2 x double> [[TMP14]], <2 x double>*
> [[TMP15]], align 8
> > > > ; CHECK-NEXT: ret void
> > > > ;
> > > > entry:
> > > > @@ -239,6 +237,97 @@ define void @lookahead_external_uses(dou
> > > > ; CHECK-NEXT: [[IDXB2:%.*]] = getelementptr inbounds double,
> double* [[B]], i64 2
> > > > ; CHECK-NEXT: [[IDXA2:%.*]] = getelementptr inbounds double,
> double* [[A]], i64 2
> > > > ; CHECK-NEXT: [[IDXB1:%.*]] = getelementptr inbounds double,
> double* [[B]], i64 1
> > > > +; CHECK-NEXT: [[A0:%.*]] = load double, double* [[IDXA0]], align
> 8
> > > > +; CHECK-NEXT: [[C0:%.*]] = load double, double* [[IDXC0]], align
> 8
> > > > +; CHECK-NEXT: [[D0:%.*]] = load double, double* [[IDXD0]], align
> 8
> > > > +; CHECK-NEXT: [[A1:%.*]] = load double, double* [[IDXA1]], align
> 8
> > > > +; CHECK-NEXT: [[B2:%.*]] = load double, double* [[IDXB2]], align
> 8
> > > > +; CHECK-NEXT: [[A2:%.*]] = load double, double* [[IDXA2]], align
> 8
> > > > +; CHECK-NEXT: [[TMP0:%.*]] = bitcast double* [[IDXB0]] to <2 x
> double>*
> > > > +; CHECK-NEXT: [[TMP1:%.*]] = load <2 x double>, <2 x double>*
> [[TMP0]], align 8
> > > > +; CHECK-NEXT: [[TMP2:%.*]] = insertelement <2 x double> undef,
> double [[C0]], i32 0
> > > > +; CHECK-NEXT: [[TMP3:%.*]] = insertelement <2 x double>
> [[TMP2]], double [[A1]], i32 1
> > > > +; CHECK-NEXT: [[TMP4:%.*]] = insertelement <2 x double> undef,
> double [[D0]], i32 0
> > > > +; CHECK-NEXT: [[TMP5:%.*]] = insertelement <2 x double>
> [[TMP4]], double [[B2]], i32 1
> > > > +; CHECK-NEXT: [[TMP6:%.*]] = fsub fast <2 x double> [[TMP3]],
> [[TMP5]]
> > > > +; CHECK-NEXT: [[TMP7:%.*]] = insertelement <2 x double> undef,
> double [[A0]], i32 0
> > > > +; CHECK-NEXT: [[TMP8:%.*]] = insertelement <2 x double>
> [[TMP7]], double [[A2]], i32 1
> > > > +; CHECK-NEXT: [[TMP9:%.*]] = fsub fast <2 x double> [[TMP8]],
> [[TMP1]]
> > > > +; CHECK-NEXT: [[TMP10:%.*]] = fadd fast <2 x double> [[TMP9]],
> [[TMP6]]
> > > > +; CHECK-NEXT: [[IDXS0:%.*]] = getelementptr inbounds double,
> double* [[S:%.*]], i64 0
> > > > +; CHECK-NEXT: [[IDXS1:%.*]] = getelementptr inbounds double,
> double* [[S]], i64 1
> > > > +; CHECK-NEXT: [[TMP11:%.*]] = bitcast double* [[IDXS0]] to <2 x
> double>*
> > > > +; CHECK-NEXT: store <2 x double> [[TMP10]], <2 x double>*
> [[TMP11]], align 8
> > > > +; CHECK-NEXT: store double [[A1]], double* [[EXT1:%.*]], align 8
> > > > +; CHECK-NEXT: ret void
> > > > +;
> > > > +entry:
> > > > + %IdxA0 = getelementptr inbounds double, double* %A, i64 0
> > > > + %IdxB0 = getelementptr inbounds double, double* %B, i64 0
> > > > + %IdxC0 = getelementptr inbounds double, double* %C, i64 0
> > > > + %IdxD0 = getelementptr inbounds double, double* %D, i64 0
> > > > +
> > > > + %IdxA1 = getelementptr inbounds double, double* %A, i64 1
> > > > + %IdxB2 = getelementptr inbounds double, double* %B, i64 2
> > > > + %IdxA2 = getelementptr inbounds double, double* %A, i64 2
> > > > + %IdxB1 = getelementptr inbounds double, double* %B, i64 1
> > > > +
> > > > + %A0 = load double, double *%IdxA0, align 8
> > > > + %B0 = load double, double *%IdxB0, align 8
> > > > + %C0 = load double, double *%IdxC0, align 8
> > > > + %D0 = load double, double *%IdxD0, align 8
> > > > +
> > > > + %A1 = load double, double *%IdxA1, align 8
> > > > + %B2 = load double, double *%IdxB2, align 8
> > > > + %A2 = load double, double *%IdxA2, align 8
> > > > + %B1 = load double, double *%IdxB1, align 8
> > > > +
> > > > + %subA0B0 = fsub fast double %A0, %B0
> > > > + %subC0D0 = fsub fast double %C0, %D0
> > > > +
> > > > + %subA1B2 = fsub fast double %A1, %B2
> > > > + %subA2B1 = fsub fast double %A2, %B1
> > > > +
> > > > + %add0 = fadd fast double %subA0B0, %subC0D0
> > > > + %add1 = fadd fast double %subA1B2, %subA2B1
> > > > +
> > > > + %IdxS0 = getelementptr inbounds double, double* %S, i64 0
> > > > + %IdxS1 = getelementptr inbounds double, double* %S, i64 1
> > > > +
> > > > + store double %add0, double *%IdxS0, align 8 store double %add1,
> > > > + double *%IdxS1, align 8
> > > > +
> > > > + ; External use
> > > > + store double %A1, double *%Ext1, align 8
> > > > + ret void
> > > > +}
> > > > +
> > > > +; A[0] B[0] C[0] D[0] A[1] B[2] A[2] B[1]
> > > > +; \ / \ / / \ / \ / \
> > > > +; - - U1,U2,U3 - - U4,U5
> > > > +; \ / \ /
> > > > +; + +
> > > > +; | |
> > > > +; S[0] S[1]
> > > > +;
> > > > +;
> > > > +; If we limit the users budget for the look-ahead heuristic to 2,
> > > > +then the ; look-ahead heuristic has no way of choosing B[1] (with 2
> > > > +external users) ; over A[1] (with 3 external users).
> > > > +; The result is that the operands are of the Add not reordered and
> > > > +the loads ; from A get vectorized instead of the loads from B.
> > > > +;
> > > > +define void @lookahead_limit_users_budget(double* %A, double *%B,
> > > > +double *%C, double *%D, double *%S, double *%Ext1, double *%Ext2,
> > > > +double *%Ext3, double *%Ext4, double *%Ext5) { ; CHECK-LABEL:
> @lookahead_limit_users_budget( ; CHECK-NEXT: entry:
> > > > +; CHECK-NEXT: [[IDXA0:%.*]] = getelementptr inbounds double,
> double* [[A:%.*]], i64 0
> > > > +; CHECK-NEXT: [[IDXB0:%.*]] = getelementptr inbounds double,
> double* [[B:%.*]], i64 0
> > > > +; CHECK-NEXT: [[IDXC0:%.*]] = getelementptr inbounds double,
> double* [[C:%.*]], i64 0
> > > > +; CHECK-NEXT: [[IDXD0:%.*]] = getelementptr inbounds double,
> double* [[D:%.*]], i64 0
> > > > +; CHECK-NEXT: [[IDXA1:%.*]] = getelementptr inbounds double,
> double* [[A]], i64 1
> > > > +; CHECK-NEXT: [[IDXB2:%.*]] = getelementptr inbounds double,
> double* [[B]], i64 2
> > > > +; CHECK-NEXT: [[IDXA2:%.*]] = getelementptr inbounds double,
> double* [[A]], i64 2
> > > > +; CHECK-NEXT: [[IDXB1:%.*]] = getelementptr inbounds double,
> double* [[B]], i64 1
> > > > ; CHECK-NEXT: [[B0:%.*]] = load double, double* [[IDXB0]], align
> 8
> > > > ; CHECK-NEXT: [[C0:%.*]] = load double, double* [[IDXC0]], align
> 8
> > > > ; CHECK-NEXT: [[D0:%.*]] = load double, double* [[IDXD0]], align
> 8
> > > > @@ -262,6 +351,10 @@ define void @lookahead_external_uses(dou
> > > > ; CHECK-NEXT: store <2 x double> [[TMP10]], <2 x double>*
> [[TMP11]], align 8
> > > > ; CHECK-NEXT: [[TMP12:%.*]] = extractelement <2 x double>
> [[TMP1]], i32 1
> > > > ; CHECK-NEXT: store double [[TMP12]], double* [[EXT1:%.*]],
> align 8
> > > > +; CHECK-NEXT: store double [[TMP12]], double* [[EXT2:%.*]],
> align 8
> > > > +; CHECK-NEXT: store double [[TMP12]], double* [[EXT3:%.*]],
> align 8
> > > > +; CHECK-NEXT: store double [[B1]], double* [[EXT4:%.*]], align 8
> > > > +; CHECK-NEXT: store double [[B1]], double* [[EXT5:%.*]], align 8
> > > > ; CHECK-NEXT: ret void
> > > > ;
> > > > entry:
> > > > @@ -300,7 +393,56 @@ entry:
> > > > store double %add0, double *%IdxS0, align 8
> > > > store double %add1, double *%IdxS1, align 8
> > > >
> > > > - ; External use
> > > > + ; External uses of A1
> > > > store double %A1, double *%Ext1, align 8
> > > > + store double %A1, double *%Ext2, align 8 store double %A1, double
> > > > + *%Ext3, align 8
> > > > +
> > > > + ; External uses of B1
> > > > + store double %B1, double *%Ext4, align 8 store double %B1, double
> > > > + *%Ext5, align 8
> > > > +
> > > > + ret void
> > > > +}
> > > > +
> > > > +; This checks that the lookahead code does not crash when
> instructions with the same opcodes have different numbers of operands (in
> this case the calls).
> > > > +
> > > > +%Class = type { i8 }
> > > > +declare double @_ZN1i2ayEv(%Class*)
> > > > +declare double @_ZN1i2axEv()
> > > > +
> > > > +define void @lookahead_crash(double* %A, double *%S, %Class *%Arg0)
> {
> > > > +; CHECK-LABEL: @lookahead_crash(
> > > > +; CHECK-NEXT: [[IDXA0:%.*]] = getelementptr inbounds double,
> double* [[A:%.*]], i64 0
> > > > +; CHECK-NEXT: [[IDXA1:%.*]] = getelementptr inbounds double,
> double* [[A]], i64 1
> > > > +; CHECK-NEXT: [[TMP1:%.*]] = bitcast double* [[IDXA0]] to <2 x
> double>*
> > > > +; CHECK-NEXT: [[TMP2:%.*]] = load <2 x double>, <2 x double>*
> [[TMP1]], align 8
> > > > +; CHECK-NEXT: [[C0:%.*]] = call double @_ZN1i2ayEv(%Class*
> [[ARG0:%.*]])
> > > > +; CHECK-NEXT: [[C1:%.*]] = call double @_ZN1i2axEv()
> > > > +; CHECK-NEXT: [[TMP3:%.*]] = insertelement <2 x double> undef,
> double [[C0]], i32 0
> > > > +; CHECK-NEXT: [[TMP4:%.*]] = insertelement <2 x double>
> [[TMP3]], double [[C1]], i32 1
> > > > +; CHECK-NEXT: [[TMP5:%.*]] = fadd fast <2 x double> [[TMP2]],
> [[TMP4]]
> > > > +; CHECK-NEXT: [[IDXS0:%.*]] = getelementptr inbounds double,
> double* [[S:%.*]], i64 0
> > > > +; CHECK-NEXT: [[IDXS1:%.*]] = getelementptr inbounds double,
> double* [[S]], i64 1
> > > > +; CHECK-NEXT: [[TMP6:%.*]] = bitcast double* [[IDXS0]] to <2 x
> double>*
> > > > +; CHECK-NEXT: store <2 x double> [[TMP5]], <2 x double>*
> [[TMP6]], align 8
> > > > +; CHECK-NEXT: ret void
> > > > +;
> > > > + %IdxA0 = getelementptr inbounds double, double* %A, i64 0
> > > > + %IdxA1 = getelementptr inbounds double, double* %A, i64 1
> > > > +
> > > > + %A0 = load double, double *%IdxA0, align 8
> > > > + %A1 = load double, double *%IdxA1, align 8
> > > > +
> > > > + %C0 = call double @_ZN1i2ayEv(%Class *%Arg0)
> > > > + %C1 = call double @_ZN1i2axEv()
> > > > +
> > > > + %add0 = fadd fast double %A0, %C0
> > > > + %add1 = fadd fast double %A1, %C1
> > > > +
> > > > + %IdxS0 = getelementptr inbounds double, double* %S, i64 0
> > > > + %IdxS1 = getelementptr inbounds double, double* %S, i64 1 store
> > > > + double %add0, double *%IdxS0, align 8 store double %add1, double
> > > > + *%IdxS1, align 8
> > > > ret void
> > > > }
> > > >
> > > >
> > > > _______________________________________________
> > > > llvm-commits mailing list
> > > > llvm-commits at lists.llvm.org
> > > > https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-commits
> > > >
> > > > _______________________________________________
> > > > llvm-commits mailing list
> > > > llvm-commits at lists.llvm.org
> > > > https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-commits
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/llvm-commits/attachments/20190722/ca711ed9/attachment-0001.html>
More information about the llvm-commits
mailing list