/* * 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; }
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; }
SSATmp* IRBuilder::preOptimizeAssertTypeOp(IRInstruction* inst, Type oldType, ConstraintFunc constrain) { auto const newType = inst->typeParam(); if (oldType.not(newType)) { // If both types are boxed this is ok and even expected as a means to // update the hint for the inner type. if (oldType.isBoxed() && newType.isBoxed()) return nullptr; // We got external information (probably from static analysis) that // conflicts with what we've built up so far. There's no reasonable way to // continue here: we can't properly fatal the request because we can't make // a catch trace or SpillStack without HhbcTranslator, we can't punt on // just this instruction because we might not be in the initial translation // phase, and we can't just plow on forward since we'll probably generate // malformed IR. Since this case is very rare, just punt on the whole trace // so it gets interpreted. TRACE_PUNT("Invalid AssertTypeOp"); } // Asserting in these situations doesn't add any information. if (oldType <= Type::Cls || newType == Type::Gen) return inst->src(0); // We're asserting a strict subtype of the old type, so keep the assert // around. if (newType < oldType) return nullptr; // oldType is at least as good as the new type. Kill this assert op but // preserve the type we were asserting in case the source type gets relaxed // past it. if (newType >= oldType) { constrain({DataTypeGeneric, newType}); return inst->src(0); } // AssertLoc is special here because it's the one AssertTypeOp that doesn't // do its own filtering of the destination type based on the input type and // the asserted type. This will hopefully be fixed soon. if (inst->is(AssertLoc)) { // Now we're left with cases where neither type is a subtype of the other // but they have some nonzero intersection. We want to end up asserting the // intersection, but we have to constrain the input to avoid reintroducing // types that were removed from the original typeParam. auto const intersect = newType & oldType; inst->setTypeParam(intersect); TypeConstraint tc; if (intersect != newType) { Type relaxed; // Find the most general constraint that doesn't modify the type being // asserted. while ((relaxed = newType & relaxType(oldType, tc)) != intersect) { if (tc.category > DataTypeGeneric && relaxed.maybeBoxed() && intersect.maybeBoxed() && (relaxed & Type::Cell) == (intersect & Type::Cell)) { // If the inner type is why we failed, constrain that a level. incCategory(tc.innerCat); } else { incCategory(tc.category); } } } constrain(tc); } return nullptr; }