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();
}