Example #1
0
SSATmp* TraceBuilder::preOptimizeStLoc(IRInstruction* inst) {
  auto locId = inst->extra<StLoc>()->locId;
  auto const curType = localType(locId, DataTypeGeneric);
  auto const newType = inst->src(1)->type();

  assert(inst->typeParam().equals(Type::None));

  // There's no need to store the type if it's going to be the same
  // KindOfFoo. We still have to store string types because we don't
  // guard on KindOfStaticString vs. KindOfString.
  auto const bothBoxed = curType.isBoxed() && newType.isBoxed();
  auto const sameUnboxed = curType != Type::None && // TODO(#2135185)
    curType.isSameKindOf(newType) &&
    !curType.isString();
  if (bothBoxed || sameUnboxed) {
    // TODO(t2598894) once relaxGuards supports proper type reflowing, we
    // should be able to relax the constraint here and degrade StLocNT to
    // StLoc if we relax its input.
    if (sameUnboxed) constrainLocal(locId, DataTypeSpecific,
                                    "StLoc -> StLocNT");
    inst->setOpcode(StLocNT);
  }

  return nullptr;
}
Example #2
0
SSATmp* IRBuilder::preOptimizeCheckLoc(IRInstruction* inst) {
  auto const locId = inst->extra<CheckLoc>()->locId;
  Type typeParam   = inst->typeParam();
  SSATmp* src      = inst->src(0);

  if (auto const prevValue = localValue(locId, DataTypeGeneric)) {
    return gen(CheckType, typeParam, inst->taken(), prevValue);
  }

  auto const prevType = localType(locId, DataTypeGeneric);

  if (prevType <= typeParam) {
    return src;
  }

  if (prevType.not(typeParam)) {
    if (typeParam.isBoxed() && prevType.isBoxed()) {
      /* When both types are non-intersecting boxed types, we're just
       * updating the inner type hint. This requires no runtime work. */
      constrainLocal(locId, DataTypeCountness, "preOptimizeCheckLoc");
      return gen(AssertLoc, LocalId(locId), typeParam, src);
    }
    /* This check will always fail. It's probably due to an incorrect
     * prediction. Generate a Jmp, and return the source because
     * following instructions may depend on the output of CheckLoc
     * (they'll be DCEd later).  Note that we can't use convertToJmp
     * because the return value isn't nullptr, so the original
     * instruction won't be inserted into the stream. */
    gen(Jmp, inst->taken());
    return src;
  }

  return nullptr;
}
Example #3
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. Always returns val, for convenience.
 */
SSATmp* TraceBuilder::constrainValue(SSATmp* const val, DataTypeCategory cat) {
  if (!val) {
    FTRACE(1, "constrainValue(nullptr, {}), bailing\n", cat);
    return nullptr;
  }

  FTRACE(1, "constrainValue({}, {})\n", *val->inst(), cat);

  // If cat is DataTypeGeneric, there's nothing to do.
  if (cat == DataTypeGeneric) return val;

  auto inst = val->inst();

  if (inst->op() == LdLoc || inst->op() == 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<LdLocData>()->valSrc;
    if (!source) {
      // val was newly created in this trace. Nothing to constrain.
      FTRACE(2, "  - valSrc is null, bailing\n");
      return val;
    }

    // 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)) {
      constrainLocal(inst->extra<LocalId>()->locId, source, cat,
                     "constrainValue");
      return val;
    }

    // Otherwise, keep chasing down the source of val.
    constrainValue(source, cat);
  } else if (inst->op() == LdStack) {
    constrainStack(inst->src(0), inst->extra<StackOffset>()->offset, cat);
  } else if (inst->op() == CheckType) {
    // Constrain this CheckType and keep going on its source value, in case
    // there are more guards to constrain.
    constrainGuard(inst, cat);
    constrainValue(inst->src(0), cat);
  } else if (inst->op() == StRef || inst->op() == StRefNT) {
    // TODO(t2598894): This can be tightened up. As a conservative
    // approximation, pass the constraint through to the source of the value.
    constrainValue(inst->src(1), cat);
  } else if (inst->isPassthrough()) {
    constrainValue(inst->getPassthroughValue(), cat);
  } 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 val;
}
Example #4
0
SSATmp* IRBuilder::preOptimizeAssertLoc(IRInstruction* inst) {
  auto const locId = inst->extra<AssertLoc>()->locId;

  if (auto const prevValue = localValue(locId, DataTypeGeneric)) {
    return gen(AssertType, inst->typeParam(), prevValue);
  }

  return m_simplifier.simplifyAssertTypeOp(
    inst, localType(locId, DataTypeGeneric), [&](TypeConstraint tc) {
      constrainLocal(locId, tc, "preOptimizeAssertLoc");
    }
  );
}
Example #5
0
SSATmp* TraceBuilder::preOptimizeAssertLoc(IRInstruction* inst) {
  auto const locId = inst->extra<AssertLoc>()->locId;

  auto const prevType = localType(locId, DataTypeGeneric);
  auto const typeParam = inst->typeParam();

  if (prevType.not(typeParam)) {
    TRACE_PUNT("Invalid AssertLoc");
  }

  if (shouldElideAssertType(prevType, typeParam, nullptr)) {
    return inst->src(0);
  }

  if (filterAssertType(inst, prevType)) {
    constrainLocal(locId, categoryForType(prevType), "AssertLoc");
  }

  return nullptr;
}
Example #6
0
SSATmp* IRBuilder::preOptimizeCheckLoc(IRInstruction* inst) {
  auto const locId = inst->extra<CheckLoc>()->locId;
  Type typeParam = inst->typeParam();

  if (auto const prevValue = localValue(locId, DataTypeGeneric)) {
    return gen(CheckType, typeParam, inst->taken(), prevValue);
  }

  auto const prevType = localType(locId, DataTypeSpecific);

  if (prevType <= typeParam) {
    return inst->src(0);
  } else {
    //
    // Normally, it doesn't make sense to be checking something that's
    // deemed to fail.  Incompatible boxed types are ok though, since
    // we don't track them precisely, but instead check them at every
    // use.
    //
    // However, in JitPGO mode right now, this pathological case can
    // happen, because profile counters are not accurate and we
    // currently don't analyze Block post-conditions when picking its
    // successors during region selection.  This can lead to
    // incompatible types in blocks selected for the same region.
    //
    if (prevType.not(typeParam)) {
      if (typeParam.isBoxed() && prevType.isBoxed()) {
        // When both types are non-intersecting boxed types, we're just
        // updating the inner type hint. This requires no runtime work.
        constrainLocal(locId, DataTypeCountness, "preOptimizeCheckLoc");
        return gen(AssertLoc, LocalId(locId), typeParam, inst->src(0));
      } else {
        assert(RuntimeOption::EvalJitPGO);
        return gen(Jmp, inst->taken());
      }
    }
  }

  return nullptr;
}
Example #7
0
bool IRBuilder::constrainLocal(uint32_t locId, TypeConstraint tc,
                               const std::string& why) {
  if (!shouldConstrainGuards()) return false;

  return constrainLocal(locId, localTypeSource(locId), tc, why);
}
Example #8
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
}
Example #9
0
bool TraceBuilder::constrainLocal(uint32_t locId, TypeConstraint tc,
                                  const std::string& why) {
  return constrainLocal(locId, localValueSource(locId), tc, why);
}
Example #10
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
}
Example #11
0
SSATmp* TraceBuilder::localValue(unsigned id, DataTypeCategory cat) {
  always_assert(id < m_locals.size());
  constrainLocal(id, cat, "localValue");
  return m_locals[id].unsafe ? nullptr : m_locals[id].value;
}
Example #12
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
}
Example #13
0
void TraceBuilder::constrainLocal(uint32_t locId, DataTypeCategory cat,
                                  const std::string& why) {
  constrainLocal(locId, localValueSource(locId), cat, why);
}
Example #14
0
Type TraceBuilder::localType(unsigned id, DataTypeCategory cat) {
  always_assert(id < m_locals.size());
  constrainLocal(id, cat, "localType");
  return m_locals[id].type;
}
Example #15
0
Type IRBuilder::localType(uint32_t id, TypeConstraint tc) {
  constrainLocal(id, tc, "localType");
  return m_state.localType(id);
}
Example #16
0
SSATmp* IRBuilder::localValue(uint32_t id, TypeConstraint tc) {
  constrainLocal(id, tc, "localValue");
  return m_state.localValue(id);
}
Example #17
0
void TraceBuilder::constrainLocal(uint32_t locId, DataTypeCategory cat) {
    constrainLocal(locId, localValueSource(locId), cat);
}