in hphp/hhbbc/type-system.cpp [4327:4552]
Type union_of(Type a, Type b) {
auto const combined = a.bits() | b.bits();
auto const ham = a.m_ham | b.m_ham;
auto const nodata = [&] { return Type { combined, ham }; };
if (!a.hasData() && !b.hasData()) return nodata();
auto const reuse = [&] (Type& dst) {
dst.m_bits = combined;
dst.m_ham = ham;
assertx(dst.checkInvariants());
return std::move(dst);
};
// Check if the given DWaitHandle or DObj is a subtype of each
// other, returning the less specific type. Returns std::nullopt if
// neither of them is.
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(obj);
}
const DObj whDObj{ DObj::Sub, wh.m_data.dwh.cls };
if (subtypeObj<true>(obj.m_data.dobj, whDObj)) return reuse(wh);
if (subtypeObj<true>(whDObj, obj.m_data.dobj)) return reuse(obj);
return std::nullopt;
};
// If both sides have the same specialization, check if they are
// equal. If they are, reuse one of the sides. Otherwise if they are
// not, return nodata() (we assume the union of two unequal
// specializations is the full type). If only one side has a
// specialization, check if the other side has the support bits. If
// it does not, we can reuse the type with the specialization (since
// it's not being unioned with anything on the other
// side). Otherwise we have nodata(). If the union contains more
// than one set of support bits, we can never keep any
// specialization.
// For wait handles, if one is a specialized object try to determine
// if one of them is a subtype of the other. If not, demote the wait
// handle to a DObj and use that.
if (is_specialized_wait_handle(a)) {
if (is_specialized_wait_handle(b)) {
assertx(a.m_data.dwh.cls.same(b.m_data.dwh.cls));
auto& atype = a.m_data.dwh.inner;
auto& btype = b.m_data.dwh.inner;
if (atype->subtypeOf(*btype)) return reuse(b);
if (btype->subtypeOf(*atype)) return reuse(a);
auto u = union_of(*atype, *btype);
if (!u.strictSubtypeOf(BInitCell)) {
a = demote_wait_handle(std::move(a));
} else {
*atype.mutate() = std::move(u);
}
return reuse(a);
}
if (is_specialized_obj(b)) {
if (auto const t = whAndObj(a, b)) return *t;
a = demote_wait_handle(std::move(a));
} else {
if (b.couldBe(BObj) || !subtypeOf(combined, BObj | kNonSupportBits)) {
return nodata();
}
return reuse(a);
}
} else if (is_specialized_wait_handle(b)) {
if (is_specialized_obj(a)) {
if (auto const t = whAndObj(b, a)) return *t;
b = demote_wait_handle(std::move(b));
} else {
if (a.couldBe(BObj) || !subtypeOf(combined, BObj | kNonSupportBits)) {
return nodata();
}
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));
auto const& aobj = a.m_data.dobj;
auto const& bobj = b.m_data.dobj;
auto const isCtx = aobj.isCtx && bobj.isCtx;
if (subtypeObj<false>(aobj, bobj)) {
return setctx(reuse(b), isCtx);
}
if (subtypeObj<false>(bobj, aobj)) {
return setctx(reuse(a), isCtx);
}
if (auto const ancestor = aobj.cls.commonAncestor(bobj.cls)) {
// We need not to distinguish between Obj<=T and Obj=T, and
// always return an Obj<=Ancestor, because that is the single
// type that includes both children.
auto ret = subObj(*ancestor);
return setctx(reuse(ret), isCtx);
}
return nodata();
}
if (b.couldBe(BObj) || !subtypeOf(combined, BObj | kNonSupportBits)) {
return nodata();
}
return reuse(a);
} else if (is_specialized_obj(b)) {
if (a.couldBe(BObj) || !subtypeOf(combined, BObj | kNonSupportBits)) {
return nodata();
}
return reuse(b);
}
if (is_specialized_cls(a)) {
if (is_specialized_cls(b)) {
auto const& acls = a.m_data.dcls;
auto const& bcls = b.m_data.dcls;
auto const isCtx = acls.isCtx && bcls.isCtx;
if (subtypeCls<false>(acls, bcls)) {
return setctx(reuse(b), isCtx);
}
if (subtypeCls<false>(bcls, acls)) {
return setctx(reuse(a), isCtx);
}
if (auto const ancestor = acls.cls.commonAncestor(bcls.cls)) {
// Similar to above, this must always return an Obj<=Ancestor.
auto ret = subCls(*ancestor);
return setctx(reuse(ret), isCtx);
}
return nodata();
}
if (b.couldBe(BCls) || !subtypeOf(combined, BCls | kNonSupportBits)) {
return nodata();
}
return reuse(a);
} else if (is_specialized_cls(b)) {
if (a.couldBe(BCls) || !subtypeOf(combined, BCls | kNonSupportBits)) {
return nodata();
}
return reuse(b);
}
if (is_specialized_array_like(a)) {
if (is_specialized_array_like(b)) {
if (a.dualDispatchDataFn(b, DualDispatchSubtype<true>{})) return reuse(b);
if (b.dualDispatchDataFn(a, DualDispatchSubtype<true>{})) return reuse(a);
return a.dualDispatchDataFn(b, DualDispatchUnion{ combined, ham });
}
if (!subtypeOf(combined, BArrLikeN | kNonSupportBits)) return nodata();
if (!b.couldBe(BArrLikeN)) return reuse(a);
if (b.dispatchArrLikeNone(a, DualDispatchSubtype<true>{})) return reuse(a);
return b.dispatchArrLikeNone(a, DualDispatchUnion{ combined, ham });
} else if (is_specialized_array_like(b)) {
if (!subtypeOf(combined, BArrLikeN | kNonSupportBits)) return nodata();
if (!a.couldBe(BArrLikeN)) return reuse(b);
if (a.dispatchArrLikeNone(b, DualDispatchSubtype<true>{})) return reuse(b);
return a.dispatchArrLikeNone(b, DualDispatchUnion{ combined, ham });
}
if (is_specialized_string(a)) {
if (is_specialized_string(b)) {
if (a.m_data.sval == b.m_data.sval) return reuse(a);
return nodata();
}
if (b.couldBe(BStr) || !subtypeOf(combined, BStr | kNonSupportBits)) {
return nodata();
}
return reuse(a);
} else if (is_specialized_string(b)) {
if (a.couldBe(BStr) || !subtypeOf(combined, BStr | kNonSupportBits)) {
return nodata();
}
return reuse(b);
}
if (is_specialized_lazycls(a)) {
if (is_specialized_lazycls(b)) {
if (a.m_data.lazyclsval == b.m_data.lazyclsval) return reuse(a);
return nodata();
}
if (b.couldBe(BLazyCls) ||
!subtypeOf(combined, BLazyCls | kNonSupportBits)) {
return nodata();
}
return reuse(a);
} else if (is_specialized_lazycls(b)) {
if (a.couldBe(BLazyCls) ||
!subtypeOf(combined, BLazyCls | kNonSupportBits)) {
return nodata();
}
return reuse(b);
}
if (is_specialized_int(a)) {
if (is_specialized_int(b)) {
if (a.m_data.ival == b.m_data.ival) return reuse(a);
return nodata();
}
if (b.couldBe(BInt) || !subtypeOf(combined, BInt | kNonSupportBits)) {
return nodata();
}
return reuse(a);
} else if (is_specialized_int(b)) {
if (a.couldBe(BInt) || !subtypeOf(combined, BInt | kNonSupportBits)) {
return nodata();
}
return reuse(b);
}
if (is_specialized_double(a)) {
if (is_specialized_double(b)) {
if (double_equals(a.m_data.dval, b.m_data.dval)) return reuse(a);
return nodata();
}
if (b.couldBe(BDbl) || !subtypeOf(combined, BDbl | kNonSupportBits)) {
return nodata();
}
return reuse(a);
} else if (is_specialized_double(b)) {
if (a.couldBe(BDbl) || !subtypeOf(combined, BDbl | kNonSupportBits)) {
return nodata();
}
return reuse(b);
}
always_assert(false);
}