Type intersection_of()

in hphp/hhbbc/type-system.cpp [4059:4325]


Type intersection_of(Type a, Type b) {
  using HPHP::HHBBC::couldBe;

  auto isect = a.bits() & b.bits();
  if (!isect) return TBottom;

  auto ham = a.m_ham.project(isect) & b.m_ham.project(isect);
  if (ham.isBottom(isect)) return TBottom;

  // Return intersected type without a specialization.
  auto const nodata = [&] {
    assertx(isect);
    return Type { isect, ham };
  };
  // If the intersection cannot support a specialization, there's no
  // need to check them.
  if (!couldBe(isect, kSupportBits)) return nodata();
  if (!a.hasData() && !b.hasData()) return nodata();

  // Return intersected type re-using the specialization in the given
  // type. Update the bits to match the intersection.
  auto const reuse = [&] (Type& dst) {
    dst.m_bits = isect;
    dst.m_ham = ham;
    assertx(dst.checkInvariants());
    return std::move(dst);
  };

  // Try to determine if the given wait-handle and given specialized
  // object are a subtype of either. If so, return the (reused) more
  // specific type. Return std::nullopt if neither are.
  auto const whAndObj = [&] (Type& wh, Type& obj) -> Optional<Type> {
    if (obj.m_data.dobj.type == DObj::Sub &&
        obj.m_data.dobj.cls.same(wh.m_data.dwh.cls) &&
        !obj.m_data.dobj.isCtx) {
      return reuse(wh);
    }
    const DObj whDObj{ DObj::Sub, wh.m_data.dwh.cls };
    if (subtypeObj<true>(obj.m_data.dobj, whDObj)) return reuse(obj);
    if (subtypeObj<true>(whDObj, obj.m_data.dobj)) return reuse(wh);

    if (obj.m_data.dobj.type == DObj::Sub &&
        obj.m_data.dobj.cls.couldBeInterface()) return reuse(wh);

    return std::nullopt;
  };

  // If both sides have matching specialized data, check if the
  // specializations match. If so, reuse one of the sides. If not, the
  // supported bits for this data cannot be present in the
  // intersection, so remove it. If this makes the intersection
  // TBottom, we're done. Otherwise, just return nodata() (the
  // intersection cannot possibly have any specialized data because
  // each side can only have one and we already determined they don't
  // match). If only one side has a specialization, check if that
  // specialization is supported by the intersection. If so, we can
  // keep it. If not, ignore it and check for other specialized
  // types. Note that this means the order of checking for
  // specializations determines the priority of which specializations
  // to keep when there's a mismatch.

  // For WaitHandles, first check if one is potentially a subtype of
  // the other (even if one is a TObj). If not, demote the wait handle
  // to an object specialization and fall into the below object
  // specialization checks.
  if (is_specialized_wait_handle(a)) {
    if (is_specialized_wait_handle(b)) {
      assertx(couldBe(isect, BObj));
      assertx(a.m_data.dwh.cls.same(b.m_data.dwh.cls));
      if (a.m_data.dwh.inner->subtypeOf(*b.m_data.dwh.inner)) return reuse(a);
      if (b.m_data.dwh.inner->subtypeOf(*a.m_data.dwh.inner)) return reuse(b);

      auto i = intersection_of(*a.m_data.dwh.inner, *b.m_data.dwh.inner);
      if (!i.is(BBottom)) {
        if (i.strictSubtypeOf(BInitCell)) {
          *a.m_data.dwh.inner.mutate() = std::move(i);
          return reuse(a);
        }
        a = demote_wait_handle(std::move(a));
        return reuse(a);
      }

      isect &= ~BObj;
      return isect ? nodata() : TBottom;
    }
    if (is_specialized_obj(b)) {
      assertx(couldBe(isect, BObj));
      if (auto const t = whAndObj(a, b)) return *t;
      a = demote_wait_handle(std::move(a));
    } else if (couldBe(isect, BObj)) {
      return reuse(a);
    }
  } else if (is_specialized_wait_handle(b)) {
    if (is_specialized_obj(a)) {
      assertx(couldBe(isect, BObj));
      if (auto const t = whAndObj(b, a)) return *t;
      b = demote_wait_handle(std::move(b));
    } else if (couldBe(isect, BObj)) {
      return reuse(b);
    }
  }

  if (is_specialized_obj(a)) {
    if (is_specialized_obj(b)) {
      assertx(!is_specialized_wait_handle(a));
      assertx(!is_specialized_wait_handle(b));
      assertx(couldBe(isect, BObj));
      auto const ctx = a.m_data.dobj.isCtx || b.m_data.dobj.isCtx;
      if (subtypeObj<false>(a.m_data.dobj, b.m_data.dobj)) {
        return setctx(reuse(a), ctx);
      }
      if (subtypeObj<false>(b.m_data.dobj, a.m_data.dobj)) {
        return setctx(reuse(b), ctx);
      }

      if (a.m_data.dobj.type == DObj::Sub &&
          b.m_data.dobj.type == DObj::Sub) {
        if (a.m_data.dobj.cls.couldBeInterface()) {
          if (!b.m_data.dobj.cls.couldBeInterface()) {
            return setctx(reuse(b), ctx);
          } else {
            return nodata();
          }
        } else if (b.m_data.dobj.cls.couldBeInterface()) {
          return setctx(reuse(a), ctx);
        }
      }

      isect &= ~BObj;
      return isect ? nodata() : TBottom;
    }
    if (couldBe(isect, BObj)) return reuse(a);
  } else if (is_specialized_obj(b)) {
    if (couldBe(isect, BObj)) return reuse(b);
  }

  if (is_specialized_cls(a)) {
    if (is_specialized_cls(b)) {
      assertx(couldBe(isect, BCls));
      auto const ctx = a.m_data.dcls.isCtx || b.m_data.dcls.isCtx;
      if (subtypeCls<false>(a.m_data.dcls, b.m_data.dcls)) {
        return setctx(reuse(a), ctx);
      }
      if (subtypeCls<false>(b.m_data.dcls, a.m_data.dcls)) {
        return setctx(reuse(b), ctx);
      }
      isect &= ~BCls;
      return isect ? nodata() : TBottom;
    }
    if (couldBe(isect, BCls)) return reuse(a);
  } else if (is_specialized_cls(b)) {
    if (couldBe(isect, BCls)) return reuse(b);
  }

  // Attempt to re-use t or intersect it with the isect bits.
  auto const adjustArrSpec = [&] (Type& t) {
    if (t.m_dataTag == DataTag::ArrLikeVal) {
      // ArrLikeVal doesn't require any intersection
      if (t.couldBe(BArrLikeE) && !couldBe(isect, BArrLikeE)) {
        // If the intersection stripped empty bits off leaving just a
        // ArrLikeVal, we need to restore the precise HAMSandwich.
        ham = HAMSandwich::FromSArr(t.m_data.aval);
      }
      return reuse(t);
    }
    // Optimization: if t's existing trep is the same as the
    // intersection (array wise), the intersection won't modify
    // anything, so we can skip it.
    if ((t.bits() & BArrLikeN) == (isect & BArrLikeN)) {
      return reuse(t);
    }
    // Otherwise we can't reuse t and must perform the intersection.
    return Type{isect}.dispatchArrLikeNone(
      t, DualDispatchIntersection{ isect, ham }
    );
  };

  // Arrays need more care because we have to intersect the
  // specialization with a DArrNone to constrain the specialization
  // types appropriately.
  if (is_specialized_array_like(a)) {
    if (is_specialized_array_like(b)) {
      assertx(couldBe(isect, BArrLikeN));
      auto const i = [&] {
        if (a.dualDispatchDataFn(b, DualDispatchSubtype<true>{})) {
          return adjustArrSpec(a);
        }
        if (b.dualDispatchDataFn(a, DualDispatchSubtype<true>{})) {
          return adjustArrSpec(b);
        }
        return a.dualDispatchDataFn(
          b,
          DualDispatchIntersection{ isect, ham }
        );
      }();
      if (!i.is(BBottom)) return i;
      isect &= ~BArrLikeN;
      ham = ham.project(isect);
      return isect ? nodata() : TBottom;
    }
    if (couldBe(isect, BArrLikeN)) {
      auto const i = adjustArrSpec(a);
      if (!i.is(BBottom)) return i;
      isect &= ~BArrLikeN;
      ham = ham.project(isect);
      return isect ? nodata() : TBottom;
    }
  } else if (is_specialized_array_like(b)) {
    if (couldBe(isect, BArrLikeN)) {
      auto const i = adjustArrSpec(b);
      if (!i.is(BBottom)) return i;
      isect &= ~BArrLikeN;
      ham = ham.project(isect);
      return isect ? nodata() : TBottom;
    }
  }

  if (is_specialized_string(a)) {
    if (is_specialized_string(b)) {
      assertx(couldBe(isect, BStr));
      if (a.m_data.sval == b.m_data.sval) return reuse(a);
      isect &= ~BStr;
      return isect ? nodata() : TBottom;
    }
    if (couldBe(isect, BStr)) return reuse(a);
  } else if (is_specialized_string(b)) {
    if (couldBe(isect, BStr)) return reuse(b);
  }

  if (is_specialized_lazycls(a)) {
    if (is_specialized_lazycls(b)) {
      assertx(couldBe(isect, BLazyCls));
      if (a.m_data.lazyclsval == b.m_data.lazyclsval) return reuse(a);
      isect &= ~BLazyCls;
      return isect ? nodata() : TBottom;
    }
    if (couldBe(isect, BLazyCls)) return reuse(a);
  } else if (is_specialized_lazycls(b)) {
    if (couldBe(isect, BLazyCls)) return reuse(b);
  }

  if (is_specialized_int(a)) {
    if (is_specialized_int(b)) {
      assertx(couldBe(isect, BInt));
      if (a.m_data.ival == b.m_data.ival) return reuse(a);
      isect &= ~BInt;
      return isect ? nodata() : TBottom;
    }
    if (couldBe(isect, BInt)) return reuse(a);
  } else if (is_specialized_int(b)) {
    if (couldBe(isect, BInt)) return reuse(b);
  }

  if (is_specialized_double(a)) {
    if (is_specialized_double(b)) {
      assertx(couldBe(isect, BDbl));
      if (double_equals(a.m_data.dval, b.m_data.dval)) return reuse(a);
      isect &= ~BDbl;
      return isect ? nodata() : TBottom;
    }
    if (couldBe(isect, BDbl)) return reuse(a);
  } else if (is_specialized_double(b)) {
    if (couldBe(isect, BDbl)) return reuse(b);
  }

  return nodata();
}