Added IterBreakV, MIter{Init,InitK,Next,NextK,Free} and fixed memory tracking bug.
[hiphop-php.git] / hphp / runtime / vm / jit / translator.cpp
bloba33a1aaf08ea6690f22b613410df96b9043fe716
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-2013 Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
16 #include "hphp/runtime/vm/jit/translator.h"
18 // Translator front-end: parse instruction stream into basic blocks, decode
19 // and normalize instructions. Propagate run-time type info to instructions
20 // to annotate their inputs and outputs with types.
21 #include <cinttypes>
22 #include <assert.h>
23 #include <stdint.h>
24 #include <stdarg.h>
26 #include <vector>
27 #include <string>
29 #include "folly/Conv.h"
31 #include "hphp/util/trace.h"
32 #include "hphp/util/biased_coin.h"
33 #include "hphp/util/map_walker.h"
34 #include "hphp/runtime/base/file_repository.h"
35 #include "hphp/runtime/base/runtime_option.h"
36 #include "hphp/runtime/base/stats.h"
37 #include "hphp/runtime/base/types.h"
38 #include "hphp/runtime/ext/ext_continuation.h"
39 #include "hphp/runtime/ext/ext_collections.h"
40 #include "hphp/runtime/vm/hhbc.h"
41 #include "hphp/runtime/vm/bytecode.h"
42 #include "hphp/runtime/vm/jit/annotation.h"
43 #include "hphp/runtime/vm/jit/hhbctranslator.h"
44 #include "hphp/runtime/vm/jit/irfactory.h"
45 #include "hphp/runtime/vm/jit/region_selection.h"
46 #include "hphp/runtime/vm/jit/targetcache.h"
47 #include "hphp/runtime/vm/jit/translator-inline.h"
48 #include "hphp/runtime/vm/jit/translator-x64.h"
49 #include "hphp/runtime/vm/jit/type.h"
50 #include "hphp/runtime/vm/pendq.h"
51 #include "hphp/runtime/vm/treadmill.h"
52 #include "hphp/runtime/vm/type_profile.h"
53 #include "hphp/runtime/vm/runtime.h"
55 namespace HPHP {
56 namespace Transl {
58 using namespace HPHP;
59 using HPHP::JIT::Type;
60 using HPHP::JIT::HhbcTranslator;
62 TRACE_SET_MOD(trans)
64 static __thread BiasedCoin *dbgTranslateCoin;
65 Translator* transl;
66 Lease Translator::s_writeLease;
68 struct TraceletContext {
69 TraceletContext() = delete;
71 TraceletContext(Tracelet* t, const TypeMap& initialTypes)
72 : m_t(t)
73 , m_numJmps(0)
74 , m_aliasTaint(false)
75 , m_varEnvTaint(false)
77 for (auto& kv : initialTypes) {
78 TRACE(1, "%s\n",
79 Trace::prettyNode("InitialType", kv.first, kv.second).c_str());
80 m_currentMap[kv.first] = t->newDynLocation(kv.first, kv.second);
84 Tracelet* m_t;
85 ChangeMap m_currentMap;
86 DepMap m_dependencies;
87 DepMap m_resolvedDeps; // dependencies resolved by static analysis
88 LocationSet m_changeSet;
89 LocationSet m_deletedSet;
90 int m_numJmps;
91 bool m_aliasTaint;
92 bool m_varEnvTaint;
94 RuntimeType currentType(const Location& l) const;
95 DynLocation* recordRead(const InputInfo& l, bool useHHIR,
96 DataType staticType = KindOfInvalid);
97 void recordWrite(DynLocation* dl);
98 void recordDelete(const Location& l);
99 void recordJmp();
100 void aliasTaint();
101 void varEnvTaint();
104 void InstrStream::append(NormalizedInstruction* ni) {
105 if (last) {
106 assert(first);
107 last->next = ni;
108 ni->prev = last;
109 ni->next = nullptr;
110 last = ni;
111 return;
113 assert(!first);
114 first = ni;
115 last = ni;
116 ni->prev = nullptr;
117 ni->next = nullptr;
120 void InstrStream::remove(NormalizedInstruction* ni) {
121 if (ni->prev) {
122 ni->prev->next = ni->next;
123 } else {
124 first = ni->next;
126 if (ni->next) {
127 ni->next->prev = ni->prev;
128 } else {
129 last = ni->prev;
131 ni->prev = nullptr;
132 ni->next = nullptr;
135 NormalizedInstruction* Tracelet::newNormalizedInstruction() {
136 NormalizedInstruction* ni = new NormalizedInstruction();
137 m_instrs.push_back(ni);
138 return ni;
141 DynLocation* Tracelet::newDynLocation(Location l, DataType t) {
142 DynLocation* dl = new DynLocation(l, t);
143 m_dynlocs.push_back(dl);
144 return dl;
147 DynLocation* Tracelet::newDynLocation(Location l, RuntimeType t) {
148 DynLocation* dl = new DynLocation(l, t);
149 m_dynlocs.push_back(dl);
150 return dl;
153 DynLocation* Tracelet::newDynLocation() {
154 DynLocation* dl = new DynLocation();
155 m_dynlocs.push_back(dl);
156 return dl;
159 void Tracelet::print() const {
160 print(std::cerr);
163 void Tracelet::print(std::ostream& out) const {
164 const NormalizedInstruction* i = m_instrStream.first;
165 if (i == nullptr) {
166 out << "<empty>\n";
167 return;
170 out << i->unit()->filepath()->data() << ':'
171 << i->unit()->getLineNumber(i->offset()) << std::endl;
172 for (; i; i = i->next) {
173 out << " " << i->offset() << ": " << i->toString() << std::endl;
177 std::string Tracelet::toString() const {
178 std::ostringstream out;
179 print(out);
180 return out.str();
183 void sktrace(SrcKey sk, const char *fmt, ...) {
184 if (!Trace::enabled) {
185 return;
187 // We don't want to print string literals, so don't pass the unit
188 string s = instrToString((Op*)curUnit()->at(sk.offset()));
189 const char *filepath = "*anonFile*";
190 if (curUnit()->filepath()->data() &&
191 strlen(curUnit()->filepath()->data()) > 0)
192 filepath = curUnit()->filepath()->data();
193 Trace::trace("%s:%llx %6d: %20s ",
194 filepath, (unsigned long long)sk.getFuncId(),
195 sk.offset(), s.c_str());
196 va_list a;
197 va_start(a, fmt);
198 Trace::vtrace(fmt, a);
199 va_end(a);
202 SrcKey Tracelet::nextSk() const {
203 return m_instrStream.last->source.advanced(curUnit());
207 * locPhysicalOffset --
209 * Return offset, in cells, of this location from its base
210 * pointer. It needs a function descriptor to see how many locals
211 * to skip for iterators; if the current frame pointer is not the context
212 * you're looking for, be sure to pass in a non-default f.
215 Translator::locPhysicalOffset(Location l, const Func* f) {
216 f = f ? f : curFunc();
217 assert_not_implemented(l.space == Location::Stack ||
218 l.space == Location::Local ||
219 l.space == Location::Iter);
220 int localsToSkip = l.space == Location::Iter ? f->numLocals() : 0;
221 int iterInflator = l.space == Location::Iter ? kNumIterCells : 1;
222 return -((l.offset + 1) * iterInflator + localsToSkip);
225 RuntimeType Translator::liveType(Location l,
226 const Unit& u,
227 bool specialize) {
228 Cell *outer;
229 switch (l.space) {
230 case Location::Stack:
231 // Stack accesses must be to addresses pushed before
232 // translation time; if they are to addresses pushed after,
233 // they should be hitting in the changemap.
234 assert(locPhysicalOffset(l) >= 0);
235 // fallthru
236 case Location::Local: {
237 Cell *base;
238 int offset = locPhysicalOffset(l);
239 base = l.space == Location::Stack ? vmsp() : vmfp();
240 outer = &base[offset];
241 } break;
242 case Location::Iter: {
243 const Iter *it = frame_iter(curFrame(), l.offset);
244 TRACE(1, "Iter input: fp %p, iter %p, offset %" PRId64 "\n", vmfp(),
245 it, l.offset);
246 return RuntimeType(it);
247 } break;
248 case Location::Litstr: {
249 return RuntimeType(u.lookupLitstrId(l.offset));
250 } break;
251 case Location::Litint: {
252 return RuntimeType(l.offset);
253 } break;
254 case Location::This: {
255 return outThisObjectType();
256 } break;
257 default: {
258 not_reached();
261 assert(IS_REAL_TYPE(outer->m_type));
262 return liveType(outer, l, specialize);
265 RuntimeType
266 Translator::liveType(const Cell* outer,
267 const Location& l,
268 bool specialize) {
269 always_assert(analysisDepth() == 0);
271 if (!outer) {
272 // An undefined global; starts out as a variant null
273 return RuntimeType(KindOfRef, KindOfNull);
275 DataType outerType = (DataType)outer->m_type;
276 assert(IS_REAL_TYPE(outerType));
277 DataType valueType = outerType;
278 DataType innerType = KindOfInvalid;
279 const Cell* valCell = outer;
280 if (outerType == KindOfRef) {
281 // Variant. Pick up the inner type, too.
282 valCell = outer->m_data.pref->tv();
283 innerType = valCell->m_type;
284 assert(IS_REAL_TYPE(innerType));
285 valueType = innerType;
286 assert(innerType != KindOfRef);
287 TRACE(2, "liveType Var -> %d\n", innerType);
288 } else {
289 TRACE(2, "liveType %d\n", outerType);
291 const Class *klass = nullptr;
292 if (valueType == KindOfObject) {
293 // Only infer the class if specialization requested
294 if (specialize) {
295 klass = valCell->m_data.pobj->getVMClass();
298 RuntimeType retval = RuntimeType(outerType, innerType);
299 if (klass != nullptr) {
300 retval = retval.setKnownClass(klass);
302 return retval;
305 RuntimeType Translator::outThisObjectType() {
307 * Use the current method's context class (ctx) as a constraint.
308 * For instance methods, if $this is non-null, we are guaranteed
309 * that $this is an instance of ctx or a class derived from
310 * ctx. Zend allows this assumption to be violated but we have
311 * deliberately chosen to diverge from them here.
313 * Note that if analysisDepth() != 0 we'll have !hasThis() here,
314 * because our fake ActRec has no $this, but we'll still return the
315 * correct object type because arGetContextClass() looks at
316 * ar->m_func's class for methods.
318 const Class *ctx = curFunc()->isMethod() ?
319 arGetContextClass(curFrame()) : nullptr;
320 if (ctx) {
321 assert(!curFrame()->hasThis() ||
322 curFrame()->getThis()->getVMClass()->classof(ctx));
323 TRACE(2, "OutThisObject: derived from Class \"%s\"\n",
324 ctx->name()->data());
325 return RuntimeType(KindOfObject, KindOfInvalid, ctx);
327 return RuntimeType(KindOfObject, KindOfInvalid);
330 bool Translator::liveFrameIsPseudoMain() {
331 ActRec* ar = (ActRec*)vmfp();
332 return ar->hasVarEnv() && ar->getVarEnv()->isGlobalScope();
335 Location
336 Translator::tvToLocation(const TypedValue* tv, const TypedValue* frame) {
337 const Cell *arg0 = frame + locPhysicalOffset(Location(Location::Local, 0));
338 // Physical stack offsets grow downwards from the frame pointer. See
339 // locPhysicalOffset.
340 int offset = -(tv - arg0);
341 assert(offset >= 0);
342 assert(offset < ((ActRec*)frame)->m_func->numLocals());
343 TRACE(2, "tvToLocation: %p -> L:%d\n", tv, offset);
344 return Location(Location::Local, offset);
347 static int64_t typeToMask(DataType t) {
348 return (t == KindOfInvalid) ? 1 : (1 << (1 + getDataTypeIndex(t)));
351 struct InferenceRule {
352 int64_t mask;
353 DataType result;
356 static DataType inferType(const InferenceRule* rules,
357 const vector<DynLocation*>& inputs) {
358 int inputMask = 0;
359 // We generate the inputMask by ORing together the mask for each input's
360 // type.
361 for (unsigned int i = 0; i < inputs.size(); ++i) {
362 DataType inType = inputs[i]->rtt.valueType();
363 inputMask |= typeToMask(inType);
365 // This loop checks each rule in order, looking for the first rule that
366 // applies. Note that we assume there's a "catch-all" at the end.
367 for (unsigned int i = 0; ; ++i) {
368 if (rules[i].mask == 0 || (rules[i].mask & inputMask) != 0) {
369 return rules[i].result;
372 // We return KindOfInvalid by default if none of the rules applied.
373 return KindOfInvalid;
377 * Inference rules used for OutArith. These are applied in order
378 * row-by-row.
381 #define TYPE_MASK(name) \
382 static const int64_t name ## Mask = typeToMask(KindOf ## name);
383 TYPE_MASK(Invalid);
384 TYPE_MASK(Uninit);
385 TYPE_MASK(Null);
386 TYPE_MASK(Boolean);
387 static const int64_t IntMask = typeToMask(KindOfInt64);
388 TYPE_MASK(Double);
389 static const int64_t StringMask = typeToMask(KindOfString) |
390 typeToMask(KindOfStaticString);
391 TYPE_MASK(Array);
392 TYPE_MASK(Object);
394 static const InferenceRule ArithRules[] = {
395 { DoubleMask, KindOfDouble },
396 { ArrayMask, KindOfArray },
397 // If one of the inputs is known to be a String or if one of the input
398 // types is unknown, the output type is Unknown
399 { StringMask | InvalidMask, KindOfInvalid },
400 // Default to Int64
401 { 0, KindOfInt64 },
404 static const int NumArithRules = sizeof(ArithRules) / sizeof(InferenceRule);
407 * Returns the type of the output of a bitwise operator on the two
408 * DynLocs. The only case that doesn't result in KindOfInt64 is String
409 * op String.
411 static const InferenceRule BitOpRules[] = {
412 { UninitMask | NullMask | BooleanMask |
413 IntMask | DoubleMask | ArrayMask | ObjectMask,
414 KindOfInt64 },
415 { StringMask, KindOfString },
416 { 0, KindOfInvalid },
419 static RuntimeType bitOpType(DynLocation* a, DynLocation* b) {
420 vector<DynLocation*> ins;
421 ins.push_back(a);
422 if (b) ins.push_back(b);
423 return RuntimeType(inferType(BitOpRules, ins));
426 static uint32_t m_w = 1; /* must not be zero */
427 static uint32_t m_z = 1; /* must not be zero */
429 static uint32_t get_random()
431 m_z = 36969 * (m_z & 65535) + (m_z >> 16);
432 m_w = 18000 * (m_w & 65535) + (m_w >> 16);
433 return (m_z << 16) + m_w; /* 32-bit result */
436 static const int kTooPolyPred = 2;
437 static const int kTooPolyRet = 6;
439 bool
440 isNormalPropertyAccess(const NormalizedInstruction& i,
441 int propInput,
442 int objInput) {
443 const LocationCode lcode = i.immVec.locationCode();
444 return
445 i.immVecM.size() == 1 &&
446 (lcode == LC || lcode == LL || lcode == LR || lcode == LH) &&
447 mcodeMaybePropName(i.immVecM[0]) &&
448 i.inputs[propInput]->isString() &&
449 i.inputs[objInput]->valueType() == KindOfObject;
452 bool
453 mInstrHasUnknownOffsets(const NormalizedInstruction& ni, Class* context) {
454 const MInstrInfo& mii = getMInstrInfo(ni.mInstrOp());
455 unsigned mi = 0;
456 unsigned ii = mii.valCount() + 1;
457 for (; mi < ni.immVecM.size(); ++mi) {
458 MemberCode mc = ni.immVecM[mi];
459 if (mcodeMaybePropName(mc)) {
460 const Class* cls = nullptr;
461 if (getPropertyOffset(ni, context, cls, mii, mi, ii).offset == -1) {
462 return true;
464 ++ii;
465 } else {
466 return true;
470 return false;
473 PropInfo getPropertyOffset(const NormalizedInstruction& ni,
474 Class* ctx,
475 const Class*& baseClass,
476 const MInstrInfo& mii,
477 unsigned mInd, unsigned iInd) {
478 if (mInd == 0) {
479 auto const baseIndex = mii.valCount();
480 baseClass = ni.inputs[baseIndex]->rtt.isObject()
481 ? ni.inputs[baseIndex]->rtt.valueClass()
482 : nullptr;
483 } else {
484 baseClass = ni.immVecClasses[mInd - 1];
486 if (!baseClass) return PropInfo();
488 if (!ni.inputs[iInd]->rtt.isString()) {
489 return PropInfo();
491 auto* const name = ni.inputs[iInd]->rtt.valueString();
492 if (!name) return PropInfo();
494 bool accessible;
495 // If we are not in repo-authoriative mode, we need to check that
496 // baseClass cannot change in between requests
497 if (!RuntimeOption::RepoAuthoritative ||
498 !(baseClass->preClass()->attrs() & AttrUnique)) {
499 if (!ctx) return PropInfo();
500 if (!ctx->classof(baseClass)) {
501 if (baseClass->classof(ctx)) {
502 // baseClass can change on us in between requests, but since
503 // ctx is an ancestor of baseClass we can make the weaker
504 // assumption that the object is an instance of ctx
505 baseClass = ctx;
506 } else {
507 // baseClass can change on us in between requests and it is
508 // not related to ctx, so bail out
509 return PropInfo();
513 // Lookup the index of the property based on ctx and baseClass
514 Slot idx = baseClass->getDeclPropIndex(ctx, name, accessible);
515 // If we couldn't find a property that is accessible in the current
516 // context, bail out
517 if (idx == kInvalidSlot || !accessible) {
518 return PropInfo();
520 // If it's a declared property we're good to go: even if a subclass
521 // redefines an accessible property with the same name it's guaranteed
522 // to be at the same offset
523 return PropInfo(
524 baseClass->declPropOffset(idx),
525 baseClass->declPropHphpcType(idx)
529 PropInfo getFinalPropertyOffset(const NormalizedInstruction& ni,
530 Class* context,
531 const MInstrInfo& mii) {
532 unsigned mInd = ni.immVecM.size() - 1;
533 unsigned iInd = mii.valCount() + 1 + mInd;
535 const Class* cls = nullptr;
536 return getPropertyOffset(ni, context, cls, mii, mInd, iInd);
539 static std::pair<DataType,double>
540 predictMVec(const NormalizedInstruction* ni) {
541 auto info = getFinalPropertyOffset(*ni,
542 curFunc()->cls(),
543 getMInstrInfo(ni->mInstrOp()));
544 if (info.offset != -1 && info.hphpcType != KindOfInvalid) {
545 FTRACE(1, "prediction for CGetM prop: {}, hphpc\n",
546 int(info.hphpcType));
547 return std::make_pair(info.hphpcType, 1.0);
550 auto& immVec = ni->immVec;
551 StringData* name;
552 MemberCode mc;
553 if (immVec.decodeLastMember(curUnit(), name, mc)) {
554 auto pred = predictType(TypeProfileKey(mc, name));
555 TRACE(1, "prediction for CGetM %s named %s: %d, %f\n",
556 mc == MET ? "elt" : "prop",
557 name->data(),
558 pred.first,
559 pred.second);
560 return pred;
563 return std::make_pair(KindOfInvalid, 0.0);
567 * predictOutputs --
569 * Provide a best guess for the output type of this instruction.
571 static DataType
572 predictOutputs(SrcKey startSk,
573 const NormalizedInstruction* ni) {
574 if (!RuntimeOption::EvalJitTypePrediction) return KindOfInvalid;
576 if (RuntimeOption::EvalJitStressTypePredPercent &&
577 RuntimeOption::EvalJitStressTypePredPercent > int(get_random() % 100)) {
578 int dt;
579 while (true) {
580 dt = get_random() % (KindOfRef + 1);
581 switch (dt) {
582 case KindOfNull:
583 case KindOfBoolean:
584 case KindOfInt64:
585 case KindOfDouble:
586 case KindOfString:
587 case KindOfArray:
588 case KindOfObject:
589 break;
590 // KindOfRef and KindOfUninit can't happen for lots of predicted
591 // types.
592 case KindOfRef:
593 case KindOfUninit:
594 default:
595 continue;
597 break;
599 return DataType(dt);
602 if (ni->op() == OpMod) {
603 // x % 0 returns boolean false, so we don't know for certain, but it's
604 // probably an int.
605 return KindOfInt64;
608 if (ni->op() == OpDiv) {
609 // Integers can produce integers if there's no residue, but $i / $j in
610 // general produces a double. $i / 0 produces boolean false, so we have
611 // actually check the result.
612 return KindOfDouble;
615 if (ni->op() == OpClsCnsD) {
616 const NamedEntityPair& cne =
617 curFrame()->m_func->unit()->lookupNamedEntityPairId(ni->imm[1].u_SA);
618 StringData* cnsName = curUnit()->lookupLitstrId(ni->imm[0].u_SA);
619 Class* cls = cne.second->getCachedClass();
620 if (cls) {
621 DataType dt = cls->clsCnsType(cnsName);
622 if (dt != KindOfUninit) {
623 TRACE(1, "clscnsd: %s:%s prediction type %d\n",
624 cne.first->data(), cnsName->data(), dt);
625 return dt;
630 static const double kAccept = 1.0;
631 std::pair<DataType, double> pred = std::make_pair(KindOfInvalid, 0.0);
632 // Type predictions grow tracelets, and can have a side effect of making
633 // them combinatorially explode if they bring in precondtions that vary a
634 // lot. Get more conservative as evidence mounts that this is a
635 // polymorphic tracelet.
636 if (tx64->numTranslations(startSk) >= kTooPolyPred) return KindOfInvalid;
637 if (ni->op() == OpCGetS) {
638 const StringData* propName = ni->inputs[1]->rtt.valueStringOrNull();
639 if (propName) {
640 pred = predictType(TypeProfileKey(TypeProfileKey::StaticPropName,
641 propName));
642 TRACE(1, "prediction for static fields named %s: %d, %f\n",
643 propName->data(),
644 pred.first,
645 pred.second);
647 } else if (hasImmVector(ni->op())) {
648 pred = predictMVec(ni);
650 if (debug && pred.second < kAccept) {
651 if (const StringData* invName = fcallToFuncName(ni)) {
652 pred = predictType(TypeProfileKey(TypeProfileKey::MethodName, invName));
653 TRACE(1, "prediction for methods named %s: %d, %f\n",
654 invName->data(),
655 pred.first,
656 pred.second);
659 if (pred.second >= kAccept) {
660 TRACE(1, "accepting prediction of type %d\n", pred.first);
661 assert(pred.first != KindOfUninit);
662 return pred.first;
664 return KindOfInvalid;
668 * Returns the type of the value a SetOpL will store into the local.
670 static RuntimeType setOpOutputType(NormalizedInstruction* ni,
671 const vector<DynLocation*>& inputs) {
672 assert(inputs.size() == 2);
673 const int kValIdx = 0;
674 const int kLocIdx = 1;
675 unsigned char op = ni->imm[1].u_OA;
676 DynLocation locLocation(inputs[kLocIdx]->location,
677 inputs[kLocIdx]->rtt.unbox());
678 assert(inputs[kLocIdx]->location.isLocal());
679 switch (op) {
680 case SetOpPlusEqual:
681 case SetOpMinusEqual:
682 case SetOpMulEqual: {
683 // Same as OutArith, except we have to fiddle with inputs a bit.
684 vector<DynLocation*> arithInputs;
685 arithInputs.push_back(&locLocation);
686 arithInputs.push_back(inputs[kValIdx]);
687 return RuntimeType(inferType(ArithRules, arithInputs));
689 case SetOpConcatEqual: return RuntimeType(KindOfString);
690 case SetOpDivEqual:
691 case SetOpModEqual: return RuntimeType(KindOfInvalid);
692 case SetOpAndEqual:
693 case SetOpOrEqual:
694 case SetOpXorEqual: return bitOpType(&locLocation, inputs[kValIdx]);
695 case SetOpSlEqual:
696 case SetOpSrEqual: return RuntimeType(KindOfInt64);
697 default:
698 not_reached();
702 static RuntimeType
703 getDynLocType(const SrcKey startSk,
704 NormalizedInstruction* ni,
705 InstrFlags::OutTypeConstraints constraint) {
706 using namespace InstrFlags;
707 auto const& inputs = ni->inputs;
708 assert(constraint != OutFInputL);
710 switch (constraint) {
711 #define CS(OutXLike, KindOfX) \
712 case OutXLike: \
713 return RuntimeType(KindOfX);
714 CS(OutInt64, KindOfInt64);
715 CS(OutBoolean, KindOfBoolean);
716 CS(OutDouble, KindOfDouble);
717 CS(OutString, KindOfString);
718 CS(OutNull, KindOfNull);
719 CS(OutUnknown, KindOfInvalid); // Subtle interaction with BB-breaking.
720 CS(OutFDesc, KindOfInvalid); // Unclear if OutFDesc has a purpose.
721 CS(OutArray, KindOfArray);
722 CS(OutObject, KindOfObject);
723 #undef CS
724 case OutPred: {
725 auto dt = predictOutputs(startSk, ni);
726 if (dt != KindOfInvalid) ni->outputPredicted = true;
727 return RuntimeType(dt);
730 case OutClassRef: {
731 Op op = Op(ni->op());
732 if ((op == OpAGetC && inputs[0]->isString())) {
733 const StringData *sd = inputs[0]->rtt.valueString();
734 if (sd) {
735 Class *klass = Unit::lookupUniqueClass(sd);
736 TRACE(3, "KindOfClass: derived class \"%s\" from string literal\n",
737 klass ? klass->preClass()->name()->data() : "NULL");
738 return RuntimeType(klass);
740 } else if (op == OpSelf) {
741 return RuntimeType(curClass());
742 } else if (op == OpParent) {
743 Class* clss = curClass();
744 if (clss != nullptr)
745 return RuntimeType(clss->parent());
747 return RuntimeType(KindOfClass);
750 case OutCns: {
751 // If it's a system constant, burn in its type. Otherwise we have
752 // to accept prediction; use the translation-time value, or fall back
753 // to the targetcache if none exists.
754 StringData *sd = curUnit()->lookupLitstrId(ni->imm[0].u_SA);
755 assert(sd);
756 const TypedValue* tv = Unit::lookupPersistentCns(sd);
757 if (tv) {
758 return RuntimeType(tv->m_type);
760 tv = Unit::lookupCns(sd);
761 if (tv) {
762 ni->outputPredicted = true;
763 TRACE(1, "CNS %s: guessing runtime type %d\n", sd->data(), tv->m_type);
764 return RuntimeType(tv->m_type);
766 return RuntimeType(KindOfInvalid);
769 case OutNullUninit: {
770 assert(ni->op() == OpNullUninit);
771 return RuntimeType(KindOfUninit);
774 case OutStringImm: {
775 assert(ni->op() == OpString);
776 StringData *sd = curUnit()->lookupLitstrId(ni->imm[0].u_SA);
777 assert(sd);
778 return RuntimeType(sd);
781 case OutArrayImm: {
782 assert(ni->op() == OpArray);
783 ArrayData *ad = curUnit()->lookupArrayId(ni->imm[0].u_AA);
784 assert(ad);
785 return RuntimeType(ad);
788 case OutBooleanImm: {
789 assert(ni->op() == OpTrue || ni->op() == OpFalse);
790 return RuntimeType(ni->op() == OpTrue);
793 case OutThisObject: {
794 return Translator::outThisObjectType();
797 case OutVUnknown: {
798 return RuntimeType(KindOfRef, KindOfInvalid);
801 case OutArith: {
802 return RuntimeType(inferType(ArithRules, inputs));
805 case OutSameAsInput: {
807 * Relies closely on the order that inputs are pushed in
808 * getInputs(). (Pushing top of stack first for multi-stack
809 * consumers, stack elements before M-vectors and locals, etc.)
811 assert(inputs.size() >= 1);
812 auto op = ni->op();
813 ASSERT_NOT_IMPLEMENTED(
814 // Sets and binds that take multiple arguments have the rhs
815 // pushed first. In the case of the M-vector versions, the
816 // rhs comes before the M-vector elements.
817 op == OpSetL || op == OpSetN || op == OpSetG || op == OpSetS ||
818 op == OpBindL || op == OpBindG || op == OpBindS || op == OpBindN ||
819 op == OpBindM ||
820 // Dup takes a single element.
821 op == OpDup
824 const int idx = 0; // all currently supported cases.
826 if (debug) {
827 if (!inputs[idx]->rtt.isVagueValue()) {
828 if (op == OpBindG || op == OpBindN || op == OpBindS ||
829 op == OpBindM || op == OpBindL) {
830 assert(inputs[idx]->rtt.isRef() && !inputs[idx]->isLocal());
831 } else {
832 assert(inputs[idx]->rtt.valueType() ==
833 inputs[idx]->rtt.outerType());
837 return inputs[idx]->rtt;
840 case OutSetM: {
842 * SetM returns null for "invalid" inputs, or a string if the base was a
843 * string. VectorTranslator ensures that invalid inputs or a string
844 * output when we weren't expecting it will cause a side exit, so we can
845 * keep this fairly simple.
848 if (ni->immVecM.size() > 1) {
849 // We don't know the type of the base for the final operation so we
850 // can't assume anything about the output
851 // type.
852 return RuntimeType(KindOfAny);
855 // For single-element vectors, we can determine the output type from the
856 // base.
857 Type baseType;
858 switch (ni->immVec.locationCode()) {
859 case LGL: case LGC:
860 case LNL: case LNC:
861 case LSL: case LSC:
862 baseType = Type::Gen;
863 break;
865 default:
866 baseType = Type::fromRuntimeType(inputs[1]->rtt);
869 const bool setElem = mcodeMaybeArrayOrMapKey(ni->immVecM[0]);
870 const Type valType = Type::fromRuntimeType(inputs[0]->rtt);
871 if (setElem && baseType.maybe(Type::Str)) {
872 if (baseType.isString()) {
873 // The base is a string so our output is a string.
874 return RuntimeType(KindOfString);
875 } else if (!valType.isString()) {
876 // The base might be a string and our value isn't known to
877 // be a string. The output type could be Str or valType.
878 return RuntimeType(KindOfAny);
882 return inputs[0]->rtt;
885 case OutCInputL: {
886 assert(inputs.size() >= 1);
887 const DynLocation* in = inputs[inputs.size() - 1];
888 RuntimeType retval;
889 if (in->rtt.outerType() == KindOfUninit) {
890 // Locals can be KindOfUninit, so we need to convert
891 // this to KindOfNull
892 retval = RuntimeType(KindOfNull);
893 } else {
894 retval = in->rtt.unbox();
896 TRACE(2, "Input (%d, %d) -> (%d, %d)\n",
897 in->rtt.outerType(), in->rtt.innerType(),
898 retval.outerType(), retval.innerType());
899 return retval;
902 case OutIncDec: {
903 const RuntimeType &inRtt = ni->inputs[0]->rtt;
904 // TODO: instead of KindOfInvalid this should track the actual
905 // type we will get from interping a non-int IncDec.
906 return RuntimeType(IS_INT_TYPE(inRtt.valueType()) ?
907 KindOfInt64 : KindOfInvalid);
910 case OutStrlen: {
911 auto const& rtt = ni->inputs[0]->rtt;
912 return RuntimeType(rtt.isString() ? KindOfInt64 : KindOfInvalid);
915 case OutCInput: {
916 assert(inputs.size() >= 1);
917 const DynLocation* in = inputs[inputs.size() - 1];
918 if (in->rtt.outerType() == KindOfRef) {
919 return in->rtt.unbox();
921 return in->rtt;
924 case OutBitOp: {
925 assert(inputs.size() == 2 ||
926 (inputs.size() == 1 && ni->op() == OpBitNot));
927 if (inputs.size() == 2) {
928 return bitOpType(inputs[0], inputs[1]);
929 } else {
930 return bitOpType(inputs[0], nullptr);
934 case OutSetOp: {
935 return setOpOutputType(ni, inputs);
938 case OutNone:
939 default:
940 return RuntimeType(KindOfInvalid);
945 * NB: this opcode structure is sparse; it cannot just be indexed by
946 * opcode.
948 using namespace InstrFlags;
949 static const struct {
950 Op op;
951 InstrInfo info;
952 } instrInfoSparse [] = {
954 // Op Inputs Outputs OutputTypes Stack delta
955 // -- ------ ------- ----------- -----------
957 /*** 1. Basic instructions ***/
959 { OpNop, {None, None, OutNone, 0 }},
960 { OpPopC, {Stack1|
961 DontGuardStack1, None, OutNone, -1 }},
962 { OpPopV, {Stack1|
963 DontGuardStack1|
964 IgnoreInnerType, None, OutNone, -1 }},
965 { OpPopR, {Stack1|
966 DontGuardStack1|
967 IgnoreInnerType, None, OutNone, -1 }},
968 { OpDup, {Stack1, StackTop2, OutSameAsInput, 1 }},
969 { OpBox, {Stack1, Stack1, OutVInput, 0 }},
970 { OpUnbox, {Stack1, Stack1, OutCInput, 0 }},
971 { OpBoxR, {Stack1, Stack1, OutVInput, 0 }},
972 { OpUnboxR, {Stack1, Stack1, OutCInput, 0 }},
974 /*** 2. Literal and constant instructions ***/
976 { OpNull, {None, Stack1, OutNull, 1 }},
977 { OpNullUninit, {None, Stack1, OutNullUninit, 1 }},
978 { OpTrue, {None, Stack1, OutBooleanImm, 1 }},
979 { OpFalse, {None, Stack1, OutBooleanImm, 1 }},
980 { OpInt, {None, Stack1, OutInt64, 1 }},
981 { OpDouble, {None, Stack1, OutDouble, 1 }},
982 { OpString, {None, Stack1, OutStringImm, 1 }},
983 { OpArray, {None, Stack1, OutArrayImm, 1 }},
984 { OpNewArray, {None, Stack1, OutArray, 1 }},
985 { OpNewTuple, {StackN, Stack1, OutArray, 0 }},
986 { OpAddElemC, {StackTop3, Stack1, OutArray, -2 }},
987 { OpAddElemV, {StackTop3, Stack1, OutArray, -2 }},
988 { OpAddNewElemC, {StackTop2, Stack1, OutArray, -1 }},
989 { OpAddNewElemV, {StackTop2, Stack1, OutArray, -1 }},
990 { OpNewCol, {None, Stack1, OutObject, 1 }},
991 { OpColAddElemC, {StackTop3, Stack1, OutObject, -2 }},
992 { OpColAddNewElemC, {StackTop2, Stack1, OutObject, -1 }},
993 { OpCns, {None, Stack1, OutCns, 1 }},
994 { OpCnsE, {None, Stack1, OutCns, 1 }},
995 { OpCnsU, {None, Stack1, OutCns, 1 }},
996 { OpClsCns, {Stack1, Stack1, OutUnknown, 0 }},
997 { OpClsCnsD, {None, Stack1, OutPred, 1 }},
998 { OpFile, {None, Stack1, OutString, 1 }},
999 { OpDir, {None, Stack1, OutString, 1 }},
1001 /*** 3. Operator instructions ***/
1003 /* Binary string */
1004 { OpConcat, {StackTop2, Stack1, OutString, -1 }},
1005 /* Arithmetic ops */
1006 { OpAdd, {StackTop2, Stack1, OutArith, -1 }},
1007 { OpSub, {StackTop2, Stack1, OutArith, -1 }},
1008 { OpMul, {StackTop2, Stack1, OutArith, -1 }},
1009 /* Div and mod might return boolean false. Sigh. */
1010 { OpDiv, {StackTop2, Stack1, OutUnknown, -1 }},
1011 { OpMod, {StackTop2, Stack1, OutPred, -1 }},
1012 /* Logical ops */
1013 { OpXor, {StackTop2, Stack1, OutBoolean, -1 }},
1014 { OpNot, {Stack1, Stack1, OutBoolean, 0 }},
1015 { OpSame, {StackTop2, Stack1, OutBoolean, -1 }},
1016 { OpNSame, {StackTop2, Stack1, OutBoolean, -1 }},
1017 { OpEq, {StackTop2, Stack1, OutBoolean, -1 }},
1018 { OpNeq, {StackTop2, Stack1, OutBoolean, -1 }},
1019 { OpLt, {StackTop2, Stack1, OutBoolean, -1 }},
1020 { OpLte, {StackTop2, Stack1, OutBoolean, -1 }},
1021 { OpGt, {StackTop2, Stack1, OutBoolean, -1 }},
1022 { OpGte, {StackTop2, Stack1, OutBoolean, -1 }},
1023 /* Bitwise ops */
1024 { OpBitAnd, {StackTop2, Stack1, OutBitOp, -1 }},
1025 { OpBitOr, {StackTop2, Stack1, OutBitOp, -1 }},
1026 { OpBitXor, {StackTop2, Stack1, OutBitOp, -1 }},
1027 { OpBitNot, {Stack1, Stack1, OutBitOp, 0 }},
1028 { OpShl, {StackTop2, Stack1, OutInt64, -1 }},
1029 { OpShr, {StackTop2, Stack1, OutInt64, -1 }},
1030 /* Cast instructions */
1031 { OpCastBool, {Stack1, Stack1, OutBoolean, 0 }},
1032 { OpCastInt, {Stack1, Stack1, OutInt64, 0 }},
1033 { OpCastDouble, {Stack1, Stack1, OutDouble, 0 }},
1034 { OpCastString, {Stack1, Stack1, OutString, 0 }},
1035 { OpCastArray, {Stack1, Stack1, OutArray, 0 }},
1036 { OpCastObject, {Stack1, Stack1, OutObject, 0 }},
1037 { OpInstanceOf, {StackTop2, Stack1, OutBoolean, -1 }},
1038 { OpInstanceOfD, {Stack1, Stack1, OutBoolean, 0 }},
1039 { OpPrint, {Stack1, Stack1, OutInt64, 0 }},
1040 { OpClone, {Stack1, Stack1, OutObject, 0 }},
1041 { OpExit, {Stack1, None, OutNone, -1 }},
1042 { OpFatal, {Stack1, None, OutNone, -1 }},
1044 /*** 4. Control flow instructions ***/
1046 { OpJmp, {None, None, OutNone, 0 }},
1047 { OpJmpZ, {Stack1, None, OutNone, -1 }},
1048 { OpJmpNZ, {Stack1, None, OutNone, -1 }},
1049 { OpSwitch, {Stack1, None, OutNone, -1 }},
1050 { OpSSwitch, {Stack1, None, OutNone, -1 }},
1052 * RetC and RetV are special. Their manipulation of the runtime stack are
1053 * outside the boundaries of the tracelet abstraction; since they always end
1054 * a basic block, they behave more like "glue" between BBs than the
1055 * instructions in the body of a BB.
1057 * RetC and RetV consume a value from the stack, and this value's type needs
1058 * to be known at compile-time.
1060 { OpRetC, {AllLocals, None, OutNone, 0 }},
1061 { OpRetV, {AllLocals, None, OutNone, 0 }},
1062 { OpThrow, {Stack1, None, OutNone, -1 }},
1063 { OpUnwind, {None, None, OutNone, 0 }},
1065 /*** 5. Get instructions ***/
1067 { OpCGetL, {Local, Stack1, OutCInputL, 1 }},
1068 { OpCGetL2, {Stack1|Local, StackIns1, OutCInputL, 1 }},
1069 { OpCGetL3, {StackTop2|Local, StackIns2, OutCInputL, 1 }},
1070 { OpCGetN, {Stack1, Stack1, OutUnknown, 0 }},
1071 { OpCGetG, {Stack1, Stack1, OutUnknown, 0 }},
1072 { OpCGetS, {StackTop2, Stack1, OutPred, -1 }},
1073 { OpCGetM, {MVector, Stack1, OutPred, 1 }},
1074 { OpVGetL, {Local, Stack1, OutVInputL, 1 }},
1075 { OpVGetN, {Stack1, Stack1, OutVUnknown, 0 }},
1076 // TODO: In pseudo-main, the VGetG instruction invalidates what we know
1077 // about the types of the locals because it could cause any one of the
1078 // local variables to become "boxed". We need to add logic to tracelet
1079 // analysis to deal with this properly.
1080 { OpVGetG, {Stack1, Stack1, OutVUnknown, 0 }},
1081 { OpVGetS, {StackTop2, Stack1, OutVUnknown, -1 }},
1082 { OpVGetM, {MVector, Stack1|Local, OutVUnknown, 1 }},
1083 { OpAGetC, {Stack1, Stack1, OutClassRef, 0 }},
1084 { OpAGetL, {Local, Stack1, OutClassRef, 1 }},
1086 /*** 6. Isset, Empty, and type querying instructions ***/
1088 { OpAKExists, {StackTop2, Stack1, OutBoolean, -1 }},
1089 { OpIssetL, {Local, Stack1, OutBoolean, 1 }},
1090 { OpIssetN, {Stack1, Stack1, OutBoolean, 0 }},
1091 { OpIssetG, {Stack1, Stack1, OutBoolean, 0 }},
1092 { OpIssetS, {StackTop2, Stack1, OutBoolean, -1 }},
1093 { OpIssetM, {MVector, Stack1, OutBoolean, 1 }},
1094 { OpEmptyL, {Local, Stack1, OutBoolean, 1 }},
1095 { OpEmptyN, {Stack1, Stack1, OutBoolean, 0 }},
1096 { OpEmptyG, {Stack1, Stack1, OutBoolean, 0 }},
1097 { OpEmptyS, {StackTop2, Stack1, OutBoolean, -1 }},
1098 { OpEmptyM, {MVector, Stack1, OutBoolean, 1 }},
1099 { OpIsNullC, {Stack1, Stack1, OutBoolean, 0 }},
1100 { OpIsBoolC, {Stack1, Stack1, OutBoolean, 0 }},
1101 { OpIsIntC, {Stack1, Stack1, OutBoolean, 0 }},
1102 { OpIsDoubleC, {Stack1, Stack1, OutBoolean, 0 }},
1103 { OpIsStringC, {Stack1, Stack1, OutBoolean, 0 }},
1104 { OpIsArrayC, {Stack1, Stack1, OutBoolean, 0 }},
1105 { OpIsObjectC, {Stack1, Stack1, OutBoolean, 0 }},
1106 { OpIsNullL, {Local, Stack1, OutBoolean, 1 }},
1107 { OpIsBoolL, {Local, Stack1, OutBoolean, 1 }},
1108 { OpIsIntL, {Local, Stack1, OutBoolean, 1 }},
1109 { OpIsDoubleL, {Local, Stack1, OutBoolean, 1 }},
1110 { OpIsStringL, {Local, Stack1, OutBoolean, 1 }},
1111 { OpIsArrayL, {Local, Stack1, OutBoolean, 1 }},
1112 { OpIsObjectL, {Local, Stack1, OutBoolean, 1 }},
1114 /*** 7. Mutator instructions ***/
1116 { OpSetL, {Stack1|Local, Stack1|Local, OutSameAsInput, 0 }},
1117 { OpSetN, {StackTop2, Stack1|Local, OutSameAsInput, -1 }},
1118 { OpSetG, {StackTop2, Stack1, OutSameAsInput, -1 }},
1119 { OpSetS, {StackTop3, Stack1, OutSameAsInput, -2 }},
1120 { OpSetM, {MVector|Stack1, Stack1|Local, OutSetM, 0 }},
1121 { OpSetWithRefLM,{MVector|Local , Local, OutNone, 0 }},
1122 { OpSetWithRefRM,{MVector|Stack1, Local, OutNone, -1 }},
1123 { OpSetOpL, {Stack1|Local, Stack1|Local, OutSetOp, 0 }},
1124 { OpSetOpN, {StackTop2, Stack1|Local, OutUnknown, -1 }},
1125 { OpSetOpG, {StackTop2, Stack1, OutUnknown, -1 }},
1126 { OpSetOpS, {StackTop3, Stack1, OutUnknown, -2 }},
1127 { OpSetOpM, {MVector|Stack1, Stack1|Local, OutUnknown, 0 }},
1128 { OpIncDecL, {Local, Stack1|Local, OutIncDec, 1 }},
1129 { OpIncDecN, {Stack1, Stack1|Local, OutUnknown, 0 }},
1130 { OpIncDecG, {Stack1, Stack1, OutUnknown, 0 }},
1131 { OpIncDecS, {StackTop2, Stack1, OutUnknown, -1 }},
1132 { OpIncDecM, {MVector, Stack1, OutUnknown, 1 }},
1133 { OpBindL, {Stack1|Local|
1134 IgnoreInnerType, Stack1|Local, OutSameAsInput, 0 }},
1135 { OpBindN, {StackTop2, Stack1|Local, OutSameAsInput, -1 }},
1136 { OpBindG, {StackTop2, Stack1, OutSameAsInput, -1 }},
1137 { OpBindS, {StackTop3, Stack1, OutSameAsInput, -2 }},
1138 { OpBindM, {MVector|Stack1, Stack1|Local, OutSameAsInput, 0 }},
1139 { OpUnsetL, {Local, Local, OutNone, 0 }},
1140 { OpUnsetN, {Stack1, Local, OutNone, -1 }},
1141 { OpUnsetG, {Stack1, None, OutNone, -1 }},
1142 { OpUnsetM, {MVector, None, OutNone, 0 }},
1144 /*** 8. Call instructions ***/
1146 { OpFPushFunc, {Stack1, FStack, OutFDesc,
1147 kNumActRecCells - 1 }},
1148 { OpFPushFuncD, {None, FStack, OutFDesc,
1149 kNumActRecCells }},
1150 { OpFPushFuncU, {None, FStack, OutFDesc,
1151 kNumActRecCells }},
1152 { OpFPushObjMethod,
1153 {StackTop2, FStack, OutFDesc,
1154 kNumActRecCells - 2 }},
1155 { OpFPushObjMethodD,
1156 {Stack1, FStack, OutFDesc,
1157 kNumActRecCells - 1 }},
1158 { OpFPushClsMethod,
1159 {StackTop2, FStack, OutFDesc,
1160 kNumActRecCells - 2 }},
1161 { OpFPushClsMethodF,
1162 {StackTop2, FStack, OutFDesc,
1163 kNumActRecCells - 2 }},
1164 { OpFPushClsMethodD,
1165 {None, FStack, OutFDesc,
1166 kNumActRecCells }},
1167 { OpFPushCtor, {Stack1, Stack1|FStack,OutObject,
1168 kNumActRecCells }},
1169 { OpFPushCtorD, {None, Stack1|FStack,OutObject,
1170 kNumActRecCells + 1 }},
1171 { OpFPushCufIter,{None, FStack, OutFDesc,
1172 kNumActRecCells }},
1173 { OpFPushCuf, {Stack1, FStack, OutFDesc,
1174 kNumActRecCells - 1 }},
1175 { OpFPushCufF, {Stack1, FStack, OutFDesc,
1176 kNumActRecCells - 1 }},
1177 { OpFPushCufSafe,{StackTop2|DontGuardAny,
1178 StackCufSafe, OutFDesc,
1179 kNumActRecCells }},
1180 { OpFPassC, {FuncdRef, None, OutSameAsInput, 0 }},
1181 { OpFPassCW, {FuncdRef, None, OutSameAsInput, 0 }},
1182 { OpFPassCE, {FuncdRef, None, OutSameAsInput, 0 }},
1183 { OpFPassV, {Stack1|FuncdRef, Stack1, OutUnknown, 0 }},
1184 { OpFPassR, {Stack1|FuncdRef, Stack1, OutFInputR, 0 }},
1185 { OpFPassL, {Local|FuncdRef, Stack1, OutFInputL, 1 }},
1186 { OpFPassN, {Stack1|FuncdRef, Stack1, OutUnknown, 0 }},
1187 { OpFPassG, {Stack1|FuncdRef, Stack1, OutFInputR, 0 }},
1188 { OpFPassS, {StackTop2|FuncdRef,
1189 Stack1, OutUnknown, -1 }},
1190 { OpFPassM, {MVector|FuncdRef, Stack1, OutUnknown, 1 }},
1192 * FCall is special. Like the Ret* instructions, its manipulation of the
1193 * runtime stack are outside the boundaries of the tracelet abstraction.
1195 { OpFCall, {FStack, Stack1, OutPred, 0 }},
1196 { OpFCallArray, {FStack, Stack1, OutPred,
1197 -(int)kNumActRecCells }},
1198 // TODO: output type is known
1199 { OpFCallBuiltin,{BStackN, Stack1, OutPred, 0 }},
1200 { OpCufSafeArray,{StackTop3|DontGuardAny,
1201 Stack1, OutArray, -2 }},
1202 { OpCufSafeReturn,{StackTop3|DontGuardAny,
1203 Stack1, OutUnknown, -2 }},
1204 { OpDecodeCufIter,{Stack1, None, OutNone, -1 }},
1206 /*** 11. Iterator instructions ***/
1208 { OpIterInit, {Stack1, Local, OutUnknown, -1 }},
1209 { OpMIterInit, {Stack1, Local, OutUnknown, -1 }},
1210 { OpWIterInit, {Stack1, Local, OutUnknown, -1 }},
1211 { OpIterInitK, {Stack1, Local, OutUnknown, -1 }},
1212 { OpMIterInitK, {Stack1, Local, OutUnknown, -1 }},
1213 { OpWIterInitK, {Stack1, Local, OutUnknown, -1 }},
1214 { OpIterNext, {None, Local, OutUnknown, 0 }},
1215 { OpMIterNext, {None, Local, OutUnknown, 0 }},
1216 { OpWIterNext, {None, Local, OutUnknown, 0 }},
1217 { OpIterNextK, {None, Local, OutUnknown, 0 }},
1218 { OpMIterNextK, {None, Local, OutUnknown, 0 }},
1219 { OpWIterNextK, {None, Local, OutUnknown, 0 }},
1220 { OpIterFree, {None, None, OutNone, 0 }},
1221 { OpMIterFree, {None, None, OutNone, 0 }},
1222 { OpCIterFree, {None, None, OutNone, 0 }},
1223 { OpIterBreak, {None, None, OutNone, 0 }},
1225 /*** 12. Include, eval, and define instructions ***/
1227 { OpIncl, {Stack1, Stack1, OutUnknown, 0 }},
1228 { OpInclOnce, {Stack1, Stack1, OutUnknown, 0 }},
1229 { OpReq, {Stack1, Stack1, OutUnknown, 0 }},
1230 { OpReqOnce, {Stack1, Stack1, OutUnknown, 0 }},
1231 { OpReqDoc, {Stack1, Stack1, OutUnknown, 0 }},
1232 { OpEval, {Stack1, Stack1, OutUnknown, 0 }},
1233 { OpDefFunc, {None, None, OutNone, 0 }},
1234 { OpDefTypedef, {None, None, OutNone, 0 }},
1235 { OpDefCls, {None, None, OutNone, 0 }},
1236 { OpDefCns, {Stack1, Stack1, OutBoolean, 0 }},
1238 /*** 13. Miscellaneous instructions ***/
1240 { OpThis, {None, Stack1, OutThisObject, 1 }},
1241 { OpBareThis, {None, Stack1, OutUnknown, 1 }},
1242 { OpCheckThis, {This, None, OutNone, 0 }},
1243 { OpInitThisLoc,
1244 {None, Local, OutUnknown, 0 }},
1245 { OpStaticLoc,
1246 {None, Stack1, OutBoolean, 1 }},
1247 { OpStaticLocInit,
1248 {Stack1, Local, OutVUnknown, -1 }},
1249 { OpCatch, {None, Stack1, OutObject, 1 }},
1250 { OpVerifyParamType,
1251 {Local, None, OutNone, 0 }},
1252 { OpClassExists, {StackTop2, Stack1, OutBoolean, -1 }},
1253 { OpInterfaceExists,
1254 {StackTop2, Stack1, OutBoolean, -1 }},
1255 { OpTraitExists, {StackTop2, Stack1, OutBoolean, -1 }},
1256 { OpSelf, {None, Stack1, OutClassRef, 1 }},
1257 { OpParent, {None, Stack1, OutClassRef, 1 }},
1258 { OpLateBoundCls,{None, Stack1, OutClassRef, 1 }},
1259 { OpNativeImpl, {None, None, OutNone, 0 }},
1260 { OpCreateCl, {BStackN, Stack1, OutObject, 1 }},
1261 { OpStrlen, {Stack1, Stack1, OutStrlen, 0 }},
1262 { OpIncStat, {None, None, OutNone, 0 }},
1263 { OpArrayIdx, {StackTop3, Stack1, OutUnknown, -2 }},
1265 /*** 14. Continuation instructions ***/
1267 { OpCreateCont, {None, Stack1, OutObject, 1 }},
1268 { OpContEnter, {Stack1, None, OutNone, -1 }},
1269 { OpUnpackCont, {Local, StackTop2, OutInt64, 2 }},
1270 { OpContSuspend, {Local|Stack1, None, OutNone, -1 }},
1271 { OpContSuspendK,{Local|StackTop2, None, OutNone, -2 }},
1272 { OpContRetC, {Local|Stack1, None, OutNone, -1 }},
1273 { OpContCheck, {None, None, OutNone, 0 }},
1274 { OpContRaise, {None, None, OutNone, 0 }},
1275 { OpContValid, {None, Stack1, OutBoolean, 1 }},
1276 { OpContKey, {None, Stack1, OutUnknown, 1 }},
1277 { OpContCurrent, {None, Stack1, OutUnknown, 1 }},
1278 { OpContStopped, {None, None, OutNone, 0 }},
1279 { OpContHandle, {Stack1, None, OutNone, -1 }},
1282 static hphp_hash_map<Op, InstrInfo> instrInfo;
1283 static bool instrInfoInited;
1284 static void initInstrInfo() {
1285 if (!instrInfoInited) {
1286 for (size_t i = 0; i < sizeof(instrInfoSparse) / sizeof(instrInfoSparse[0]);
1287 i++) {
1288 instrInfo[instrInfoSparse[i].op] = instrInfoSparse[i].info;
1291 instrInfoInited = true;
1295 const InstrInfo& getInstrInfo(Op op) {
1296 assert(instrInfoInited);
1297 return instrInfo[op];
1300 static int numHiddenStackInputs(const NormalizedInstruction& ni) {
1301 assert(ni.immVec.isValid());
1302 return ni.immVec.numStackValues();
1305 namespace {
1306 int64_t countOperands(uint64_t mask) {
1307 const uint64_t ignore = FuncdRef | Local | Iter | AllLocals |
1308 DontGuardLocal | DontGuardStack1 | DontBreakLocal | DontBreakStack1 |
1309 IgnoreInnerType | DontGuardAny | This;
1310 mask &= ~ignore;
1312 static const uint64_t counts[][2] = {
1313 {Stack3, 1},
1314 {Stack2, 1},
1315 {Stack1, 1},
1316 {StackIns1, 2},
1317 {StackIns2, 3},
1318 {FStack, kNumActRecCells},
1321 int64_t count = 0;
1322 for (auto const& pair : counts) {
1323 if (mask & pair[0]) {
1324 count += pair[1];
1325 mask &= ~pair[0];
1328 assert(mask == 0);
1329 return count;
1333 int64_t getStackPopped(const NormalizedInstruction& ni) {
1334 switch (ni.op()) {
1335 case OpFCall: return ni.imm[0].u_IVA + kNumActRecCells;
1336 case OpFCallArray: return kNumActRecCells + 1;
1338 case OpFCallBuiltin:
1339 case OpNewTuple:
1340 case OpCreateCl: return ni.imm[0].u_IVA;
1342 default: break;
1345 uint64_t mask = getInstrInfo(ni.op()).in;
1346 int64_t count = 0;
1348 if (mask & MVector) {
1349 count += ni.immVec.numStackValues();
1350 mask &= ~MVector;
1352 if (mask & (StackN | BStackN)) {
1353 count += ni.imm[0].u_IVA;
1354 mask &= ~(StackN | BStackN);
1357 return count + countOperands(mask);
1360 int64_t getStackPushed(const NormalizedInstruction& ni) {
1361 switch (ni.op()) {
1362 case OpFPushCufSafe: return kNumActRecCells + 2;
1364 default: break;
1367 return countOperands(getInstrInfo(ni.op()).out);
1370 int getStackDelta(const NormalizedInstruction& ni) {
1371 int hiddenStackInputs = 0;
1372 initInstrInfo();
1373 auto op = ni.op();
1374 switch (op) {
1375 case OpFCall: {
1376 int numArgs = ni.imm[0].u_IVA;
1377 return 1 - numArgs - kNumActRecCells;
1380 case OpFCallBuiltin:
1381 case OpNewTuple:
1382 case OpCreateCl:
1383 return 1 - ni.imm[0].u_IVA;
1385 default:
1386 break;
1388 const InstrInfo& info = instrInfo[op];
1389 if (info.in & MVector) {
1390 hiddenStackInputs = numHiddenStackInputs(ni);
1391 SKTRACE(2, ni.source, "Has %d hidden stack inputs\n", hiddenStackInputs);
1393 int delta = instrInfo[op].numPushed - hiddenStackInputs;
1394 return delta;
1397 static NormalizedInstruction* findInputSrc(NormalizedInstruction* ni,
1398 DynLocation* dl) {
1399 while (ni != nullptr) {
1400 if (ni->outStack == dl ||
1401 ni->outLocal == dl ||
1402 ni->outLocal2 == dl ||
1403 ni->outStack2 == dl ||
1404 ni->outStack3 == dl) {
1405 break;
1407 ni = ni->prev;
1409 return ni;
1413 * For MetaData information that affects whether we want to even put a
1414 * value in the ni->inputs, we need to look at it before we call
1415 * getInputs(), so this is separate from applyInputMetaData.
1417 * We also check GuardedThis here, since RetC is short-circuited in
1418 * applyInputMetaData.
1420 void Translator::preInputApplyMetaData(Unit::MetaHandle metaHand,
1421 NormalizedInstruction* ni) {
1422 if (!metaHand.findMeta(ni->unit(), ni->offset())) return;
1424 Unit::MetaInfo info;
1425 while (metaHand.nextArg(info)) {
1426 switch (info.m_kind) {
1427 case Unit::MetaInfo::Kind::NonRefCounted:
1428 ni->nonRefCountedLocals.resize(curFunc()->numLocals());
1429 ni->nonRefCountedLocals[info.m_data] = 1;
1430 break;
1431 case Unit::MetaInfo::Kind::GuardedThis:
1432 ni->guardedThis = true;
1433 break;
1434 default:
1435 break;
1440 bool Translator::applyInputMetaData(Unit::MetaHandle& metaHand,
1441 NormalizedInstruction* ni,
1442 TraceletContext& tas,
1443 InputInfos &inputInfos) {
1444 if (!metaHand.findMeta(ni->unit(), ni->offset())) return false;
1446 Unit::MetaInfo info;
1447 if (!metaHand.nextArg(info)) return false;
1448 if (info.m_kind == Unit::MetaInfo::Kind::NopOut) {
1449 ni->noOp = true;
1450 return true;
1454 * We need to adjust the indexes in MetaInfo::m_arg if this
1455 * instruction takes other stack arguments than those related to the
1456 * MVector. (For example, the rhs of an assignment.)
1458 const InstrInfo& iInfo = instrInfo[ni->op()];
1459 if (iInfo.in & AllLocals) {
1461 * RetC/RetV dont care about their stack input, but it may have
1462 * been annotated. Skip it (because RetC/RetV pretend they dont
1463 * have a stack input).
1465 return false;
1467 if (iInfo.in == FuncdRef) {
1469 * FPassC* pretend to have no inputs
1471 return false;
1473 const int base = !(iInfo.in & MVector) ? 0 :
1474 !(iInfo.in & Stack1) ? 0 :
1475 !(iInfo.in & Stack2) ? 1 :
1476 !(iInfo.in & Stack3) ? 2 : 3;
1478 do {
1479 SKTRACE(3, ni->source, "considering MetaInfo of kind %d\n", info.m_kind);
1481 int arg = info.m_arg & Unit::MetaInfo::VectorArg ?
1482 base + (info.m_arg & ~Unit::MetaInfo::VectorArg) : info.m_arg;
1484 switch (info.m_kind) {
1485 case Unit::MetaInfo::Kind::NoSurprise:
1486 ni->noSurprise = true;
1487 break;
1488 case Unit::MetaInfo::Kind::GuardedCls:
1489 ni->guardedCls = true;
1490 break;
1491 case Unit::MetaInfo::Kind::ArrayCapacity:
1492 ni->imm[0].u_IVA = info.m_data;
1493 break;
1494 case Unit::MetaInfo::Kind::DataTypePredicted: {
1495 // If the original type was invalid or predicted, then use the
1496 // prediction in the meta-data.
1497 assert((unsigned) arg < inputInfos.size());
1499 SKTRACE(1, ni->source, "MetaInfo DataTypePredicted for input %d; "
1500 "newType = %d\n", arg, DataType(info.m_data));
1501 InputInfo& ii = inputInfos[arg];
1502 DynLocation* dl = tas.recordRead(ii, false, KindOfInvalid);
1503 NormalizedInstruction* src = findInputSrc(tas.m_t->m_instrStream.last,
1504 dl);
1505 if (src) {
1506 // Update the rtt and mark src's output as predicted if either:
1507 // a) we don't have type information yet (ie, it's KindOfInvalid), or
1508 // b) src's output was predicted. This is assuming that the
1509 // front-end's prediction is more accurate.
1510 if (dl->rtt.outerType() == KindOfInvalid || src->outputPredicted) {
1511 SKTRACE(1, ni->source, "MetaInfo DataTypePredicted for input %d; "
1512 "replacing oldType = %d with newType = %d\n", arg,
1513 dl->rtt.outerType(), DataType(info.m_data));
1514 dl->rtt = RuntimeType((DataType)info.m_data);
1515 src->outputPredicted = true;
1516 src->outputPredictionStatic = true;
1519 break;
1521 case Unit::MetaInfo::Kind::DataTypeInferred: {
1522 assert((unsigned)arg < inputInfos.size());
1523 SKTRACE(1, ni->source, "MetaInfo DataTypeInferred for input %d; "
1524 "newType = %d\n", arg, DataType(info.m_data));
1525 InputInfo& ii = inputInfos[arg];
1526 ii.dontGuard = true;
1527 DynLocation* dl = tas.recordRead(ii, true, (DataType)info.m_data);
1528 if (dl->rtt.outerType() != info.m_data &&
1529 (!dl->isString() || info.m_data != KindOfString)) {
1530 if (dl->rtt.outerType() != KindOfInvalid) {
1531 // Either static analysis is wrong, or
1532 // this was mis-predicted by the type
1533 // profiler, or this code is unreachable,
1534 // and there's an earlier bytecode in the tracelet
1535 // thats going to fatal
1536 NormalizedInstruction *src = nullptr;
1537 if (mapContains(tas.m_changeSet, dl->location)) {
1538 src = findInputSrc(tas.m_t->m_instrStream.last, dl);
1539 if (src && src->outputPredicted) {
1540 src->outputPredicted = false;
1541 } else {
1542 src = nullptr;
1545 if (!src) {
1546 // Not a type-profiler mis-predict
1547 if (tas.m_t->m_instrStream.first) {
1548 // We're not the first instruction, so punt
1549 // If this bytecode /is/ reachable, we'll
1550 // get here again, and that time, we will
1551 // be the first instruction
1552 punt();
1554 not_reached();
1557 dl->rtt = RuntimeType((DataType)info.m_data);
1558 ni->markInputInferred(arg);
1559 } else {
1561 * Static inference confirmed the expected type
1562 * but if the expected type was provided by the type
1563 * profiler we want to clear outputPredicted to
1564 * avoid unneeded guards
1566 if (mapContains(tas.m_changeSet, dl->location)) {
1567 NormalizedInstruction *src =
1568 findInputSrc(tas.m_t->m_instrStream.last, dl);
1569 if (src->outputPredicted) {
1570 src->outputPredicted = false;
1571 ni->markInputInferred(arg);
1575 break;
1578 case Unit::MetaInfo::Kind::String: {
1579 const StringData* sd = ni->unit()->lookupLitstrId(info.m_data);
1580 assert((unsigned)arg < inputInfos.size());
1581 InputInfo& ii = inputInfos[arg];
1582 ii.dontGuard = true;
1583 DynLocation* dl = tas.recordRead(ii, true, KindOfString);
1584 assert(!dl->rtt.isString() || !dl->rtt.valueString() ||
1585 dl->rtt.valueString() == sd);
1586 SKTRACE(1, ni->source, "MetaInfo on input %d; old type = %s\n",
1587 arg, dl->pretty().c_str());
1588 dl->rtt = RuntimeType(sd);
1589 break;
1592 case Unit::MetaInfo::Kind::Class: {
1593 assert((unsigned)arg < inputInfos.size());
1594 InputInfo& ii = inputInfos[arg];
1595 DynLocation* dl = tas.recordRead(ii, true);
1596 if (dl->rtt.valueType() != KindOfObject) {
1597 continue;
1600 const StringData* metaName = ni->unit()->lookupLitstrId(info.m_data);
1601 const StringData* rttName =
1602 dl->rtt.valueClass() ? dl->rtt.valueClass()->name() : nullptr;
1603 // The two classes might not be exactly the same, which is ok
1604 // as long as metaCls is more derived than rttCls.
1605 Class* metaCls = Unit::lookupUniqueClass(metaName);
1606 Class* rttCls = rttName ? Unit::lookupUniqueClass(rttName) : nullptr;
1607 if (metaCls && rttCls && metaCls != rttCls &&
1608 !metaCls->classof(rttCls)) {
1609 // Runtime type is more derived
1610 metaCls = 0;
1612 if (metaCls && metaCls != rttCls) {
1613 SKTRACE(1, ni->source, "replacing input %d with a MetaInfo-supplied "
1614 "class of %s; old type = %s\n",
1615 arg, metaName->data(), dl->pretty().c_str());
1616 if (dl->rtt.isRef()) {
1617 dl->rtt = RuntimeType(KindOfRef, KindOfObject, metaCls);
1618 } else {
1619 dl->rtt = RuntimeType(KindOfObject, KindOfInvalid, metaCls);
1622 break;
1625 case Unit::MetaInfo::Kind::MVecPropClass: {
1626 const StringData* metaName = ni->unit()->lookupLitstrId(info.m_data);
1627 Class* metaCls = Unit::lookupUniqueClass(metaName);
1628 if (metaCls) {
1629 ni->immVecClasses[arg] = metaCls;
1631 break;
1634 case Unit::MetaInfo::Kind::NopOut:
1635 // NopOut should always be the first and only annotation
1636 // and was handled above.
1637 not_reached();
1639 case Unit::MetaInfo::Kind::GuardedThis:
1640 case Unit::MetaInfo::Kind::NonRefCounted:
1641 // fallthrough; these are handled in preInputApplyMetaData.
1642 case Unit::MetaInfo::Kind::None:
1643 break;
1645 } while (metaHand.nextArg(info));
1647 return false;
1650 static void addMVectorInputs(NormalizedInstruction& ni,
1651 int& currentStackOffset,
1652 std::vector<InputInfo>& inputs) {
1653 assert(ni.immVec.isValid());
1654 ni.immVecM.reserve(ni.immVec.size());
1656 int UNUSED stackCount = 0;
1657 int UNUSED localCount = 0;
1659 currentStackOffset -= ni.immVec.numStackValues();
1660 int localStackOffset = currentStackOffset;
1662 auto push_stack = [&] {
1663 ++stackCount;
1664 inputs.emplace_back(Location(Location::Stack, localStackOffset++));
1666 auto push_local = [&] (int imm) {
1667 ++localCount;
1668 inputs.emplace_back(Location(Location::Local, imm));
1672 * Note that we have to push as we go so that the arguments come in
1673 * the order expected for the M-vector.
1675 * Indexes into these argument lists must also be in the same order
1676 * as the information in Unit::MetaInfo, because the analysis phase
1677 * may replace some of them with literals.
1681 * Also note: if we eventually have immediates that are not local
1682 * ids (i.e. string ids), this analysis step is going to have to be
1683 * a bit wiser.
1685 const uint8_t* vec = ni.immVec.vec();
1686 const LocationCode lcode = LocationCode(*vec++);
1688 const bool trailingClassRef = lcode == LSL || lcode == LSC;
1690 switch (numLocationCodeStackVals(lcode)) {
1691 case 0: {
1692 if (lcode == LH) {
1693 inputs.emplace_back(Location(Location::This));
1694 } else {
1695 assert(lcode == LL || lcode == LGL || lcode == LNL);
1696 int numImms = numLocationCodeImms(lcode);
1697 for (int i = 0; i < numImms; ++i) {
1698 push_local(decodeVariableSizeImm(&vec));
1701 } break;
1702 case 1:
1703 if (lcode == LSL) {
1704 // We'll get the trailing stack value after pushing all the
1705 // member vector elements.
1706 push_local(decodeVariableSizeImm(&vec));
1707 } else {
1708 push_stack();
1710 break;
1711 case 2:
1712 push_stack();
1713 if (!trailingClassRef) {
1714 // This one is actually at the back.
1715 push_stack();
1717 break;
1718 default: not_reached();
1721 // Now push all the members in the correct order.
1722 while (vec - ni.immVec.vec() < ni.immVec.size()) {
1723 const MemberCode mcode = MemberCode(*vec++);
1724 ni.immVecM.push_back(mcode);
1726 if (mcode == MW) {
1727 // No stack and no locals.
1728 } else if (memberCodeHasImm(mcode)) {
1729 int64_t imm = decodeMemberCodeImm(&vec, mcode);
1730 if (memberCodeImmIsLoc(mcode)) {
1731 push_local(imm);
1732 } else if (memberCodeImmIsString(mcode)) {
1733 inputs.emplace_back(Location(Location::Litstr, imm));
1734 } else {
1735 assert(memberCodeImmIsInt(mcode));
1736 inputs.emplace_back(Location(Location::Litint, imm));
1738 } else {
1739 push_stack();
1741 inputs.back().dontGuardInner = true;
1744 if (trailingClassRef) {
1745 push_stack();
1748 ni.immVecClasses.resize(ni.immVecM.size());
1750 assert(vec - ni.immVec.vec() == ni.immVec.size());
1751 assert(stackCount == ni.immVec.numStackValues());
1753 SKTRACE(2, ni.source, "M-vector using %d hidden stack "
1754 "inputs, %d locals\n", stackCount, localCount);
1758 * getInputs --
1759 * Returns locations for this instruction's inputs.
1761 * Throws:
1762 * TranslationFailedExc:
1763 * Unimplemented functionality, probably an opcode.
1765 * UnknownInputExc:
1766 * Consumed a datum whose type or value could not be constrained at
1767 * translation time, because the tracelet has already modified it.
1768 * Truncate the tracelet at the preceding instruction, which must
1769 * exists because *something* modified something in it.
1771 void Translator::getInputs(SrcKey startSk,
1772 NormalizedInstruction* ni,
1773 int& currentStackOffset,
1774 InputInfos& inputs,
1775 std::function<Type(int)> localType) {
1776 #ifdef USE_TRACE
1777 const SrcKey& sk = ni->source;
1778 #endif
1779 assert(inputs.empty());
1780 if (debug && !mapContains(instrInfo, ni->op())) {
1781 fprintf(stderr, "Translator does not understand "
1782 "instruction %s\n", opcodeToName(ni->op()));
1783 assert(false);
1785 const InstrInfo& info = instrInfo[ni->op()];
1786 Operands input = info.in;
1787 if (input & FuncdRef) {
1788 inputs.needsRefCheck = true;
1790 if (input & Iter) {
1791 inputs.emplace_back(Location(Location::Iter, ni->imm[0].u_IVA));
1793 if (input & FStack) {
1794 currentStackOffset -= ni->imm[0].u_IVA; // arguments consumed
1795 currentStackOffset -= kNumActRecCells; // ActRec is torn down as well
1797 if (input & IgnoreInnerType) ni->ignoreInnerType = true;
1798 if (input & Stack1) {
1799 SKTRACE(1, sk, "getInputs: stack1 %d\n", currentStackOffset - 1);
1800 inputs.emplace_back(Location(Location::Stack, --currentStackOffset));
1801 if (input & DontGuardStack1) inputs.back().dontGuard = true;
1802 if (input & DontBreakStack1) inputs.back().dontBreak = true;
1803 if (input & Stack2) {
1804 SKTRACE(1, sk, "getInputs: stack2 %d\n", currentStackOffset - 1);
1805 inputs.emplace_back(Location(Location::Stack, --currentStackOffset));
1806 if (input & Stack3) {
1807 SKTRACE(1, sk, "getInputs: stack3 %d\n", currentStackOffset - 1);
1808 inputs.emplace_back(Location(Location::Stack, --currentStackOffset));
1812 if (input & StackN) {
1813 int numArgs = ni->imm[0].u_IVA;
1814 SKTRACE(1, sk, "getInputs: stackN %d %d\n", currentStackOffset - 1,
1815 numArgs);
1816 for (int i = 0; i < numArgs; i++) {
1817 inputs.emplace_back(Location(Location::Stack, --currentStackOffset));
1818 inputs.back().dontGuard = true;
1819 inputs.back().dontBreak = true;
1822 if (input & BStackN) {
1823 int numArgs = ni->imm[0].u_IVA;
1824 SKTRACE(1, sk, "getInputs: BStackN %d %d\n", currentStackOffset - 1,
1825 numArgs);
1826 for (int i = 0; i < numArgs; i++) {
1827 inputs.emplace_back(Location(Location::Stack, --currentStackOffset));
1830 if (input & MVector) {
1831 addMVectorInputs(*ni, currentStackOffset, inputs);
1833 if (input & Local) {
1834 // Many of the continuation instructions read local 0. All other
1835 // instructions that take a Local have its index at their first
1836 // immediate.
1837 int loc;
1838 auto insertAt = inputs.end();
1839 switch (ni->op()) {
1840 case OpUnpackCont:
1841 case OpContSuspend:
1842 case OpContSuspendK:
1843 case OpContRetC:
1844 loc = 0;
1845 break;
1847 case OpSetWithRefLM:
1848 insertAt = inputs.begin();
1849 // fallthrough
1850 case OpFPassL:
1851 loc = ni->imm[1].u_IVA;
1852 break;
1854 default:
1855 loc = ni->imm[0].u_IVA;
1856 break;
1858 SKTRACE(1, sk, "getInputs: local %d\n", loc);
1859 inputs.emplace(insertAt, Location(Location::Local, loc));
1860 if (input & DontGuardLocal) inputs.back().dontGuard = true;
1861 if (input & DontBreakLocal) inputs.back().dontBreak = true;
1864 const bool wantInlineReturn = [&] {
1865 const int localCount = curFunc()->numLocals();
1866 // Inline return causes us to guard this tracelet more precisely. If
1867 // we're already chaining to get here, just do a generic return in the
1868 // hopes of avoiding further specialization. The localCount constraint
1869 // is an unfortunate consequence of the current generic machinery not
1870 // working for 0 locals.
1871 if (tx64->numTranslations(startSk) >= kTooPolyRet && localCount > 0) {
1872 return false;
1874 ni->nonRefCountedLocals.resize(localCount);
1875 int numRefCounted = 0;
1876 for (int i = 0; i < localCount; ++i) {
1877 auto curType = localType(i);
1878 if (ni->nonRefCountedLocals[i]) {
1879 assert(curType.notCounted() && "Static analysis was wrong");
1881 numRefCounted += curType.maybeCounted();
1883 return numRefCounted <= kMaxInlineReturnDecRefs;
1884 }();
1886 if ((input & AllLocals) && wantInlineReturn) {
1887 ni->inlineReturn = true;
1888 ni->ignoreInnerType = true;
1889 int n = curFunc()->numLocals();
1890 for (int i = 0; i < n; ++i) {
1891 if (!ni->nonRefCountedLocals[i]) {
1892 inputs.emplace_back(Location(Location::Local, i));
1897 SKTRACE(1, sk, "stack args: virtual sfo now %d\n", currentStackOffset);
1898 TRACE(1, "%s\n", Trace::prettyNode("Inputs", inputs).c_str());
1900 if (inputs.size() &&
1901 ((input & DontGuardAny) || dontGuardAnyInputs(ni->op()))) {
1902 for (int i = inputs.size(); i--; ) {
1903 inputs[i].dontGuard = true;
1906 if (input & This) {
1907 inputs.emplace_back(Location(Location::This));
1911 bool outputDependsOnInput(const Op instr) {
1912 switch (instrInfo[instr].type) {
1913 case OutNull:
1914 case OutNullUninit:
1915 case OutString:
1916 case OutStringImm:
1917 case OutDouble:
1918 case OutBoolean:
1919 case OutBooleanImm:
1920 case OutInt64:
1921 case OutArray:
1922 case OutArrayImm:
1923 case OutObject:
1924 case OutThisObject:
1925 case OutUnknown:
1926 case OutVUnknown:
1927 case OutClassRef:
1928 case OutPred:
1929 case OutCns:
1930 case OutStrlen:
1931 case OutNone:
1932 return false;
1933 case OutFDesc:
1934 case OutSameAsInput:
1935 case OutCInput:
1936 case OutVInput:
1937 case OutCInputL:
1938 case OutVInputL:
1939 case OutFInputL:
1940 case OutFInputR:
1941 case OutArith:
1942 case OutBitOp:
1943 case OutSetOp:
1944 case OutIncDec:
1945 case OutSetM:
1946 return true;
1948 not_reached();
1952 * getOutputs --
1953 * Builds a vector describing this instruction's outputs. Also
1954 * records any write to a value that *might* alias a local.
1956 * Throws:
1957 * TranslationFailedExc:
1958 * Unimplemented functionality, probably an opcode.
1960 void Translator::getOutputs(/*inout*/ Tracelet& t,
1961 /*inout*/ NormalizedInstruction* ni,
1962 /*inout*/ int& currentStackOffset,
1963 /*out*/ bool& varEnvTaint) {
1964 varEnvTaint = false;
1966 const vector<DynLocation*>& inputs = ni->inputs;
1967 const Op op = ni->op();
1969 initInstrInfo();
1970 assert_not_implemented(instrInfo.find(op) != instrInfo.end());
1971 const Operands outLocs = instrInfo[op].out;
1972 const OutTypeConstraints typeInfo = instrInfo[op].type;
1974 SKTRACE(1, ni->source, "output flavor %d\n", typeInfo);
1975 if (typeInfo == OutFInputL || typeInfo == OutFInputR ||
1976 typeInfo == OutVInputL) {
1977 // Variable number of outputs. If we box the loc we're reading,
1978 // we need to write out its boxed-ness.
1979 assert(inputs.size() >= 1);
1980 const DynLocation* in = inputs[inputs.size() - 1];
1981 DynLocation* outDynLoc = t.newDynLocation(in->location, in->rtt);
1982 outDynLoc->location = Location(Location::Stack, currentStackOffset++);
1983 bool isRef;
1984 if (typeInfo == OutVInputL) {
1985 isRef = true;
1986 } else {
1987 assert(typeInfo == OutFInputL || typeInfo == OutFInputR);
1988 isRef = ni->preppedByRef;
1990 if (isRef) {
1991 // Locals can be KindOfUninit, so we need to convert
1992 // this to KindOfNull
1993 if (in->rtt.outerType() == KindOfUninit) {
1994 outDynLoc->rtt = RuntimeType(KindOfRef, KindOfNull);
1995 } else {
1996 outDynLoc->rtt = in->rtt.box();
1998 SKTRACE(1, ni->source, "boxed type: %d -> %d\n",
1999 outDynLoc->rtt.outerType(), outDynLoc->rtt.innerType());
2000 } else {
2001 if (outDynLoc->rtt.outerType() == KindOfUninit) {
2002 outDynLoc->rtt = RuntimeType(KindOfNull);
2003 } else {
2004 outDynLoc->rtt = outDynLoc->rtt.unbox();
2006 SKTRACE(1, ni->source, "unboxed type: %d\n",
2007 outDynLoc->rtt.outerType());
2009 assert(outDynLoc->location.isStack());
2010 ni->outStack = outDynLoc;
2012 if (isRef && in->rtt.outerType() != KindOfRef &&
2013 typeInfo != OutFInputR &&
2014 in->location.isLocal()) {
2015 // VGetH or FPassH boxing a local
2016 DynLocation* smashedLocal =
2017 t.newDynLocation(in->location, outDynLoc->rtt);
2018 assert(smashedLocal->location.isLocal());
2019 ni->outLocal = smashedLocal;
2021 // Other things that might be getting boxed here include globals
2022 // and array values; since we don't attempt to track these things'
2023 // types in symbolic execution anyway, we can ignore them.
2024 return;
2027 int opnd = None;
2028 for (int outLocsCopy = (int)outLocs;
2029 outLocsCopy != (int)None;
2030 outLocsCopy &= ~opnd) {
2031 opnd = 1 << (ffs(outLocsCopy) - 1);
2032 assert(opnd != None && opnd != Stack3); // no instr produces 3 values
2033 assert(opnd != FuncdRef); // reffiness is immutable
2034 Location loc;
2035 switch (opnd) {
2036 // Pseudo-outputs that affect translator state
2037 case FStack: {
2038 currentStackOffset += kNumActRecCells;
2039 if (op == OpFPushFuncD) {
2040 const Unit& cu = *ni->unit();
2041 Id funcId = ni->imm[1].u_SA;
2042 const NamedEntityPair &nep = cu.lookupNamedEntityPairId(funcId);
2043 const Func* f = Unit::lookupFunc(nep.second);
2044 if (f && f->isNameBindingImmutable(&cu)) {
2045 t.m_arState.pushFuncD(f);
2046 } else {
2047 t.m_arState.pushDynFunc();
2049 } else {
2050 // Non-deterministic in some way
2051 t.m_arState.pushDynFunc();
2053 } continue; // no instr-associated output
2055 case Local: {
2056 if (op == OpSetN || op == OpSetOpN || op == OpIncDecN ||
2057 op == OpBindN || op == OpUnsetN) {
2058 varEnvTaint = true;
2059 continue;
2061 ASSERT_NOT_IMPLEMENTED(op == OpSetOpL ||
2062 op == OpSetM || op == OpSetOpM ||
2063 op == OpBindM ||
2064 op == OpSetWithRefLM || op == OpSetWithRefRM ||
2065 op == OpIncDecL ||
2066 op == OpVGetM ||
2067 op == OpStaticLocInit || op == OpInitThisLoc ||
2068 op == OpSetL || op == OpBindL ||
2069 op == OpUnsetL ||
2070 op == OpIterInit || op == OpIterInitK ||
2071 op == OpMIterInit || op == OpMIterInitK ||
2072 op == OpWIterInit || op == OpWIterInitK ||
2073 op == OpIterNext || op == OpIterNextK ||
2074 op == OpMIterNext || op == OpMIterNextK ||
2075 op == OpWIterNext || op == OpWIterNextK);
2076 if (op == OpIncDecL) {
2077 assert(ni->inputs.size() == 1);
2078 const RuntimeType &inRtt = ni->inputs[0]->rtt;
2079 RuntimeType rtt = IS_INT_TYPE(inRtt.valueType()) ? inRtt :
2080 RuntimeType(KindOfInvalid);
2081 DynLocation* incDecLoc =
2082 t.newDynLocation(ni->inputs[0]->location, rtt);
2083 assert(incDecLoc->location.isLocal());
2084 ni->outLocal = incDecLoc;
2085 continue; // Doesn't mutate a loc's types for int. Carry on.
2087 if (op == OpUnsetL) {
2088 assert(ni->inputs.size() == 1);
2089 DynLocation* inLoc = ni->inputs[0];
2090 assert(inLoc->location.isLocal());
2091 RuntimeType newLhsRtt = RuntimeType(KindOfUninit);
2092 Location locLocation = inLoc->location;
2093 SKTRACE(2, ni->source, "(%s, %" PRId64 ") <- type %d\n",
2094 locLocation.spaceName(), locLocation.offset,
2095 newLhsRtt.valueType());
2096 DynLocation* unsetLoc = t.newDynLocation(locLocation, newLhsRtt);
2097 assert(unsetLoc->location.isLocal());
2098 ni->outLocal = unsetLoc;
2099 continue;
2101 if (op == OpStaticLocInit || op == OpInitThisLoc) {
2102 ni->outLocal = t.newDynLocation(Location(Location::Local,
2103 ni->imm[0].u_OA),
2104 KindOfInvalid);
2105 continue;
2107 if (op == OpSetM || op == OpSetOpM ||
2108 op == OpVGetM || op == OpBindM ||
2109 op == OpSetWithRefLM || op == OpSetWithRefRM) {
2110 switch (ni->immVec.locationCode()) {
2111 case LL: {
2112 const int kVecStart = (op == OpSetM ||
2113 op == OpSetOpM ||
2114 op == OpBindM ||
2115 op == OpSetWithRefLM ||
2116 op == OpSetWithRefRM) ?
2117 1 : 0; // 0 is rhs for SetM/SetOpM
2118 DynLocation* inLoc = ni->inputs[kVecStart];
2119 assert(inLoc->location.isLocal());
2120 Location locLoc = inLoc->location;
2121 if (inLoc->rtt.isString() ||
2122 inLoc->rtt.valueType() == KindOfBoolean) {
2123 // Strings and bools produce value-dependent results; "" and
2124 // false upgrade to an array successfully, while other values
2125 // fail and leave the lhs unmodified.
2126 DynLocation* baseLoc = t.newDynLocation(locLoc, KindOfInvalid);
2127 assert(baseLoc->isLocal());
2128 ni->outLocal = baseLoc;
2129 } else if (inLoc->rtt.valueType() == KindOfUninit ||
2130 inLoc->rtt.valueType() == KindOfNull) {
2131 RuntimeType newLhsRtt = inLoc->rtt.setValueType(
2132 mcodeMaybePropName(ni->immVecM[0]) ?
2133 KindOfObject : KindOfArray);
2134 SKTRACE(2, ni->source, "(%s, %" PRId64 ") <- type %d\n",
2135 locLoc.spaceName(), locLoc.offset,
2136 newLhsRtt.valueType());
2137 DynLocation* baseLoc = t.newDynLocation(locLoc, newLhsRtt);
2138 assert(baseLoc->location.isLocal());
2139 ni->outLocal = baseLoc;
2141 // Note (if we start translating pseudo-mains):
2143 // A SetM in pseudo-main might alias a local whose type we're
2144 // remembering:
2146 // $GLOBALS['a'] = 123; // $a :: Int
2148 // and more deviously:
2150 // $loc['b'][17] = $GLOBALS; $x = 'b'; $y = 17;
2151 // $loc[$x][$y]['a'] = 123; // $a :: Int
2152 break;
2154 case LNL:
2155 case LNC:
2156 varEnvTaint = true;
2157 break;
2158 case LGL:
2159 case LGC:
2160 break;
2161 default:
2162 break;
2164 continue;
2166 if (op == OpSetOpL) {
2167 const int kLocIdx = 1;
2168 DynLocation* inLoc = ni->inputs[kLocIdx];
2169 assert(inLoc->location.isLocal());
2170 DynLocation* dl = t.newDynLocation();
2171 dl->location = inLoc->location;
2172 dl->rtt = setOpOutputType(ni, ni->inputs);
2173 if (inLoc->isRef()) {
2174 dl->rtt = dl->rtt.box();
2176 SKTRACE(2, ni->source, "(%s, %" PRId64 ") <- type %d\n",
2177 inLoc->location.spaceName(), inLoc->location.offset,
2178 dl->rtt.valueType());
2179 assert(dl->location.isLocal());
2180 ni->outLocal = dl;
2181 continue;
2183 if (op >= OpIterInit && op <= OpWIterNextK) {
2184 assert(op == OpIterInit || op == OpIterInitK ||
2185 op == OpMIterInit || op == OpMIterInitK ||
2186 op == OpWIterInit || op == OpWIterInitK ||
2187 op == OpIterNext || op == OpIterNextK ||
2188 op == OpMIterNext || op == OpMIterNextK ||
2189 op == OpWIterNext || op == OpWIterNextK);
2190 const int kValImmIdx = 2;
2191 const int kKeyImmIdx = 3;
2192 DynLocation* outVal = t.newDynLocation();
2193 int off = ni->imm[kValImmIdx].u_IVA;
2194 outVal->location = Location(Location::Local, off);
2195 if (op == OpMIterInit || op == OpMIterInitK ||
2196 op == OpMIterNext || op == OpMIterNextK) {
2197 outVal->rtt = RuntimeType(KindOfRef, KindOfInvalid);
2198 } else {
2199 outVal->rtt = RuntimeType(KindOfInvalid);
2201 ni->outLocal = outVal;
2202 if (op == OpIterInitK || op == OpIterNextK ||
2203 op == OpWIterInitK || op == OpWIterNextK ||
2204 op == OpMIterInitK || op == OpMIterNextK) {
2205 DynLocation* outKey = t.newDynLocation();
2206 int keyOff = getImm((Op*)ni->pc(), kKeyImmIdx).u_IVA;
2207 outKey->location = Location(Location::Local, keyOff);
2208 outKey->rtt = RuntimeType(KindOfInvalid);
2209 ni->outLocal2 = outKey;
2211 continue;
2213 assert(ni->inputs.size() == 2);
2214 const int kValIdx = 0;
2215 const int kLocIdx = 1;
2216 DynLocation* inLoc = ni->inputs[kLocIdx];
2217 DynLocation* inVal = ni->inputs[kValIdx];
2218 Location locLocation = inLoc->location;
2219 // Variant RHS possible only when binding.
2220 assert(inVal->rtt.isVagueValue() ||
2221 (op == OpBindL) ==
2222 (inVal->rtt.outerType() == KindOfRef));
2223 assert(!inVal->location.isLocal());
2224 assert(inLoc->location.isLocal());
2225 RuntimeType newLhsRtt = inVal->rtt.isVagueValue() || op == OpBindL ?
2226 inVal->rtt :
2227 inLoc->rtt.setValueType(inVal->rtt.outerType());
2228 if (inLoc->rtt.outerType() == KindOfRef) {
2229 assert(newLhsRtt.outerType() == KindOfRef);
2230 } else {
2231 assert(op == OpBindL ||
2232 newLhsRtt.outerType() != KindOfRef);
2234 SKTRACE(2, ni->source, "(%s, %" PRId64 ") <- type %d\n",
2235 locLocation.spaceName(), locLocation.offset,
2236 inVal->rtt.valueType());
2237 DynLocation* outLhsLoc = t.newDynLocation(locLocation, newLhsRtt);
2238 assert(outLhsLoc->location.isLocal());
2239 ni->outLocal = outLhsLoc;
2240 } continue; // already pushed an output for the local
2242 case Stack1:
2243 case Stack2:
2244 loc = Location(Location::Stack, currentStackOffset++);
2245 break;
2246 case StackIns1: {
2247 // First stack output is where the inserted element will go.
2248 // The output code for the instruction will affect what we
2249 // think about this location.
2250 loc = Location(Location::Stack, currentStackOffset++);
2252 // The existing top is just being moved up a notch. This one
2253 // always functions as if it were OutSameAsInput.
2254 assert(ni->inputs.size() >= 1);
2255 ni->outStack2 = t.newDynLocation(
2256 Location(Location::Stack, currentStackOffset++),
2257 ni->inputs[0]->rtt
2259 } break;
2260 case StackIns2: {
2261 // Similar to StackIns1.
2262 loc = Location(Location::Stack, currentStackOffset++);
2264 // Move the top two locations up a slot.
2265 assert(ni->inputs.size() >= 2);
2266 ni->outStack2 = t.newDynLocation(
2267 Location(Location::Stack, currentStackOffset++),
2268 ni->inputs[1]->rtt
2270 ni->outStack3 = t.newDynLocation(
2271 Location(Location::Stack, currentStackOffset++),
2272 ni->inputs[0]->rtt
2274 } break;
2275 default:
2276 not_reached();
2278 DynLocation* dl = t.newDynLocation();
2279 dl->location = loc;
2280 dl->rtt = getDynLocType(t.m_sk, ni, typeInfo);
2281 SKTRACE(2, ni->source, "recording output t(%d->%d) #(%s, %" PRId64 ")\n",
2282 dl->rtt.outerType(), dl->rtt.innerType(),
2283 dl->location.spaceName(), dl->location.offset);
2284 assert(dl->location.isStack());
2285 ni->outStack = dl;
2289 void
2290 Translator::requestResetHighLevelTranslator() {
2291 if (dbgTranslateCoin) {
2292 dbgTranslateCoin->reset();
2296 bool DynLocation::canBeAliased() const {
2297 return isValue() &&
2298 ((Translator::liveFrameIsPseudoMain() && isLocal()) || isRef());
2301 // Test the type of a location without recording it as a read yet.
2302 RuntimeType TraceletContext::currentType(const Location& l) const {
2303 DynLocation* dl;
2304 if (!mapGet(m_currentMap, l, &dl)) {
2305 assert(!mapContains(m_deletedSet, l));
2306 assert(!mapContains(m_changeSet, l));
2307 return tx64->liveType(l, *curUnit());
2309 return dl->rtt;
2312 DynLocation* TraceletContext::recordRead(const InputInfo& ii,
2313 bool useHHIR,
2314 DataType staticType) {
2315 DynLocation* dl;
2316 const Location& l = ii.loc;
2317 if (!mapGet(m_currentMap, l, &dl)) {
2318 // We should never try to read a location that has been deleted
2319 assert(!mapContains(m_deletedSet, l));
2320 // If the given location was not in m_currentMap, then it shouldn't
2321 // be in m_changeSet either
2322 assert(!mapContains(m_changeSet, l));
2323 if (ii.dontGuard && !l.isLiteral()) {
2324 assert(!useHHIR || staticType != KindOfRef);
2325 dl = m_t->newDynLocation(l, RuntimeType(staticType));
2326 if (useHHIR && staticType != KindOfInvalid) {
2327 m_resolvedDeps[l] = dl;
2329 } else {
2330 RuntimeType rtt = tx64->liveType(l, *curUnit());
2331 assert(rtt.isIter() || !rtt.isVagueValue());
2332 // Allocate a new DynLocation to represent this and store it in the
2333 // current map.
2334 dl = m_t->newDynLocation(l, rtt);
2336 if (!l.isLiteral()) {
2337 if (m_varEnvTaint && dl->isValue() && dl->isLocal()) {
2338 dl->rtt = RuntimeType(KindOfInvalid);
2339 } else if ((m_aliasTaint && dl->canBeAliased()) ||
2340 (rtt.isValue() && rtt.isRef() && ii.dontGuardInner)) {
2341 dl->rtt = rtt.setValueType(KindOfInvalid);
2343 // Record that we depend on the live type of the specified location
2344 // as well (and remember what the live type was)
2345 m_dependencies[l] = dl;
2348 m_currentMap[l] = dl;
2350 TRACE(2, "recordRead: %s : %s\n", l.pretty().c_str(),
2351 dl->rtt.pretty().c_str());
2352 return dl;
2355 void TraceletContext::recordWrite(DynLocation* dl) {
2356 TRACE(2, "recordWrite: %s : %s\n", dl->location.pretty().c_str(),
2357 dl->rtt.pretty().c_str());
2358 m_currentMap[dl->location] = dl;
2359 m_changeSet.insert(dl->location);
2360 m_deletedSet.erase(dl->location);
2363 void TraceletContext::recordDelete(const Location& l) {
2364 // We should not be trying to delete the rtt of location that is
2365 // not in m_currentMap
2366 TRACE(2, "recordDelete: %s\n", l.pretty().c_str());
2367 m_currentMap.erase(l);
2368 m_changeSet.erase(l);
2369 m_deletedSet.insert(l);
2372 void TraceletContext::aliasTaint() {
2373 m_aliasTaint = true;
2374 for (ChangeMap::iterator it = m_currentMap.begin();
2375 it != m_currentMap.end(); ++it) {
2376 DynLocation* dl = it->second;
2377 if (dl->canBeAliased()) {
2378 TRACE(1, "(%s, %" PRId64 ") <- inner type invalidated\n",
2379 it->first.spaceName(), it->first.offset);
2380 RuntimeType newRtt = dl->rtt.setValueType(KindOfInvalid);
2381 it->second = m_t->newDynLocation(dl->location, newRtt);
2386 void TraceletContext::varEnvTaint() {
2387 m_varEnvTaint = true;
2388 for (ChangeMap::iterator it = m_currentMap.begin();
2389 it != m_currentMap.end(); ++it) {
2390 DynLocation* dl = it->second;
2391 if (dl->isValue() && dl->isLocal()) {
2392 TRACE(1, "(%s, %" PRId64 ") <- type invalidated\n",
2393 it->first.spaceName(), it->first.offset);
2394 it->second = m_t->newDynLocation(dl->location,
2395 RuntimeType(KindOfInvalid));
2400 void TraceletContext::recordJmp() {
2401 m_numJmps++;
2405 * Helpers for recovering context of this instruction.
2407 Op NormalizedInstruction::op() const {
2408 auto op = toOp(*pc());
2409 assert(isValidOpcode(op));
2410 return (Op)op;
2413 Op NormalizedInstruction::mInstrOp() const {
2414 Op opcode = op();
2415 #define MII(instr, a, b, i, v, d) case Op##instr##M: return opcode;
2416 switch (opcode) {
2417 MINSTRS
2418 case OpFPassM:
2419 return preppedByRef ? OpVGetM : OpCGetM;
2420 default:
2421 not_reached();
2423 #undef MII
2426 PC NormalizedInstruction::pc() const {
2427 return unit()->at(source.offset());
2430 const Unit* NormalizedInstruction::unit() const {
2431 return m_unit;
2434 Offset NormalizedInstruction::offset() const {
2435 return source.offset();
2438 std::string NormalizedInstruction::toString() const {
2439 return instrToString((Op*)pc(), unit());
2442 void Translator::postAnalyze(NormalizedInstruction* ni, SrcKey& sk,
2443 Tracelet& t, TraceletContext& tas) {
2444 if (ni->op() == OpBareThis &&
2445 ni->outStack->rtt.isVagueValue()) {
2446 SrcKey src = sk;
2447 const Unit* unit = ni->m_unit;
2448 src.advance(unit);
2449 Opcode next = *unit->at(src.offset());
2450 if (next == OpInstanceOfD || next == OpIsNullC) {
2451 ni->outStack->rtt = RuntimeType(KindOfObject);
2453 return;
2457 static bool isPop(const NormalizedInstruction* instr) {
2458 auto opc = instr->op();
2459 return (opc == OpPopC ||
2460 opc == OpPopV ||
2461 opc == OpPopR);
2464 NormalizedInstruction::OutputUse
2465 NormalizedInstruction::getOutputUsage(const DynLocation* output) const {
2466 for (NormalizedInstruction* succ = next; succ; succ = succ->next) {
2467 if (succ->noOp) continue;
2468 for (size_t i = 0; i < succ->inputs.size(); ++i) {
2469 if (succ->inputs[i] == output) {
2470 if (succ->inputWasInferred(i)) {
2471 return OutputUse::Inferred;
2473 if (Translator::Get()->dontGuardAnyInputs(succ->op())) {
2474 /* the consumer doesnt care about its inputs
2475 but we may still have inferred something about
2476 its outputs that a later instruction may depend on
2478 if (!outputDependsOnInput(succ->op()) ||
2479 !(succ->outStack && !succ->outStack->rtt.isVagueValue() &&
2480 succ->getOutputUsage(succ->outStack) != OutputUse::Used) ||
2481 !(succ->outLocal && !succ->outLocal->rtt.isVagueValue() &&
2482 succ->getOutputUsage(succ->outLocal) != OutputUse::Used)) {
2483 return OutputUse::DoesntCare;
2486 return OutputUse::Used;
2490 return OutputUse::Unused;
2493 bool NormalizedInstruction::isOutputUsed(const DynLocation* output) const {
2494 return (output && !output->rtt.isVagueValue() &&
2495 getOutputUsage(output) == OutputUse::Used);
2498 bool NormalizedInstruction::isAnyOutputUsed() const
2500 return (isOutputUsed(outStack) ||
2501 isOutputUsed(outLocal));
2504 GuardType::GuardType(DataType outer, DataType inner)
2505 : outerType(outer), innerType(inner) {
2508 GuardType::GuardType(const RuntimeType& rtt) {
2509 assert(rtt.isValue());
2510 outerType = rtt.outerType();
2511 innerType = rtt.innerType();
2514 GuardType::GuardType(const GuardType& other) {
2515 *this = other;
2519 const DataType GuardType::getOuterType() const {
2520 return outerType;
2523 const DataType GuardType::getInnerType() const {
2524 return innerType;
2527 bool GuardType::isSpecific() const {
2528 return outerType > KindOfInvalid;
2531 bool GuardType::isRelaxed() const {
2532 switch (outerType) {
2533 case KindOfAny:
2534 case KindOfUncounted:
2535 case KindOfUncountedInit:
2536 return true;
2537 default:
2538 return false;
2542 bool GuardType::isGeneric() const {
2543 return outerType == KindOfAny;
2546 bool GuardType::isCounted() const {
2547 switch (outerType) {
2548 case KindOfAny:
2549 case KindOfStaticString:
2550 case KindOfString:
2551 case KindOfArray:
2552 case KindOfObject:
2553 case KindOfRef:
2554 return true;
2555 default:
2556 return false;
2560 bool GuardType::isMoreRefinedThan(const GuardType& other) const {
2561 return getCategory() > other.getCategory();
2564 DataTypeCategory GuardType::getCategory() const {
2565 switch (outerType) {
2566 case KindOfAny: return DataTypeGeneric;
2567 case KindOfUncounted: return DataTypeCountness;
2568 case KindOfUncountedInit: return DataTypeCountnessInit;
2569 default: return DataTypeSpecific;
2573 bool GuardType::mayBeUninit() const {
2574 switch (outerType) {
2575 case KindOfAny:
2576 case KindOfUncounted:
2577 case KindOfUninit:
2578 return true;
2579 default:
2580 return false;
2584 GuardType GuardType::getCountness() const {
2585 // Note that translations need to be able to handle KindOfString and
2586 // KindOfStaticString interchangeably. This implies that KindOfStaticString
2587 // needs to be treated as KindOfString, i.e. as possibly counted.
2588 assert(isSpecific());
2589 switch (outerType) {
2590 case KindOfUninit:
2591 case KindOfNull:
2592 case KindOfBoolean:
2593 case KindOfInt64:
2594 case KindOfDouble: return GuardType(KindOfUncounted);
2595 default: return *this;
2599 GuardType GuardType::getCountnessInit() const {
2600 assert(isSpecific());
2601 switch (outerType) {
2602 case KindOfNull:
2603 case KindOfBoolean:
2604 case KindOfInt64:
2605 case KindOfDouble: return GuardType(KindOfUncountedInit);
2606 default: return *this;
2612 * Returns true iff loc is consumed by a Pop* instruction in the sequence
2613 * starting at instr.
2615 bool isPopped(DynLocation* loc, NormalizedInstruction* instr) {
2616 for (; instr ; instr = instr->next) {
2617 for (size_t i = 0; i < instr->inputs.size(); i++) {
2618 if (instr->inputs[i] == loc) {
2619 return isPop(instr);
2623 return false;
2626 DataTypeCategory
2627 Translator::getOperandConstraintCategory(NormalizedInstruction* instr,
2628 size_t opndIdx) {
2629 auto opc = instr->op();
2631 switch (opc) {
2632 case OpSetS:
2633 case OpSetG:
2634 case OpSetL: {
2635 if (opndIdx == 0) { // stack value
2636 // If the output on the stack is simply popped, then we don't
2637 // even care whether the type is ref-counted or not because
2638 // the ref-count is transfered to the target location.
2639 if (!instr->outStack || isPopped(instr->outStack, instr->next)) {
2640 return DataTypeGeneric;
2642 return DataTypeCountness;
2644 if (opc == OpSetL) {
2645 // old local value is dec-refed
2646 assert(opndIdx == 1);
2647 return DataTypeCountness;
2649 return DataTypeSpecific;
2652 case OpCGetL:
2653 return DataTypeCountnessInit;
2655 case OpRetC:
2656 case OpRetV:
2657 return DataTypeCountness;
2659 case OpFCall:
2660 // Note: instead of pessimizing calls that may be inlined with
2661 // DataTypeSpecific, we could apply the operand constraints of
2662 // the callee in constrainDep.
2663 return (instr->calleeTrace && !instr->calleeTrace->m_inliningFailed)
2664 ? DataTypeSpecific
2665 : DataTypeGeneric;
2667 case OpFCallArray:
2668 return DataTypeGeneric;
2670 case OpPopC:
2671 case OpPopV:
2672 case OpPopR:
2673 return DataTypeCountness;
2675 case OpContSuspend:
2676 case OpContSuspendK:
2677 case OpContRetC:
2678 // The stack input is teleported to the continuation's m_value field
2679 return opndIdx == 0 ? DataTypeGeneric : DataTypeSpecific;
2681 case OpContHandle:
2682 // This always calls the interpreter
2683 return DataTypeGeneric;
2685 case OpAddElemC:
2686 // The stack input is teleported to the array
2687 return opndIdx == 0 ? DataTypeGeneric : DataTypeSpecific;
2689 case OpArrayIdx:
2690 // The default value (w/ opndIdx 0) is simply passed to a helper,
2691 // which takes care of dec-refing it if needed
2692 return opndIdx == 0 ? DataTypeGeneric : DataTypeSpecific;
2694 default:
2695 return DataTypeSpecific;
2699 GuardType
2700 Translator::getOperandConstraintType(NormalizedInstruction* instr,
2701 size_t opndIdx,
2702 const GuardType& specType) {
2703 DataTypeCategory dtCategory = getOperandConstraintCategory(instr, opndIdx);
2704 switch (dtCategory) {
2705 case DataTypeGeneric: return GuardType(KindOfAny);
2706 case DataTypeCountness: return specType.getCountness();
2707 case DataTypeCountnessInit: return specType.getCountnessInit();
2708 case DataTypeSpecific:
2709 default: return specType;
2713 void Translator::constrainOperandType(GuardType& relxType,
2714 NormalizedInstruction* instr,
2715 size_t opndIdx,
2716 const GuardType& specType) {
2717 if (relxType.isSpecific()) return; // Can't constrain any further
2719 GuardType consType = getOperandConstraintType(instr, opndIdx, specType);
2720 if (consType.isMoreRefinedThan(relxType)) {
2721 relxType = consType;
2726 * This method looks at every use of loc in the stream of instructions
2727 * starting at firstInstr and constrains the relxType towards specType
2728 * according to each use. Note that this method not only looks at
2729 * direct uses of loc, but it also recursively looks at any other
2730 * DynLocs whose type depends on loc's type.
2732 void Translator::constrainDep(const DynLocation* loc,
2733 NormalizedInstruction* firstInstr,
2734 GuardType specType,
2735 GuardType& relxType) {
2736 if (relxType.isSpecific()) return; // can't contrain it any further
2738 for (NormalizedInstruction* instr = firstInstr; instr; instr = instr->next) {
2739 if (instr->noOp) continue;
2740 auto opc = instr->op();
2741 size_t nInputs = instr->inputs.size();
2742 for (size_t i = 0; i < nInputs; i++) {
2743 DynLocation* usedLoc = instr->inputs[i];
2744 if (usedLoc == loc) {
2745 constrainOperandType(relxType, instr, i, specType);
2747 // If the instruction's input doesn't propagate to its output,
2748 // then we're done. Otherwise, we need to constrain relxType
2749 // based on the uses of the output.
2750 if (!outputDependsOnInput(opc)) continue;
2752 bool outputIsStackInput = false;
2753 const DynLocation* outStack = instr->outStack;
2754 const DynLocation* outLocal = instr->outLocal;
2756 switch (instrInfo[opc].type) {
2757 case OutSameAsInput:
2758 outputIsStackInput = true;
2759 break;
2761 case OutCInput:
2762 outputIsStackInput = true;
2763 // fall-through
2764 case OutCInputL:
2765 if (specType.getOuterType() == KindOfRef &&
2766 instr->isAnyOutputUsed()) {
2767 // Value gets unboxed along the way. Pessimize it for now.
2768 relxType = specType;
2769 return;
2771 break;
2773 default:
2774 relxType = specType;
2775 return;
2778 // The instruction input's type propagates to the outputs.
2779 // So constrain the dependence further based on uses of outputs.
2780 if ((i == 0 && outputIsStackInput) || // stack input @ [0]
2781 (i == nInputs - 1 && !outputIsStackInput)) { // local input is last
2782 if (outStack && !outStack->rtt.isVagueValue()) {
2783 // For SetL, getOperandConstraintCategory() generates
2784 // DataTypeGeneric if the stack output is popped. In this
2785 // case, don't further constrain the stack output,
2786 // otherwise the Pop* would make it a DataTypeCountness.
2787 if (opc != OpSetL || !relxType.isGeneric()) {
2788 constrainDep(outStack, instr->next, specType, relxType);
2791 if (outLocal && !outLocal->rtt.isVagueValue()) {
2792 constrainDep(outLocal, instr->next, specType, relxType);
2801 * Propagates the relaxed type relxType for loc to all its consumers,
2802 * starting at firstInstr.
2804 void Translator::propagateRelaxedType(Tracelet& tclet,
2805 NormalizedInstruction* firstInstr,
2806 DynLocation* loc,
2807 const GuardType& relxType) {
2808 assert(relxType.isRelaxed());
2810 for (NormalizedInstruction* instr = firstInstr; instr; instr = instr->next) {
2811 if (instr->noOp) continue;
2812 auto opc = instr->op();
2813 size_t nInputs = instr->inputs.size();
2814 for (size_t i = 0; i < nInputs; i++) {
2815 DynLocation* usedLoc = instr->inputs[i];
2816 if (usedLoc == loc) {
2817 auto outKind = instrInfo[opc].type;
2819 // stack input propagates to outputs
2820 bool outputIsStackInput = (i == 0 && // stack input is inputs[0]
2821 (outKind == OutSameAsInput || outKind == OutCInput));
2822 bool outputIsLocalInput = (i == nInputs - 1 && // local input is last
2823 outKind == OutCInputL);
2824 bool outputIsInput = outputIsStackInput || outputIsLocalInput;
2826 if (outputIsInput) {
2827 // if instr has outStack, update its type and propagate to consumers
2828 if (instr->outStack) {
2830 TRACE(6,
2831 "propagateRelaxedType: Loc: %s oldType: %s => newType: %s\n",
2832 instr->outStack->location.pretty().c_str(),
2833 instr->outStack->rtt.pretty().c_str(),
2834 RuntimeType(relxType.getOuterType(),
2835 relxType.getInnerType()).pretty().c_str());
2837 instr->outStack->rtt = RuntimeType(relxType.getOuterType(),
2838 relxType.getInnerType());
2839 propagateRelaxedType(tclet, instr->next, instr->outStack, relxType);
2841 // if instr has outLocal, update its type and propagate to consumers
2842 if (instr->outLocal) {
2844 TRACE(6,
2845 "propagateRelaxedType: Loc: %s oldType: %s => newType: %s\n",
2846 instr->outLocal->location.pretty().c_str(),
2847 instr->outLocal->rtt.pretty().c_str(),
2848 RuntimeType(relxType.getOuterType(),
2849 relxType.getInnerType()).pretty().c_str());
2851 instr->outLocal->rtt = RuntimeType(relxType.getOuterType(),
2852 relxType.getInnerType());
2853 propagateRelaxedType(tclet, instr->next, instr->outLocal, relxType);
2862 * This method looks at all the uses of the tracelet dependencies in the
2863 * instruction stream and tries to relax the type associated with each location.
2865 void Translator::relaxDeps(Tracelet& tclet, TraceletContext& tctxt) {
2866 DynLocTypeMap locRelxTypeMap;
2868 // Initialize type maps. Relaxed types start off very relaxed, and then
2869 // they may get more specific depending on how the instructions use them.
2870 DepMap& deps = tctxt.m_dependencies;
2871 for (auto depIt = deps.begin(); depIt != deps.end(); depIt++) {
2872 DynLocation* loc = depIt->second;
2873 const RuntimeType& rtt = depIt->second->rtt;
2874 if (rtt.isValue() && !rtt.isVagueValue() && !rtt.isClass() &&
2875 !loc->location.isThis()) {
2876 GuardType relxType = GuardType(KindOfAny);
2877 GuardType specType = GuardType(rtt);
2878 constrainDep(loc, tclet.m_instrStream.first, specType, relxType);
2879 locRelxTypeMap[loc] = relxType;
2883 // For each dependency, if we found a more relaxed type for it, use
2884 // such type.
2885 for (auto& kv : locRelxTypeMap) {
2886 DynLocation* loc = kv.first;
2887 const GuardType& relxType = kv.second;
2888 if (relxType.isRelaxed()) {
2889 TRACE(1, "relaxDeps: Loc: %s oldType: %s => newType: %s\n",
2890 loc->location.pretty().c_str(),
2891 deps[loc->location]->rtt.pretty().c_str(),
2892 RuntimeType(relxType.getOuterType(),
2893 relxType.getInnerType()).pretty().c_str());
2894 assert(deps[loc->location] == loc);
2895 assert(relxType.getOuterType() != KindOfInvalid);
2896 deps[loc->location]->rtt = RuntimeType(relxType.getOuterType(),
2897 relxType.getInnerType());
2898 propagateRelaxedType(tclet, tclet.m_instrStream.first, loc, relxType);
2904 * This method looks at all the uses of the tracelet dependencies in the
2905 * instruction stream and tries to specialize the type associated
2906 * with each location.
2908 void Translator::specializeDeps(Tracelet& tclet, TraceletContext& tctxt) {
2909 // Process the instruction stream, look for CGetM/SetM/IssetM and if the
2910 // instructions are for Vector types and consistent with Vector usage
2911 // specialize the guard
2912 for (NormalizedInstruction* instr = tclet.m_instrStream.first; instr;
2913 instr = instr->next) {
2914 auto op = instr->op();
2915 if ((op == OpCGetM || op == OpIssetM || op == OpFPassM) &&
2916 instr->inputs.size() == 2) {
2917 specializeCollections(instr, 0, tctxt);
2918 } else if (op == OpSetM && instr->inputs.size() == 3) {
2919 specializeCollections(instr, 1, tctxt);
2924 void Translator::specializeCollections(NormalizedInstruction* instr,
2925 int index,
2926 TraceletContext& tctxt) {
2927 if (instr->inputs[index]->isObject()
2928 || instr->inputs[index]->isRefToObject()) {
2929 Location l = instr->inputs[index]->location;
2930 auto dep = tctxt.m_dependencies.find(l);
2931 if (dep != tctxt.m_dependencies.end()) {
2932 RuntimeType specialized = liveType(l, *curUnit(), true);
2933 const Class* klass = specialized.knownClass();
2934 if (klass != nullptr && isOptimizableCollectionClass(klass)) {
2935 tctxt.m_dependencies[l]->rtt = specialized;
2941 static bool checkTaintFuncs(StringData* name) {
2942 static const StringData* s_extract =
2943 StringData::GetStaticString("extract");
2944 return name->isame(s_extract);
2948 * Check whether the a given FCall should be analyzed for possible
2949 * inlining or not.
2951 static bool shouldAnalyzeCallee(const NormalizedInstruction* fcall,
2952 const FPIEnt* fpi,
2953 const Op pushOp) {
2954 auto const numArgs = fcall->imm[0].u_IVA;
2955 auto const target = fcall->funcd;
2957 if (!RuntimeOption::RepoAuthoritative) return false;
2959 if (pushOp != OpFPushFuncD && pushOp != OpFPushObjMethodD
2960 && pushOp != OpFPushCtorD && pushOp != OpFPushCtor
2961 && pushOp != OpFPushClsMethodD) {
2962 FTRACE(1, "analyzeCallee: push op ({}) was not supported\n",
2963 opcodeToName(pushOp));
2964 return false;
2967 if (!target) {
2968 FTRACE(1, "analyzeCallee: target func not known\n");
2969 return false;
2971 if (target->info()) {
2972 FTRACE(1, "analyzeCallee: target func is a builtin\n");
2973 return false;
2976 constexpr int kMaxSubtraceAnalysisDepth = 2;
2977 if (tx64->analysisDepth() + 1 >= kMaxSubtraceAnalysisDepth) {
2978 FTRACE(1, "analyzeCallee: max inlining depth reached\n");
2979 return false;
2982 if (numArgs != target->numParams()) {
2983 FTRACE(1, "analyzeCallee: param count mismatch {} != {}\n",
2984 numArgs, target->numParams());
2985 return false;
2987 if (target->numLocals() != target->numParams()) {
2988 FTRACE(1, "analyzeCallee: not inlining functions with more locals "
2989 "than params\n");
2990 return false;
2993 if (pushOp == OpFPushClsMethodD && target->mayHaveThis()) {
2994 FTRACE(1, "analyzeCallee: not inlining static calls which may have a "
2995 "this pointer\n");
2996 return false;
2999 // Find the fpush and ensure it's in this tracelet---refuse to
3000 // inline if there are any calls in order to prepare arguments.
3001 for (auto* ni = fcall->prev; ni; ni = ni->prev) {
3002 if (ni->source.offset() == fpi->m_fpushOff) {
3003 return true;
3005 if (isFCallStar(ni->op()) || ni->op() == OpFCallBuiltin) {
3006 FTRACE(1, "analyzeCallee: fpi region contained other calls\n");
3007 return false;
3010 FTRACE(1, "analyzeCallee: push instruction was in a different "
3011 "tracelet\n");
3012 return false;
3015 extern bool shouldIRInline(const Func* curFunc,
3016 const Func* func,
3017 const Tracelet& callee);
3019 void Translator::analyzeCallee(TraceletContext& tas,
3020 Tracelet& parent,
3021 NormalizedInstruction* fcall) {
3022 auto const callerFunc = curFunc();
3023 auto const fpi = callerFunc->findFPI(fcall->source.offset());
3024 auto const pushOp = curUnit()->getOpcode(fpi->m_fpushOff);
3026 if (!shouldAnalyzeCallee(fcall, fpi, pushOp)) return;
3028 auto const numArgs = fcall->imm[0].u_IVA;
3029 auto const target = fcall->funcd;
3032 * Prepare a map for all the known information about the argument
3033 * types.
3035 * Also, fill out KindOfUninit for any remaining locals. The point
3036 * here is that the subtrace can't call liveType for a local or
3037 * stack location (since our ActRec is fake), so we need them all in
3038 * the TraceletContext.
3040 * If any of the argument types are unknown (including inner-types
3041 * of KindOfRefs), we don't really try to analyze the callee. It
3042 * might be possible to do this but we'll need to modify the
3043 * analyzer to support unknown input types before there are any
3044 * NormalizedInstructions in the Tracelet.
3046 TypeMap initialMap;
3047 LocationSet callerArgLocs;
3048 for (int i = 0; i < numArgs; ++i) {
3049 auto callerLoc = Location(Location::Stack, fcall->stackOffset - i - 1);
3050 auto calleeLoc = Location(Location::Local, numArgs - i - 1);
3051 auto type = tas.currentType(callerLoc);
3053 callerArgLocs.insert(callerLoc);
3055 if (type.isVagueValue()) {
3056 FTRACE(1, "analyzeCallee: {} has unknown type\n", callerLoc.pretty());
3057 return;
3059 if (type.isValue() && type.isRef() &&
3060 type.innerType() == KindOfInvalid) {
3061 FTRACE(1, "analyzeCallee: {} has unknown inner-refdata type\n",
3062 callerLoc.pretty());
3063 return;
3066 FTRACE(2, "mapping arg{} locs {} -> {} :: {}\n",
3067 numArgs - i - 1,
3068 callerLoc.pretty(),
3069 calleeLoc.pretty(),
3070 type.pretty());
3071 initialMap[calleeLoc] = type;
3073 for (int i = numArgs; i < target->numLocals(); ++i) {
3074 initialMap[Location(Location::Local, i)] = RuntimeType(KindOfUninit);
3078 * When reentering analyze to generate a Tracelet for a callee,
3079 * currently we handle this by creating a fake ActRec on the stack.
3081 * This is mostly a compromise to deal with existing code during the
3082 * analysis phase which pretty liberally inspects live VM state.
3084 ActRec fakeAR;
3085 fakeAR.m_savedRbp = reinterpret_cast<uintptr_t>(curFrame());
3086 fakeAR.m_savedRip = 0xbaabaa; // should never be inspected
3087 fakeAR.m_func = fcall->funcd;
3088 fakeAR.m_soff = 0xb00b00; // should never be inspected
3089 fakeAR.m_numArgsAndCtorFlag = numArgs;
3090 fakeAR.m_varEnv = nullptr;
3093 * Even when inlining an object method, we can leave the m_this as
3094 * null. See outThisObjectType().
3096 fakeAR.m_this = nullptr;
3098 FTRACE(1, "analyzing sub trace =================================\n");
3099 auto const oldFP = vmfp();
3100 auto const oldSP = vmsp();
3101 auto const oldPC = vmpc();
3102 auto const oldAnalyzeCalleeDepth = m_analysisDepth++;
3103 vmpc() = nullptr; // should never be used
3104 vmsp() = nullptr; // should never be used
3105 vmfp() = reinterpret_cast<Cell*>(&fakeAR);
3106 auto restoreFrame = [&]{
3107 vmfp() = oldFP;
3108 vmsp() = oldSP;
3109 vmpc() = oldPC;
3110 m_analysisDepth = oldAnalyzeCalleeDepth;
3112 SCOPE_EXIT {
3113 // It's ok to restoreFrame() twice---we have it in this scope
3114 // handler to ensure it still happens if we exit via an exception.
3115 restoreFrame();
3116 FTRACE(1, "finished sub trace ===================================\n");
3119 auto subTrace = analyze(SrcKey(target, target->base()), initialMap);
3122 * Verify the target trace actually ended with a return, or we have
3123 * no business doing anything based on it right now.
3125 if (!subTrace->m_instrStream.last ||
3126 (subTrace->m_instrStream.last->op() != OpRetC &&
3127 subTrace->m_instrStream.last->op() != OpRetV)) {
3128 FTRACE(1, "analyzeCallee: callee did not end in a return\n");
3129 return;
3133 * If the IR can't inline this, give up now. Below we're going to
3134 * start making changes to the traclet that is making the call
3135 * (potentially increasing the specificity of guards), and we don't
3136 * want to do that unnecessarily.
3138 if (!shouldIRInline(callerFunc, target, *subTrace)) {
3139 if (UNLIKELY(Stats::enabledAny() && getenv("HHVM_STATS_FAILEDINL"))) {
3140 subTrace->m_inliningFailed = true;
3141 // Save the trace for stats purposes but don't waste time doing any
3142 // further processing since we know we won't inline it.
3143 fcall->calleeTrace = std::move(subTrace);
3145 return;
3149 * Disabled for now:
3151 * Propagate the return type to our caller. If the return type is
3152 * not vague, it will hold if we can inline the trace.
3154 * This isn't really a sensible thing to do if we aren't also going
3155 * to inline the callee, however, because the return type may only
3156 * be what it is due to other output predictions (CGetMs or FCall)
3157 * inside the callee. This means we would need to check the return
3158 * value in the caller still as if it were a predicted return type.
3160 Location retVal(Location::Stack, 0);
3161 auto it = subTrace->m_changes.find(retVal);
3162 assert(it != subTrace->m_changes.end());
3163 FTRACE(1, "subtrace return: {}\n", it->second->pretty());
3164 if (false) {
3165 if (!it->second->rtt.isVagueValue() && !it->second->rtt.isRef()) {
3166 FTRACE(1, "changing callee's return type from {} to {}\n",
3167 fcall->outStack->rtt.pretty(),
3168 it->second->pretty());
3170 fcall->outputPredicted = true;
3171 fcall->outputPredictionStatic = false;
3172 fcall->outStack = parent.newDynLocation(fcall->outStack->location,
3173 it->second->rtt);
3174 tas.recordWrite(fcall->outStack);
3179 * In order for relaxDeps not to relax guards on some things we may
3180 * potentially have depended on here, we need to ensure that the
3181 * call instruction depends on all the inputs we've used.
3183 * (We could do better by letting relaxDeps look through the
3184 * callee.)
3186 restoreFrame();
3187 for (auto& loc : callerArgLocs) {
3188 fcall->inputs.push_back(tas.recordRead(InputInfo(loc), true));
3191 FTRACE(1, "analyzeCallee: inline candidate\n");
3192 fcall->calleeTrace = std::move(subTrace);
3196 * analyze --
3198 * Given a sequence of bytecodes, return our tracelet IR.
3200 * The purposes of this analysis is to determine:
3202 * 1. Pre-conditions: What locations get read before they get written to:
3203 * we will need typechecks for these and we will want to load them into
3204 * registers. (m_dependencies)
3206 * 2. Post-conditions: the locations that have been written to and are
3207 * still live at the end of the tracelet. We need to allocate registers
3208 * of these and we need to spill them at the end of the tracelet.
3209 * (m_changes)
3211 * 3. Determine the runtime types for each instruction's input locations
3212 * and output locations.
3214 * The main analysis works by doing a single pass over the instructions. It
3215 * effectively simulates the execution of each instruction, updating its
3216 * knowledge about types as it goes.
3218 * The TraceletContext class is used to keep track of the current state of
3219 * the world. Initially it is empty, and when the inputs for the first
3220 * instruction are analyzed we call recordRead(). The recordRead() function
3221 * in turn inspects the live types of the inputs and adds them to the type
3222 * map. This serves two purposes: (1) it figures out what typechecks this
3223 * tracelet needs; and (2) it guarantees that the code we generate will
3224 * satisfy the live types that are about to be passed in.
3226 * Over time the TraceletContext's type map will change. However, we need to
3227 * record what the types _were_ right before and right after a given
3228 * instruction executes. This is where the NormalizedInstruction class comes
3229 * in. We store the RuntimeTypes from the TraceletContext right before an
3230 * instruction executes into the NormalizedInstruction's 'inputs' field, and
3231 * we store the RuntimeTypes from the TraceletContext right after the
3232 * instruction executes into the various output fields.
3234 std::unique_ptr<Tracelet> Translator::analyze(SrcKey sk,
3235 const TypeMap& initialTypes) {
3236 std::unique_ptr<Tracelet> retval(new Tracelet());
3237 auto& t = *retval;
3238 t.m_sk = sk;
3239 t.m_func = curFunc();
3241 DEBUG_ONLY const char* file = curUnit()->filepath()->data();
3242 DEBUG_ONLY const int lineNum = curUnit()->getLineNumber(t.m_sk.offset());
3243 DEBUG_ONLY const char* funcName = curFunc()->fullName()->data();
3245 TRACE(1, "Translator::analyze %s:%d %s\n", file, lineNum, funcName);
3246 TraceletContext tas(&t, initialTypes);
3247 int stackFrameOffset = 0;
3248 int oldStackFrameOffset = 0;
3250 // numOpcodes counts the original number of opcodes in a tracelet
3251 // before the translator does any optimization
3252 t.m_numOpcodes = 0;
3253 Unit::MetaHandle metaHand;
3255 const Unit *unit = curUnit();
3256 for (;; sk.advance(unit)) {
3257 head:
3258 NormalizedInstruction* ni = t.newNormalizedInstruction();
3259 ni->source = sk;
3260 ni->stackOffset = stackFrameOffset;
3261 ni->funcd = (t.m_arState.getCurrentState() == ActRecState::State::KNOWN) ?
3262 t.m_arState.getCurrentFunc() : nullptr;
3263 ni->m_unit = unit;
3264 ni->breaksTracelet = false;
3265 ni->changesPC = opcodeChangesPC(ni->op());
3266 ni->fuseBranch = false;
3267 ni->outputPredicted = false;
3268 ni->outputPredictionStatic = false;
3270 assert(!t.m_analysisFailed);
3271 oldStackFrameOffset = stackFrameOffset;
3272 populateImmediates(*ni);
3274 SKTRACE(1, sk, "stack args: virtual sfo now %d\n", stackFrameOffset);
3276 // Translation could fail entirely (because of an unknown opcode), or
3277 // encounter an input that cannot be computed.
3278 try {
3279 preInputApplyMetaData(metaHand, ni);
3280 InputInfos inputInfos;
3281 getInputs(t.m_sk, ni, stackFrameOffset, inputInfos, [&](int i) {
3282 return Type::fromRuntimeType(
3283 tas.currentType(Location(Location::Local, i)));
3285 bool noOp = applyInputMetaData(metaHand, ni, tas, inputInfos);
3286 if (noOp) {
3287 t.m_instrStream.append(ni);
3288 ++t.m_numOpcodes;
3289 stackFrameOffset = oldStackFrameOffset;
3290 continue;
3292 if (inputInfos.needsRefCheck) {
3293 // Drive the arState machine; if it is going to throw an input
3294 // exception, do so here.
3295 int argNum = ni->imm[0].u_IVA;
3296 // instrSpToArDelta() returns the delta relative to the sp at the
3297 // beginning of the instruction, but getReffiness() wants the delta
3298 // relative to the sp at the beginning of the tracelet, so we adjust
3299 // by subtracting ni->stackOff
3300 int entryArDelta = instrSpToArDelta((Op*)ni->pc()) - ni->stackOffset;
3301 ni->preppedByRef = t.m_arState.getReffiness(argNum, entryArDelta,
3302 &t.m_refDeps);
3303 SKTRACE(1, sk, "passing arg%d by %s\n", argNum,
3304 ni->preppedByRef ? "reference" : "value");
3307 for (unsigned int i = 0; i < inputInfos.size(); i++) {
3308 SKTRACE(2, sk, "typing input %d\n", i);
3309 const InputInfo& ii = inputInfos[i];
3310 DynLocation* dl = tas.recordRead(ii, true);
3311 const RuntimeType& rtt = dl->rtt;
3312 // Some instructions are able to handle an input with an unknown type
3313 if (!ii.dontBreak && !ii.dontGuard) {
3314 if (rtt.isVagueValue()) {
3315 // Consumed a "poisoned" output: e.g., result of an array
3316 // deref.
3317 throwUnknownInput();
3319 if (!ni->ignoreInnerType && !ii.dontGuardInner) {
3320 if (rtt.isValue() && rtt.isRef() &&
3321 rtt.innerType() == KindOfInvalid) {
3322 throwUnknownInput();
3326 ni->inputs.push_back(dl);
3328 } catch (TranslationFailedExc& tfe) {
3329 SKTRACE(1, sk, "Translator fail: %s:%d\n", tfe.m_file, tfe.m_line);
3330 if (!t.m_numOpcodes) {
3331 t.m_analysisFailed = true;
3332 t.m_instrStream.append(ni);
3333 ++t.m_numOpcodes;
3335 goto breakBB;
3336 } catch (UnknownInputExc& uie) {
3337 // Subtle: if this instruction consumes an unknown runtime type,
3338 // break the BB on the *previous* instruction. We know that a
3339 // previous instruction exists, because the KindOfInvalid must
3340 // have come from somewhere.
3341 always_assert(t.m_instrStream.last);
3342 SKTRACE(2, sk, "Consumed unknown input (%s:%d); breaking BB at "
3343 "predecessor\n", uie.m_file, uie.m_line);
3344 goto breakBB;
3347 SKTRACE(2, sk, "stack args: virtual sfo now %d\n", stackFrameOffset);
3349 bool doVarEnvTaint; // initialized by reference.
3351 try {
3352 getOutputs(t, ni, stackFrameOffset, doVarEnvTaint);
3353 } catch (TranslationFailedExc& tfe) {
3354 SKTRACE(1, sk, "Translator getOutputs fail: %s:%d\n",
3355 tfe.m_file, tfe.m_line);
3356 if (!t.m_numOpcodes) {
3357 t.m_analysisFailed = true;
3358 t.m_instrStream.append(ni);
3359 ++t.m_numOpcodes;
3361 goto breakBB;
3364 if (isFCallStar(ni->op())) {
3365 if (!doVarEnvTaint) {
3366 const FPIEnt *fpi = curFunc()->findFPI(ni->source.offset());
3367 assert(fpi);
3368 Offset fpushOff = fpi->m_fpushOff;
3369 PC fpushPc = curUnit()->at(fpushOff);
3370 if (*fpushPc == OpFPushFunc) {
3371 doVarEnvTaint = true;
3372 } else if (*fpushPc == OpFPushFuncD) {
3373 StringData *funcName =
3374 curUnit()->lookupLitstrId(getImm((Op*)fpushPc, 1).u_SA);
3375 doVarEnvTaint = checkTaintFuncs(funcName);
3376 } else if (*fpushPc == OpFPushFuncU) {
3377 StringData *fallbackName =
3378 curUnit()->lookupLitstrId(getImm((Op*)fpushPc, 2).u_SA);
3379 doVarEnvTaint = checkTaintFuncs(fallbackName);
3382 t.m_arState.pop();
3384 if (ni->op() == OpFCallBuiltin && !doVarEnvTaint) {
3385 StringData* funcName = curUnit()->lookupLitstrId(ni->imm[2].u_SA);
3386 doVarEnvTaint = checkTaintFuncs(funcName);
3388 if (doVarEnvTaint) {
3389 tas.varEnvTaint();
3392 DynLocation* outputs[] = { ni->outStack,
3393 ni->outLocal, ni->outLocal2,
3394 ni->outStack2, ni->outStack3 };
3395 for (size_t i = 0; i < sizeof(outputs) / sizeof(*outputs); ++i) {
3396 if (outputs[i]) {
3397 DynLocation* o = outputs[i];
3398 SKTRACE(2, sk, "inserting output t(%d->%d) #(%s, %" PRId64 ")\n",
3399 o->rtt.outerType(), o->rtt.innerType(),
3400 o->location.spaceName(), o->location.offset);
3401 tas.recordWrite(o);
3405 SKTRACE(1, sk, "stack args: virtual sfo now %d\n", stackFrameOffset);
3407 // This assert failing means that your instruction has an
3408 // inconsistent row in the InstrInfo table; the stackDelta doesn't
3409 // agree with the inputs and outputs.
3410 assert(getStackDelta(*ni) == (stackFrameOffset - oldStackFrameOffset));
3411 // If this instruction decreased the depth of the stack, mark the
3412 // appropriate stack locations as "dead". But we need to leave
3413 // them in the TraceletContext until after analyzeCallee (if this
3414 // is an FCall).
3415 if (stackFrameOffset < oldStackFrameOffset) {
3416 for (int i = stackFrameOffset; i < oldStackFrameOffset; ++i) {
3417 ni->deadLocs.push_back(Location(Location::Stack, i));
3421 if (ni->outputPredicted) {
3422 assert(ni->outStack);
3423 ni->outPred = Type::fromDynLocation(ni->outStack);
3426 t.m_stackChange += getStackDelta(*ni);
3428 t.m_instrStream.append(ni);
3429 ++t.m_numOpcodes;
3432 * The annotation step attempts to track Func*'s associated with
3433 * given FCalls when the FPush is in a different tracelet.
3435 * When we're analyzing a callee, we can't do this because we may
3436 * have class information in some of our RuntimeTypes that is only
3437 * true because of who the caller was. (Normally it is only there
3438 * if it came from static analysis.)
3440 if (analysisDepth() == 0) {
3441 annotate(ni);
3444 if (ni->op() == OpFCall) {
3445 analyzeCallee(tas, t, ni);
3448 for (auto& l : ni->deadLocs) {
3449 tas.recordDelete(l);
3452 // Check if we need to break the tracelet.
3454 // If we've gotten this far, it mostly boils down to control-flow
3455 // instructions. However, we'll trace through a few unconditional jmps.
3456 if (ni->op() == OpJmp &&
3457 ni->imm[0].u_IA > 0 &&
3458 tas.m_numJmps < MaxJmpsTracedThrough) {
3459 // Continue tracing through jumps. To prevent pathologies, only trace
3460 // through a finite number of forward jumps.
3461 SKTRACE(1, sk, "greedily continuing through %dth jmp + %d\n",
3462 tas.m_numJmps, ni->imm[0].u_IA);
3463 tas.recordJmp();
3464 sk = SrcKey(curFunc(), sk.offset() + ni->imm[0].u_IA);
3465 goto head; // don't advance sk
3466 } else if (opcodeBreaksBB(ni->op()) ||
3467 (dontGuardAnyInputs(ni->op()) && opcodeChangesPC(ni->op()))) {
3468 SKTRACE(1, sk, "BB broken\n");
3469 sk.advance(unit);
3470 goto breakBB;
3472 postAnalyze(ni, sk, t, tas);
3474 breakBB:
3475 NormalizedInstruction* ni = t.m_instrStream.last;
3476 while (ni) {
3477 switch (ni->op()) {
3478 // We dont want to end a tracelet with a literal;
3479 // it will cause the literal to be pushed on the
3480 // stack, and the next tracelet will have to guard
3481 // on the type.
3482 case OpNull:
3483 case OpNullUninit:
3484 case OpTrue:
3485 case OpFalse:
3486 case OpInt:
3487 case OpDouble:
3488 case OpString:
3489 case OpArray:
3490 // Similarly, This, Self and Parent will lose
3491 // type information thats only useful in the
3492 // following tracelet.
3493 case OpThis:
3494 case OpSelf:
3495 case OpParent:
3496 ni = ni->prev;
3497 continue;
3498 default:
3499 break;
3501 break;
3503 if (ni) {
3504 while (ni != t.m_instrStream.last) {
3505 t.m_stackChange -= getStackDelta(*t.m_instrStream.last);
3506 sk = t.m_instrStream.last->source;
3507 t.m_instrStream.remove(t.m_instrStream.last);
3508 --t.m_numOpcodes;
3512 relaxDeps(t, tas);
3513 specializeDeps(t, tas);
3515 // Mark the last instruction appropriately
3516 assert(t.m_instrStream.last);
3517 t.m_instrStream.last->breaksTracelet = true;
3518 // Populate t.m_changes, t.intermediates, t.m_dependencies
3519 t.m_dependencies = tas.m_dependencies;
3520 t.m_resolvedDeps = tas.m_resolvedDeps;
3521 t.m_changes.clear();
3522 LocationSet::iterator it = tas.m_changeSet.begin();
3523 for (; it != tas.m_changeSet.end(); ++it) {
3524 t.m_changes[*it] = tas.m_currentMap[*it];
3527 TRACE(1, "Tracelet done: stack delta %d\n", t.m_stackChange);
3528 return retval;
3531 Translator::Translator()
3532 : m_curTrace(nullptr)
3533 , m_curNI(nullptr)
3534 , m_resumeHelper(nullptr)
3535 , m_createdTime(Timer::GetCurrentTimeMicros())
3536 , m_analysisDepth(0)
3538 initInstrInfo();
3541 Translator::~Translator() {
3544 Translator*
3545 Translator::Get() {
3546 return TranslatorX64::Get();
3549 bool
3550 Translator::isSrcKeyInBL(const Unit* unit, const SrcKey& sk) {
3551 Lock l(m_dbgBlacklistLock);
3552 if (m_dbgBLSrcKey.find(sk) != m_dbgBLSrcKey.end()) {
3553 return true;
3555 for (PC pc = unit->at(sk.offset()); !opcodeBreaksBB(toOp(*pc));
3556 pc += instrLen((Op*)pc)) {
3557 if (m_dbgBLPC.checkPC(pc)) {
3558 m_dbgBLSrcKey.insert(sk);
3559 return true;
3562 return false;
3565 void
3566 Translator::clearDbgBL() {
3567 Lock l(m_dbgBlacklistLock);
3568 m_dbgBLSrcKey.clear();
3569 m_dbgBLPC.clear();
3572 bool
3573 Translator::addDbgBLPC(PC pc) {
3574 Lock l(m_dbgBlacklistLock);
3575 if (m_dbgBLPC.checkPC(pc)) {
3576 // already there
3577 return false;
3579 m_dbgBLPC.addPC(pc);
3580 return true;
3583 // Return the SrcKey for the operation that should follow the supplied
3584 // NormalizedInstruction.
3585 SrcKey Translator::nextSrcKey(const NormalizedInstruction& i) {
3586 return i.source.advanced(curUnit());
3589 void Translator::populateImmediates(NormalizedInstruction& inst) {
3590 for (int i = 0; i < numImmediates(inst.op()); i++) {
3591 inst.imm[i] = getImm((Op*)inst.pc(), i);
3593 if (hasImmVector(toOp(*inst.pc()))) {
3594 inst.immVec = getImmVector((Op*)inst.pc());
3596 if (inst.op() == OpFCallArray) {
3597 inst.imm[0].u_IVA = 1;
3601 const char* Translator::translateResultName(TranslateResult r) {
3602 static const char* const names[] = {
3603 "Failure",
3604 "Retry",
3605 "Success",
3607 return names[r];
3611 * Similar to applyInputMetaData, but designed to be used during ir
3612 * generation. Reads and writes types of values using m_hhbcTrans. This will
3613 * eventually replace applyInputMetaData.
3615 void Translator::readMetaData(Unit::MetaHandle& handle,
3616 NormalizedInstruction& inst) {
3617 if (!handle.findMeta(inst.unit(), inst.offset())) return;
3619 Unit::MetaInfo info;
3620 if (!handle.nextArg(info)) return;
3621 if (info.m_kind == Unit::MetaInfo::Kind::NopOut) {
3622 inst.noOp = true;
3623 return;
3627 * We need to adjust the indexes in MetaInfo::m_arg if this instruction takes
3628 * other stack arguments than those related to the MVector. (For example,
3629 * the rhs of an assignment.)
3631 auto const& iInfo = instrInfo[inst.op()];
3632 if (iInfo.in & AllLocals) {
3634 * RetC/RetV dont care about their stack input, but it may have been
3635 * annotated. Skip it (because RetC/RetV pretend they dont have a stack
3636 * input).
3638 return;
3640 if (iInfo.in == FuncdRef) {
3642 * FPassC* pretend to have no inputs
3644 return;
3646 const int base = !(iInfo.in & MVector) ? 0 :
3647 !(iInfo.in & Stack1) ? 0 :
3648 !(iInfo.in & Stack2) ? 1 :
3649 !(iInfo.in & Stack3) ? 2 : 3;
3651 do {
3652 SKTRACE(3, inst.source, "considering MetaInfo of kind %d\n", info.m_kind);
3654 int arg = info.m_arg & Unit::MetaInfo::VectorArg ?
3655 base + (info.m_arg & ~Unit::MetaInfo::VectorArg) : info.m_arg;
3656 auto updateType = [&]{
3657 auto& input = *inst.inputs[arg];
3658 input.rtt = m_hhbcTrans->rttFromLocation(input.location);
3661 switch (info.m_kind) {
3662 case Unit::MetaInfo::Kind::NoSurprise:
3663 inst.noSurprise = true;
3664 break;
3665 case Unit::MetaInfo::Kind::GuardedCls:
3666 inst.guardedCls = true;
3667 break;
3668 case Unit::MetaInfo::Kind::ArrayCapacity:
3669 inst.imm[0].u_IVA = info.m_data;
3670 break;
3671 case Unit::MetaInfo::Kind::DataTypePredicted: {
3672 auto const& loc = inst.inputs[arg]->location;
3673 auto const t = Type::fromDataType(DataType(info.m_data));
3674 auto const offset = inst.source.offset();
3676 // These 'predictions' mean the type is InitNull or the predicted type,
3677 // so we assert InitNull | t, then guard t. This allows certain
3678 // optimizations in the IR.
3679 m_hhbcTrans->assertTypeLocation(loc, Type::InitNull | t);
3680 m_hhbcTrans->checkTypeLocation(loc, t, offset);
3681 updateType();
3682 break;
3684 case Unit::MetaInfo::Kind::DataTypeInferred: {
3685 m_hhbcTrans->assertTypeLocation(
3686 inst.inputs[arg]->location,
3687 Type::fromDataType(DataType(info.m_data)));
3688 updateType();
3689 break;
3691 case Unit::MetaInfo::Kind::String: {
3692 m_hhbcTrans->assertString(inst.inputs[arg]->location,
3693 inst.unit()->lookupLitstrId(info.m_data));
3694 updateType();
3695 break;
3697 case Unit::MetaInfo::Kind::Class: {
3698 RuntimeType& rtt = inst.inputs[arg]->rtt;
3699 if (rtt.valueType() != KindOfObject) {
3700 continue;
3703 const StringData* metaName = inst.unit()->lookupLitstrId(info.m_data);
3704 const StringData* rttName =
3705 rtt.valueClass() ? rtt.valueClass()->name() : nullptr;
3706 // The two classes might not be exactly the same, which is ok
3707 // as long as metaCls is more derived than rttCls.
3708 Class* metaCls = Unit::lookupUniqueClass(metaName);
3709 Class* rttCls = rttName ? Unit::lookupUniqueClass(rttName) : nullptr;
3710 if (metaCls && rttCls && metaCls != rttCls &&
3711 !metaCls->classof(rttCls)) {
3712 // Runtime type is more derived
3713 metaCls = 0;
3715 if (metaCls && metaCls != rttCls) {
3716 SKTRACE(1, inst.source, "replacing input %d with a MetaInfo-supplied "
3717 "class of %s; old type = %s\n",
3718 arg, metaName->data(), rtt.pretty().c_str());
3719 if (rtt.isRef()) {
3720 rtt = RuntimeType(KindOfRef, KindOfObject, metaCls);
3721 } else {
3722 rtt = RuntimeType(KindOfObject, KindOfInvalid, metaCls);
3725 break;
3727 case Unit::MetaInfo::Kind::MVecPropClass: {
3728 const StringData* metaName = inst.unit()->lookupLitstrId(info.m_data);
3729 Class* metaCls = Unit::lookupUniqueClass(metaName);
3730 if (metaCls) {
3731 inst.immVecClasses[arg] = metaCls;
3733 break;
3735 case Unit::MetaInfo::Kind::NopOut:
3736 // NopOut should always be the first and only annotation
3737 // and was handled above.
3738 not_reached();
3740 case Unit::MetaInfo::Kind::GuardedThis:
3741 case Unit::MetaInfo::Kind::NonRefCounted:
3742 // fallthrough; these are handled in preInputApplyMetaData.
3743 case Unit::MetaInfo::Kind::None:
3744 break;
3746 } while (handle.nextArg(info));
3749 bool Translator::instrMustInterp(const NormalizedInstruction& inst) {
3750 if (RuntimeOption::EvalJitAlwaysInterpOne) return true;
3752 switch (inst.op()) {
3753 // Generate a case for each instruction we support at least partially.
3754 # define CASE(name) case Op##name:
3755 INSTRS
3756 # undef CASE
3757 # define NOTHING(...) // PSEUDOINSTR_DISPATCH has the cases in it
3758 PSEUDOINSTR_DISPATCH(NOTHING)
3759 # undef NOTHING
3760 return false;
3762 default:
3763 return true;
3767 static Location toLocation(const RegionDesc::Location& loc) {
3768 typedef RegionDesc::Location::Tag T;
3769 switch (loc.tag()) {
3770 case T::Stack:
3771 return Location(Location::Stack, loc.stackOffset());
3773 case T::Local:
3774 return Location(Location::Local, loc.localId());
3776 not_reached();
3779 Translator::TranslateResult
3780 Translator::translateRegion(const RegionDesc& region,
3781 RegionBlacklist& toInterp) {
3782 typedef JIT::RegionDesc::Block Block;
3783 FTRACE(1, "translateRegion starting with:\n{}\n", show(region));
3784 assert(!region.blocks.empty());
3785 const SrcKey startSk = region.blocks.front()->start();
3787 for (auto const& block : region.blocks) {
3788 SrcKey sk = block->start();
3789 const Func* topFunc = nullptr;
3790 auto typePreds = makeMapWalker(block->typePreds());
3791 auto byRefs = makeMapWalker(block->paramByRefs());
3792 auto refPreds = makeMapWalker(block->reffinessPreds());
3793 auto knownFuncs = makeMapWalker(block->knownFuncs());
3795 for (unsigned i = 0; i < block->length(); ++i, sk.advance(block->unit())) {
3796 // Emit prediction guards. If this is the first instruction in the
3797 // region the guards will go to a retranslate request. Otherwise, they'll
3798 // go to a side exit.
3799 while (typePreds.hasNext(sk)) {
3800 auto const& pred = typePreds.next();
3801 if (block == region.blocks.front() && i == 0) {
3802 m_hhbcTrans->guardTypeLocation(toLocation(pred.location), pred.type);
3803 } else {
3804 m_hhbcTrans->checkTypeLocation(toLocation(pred.location), pred.type,
3805 sk.offset());
3809 // Emit reffiness guards. For now, we only support reffiness guards at
3810 // the beginning of the region.
3811 while (refPreds.hasNext(sk)) {
3812 assert(sk == startSk);
3813 auto const& pred = refPreds.next();
3814 m_hhbcTrans->guardRefs(pred.arSpOffset, pred.mask, pred.vals);
3817 // Update the current funcd, if we have a new one.
3818 if (knownFuncs.hasNext(sk)) {
3819 topFunc = knownFuncs.next();
3822 // Create and initialize the instruction.
3823 NormalizedInstruction inst;
3824 inst.source = sk;
3825 inst.m_unit = block->unit();
3826 inst.breaksTracelet =
3827 i == block->length() - 1 && block == region.blocks.back();
3828 inst.changesPC = opcodeChangesPC(inst.op());
3829 inst.funcd = topFunc;
3830 populateImmediates(inst);
3832 // We can get a more precise output type for interpOne if we know all of
3833 // its inputs, so we still populate the rest of the instruction even if
3834 // this is true.
3835 inst.interp = toInterp.count(sk);
3837 // Apply the first round of metadata from the repo and get a list of
3838 // input locations.
3839 InputInfos inputInfos;
3840 Unit::MetaHandle metaHand;
3841 preInputApplyMetaData(metaHand, &inst);
3843 // TranslatorX64 expected top of stack to be index -1, with indexes
3844 // growing down from there. hhir defines top of stack to be index 1, with
3845 // indexes growing up from there. To compensate we start with a stack
3846 // offset of 1 and negate the index of any stack input after the call to
3847 // getInputs.
3848 int stackOff = 1;
3849 getInputs(startSk, &inst, stackOff, inputInfos, [&](int i) {
3850 return m_hhbcTrans->traceBuilder()->getLocalType(i);
3852 for (auto& info : inputInfos) {
3853 if (info.loc.isStack()) info.loc.offset = -info.loc.offset;
3856 // Populate the NormalizedInstruction's input vector, using types from
3857 // HhbcTranslator.
3858 std::vector<DynLocation> dynLocs;
3859 dynLocs.reserve(inputInfos.size());
3860 auto newDynLoc = [&](const InputInfo& ii) {
3861 dynLocs.emplace_back(ii.loc, m_hhbcTrans->rttFromLocation(ii.loc));
3862 FTRACE(2, "rttFromLocation: {} -> {}\n",
3863 ii.loc.pretty(), dynLocs.back().rtt.pretty());
3864 return &dynLocs.back();
3866 FTRACE(2, "populating inputs for {}\n", inst.toString());
3867 for (auto const& ii : inputInfos) {
3868 inst.inputs.push_back(newDynLoc(ii));
3871 // Apply the remaining metadata. This may change the types of some of
3872 // inst's inputs.
3873 readMetaData(metaHand, inst);
3874 if (!inst.noOp && inputInfos.needsRefCheck) {
3875 assert(byRefs.hasNext(sk));
3876 auto byRef = byRefs.next();
3877 inst.preppedByRef = byRef == RegionDesc::ParamByRef::Yes;
3880 // Check for a type prediction. Put it in the NormalizedInstruction so
3881 // the emit* method can use it if needed.
3882 auto const& iInfo = instrInfo[inst.op()];
3883 auto doPrediction = iInfo.type == OutPred && !inst.breaksTracelet;
3884 if (doPrediction) {
3885 // All OutPred ops have a single stack output for now.
3886 assert(iInfo.out == Stack1);
3887 auto dt = predictOutputs(startSk, &inst);
3888 if (dt != KindOfInvalid) {
3889 inst.outPred = Type::fromDataType(dt);
3890 } else {
3891 doPrediction = false;
3895 // Emit IR for the body of the instruction.
3896 Util::Nuller<NormalizedInstruction> niNuller(&m_curNI);
3897 m_curNI = &inst;
3898 try {
3899 translateInstr(inst);
3900 } catch (const JIT::FailedIRGen& exn) {
3901 FTRACE(1, "ir generation for {} failed with {}\n",
3902 inst.toString(), exn.what());
3903 always_assert(!toInterp.count(sk));
3904 toInterp.insert(sk);
3905 return Retry;
3908 // Check the prediction. If the predicted type is less specific than what
3909 // is currently on the eval stack, checkTypeLocation won't emit any code.
3910 if (doPrediction) {
3911 m_hhbcTrans->checkTypeLocation(Location(Location::Stack, 0),
3912 inst.outPred,
3913 sk.advanced(block->unit()).offset());
3917 assert(!typePreds.hasNext());
3918 assert(!byRefs.hasNext());
3919 assert(!refPreds.hasNext());
3920 assert(!knownFuncs.hasNext());
3923 traceEnd();
3924 try {
3925 traceCodeGen();
3926 } catch (const JIT::FailedCodeGen& exn) {
3927 FTRACE(1, "code generation failed with {}\n", exn.what());
3928 SrcKey sk{exn.vmFunc, exn.bcOff};
3929 always_assert(!toInterp.count(sk));
3930 toInterp.insert(sk);
3931 return Retry;
3934 return Success;
3937 uint64_t* Translator::getTransCounterAddr() {
3938 if (!isTransDBEnabled()) return nullptr;
3940 TransID id = m_translations.size();
3942 // allocate a new chunk of counters if necessary
3943 if (id >= m_transCounters.size() * transCountersPerChunk) {
3944 uint32_t size = sizeof(uint64_t) * transCountersPerChunk;
3945 auto *chunk = (uint64_t*)malloc(size);
3946 bzero(chunk, size);
3947 m_transCounters.push_back(chunk);
3949 assert(id / transCountersPerChunk < m_transCounters.size());
3950 return &(m_transCounters[id / transCountersPerChunk]
3951 [id % transCountersPerChunk]);
3954 uint32_t Translator::addTranslation(const TransRec& transRec) {
3955 if (Trace::moduleEnabledRelease(Trace::trans, 1)) {
3956 // Log the translation's size, creation time, SrcKey, and size
3957 Trace::traceRelease("New translation: %" PRId64 " %s %u %u %d\n",
3958 Timer::GetCurrentTimeMicros() - m_createdTime,
3959 folly::format("{}:{}:{}",
3960 curUnit()->filepath()->data(),
3961 transRec.src.getFuncId(),
3962 transRec.src.offset()).str().c_str(),
3963 transRec.aLen,
3964 transRec.astubsLen,
3965 transRec.kind);
3968 if (!isTransDBEnabled()) return -1u;
3969 uint32_t id = getCurrentTransID();
3970 m_translations.push_back(transRec);
3971 m_translations[id].setID(id);
3973 if (transRec.aLen > 0) {
3974 m_transDB[transRec.aStart] = id;
3976 if (transRec.astubsLen > 0) {
3977 m_transDB[transRec.astubsStart] = id;
3980 return id;
3983 uint64_t Translator::getTransCounter(TransID transId) const {
3984 if (!isTransDBEnabled()) return -1ul;
3985 assert(transId < m_translations.size());
3987 uint64_t counter;
3989 if (transId / transCountersPerChunk >= m_transCounters.size()) {
3990 counter = 0;
3991 } else {
3992 counter = m_transCounters[transId / transCountersPerChunk]
3993 [transId % transCountersPerChunk];
3995 return counter;
3998 void Translator::setTransCounter(TransID transId, uint64_t value) {
3999 assert(transId < m_translations.size());
4000 assert(transId / transCountersPerChunk < m_transCounters.size());
4002 m_transCounters[transId / transCountersPerChunk]
4003 [transId % transCountersPerChunk] = value;
4006 namespace {
4008 struct DeferredFileInvalidate : public DeferredWorkItem {
4009 Eval::PhpFile* m_f;
4010 explicit DeferredFileInvalidate(Eval::PhpFile* f) : m_f(f) {
4011 TRACE(2, "DeferredFileInvalidate @ %p, m_f %p\n", this, m_f); }
4012 void operator()() {
4013 TRACE(2, "DeferredFileInvalidate: Firing @ %p , m_f %p\n", this, m_f);
4014 tx64->invalidateFileWork(m_f);
4018 struct DeferredPathInvalidate : public DeferredWorkItem {
4019 const std::string m_path;
4020 explicit DeferredPathInvalidate(const std::string& path) : m_path(path) {
4021 assert(m_path.size() >= 1 && m_path[0] == '/');
4023 void operator()() {
4024 String spath(m_path);
4026 * inotify saw this path change. Now poke the file repository;
4027 * it will notice the underlying PhpFile* has changed, and notify
4028 * us via ::invalidateFile.
4030 * We don't actually need to *do* anything with the PhpFile* from
4031 * this lookup; since the path has changed, the file we'll get out is
4032 * going to be some new file, not the old file that needs invalidation.
4034 UNUSED Eval::PhpFile* f =
4035 g_vmContext->lookupPhpFile(spath.get(), "");
4036 // We don't keep around the extra ref.
4037 if (f) f->decRefAndDelete();
4043 void Translator::invalidateFileWork(Eval::PhpFile* f) {
4044 class FileInvalidationTrigger : public Treadmill::WorkItem {
4045 Eval::PhpFile* m_f;
4046 int m_nRefs;
4047 public:
4048 FileInvalidationTrigger(Eval::PhpFile* f, int n) : m_f(f), m_nRefs(n) { }
4049 virtual void operator()() {
4050 if (m_f->decRef(m_nRefs) == 0) {
4051 Eval::FileRepository::onDelete(m_f);
4055 size_t nSmashed = tx64->m_srcDB.invalidateCode(f);
4056 if (nSmashed) {
4057 // The srcDB found an entry for this file. The entry's dependency
4058 // on this file was counted as a reference, and the code is no longer
4059 // reachable. We need to wait until the last outstanding request
4060 // drains to know that we can really remove the reference.
4061 Treadmill::WorkItem::enqueue(new FileInvalidationTrigger(f, nSmashed));
4065 bool Translator::invalidateFile(Eval::PhpFile* f) {
4066 // This is called from high rank, but we'll need the write lease to
4067 // invalidate code.
4068 if (!RuntimeOption::EvalJit) return false;
4069 assert(f != nullptr);
4070 PendQ::defer(new DeferredFileInvalidate(f));
4071 return true;
4074 void invalidatePath(const std::string& path) {
4075 TRACE(1, "invalidatePath: abspath %s\n", path.c_str());
4076 PendQ::defer(new DeferredPathInvalidate(path));
4079 static const char *transKindStr[] = {
4080 "Normal_Tx64",
4081 "Normal_HHIR",
4082 "Anchor",
4083 "Prologue",
4086 const char *getTransKindName(TransKind kind) {
4087 assert(kind >= 0 && kind <= TransProlog);
4088 return transKindStr[kind];
4091 string
4092 TransRec::print(uint64_t profCount) const {
4093 const size_t kBufSize = 1000;
4094 static char formatBuf[kBufSize];
4096 snprintf(formatBuf, kBufSize,
4097 "Translation %d {\n"
4098 " src.md5 = %s\n"
4099 " src.funcId = %u\n"
4100 " src.startOffset = 0x%x\n"
4101 " src.stopOffset = 0x%x\n"
4102 " kind = %u (%s)\n"
4103 " aStart = %p\n"
4104 " aLen = 0x%x\n"
4105 " stubStart = %p\n"
4106 " stubLen = 0x%x\n"
4107 " profCount = %" PRIu64 "\n"
4108 " bcMapping = %lu\n",
4109 id, md5.toString().c_str(), src.getFuncId(), src.offset(),
4110 bcStopOffset, kind, getTransKindName(kind), aStart, aLen,
4111 astubsStart, astubsLen, profCount, bcMapping.size());
4113 string ret(formatBuf);
4115 for (size_t i = 0; i < bcMapping.size(); i++) {
4116 snprintf(formatBuf, kBufSize, " 0x%x %p %p\n",
4117 bcMapping[i].bcStart,
4118 bcMapping[i].aStart,
4119 bcMapping[i].astubsStart);
4121 ret += string(formatBuf);
4124 ret += "}\n\n";
4125 return ret;
4128 void
4129 ActRecState::pushFuncD(const Func* func) {
4130 TRACE(2, "ActRecState: pushStatic func %p(%s)\n", func, func->name()->data());
4131 Record r;
4132 r.m_state = State::KNOWN;
4133 r.m_topFunc = func;
4134 r.m_entryArDelta = InvalidEntryArDelta;
4135 m_arStack.push_back(r);
4138 void
4139 ActRecState::pushDynFunc() {
4140 TRACE(2, "ActRecState: pushDynFunc\n");
4141 Record r;
4142 r.m_state = State::UNKNOWABLE;
4143 r.m_topFunc = nullptr;
4144 r.m_entryArDelta = InvalidEntryArDelta;
4145 m_arStack.push_back(r);
4148 void
4149 ActRecState::pop() {
4150 if (!m_arStack.empty()) {
4151 m_arStack.pop_back();
4156 * getReffiness() returns true if the parameter specified by argNum is pass
4157 * by reference, otherwise it returns false. This function may also throw an
4158 * UnknownInputException if the reffiness cannot be determined.
4160 * Note that the 'entryArDelta' parameter specifies the delta between sp at
4161 * the beginning of the tracelet and ar.
4163 bool
4164 ActRecState::getReffiness(int argNum, int entryArDelta, RefDeps* outRefDeps) {
4165 assert(outRefDeps);
4166 TRACE(2, "ActRecState: getting reffiness for arg %d\n", argNum);
4167 if (m_arStack.empty()) {
4168 // The ActRec in question was pushed before the beginning of the
4169 // tracelet, so we can make a guess about parameter reffiness and
4170 // record our assumptions about parameter reffiness as tracelet
4171 // guards.
4172 const ActRec* ar = arFromSpOffset((ActRec*)vmsp(), entryArDelta);
4173 Record r;
4174 r.m_state = State::GUESSABLE;
4175 r.m_entryArDelta = entryArDelta;
4176 r.m_topFunc = ar->m_func;
4177 m_arStack.push_back(r);
4179 Record& r = m_arStack.back();
4180 if (r.m_state == State::UNKNOWABLE) {
4181 TRACE(2, "ActRecState: unknowable, throwing in the towel\n");
4182 throwUnknownInput();
4183 not_reached();
4185 assert(r.m_topFunc);
4186 bool retval = r.m_topFunc->byRef(argNum);
4187 if (r.m_state == State::GUESSABLE) {
4188 assert(r.m_entryArDelta != InvalidEntryArDelta);
4189 TRACE(2, "ActRecState: guessing arg%d -> %d\n", argNum, retval);
4190 outRefDeps->addDep(r.m_entryArDelta, argNum, retval);
4192 return retval;
4195 const Func*
4196 ActRecState::getCurrentFunc() {
4197 if (m_arStack.empty()) return nullptr;
4198 return m_arStack.back().m_topFunc;
4201 ActRecState::State
4202 ActRecState::getCurrentState() {
4203 if (m_arStack.empty()) return State::GUESSABLE;
4204 return m_arStack.back().m_state;
4207 const Func* lookupImmutableMethod(const Class* cls, const StringData* name,
4208 bool& magicCall, bool staticLookup) {
4209 if (!cls || RuntimeOption::EvalJitEnableRenameFunction) return nullptr;
4210 if (cls->attrs() & AttrInterface) return nullptr;
4211 bool privateOnly = false;
4212 if (!RuntimeOption::RepoAuthoritative ||
4213 !(cls->preClass()->attrs() & AttrUnique)) {
4214 Class* ctx = curFunc()->cls();
4215 if (!ctx || !ctx->classof(cls)) {
4216 return nullptr;
4218 if (!staticLookup) privateOnly = true;
4221 const Func* func;
4222 MethodLookup::LookupResult res = staticLookup ?
4223 g_vmContext->lookupClsMethod(func, cls, name, 0,
4224 g_vmContext->getFP(), false) :
4225 g_vmContext->lookupObjMethod(func, cls, name, false);
4227 if (res == MethodLookup::LookupResult::MethodNotFound) return nullptr;
4229 assert(res == MethodLookup::LookupResult::MethodFoundWithThis ||
4230 res == MethodLookup::LookupResult::MethodFoundNoThis ||
4231 (staticLookup ?
4232 res == MethodLookup::LookupResult::MagicCallStaticFound :
4233 res == MethodLookup::LookupResult::MagicCallFound));
4235 magicCall =
4236 res == MethodLookup::LookupResult::MagicCallStaticFound ||
4237 res == MethodLookup::LookupResult::MagicCallFound;
4239 if ((privateOnly && (!(func->attrs() & AttrPrivate) || magicCall)) ||
4240 func->isAbstract() ||
4241 func->attrs() & AttrDynamicInvoke) {
4242 return nullptr;
4245 if (staticLookup) {
4246 if (magicCall) {
4248 * i) We cant tell if a magic call would go to __call or __callStatic
4249 * - Could deal with this by checking for the existence of __call
4251 * ii) hphp semantics is that in the case of an object call, we look
4252 * for __call in the scope of the object (this is incompatible
4253 * with zend) which means we would have to know that there is no
4254 * __call higher up in the tree
4255 * - Could deal with this by checking for AttrNoOverride on the
4256 * class
4258 func = nullptr;
4260 } else if (!(func->attrs() & AttrPrivate)) {
4261 if (magicCall || func->attrs() & AttrStatic) {
4262 if (!(cls->preClass()->attrs() & AttrNoOverride)) {
4263 func = nullptr;
4265 } else if (!(func->attrs() & AttrNoOverride && !func->hasStaticLocals()) &&
4266 !(cls->preClass()->attrs() & AttrNoOverride)) {
4267 func = nullptr;
4270 return func;
4273 std::string traceletShape(const Tracelet& trace) {
4274 std::string ret;
4276 for (auto ni = trace.m_instrStream.first; ni; ni = ni->next) {
4277 using folly::toAppend;
4279 toAppend(opcodeToName(ni->op()), &ret);
4280 if (ni->immVec.isValid()) {
4281 toAppend(
4282 "<",
4283 locationCodeString(ni->immVec.locationCode()),
4284 &ret);
4285 for (auto& mc : ni->immVecM) {
4286 toAppend(" ", memberCodeString(mc), &ret);
4288 toAppend(">", &ret);
4290 toAppend(" ", &ret);
4293 return ret;
4296 } // HPHP::Transl
4298 void invalidatePath(const std::string& path) {
4299 TRACE(1, "invalidatePath: abspath %s\n", path.c_str());
4300 PendQ::defer(new DeferredPathInvalidate(path));
4303 } // HPHP