[LLVMdev] MergeFunctions: reduce complexity to O(log(N))

Stepan Dyatkovskiy stpworld at narod.ru
Thu Mar 13 11:08:10 PDT 2014


Hi Nick,
I have committed 0001 as r203788.
I'm working on fixes for 0002 - 0014.

 > After reading through this patch series, I feel like I'm missing
 > something important. Where's the sort function? It looks like we're
 > still comparing all functions to all other functions.
When you insert functions into std::set or its analogs it does all the 
job for you. Since internally it builds a binary tree using "less" 
comparison, and each insert/look-up operation takes O(log(N)) time.

-Stepan.

Nick Lewycky wrote:
> On 27 February 2014 08:23, Stepan Dyatkovskiy <stpworld at narod.ru
> <mailto:stpworld at narod.ru>> wrote:
>
>     Hi Nick,
>
>     I tried to rework changes as you requested. One of patches (0004
>     with extra assertions) has been removed.
>
>
>      > +  bool isEquivalentType(Type *Ty1, Type *Ty2) const {
>      > +    return cmpType(Ty1, Ty2) == 0;
>      > +  }
>      >
>      > Why do we still need isEquivalentType? Can we nuke this?
>     Yup. After applying all the patches isEquivalentType will be totally
>     replaced with cmpType. All isEqXXXX and friends will be removed in
>     0011 (old 0012). No traces left.
>     Old function wasn't removed in 0001 just for keeping patches without
>     extra noise like:
>
>     - something that uses isEquivalentType
>     + something that uses cmpType
>
>     The point is, that "something" that uses isEquivalentType, will be
>     also replaced with one of next patches in this series.
>
>
>      >
>      > +static int cmpNumbers(uint64_t L, uint64_t R) {
>      > +  if (L < R) return -1;
>      > +  if (L > R) return 1;
>      > +  return 0;
>      > +}
>      >
>      > At a high level, you don't actually need a <=> operator to use a
>     sort. A
>      > strict ordering ( < operator) is sufficient. (Note that for
>     mergefunc, a
>      > strict weak ordering is not sufficient, it must be a total ordering.)
>     That could be done with int FunctionComparator::compare(). We can
>     replace it with bool FunctionComparator::less(). Though for all
>     other cmp methods need at least 2 bits of information as result:
>     1. Whether things are equal.
>     2. Whether left less than right.
>
>     As for FunctionComparator::compare(), conversion into less() will
>     increase time of sanity check (patch #0010).
>     Sanity check is just a sweet bonus. It checks that ordering
>     implemented properly (checks order relation properties).
>     Turning compare() into less() mean, that we'll have to run
>     comparison two times: L.less(R) and R.less(L). But may be sanity
>     check is not a thing to be published at all.
>
>
>      >
>      > Consider hoisting this inside the FunctionComparator class? That
>     class
>      > should have a bunch of implementations of comparisons between various
>      > different things, which can pass down to other methods in the
>     same class.
>     In new patch series attached to this post, I have moved all static
>     methods into FunctionComparator.
>
>
>      > +  // Replacement for type::canLosslesslyBitCastTo, that
>      > +  // establish order relation on this kind of properties.
>      > +  int checkForLosslessBitcast(const Type *L, const Type *R);
>      >
>      > Type:: not type:: . Please make this comment more descriptive.
>     Done.
>     [new comment]
>     Replacement for Type::canLosslesslyBitCastTo, that
>
>     establish order relation on this kind of properties
>     Returns 0, if L and R types could be converted to each other without
>     reinterpretation of bits.
>     Otherwise method returns -1 or 1, defining total ordering between
>     types in context of lossless bitcastability trait.
>     E.g.: if L is less than R (result is -1), than every type that could be
>     losslessly bitcasted to L is less than R.
>     [/new comment]
>
>
>      >
>      > +  /// Replacement for C1 == getBitCast(C2, C1Ty)
>      > +  /// Its more controllable, and supposed to be simpler and more
>      > predictionable.
>      > +  /// As very important advantage: it allows to introduce order
>     relation on
>      > +  /// constants set, and thus use it as trait in refinement
>     routines.
>      >
>      > "Its" --> "It's". "predictionable" --> "predictable". And how is
>     it more
>      > predictable? I think this comment would be better if it described the
>      > function instead of making comparisons between it and other
>     functions.
>      > Something like, "Compare constants under a system where pointer
>     to X and
>      > pointer to Y are considered equal" or whatever is actually true here.
>     Done.
>     [new comment]
>
>     Replacement for C1 == getBitCast(C2, C1Ty)
>     Parses constants contents, assuming that types are losslessly
>     bitcasted between each other. So actually it ignores types and only
>     compares bits from L and R.
>     Returns 0, if L and R has equivalent content.
>     -1 or 1 if values are different. Maintaining total ordering requires
>     two values that indicates non-equivalence (L less R, L greater R).
>     [/new comment]
>
>
>      >
>      > +enum ConstantType {
>      > I'm not sure that this buys you much. All the "isa" tests can be
>     broken
>      > down into a switch on getValueID() with the one exception of
>     isNullValue().
>     Done.
>
>
>      > +    assert(
>      > +      C1->getType()->__canLosslesslyBitCastTo(C2->__getType()) &&
>      > +      "Pass is healthless. checkForLosslessBitcast should be
>     twin of "
>      > +      "canLosslesslyBitCastTo method, except the case when the
>     last one "
>      > +      "returns false, the first one should return -1 or 1");
>     ...
>
>      > I think we can skip the asserts here. They aren't detecting a
>     specific
>      > bug, they're checking whether the new code does a certain task
>     relative
>      > to the old code. Drop the old code, your new code is the new
>     sheriff in
>      > town.
>     Asserts has been removed.
>
>
>      >
>      > +  DenseMap<const Value*, int> sn_map1, sn_map2;
>      >
>      > What is sn short for ("seen")? Why are there two of these?
>     Serial number :-)
>
>      >
>      > +  std::pair<DenseMap<const Value *, int>::iterator, bool>
>      > +    LeftSN = sn_map1.insert(std::make_pair(__V1, sn_map1.size())),
>      > +    RightSN = sn_map2.insert(std::make_pair(__V2, sn_map2.size()));
>      >
>      > So I think I get it, this is easy to reason about. You number
>     each value
>      > going down both functions. But I think you can eliminate one of these
>      > maps because you know that if the left and right were ever different
>      > then we terminate the comparison immediately. You can at least
>     share one
>      > map with both V1 and V2, but I think you can reduce the number of map
>      > insertions.
>     Not sure. Consider that in left you have:
>     %A = alloca i32
>     %B = alloca i32
>     store i32 1, i32* %A
>
>     And in right:
>     %A = alloca i32
>     %B = alloca i32
>     store i32 1, i32* %B
>
>     When we meet 'store' instruction we need to check in which order %A
>     was allocated at left and in which order %B was allocated at right.
>     So for each value in function we have to assign serial number. Then
>     comparing to local values from different functions we can just check
>     their serial numbers.
>     Though, may be I missed something? May be there is another way to
>     determine "who was first".
>
>
>      > High-level question, are attributes ordered? Your comparison is
>     ordered.
>      > So if I have function F with string attributes "sse2",
>     "gc=hemisphere"
>      > and another function G with string attributes "gc=hemisphere", "sse2"
>      > then they would be considered different. I don't think that's right.
>     I didn't check it. But if it is not ordered, we could order it
>     lexicographically.
>
>
>      >
>      > +  int cmpOperation(const Instruction *I1, const Instruction *I2)
>     const;
>      > +
>      >     bool isEquivalentOperation(const Instruction *I1,
>      > -                             const Instruction *I2) const;
>      > +                             const Instruction *I2) const {
>      > +    return cmpOperation(I1, I2) == 0;
>      > +  }
>      >
>      > Hopefully this patch series ultimately eliminates calls of
>      > isEquivalentOperation?
>     Of course. Just like isEquivalentType.
>
>
>      > By the way, this is an interesting one. Should "add x, y" and
>     "add nsw
>      > x, y" be equal or not?
>     Yeah. Those are interesting questions. We could even treat 'mul a,
>     2' and 'shl a,1' as equal in some cases. I think it's matter of time
>     and further optimization.
>
>     Please find first reworked patches in attachment.
>
>
> I am extremely concerned that you may end up with A < B and B < C but A
>  > C. There are cases where you use cmpTypes to compare two types, then
> others where you compare them via cmpNumbers. The only way to ensure
> that the sort function is self-consistent is to be entirely consistent
> with how we traverse the function.
>
> 0001:
> +  int cmpType(Type *Ty1, Type *Ty2) const;
> +
>
> Please add a comment for this method. Include the meaning of the
> returned value. ("man strcmp" for inspiration.)
>
> +  static int cmpNumbers(uint64_t L, uint64_t R);
>
> Optional: is there any benefit to making this static instead of a const
> method? It doesn't need access to the 'this' pointer, but it seems like
> that's an incidental artifact of the implementation. The other cmp*
> members are const methods.
>
> +      return cmpNumbers(FTy1->isVarArg(), FTy2->isVarArg());;
>
> Extra semi-colon.
>
> I trust you to apply the fixes above, you can choose to commit the patch
> without going through another round of review with me.
>
> 0002:
>
> +int FunctionComparator::checkForLosslessBitcast(const Type *L, const
> Type *R) {
> ...
> +  int TypesRes = cmpNumbers((uint64_t) L, (uint64_t) R);
> ...
> +      return TypesRes;
>
> Why isn't this using cmpType? Alternatively, any reason not to have
> cmpPointers(const void *L, const void *R)?
>
> +      }
> +      else {
>
> Please put the else on the same line as the closing brace.
>
> +    else if (const VectorType *thisPTy = dyn_cast<VectorType>(L))
>
> Missing initial capital, see
> http://llvm.org/docs/CodingStandards.html#name-types-functions-variables-and-enumerators-properly
> . Also, I find it odd that you switch from "L" and "R" to "this" and
> "that". Please make those consistent, probably on left and right.
>
> 0003:
>
> +  int cmpConstants(const Constant *L, const Constant *R);
>
> Any reason this method isn't const?
>
> +int FunctionComparator::cmpConstants(const Constant *L, const Constant
> *R) {
> +
> +  // Pack null value as one more Value ID
> +  unsigned LType = L->isNullValue() ? 0 : L->getValueID() + 1;
> +  unsigned RType = R->isNullValue() ? 0 : R->getValueID() + 1;
> +
> +  if (int Res = cmpNumbers(LType, RType))
> +    return Res;
> +
> +  if (LType == 0)
> +    return 0;
> +
> +  switch (LType-1) {
>
> Optional: how about:
>
>    if (L->isNullValue() && R->isNullValue())
>      return cmpType(L->getType(), R->getType());
>    if (L->isNullValue() && !R->isNullValue())
>      return 1;
>    if (!L->isNullValue() && R->isNullVaue())
>      return -1;
>
>    if (int Res = cmpNumbers(L->getValueID(), R->getValueID()))
>      return Res;
>
>    switch (L->getValueID()) {
>
> ? In particular I'm not a fan of how LType/RType are equal to valueID+1,
> even with the comment.
>
> +  case Value::FunctionVal:
> +  case Value::GlobalVariableVal:
> +  case Value::GlobalAliasVal:
> +  default: // Unknown constant
> +    return cmpNumbers((uint64_t)L, (uint64_t)R);
>
> Please assert on unknown constant.
>
> How are function, global variable and alias reachable here?
>
> 0004:
>
> Thanks for the comment on sn_mapL/R!
>
> +int FunctionComparator::cmpEnumerate(const Value *V1, const Value *V2) {
>
> "Compare enumerate"? This name doesn't make sense to me. "cmpValue" perhaps?
>
> +  const Constant *C1 = dyn_cast<Constant>(V1);
> +  const Constant *C2 = dyn_cast<Constant>(V2);
> +  if (C1 && C2) {
> +    if (V1 == V2) return 0;
>       // TODO: constant expressions with GEP or references to F1 or F2.
> -    if (C1->isNullValue() && C2->isNullValue() &&
> -        isEquivalentType(C1->getType(), C2->getType()))
> -      return true;
> -    // Try bitcasting C2 to C1's type. If the bitcast is legal and
> returns C1
> -    // then they must have equal bit patterns.
> -    return checkForLosslessBitcast(C1->getType(), C2->getType()) == 0 &&
> -        cmpConstants(C1, C2) == 0;
> +    if (C1->isNullValue() && C2->isNullValue()) {
> +      if (int Res = cmpType(C1->getType(), C2->getType()))
> +        return Res;
> +    }
> +
> +    // Check whether C2 and C1 has equal bit patterns.
> +    if (int Res = checkForLosslessBitcast(C1->getType(), C2->getType()))
> +      return Res;
> +
> +    // Compare C1 and the bitcast result.
> +    if (int Res = cmpConstants(C1, C2))
> +      return Res;
> +
> +    return 0;
>     }
> +  if (C1)
> +    return -1;
> +  if (C2)
> +    return 1;
>
> I'm confused why this isn't just using cmpConstants? I think the
> isNullValue to cmpType check is already in cmpConstants, why not move
> the checkForLosslessBitcast there? Is cmpConstants used from elsewhere
> and needs to not have that check?
>
> +  std::pair<DenseMap<const Value *, int>::iterator, bool>
> +    LeftSN = sn_mapL.insert(std::make_pair(V1, sn_mapL.size())),
> +    RightSN = sn_mapR.insert(std::make_pair(V2, sn_mapR.size()));
>
> "auto"?
>
> 0005:
>
> Okay, this makes attributes ordered.
>
> +      enum AttrType {
> +        Enum,
> +        Align,
> +        Other
> +      } LeftAttrType = Other, RightAttrType = Other;
> +
> +      if (LA.isAlignAttribute()) LeftAttrType = Align;
> +      else if (LA.isEnumAttribute()) LeftAttrType = Enum;
> +      if (RA.isAlignAttribute()) RightAttrType = Align;
> +      else if (RA.isEnumAttribute()) RightAttrType = Enum;
> +
> +      if (int Res = cmpNumbers(LeftAttrType, RightAttrType))
> +        return Res;
> +
> +      switch (LeftAttrType) {
> +      case Enum:
> +        if (int Res = cmpNumbers(LA.getKindAsEnum(), RA.getKindAsEnum()))
> +          return Res;
> +        break;
> +      case Align:
> +        if (int Res = cmpNumbers(LA.getValueAsInt(), RA.getValueAsInt()))
> +          return Res;
> +        break;
> +      case Other:
> +        if (int Res = cmpStrings(LA.getKindAsString(),
> RA.getKindAsString()))
> +          return Res;
> +        if (int Res = cmpStrings(LA.getValueAsString(),
> RA.getValueAsString()))
> +          return Res;
> +        break;
> +      }
>
> Attributes already have operator< and operator==. Please reuse them.
>
> 0006:
>
> This looks fine.
>
> 0007:
>
> +  int cmpGEP(const GEPOperator *GEP1, const GEPOperator *GEP2);
> +  int cmpGEP(const GetElementPtrInst *GEP1,
> +             const GetElementPtrInst *GEP2) {
>
> Const members?
>
> +  unsigned BitWidth = DL ? DL->getPointerSizeInBits(AS1) : 1;
> +  APInt Offset1(BitWidth, 0), Offset2(BitWidth, 0);
> +  if (DL &&
> +      (GEP1->accumulateConstantOffset(*DL, Offset1) &&
> +       GEP2->accumulateConstantOffset(*DL, Offset2)))
> +    return cmpAPInt(Offset1, Offset2);
>
> I think you should put this under if (DL) { /* declare Offset1, Offset2,
> etc. */ }
>
> 0008:
>
> +        // TODO: Already checked in cmpOps
>
> Did you mean "cmpOperation"?
>
> -    if (!enumerate(F1BB, F2BB) || !compare(F1BB, F2BB))
> +    if (!enumerate(F1BB, F2BB) || compare(F1BB, F2BB) != 0)
>
> "compare" still returns "bool". I'm going to assume this was meant to go
> in 0009.
>
> 0009:
>
> Looks fine.
>
> 0010: not reviewing this now.
>
> 0011:
>
> Yay!
>
> 0012:
>
> +  typedef std::set<FunctionPtr> FnTreeType;
>
> std::set is frowned upon, see
> http://llvm.org/docs/ProgrammersManual.html#set . Do you actually rely
> on the stable iterator guarantee? Or would another set-like container be
> a better fit?
>
> +  FunctionPtr(Function *_F, const DataLayout *_DL) : F(_F), DL(_DL) {}
>
> No. An identifier with underscore then capital letter is reserved for
> the implementation. You can just call them "F" and "DL", then the ctor
> initializer list can be "F(F), DL(DL)" and that will work fine.
>
> 0013:
>
> +// a ² a (reflexivity)
>
> I'm seeing a "squared" between the two 'a's. Could you represent this in
> plain ASCII?
>
> +// Comparison  iterates through each instruction in each basic block.
>
> Two spaces before "iterates" should be one.
>
> +// While ability to deal with complex references could be really
> perspective.
>
> Sorry, I'm not sure what this sentence means? The thing your example is
> talking about is showing is that two functions in an SCC may be
> equivalent, and detecting them requires "blinding" (ignoring) certain
> details of the function when you do the comparison. We blind ourselves
> to the pointee types, but not to callees of functions in the same SCC.
>
> 0014:
>
> Yay again!
>
>
> After reading through this patch series, I feel like I'm missing
> something important. Where's the sort function? It looks like we're
> still comparing all functions to all other functions.
>
> Nick
>




More information about the llvm-dev mailing list