Пример #1
0
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;
}
Пример #2
0
/*
 * Returns true iff t is specific enough to fit cat.
 */
bool typeFitsConstraint(Type t, DataTypeCategory cat) {
  switch (cat) {
    case DataTypeGeneric:
      return true;

    case DataTypeCountness:
      // Consumers using this constraint are probably going to decref the
      // value, so it's ok if we know whether t is counted or not. Arr and Str
      // are special cased because we don't guard on staticness for them.
      return t.notCounted() ||
             t <= (Type::Counted | Type::StaticArr | Type::StaticStr);

    case DataTypeCountnessInit:
      return typeFitsConstraint(t, DataTypeCountness) &&
             (t <= Type::Uninit || t.not(Type::Uninit));

    case DataTypeSpecific:
      return t.isKnownDataType();

    case DataTypeSpecialized:
      return t.isSpecialized();
  }

  not_reached();
}
Пример #3
0
void RegionDesc::Block::addPreCondition(const GuardedLocation& locGuard) {
  FTRACE(2, "Block::addPreCondition({})\n", show(locGuard));
  assertx(locGuard.type != TBottom);
  assertx(locGuard.type <= TStkElem);
  assertx(locGuard.type.isSpecialized() ||
          typeFitsConstraint(locGuard.type, locGuard.category));
  m_typePreConditions.push_back(locGuard);
}
Пример #4
0
/*
 * 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;
}
Пример #5
0
/*
 * Returns true iff a guard to constrain was found, and tc was more specific
 * than the guard's existing constraint. Note that this doesn't necessarily
 * mean that the guard was constrained: tc.weak might be true.
 */
bool IRBuilder::constrainGuard(IRInstruction* inst, TypeConstraint tc) {
  if (!shouldConstrainGuards()) return false;

  auto& guard = m_guardConstraints[inst];
  auto changed = false;
  auto const assertFits = typeFitsConstraint(guard.assertedType, tc);
  ITRACE(2, "constrainGuard({}, {}): existing constraint {}, assertFits: {}\n",
         *inst, tc, guard, assertFits ? "true" : "false");
  Indent _i;

  // For category and innerCat, constrain the guard if the assertedType isn't
  // strong enough to fit what we want and tc is more specific than the
  // existing category.

  if (!assertFits && tc.innerCat > guard.innerCat) {
    if (!tc.weak) {
      ITRACE(1, "constraining inner type of {}: {} -> {}\n",
             *inst, guard.innerCat, tc.innerCat);
      guard.innerCat = tc.innerCat;
    }
    changed = true;
  } else {
    ITRACE(2, "not constraining innerCat\n");
  }

  if (!assertFits && tc.category > guard.category) {
    if (!tc.weak) {
      ITRACE(1, "constraining {}: {} -> {}\n",
             *inst, guard.category, tc.category);
      guard.category = tc.category;
    }
    changed = true;
  } else {
    ITRACE(2, "not constraining category\n");
  }

  // It's fairly common to have a local that we've asserted to be Obj, and then
  // later assert that it's Obj<C>|InitNull. We want to use their intersection,
  // so in this case we'd assert Obj<C>.
  always_assert(tc.assertedType.maybe(guard.assertedType));
  auto assertCommon = tc.assertedType & guard.assertedType;
  if (assertCommon < guard.assertedType) {
    // We don't check tc.weak here because assertedType is supposed to be
    // statically known type information.
    ITRACE(1, "using {} to refine assertedType of {}: {} -> {}\n",
           tc.assertedType, *inst, guard.assertedType, assertCommon);
    guard.assertedType = assertCommon;
  } else {
    ITRACE(2, "not refining assertedType\n");
  }

  return changed;
}
Пример #6
0
void TraceBuilder::constrainLocal(uint32_t locId, SSATmp* valSrc,
                                  DataTypeCategory cat,
                                  const std::string& why) {
  FTRACE(1, "constrainLocal({}, {}, {}, {})\n",
         locId, valSrc ? valSrc->inst()->toString() : "null", cat, why);

  if (!valSrc) return;
  if (!valSrc->isA(Type::FramePtr)) {
    constrainValue(valSrc, cat);
    return;
  }

  // When valSrc is a FramePtr, that means we loaded the value the local had
  // coming into the trace. Trace through the FramePtr chain, looking for a
  // guard for this local id. If we find it, constrain the guard. If we don't
  // find it, there wasn't a guard for this local so there's nothing to
  // constrain.
  for (auto fpInst = valSrc->inst();
       fpInst->op() != DefFP && fpInst->op() != DefInlineFP;
       fpInst = fpInst->src(0)->inst()) {
    FTRACE(2, "    - fp = {}\n", *fpInst);
    assert(fpInst->dst()->isA(Type::FramePtr));

    auto instLoc = [fpInst]{ return fpInst->extra<LocalId>()->locId; };
    switch (fpInst->op()) {
      case GuardLoc:
      case CheckLoc:
        if (instLoc() != locId) break;
        FTRACE(2, "    - found guard to constrain\n");
        constrainGuard(fpInst, cat);
        return;

      case AssertLoc:
        if (instLoc() != locId) break;
        // If the refined the type of the local satisfies the constraint we're
        // trying to apply, we can stop here. This can happen if we assert a
        // more general type than what we already know. Otherwise we need to
        // keep tracing back to the guard.
        if (typeFitsConstraint(fpInst->typeParam(), cat)) return;
        break;

      case FreeActRec:
        always_assert(0 && "Attempt to read a local after freeing its frame");

      default:
        not_reached();
    }
  }

  FTRACE(2, "    - no guard to constrain\n");
}
Пример #7
0
/*
 * Returns true iff t is specific enough to fit tc, meaning a consumer
 * constraining a value with tc would be satisfied with t as the value's type
 * after relaxation.
 */
bool typeFitsConstraint(Type t, TypeConstraint tc) {
  assert(t != Type::Bottom);

  if (tc.innerCat > DataTypeGeneric) {
    // First check the outer constraint.
    if (!typeFitsConstraint(t, tc.category)) return false;

    // Then, if t might be boxed, check the inner type.
    return t.notBoxed() ||
      typeFitsConstraint((t & Type::BoxedCell).innerType(), tc.innerCat);
  }

  switch (tc.category) {
    case DataTypeGeneric:
      return true;

    case DataTypeCountness:
      // Consumers using this constraint are probably going to decref the
      // value, so it's ok if we know whether t is counted or not. Arr and Str
      // are special cased because we don't guard on staticness for them.
      return t.notCounted() ||
             t <= (Type::Counted | Type::StaticArr | Type::StaticStr);

    case DataTypeCountnessInit:
      return typeFitsConstraint(t, DataTypeCountness) &&
             (t <= Type::Uninit || t.not(Type::Uninit));

    case DataTypeSpecific:
      return t.isKnownDataType();

    case DataTypeSpecialized:
      return t.isSpecialized();
  }

  not_reached();
}
Пример #8
0
/*
 * Check invariants about the metadata for this Block.
 *
 * 1. Each SrcKey in m_typePredictions, m_preConditions, m_byRefs, m_refPreds,
 *    and m_knownFuncs is within the bounds of the block.
 *
 * 2. Each local id referred to in the type prediction list is valid.
 *
 * 3. (Unchecked) each stack offset in the type prediction list is valid.
*/
void RegionDesc::Block::checkMetadata() const {
  auto rangeCheck = [&](const char* type, Offset o) {
    if (o < m_start || o > m_last) {
      std::cerr << folly::format("{} at {} outside range [{}, {}]\n",
                                 type, o, m_start, m_last);
      assertx(!"Region::Block contained out-of-range metadata");
    }
  };

  auto checkTypedLocations = [&](const char* msg, const TypedLocVec& vec) {
    for (auto& typedLoc : vec) {
      auto& loc = typedLoc.location;
      switch (loc.tag()) {
        case LTag::Local:
          assertx(loc.localId() < m_func->numLocals());
          break;
        case LTag::Stack:
          break;
      }
    }
  };

  auto checkGuardedLocations = [&](const char* msg, const GuardedLocVec& vec) {
    for (auto& guardedLoc : vec) {
      assertx(guardedLoc.type.isSpecialized() ||
              typeFitsConstraint(guardedLoc.type, guardedLoc.category));
      auto& loc = guardedLoc.location;
      switch (loc.tag()) {
        case LTag::Local:
          assertx(loc.localId() < m_func->numLocals());
          break;
        case LTag::Stack:
          break;
      }
    }
  };

  checkTypedLocations("type prediction", m_typePredictions);
  checkGuardedLocations("type precondition", m_typePreConditions);

  for (auto& byRef : m_byRefs) {
    rangeCheck("parameter reference flag", byRef.first.offset());
  }
  for (auto& func : m_knownFuncs) {
    rangeCheck("known Func*", func.first.offset());
  }
}
Пример #9
0
bool typeFitsConstraint(Type t, GuardConstraint gc) {
  switch (gc.category) {
    case DataTypeGeneric:
      return true;

    case DataTypeCountness:
    case DataTypeBoxAndCountness:
      // Consumers using this constraint expect the type to be relaxed to
      // Uncounted or left alone, so something like Arr|Obj isn't specific
      // enough.
      return !t.maybe(TCounted) ||
             t.subtypeOfAny(TStr, TArr, TVec, TDict, TKeyset, TObj,
                            TRes, TBoxedCell);

    case DataTypeBoxAndCountnessInit:
      return typeFitsConstraint(t, DataTypeBoxAndCountness) &&
             (t <= TUninit || !t.maybe(TUninit));

    case DataTypeSpecific:
      return t.isKnownDataType();

    case DataTypeSpecialized:
      // Type::isSpecialized() returns true for types like {Arr<Packed>|Int}
      // and Arr has non-specialized subtypes, so we require that t is
      // specialized, a strict subtype of Obj or Arr, and that it fits the
      // specific requirements of gc.

      assertx(gc.wantClass() ^ gc.wantArrayKind());

      if (t < TObj && t.clsSpec()) {
        return gc.wantClass() &&
               t.clsSpec().cls()->classof(gc.desiredClass());
      }
      if (t < TArr && t.arrSpec()) {
        auto arrSpec = t.arrSpec();
        if (gc.wantArrayKind() && !arrSpec.kind()) return false;
        return true;
      }

      return false;
  }

  not_reached();
}
Пример #10
0
bool typeFitsConstraint(Type t, TypeConstraint tc) {
  always_assert(t != Type::Bottom);

  switch (tc.category) {
    case DataTypeGeneric:
      return true;

    case DataTypeCountness:
      // Consumers using this constraint expect the type to be relaxed to
      // Uncounted or left alone, so something like Arr|Obj isn't specific
      // enough.
      return t.notCounted() ||
             t.subtypeOfAny(Type::Str, Type::Arr, Type::Obj,
                            Type::Res, Type::BoxedCell);

    case DataTypeCountnessInit:
      return typeFitsConstraint(t, DataTypeCountness) &&
             (t <= Type::Uninit || t.not(Type::Uninit));

    case DataTypeSpecific:
      return t.isKnownDataType();

    case DataTypeSpecialized:
      // Type::isSpecialized() returns true for types like {Arr<Packed>|Int}
      // and Arr has non-specialized subtypes, so we require that t is
      // specialized, a strict subtype of Obj or Arr, and that it fits the
      // specific requirements of tc.

      assert(tc.wantClass() ^ tc.wantArrayKind());
      if (!t.isSpecialized()) return false;
      if (t < Type::Obj) {
        return tc.wantClass() && t.getClass()->classof(tc.desiredClass());
      }
      if (t < Type::Arr) {
        return tc.wantArrayKind() && t.hasArrayKind();
      }
      return false;
  }

  not_reached();
}
Пример #11
0
bool IRBuilder::constrainLocal(uint32_t locId, SSATmp* typeSrc,
                               TypeConstraint tc,
                               const std::string& why) {
  if (!shouldConstrainGuards()) return false;
  always_assert(IMPLIES(tc.innerCat > DataTypeGeneric,
                        tc.category >= DataTypeCountness));

  ITRACE(1, "constrainLocal({}, {}, {}, {})\n",
         locId, typeSrc ? typeSrc->inst()->toString() : "null", tc, why);
  Indent _i;

  if (!typeSrc) return false;
  if (!typeSrc->isA(Type::FramePtr)) {
    return constrainValue(typeSrc, tc);
  }

  // When typeSrc is a FramePtr, that means we loaded the value the local had
  // coming into the trace. Trace through the FramePtr chain, looking for a
  // guard for this local id. If we find it, constrain the guard. If we don't
  // find it, there wasn't a guard for this local so there's nothing to
  // constrain.
  auto guard = guardForLocal(locId, typeSrc);
  while (guard) {
    if (guard->is(AssertLoc)) {
      // If the refined type of the local satisfies the constraint we're
      // trying to apply, we can stop here. This can happen if we assert a
      // more general type than what we already know. Otherwise we need to
      // keep tracing back to the guard.
      if (typeFitsConstraint(guard->typeParam(), tc)) return false;
      guard = guardForLocal(locId, guard->src(0));
    } else {
      assert(guard->is(GuardLoc, CheckLoc));
      ITRACE(2, "found guard to constrain\n");
      return constrainGuard(guard, tc);
    }
  }

  ITRACE(2, "no guard to constrain\n");
  return false;
}
Пример #12
0
void TraceBuilder::constrainLocal(uint32_t locId, SSATmp* valSrc,
                                  TypeConstraint tc,
                                  const std::string& why) {
  if (!shouldConstrainGuards()) return;

  FTRACE(1, "constrainLocal({}, {}, {}, {})\n",
         locId, valSrc ? valSrc->inst()->toString() : "null", tc, why);

  if (!valSrc) return;
  if (!valSrc->isA(Type::FramePtr)) {
    constrainValue(valSrc, tc);
    return;
  }

  // When valSrc is a FramePtr, that means we loaded the value the local had
  // coming into the trace. Trace through the FramePtr chain, looking for a
  // guard for this local id. If we find it, constrain the guard. If we don't
  // find it, there wasn't a guard for this local so there's nothing to
  // constrain.
  auto guard = guardForLocal(locId, valSrc);
  while (guard) {
    if (guard->is(AssertLoc)) {
      // If the refined the type of the local satisfies the constraint we're
      // trying to apply, we can stop here. This can happen if we assert a
      // more general type than what we already know. Otherwise we need to
      // keep tracing back to the guard.
      if (typeFitsConstraint(guard->typeParam(), tc.category)) return;
      guard = guardForLocal(locId, guard->src(0));
    } else {
      assert(guard->is(GuardLoc, AssertLoc));
      FTRACE(2, "    - found guard to constrain\n");
      constrainGuard(guard, tc);
      return;
    }
  }

  FTRACE(2, "    - no guard to constrain\n");
}
Пример #13
0
/**
 * 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
}
Пример #14
0
/**
 * 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
}
Пример #15
0
/**
 * 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
}
Пример #16
0
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);
  }
}