TEST(Type, RelaxType) { EXPECT_EQ(Type::Gen, relaxType(Type::BoxedStr, {DataTypeGeneric})); EXPECT_EQ(Type::BoxedInitCell | Type::Uncounted, relaxType(Type::BoxedObj | Type::InitNull, {DataTypeCountness, DataTypeGeneric})); auto inner = TypeConstraint{DataTypeSpecialized}; inner.setDesiredClass(SystemLib::s_IteratorClass); inner.innerCat = DataTypeSpecialized; inner.category = DataTypeSpecific; auto type = Type::Obj.specialize(SystemLib::s_IteratorClass).box(); EXPECT_EQ("BoxedObj<Iterator>", type.toString()); EXPECT_EQ(type, relaxType(type, inner)); }
/* * 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. */ bool relaxGuards(IRTrace* trace, const IRFactory& factory, const GuardConstraints& guards) { FTRACE(1, "relaxing guards for trace {}\n", trace); auto blocks = rpoSortCfg(trace, factory); Block* reflowBlock = nullptr; for (auto* block : blocks) { for (auto& inst : *block) { if (!isGuardOp(inst.op())) continue; auto it = guards.find(inst.id()); auto category = it == guards.end() ? DataTypeGeneric : it->second; auto const oldType = inst.typeParam(); auto newType = relaxType(oldType, category); if (!oldType.equals(newType)) { FTRACE(1, "relaxGuards changing {}'s type to {}\n", inst, newType); inst.setTypeParam(newType); if (!reflowBlock) reflowBlock = block; } } } // TODO(t2598894): For now we require regenerating the IR after guard // relaxation, so it's only useful in the tracelet region selector. if (false && reflowBlock) reflowTypes(reflowBlock, blocks); return (bool)reflowBlock; }
void NonlinearSolver<PhysicalModel>::stabilizeNonlinearUpdate(V& dx, V& dxOld, const double omega) const { // The dxOld is updated with dx. // If omega is equal to 1., no relaxtion will be appiled. const V tempDxOld = dxOld; dxOld = dx; switch (relaxType()) { case DAMPEN: if (omega == 1.) { return; } dx = dx*omega; return; case SOR: if (omega == 1.) { return; } dx = dx*omega + (1.-omega)*tempDxOld; return; default: OPM_THROW(std::runtime_error, "Can only handle DAMPEN and SOR relaxation type."); } return; }
/* * Returns the least specific supertype of t that maintains the properties * required by cat. */ Type relaxType(Type t, DataTypeCategory cat) { always_assert(t.subtypeOf(Type::Gen)); switch (cat) { case DataTypeGeneric: return Type::Gen; case DataTypeCountness: return t.notCounted() ? Type::Uncounted : t.unspecialize(); case DataTypeCountnessInit: if (t.subtypeOf(Type::Uninit)) return Type::Uninit; return relaxType(t, DataTypeCountness); case DataTypeSpecific: assert(t.isKnownDataType()); return t.unspecialize(); case DataTypeSpecialized: assert(t.isSpecialized()); return t; } not_reached(); }
TEST(Type, RelaxType) { EXPECT_EQ(Type::Gen, relaxType(Type::BoxedStr, {DataTypeGeneric})); EXPECT_EQ(Type::BoxedInitCell | Type::Uncounted, relaxType(Type::BoxedObj | Type::InitNull, {DataTypeCountness})); auto tc = TypeConstraint{DataTypeSpecialized}; tc.setDesiredClass(SystemLib::s_IteratorClass); tc.category = DataTypeSpecialized; auto type = Type::Obj.specialize(SystemLib::s_IteratorClass); EXPECT_EQ("Obj<=Iterator", type.toString()); EXPECT_EQ(type, relaxType(type, tc)); EXPECT_EQ(Type::BoxedInitCell, relaxType(Type::BoxedInitCell, DataTypeCountnessInit)); EXPECT_EQ(Type::BoxedInitCell, relaxType(Type::BoxedInitCell, DataTypeCountness)); }
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; }
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)); }
/* * 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; }
/* * 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; }
void NonlinearSolver<PhysicalModel>::stabilizeNonlinearUpdate(BVector& dx, BVector& dxOld, const double omega) const { // The dxOld is updated with dx. // If omega is equal to 1., no relaxtion will be appiled. BVector tempDxOld = dxOld; dxOld = dx; switch (relaxType()) { case DAMPEN: { if (omega == 1.) { return; } auto i = dx.size(); for (i = 0; i < dx.size(); ++i) { dx[i] *= omega; } return; } case SOR: { if (omega == 1.) { return; } auto i = dx.size(); for (i = 0; i < dx.size(); ++i) { dx[i] *= omega; tempDxOld[i] *= (1.-omega); dx[i] += tempDxOld[i]; } return; } default: OPM_THROW(std::runtime_error, "Can only handle DAMPEN and SOR relaxation type."); } return; }
/* * 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& constraints, RelaxGuardsFlags flags) { Timer _t(Timer::optimize_relaxGuards); ITRACE(2, "entering relaxGuards\n"); Indent _i; bool simple = flags & RelaxSimple; bool reflow = flags & RelaxReflow; splitCriticalEdges(unit); auto& guards = constraints.guards; 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; ITRACE(2, "relaxGuards processing {} with constraint {}\n", inst, constraint); auto simplifyCategory = [simple](DataTypeCategory& cat) { if (simple && cat > DataTypeGeneric && cat < DataTypeSpecific) { cat = DataTypeSpecific; } }; simplifyCategory(constraint.category); simplifyCategory(constraint.innerCat); auto const oldType = inst.typeParam(); auto newType = relaxType(oldType, constraint); if (oldType != newType) { ITRACE(1, "relaxGuards changing {}'s type to {}\n", inst, newType); inst.setTypeParam(newType); changed = true; } } } if (!changed) return false; if (!reflow) return true; // Make a second pass to reflow types, with some special logic for loads. FrameState state{unit, unit.entry()->front().marker()}; for (auto* block : blocks) { ITRACE(2, "relaxGuards reflow entering B{}\n", block->id()); Indent _i; state.startBlock(block, block->front().marker()); for (auto& inst : *block) { state.setMarker(inst.marker()); copyProp(&inst); visitLoad(&inst, state); retypeDests(&inst, &unit); state.update(&inst); } state.finishBlock(block); } return true; }
/** * 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 }
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; }
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); } }
/* * 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; }
int NewtonSolver<PhysicalModel>:: step(const double dt, ReservoirState& reservoir_state, WellState& well_state) { // Do model-specific once-per-step calculations. model_->prepareStep(dt, reservoir_state, well_state); // For each iteration we store in a vector the norms of the residual of // the mass balance for each active phase, the well flux and the well equations. std::vector<std::vector<double>> residual_norms_history; // Assemble residual and Jacobian, store residual norms. model_->assemble(reservoir_state, well_state, true); residual_norms_history.push_back(model_->computeResidualNorms()); // Set up for main Newton loop. double omega = 1.0; int iteration = 0; bool converged = model_->getConvergence(dt, iteration); const int sizeNonLinear = model_->sizeNonLinear(); V dxOld = V::Zero(sizeNonLinear); bool isOscillate = false; bool isStagnate = false; const enum RelaxType relaxtype = relaxType(); int linearIterations = 0; // ---------- Main Newton loop ---------- while ( (!converged && (iteration < maxIter())) || (minIter() > iteration)) { // Compute the Newton update to the primary variables. V dx = model_->solveJacobianSystem(); // Store number of linear iterations used. linearIterations += model_->linearIterationsLastSolve(); // Stabilize the Newton update. detectNewtonOscillations(residual_norms_history, iteration, relaxRelTol(), isOscillate, isStagnate); if (isOscillate) { omega -= relaxIncrement(); omega = std::max(omega, relaxMax()); if (model_->terminalOutputEnabled()) { std::cout << " Oscillating behavior detected: Relaxation set to " << omega << std::endl; } } stabilizeNewton(dx, dxOld, omega, relaxtype); // Apply the update, the model may apply model-dependent // limitations and chopping of the update. model_->updateState(dx, reservoir_state, well_state); // Assemble residual and Jacobian, store residual norms. model_->assemble(reservoir_state, well_state, false); residual_norms_history.push_back(model_->computeResidualNorms()); // increase iteration counter ++iteration; converged = model_->getConvergence(dt, iteration); } if (!converged) { if (model_->terminalOutputEnabled()) { std::cerr << "WARNING: Failed to compute converged solution in " << iteration << " iterations." << std::endl; } return -1; // -1 indicates that the solver has to be restarted } linearIterations_ += linearIterations; newtonIterations_ += iteration; linearIterationsLast_ = linearIterations; newtonIterationsLast_ = iteration; // Do model-specific post-step actions. model_->afterStep(dt, reservoir_state, well_state); return linearIterations; }
TEST(Type, Relax) { EXPECT_EQ(Type::BoxedInitCell | Type::InitNull, relaxType(Type::BoxedObj | Type::InitNull, {DataTypeCountness, Type::Gen, DataTypeGeneric})); }
/* * 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; }