TEST(Type, RelaxConstraint) { EXPECT_EQ(TypeConstraint(DataTypeCountness), relaxConstraint(TypeConstraint{DataTypeSpecific}, Type::Cell, Type::Arr)); EXPECT_EQ(TypeConstraint(DataTypeGeneric), relaxConstraint(TypeConstraint{DataTypeCountness}, Type::Arr, Type::Cell)); }
TEST(Type, Relax) { EXPECT_EQ(Type::BoxedInitCell | Type::InitNull, relaxType(Type::BoxedObj |Type::InitNull, {DataTypeCountness, DataTypeGeneric})); EXPECT_EQ(TypeConstraint(DataTypeCountness, DataTypeCountness), relaxConstraint(TypeConstraint{DataTypeSpecific, DataTypeSpecific}, Type::BoxedCell, Type::BoxedArr)); EXPECT_EQ(TypeConstraint(DataTypeGeneric, DataTypeGeneric), relaxConstraint(TypeConstraint{DataTypeCountness, DataTypeSpecific}, Type::BoxedArr, Type::BoxedCell)); }
/** * 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); } }