/** * 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 }
/** * 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 }