SSATmp* TraceBuilder::preOptimizeStLoc(IRInstruction* inst) { auto locId = inst->extra<StLoc>()->locId; auto const curType = localType(locId, DataTypeGeneric); auto const newType = inst->src(1)->type(); assert(inst->typeParam().equals(Type::None)); // There's no need to store the type if it's going to be the same // KindOfFoo. We still have to store string types because we don't // guard on KindOfStaticString vs. KindOfString. auto const bothBoxed = curType.isBoxed() && newType.isBoxed(); auto const sameUnboxed = curType != Type::None && // TODO(#2135185) curType.isSameKindOf(newType) && !curType.isString(); if (bothBoxed || sameUnboxed) { // TODO(t2598894) once relaxGuards supports proper type reflowing, we // should be able to relax the constraint here and degrade StLocNT to // StLoc if we relax its input. if (sameUnboxed) constrainLocal(locId, DataTypeSpecific, "StLoc -> StLocNT"); inst->setOpcode(StLocNT); } return nullptr; }
SSATmp* IRBuilder::preOptimizeCheckLoc(IRInstruction* inst) { auto const locId = inst->extra<CheckLoc>()->locId; Type typeParam = inst->typeParam(); SSATmp* src = inst->src(0); if (auto const prevValue = localValue(locId, DataTypeGeneric)) { return gen(CheckType, typeParam, inst->taken(), prevValue); } auto const prevType = localType(locId, DataTypeGeneric); if (prevType <= typeParam) { return src; } if (prevType.not(typeParam)) { if (typeParam.isBoxed() && prevType.isBoxed()) { /* When both types are non-intersecting boxed types, we're just * updating the inner type hint. This requires no runtime work. */ constrainLocal(locId, DataTypeCountness, "preOptimizeCheckLoc"); return gen(AssertLoc, LocalId(locId), typeParam, src); } /* This check will always fail. It's probably due to an incorrect * prediction. Generate a Jmp, and return the source because * following instructions may depend on the output of CheckLoc * (they'll be DCEd later). Note that we can't use convertToJmp * because the return value isn't nullptr, so the original * instruction won't be inserted into the stream. */ gen(Jmp, inst->taken()); return src; } return nullptr; }
/** * 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. Always returns val, for convenience. */ SSATmp* TraceBuilder::constrainValue(SSATmp* const val, DataTypeCategory cat) { if (!val) { FTRACE(1, "constrainValue(nullptr, {}), bailing\n", cat); return nullptr; } FTRACE(1, "constrainValue({}, {})\n", *val->inst(), cat); // If cat is DataTypeGeneric, there's nothing to do. if (cat == DataTypeGeneric) return val; auto inst = val->inst(); if (inst->op() == LdLoc || inst->op() == 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<LdLocData>()->valSrc; if (!source) { // val was newly created in this trace. Nothing to constrain. FTRACE(2, " - valSrc is null, bailing\n"); return val; } // 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)) { constrainLocal(inst->extra<LocalId>()->locId, source, cat, "constrainValue"); return val; } // Otherwise, keep chasing down the source of val. constrainValue(source, cat); } else if (inst->op() == LdStack) { constrainStack(inst->src(0), inst->extra<StackOffset>()->offset, cat); } else if (inst->op() == CheckType) { // Constrain this CheckType and keep going on its source value, in case // there are more guards to constrain. constrainGuard(inst, cat); constrainValue(inst->src(0), cat); } else if (inst->op() == StRef || inst->op() == StRefNT) { // TODO(t2598894): This can be tightened up. As a conservative // approximation, pass the constraint through to the source of the value. constrainValue(inst->src(1), cat); } else if (inst->isPassthrough()) { constrainValue(inst->getPassthroughValue(), cat); } 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 val; }
SSATmp* IRBuilder::preOptimizeAssertLoc(IRInstruction* inst) { auto const locId = inst->extra<AssertLoc>()->locId; if (auto const prevValue = localValue(locId, DataTypeGeneric)) { return gen(AssertType, inst->typeParam(), prevValue); } return m_simplifier.simplifyAssertTypeOp( inst, localType(locId, DataTypeGeneric), [&](TypeConstraint tc) { constrainLocal(locId, tc, "preOptimizeAssertLoc"); } ); }
SSATmp* TraceBuilder::preOptimizeAssertLoc(IRInstruction* inst) { auto const locId = inst->extra<AssertLoc>()->locId; auto const prevType = localType(locId, DataTypeGeneric); auto const typeParam = inst->typeParam(); if (prevType.not(typeParam)) { TRACE_PUNT("Invalid AssertLoc"); } if (shouldElideAssertType(prevType, typeParam, nullptr)) { return inst->src(0); } if (filterAssertType(inst, prevType)) { constrainLocal(locId, categoryForType(prevType), "AssertLoc"); } return nullptr; }
SSATmp* IRBuilder::preOptimizeCheckLoc(IRInstruction* inst) { auto const locId = inst->extra<CheckLoc>()->locId; Type typeParam = inst->typeParam(); if (auto const prevValue = localValue(locId, DataTypeGeneric)) { return gen(CheckType, typeParam, inst->taken(), prevValue); } auto const prevType = localType(locId, DataTypeSpecific); if (prevType <= typeParam) { return inst->src(0); } else { // // Normally, it doesn't make sense to be checking something that's // deemed to fail. Incompatible boxed types are ok though, since // we don't track them precisely, but instead check them at every // use. // // However, in JitPGO mode right now, this pathological case can // happen, because profile counters are not accurate and we // currently don't analyze Block post-conditions when picking its // successors during region selection. This can lead to // incompatible types in blocks selected for the same region. // if (prevType.not(typeParam)) { if (typeParam.isBoxed() && prevType.isBoxed()) { // When both types are non-intersecting boxed types, we're just // updating the inner type hint. This requires no runtime work. constrainLocal(locId, DataTypeCountness, "preOptimizeCheckLoc"); return gen(AssertLoc, LocalId(locId), typeParam, inst->src(0)); } else { assert(RuntimeOption::EvalJitPGO); return gen(Jmp, inst->taken()); } } } return nullptr; }
bool IRBuilder::constrainLocal(uint32_t locId, TypeConstraint tc, const std::string& why) { if (!shouldConstrainGuards()) return false; return constrainLocal(locId, localTypeSource(locId), tc, why); }
/** * 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 }
bool TraceBuilder::constrainLocal(uint32_t locId, TypeConstraint tc, const std::string& why) { return constrainLocal(locId, localValueSource(locId), tc, why); }
/** * 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 }
SSATmp* TraceBuilder::localValue(unsigned id, DataTypeCategory cat) { always_assert(id < m_locals.size()); constrainLocal(id, cat, "localValue"); return m_locals[id].unsafe ? nullptr : m_locals[id].value; }
/** * 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 }
void TraceBuilder::constrainLocal(uint32_t locId, DataTypeCategory cat, const std::string& why) { constrainLocal(locId, localValueSource(locId), cat, why); }
Type TraceBuilder::localType(unsigned id, DataTypeCategory cat) { always_assert(id < m_locals.size()); constrainLocal(id, cat, "localType"); return m_locals[id].type; }
Type IRBuilder::localType(uint32_t id, TypeConstraint tc) { constrainLocal(id, tc, "localType"); return m_state.localType(id); }
SSATmp* IRBuilder::localValue(uint32_t id, TypeConstraint tc) { constrainLocal(id, tc, "localValue"); return m_state.localValue(id); }
void TraceBuilder::constrainLocal(uint32_t locId, DataTypeCategory cat) { constrainLocal(locId, localValueSource(locId), cat); }