Type Type::combine(bits_t newBits, Type other) const { static_assert(std::is_same<Oper, Union>::value || std::is_same<Oper, Intersect>::value, "Type::combine given unsupported template argument"); // If neither type is specialized, the result is simple. if (LIKELY(!isSpecialized() && !other.isSpecialized())) { return Type(newBits); } // If one of the types can't be specialized while the other is specialized, // preserve the specialization. if (!canSpecializeAny() || !other.canSpecializeAny()) { auto const specType = isSpecialized() ? specializedType() : other.specializedType(); // If the specialized type doesn't exist in newBits, drop the // specialization. if (newBits & specType.m_bits) return Type(newBits, specType.m_extra); return Type(newBits); } // If both types are eligible for the same kind of specialization and at // least one is specialized, delegate to Oper::combineSame. if (canSpecializeClass() && other.canSpecializeClass()) { folly::Optional<const Class*> aClass, bClass; if (getClass()) aClass = getClass(); if (other.getClass()) bClass = other.getClass(); return Oper::template combineSame<ClassOps>(newBits, kAnyObj, aClass, bClass); } if (canSpecializeArrayKind() && other.canSpecializeArrayKind()) { folly::Optional<ArrayData::ArrayKind> aKind, bKind; if (hasArrayKind()) aKind = getArrayKind(); if (other.hasArrayKind()) bKind = other.getArrayKind(); return Oper::template combineSame<ArrayOps>(newBits, kAnyArr, aKind, bKind); } // The types are eligible for different kinds of specialization and at least // one is specialized, so delegate to Oper::combineDifferent. return Oper::combineDifferent(newBits, *this, other); }
RuntimeType Type::toRuntimeType() const { assert(!isPtr()); auto const outer = isBoxed() ? KindOfRef : toDataType(); auto const inner = isBoxed() ? innerType().toDataType() : KindOfNone; auto rtt = RuntimeType{outer, inner}; if (isSpecialized()) { if (subtypeOf(Type::Arr)) { return rtt.setArrayKind(getArrayKind()); } else if (subtypeOf(Type::Obj)) { return rtt.setKnownClass(getClass()); } } return rtt; }
Type Type::operator-(Type other) const { auto const newBits = m_bits & ~other.m_bits; auto const spec1 = isSpecialized(); auto const spec2 = other.isSpecialized(); // The common easy case is when neither type is specialized. if (LIKELY(!spec1 && !spec2)) return Type(newBits); if (spec1 && spec2) { if (canSpecializeClass() != other.canSpecializeClass()) { // Both are specialized but in different ways. Our specialization is // preserved. return Type(newBits, m_extra); } // Subtracting different specializations of the same type could get messy // so we don't support it for now. always_assert(specializedType() == other.specializedType() && "Incompatible specialized types given to operator-"); // If we got here, both types have the same specialization, so it's removed // from the result. return Type(newBits); } // If masking out other's bits removed all of the bits that correspond to our // specialization, take it out. Otherwise, preserve it. if (spec1) { if (canSpecializeClass()) { if (!(newBits & kAnyObj)) return Type(newBits); return Type(newBits, m_class); } if (canSpecializeArrayKind()) { if (!(newBits & kAnyArr)) return Type(newBits); return Type(newBits, m_arrayKind); } not_reached(); } // Only other is specialized. This is where things get a little fuzzy. We // want to be able to support things like Obj - Obj<C> but we can't represent // Obj<~C>. We compromise and return Bottom in cases like this, which means // we need to be careful because (a - b) == Bottom doesn't imply a <= b in // this world. if (other.canSpecializeClass()) return Type(newBits & ~kAnyObj); return Type(newBits & ~kAnyArr); }
Type Type::operator-(Type other) const { auto const newBits = m_bits & ~other.m_bits; if (m_hasConstVal) { // If other is a constant of the same type, the result is Bottom or this // depending on whether or not it's the same constant. if (other.m_bits == m_bits && other.m_hasConstVal) { return other.m_extra == m_extra ? Bottom : *this; } // Otherwise, just check to see if the constant's type was removed in // newBits. return (newBits & m_bits) ? *this : Bottom; } // Rather than try to represent types like "all Ints except 24", treat t - // Int<24> as t - Int. other = other.dropConstVal(); auto const spec1 = isSpecialized(); auto const spec2 = other.isSpecialized(); // The common easy case is when neither type is specialized. if (LIKELY(!spec1 && !spec2)) return Type(newBits); if (spec1 && spec2) { if (canSpecializeClass() != other.canSpecializeClass()) { // Both are specialized but in different ways. Our specialization is // preserved. return Type(newBits, m_extra); } // Subtracting different specializations of the same type could get messy // so we don't support it for now. always_assert(specializedType() == other.specializedType() && "Incompatible specialized types given to operator-"); // If we got here, both types have the same specialization, so it's removed // from the result. return Type(newBits); } // If masking out other's bits removed all of the bits that correspond to our // specialization, take it out. Otherwise, preserve it. if (spec1) { if (canSpecializeClass()) { if (!(newBits & kAnyObj)) return Type(newBits); return Type(newBits, m_class); } if (canSpecializeArrayKind()) { if (!(newBits & kAnyArr)) return Type(newBits); return Type(newBits, getArrayKind()); } not_reached(); } // Only other is specialized. This is where things get a little fuzzy. We // want to be able to support things like Obj - Obj<C> but we can't represent // Obj<~C>. We compromise and return Bottom in cases like this, which means // we need to be careful because (a - b) == Bottom doesn't imply a <= b in // this world. if (other.canSpecializeClass()) return Type(newBits & ~kAnyObj); return Type(newBits & ~kAnyArr); }
bool Type::subtypeOf(Type t2) const { // First, check for any members in m_bits that aren't in t2.m_bits. if ((m_bits & t2.m_bits) != m_bits) return false; // If t2 is a constant, we must be the same constant or Bottom. if (t2.m_hasConstVal) { assert(!t2.isUnion()); return m_bits == kBottom || (m_hasConstVal && m_extra == t2.m_extra); } // If t2 is specialized, we must either not be eligible for the same kind of // specialization (Int <= {Int|Arr<Packed>}) or have a specialization that is // a subtype of t2's specialization. if (t2.isSpecialized()) { if (t2.canSpecializeClass()) { if (!isSpecialized()) return false; // Obj=A <: Obj=A // Obj<=A <: Obj<=A if (m_class.isExact() == t2.m_class.isExact() && getClass() == t2.getClass()) { return true; } // A <: B // ---------------- // Obj=A <: Obj<=B // Obj<=A <: Obj<=B if (!t2.m_class.isExact()) return getClass()->classof(t2.getClass()); return false; } assert(t2.canSpecializeArray()); if (!canSpecializeArray()) return true; if (!isSpecialized()) return false; // Both types are specialized Arr types. "Specialized" in this context // means it has at least one of a RepoAuthType::Array* or (const ArrayData* // or ArrayData::ArrayKind). We may return false erroneously in cases where // a 100% accurate comparison of the specializations would be prohibitively // expensive. if (m_arrayInfo == t2.m_arrayInfo) return true; auto rat1 = getArrayType(); auto rat2 = t2.getArrayType(); if (rat1 != rat2 && !(rat1 && !rat2)) { // Different rats are only ok if rat1 is present and rat2 isn't. It's // possible for one rat to be a subtype of another rat or array kind, but // checking that can be very expensive. return false; } auto kind1 = getOptArrayKind(); auto kind2 = t2.getOptArrayKind(); assert(kind1 || kind2); if (kind1 && !kind2) return true; if (kind2 && !kind1) return false; if (*kind1 != *kind2) return false; // Same kinds but we still have to check for const arrays. a <= b iff they // have the same const array or a has a const array and b doesn't. If they // have the same non-nullptr const array the m_arrayInfo check up above // should've triggered. auto const1 = isConst() ? arrVal() : nullptr; auto const2 = t2.isConst() ? t2.arrVal() : nullptr; assert((!const1 && !const2) || const1 != const2); return const1 == const2 || (const1 && !const2); } return true; }
void cgCheckType(IRLS& env, const IRInstruction* inst) { // Note: If you add new supported type checks, you should update // negativeCheckType() to indicate whether it is precise or not. auto const src = inst->src(0); auto const dst = inst->dst(); auto const srcData = srcLoc(env, inst, 0).reg(0); auto const srcType = srcLoc(env, inst, 0).reg(1); auto& v = vmain(env); auto const doJcc = [&] (ConditionCode cc, Vreg sf) { fwdJcc(v, env, ccNegate(cc), sf, inst->taken()); }; auto const doMov = [&] { auto const dstData = dstLoc(env, inst, 0).reg(0); auto const dstType = dstLoc(env, inst, 0).reg(1); if (dst->isA(TBool) && !src->isA(TBool)) { v << movtqb{srcData, dstData}; } else { v << copy{srcData, dstData}; } if (dstType == InvalidReg) return; if (srcType != InvalidReg) { v << copy{srcType, dstType}; } else { v << ldimmq{src->type().toDataType(), dstType}; } }; auto const typeParam = inst->typeParam(); if (src->isA(typeParam)) { // src is the target type or better. Just define our dst. doMov(); return; } if (!src->type().maybe(typeParam)) { // src is definitely not the target type. Always jump. v << jmp{label(env, inst->taken())}; return; } if (srcType != InvalidReg) { emitTypeTest(v, env, typeParam, srcType, srcData, v.makeReg(), doJcc); doMov(); return; } if (src->type() <= TBoxedCell && typeParam <= TBoxedCell) { // We should never have specific known Boxed types; those should only be // used for hints and predictions. always_assert(!(typeParam < TBoxedInitCell)); doMov(); return; } /* * See if we're just checking the array kind or object class of a value with * a mostly-known type. * * Important: We don't support typeParam being something like * StaticArr=kPackedKind unless the src->type() also already knows its * staticness. We do allow things like CheckType<Arr=Packed> t1:StaticArr, * though. This is why we have to check that the unspecialized type is at * least as big as the src->type(). */ if (typeParam.isSpecialized() && typeParam.unspecialize() >= src->type()) { detail::emitSpecializedTypeTest(v, env, typeParam, srcData, v.makeReg(), doJcc); doMov(); return; } /* * Since not all of our unions carry a type register, there are some * situations with strings and arrays that are neither constantly-foldable * nor in the emitTypeTest() code path. * * We currently actually check their persistent bit here, which will let * both static and uncounted strings through. Also note that * CheckType<Uncounted> t1:{Null|Str} doesn't get this treatment currently--- * the emitTypeTest() path above will only check the type register. */ if (!typeParam.isSpecialized() && typeParam <= TUncounted && src->type().subtypeOfAny(TStr, TArr) && src->type().maybe(typeParam)) { assertx(src->type().maybe(TPersistent)); auto const sf = v.makeReg(); v << cmplim{0, srcData[FAST_REFCOUNT_OFFSET], sf}; doJcc(CC_L, sf); doMov(); return; } always_assert_flog( false, "Bad src: {} and dst: {} types in '{}'", src->type(), typeParam, *inst ); }