GuardConstraint relaxConstraint(GuardConstraint origGc, Type knownType, Type toRelax) { ITRACE(4, "relaxConstraint({}, knownType = {}, toRelax = {})\n", origGc, knownType, toRelax); Trace::Indent _i; // AssertType can be given TCtx, which should never relax. if (toRelax.maybe(TCctx)) { always_assert(toRelax <= TCtx); return origGc; } auto const dstType = knownType & toRelax; always_assert_flog(typeFitsConstraint(dstType, origGc), "refine({}, {}) doesn't fit {}", knownType, toRelax, origGc); // Preserve origGc's weak property. GuardConstraint newGc{DataTypeGeneric}; newGc.weak = origGc.weak; while (true) { if (newGc.isSpecialized()) { // We need to ask for the right kind of specialization, so grab it from // origGc. if (origGc.wantArrayKind()) newGc.setWantArrayKind(); if (origGc.wantClass()) newGc.setDesiredClass(origGc.desiredClass()); } auto const relaxed = relaxType(toRelax, newGc.category); auto const newDstType = relaxed & knownType; if (typeFitsConstraint(newDstType, origGc)) break; ITRACE(5, "newDstType = {}, newGc = {}; incrementing constraint\n", newDstType, newGc); incCategory(newGc.category); } // DataTypeCountness can be relaxed to DataTypeGeneric in // optimizeProfiledGuards, so we can't rely on this category to give type // information through guards. Since relaxConstraint is used to relax the // DataTypeCategory for guards, we cannot return DataTypeCountness unless we // already had it to start with. Instead, we return DataTypeBoxCountness, // which won't be further relaxed by optimizeProfiledGuards. if (newGc.category == DataTypeCountness && origGc != DataTypeCountness) { newGc.category = DataTypeBoxAndCountness; } ITRACE(4, "Returning {}\n", newGc); // newGc shouldn't be any more specific than origGc. always_assert(newGc.category <= origGc.category); return newGc; }
/* * Returns true iff t is specific enough to fit cat. */ bool typeFitsConstraint(Type t, DataTypeCategory cat) { switch (cat) { case DataTypeGeneric: return true; case DataTypeCountness: // Consumers using this constraint are probably going to decref the // value, so it's ok if we know whether t is counted or not. Arr and Str // are special cased because we don't guard on staticness for them. return t.notCounted() || t <= (Type::Counted | Type::StaticArr | Type::StaticStr); case DataTypeCountnessInit: return typeFitsConstraint(t, DataTypeCountness) && (t <= Type::Uninit || t.not(Type::Uninit)); case DataTypeSpecific: return t.isKnownDataType(); case DataTypeSpecialized: return t.isSpecialized(); } not_reached(); }
void RegionDesc::Block::addPreCondition(const GuardedLocation& locGuard) { FTRACE(2, "Block::addPreCondition({})\n", show(locGuard)); assertx(locGuard.type != TBottom); assertx(locGuard.type <= TStkElem); assertx(locGuard.type.isSpecialized() || typeFitsConstraint(locGuard.type, locGuard.category)); m_typePreConditions.push_back(locGuard); }
/* * relaxConstraint returns the least specific TypeConstraint 'tc' that doesn't * prevent the intersection of knownType and relaxType(toRelax, tc) from * satisfying origTc. It is used in IRBuilder::constrainValue and * IRBuilder::constrainStack to determine how to constrain the typeParam and * src values of CheckType/CheckStk instructions, and the src values of * AssertType/AssertStk instructions. * * AssertType example: * t24:Obj<C> = AssertType<{Obj<C>|InitNull}> t4:Obj * * If constrainValue is called with (t24, DataTypeSpecialized), relaxConstraint * will be called with (DataTypeSpecialized, Obj<C>|InitNull, Obj). After a few * iterations it will determine that constraining Obj with DataTypeCountness * will still allow the result type of the AssertType instruction to satisfy * DataTypeSpecialized, because relaxType(Obj, DataTypeCountness) == Obj. */ TypeConstraint relaxConstraint(const TypeConstraint origTc, const Type knownType, const Type toRelax) { ITRACE(4, "relaxConstraint({}, knownType = {}, toRelax = {})\n", origTc, knownType, toRelax); Trace::Indent _i; auto const dstType = refineType(knownType, toRelax); always_assert_flog(typeFitsConstraint(dstType, origTc), "refine({}, {}) doesn't fit {}", knownType, toRelax, origTc); // Preserve origTc's weak property. TypeConstraint newTc{DataTypeGeneric, DataTypeGeneric}; newTc.weak = origTc.weak; while (true) { if (newTc.isSpecialized()) { // We need to ask for the right kind of specialization, so grab it from // origTc. if (origTc.wantArrayKind()) newTc.setWantArrayKind(); if (origTc.wantClass()) newTc.setDesiredClass(origTc.desiredClass()); } auto const relaxed = relaxType(toRelax, newTc); auto const newDstType = refineType(relaxed, knownType); if (typeFitsConstraint(newDstType, origTc)) break; ITRACE(5, "newDstType = {}, newTc = {}; ", newDstType, newTc); if (newTc.category == DataTypeGeneric || !typeFitsOuterConstraint(newDstType, origTc)) { FTRACE(5, "incrementing outer\n"); incCategory(newTc.category); } else if (!typeFitsInnerConstraint(newDstType, origTc)) { FTRACE(5, "incrementing inner\n"); incCategory(newTc.innerCat); } else { not_reached(); } } ITRACE(4, "Returning {}\n", newTc); // newTc shouldn't be any more specific than origTc. always_assert(newTc.category <= origTc.category && newTc.innerCat <= origTc.innerCat); return newTc; }
/* * Returns true iff a guard to constrain was found, and tc was more specific * than the guard's existing constraint. Note that this doesn't necessarily * mean that the guard was constrained: tc.weak might be true. */ bool IRBuilder::constrainGuard(IRInstruction* inst, TypeConstraint tc) { if (!shouldConstrainGuards()) return false; auto& guard = m_guardConstraints[inst]; auto changed = false; auto const assertFits = typeFitsConstraint(guard.assertedType, tc); ITRACE(2, "constrainGuard({}, {}): existing constraint {}, assertFits: {}\n", *inst, tc, guard, assertFits ? "true" : "false"); Indent _i; // For category and innerCat, constrain the guard if the assertedType isn't // strong enough to fit what we want and tc is more specific than the // existing category. if (!assertFits && tc.innerCat > guard.innerCat) { if (!tc.weak) { ITRACE(1, "constraining inner type of {}: {} -> {}\n", *inst, guard.innerCat, tc.innerCat); guard.innerCat = tc.innerCat; } changed = true; } else { ITRACE(2, "not constraining innerCat\n"); } if (!assertFits && tc.category > guard.category) { if (!tc.weak) { ITRACE(1, "constraining {}: {} -> {}\n", *inst, guard.category, tc.category); guard.category = tc.category; } changed = true; } else { ITRACE(2, "not constraining category\n"); } // It's fairly common to have a local that we've asserted to be Obj, and then // later assert that it's Obj<C>|InitNull. We want to use their intersection, // so in this case we'd assert Obj<C>. always_assert(tc.assertedType.maybe(guard.assertedType)); auto assertCommon = tc.assertedType & guard.assertedType; if (assertCommon < guard.assertedType) { // We don't check tc.weak here because assertedType is supposed to be // statically known type information. ITRACE(1, "using {} to refine assertedType of {}: {} -> {}\n", tc.assertedType, *inst, guard.assertedType, assertCommon); guard.assertedType = assertCommon; } else { ITRACE(2, "not refining assertedType\n"); } return changed; }
void TraceBuilder::constrainLocal(uint32_t locId, SSATmp* valSrc, DataTypeCategory cat, const std::string& why) { FTRACE(1, "constrainLocal({}, {}, {}, {})\n", locId, valSrc ? valSrc->inst()->toString() : "null", cat, why); if (!valSrc) return; if (!valSrc->isA(Type::FramePtr)) { constrainValue(valSrc, cat); return; } // When valSrc is a FramePtr, that means we loaded the value the local had // coming into the trace. Trace through the FramePtr chain, looking for a // guard for this local id. If we find it, constrain the guard. If we don't // find it, there wasn't a guard for this local so there's nothing to // constrain. for (auto fpInst = valSrc->inst(); fpInst->op() != DefFP && fpInst->op() != DefInlineFP; fpInst = fpInst->src(0)->inst()) { FTRACE(2, " - fp = {}\n", *fpInst); assert(fpInst->dst()->isA(Type::FramePtr)); auto instLoc = [fpInst]{ return fpInst->extra<LocalId>()->locId; }; switch (fpInst->op()) { case GuardLoc: case CheckLoc: if (instLoc() != locId) break; FTRACE(2, " - found guard to constrain\n"); constrainGuard(fpInst, cat); return; case AssertLoc: if (instLoc() != locId) break; // If the refined the type of the local satisfies the constraint we're // trying to apply, we can stop here. This can happen if we assert a // more general type than what we already know. Otherwise we need to // keep tracing back to the guard. if (typeFitsConstraint(fpInst->typeParam(), cat)) return; break; case FreeActRec: always_assert(0 && "Attempt to read a local after freeing its frame"); default: not_reached(); } } FTRACE(2, " - no guard to constrain\n"); }
/* * Returns true iff t is specific enough to fit tc, meaning a consumer * constraining a value with tc would be satisfied with t as the value's type * after relaxation. */ bool typeFitsConstraint(Type t, TypeConstraint tc) { assert(t != Type::Bottom); if (tc.innerCat > DataTypeGeneric) { // First check the outer constraint. if (!typeFitsConstraint(t, tc.category)) return false; // Then, if t might be boxed, check the inner type. return t.notBoxed() || typeFitsConstraint((t & Type::BoxedCell).innerType(), tc.innerCat); } switch (tc.category) { case DataTypeGeneric: return true; case DataTypeCountness: // Consumers using this constraint are probably going to decref the // value, so it's ok if we know whether t is counted or not. Arr and Str // are special cased because we don't guard on staticness for them. return t.notCounted() || t <= (Type::Counted | Type::StaticArr | Type::StaticStr); case DataTypeCountnessInit: return typeFitsConstraint(t, DataTypeCountness) && (t <= Type::Uninit || t.not(Type::Uninit)); case DataTypeSpecific: return t.isKnownDataType(); case DataTypeSpecialized: return t.isSpecialized(); } not_reached(); }
/* * Check invariants about the metadata for this Block. * * 1. Each SrcKey in m_typePredictions, m_preConditions, m_byRefs, m_refPreds, * and m_knownFuncs is within the bounds of the block. * * 2. Each local id referred to in the type prediction list is valid. * * 3. (Unchecked) each stack offset in the type prediction list is valid. */ void RegionDesc::Block::checkMetadata() const { auto rangeCheck = [&](const char* type, Offset o) { if (o < m_start || o > m_last) { std::cerr << folly::format("{} at {} outside range [{}, {}]\n", type, o, m_start, m_last); assertx(!"Region::Block contained out-of-range metadata"); } }; auto checkTypedLocations = [&](const char* msg, const TypedLocVec& vec) { for (auto& typedLoc : vec) { auto& loc = typedLoc.location; switch (loc.tag()) { case LTag::Local: assertx(loc.localId() < m_func->numLocals()); break; case LTag::Stack: break; } } }; auto checkGuardedLocations = [&](const char* msg, const GuardedLocVec& vec) { for (auto& guardedLoc : vec) { assertx(guardedLoc.type.isSpecialized() || typeFitsConstraint(guardedLoc.type, guardedLoc.category)); auto& loc = guardedLoc.location; switch (loc.tag()) { case LTag::Local: assertx(loc.localId() < m_func->numLocals()); break; case LTag::Stack: break; } } }; checkTypedLocations("type prediction", m_typePredictions); checkGuardedLocations("type precondition", m_typePreConditions); for (auto& byRef : m_byRefs) { rangeCheck("parameter reference flag", byRef.first.offset()); } for (auto& func : m_knownFuncs) { rangeCheck("known Func*", func.first.offset()); } }
bool typeFitsConstraint(Type t, GuardConstraint gc) { switch (gc.category) { case DataTypeGeneric: return true; case DataTypeCountness: case DataTypeBoxAndCountness: // Consumers using this constraint expect the type to be relaxed to // Uncounted or left alone, so something like Arr|Obj isn't specific // enough. return !t.maybe(TCounted) || t.subtypeOfAny(TStr, TArr, TVec, TDict, TKeyset, TObj, TRes, TBoxedCell); case DataTypeBoxAndCountnessInit: return typeFitsConstraint(t, DataTypeBoxAndCountness) && (t <= TUninit || !t.maybe(TUninit)); case DataTypeSpecific: return t.isKnownDataType(); case DataTypeSpecialized: // Type::isSpecialized() returns true for types like {Arr<Packed>|Int} // and Arr has non-specialized subtypes, so we require that t is // specialized, a strict subtype of Obj or Arr, and that it fits the // specific requirements of gc. assertx(gc.wantClass() ^ gc.wantArrayKind()); if (t < TObj && t.clsSpec()) { return gc.wantClass() && t.clsSpec().cls()->classof(gc.desiredClass()); } if (t < TArr && t.arrSpec()) { auto arrSpec = t.arrSpec(); if (gc.wantArrayKind() && !arrSpec.kind()) return false; return true; } return false; } not_reached(); }
bool typeFitsConstraint(Type t, TypeConstraint tc) { always_assert(t != Type::Bottom); switch (tc.category) { case DataTypeGeneric: return true; case DataTypeCountness: // Consumers using this constraint expect the type to be relaxed to // Uncounted or left alone, so something like Arr|Obj isn't specific // enough. return t.notCounted() || t.subtypeOfAny(Type::Str, Type::Arr, Type::Obj, Type::Res, Type::BoxedCell); case DataTypeCountnessInit: return typeFitsConstraint(t, DataTypeCountness) && (t <= Type::Uninit || t.not(Type::Uninit)); case DataTypeSpecific: return t.isKnownDataType(); case DataTypeSpecialized: // Type::isSpecialized() returns true for types like {Arr<Packed>|Int} // and Arr has non-specialized subtypes, so we require that t is // specialized, a strict subtype of Obj or Arr, and that it fits the // specific requirements of tc. assert(tc.wantClass() ^ tc.wantArrayKind()); if (!t.isSpecialized()) return false; if (t < Type::Obj) { return tc.wantClass() && t.getClass()->classof(tc.desiredClass()); } if (t < Type::Arr) { return tc.wantArrayKind() && t.hasArrayKind(); } return false; } not_reached(); }
bool IRBuilder::constrainLocal(uint32_t locId, SSATmp* typeSrc, TypeConstraint tc, const std::string& why) { if (!shouldConstrainGuards()) return false; always_assert(IMPLIES(tc.innerCat > DataTypeGeneric, tc.category >= DataTypeCountness)); ITRACE(1, "constrainLocal({}, {}, {}, {})\n", locId, typeSrc ? typeSrc->inst()->toString() : "null", tc, why); Indent _i; if (!typeSrc) return false; if (!typeSrc->isA(Type::FramePtr)) { return constrainValue(typeSrc, tc); } // When typeSrc is a FramePtr, that means we loaded the value the local had // coming into the trace. Trace through the FramePtr chain, looking for a // guard for this local id. If we find it, constrain the guard. If we don't // find it, there wasn't a guard for this local so there's nothing to // constrain. auto guard = guardForLocal(locId, typeSrc); while (guard) { if (guard->is(AssertLoc)) { // If the refined type of the local satisfies the constraint we're // trying to apply, we can stop here. This can happen if we assert a // more general type than what we already know. Otherwise we need to // keep tracing back to the guard. if (typeFitsConstraint(guard->typeParam(), tc)) return false; guard = guardForLocal(locId, guard->src(0)); } else { assert(guard->is(GuardLoc, CheckLoc)); ITRACE(2, "found guard to constrain\n"); return constrainGuard(guard, tc); } } ITRACE(2, "no guard to constrain\n"); return false; }
void TraceBuilder::constrainLocal(uint32_t locId, SSATmp* valSrc, TypeConstraint tc, const std::string& why) { if (!shouldConstrainGuards()) return; FTRACE(1, "constrainLocal({}, {}, {}, {})\n", locId, valSrc ? valSrc->inst()->toString() : "null", tc, why); if (!valSrc) return; if (!valSrc->isA(Type::FramePtr)) { constrainValue(valSrc, tc); return; } // When valSrc is a FramePtr, that means we loaded the value the local had // coming into the trace. Trace through the FramePtr chain, looking for a // guard for this local id. If we find it, constrain the guard. If we don't // find it, there wasn't a guard for this local so there's nothing to // constrain. auto guard = guardForLocal(locId, valSrc); while (guard) { if (guard->is(AssertLoc)) { // If the refined the type of the local satisfies the constraint we're // trying to apply, we can stop here. This can happen if we assert a // more general type than what we already know. Otherwise we need to // keep tracing back to the guard. if (typeFitsConstraint(guard->typeParam(), tc.category)) return; guard = guardForLocal(locId, guard->src(0)); } else { assert(guard->is(GuardLoc, AssertLoc)); FTRACE(2, " - found guard to constrain\n"); constrainGuard(guard, tc); return; } } FTRACE(2, " - no guard to constrain\n"); }
/** * Trace back to the guard that provided the type of val, if * any. Constrain it so its type will not be relaxed beyond the given * DataTypeCategory. Returns true iff one or more guard instructions * were constrained. */ bool IRBuilder::constrainValue(SSATmp* const val, TypeConstraint tc) { if (!shouldConstrainGuards()) return false; if (!val) { FTRACE(1, "constrainValue(nullptr, {}), bailing\n", tc); return false; } FTRACE(1, "constrainValue({}, {})\n", *val->inst(), tc); auto inst = val->inst(); if (inst->is(LdLoc, LdLocAddr)) { // We've hit a LdLoc(Addr). If the source of the value is non-null and not // a FramePtr, it's a real value that was killed by a Call. The value won't // be live but it's ok to use it to track down the guard. auto source = inst->extra<LocalData>()->typeSrc; if (!source) { // val was newly created in this trace. Nothing to constrain. FTRACE(2, " - typeSrc is null, bailing\n"); return false; } // If valSrc is a FramePtr, it represents the frame the value was // originally loaded from. Look for the guard for this local. if (source->isA(Type::FramePtr)) { return constrainLocal(inst->extra<LocalId>()->locId, source, tc, "constrainValue"); } // Otherwise, keep chasing down the source of val. return constrainValue(source, tc); } else if (inst->is(LdStack, LdStackAddr)) { return constrainStack(inst->src(0), inst->extra<StackOffset>()->offset, tc); } else if (inst->is(CheckType, AssertType)) { // If the dest type of the instruction fits the constraint we want, we can // stop here without constraining any further. Otherwise, continue through // to the source. auto changed = false; if (inst->is(CheckType)) changed = constrainGuard(inst, tc) || changed; auto dstType = inst->typeParam(); if (!typeFitsConstraint(dstType, tc.category)) { changed = constrainValue(inst->src(0), tc) || changed; } return changed; } else if (inst->is(StRef)) { // StRef requires that src(0) is boxed so we're relying on callers to // appropriately constrain the values they pass to it. Any innerCat in tc // should be applied to the value being stored. tc.category = tc.innerCat; tc.innerCat = DataTypeGeneric; tc.assertedType = Type::Gen; return constrainValue(inst->src(1), tc); } else if (inst->is(Box, BoxPtr, Unbox, UnboxPtr)) { // All Box/Unbox opcodes are similar to StRef/LdRef in some situations and // Mov in others (determined at runtime), so we need to constrain both // outer and inner. auto maxCat = std::max(tc.category, tc.innerCat); tc.category = maxCat; tc.innerCat = maxCat; tc.assertedType = Type::Gen; return constrainValue(inst->src(0), tc); } else if (inst->is(LdRef)) { // Like StRef, we're relying on the caller to have appropriately // constrained the outer type of the box. Constrain the inner type of the // box with tc. tc.innerCat = tc.category; tc.category = DataTypeGeneric; tc.assertedType = Type::Gen; return constrainValue(inst->src(0), tc); } else if (inst->isPassthrough()) { return constrainValue(inst->getPassthroughValue(), tc); } else { // Any instructions not special cased above produce a new value, so // there's no guard for us to constrain. FTRACE(2, " - value is new in this trace, bailing\n"); return false; } // TODO(t2598894): Should be able to do something with LdMem<T> here }
/** * Trace back to the guard that provided the type of val, if * any. Constrain it so its type will not be relaxed beyond the given * DataTypeCategory. Returns true iff one or more guard instructions * were constrained. */ bool TraceBuilder::constrainValue(SSATmp* const val, TypeConstraint tc) { if (!shouldConstrainGuards()) return false; if (!val) { FTRACE(1, "constrainValue(nullptr, {}), bailing\n", tc); return false; } FTRACE(1, "constrainValue({}, {})\n", *val->inst(), tc); auto inst = val->inst(); if (inst->is(LdLoc, LdLocAddr)) { // We've hit a LdLoc(Addr). If the source of the value is non-null and not // a FramePtr, it's a real value that was killed by a Call. The value won't // be live but it's ok to use it to track down the guard. auto source = inst->extra<LocalData>()->valSrc; if (!source) { // val was newly created in this trace. Nothing to constrain. FTRACE(2, " - valSrc is null, bailing\n"); return false; } // If valSrc is a FramePtr, it represents the frame the value was // originally loaded from. Look for the guard for this local. if (source->isA(Type::FramePtr)) { return constrainLocal(inst->extra<LocalId>()->locId, source, tc, "constrainValue"); } // Otherwise, keep chasing down the source of val. return constrainValue(source, tc); } else if (inst->is(LdStack, LdStackAddr)) { return constrainStack(inst->src(0), inst->extra<StackOffset>()->offset, tc); } else if (inst->is(CheckType, AssertType)) { // If the dest type of the instruction fits the constraint we want, we can // stop here without constraining any further. Otherwise, continue through // to the source. auto changed = false; if (inst->is(CheckType)) changed = constrainGuard(inst, tc) || changed; auto dstType = inst->typeParam(); if (!typeFitsConstraint(dstType, tc.category)) { changed = constrainValue(inst->src(0), tc) || changed; } return changed; } else if (inst->is(StRef, StRefNT, Box, BoxPtr)) { // If our caller cares about the inner type, propagate that through. // Otherwise we're done. if (tc.innerCat) { auto src = inst->src(inst->is(StRef, StRefNT) ? 1 : 0); tc.innerCat.reset(); return constrainValue(src, tc); } return false; } else if (inst->is(LdRef, Unbox, UnboxPtr)) { // Pass through to the source of the box, remembering that we care about // the inner type of the box. assert(!tc.innerCat); tc.innerCat = tc.category; return constrainValue(inst->src(0), tc); } else if (inst->isPassthrough()) { return constrainValue(inst->getPassthroughValue(), tc); } else { // Any instructions not special cased above produce a new value, so // there's no guard for us to constrain. FTRACE(2, " - value is new in this trace, bailing\n"); return false; } // TODO(t2598894): Should be able to do something with LdMem<T> here }
/** * Trace back to the guard that provided the type of val, if * any. Constrain it so its type will not be relaxed beyond the given * DataTypeCategory. Returns true iff one or more guard instructions * were constrained. */ bool IRBuilder::constrainValue(SSATmp* const val, TypeConstraint tc) { if (!shouldConstrainGuards()) return false; always_assert(IMPLIES(tc.innerCat > DataTypeGeneric, tc.category >= DataTypeCountness)); if (!val) { ITRACE(1, "constrainValue(nullptr, {}), bailing\n", tc); return false; } ITRACE(1, "constrainValue({}, {})\n", *val->inst(), tc); Indent _i; auto inst = val->inst(); if (inst->is(LdLoc, LdLocAddr)) { // We've hit a LdLoc(Addr). If the source of the value is non-null and not // a FramePtr, it's a real value that was killed by a Call. The value won't // be live but it's ok to use it to track down the guard. auto source = inst->extra<LocalData>()->typeSrc; if (!source) { // val was newly created in this trace. Nothing to constrain. ITRACE(2, "typeSrc is null, bailing\n"); return false; } // If typeSrc is a FramePtr, it represents the frame the value was // originally loaded from. Look for the guard for this local. if (source->isA(Type::FramePtr)) { return constrainLocal(inst->extra<LocalId>()->locId, source, tc, "constrainValue"); } // Otherwise, keep chasing down the source of val. return constrainValue(source, tc); } else if (inst->is(LdStack, LdStackAddr)) { return constrainStack(inst->src(0), inst->extra<StackOffset>()->offset, tc); } else if (inst->is(AssertType)) { // Sometimes code in HhbcTranslator asks for a value with DataTypeSpecific // but can tolerate a less specific value. If that happens, there's nothing // to constrain. if (!typeFitsConstraint(val->type(), tc)) return false; // If the immutable typeParam fits the constraint, we're done. auto const typeParam = inst->typeParam(); if (typeFitsConstraint(typeParam, tc)) return false; auto const newTc = relaxConstraint(tc, typeParam, inst->src(0)->type()); ITRACE(1, "tracing through {}, orig tc: {}, new tc: {}\n", *inst, tc, newTc); return constrainValue(inst->src(0), newTc); } else if (inst->is(CheckType)) { // Sometimes code in HhbcTranslator asks for a value with DataTypeSpecific // but can tolerate a less specific value. If that happens, there's nothing // to constrain. if (!typeFitsConstraint(val->type(), tc)) return false; bool changed = false; auto const typeParam = inst->typeParam(); auto const srcType = inst->src(0)->type(); // Constrain the guard on the CheckType, but first relax the constraint // based on what's known about srcType. auto const guardTc = relaxConstraint(tc, srcType, typeParam); changed = constrainGuard(inst, guardTc) || changed; // Relax typeParam with its current constraint. This is used below to // recursively relax the constraint on the source, if needed. auto constraint = m_guardConstraints[inst]; constraint.category = std::max(constraint.category, guardTc.category); constraint.innerCat = std::max(constraint.innerCat, guardTc.innerCat); auto const knownType = refineType(relaxType(typeParam, constraint), constraint.assertedType); if (!typeFitsConstraint(knownType, tc)) { auto const newTc = relaxConstraint(tc, knownType, srcType); ITRACE(1, "tracing through {}, orig tc: {}, new tc: {}\n", *inst, tc, newTc); changed = constrainValue(inst->src(0), newTc) || changed; } return changed; } else if (inst->is(StRef)) { // StRef requires that src(0) is boxed so we're relying on callers to // appropriately constrain the values they pass to it. Any innerCat in tc // should be applied to the value being stored. tc.category = tc.innerCat; tc.innerCat = DataTypeGeneric; tc.assertedType = Type::Gen; return constrainValue(inst->src(1), tc); } else if (inst->is(Box, BoxPtr, Unbox, UnboxPtr)) { // All Box/Unbox opcodes are similar to StRef/LdRef in some situations and // Mov in others (determined at runtime), so we need to constrain both // outer and inner. auto maxCat = std::max(tc.category, tc.innerCat); tc.category = maxCat; tc.innerCat = maxCat; tc.assertedType = Type::Gen; return constrainValue(inst->src(0), tc); } else if (inst->is(LdRef)) { // Constrain the inner type of the box with tc, using DataTypeCountness for // the outer constraint to preserve the fact that it's a box. tc.innerCat = tc.category; tc.category = DataTypeCountness; tc.assertedType = Type::Gen; return constrainValue(inst->src(0), tc); } else if (inst->isPassthrough()) { return constrainValue(inst->getPassthroughValue(), tc); } else { // Any instructions not special cased above produce a new value, so // there's no guard for us to constrain. ITRACE(2, "value is new in this trace, bailing\n"); return false; } // TODO(t2598894): Should be able to do something with LdMem<T> here }
bool IRBuilder::constrainStack(SSATmp* sp, int32_t idx, TypeConstraint tc) { if (!shouldConstrainGuards()) return false; always_assert(IMPLIES(tc.innerCat > DataTypeGeneric, tc.category >= DataTypeCountness)); ITRACE(1, "constrainStack({}, {}, {})\n", *sp->inst(), idx, tc); Indent _i; assert(sp->isA(Type::StkPtr)); // We've hit a LdStack. If getStackValue gives us a value, recurse on // that. Otherwise, look at the instruction that gave us the type of the // stack element. If it's a GuardStk or CheckStk, it's our target. If it's // anything else, the value is new so there's no guard to relax. auto stackInfo = getStackValue(sp, idx); // Sometimes code in HhbcTranslator asks for a value with DataTypeSpecific // but can tolerate a less specific value. If that happens, there's nothing // to constrain. if (!typeFitsConstraint(stackInfo.knownType, tc)) return false; IRInstruction* typeSrc = stackInfo.typeSrc; if (stackInfo.value) { ITRACE(1, "value = {}\n", *stackInfo.value->inst()); return constrainValue(stackInfo.value, tc); } else if (typeSrc->is(AssertStk)) { // If the immutable typeParam fits the constraint, we're done. auto const typeParam = typeSrc->typeParam(); if (typeFitsConstraint(typeParam, tc)) return false; auto const srcIdx = typeSrc->extra<StackOffset>()->offset; auto const srcType = getStackValue(typeSrc->src(0), srcIdx).knownType; auto const newTc = relaxConstraint(tc, typeParam, srcType); ITRACE(1, "tracing through {}, orig tc: {}, new tc: {}\n", *typeSrc, tc, newTc); return constrainStack(typeSrc->src(0), srcIdx, newTc); } else if (typeSrc->is(CheckStk)) { auto changed = false; auto const typeParam = typeSrc->typeParam(); auto const srcIdx = typeSrc->extra<StackOffset>()->offset; auto const srcType = getStackValue(typeSrc->src(0), srcIdx).knownType; // Constrain the guard on the CheckType, but first relax the constraint // based on what's known about srcType. auto const guardTc = relaxConstraint(tc, srcType, typeParam); changed = constrainGuard(typeSrc, guardTc) || changed; // Relax typeParam with its current constraint. This is used below to // recursively relax the constraint on the source, if needed. auto constraint = m_guardConstraints[typeSrc]; constraint.category = std::max(constraint.category, guardTc.category); constraint.innerCat = std::max(constraint.innerCat, guardTc.innerCat); auto const knownType = refineType(relaxType(typeParam, constraint), constraint.assertedType); if (!typeFitsConstraint(knownType, tc)) { auto const newTc = relaxConstraint(tc, knownType, srcType); ITRACE(1, "tracing through {}, orig tc: {}, new tc: {}\n", *typeSrc, tc, newTc); changed = constrainStack(typeSrc->src(0), srcIdx, newTc) || changed; } return changed; } else { ITRACE(1, "typeSrc = {}\n", *typeSrc); return typeSrc->is(GuardStk) && constrainGuard(typeSrc, tc); } }