/* * For all guard instructions in trace, check to see if we can relax the * destination type to something less specific. The GuardConstraints map * contains information about what properties of the guarded type matter for * each instruction. Returns true iff any changes were made to the trace. */ bool relaxGuards(const IRUnit& unit, const GuardConstraints& guards) { FTRACE(1, "relaxing guards for trace {}\n", unit.main()); auto blocks = rpoSortCfg(unit); Block* reflowBlock = nullptr; for (auto* block : blocks) { for (auto& inst : *block) { if (!isGuardOp(inst.op())) continue; auto it = guards.find(&inst); auto constraint = it == guards.end() ? TypeConstraint() : it->second; // TODO(t2598894): Support relaxing inner types auto const oldType = inst.typeParam(); auto newType = relaxType(oldType, constraint.category); if (constraint.knownType <= newType) { // If the known type is at least as good as the relaxed type, we can // replace the guard with an assert. auto newOp = guardToAssert(inst.op()); FTRACE(1, "relaxGuards changing {}'s type to {}, op to {}\n", inst, constraint.knownType, newOp); inst.setTypeParam(constraint.knownType); inst.setOpcode(newOp); inst.setTaken(nullptr); if (!reflowBlock) reflowBlock = block; } else if (!oldType.equals(newType)) { FTRACE(1, "relaxGuards changing {}'s type to {}\n", inst, newType); inst.setTypeParam(newType); if (!reflowBlock) reflowBlock = block; } } } if (reflowBlock) reflowTypes(reflowBlock, blocks); return (bool)reflowBlock; }
/* * For all guard instructions in trace, check to see if we can relax the * destination type to something less specific. The GuardConstraints map * contains information about what properties of the guarded type matter for * each instruction. If simple is true, guards will not be relaxed past * DataTypeSpecific except guards which are relaxed all the way to * DataTypeGeneric. Returns true iff any changes were made to the trace. */ bool relaxGuards(IRUnit& unit, const GuardConstraints& guards, bool simple) { auto blocks = rpoSortCfg(unit); auto changed = false; for (auto* block : blocks) { for (auto& inst : *block) { if (!isGuardOp(inst.op())) continue; auto it = guards.find(&inst); auto constraint = it == guards.end() ? TypeConstraint() : it->second; if (simple && constraint.category > DataTypeGeneric && constraint.category < DataTypeSpecific) { constraint.category = DataTypeSpecific; } // TODO(t2598894): Support relaxing inner types auto const oldType = inst.typeParam(); auto newType = relaxType(oldType, constraint.category); if (constraint.knownType <= newType) { // If the known type is at least as good as the relaxed type, we can // replace the guard with an assert. auto newOp = guardToAssert(inst.op()); auto newType = std::min(constraint.knownType, previousGuardType(&inst)); FTRACE(1, "relaxGuards changing {}'s type to {}, op to {}\n", inst, newType, newOp); assert(!hasEdges(newOp)); if (inst.hasEdges()) { block->push_back(unit.gen(Jmp, inst.marker(), inst.next())); } inst.setTypeParam(newType); inst.setOpcode(newOp); changed = true; } else if (!oldType.equals(newType)) { FTRACE(1, "relaxGuards changing {}'s type to {}\n", inst, newType); inst.setTypeParam(newType); changed = true; } } } if (!changed) return false; // Make a second pass to reflow types, with some special logic for loads. FrameState state(unit); for (auto* block : blocks) { state.startBlock(block); for (auto& inst : *block) { state.setMarker(inst.marker()); visitLoad(&inst, state); retypeDests(&inst); state.update(&inst); } state.finishBlock(block); } return true; }
/* * For all guard instructions in unit, check to see if we can relax the * destination type to something less specific. The GuardConstraints map * contains information about what properties of the guarded type matter for * each instruction. If simple is true, guards will not be relaxed past * DataTypeSpecific except guards which are relaxed all the way to * DataTypeGeneric. Returns true iff any changes were made to the trace. */ bool relaxGuards(IRUnit& unit, const GuardConstraints& guards, bool simple) { Timer _t("optimize_relaxGuards"); splitCriticalEdges(unit); auto blocks = rpoSortCfg(unit); auto changed = false; for (auto* block : blocks) { for (auto& inst : *block) { if (!isGuardOp(inst.op())) continue; auto it = guards.find(&inst); auto constraint = it == guards.end() ? TypeConstraint() : it->second; FTRACE(2, "relaxGuards processing {} with constraint {}\n", inst, constraint); if (simple && constraint.category > DataTypeGeneric && constraint.category < DataTypeSpecific) { constraint.category = DataTypeSpecific; } auto const oldType = inst.typeParam(); auto newType = relaxType(oldType, constraint); // Sometimes we (legitimately) end up with a guard like this: // // t4:StkPtr = GuardStk<BoxedArr,0,<DataTypeGeneric, // inner:DataTypeSpecific, // Type::BoxedCell>> t2:StkPtr // // The outer category is DataTypeGeneric because we know from eval stack // flavors that the top of the stack here is always boxed. The inner // category is DataTypeSpecific, indicating we care what the inner type // is, even though it's just a hint. If we treated this like any other // guard, we would relax the typeParam to Type::Gen and insert an assert // to Type::BoxedCell right after it. Unfortunately, this loses the hint // that the inner type is Arr. Eventually we should have some side // channel for passing around hints for inner ref types, but for now the // best we can do is forcibly keep the guard around, preserving the inner // type hint. if (constraint.assertedType.isBoxed() && oldType < constraint.assertedType) { auto relaxedInner = relaxInner(oldType, constraint); if (relaxedInner < Type::BoxedCell && newType >= Type::BoxedCell) { FTRACE(1, "relaxGuards changing newType to {}\n", newType); newType = relaxedInner; } } if (constraint.assertedType < newType) { // If the asserted type is more specific than the new guarded type, set // the guard to the relaxed type but insert an assert operation between // the instruction and its dst. We go from something like this: // // t5:FramePtr = GuardLoc<Int, 4, <DataTypeGeneric,Int>> t4:FramePtr // // to this: // // t6:FramePtr = GuardLoc<Gen, 4> t4:FramePtr // t5:FramePtr = AssertLoc<Int, 4> t6:FramePtr auto* oldDst = inst.dst(); auto* newDst = unit.genDst(&inst); auto* newAssert = [&] { switch (inst.op()) { case GuardLoc: case CheckLoc: return unit.genWithDst(oldDst, guardToAssert(inst.op()), inst.marker(), *inst.extra<LocalId>(), constraint.assertedType, newDst); case GuardStk: case CheckStk: return unit.genWithDst(oldDst, guardToAssert(inst.op()), inst.marker(), *inst.extra<StackOffset>(), constraint.assertedType, newDst); case CheckType: return unit.genWithDst(oldDst, guardToAssert(inst.op()), inst.marker(), constraint.assertedType, newDst); default: always_assert(false); } }(); FTRACE(1, "relaxGuards inserting {} between {} and its dst, " "changing typeParam to {}\n", *newAssert, inst, newType); inst.setTypeParam(newType); // Now, insert the assert after the guard. For control flow guards, // this means inserting it on the next edge. if (inst.isControlFlow()) { auto* block = inst.next(); block->insert(block->skipHeader(), newAssert); } else { auto* block = inst.block(); auto it = block->iteratorTo(&inst); ++it; block->insert(it, newAssert); } changed = true; } else if (oldType != newType) { FTRACE(1, "relaxGuards changing {}'s type to {}\n", inst, newType); inst.setTypeParam(newType); changed = true; } } } if (!changed) return false; // Make a second pass to reflow types, with some special logic for loads. FrameState state(unit); for (auto* block : blocks) { state.startBlock(block); for (auto& inst : *block) { state.setMarker(inst.marker()); copyProp(&inst); visitLoad(&inst, state); if (!removeGuard(unit, &inst, state)) { retypeDests(&inst); state.update(&inst); } } state.finishBlock(block); } return true; }