Add TranslArgs and delete a bunch of tx64 code
[hiphop-php.git] / hphp / runtime / vm / translator / translator.cpp
blob247d3f81b6107714c08dca7b030212f0730c347b
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010- 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/translator/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"
34 #include "hphp/runtime/base/runtime_option.h"
35 #include "hphp/runtime/base/types.h"
36 #include "hphp/runtime/ext/ext_continuation.h"
37 #include "hphp/runtime/vm/hhbc.h"
38 #include "hphp/runtime/vm/bytecode.h"
39 #include "hphp/runtime/vm/translator/targetcache.h"
40 #include "hphp/runtime/vm/translator/translator-deps.h"
41 #include "hphp/runtime/vm/translator/translator-inline.h"
42 #include "hphp/runtime/vm/translator/translator-x64.h"
43 #include "hphp/runtime/vm/translator/annotation.h"
44 #include "hphp/runtime/vm/type_profile.h"
45 #include "hphp/runtime/vm/runtime.h"
47 namespace HPHP {
48 namespace Transl {
50 using namespace HPHP;
52 TRACE_SET_MOD(trans)
54 static __thread BiasedCoin *dbgTranslateCoin;
55 Translator* transl;
56 Lease Translator::s_writeLease;
58 struct TraceletContext {
59 TraceletContext() = delete;
61 TraceletContext(Tracelet* t, const TypeMap& initialTypes)
62 : m_t(t)
63 , m_numJmps(0)
64 , m_aliasTaint(false)
65 , m_varEnvTaint(false)
67 for (auto& kv : initialTypes) {
68 TRACE(1, "%s\n",
69 Trace::prettyNode("InitialType", kv.first, kv.second).c_str());
70 m_currentMap[kv.first] = t->newDynLocation(kv.first, kv.second);
74 Tracelet* m_t;
75 ChangeMap m_currentMap;
76 DepMap m_dependencies;
77 DepMap m_resolvedDeps; // dependencies resolved by static analysis
78 LocationSet m_changeSet;
79 LocationSet m_deletedSet;
80 int m_numJmps;
81 bool m_aliasTaint;
82 bool m_varEnvTaint;
84 RuntimeType currentType(const Location& l) const;
85 DynLocation* recordRead(const InputInfo& l, bool useHHIR,
86 DataType staticType = KindOfInvalid);
87 void recordWrite(DynLocation* dl, NormalizedInstruction* source);
88 void recordDelete(const Location& l);
89 void recordJmp();
90 void aliasTaint();
91 void varEnvTaint();
94 void InstrStream::append(NormalizedInstruction* ni) {
95 if (last) {
96 assert(first);
97 last->next = ni;
98 ni->prev = last;
99 ni->next = nullptr;
100 last = ni;
101 return;
103 assert(!first);
104 first = ni;
105 last = ni;
106 ni->prev = nullptr;
107 ni->next = nullptr;
110 void InstrStream::remove(NormalizedInstruction* ni) {
111 if (ni->prev) {
112 ni->prev->next = ni->next;
113 } else {
114 first = ni->next;
116 if (ni->next) {
117 ni->next->prev = ni->prev;
118 } else {
119 last = ni->prev;
121 ni->prev = nullptr;
122 ni->next = nullptr;
125 void Tracelet::constructLiveRanges() {
126 // Helper function.
127 auto considerLoc = [this](DynLocation* dloc,
128 const NormalizedInstruction* ni,
129 bool output) {
130 if (!dloc) return;
131 Location loc = dloc->location;
132 m_liveEnd[loc] = ni->sequenceNum;
133 if (output) m_liveDirtyEnd[loc] = ni->sequenceNum;
135 // We assign each instruction a sequence number. We do this here, rather
136 // than when creating the instruction, to allow splicing and removing
137 // instructions
138 int sequenceNum = 0;
139 for (auto ni = m_instrStream.first; ni; ni = ni->next) {
140 ni->sequenceNum = sequenceNum++;
141 considerLoc(ni->outLocal, ni, true);
142 considerLoc(ni->outStack3, ni, true);
143 considerLoc(ni->outStack2, ni, true);
144 considerLoc(ni->outStack, ni, true);
145 for (auto inp : ni->inputs) {
146 considerLoc(inp, ni, false);
151 bool Tracelet::isLiveAfterInstr(Location l,
152 const NormalizedInstruction& ni) const {
153 const auto end = m_liveEnd.find(l);
154 assert(end != m_liveEnd.end());
155 return ni.sequenceNum < end->second;
158 bool Tracelet::isWrittenAfterInstr(Location l,
159 const NormalizedInstruction& ni) const {
160 const auto end = m_liveDirtyEnd.find(l);
161 if (end == m_liveDirtyEnd.end()) return false;
162 return ni.sequenceNum < end->second;
165 NormalizedInstruction* Tracelet::newNormalizedInstruction() {
166 NormalizedInstruction* ni = new NormalizedInstruction();
167 m_instrs.push_back(ni);
168 return ni;
171 DynLocation* Tracelet::newDynLocation(Location l, DataType t) {
172 DynLocation* dl = new DynLocation(l, t);
173 m_dynlocs.push_back(dl);
174 return dl;
177 DynLocation* Tracelet::newDynLocation(Location l, RuntimeType t) {
178 DynLocation* dl = new DynLocation(l, t);
179 m_dynlocs.push_back(dl);
180 return dl;
183 DynLocation* Tracelet::newDynLocation() {
184 DynLocation* dl = new DynLocation();
185 m_dynlocs.push_back(dl);
186 return dl;
189 void Tracelet::print() const {
190 print(std::cerr);
193 void Tracelet::print(std::ostream& out) const {
194 const NormalizedInstruction* i = m_instrStream.first;
195 if (i == nullptr) {
196 out << "<empty>\n";
197 return;
200 out << i->unit()->filepath()->data() << ':'
201 << i->unit()->getLineNumber(i->offset()) << std::endl;
202 for (; i; i = i->next) {
203 out << " " << i->offset() << ": " << i->toString() << std::endl;
207 void
208 SrcKey::trace(const char *fmt, ...) const {
209 if (!Trace::enabled) {
210 return;
212 // We don't want to print string literals, so don't pass the unit
213 string s = instrToString(curUnit()->at(m_offset));
214 const char *filepath = "*anonFile*";
215 if (curUnit()->filepath()->data() &&
216 strlen(curUnit()->filepath()->data()) > 0)
217 filepath = curUnit()->filepath()->data();
218 Trace::trace("%s:%llx %6d: %20s ",
219 filepath, (unsigned long long)getFuncId(),
220 m_offset, s.c_str());
221 va_list a;
222 va_start(a, fmt);
223 Trace::vtrace(fmt, a);
224 va_end(a);
227 void
228 SrcKey::print(int ninstrs) const {
229 const Unit* u = curUnit();
230 Opcode* op = (Opcode*)u->at(m_offset);
231 std::cerr << u->filepath()->data() << ':' << u->getLineNumber(m_offset)
232 << std::endl;
233 for (int i = 0;
234 i < ninstrs && (uintptr_t)op < ((uintptr_t)u->entry() + u->bclen());
235 op += instrLen(op), ++i) {
236 std::cerr << " " << u->offsetOf(op) << ": " << instrToString(op, u)
237 << std::endl;
241 std::string
242 SrcKey::pretty() const {
243 std::ostringstream result;
244 const char* filepath = tl_regState == REGSTATE_CLEAN ?
245 curUnit()->filepath()->data() : "unknown";
246 result << filepath << ':' << getFuncId() << ':' << m_offset;
247 return result.str();
250 // advance --
252 // Move over the current instruction pointer.
253 void
254 Translator::advance(const Opcode** instrs) {
255 (*instrs) += instrLen(*instrs);
259 * locPhysicalOffset --
261 * Return offset, in cells, of this location from its base
262 * pointer. It needs a function descriptor to see how many locals
263 * to skip for iterators; if the current frame pointer is not the context
264 * you're looking for, be sure to pass in a non-default f.
267 Translator::locPhysicalOffset(Location l, const Func* f) {
268 f = f ? f : curFunc();
269 assert_not_implemented(l.space == Location::Stack ||
270 l.space == Location::Local ||
271 l.space == Location::Iter);
272 int localsToSkip = l.space == Location::Iter ? f->numLocals() : 0;
273 int iterInflator = l.space == Location::Iter ? kNumIterCells : 1;
274 return -((l.offset + 1) * iterInflator + localsToSkip);
277 RuntimeType Translator::liveType(Location l, const Unit& u) {
278 Cell *outer;
279 switch (l.space) {
280 case Location::Stack:
281 // Stack accesses must be to addresses pushed before
282 // translation time; if they are to addresses pushed after,
283 // they should be hitting in the changemap.
284 assert(locPhysicalOffset(l) >= 0);
285 // fallthru
286 case Location::Local: {
287 Cell *base;
288 int offset = locPhysicalOffset(l);
289 base = l.space == Location::Stack ? vmsp() : vmfp();
290 outer = &base[offset];
291 } break;
292 case Location::Iter: {
293 const Iter *it = frame_iter(curFrame(), l.offset);
294 TRACE(1, "Iter input: fp %p, iter %p, offset %" PRId64 "\n", vmfp(),
295 it, l.offset);
296 return RuntimeType(it);
297 } break;
298 case Location::Litstr: {
299 return RuntimeType(u.lookupLitstrId(l.offset));
300 } break;
301 case Location::Litint: {
302 return RuntimeType(l.offset);
303 } break;
304 case Location::This: {
305 return outThisObjectType();
306 } break;
307 default: {
308 not_reached();
311 assert(IS_REAL_TYPE(outer->m_type));
312 return liveType(outer, l);
315 RuntimeType
316 Translator::liveType(const Cell* outer, const Location& l) {
317 always_assert(analysisDepth() == 0);
319 if (!outer) {
320 // An undefined global; starts out as a variant null
321 return RuntimeType(KindOfRef, KindOfNull);
323 DataType outerType = (DataType)outer->m_type;
324 assert(IS_REAL_TYPE(outerType));
325 DataType valueType = outerType;
326 const Cell* valCell = outer;
327 if (outerType == KindOfRef) {
328 // Variant. Pick up the inner type, too.
329 valCell = outer->m_data.pref->tv();
330 DataType innerType = valCell->m_type;
331 assert(IS_REAL_TYPE(innerType));
332 valueType = innerType;
333 assert(innerType != KindOfRef);
334 TRACE(2, "liveType Var -> %d\n", innerType);
335 return RuntimeType(KindOfRef, innerType);
337 const Class *klass = nullptr;
338 if (valueType == KindOfObject) {
339 // TODO: Infer the class, too.
340 if (false) {
341 klass = valCell->m_data.pobj->getVMClass();
344 TRACE(2, "liveType %d\n", outerType);
345 RuntimeType retval = RuntimeType(outerType, KindOfInvalid, klass);
346 return retval;
349 RuntimeType Translator::outThisObjectType() {
351 * Use the current method's context class (ctx) as a constraint.
352 * For instance methods, if $this is non-null, we are guaranteed
353 * that $this is an instance of ctx or a class derived from
354 * ctx. Zend allows this assumption to be violated but we have
355 * deliberately chosen to diverge from them here.
357 * Note that if analysisDepth() != 0 we'll have !hasThis() here,
358 * because our fake ActRec has no $this, but we'll still return the
359 * correct object type because arGetContextClass() looks at
360 * ar->m_func's class for methods.
362 const Class *ctx = curFunc()->isMethod() ?
363 arGetContextClass(curFrame()) : nullptr;
364 if (ctx) {
365 assert(!curFrame()->hasThis() ||
366 curFrame()->getThis()->getVMClass()->classof(ctx));
367 TRACE(2, "OutThisObject: derived from Class \"%s\"\n",
368 ctx->name()->data());
369 return RuntimeType(KindOfObject, KindOfInvalid, ctx);
371 return RuntimeType(KindOfObject, KindOfInvalid);
374 bool Translator::liveFrameIsPseudoMain() {
375 ActRec* ar = (ActRec*)vmfp();
376 return ar->hasVarEnv() && ar->getVarEnv()->isGlobalScope();
379 Location
380 Translator::tvToLocation(const TypedValue* tv, const TypedValue* frame) {
381 const Cell *arg0 = frame + locPhysicalOffset(Location(Location::Local, 0));
382 // Physical stack offsets grow downwards from the frame pointer. See
383 // locPhysicalOffset.
384 int offset = -(tv - arg0);
385 assert(offset >= 0);
386 assert(offset < ((ActRec*)frame)->m_func->numLocals());
387 TRACE(2, "tvToLocation: %p -> L:%d\n", tv, offset);
388 return Location(Location::Local, offset);
391 /* Opcode type-table. */
392 enum OutTypeConstraints {
393 OutNull,
394 OutNullUninit,
395 OutString,
396 OutStringImm, // String w/ precisely known immediate.
397 OutDouble,
398 OutBoolean,
399 OutBooleanImm,
400 OutInt64,
401 OutArray,
402 OutArrayImm,
403 OutObject,
404 OutThisObject, // Object from current environment
405 OutFDesc, // Blows away the current function desc
407 OutUnknown, // Not known at tracelet compile-time
408 OutPred, // Unknown, but give prediction a whirl.
409 OutCns, // Constant; may be known at compile-time
410 OutVUnknown, // type is V(unknown)
412 OutSameAsInput, // type is the same as the first stack inpute
413 OutCInput, // type is C(input)
414 OutVInput, // type is V(input)
415 OutCInputL, // type is C(type) of local input
416 OutVInputL, // type is V(type) of local input
417 OutFInputL, // type is V(type) of local input if current param is
418 // by ref, else type is C(type) of local input
419 OutFInputR, // Like FInputL, but for R's on the stack.
421 OutArith, // For Add, Sub, Mul
422 OutBitOp, // For BitAnd, BitOr, BitXor
423 OutSetOp, // For SetOpL
424 OutIncDec, // For IncDecL
425 OutStrlen, // OpStrLen
426 OutClassRef, // KindOfClass
427 OutNone
431 * Input codes indicate what an instruction reads, and some other
432 * things about their behavior. The order these show up in the inputs
433 * vector is given in getInputs(), and is relevant in a few cases
434 * (e.g. instructions taking both stack inputs and MVectors).
436 enum Operands {
437 None = 0,
438 Stack3 = 1 << 0,
439 Stack2 = 1 << 1,
440 Stack1 = 1 << 2,
441 StackIns1 = 1 << 3, // Insert an element under top of stack
442 StackIns2 = 1 << 4, // Insert an element under top 2 of stack
443 FuncdRef = 1 << 5, // Input to FPass*
444 FStack = 1 << 6, // output of FPushFuncD and friends
445 Local = 1 << 7, // Writes to a local
446 MVector = 1 << 8, // Member-vector input
447 Iter = 1 << 9, // Iterator in imm[0]
448 AllLocals = 1 << 10, // All locals (used by RetC)
449 DontGuardLocal = 1 << 11, // Dont force a guard on behalf of the local input
450 DontGuardStack1 = 1 << 12, // Dont force a guard on behalf of stack1 input
451 DontBreakLocal = 1 << 13, // Dont break a tracelet on behalf of the local
452 DontBreakStack1 = 1 << 14, // Dont break a tracelet on behalf of stack1 input
453 IgnoreInnerType = 1 << 15, // Instruction doesnt care about the inner types
454 DontGuardAny = 1 << 16, // Dont force a guard for any input
455 This = 1 << 17, // Input to CheckThis
456 StackN = 1 << 18, // pop N cells from stack; n = imm[0].u_IVA
457 BStackN = 1 << 19, // consume N cells from stack for builtin call;
458 // n = imm[0].u_IVA
459 StackTop2 = Stack1 | Stack2,
460 StackTop3 = Stack1 | Stack2 | Stack3,
461 StackCufSafe = StackIns1 | FStack
464 Operands
465 operator|(const Operands& l, const Operands& r) {
466 return Operands(int(r) | int(l));
469 static int64_t typeToMask(DataType t) {
470 return (t == KindOfInvalid) ? 1 : (1 << (1 + getDataTypeIndex(t)));
473 struct InferenceRule {
474 int64_t mask;
475 DataType result;
478 static DataType inferType(const InferenceRule* rules,
479 const vector<DynLocation*>& inputs) {
480 int inputMask = 0;
481 // We generate the inputMask by ORing together the mask for each input's
482 // type.
483 for (unsigned int i = 0; i < inputs.size(); ++i) {
484 DataType inType = inputs[i]->rtt.valueType();
485 inputMask |= typeToMask(inType);
487 // This loop checks each rule in order, looking for the first rule that
488 // applies. Note that we assume there's a "catch-all" at the end.
489 for (unsigned int i = 0; ; ++i) {
490 if (rules[i].mask == 0 || (rules[i].mask & inputMask) != 0) {
491 return rules[i].result;
494 // We return KindOfInvalid by default if none of the rules applied.
495 return KindOfInvalid;
499 * Inference rules used for OutArith. These are applied in order
500 * row-by-row.
503 #define TYPE_MASK(name) \
504 static const int64_t name ## Mask = typeToMask(KindOf ## name);
505 TYPE_MASK(Invalid);
506 TYPE_MASK(Uninit);
507 TYPE_MASK(Null);
508 TYPE_MASK(Boolean);
509 static const int64_t IntMask = typeToMask(KindOfInt64);
510 TYPE_MASK(Double);
511 static const int64_t StringMask = typeToMask(KindOfString) |
512 typeToMask(KindOfStaticString);
513 TYPE_MASK(Array);
514 TYPE_MASK(Object);
516 static const InferenceRule ArithRules[] = {
517 { DoubleMask, KindOfDouble },
518 { ArrayMask, KindOfArray },
519 // If one of the inputs is known to be a String or if one of the input
520 // types is unknown, the output type is Unknown
521 { StringMask | InvalidMask, KindOfInvalid },
522 // Default to Int64
523 { 0, KindOfInt64 },
526 static const int NumArithRules = sizeof(ArithRules) / sizeof(InferenceRule);
529 * Returns the type of the output of a bitwise operator on the two
530 * DynLocs. The only case that doesn't result in KindOfInt64 is String
531 * op String.
533 static const InferenceRule BitOpRules[] = {
534 { UninitMask | NullMask | BooleanMask |
535 IntMask | DoubleMask | ArrayMask | ObjectMask,
536 KindOfInt64 },
537 { StringMask, KindOfString },
538 { 0, KindOfInvalid },
541 static RuntimeType bitOpType(DynLocation* a, DynLocation* b) {
542 vector<DynLocation*> ins;
543 ins.push_back(a);
544 if (b) ins.push_back(b);
545 return RuntimeType(inferType(BitOpRules, ins));
548 static uint32_t m_w = 1; /* must not be zero */
549 static uint32_t m_z = 1; /* must not be zero */
551 static uint32_t get_random()
553 m_z = 36969 * (m_z & 65535) + (m_z >> 16);
554 m_w = 18000 * (m_w & 65535) + (m_w >> 16);
555 return (m_z << 16) + m_w; /* 32-bit result */
558 static const int kTooPolyPred = 2;
559 static const int kTooPolyRet = 6;
561 bool
562 isNormalPropertyAccess(const NormalizedInstruction& i,
563 int propInput,
564 int objInput) {
565 const LocationCode lcode = i.immVec.locationCode();
566 return
567 i.immVecM.size() == 1 &&
568 (lcode == LC || lcode == LL || lcode == LR || lcode == LH) &&
569 mcodeMaybePropName(i.immVecM[0]) &&
570 i.inputs[propInput]->isString() &&
571 i.inputs[objInput]->valueType() == KindOfObject;
574 bool
575 mInstrHasUnknownOffsets(const NormalizedInstruction& ni, Class* context) {
576 const MInstrInfo& mii = getMInstrInfo(ni.mInstrOp());
577 unsigned mi = 0;
578 unsigned ii = mii.valCount() + 1;
579 for (; mi < ni.immVecM.size(); ++mi) {
580 MemberCode mc = ni.immVecM[mi];
581 if (mcodeMaybePropName(mc)) {
582 const Class* cls = nullptr;
583 if (getPropertyOffset(ni, context, cls, mii, mi, ii).offset == -1) {
584 return true;
586 ++ii;
587 } else {
588 return true;
592 return false;
595 PropInfo getPropertyOffset(const NormalizedInstruction& ni,
596 Class* ctx,
597 const Class*& baseClass,
598 const MInstrInfo& mii,
599 unsigned mInd, unsigned iInd) {
600 if (mInd == 0) {
601 auto const baseIndex = mii.valCount();
602 baseClass = ni.inputs[baseIndex]->rtt.isObject()
603 ? ni.inputs[baseIndex]->rtt.valueClass()
604 : nullptr;
605 } else {
606 baseClass = ni.immVecClasses[mInd - 1];
608 if (!baseClass) return PropInfo();
610 if (!ni.inputs[iInd]->rtt.isString()) {
611 return PropInfo();
613 auto* const name = ni.inputs[iInd]->rtt.valueString();
614 if (!name) return PropInfo();
616 bool accessible;
617 // If we are not in repo-authoriative mode, we need to check that
618 // baseClass cannot change in between requests
619 if (!RuntimeOption::RepoAuthoritative ||
620 !(baseClass->preClass()->attrs() & AttrUnique)) {
621 if (!ctx) return PropInfo();
622 if (!ctx->classof(baseClass)) {
623 if (baseClass->classof(ctx)) {
624 // baseClass can change on us in between requests, but since
625 // ctx is an ancestor of baseClass we can make the weaker
626 // assumption that the object is an instance of ctx
627 baseClass = ctx;
628 } else {
629 // baseClass can change on us in between requests and it is
630 // not related to ctx, so bail out
631 return PropInfo();
635 // Lookup the index of the property based on ctx and baseClass
636 Slot idx = baseClass->getDeclPropIndex(ctx, name, accessible);
637 // If we couldn't find a property that is accessible in the current
638 // context, bail out
639 if (idx == kInvalidSlot || !accessible) {
640 return PropInfo();
642 // If it's a declared property we're good to go: even if a subclass
643 // redefines an accessible property with the same name it's guaranteed
644 // to be at the same offset
645 return PropInfo(
646 baseClass->declPropOffset(idx),
647 baseClass->declPropHphpcType(idx)
651 PropInfo getFinalPropertyOffset(const NormalizedInstruction& ni,
652 Class* context,
653 const MInstrInfo& mii) {
654 unsigned mInd = ni.immVecM.size() - 1;
655 unsigned iInd = mii.valCount() + 1 + mInd;
657 const Class* cls = nullptr;
658 return getPropertyOffset(ni, context, cls, mii, mInd, iInd);
661 static std::pair<DataType,double>
662 predictMVec(const NormalizedInstruction* ni) {
663 auto info = getFinalPropertyOffset(*ni,
664 curFunc()->cls(),
665 getMInstrInfo(ni->mInstrOp()));
666 if (info.offset != -1 && info.hphpcType != KindOfInvalid) {
667 FTRACE(1, "prediction for CGetM prop: {}, hphpc\n",
668 int(info.hphpcType));
669 return std::make_pair(info.hphpcType, 1.0);
672 auto& immVec = ni->immVec;
673 StringData* name;
674 MemberCode mc;
675 if (immVec.decodeLastMember(curUnit(), name, mc)) {
676 auto pred = predictType(TypeProfileKey(mc, name));
677 TRACE(1, "prediction for CGetM %s named %s: %d, %f\n",
678 mc == MET ? "elt" : "prop",
679 name->data(),
680 pred.first,
681 pred.second);
682 return pred;
685 return std::make_pair(KindOfInvalid, 0.0);
689 * predictOutputs --
691 * Provide a best guess for the output type of this instruction.
693 static DataType
694 predictOutputs(const Tracelet& t,
695 NormalizedInstruction* ni) {
696 if (RuntimeOption::EvalJitStressTypePredPercent &&
697 RuntimeOption::EvalJitStressTypePredPercent > int(get_random() % 100)) {
698 ni->outputPredicted = true;
699 int dt;
700 while (true) {
701 dt = get_random() % (KindOfRef + 1);
702 switch (dt) {
703 case KindOfNull:
704 case KindOfBoolean:
705 case KindOfInt64:
706 case KindOfDouble:
707 case KindOfString:
708 case KindOfArray:
709 case KindOfObject:
710 break;
711 // KindOfRef and KindOfUninit can't happen for lots of predicted
712 // types.
713 case KindOfRef:
714 case KindOfUninit:
715 default:
716 continue;
718 break;
720 return DataType(dt);
723 if (ni->op() == OpMod) {
724 // x % 0 returns boolean false, so we don't know for certain, but it's
725 // probably an int.
726 return KindOfInt64;
729 if (ni->op() == OpDiv) {
730 // Integers can produce integers if there's no residue, but $i / $j in
731 // general produces a double. $i / 0 produces boolean false, so we have
732 // actually check the result.
733 return KindOfDouble;
736 if (ni->op() == OpClsCnsD) {
737 const NamedEntityPair& cne =
738 curFrame()->m_func->unit()->lookupNamedEntityPairId(ni->imm[1].u_SA);
739 StringData* cnsName = curUnit()->lookupLitstrId(ni->imm[0].u_SA);
740 Class* cls = cne.second->getCachedClass();
741 if (cls) {
742 DataType dt = cls->clsCnsType(cnsName);
743 if (dt != KindOfUninit) {
744 ni->outputPredicted = true;
745 TRACE(1, "clscnsd: %s:%s prediction type %d\n",
746 cne.first->data(), cnsName->data(), dt);
747 return dt;
752 static const double kAccept = 1.0;
753 std::pair<DataType, double> pred = std::make_pair(KindOfInvalid, 0.0);
754 // Type predictions grow tracelets, and can have a side effect of making
755 // them combinatorially explode if they bring in precondtions that vary a
756 // lot. Get more conservative as evidence mounts that this is a
757 // polymorphic tracelet.
758 if (tx64->numTranslations(t.m_sk) >= kTooPolyPred) return KindOfInvalid;
759 if (hasImmVector(ni->op())) {
760 pred = predictMVec(ni);
762 if (debug && pred.second < kAccept) {
763 if (const StringData* invName = fcallToFuncName(ni)) {
764 pred = predictType(TypeProfileKey(TypeProfileKey::MethodName, invName));
765 TRACE(1, "prediction for methods named %s: %d, %f\n",
766 invName->data(),
767 pred.first,
768 pred.second);
771 if (pred.second >= kAccept) {
772 ni->outputPredicted = true;
773 TRACE(1, "accepting prediction of type %d\n", pred.first);
774 assert(pred.first != KindOfUninit);
775 return pred.first;
777 return KindOfInvalid;
781 * Returns the type of the value a SetOpL will store into the local.
783 static RuntimeType setOpOutputType(NormalizedInstruction* ni,
784 const vector<DynLocation*>& inputs) {
785 assert(inputs.size() == 2);
786 const int kValIdx = 0;
787 const int kLocIdx = 1;
788 unsigned char op = ni->imm[1].u_OA;
789 DynLocation locLocation(inputs[kLocIdx]->location,
790 inputs[kLocIdx]->rtt.unbox());
791 assert(inputs[kLocIdx]->location.isLocal());
792 switch (op) {
793 case SetOpPlusEqual:
794 case SetOpMinusEqual:
795 case SetOpMulEqual: {
796 // Same as OutArith, except we have to fiddle with inputs a bit.
797 vector<DynLocation*> arithInputs;
798 arithInputs.push_back(&locLocation);
799 arithInputs.push_back(inputs[kValIdx]);
800 return RuntimeType(inferType(ArithRules, arithInputs));
802 case SetOpConcatEqual: return RuntimeType(KindOfString);
803 case SetOpDivEqual:
804 case SetOpModEqual: return RuntimeType(KindOfInvalid);
805 case SetOpAndEqual:
806 case SetOpOrEqual:
807 case SetOpXorEqual: return bitOpType(&locLocation, inputs[kValIdx]);
808 case SetOpSlEqual:
809 case SetOpSrEqual: return RuntimeType(KindOfInt64);
810 default:
811 assert(false);
813 NOT_REACHED();
814 return RuntimeType(KindOfInvalid);
817 static RuntimeType
818 getDynLocType(const vector<DynLocation*>& inputs,
819 const Tracelet& t,
820 Opcode opcode,
821 NormalizedInstruction* ni,
822 Operands op,
823 OutTypeConstraints constraint,
824 DynLocation* outDynLoc) {
825 assert(constraint != OutFInputL);
827 switch (constraint) {
828 #define CS(OutXLike, KindOfX) \
829 case OutXLike: \
830 return RuntimeType(KindOfX);
831 CS(OutInt64, KindOfInt64);
832 CS(OutBoolean, KindOfBoolean);
833 CS(OutDouble, KindOfDouble);
834 CS(OutString, KindOfString);
835 CS(OutNull, KindOfNull);
836 CS(OutUnknown, KindOfInvalid); // Subtle interaction with BB-breaking.
837 CS(OutFDesc, KindOfInvalid); // Unclear if OutFDesc has a purpose.
838 CS(OutArray, KindOfArray);
839 CS(OutObject, KindOfObject);
840 #undef CS
841 case OutPred: return RuntimeType(predictOutputs(t, ni));
843 case OutClassRef: {
844 Op op = Op(ni->op());
845 if ((op == OpAGetC && inputs[0]->isString())) {
846 const StringData *sd = inputs[0]->rtt.valueString();
847 if (sd) {
848 Class *klass = Unit::lookupUniqueClass(sd);
849 TRACE(3, "KindOfClass: derived class \"%s\" from string literal\n",
850 klass ? klass->preClass()->name()->data() : "NULL");
851 return RuntimeType(klass);
853 } else if (op == OpSelf) {
854 return RuntimeType(curClass());
855 } else if (op == OpParent) {
856 Class* clss = curClass();
857 if (clss != nullptr)
858 return RuntimeType(clss->parent());
860 return RuntimeType(KindOfClass);
863 case OutCns: {
864 // If it's a system constant, burn in its type. Otherwise we have
865 // to accept prediction; use the translation-time value, or fall back
866 // to the targetcache if none exists.
867 StringData *sd = curUnit()->lookupLitstrId(ni->imm[0].u_SA);
868 assert(sd);
869 const TypedValue* tv = Unit::lookupPersistentCns(sd);
870 if (tv) {
871 return RuntimeType(tv->m_type);
873 tv = Unit::lookupCns(sd);
874 if (tv) {
875 ni->outputPredicted = true;
876 TRACE(1, "CNS %s: guessing runtime type %d\n", sd->data(), tv->m_type);
877 return RuntimeType(tv->m_type);
879 return RuntimeType(KindOfInvalid);
882 case OutNullUninit: {
883 assert(ni->op() == OpNullUninit);
884 return RuntimeType(KindOfUninit);
887 case OutStringImm: {
888 assert(ni->op() == OpString);
889 StringData *sd = curUnit()->lookupLitstrId(ni->imm[0].u_SA);
890 assert(sd);
891 return RuntimeType(sd);
894 case OutArrayImm: {
895 assert(ni->op() == OpArray);
896 ArrayData *ad = curUnit()->lookupArrayId(ni->imm[0].u_AA);
897 assert(ad);
898 return RuntimeType(ad);
901 case OutBooleanImm: {
902 assert(ni->op() == OpTrue || ni->op() == OpFalse);
903 return RuntimeType(ni->op() == OpTrue);
906 case OutThisObject: {
907 return Translator::outThisObjectType();
910 case OutVUnknown: {
911 return RuntimeType(KindOfRef, KindOfInvalid);
914 case OutArith: {
915 return RuntimeType(inferType(ArithRules, inputs));
918 case OutSameAsInput: {
920 * Relies closely on the order that inputs are pushed in
921 * getInputs(). (Pushing top of stack first for multi-stack
922 * consumers, stack elements before M-vectors and locals, etc.)
924 assert(inputs.size() >= 1);
925 Opcode op = ni->op();
926 ASSERT_NOT_IMPLEMENTED(
927 // Sets and binds that take multiple arguments have the rhs
928 // pushed first. In the case of the M-vector versions, the
929 // rhs comes before the M-vector elements.
930 op == OpSetL || op == OpSetN || op == OpSetG || op == OpSetS ||
931 op == OpBindL || op == OpBindG || op == OpBindS || op == OpBindN ||
932 op == OpSetM || op == OpBindM ||
933 // Dup takes a single element.
934 op == OpDup
937 if (op == OpSetM || op == OpBindM) {
939 * these return null for "invalid" inputs
940 * if we cant prove everything's ok, we will
941 * have to insert a side exit
943 bool ok = inputs.size() <= 3;
944 if (ok) {
945 switch (inputs[1]->rtt.valueType()) {
946 case KindOfObject:
947 ok = mcodeMaybePropName(ni->immVecM[0]);
948 break;
949 case KindOfArray:
950 ok = mcodeMaybeArrayKey(ni->immVecM[0]);
951 break;
952 case KindOfNull:
953 case KindOfUninit:
954 break;
955 default:
956 ok = false;
959 if (ok) {
960 for (int i = inputs.size(); --i >= 2; ) {
961 switch (inputs[i]->rtt.valueType()) {
962 case KindOfObject:
963 case KindOfArray:
964 ok = false;
965 break;
966 default:
967 continue;
969 break;
972 if (!ok) {
973 ni->outputPredicted = true;
977 const int idx = 0; // all currently supported cases.
979 if (debug) {
980 if (!inputs[idx]->rtt.isVagueValue()) {
981 if (op == OpBindG || op == OpBindN || op == OpBindS ||
982 op == OpBindM || op == OpBindL) {
983 assert(inputs[idx]->rtt.isRef() && !inputs[idx]->isLocal());
984 } else {
985 assert(inputs[idx]->rtt.valueType() ==
986 inputs[idx]->rtt.outerType());
990 return inputs[idx]->rtt;
993 case OutCInputL: {
994 assert(inputs.size() >= 1);
995 const DynLocation* in = inputs[inputs.size() - 1];
996 RuntimeType retval;
997 if (in->rtt.outerType() == KindOfUninit) {
998 // Locals can be KindOfUninit, so we need to convert
999 // this to KindOfNull
1000 retval = RuntimeType(KindOfNull);
1001 } else {
1002 retval = in->rtt.unbox();
1004 TRACE(2, "Input (%d, %d) -> (%d, %d)\n",
1005 in->rtt.outerType(), in->rtt.innerType(),
1006 retval.outerType(), retval.innerType());
1007 return retval;
1010 case OutIncDec: {
1011 const RuntimeType &inRtt = ni->inputs[0]->rtt;
1012 // TODO: instead of KindOfInvalid this should track the actual
1013 // type we will get from interping a non-int IncDec.
1014 return RuntimeType(IS_INT_TYPE(inRtt.valueType()) ?
1015 KindOfInt64 : KindOfInvalid);
1018 case OutStrlen: {
1019 auto const& rtt = ni->inputs[0]->rtt;
1020 return RuntimeType(rtt.isString() ? KindOfInt64 : KindOfInvalid);
1023 case OutCInput: {
1024 assert(inputs.size() >= 1);
1025 const DynLocation* in = inputs[inputs.size() - 1];
1026 if (in->rtt.outerType() == KindOfRef) {
1027 return in->rtt.unbox();
1029 return in->rtt;
1032 case OutBitOp: {
1033 assert(inputs.size() == 2 ||
1034 (inputs.size() == 1 && opcode == OpBitNot));
1035 if (inputs.size() == 2) {
1036 return bitOpType(inputs[0], inputs[1]);
1037 } else {
1038 return bitOpType(inputs[0], nullptr);
1042 case OutSetOp: {
1043 return setOpOutputType(ni, inputs);
1046 case OutNone:
1047 default:
1048 return RuntimeType(KindOfInvalid);
1053 * NB: this opcode structure is sparse; it cannot just be indexed by
1054 * opcode.
1056 struct InstrInfo {
1057 Operands in;
1058 Operands out;
1059 OutTypeConstraints type; // How are outputs related to inputs?
1060 int stackDelta; // Impact on stack: # cells *pushed*
1063 static const struct {
1064 Opcode op;
1065 InstrInfo info;
1066 } instrInfoSparse [] = {
1068 // Op Inputs Outputs OutputTypes Stack delta
1069 // -- ------ ------- ----------- -----------
1071 /*** 1. Basic instructions ***/
1073 { OpNop, {None, None, OutNone, 0 }},
1074 { OpPopC, {Stack1|
1075 DontGuardStack1, None, OutNone, -1 }},
1076 { OpPopV, {Stack1|
1077 DontGuardStack1|
1078 IgnoreInnerType, None, OutNone, -1 }},
1079 { OpPopR, {Stack1|
1080 DontGuardStack1|
1081 IgnoreInnerType, None, OutNone, -1 }},
1082 { OpDup, {Stack1, StackTop2, OutSameAsInput, 1 }},
1083 { OpBox, {Stack1, Stack1, OutVInput, 0 }},
1084 { OpUnbox, {Stack1, Stack1, OutCInput, 0 }},
1085 { OpBoxR, {Stack1, Stack1, OutVInput, 0 }},
1086 { OpUnboxR, {Stack1, Stack1, OutCInput, 0 }},
1088 /*** 2. Literal and constant instructions ***/
1090 { OpNull, {None, Stack1, OutNull, 1 }},
1091 { OpNullUninit, {None, Stack1, OutNullUninit, 1 }},
1092 { OpTrue, {None, Stack1, OutBooleanImm, 1 }},
1093 { OpFalse, {None, Stack1, OutBooleanImm, 1 }},
1094 { OpInt, {None, Stack1, OutInt64, 1 }},
1095 { OpDouble, {None, Stack1, OutDouble, 1 }},
1096 { OpString, {None, Stack1, OutStringImm, 1 }},
1097 { OpArray, {None, Stack1, OutArrayImm, 1 }},
1098 { OpNewArray, {None, Stack1, OutArray, 1 }},
1099 { OpNewTuple, {StackN, Stack1, OutArray, 0 }},
1100 { OpAddElemC, {StackTop3, Stack1, OutArray, -2 }},
1101 { OpAddElemV, {StackTop3, Stack1, OutArray, -2 }},
1102 { OpAddNewElemC, {StackTop2, Stack1, OutArray, -1 }},
1103 { OpAddNewElemV, {StackTop2, Stack1, OutArray, -1 }},
1104 { OpNewCol, {None, Stack1, OutObject, 1 }},
1105 { OpColAddElemC, {StackTop3, Stack1, OutObject, -2 }},
1106 { OpColAddNewElemC, {StackTop2, Stack1, OutObject, -1 }},
1107 { OpCns, {None, Stack1, OutCns, 1 }},
1108 { OpCnsE, {None, Stack1, OutCns, 1 }},
1109 { OpCnsU, {None, Stack1, OutCns, 1 }},
1110 { OpClsCns, {Stack1, Stack1, OutUnknown, 0 }},
1111 { OpClsCnsD, {None, Stack1, OutPred, 1 }},
1112 { OpFile, {None, Stack1, OutString, 1 }},
1113 { OpDir, {None, Stack1, OutString, 1 }},
1115 /*** 3. Operator instructions ***/
1117 /* Binary string */
1118 { OpConcat, {StackTop2, Stack1, OutString, -1 }},
1119 /* Arithmetic ops */
1120 { OpAdd, {StackTop2, Stack1, OutArith, -1 }},
1121 { OpSub, {StackTop2, Stack1, OutArith, -1 }},
1122 { OpMul, {StackTop2, Stack1, OutArith, -1 }},
1123 /* Div and mod might return boolean false. Sigh. */
1124 { OpDiv, {StackTop2, Stack1, OutUnknown, -1 }},
1125 { OpMod, {StackTop2, Stack1, OutUnknown, -1 }},
1126 /* Logical ops */
1127 { OpXor, {StackTop2, Stack1, OutBoolean, -1 }},
1128 { OpNot, {Stack1, Stack1, OutBoolean, 0 }},
1129 { OpSame, {StackTop2, Stack1, OutBoolean, -1 }},
1130 { OpNSame, {StackTop2, Stack1, OutBoolean, -1 }},
1131 { OpEq, {StackTop2, Stack1, OutBoolean, -1 }},
1132 { OpNeq, {StackTop2, Stack1, OutBoolean, -1 }},
1133 { OpLt, {StackTop2, Stack1, OutBoolean, -1 }},
1134 { OpLte, {StackTop2, Stack1, OutBoolean, -1 }},
1135 { OpGt, {StackTop2, Stack1, OutBoolean, -1 }},
1136 { OpGte, {StackTop2, Stack1, OutBoolean, -1 }},
1137 /* Bitwise ops */
1138 { OpBitAnd, {StackTop2, Stack1, OutBitOp, -1 }},
1139 { OpBitOr, {StackTop2, Stack1, OutBitOp, -1 }},
1140 { OpBitXor, {StackTop2, Stack1, OutBitOp, -1 }},
1141 { OpBitNot, {Stack1, Stack1, OutBitOp, 0 }},
1142 { OpShl, {StackTop2, Stack1, OutInt64, -1 }},
1143 { OpShr, {StackTop2, Stack1, OutInt64, -1 }},
1144 /* Cast instructions */
1145 { OpCastBool, {Stack1, Stack1, OutBoolean, 0 }},
1146 { OpCastInt, {Stack1, Stack1, OutInt64, 0 }},
1147 { OpCastDouble, {Stack1, Stack1, OutDouble, 0 }},
1148 { OpCastString, {Stack1, Stack1, OutString, 0 }},
1149 { OpCastArray, {Stack1, Stack1, OutArray, 0 }},
1150 { OpCastObject, {Stack1, Stack1, OutObject, 0 }},
1151 { OpInstanceOf, {StackTop2, Stack1, OutBoolean, -1 }},
1152 { OpInstanceOfD, {Stack1, Stack1, OutBoolean, 0 }},
1153 { OpPrint, {Stack1, Stack1, OutInt64, 0 }},
1154 { OpClone, {Stack1, Stack1, OutObject, 0 }},
1155 { OpExit, {Stack1, None, OutNone, -1 }},
1156 { OpFatal, {Stack1, None, OutNone, -1 }},
1158 /*** 4. Control flow instructions ***/
1160 { OpJmp, {None, None, OutNone, 0 }},
1161 { OpJmpZ, {Stack1, None, OutNone, -1 }},
1162 { OpJmpNZ, {Stack1, None, OutNone, -1 }},
1163 { OpSwitch, {Stack1, None, OutNone, -1 }},
1164 { OpSSwitch, {Stack1, None, OutNone, -1 }},
1166 * RetC and RetV are special. Their manipulation of the runtime stack are
1167 * outside the boundaries of the tracelet abstraction; since they always end
1168 * a basic block, they behave more like "glue" between BBs than the
1169 * instructions in the body of a BB.
1171 * RetC and RetV consume a value from the stack, and this value's type needs
1172 * to be known at compile-time.
1174 { OpRetC, {AllLocals, None, OutNone, 0 }},
1175 { OpRetV, {AllLocals, None, OutNone, 0 }},
1176 { OpThrow, {Stack1, None, OutNone, -1 }},
1177 { OpUnwind, {None, None, OutNone, 0 }},
1179 /*** 5. Get instructions ***/
1181 { OpCGetL, {Local, Stack1, OutCInputL, 1 }},
1182 { OpCGetL2, {Stack1|Local, StackIns1, OutCInputL, 1 }},
1183 { OpCGetL3, {StackTop2|Local, StackIns2, OutCInputL, 1 }},
1184 { OpCGetN, {Stack1, Stack1, OutUnknown, 0 }},
1185 { OpCGetG, {Stack1, Stack1, OutUnknown, 0 }},
1186 { OpCGetS, {StackTop2, Stack1, OutUnknown, -1 }},
1187 { OpCGetM, {MVector, Stack1, OutPred, 1 }},
1188 { OpVGetL, {Local, Stack1, OutVInputL, 1 }},
1189 { OpVGetN, {Stack1, Stack1, OutVUnknown, 0 }},
1190 // TODO: In pseudo-main, the VGetG instruction invalidates what we know
1191 // about the types of the locals because it could cause any one of the
1192 // local variables to become "boxed". We need to add logic to tracelet
1193 // analysis to deal with this properly.
1194 { OpVGetG, {Stack1, Stack1, OutVUnknown, 0 }},
1195 { OpVGetS, {StackTop2, Stack1, OutVUnknown, -1 }},
1196 { OpVGetM, {MVector, Stack1|Local, OutVUnknown, 1 }},
1197 { OpAGetC, {Stack1, Stack1, OutClassRef, 0 }},
1198 { OpAGetL, {Local, Stack1, OutClassRef, 1 }},
1200 /*** 6. Isset, Empty, and type querying instructions ***/
1202 { OpAKExists, {StackTop2, Stack1, OutBoolean, -1 }},
1203 { OpIssetL, {Local, Stack1, OutBoolean, 1 }},
1204 { OpIssetN, {Stack1, Stack1, OutBoolean, 0 }},
1205 { OpIssetG, {Stack1, Stack1, OutBoolean, 0 }},
1206 { OpIssetS, {StackTop2, Stack1, OutBoolean, -1 }},
1207 { OpIssetM, {MVector, Stack1, OutBoolean, 1 }},
1208 { OpEmptyL, {Local, Stack1, OutBoolean, 1 }},
1209 { OpEmptyN, {Stack1, Stack1, OutBoolean, 0 }},
1210 { OpEmptyG, {Stack1, Stack1, OutBoolean, 0 }},
1211 { OpEmptyS, {StackTop2, Stack1, OutBoolean, -1 }},
1212 { OpEmptyM, {MVector, Stack1, OutBoolean, 1 }},
1213 { OpIsNullC, {Stack1, Stack1, OutBoolean, 0 }},
1214 { OpIsBoolC, {Stack1, Stack1, OutBoolean, 0 }},
1215 { OpIsIntC, {Stack1, Stack1, OutBoolean, 0 }},
1216 { OpIsDoubleC, {Stack1, Stack1, OutBoolean, 0 }},
1217 { OpIsStringC, {Stack1, Stack1, OutBoolean, 0 }},
1218 { OpIsArrayC, {Stack1, Stack1, OutBoolean, 0 }},
1219 { OpIsObjectC, {Stack1, Stack1, OutBoolean, 0 }},
1220 { OpIsNullL, {Local, Stack1, OutBoolean, 1 }},
1221 { OpIsBoolL, {Local, Stack1, OutBoolean, 1 }},
1222 { OpIsIntL, {Local, Stack1, OutBoolean, 1 }},
1223 { OpIsDoubleL, {Local, Stack1, OutBoolean, 1 }},
1224 { OpIsStringL, {Local, Stack1, OutBoolean, 1 }},
1225 { OpIsArrayL, {Local, Stack1, OutBoolean, 1 }},
1226 { OpIsObjectL, {Local, Stack1, OutBoolean, 1 }},
1228 /*** 7. Mutator instructions ***/
1230 { OpSetL, {Stack1|Local, Stack1|Local, OutSameAsInput, 0 }},
1231 { OpSetN, {StackTop2, Stack1|Local, OutSameAsInput, -1 }},
1232 { OpSetG, {StackTop2, Stack1|Local, OutSameAsInput, -1 }},
1233 { OpSetS, {StackTop3, Stack1, OutSameAsInput, -2 }},
1234 { OpSetM, {MVector|Stack1, Stack1|Local, OutSameAsInput, 0 }},
1235 { OpSetOpL, {Stack1|Local, Stack1|Local, OutSetOp, 0 }},
1236 { OpSetOpN, {StackTop2, Stack1|Local, OutUnknown, -1 }},
1237 { OpSetOpG, {StackTop2, Stack1|Local, OutUnknown, -1 }},
1238 { OpSetOpS, {StackTop3, Stack1, OutUnknown, -2 }},
1239 { OpSetOpM, {MVector|Stack1, Stack1|Local, OutUnknown, 0 }},
1240 { OpIncDecL, {Local, Stack1|Local, OutIncDec, 1 }},
1241 { OpIncDecN, {Stack1, Stack1|Local, OutUnknown, 0 }},
1242 { OpIncDecG, {Stack1, Stack1|Local, OutUnknown, 0 }},
1243 { OpIncDecS, {StackTop2, Stack1, OutUnknown, -1 }},
1244 { OpIncDecM, {MVector, Stack1, OutUnknown, 1 }},
1245 { OpBindL, {Stack1|Local|
1246 IgnoreInnerType, Stack1|Local, OutSameAsInput, 0 }},
1247 { OpBindN, {StackTop2, Stack1|Local, OutSameAsInput, -1 }},
1248 { OpBindG, {StackTop2, Stack1|Local, OutSameAsInput, -1 }},
1249 { OpBindS, {StackTop3, Stack1, OutSameAsInput, -2 }},
1250 { OpBindM, {MVector|Stack1, Stack1|Local, OutSameAsInput, 0 }},
1251 { OpUnsetL, {Local, Local, OutNone, 0 }},
1252 { OpUnsetN, {Stack1, Local, OutNone, -1 }},
1253 { OpUnsetG, {Stack1, Local, OutNone, -1 }},
1254 { OpUnsetM, {MVector, None, OutNone, 0 }},
1256 /*** 8. Call instructions ***/
1258 { OpFPushFunc, {Stack1, FStack, OutFDesc,
1259 kNumActRecCells - 1 }},
1260 { OpFPushFuncD, {None, FStack, OutFDesc,
1261 kNumActRecCells }},
1262 { OpFPushFuncU, {None, FStack, OutFDesc,
1263 kNumActRecCells }},
1264 { OpFPushObjMethod,
1265 {StackTop2, FStack, OutFDesc,
1266 kNumActRecCells - 2 }},
1267 { OpFPushObjMethodD,
1268 {Stack1, FStack, OutFDesc,
1269 kNumActRecCells - 1 }},
1270 { OpFPushClsMethod,
1271 {StackTop2, FStack, OutFDesc,
1272 kNumActRecCells - 2 }},
1273 { OpFPushClsMethodF,
1274 {StackTop2, FStack, OutFDesc,
1275 kNumActRecCells - 2 }},
1276 { OpFPushClsMethodD,
1277 {None, FStack, OutFDesc,
1278 kNumActRecCells }},
1279 { OpFPushCtor, {Stack1, Stack1|FStack,OutObject,
1280 kNumActRecCells }},
1281 { OpFPushCtorD, {None, Stack1|FStack,OutObject,
1282 kNumActRecCells + 1 }},
1283 { OpFPushCuf, {Stack1, FStack, OutFDesc,
1284 kNumActRecCells - 1 }},
1285 { OpFPushCufF, {Stack1, FStack, OutFDesc,
1286 kNumActRecCells - 1 }},
1287 { OpFPushCufSafe,{StackTop2|DontGuardAny,
1288 StackCufSafe, OutFDesc,
1289 kNumActRecCells }},
1290 { OpFPassC, {FuncdRef, None, OutNull, 0 }},
1291 { OpFPassCW, {FuncdRef, None, OutNull, 0 }},
1292 { OpFPassCE, {FuncdRef, None, OutNull, 0 }},
1293 { OpFPassV, {Stack1|FuncdRef, Stack1, OutUnknown, 0 }},
1294 { OpFPassR, {Stack1|FuncdRef, Stack1, OutFInputR, 0 }},
1295 { OpFPassL, {Local|FuncdRef, Stack1, OutFInputL, 1 }},
1296 { OpFPassN, {Stack1|FuncdRef, Stack1, OutUnknown, 0 }},
1297 { OpFPassG, {Stack1|FuncdRef, Stack1, OutFInputR, 0 }},
1298 { OpFPassS, {StackTop2|FuncdRef,
1299 Stack1, OutUnknown, -1 }},
1300 { OpFPassM, {MVector|FuncdRef, Stack1, OutUnknown, 1 }},
1301 { OpBPassC, {None, None, OutNull, 0 }},
1302 { OpBPassV, {None, None, OutNull, 0 }},
1304 * FCall is special. Like the Ret* instructions, its manipulation of the
1305 * runtime stack are outside the boundaries of the tracelet abstraction.
1307 { OpFCall, {FStack, Stack1, OutPred, 0 }},
1308 { OpFCallArray, {FStack, Stack1, OutPred,
1309 -(int)kNumActRecCells }},
1310 // TODO: output type is known
1311 { OpFCallBuiltin,{BStackN, Stack1, OutPred, 0 }},
1312 { OpCufSafeArray,{StackTop3|DontGuardAny,
1313 Stack1, OutArray, -2 }},
1314 { OpCufSafeReturn,{StackTop3|DontGuardAny,
1315 Stack1, OutUnknown, -2 }},
1317 /*** 11. Iterator instructions ***/
1319 { OpIterInit, {Stack1, Local, OutUnknown, -1 }},
1320 { OpMIterInit, {Stack1, Local, OutUnknown, -1 }},
1321 { OpIterInitK, {Stack1, Local, OutUnknown, -1 }},
1322 { OpMIterInitK, {Stack1, Local, OutUnknown, -1 }},
1323 { OpIterNext, {None, Local, OutUnknown, 0 }},
1324 { OpMIterNext, {None, Local, OutUnknown, 0 }},
1325 { OpIterNextK, {None, Local, OutUnknown, 0 }},
1326 { OpMIterNextK, {None, Local, OutUnknown, 0 }},
1327 { OpIterFree, {None, None, OutNone, 0 }},
1328 { OpMIterFree, {None, None, OutNone, 0 }},
1330 /*** 12. Include, eval, and define instructions ***/
1332 { OpIncl, {Stack1, Stack1, OutUnknown, 0 }},
1333 { OpInclOnce, {Stack1, Stack1, OutUnknown, 0 }},
1334 { OpReq, {Stack1, Stack1, OutUnknown, 0 }},
1335 { OpReqOnce, {Stack1, Stack1, OutUnknown, 0 }},
1336 { OpReqDoc, {Stack1, Stack1, OutUnknown, 0 }},
1337 { OpEval, {Stack1, Stack1, OutUnknown, 0 }},
1338 { OpDefFunc, {None, None, OutNone, 0 }},
1339 { OpDefTypedef, {None, None, OutNone, 0 }},
1340 { OpDefCls, {None, None, OutNone, 0 }},
1341 { OpDefCns, {Stack1, Stack1, OutBoolean, 0 }},
1343 /*** 13. Miscellaneous instructions ***/
1345 { OpThis, {None, Stack1, OutThisObject, 1 }},
1346 { OpBareThis, {None, Stack1, OutUnknown, 1 }},
1347 { OpCheckThis, {This, None, OutNone, 0 }},
1348 { OpInitThisLoc,
1349 {None, Local, OutUnknown, 0 }},
1350 { OpStaticLoc,
1351 {None, Stack1, OutBoolean, 1 }},
1352 { OpStaticLocInit,
1353 {Stack1, Local, OutVUnknown, -1 }},
1354 { OpCatch, {None, Stack1, OutObject, 1 }},
1355 { OpVerifyParamType,
1356 {Local, None, OutNone, 0 }},
1357 { OpClassExists, {StackTop2, Stack1, OutBoolean, -1 }},
1358 { OpInterfaceExists,
1359 {StackTop2, Stack1, OutBoolean, -1 }},
1360 { OpTraitExists, {StackTop2, Stack1, OutBoolean, -1 }},
1361 { OpSelf, {None, Stack1, OutClassRef, 1 }},
1362 { OpParent, {None, Stack1, OutClassRef, 1 }},
1363 { OpLateBoundCls,{None, Stack1, OutClassRef, 1 }},
1364 { OpNativeImpl, {None, None, OutNone, 0 }},
1365 { OpCreateCl, {BStackN, Stack1, OutObject, 1 }},
1366 { OpStrlen, {Stack1, Stack1, OutStrlen, 0 }},
1367 { OpIncStat, {None, None, OutNone, 0 }},
1368 { OpArrayIdx, {StackTop3, Stack1, OutUnknown, -2 }},
1370 /*** 14. Continuation instructions ***/
1372 { OpCreateCont, {None, Stack1, OutObject, 1 }},
1373 { OpContEnter, {None, None, OutNone, 0 }},
1374 { OpContExit, {None, None, OutNone, 0 }},
1375 { OpUnpackCont, {Local, Stack1, OutInt64, 1 }},
1376 { OpPackCont, {Local|Stack1, None, OutNone, -1 }},
1377 { OpContReceive, {Local, Stack1, OutUnknown, 1 }},
1378 { OpContRetC, {Local|Stack1, None, OutNone, -1 }},
1379 { OpContNext, {None, None, OutNone, 0 }},
1380 { OpContSend, {Local, None, OutNone, 0 }},
1381 { OpContRaise, {Local, None, OutNone, 0 }},
1382 { OpContValid, {None, Stack1, OutBoolean, 1 }},
1383 { OpContCurrent, {None, Stack1, OutUnknown, 1 }},
1384 { OpContStopped, {None, None, OutNone, 0 }},
1385 { OpContHandle, {Stack1, None, OutNone, -1 }},
1388 static hphp_hash_map<Opcode, InstrInfo> instrInfo;
1389 static bool instrInfoInited;
1390 static void initInstrInfo() {
1391 if (!instrInfoInited) {
1392 for (size_t i = 0; i < sizeof(instrInfoSparse) / sizeof(instrInfoSparse[0]);
1393 i++) {
1394 instrInfo[instrInfoSparse[i].op] = instrInfoSparse[i].info;
1397 instrInfoInited = true;
1401 static int numHiddenStackInputs(const NormalizedInstruction& ni) {
1402 assert(ni.immVec.isValid());
1403 return ni.immVec.numStackValues();
1406 int getStackDelta(const NormalizedInstruction& ni) {
1407 int hiddenStackInputs = 0;
1408 initInstrInfo();
1409 Opcode op = ni.op();
1410 switch (op) {
1411 case OpFCall: {
1412 int numArgs = ni.imm[0].u_IVA;
1413 return 1 - numArgs - kNumActRecCells;
1416 case OpFCallBuiltin:
1417 case OpNewTuple:
1418 case OpCreateCl:
1419 return 1 - ni.imm[0].u_IVA;
1421 const InstrInfo& info = instrInfo[op];
1422 if (info.in & MVector) {
1423 hiddenStackInputs = numHiddenStackInputs(ni);
1424 SKTRACE(2, ni.source, "Has %d hidden stack inputs\n", hiddenStackInputs);
1426 int delta = instrInfo[op].stackDelta - hiddenStackInputs;
1427 return delta;
1431 * analyzeSecondPass --
1433 * Whole-tracelet analysis pass, after we've set up the dataflow
1434 * graph. Modifies the instruction stream, and further annotates
1435 * individual instructions.
1437 void Translator::analyzeSecondPass(Tracelet& t) {
1438 assert(t.m_instrStream.last);
1439 NormalizedInstruction* next;
1440 for (NormalizedInstruction* ni = t.m_instrStream.first; ni; ni = next) {
1441 const Opcode op = ni->op();
1442 next = ni->next;
1444 if (op == OpNop) {
1445 t.m_instrStream.remove(ni);
1446 continue;
1449 if (op == OpCGetL) {
1451 * If the local isn't more broadly useful in this tracelet, don't bother
1452 * allocating space for it.
1454 const Location& local = ni->inputs[0]->location;
1456 bool seen = false;
1457 for (NormalizedInstruction* cur = ni->next; cur; cur = cur->next) {
1458 if ((ni->m_txFlags & Native) != Native) break;
1459 for (unsigned dli = 0; dli < cur->inputs.size(); ++dli) {
1460 Location& loc = cur->inputs[dli]->location;
1461 if (loc == local) {
1462 SKTRACE(1, ni->source, "CGetL: loading input\n");
1463 seen = true;
1464 break;
1469 ni->manuallyAllocInputs = !seen && !ni->inputs[0]->rtt.isUninit();
1470 SKTRACE(1, ni->source, "CGetL: manuallyAllocInputs: %d\n",
1471 ni->manuallyAllocInputs);
1472 continue;
1475 NormalizedInstruction* prev = ni->prev;
1476 if (!prev || !(prev->m_txFlags & Supported)) continue;
1477 const Opcode prevOp = prev->op();
1479 if (!ni->next &&
1480 (op == OpJmpZ || op == OpJmpNZ)) {
1481 if (prevOp == OpNot && (ni->m_txFlags & Supported)) {
1482 ni->invertCond = !ni->invertCond;
1483 ni->inputs[0] = prev->inputs[0];
1484 assert(!prev->deadLocs.size());
1485 t.m_instrStream.remove(prev);
1486 next = ni;
1487 continue;
1489 if (prevOp == OpGt || prevOp == OpLt ||
1490 prevOp == OpGte || prevOp == OpLte ||
1491 prevOp == OpEq || prevOp == OpNeq ||
1492 prevOp == OpIssetL || prevOp == OpAKExists ||
1493 isTypePred(prevOp) || prevOp == OpInstanceOfD ||
1494 prev->fuseBranch) {
1495 prev->breaksTracelet = true;
1496 prev->changesPC = true; // Dont generate generic glue.
1497 // Leave prev->next linked. The translator will end up needing it. The
1498 // breaksTracelet annotation here will prevent us from really translating the
1499 // Jmp*.
1500 continue;
1504 if (op == OpFPushClsMethodF && ni->directCall &&
1505 prevOp == OpAGetC &&
1506 prev->prev && prev->prev->op() == OpString &&
1507 prev->prev->prev && prev->prev->prev->op() == OpString) {
1509 * We have a fully determined OpFPushClsMethodF. We dont
1510 * need to put the class and method name strings, or the
1511 * Class* into registers.
1513 prev->outStack = nullptr;
1514 prev->prev->outStack = nullptr;
1515 prev->prev->prev->outStack = nullptr;
1518 if (RuntimeOption::RepoAuthoritative &&
1519 prevOp == OpFPushCtorD &&
1520 !prev->noCtor &&
1521 prev->imm[0].u_IVA == 0 &&
1522 op == OpFCall && (ni->m_txFlags & Supported) &&
1523 ni->next && (ni->next->m_txFlags & Supported) &&
1524 ni->next->op() == OpPopR) {
1525 /* new obj with a ctor that takes no args */
1526 const NamedEntityPair& np =
1527 curUnit()->lookupNamedEntityPairId(prev->imm[1].u_SA);
1528 const Class* cls = Unit::lookupUniqueClass(np.second);
1529 if (cls && (cls->attrs() & AttrUnique) &&
1530 Func::isSpecial(cls->getCtor()->name())) {
1531 /* its the generated 86ctor, so no need to call it */
1532 next = next->next;
1533 t.m_instrStream.remove(ni->next);
1534 t.m_instrStream.remove(ni);
1535 prev->noCtor = 1;
1536 SKTRACE(1, prev->source, "FPushCtorD: killing ctor for %s in %s\n",
1537 np.first->data(), curFunc()->fullName()->data());
1538 continue;
1543 * If this is a Pop instruction and the previous instruction pushed a
1544 * single return value cell on the stack, we can roll the pop into the
1545 * previous instruction.
1547 * TODO: SetG/SetS?
1549 const bool isPop = op == OpPopC || op == OpPopV;
1550 const bool isOptimizable = prevOp == OpSetL ||
1551 prevOp == OpBindL ||
1552 prevOp == OpIncDecL ||
1553 prevOp == OpPrint ||
1554 prevOp == OpSetM ||
1555 prevOp == OpSetOpM ||
1556 prevOp == OpIncDecM;
1558 if (isPop && isOptimizable) {
1559 // If one of these instructions already has a null outStack, we
1560 // already hoisted a pop into it.
1561 const bool alreadyHoisted = !prev->outStack;
1563 if (!alreadyHoisted) {
1564 prev->outStack = nullptr;
1565 SKTRACE(3, ni->source, "hoisting Pop instruction in analysis\n");
1566 for (unsigned i = 0; i < ni->deadLocs.size(); ++i) {
1567 prev->deadLocs.push_back(ni->deadLocs[i]);
1569 t.m_instrStream.remove(ni);
1570 if ((prevOp == OpSetM || prevOp == OpSetOpM || prevOp == OpIncDecM) &&
1571 prev->prev && prev->prev->op() == OpCGetL &&
1572 prev->prev->inputs[0]->outerType() != KindOfUninit) {
1573 assert(prev->prev->outStack);
1574 prev->prev->outStack = 0;
1575 prev->prev->manuallyAllocInputs = true;
1576 prev->prev->ignoreInnerType = true;
1577 prev->inputs[0] = prev->prev->inputs[0];
1578 prev->grouped = true;
1580 continue;
1585 * A Not instruction following an Is* instruction can
1586 * be folded.
1588 if (op == OpNot) {
1589 switch (prevOp) {
1590 case OpAKExists:
1591 case OpIssetL:
1592 case OpIsNullL: case OpIsNullC:
1593 case OpIsBoolL: case OpIsBoolC:
1594 case OpIsIntL: case OpIsIntC:
1595 case OpIsDoubleL: case OpIsDoubleC:
1596 case OpIsStringL: case OpIsStringC:
1597 case OpIsArrayL: case OpIsArrayC:
1598 case OpIsObjectL: case OpIsObjectC:
1599 prev->invertCond = !prev->invertCond;
1600 prev->outStack = ni->outStack;
1601 SKTRACE(3, ni->source, "folding Not instruction in analysis\n");
1602 assert(!ni->deadLocs.size());
1603 t.m_instrStream.remove(ni);
1604 continue;
1608 if (op == OpInstanceOfD && prevOp == OpCGetL &&
1609 (ni->m_txFlags & Supported)) {
1610 assert(prev->outStack);
1611 ni->inputs[0] = prev->inputs[0];
1613 the CGetL becomes a no-op (other
1614 than checking for UninitNull), but
1615 we mark the InstanceOfD as grouped to
1616 avoid breaking the tracelet between the
1617 two.
1619 prev->ignoreInnerType = true;
1620 prev->outStack = 0;
1621 prev->manuallyAllocInputs = true;
1622 ni->grouped = true;
1625 if ((op == OpInstanceOfD || op == OpIsNullC) &&
1626 (ni->m_txFlags & Supported) &&
1627 (prevOp == OpThis || prevOp == OpBareThis)) {
1628 prev->outStack = 0;
1629 ni->grouped = true;
1630 ni->manuallyAllocInputs = true;
1634 * TODO: #1181258 this should mostly be subsumed by the IR.
1635 * Remove this once the IR is seen to be handling it.
1637 NormalizedInstruction* pp = nullptr;
1638 if (prevOp == OpString &&
1639 (ni->m_txFlags & Supported)) {
1640 switch (op) {
1641 case OpReqDoc:
1642 /* Dont waste a register on the string */
1643 prev->outStack = nullptr;
1644 pp = prev->prev;
1648 if (op == OpRetC && (ni->m_txFlags & Supported) &&
1649 (prevOp == OpString ||
1650 prevOp == OpInt ||
1651 prevOp == OpNull ||
1652 prevOp == OpTrue ||
1653 prevOp == OpFalse ||
1654 prevOp == OpDouble ||
1655 prevOp == OpArray ||
1656 prevOp == OpThis ||
1657 prevOp == OpBareThis)) {
1658 assert(!ni->outStack);
1659 ni->grouped = true;
1660 prev->outStack = nullptr;
1661 pp = prev->prev;
1664 if (pp && pp->op() == OpPopC &&
1665 pp->m_txFlags == Native) {
1666 NormalizedInstruction* ppp = prev->prev->prev;
1667 if (ppp && (ppp->m_txFlags & Supported)) {
1668 switch (ppp->op()) {
1669 case OpReqDoc:
1671 We have a require+pop followed by a require or a scalar ret,
1672 where the pop doesnt have to do any work (the pop is Native).
1673 There is no need to inc/dec rbx between the two (since
1674 there will be no code between them)
1676 ppp->outStack = nullptr;
1677 ni->skipSync = true;
1678 break;
1680 default:
1681 // do nothing
1682 break;
1689 static NormalizedInstruction* findInputSrc(NormalizedInstruction* ni,
1690 DynLocation* dl) {
1691 while (ni != nullptr) {
1692 if (ni->outStack == dl ||
1693 ni->outLocal == dl ||
1694 ni->outLocal2 == dl ||
1695 ni->outStack2 == dl ||
1696 ni->outStack3 == dl) {
1697 break;
1699 ni = ni->prev;
1701 return ni;
1705 * For MetaData information that affects whether we want to even put a
1706 * value in the ni->inputs, we need to look at it before we call
1707 * getInputs(), so this is separate from applyInputMetaData.
1709 * We also check GuardedThis here, since RetC is short-circuited in
1710 * applyInputMetaData.
1712 void Translator::preInputApplyMetaData(Unit::MetaHandle metaHand,
1713 NormalizedInstruction* ni) {
1714 if (!metaHand.findMeta(ni->unit(), ni->offset())) return;
1716 Unit::MetaInfo info;
1717 while (metaHand.nextArg(info)) {
1718 switch (info.m_kind) {
1719 case Unit::MetaInfo::NonRefCounted:
1720 ni->nonRefCountedLocals.resize(curFunc()->numLocals());
1721 ni->nonRefCountedLocals[info.m_data] = 1;
1722 break;
1723 case Unit::MetaInfo::GuardedThis:
1724 ni->guardedThis = true;
1725 break;
1726 default:
1727 break;
1732 bool Translator::applyInputMetaData(Unit::MetaHandle& metaHand,
1733 NormalizedInstruction* ni,
1734 TraceletContext& tas,
1735 InputInfos &inputInfos) {
1736 if (!metaHand.findMeta(ni->unit(), ni->offset())) return false;
1738 Unit::MetaInfo info;
1739 if (!metaHand.nextArg(info)) return false;
1740 if (info.m_kind == Unit::MetaInfo::NopOut) {
1741 ni->noOp = true;
1742 return true;
1746 * We need to adjust the indexes in MetaInfo::m_arg if this
1747 * instruction takes other stack arguments than those related to the
1748 * MVector. (For example, the rhs of an assignment.)
1750 const InstrInfo& iInfo = instrInfo[ni->op()];
1751 if (iInfo.in & AllLocals) {
1753 * RetC/RetV dont care about their stack input, but it may have
1754 * been annotated. Skip it (because RetC/RetV pretend they dont
1755 * have a stack input).
1757 return false;
1759 if (iInfo.in == FuncdRef) {
1761 * FPassC* pretend to have no inputs
1763 return false;
1765 const int base = !(iInfo.in & MVector) ? 0 :
1766 !(iInfo.in & Stack1) ? 0 :
1767 !(iInfo.in & Stack2) ? 1 :
1768 !(iInfo.in & Stack3) ? 2 : 3;
1770 do {
1771 SKTRACE(3, ni->source, "considering MetaInfo of kind %d\n", info.m_kind);
1773 int arg = info.m_arg & Unit::MetaInfo::VectorArg ?
1774 base + (info.m_arg & ~Unit::MetaInfo::VectorArg) : info.m_arg;
1776 switch (info.m_kind) {
1777 case Unit::MetaInfo::NoSurprise:
1778 ni->noSurprise = true;
1779 break;
1780 case Unit::MetaInfo::GuardedCls:
1781 ni->guardedCls = true;
1782 break;
1783 case Unit::MetaInfo::ArrayCapacity:
1784 ni->imm[0].u_IVA = info.m_data;
1785 break;
1786 case Unit::MetaInfo::DataTypePredicted: {
1787 // If the original type was invalid or predicted, then use the
1788 // prediction in the meta-data.
1789 assert((unsigned) arg < inputInfos.size());
1791 SKTRACE(1, ni->source, "MetaInfo DataTypePredicted for input %d; "
1792 "newType = %d\n", arg, DataType(info.m_data));
1793 InputInfo& ii = inputInfos[arg];
1794 DynLocation* dl = tas.recordRead(ii, false, KindOfInvalid);
1795 NormalizedInstruction* src = findInputSrc(tas.m_t->m_instrStream.last,
1796 dl);
1797 if (src) {
1798 // Update the rtt and mark src's output as predicted if either:
1799 // a) we don't have type information yet (ie, it's KindOfInvalid), or
1800 // b) src's output was predicted. This is assuming that the
1801 // front-end's prediction is more accurate.
1802 if (dl->rtt.outerType() == KindOfInvalid || src->outputPredicted) {
1803 SKTRACE(1, ni->source, "MetaInfo DataTypePredicted for input %d; "
1804 "replacing oldType = %d with newType = %d\n", arg,
1805 dl->rtt.outerType(), DataType(info.m_data));
1806 dl->rtt = RuntimeType((DataType)info.m_data);
1807 src->outputPredicted = true;
1808 src->outputPredictionStatic = true;
1811 break;
1813 case Unit::MetaInfo::DataTypeInferred: {
1814 assert((unsigned)arg < inputInfos.size());
1815 SKTRACE(1, ni->source, "MetaInfo DataTypeInferred for input %d; "
1816 "newType = %d\n", arg, DataType(info.m_data));
1817 InputInfo& ii = inputInfos[arg];
1818 ii.dontGuard = true;
1819 DynLocation* dl = tas.recordRead(ii, true, (DataType)info.m_data);
1820 if (dl->rtt.outerType() != info.m_data &&
1821 (!dl->isString() || info.m_data != KindOfString)) {
1822 if (dl->rtt.outerType() != KindOfInvalid) {
1823 // Either static analysis is wrong, or
1824 // this was mis-predicted by the type
1825 // profiler, or this code is unreachable,
1826 // and there's an earlier bytecode in the tracelet
1827 // thats going to fatal
1828 NormalizedInstruction *src = nullptr;
1829 if (mapContains(tas.m_changeSet, dl->location)) {
1830 src = findInputSrc(tas.m_t->m_instrStream.last, dl);
1831 if (src && src->outputPredicted) {
1832 src->outputPredicted = false;
1833 } else {
1834 src = nullptr;
1837 if (!src) {
1838 // Not a type-profiler mis-predict
1839 if (tas.m_t->m_instrStream.first) {
1840 // We're not the first instruction, so punt
1841 // If this bytecode /is/ reachable, we'll
1842 // get here again, and that time, we will
1843 // be the first instruction
1844 punt();
1846 not_reached();
1849 dl->rtt = RuntimeType((DataType)info.m_data);
1850 ni->markInputInferred(arg);
1851 } else {
1853 * Static inference confirmed the expected type
1854 * but if the expected type was provided by the type
1855 * profiler we want to clear outputPredicted to
1856 * avoid unneeded guards
1858 if (mapContains(tas.m_changeSet, dl->location)) {
1859 NormalizedInstruction *src =
1860 findInputSrc(tas.m_t->m_instrStream.last, dl);
1861 if (src->outputPredicted) {
1862 src->outputPredicted = false;
1863 ni->markInputInferred(arg);
1867 break;
1870 case Unit::MetaInfo::String: {
1871 const StringData* sd = ni->unit()->lookupLitstrId(info.m_data);
1872 assert((unsigned)arg < inputInfos.size());
1873 InputInfo& ii = inputInfos[arg];
1874 ii.dontGuard = true;
1875 DynLocation* dl = tas.recordRead(ii, true, KindOfString);
1876 assert(!dl->rtt.isString() || !dl->rtt.valueString() ||
1877 dl->rtt.valueString() == sd);
1878 SKTRACE(1, ni->source, "MetaInfo on input %d; old type = %s\n",
1879 arg, dl->pretty().c_str());
1880 dl->rtt = RuntimeType(sd);
1881 break;
1884 case Unit::MetaInfo::Class: {
1885 assert((unsigned)arg < inputInfos.size());
1886 InputInfo& ii = inputInfos[arg];
1887 DynLocation* dl = tas.recordRead(ii, true);
1888 if (dl->rtt.valueType() != KindOfObject) {
1889 continue;
1892 const StringData* metaName = ni->unit()->lookupLitstrId(info.m_data);
1893 const StringData* rttName =
1894 dl->rtt.valueClass() ? dl->rtt.valueClass()->name() : nullptr;
1895 // The two classes might not be exactly the same, which is ok
1896 // as long as metaCls is more derived than rttCls.
1897 Class* metaCls = Unit::lookupUniqueClass(metaName);
1898 Class* rttCls = rttName ? Unit::lookupUniqueClass(rttName) : nullptr;
1899 if (metaCls && rttCls && metaCls != rttCls &&
1900 !metaCls->classof(rttCls)) {
1901 // Runtime type is more derived
1902 metaCls = 0;
1904 if (metaCls && metaCls != rttCls) {
1905 SKTRACE(1, ni->source, "replacing input %d with a MetaInfo-supplied "
1906 "class of %s; old type = %s\n",
1907 arg, metaName->data(), dl->pretty().c_str());
1908 if (dl->rtt.isRef()) {
1909 dl->rtt = RuntimeType(KindOfRef, KindOfObject, metaCls);
1910 } else {
1911 dl->rtt = RuntimeType(KindOfObject, KindOfInvalid, metaCls);
1914 break;
1917 case Unit::MetaInfo::MVecPropClass: {
1918 const StringData* metaName = ni->unit()->lookupLitstrId(info.m_data);
1919 Class* metaCls = Unit::lookupUniqueClass(metaName);
1920 if (metaCls) {
1921 ni->immVecClasses[arg] = metaCls;
1923 break;
1926 case Unit::MetaInfo::NopOut:
1927 // NopOut should always be the first and only annotation
1928 // and was handled above.
1929 not_reached();
1931 case Unit::MetaInfo::GuardedThis:
1932 case Unit::MetaInfo::NonRefCounted:
1933 // fallthrough; these are handled in preInputApplyMetaData.
1934 case Unit::MetaInfo::None:
1935 break;
1937 } while (metaHand.nextArg(info));
1939 return false;
1942 static void addMVectorInputs(NormalizedInstruction& ni,
1943 int& currentStackOffset,
1944 std::vector<InputInfo>& inputs) {
1945 assert(ni.immVec.isValid());
1946 ni.immVecM.reserve(ni.immVec.size());
1948 int UNUSED stackCount = 0;
1949 int UNUSED localCount = 0;
1951 currentStackOffset -= ni.immVec.numStackValues();
1952 int localStackOffset = currentStackOffset;
1954 auto push_stack = [&] {
1955 ++stackCount;
1956 inputs.emplace_back(Location(Location::Stack, localStackOffset++));
1958 auto push_local = [&] (int imm) {
1959 ++localCount;
1960 inputs.emplace_back(Location(Location::Local, imm));
1964 * Note that we have to push as we go so that the arguments come in
1965 * the order expected for the M-vector.
1967 * Indexes into these argument lists must also be in the same order
1968 * as the information in Unit::MetaInfo, because the analysis phase
1969 * may replace some of them with literals.
1973 * Also note: if we eventually have immediates that are not local
1974 * ids (i.e. string ids), this analysis step is going to have to be
1975 * a bit wiser.
1977 const uint8_t* vec = ni.immVec.vec();
1978 const LocationCode lcode = LocationCode(*vec++);
1980 const bool trailingClassRef = lcode == LSL || lcode == LSC;
1982 switch (numLocationCodeStackVals(lcode)) {
1983 case 0: {
1984 if (lcode == LH) {
1985 inputs.emplace_back(Location(Location::This));
1986 } else {
1987 assert(lcode == LL || lcode == LGL || lcode == LNL);
1988 int numImms = numLocationCodeImms(lcode);
1989 for (int i = 0; i < numImms; ++i) {
1990 push_local(decodeVariableSizeImm(&vec));
1993 } break;
1994 case 1:
1995 if (lcode == LSL) {
1996 // We'll get the trailing stack value after pushing all the
1997 // member vector elements.
1998 push_local(decodeVariableSizeImm(&vec));
1999 } else {
2000 push_stack();
2002 break;
2003 case 2:
2004 push_stack();
2005 if (!trailingClassRef) {
2006 // This one is actually at the back.
2007 push_stack();
2009 break;
2010 default: not_reached();
2013 // Now push all the members in the correct order.
2014 while (vec - ni.immVec.vec() < ni.immVec.size()) {
2015 const MemberCode mcode = MemberCode(*vec++);
2016 ni.immVecM.push_back(mcode);
2018 if (mcode == MW) {
2019 // No stack and no locals.
2020 } else if (memberCodeHasImm(mcode)) {
2021 int64_t imm = decodeMemberCodeImm(&vec, mcode);
2022 if (memberCodeImmIsLoc(mcode)) {
2023 push_local(imm);
2024 } else if (memberCodeImmIsString(mcode)) {
2025 inputs.emplace_back(Location(Location::Litstr, imm));
2026 } else {
2027 assert(memberCodeImmIsInt(mcode));
2028 inputs.emplace_back(Location(Location::Litint, imm));
2030 } else {
2031 push_stack();
2033 inputs.back().dontGuardInner = true;
2036 if (trailingClassRef) {
2037 push_stack();
2040 ni.immVecClasses.resize(ni.immVecM.size());
2042 assert(vec - ni.immVec.vec() == ni.immVec.size());
2043 assert(stackCount == ni.immVec.numStackValues());
2045 SKTRACE(2, ni.source, "M-vector using %d hidden stack "
2046 "inputs, %d locals\n", stackCount, localCount);
2050 * getInputs --
2051 * Returns locations for this instruction's inputs.
2053 * Throws:
2054 * TranslationFailedExc:
2055 * Unimplemented functionality, probably an opcode.
2057 * UnknownInputExc:
2058 * Consumed a datum whose type or value could not be constrained at
2059 * translation time, because the tracelet has already modified it.
2060 * Truncate the tracelet at the preceding instruction, which must
2061 * exists because *something* modified something in it.
2063 void Translator::getInputs(Tracelet& t,
2064 NormalizedInstruction* ni,
2065 int& currentStackOffset,
2066 InputInfos& inputs,
2067 const TraceletContext& tas) {
2068 #ifdef USE_TRACE
2069 const SrcKey& sk = ni->source;
2070 #endif
2071 assert(inputs.empty());
2072 if (debug && !mapContains(instrInfo, ni->op())) {
2073 fprintf(stderr, "Translator does not understand "
2074 "instruction %s\n", opcodeToName(ni->op()));
2075 assert(false);
2077 const InstrInfo& info = instrInfo[ni->op()];
2078 Operands input = info.in;
2079 if (input & FuncdRef) {
2080 inputs.needsRefCheck = true;
2082 if (input & Iter) {
2083 inputs.emplace_back(Location(Location::Iter, ni->imm[0].u_IVA));
2085 if (input & FStack) {
2086 currentStackOffset -= ni->imm[0].u_IVA; // arguments consumed
2087 currentStackOffset -= kNumActRecCells; // ActRec is torn down as well
2089 if (input & IgnoreInnerType) ni->ignoreInnerType = true;
2090 if (input & Stack1) {
2091 SKTRACE(1, sk, "getInputs: stack1 %d\n", currentStackOffset - 1);
2092 inputs.emplace_back(Location(Location::Stack, --currentStackOffset));
2093 if (input & DontGuardStack1) inputs.back().dontGuard = true;
2094 if (input & DontBreakStack1) inputs.back().dontBreak = true;
2095 if (input & Stack2) {
2096 SKTRACE(1, sk, "getInputs: stack2 %d\n", currentStackOffset - 1);
2097 inputs.emplace_back(Location(Location::Stack, --currentStackOffset));
2098 if (input & Stack3) {
2099 SKTRACE(1, sk, "getInputs: stack3 %d\n", currentStackOffset - 1);
2100 inputs.emplace_back(Location(Location::Stack, --currentStackOffset));
2104 if (input & StackN) {
2105 int numArgs = ni->imm[0].u_IVA;
2106 SKTRACE(1, sk, "getInputs: stackN %d %d\n", currentStackOffset - 1,
2107 numArgs);
2108 for (int i = 0; i < numArgs; i++) {
2109 inputs.emplace_back(Location(Location::Stack, --currentStackOffset));
2110 inputs.back().dontGuard = true;
2111 inputs.back().dontBreak = true;
2114 if (input & BStackN) {
2115 int numArgs = ni->imm[0].u_IVA;
2116 SKTRACE(1, sk, "getInputs: BStackN %d %d\n", currentStackOffset - 1,
2117 numArgs);
2118 for (int i = 0; i < numArgs; i++) {
2119 inputs.emplace_back(Location(Location::Stack, --currentStackOffset));
2122 if (input & MVector) {
2123 addMVectorInputs(*ni, currentStackOffset, inputs);
2125 if (input & Local) {
2126 // Many of the continuation instructions read local 0. All other
2127 // instructions that take a Local have its index at their first
2128 // immediate.
2129 int loc;
2130 switch (ni->op()) {
2131 case OpUnpackCont:
2132 case OpPackCont:
2133 case OpContReceive:
2134 case OpContRetC:
2135 case OpContSend:
2136 case OpContRaise:
2137 loc = 0;
2138 break;
2140 case OpFPassL:
2141 loc = ni->imm[1].u_IVA;
2142 break;
2144 default:
2145 loc = ni->imm[0].u_IVA;
2146 break;
2148 SKTRACE(1, sk, "getInputs: local %d\n", loc);
2149 inputs.emplace_back(Location(Location::Local, loc));
2150 if (input & DontGuardLocal) inputs.back().dontGuard = true;
2151 if (input & DontBreakLocal) inputs.back().dontBreak = true;
2154 const bool wantInlineReturn = [&] {
2155 const int localCount = curFunc()->numLocals();
2156 // Inline return causes us to guard this tracelet more precisely. If
2157 // we're already chaining to get here, just do a generic return in the
2158 // hopes of avoiding further specialization. The localCount constraint
2159 // is an unfortunate consequence of the current generic machinery not
2160 // working for 0 locals.
2161 if (tx64->numTranslations(t.m_sk) >= kTooPolyRet && localCount > 0) {
2162 return false;
2164 ni->nonRefCountedLocals.resize(localCount);
2165 int numRefCounted = 0;
2166 for (int i = 0; i < localCount; ++i) {
2167 auto curType = tas.currentType(Location(Location::Local, i));
2168 if (ni->nonRefCountedLocals[i]) {
2169 assert(!curType.isRefCounted() && "Static analysis was wrong");
2171 numRefCounted += curType.isRefCounted();
2173 return numRefCounted <= kMaxInlineReturnDecRefs;
2174 }();
2176 if ((input & AllLocals) && wantInlineReturn) {
2177 ni->inlineReturn = true;
2178 ni->ignoreInnerType = true;
2179 int n = curFunc()->numLocals();
2180 for (int i = 0; i < n; ++i) {
2181 if (!ni->nonRefCountedLocals[i]) {
2182 inputs.emplace_back(Location(Location::Local, i));
2187 SKTRACE(1, sk, "stack args: virtual sfo now %d\n", currentStackOffset);
2188 TRACE(1, "%s\n", Trace::prettyNode("Inputs", inputs).c_str());
2190 if (inputs.size() &&
2191 ((input & DontGuardAny) || dontGuardAnyInputs(ni->op()))) {
2192 for (int i = inputs.size(); i--; ) {
2193 inputs[i].dontGuard = true;
2196 if (input & This) {
2197 inputs.emplace_back(Location(Location::This));
2201 bool outputDependsOnInput(const Opcode instr) {
2202 switch (instrInfo[instr].type) {
2203 case OutNull:
2204 case OutNullUninit:
2205 case OutString:
2206 case OutStringImm:
2207 case OutDouble:
2208 case OutBoolean:
2209 case OutBooleanImm:
2210 case OutInt64:
2211 case OutArray:
2212 case OutArrayImm:
2213 case OutObject:
2214 case OutThisObject:
2215 case OutUnknown:
2216 case OutVUnknown:
2217 case OutClassRef:
2218 case OutPred:
2219 case OutCns:
2220 case OutStrlen:
2221 case OutNone:
2222 return false;
2223 case OutFDesc:
2224 case OutSameAsInput:
2225 case OutCInput:
2226 case OutVInput:
2227 case OutCInputL:
2228 case OutVInputL:
2229 case OutFInputL:
2230 case OutFInputR:
2231 case OutArith:
2232 case OutBitOp:
2233 case OutSetOp:
2234 case OutIncDec:
2235 return true;
2237 not_reached();
2241 * getOutputs --
2242 * Builds a vector describing this instruction's outputs. Also
2243 * records any write to a value that *might* alias a local.
2245 * Throws:
2246 * TranslationFailedExc:
2247 * Unimplemented functionality, probably an opcode.
2249 void Translator::getOutputs(/*inout*/ Tracelet& t,
2250 /*inout*/ NormalizedInstruction* ni,
2251 /*inout*/ int& currentStackOffset,
2252 /*out*/ bool& varEnvTaint) {
2253 varEnvTaint = false;
2255 const vector<DynLocation*>& inputs = ni->inputs;
2256 Op op = ni->op();
2258 initInstrInfo();
2259 assert_not_implemented(instrInfo.find(op) != instrInfo.end());
2260 const Operands outLocs = instrInfo[op].out;
2261 const OutTypeConstraints typeInfo = instrInfo[op].type;
2263 SKTRACE(1, ni->source, "output flavor %d\n", typeInfo);
2264 if (typeInfo == OutFInputL || typeInfo == OutFInputR ||
2265 typeInfo == OutVInputL) {
2266 // Variable number of outputs. If we box the loc we're reading,
2267 // we need to write out its boxed-ness.
2268 assert(inputs.size() >= 1);
2269 const DynLocation* in = inputs[inputs.size() - 1];
2270 DynLocation* outDynLoc = t.newDynLocation(in->location, in->rtt);
2271 outDynLoc->location = Location(Location::Stack, currentStackOffset++);
2272 bool isRef;
2273 if (typeInfo == OutVInputL) {
2274 isRef = true;
2275 } else {
2276 assert(typeInfo == OutFInputL || typeInfo == OutFInputR);
2277 isRef = ni->preppedByRef;
2279 if (isRef) {
2280 // Locals can be KindOfUninit, so we need to convert
2281 // this to KindOfNull
2282 if (in->rtt.outerType() == KindOfUninit) {
2283 outDynLoc->rtt = RuntimeType(KindOfRef, KindOfNull);
2284 } else {
2285 outDynLoc->rtt = in->rtt.box();
2287 SKTRACE(1, ni->source, "boxed type: %d -> %d\n",
2288 outDynLoc->rtt.outerType(), outDynLoc->rtt.innerType());
2289 } else {
2290 if (outDynLoc->rtt.outerType() == KindOfUninit) {
2291 outDynLoc->rtt = RuntimeType(KindOfNull);
2292 } else {
2293 outDynLoc->rtt = outDynLoc->rtt.unbox();
2295 SKTRACE(1, ni->source, "unboxed type: %d\n",
2296 outDynLoc->rtt.outerType());
2298 assert(outDynLoc->location.isStack());
2299 ni->outStack = outDynLoc;
2301 if (isRef && in->rtt.outerType() != KindOfRef &&
2302 typeInfo != OutFInputR &&
2303 in->location.isLocal()) {
2304 // VGetH or FPassH boxing a local
2305 DynLocation* smashedLocal =
2306 t.newDynLocation(in->location, outDynLoc->rtt);
2307 assert(smashedLocal->location.isLocal());
2308 ni->outLocal = smashedLocal;
2310 // Other things that might be getting boxed here include globals
2311 // and array values; since we don't attempt to track these things'
2312 // types in symbolic execution anyway, we can ignore them.
2313 return;
2316 int opnd = None;
2317 for (int outLocsCopy = (int)outLocs;
2318 outLocsCopy != (int)None;
2319 outLocsCopy &= ~opnd) {
2320 opnd = 1 << (ffs(outLocsCopy) - 1);
2321 assert(opnd != None && opnd != Stack3); // no instr produces 3 values
2322 assert(opnd != FuncdRef); // reffiness is immutable
2323 Location loc;
2324 switch (opnd) {
2325 // Pseudo-outputs that affect translator state
2326 case FStack: {
2327 currentStackOffset += kNumActRecCells;
2328 if (op == OpFPushFuncD) {
2329 const Unit& cu = *ni->unit();
2330 Id funcId = ni->imm[1].u_SA;
2331 const NamedEntityPair &nep = cu.lookupNamedEntityPairId(funcId);
2332 const Func* f = Unit::lookupFunc(nep.second);
2333 if (f && f->isNameBindingImmutable(&cu)) {
2334 t.m_arState.pushFuncD(f);
2335 } else {
2336 t.m_arState.pushDynFunc();
2338 } else {
2339 // Non-deterministic in some way
2340 t.m_arState.pushDynFunc();
2342 } continue; // no instr-associated output
2344 case Local: {
2345 if (op == OpSetN || op == OpSetOpN || op == OpIncDecN ||
2346 op == OpBindN || op == OpUnsetN) {
2347 varEnvTaint = true;
2348 continue;
2350 ASSERT_NOT_IMPLEMENTED(op == OpSetOpL ||
2351 op == OpSetM || op == OpSetOpM ||
2352 op == OpBindM ||
2353 op == OpIncDecL || op == OpIncDecG ||
2354 op == OpUnsetG || op == OpBindG ||
2355 op == OpSetG || op == OpSetOpG ||
2356 op == OpVGetM ||
2357 op == OpStaticLocInit || op == OpInitThisLoc ||
2358 op == OpSetL || op == OpBindL ||
2359 op == OpUnsetL ||
2360 op == OpIterInit || op == OpIterInitK ||
2361 op == OpMIterInit || op == OpMIterInitK ||
2362 op == OpIterNext || op == OpIterNextK ||
2363 op == OpMIterNext || op == OpMIterNextK);
2364 if (op == OpIncDecL) {
2365 assert(ni->inputs.size() == 1);
2366 const RuntimeType &inRtt = ni->inputs[0]->rtt;
2367 RuntimeType rtt = IS_INT_TYPE(inRtt.valueType()) ? inRtt :
2368 RuntimeType(KindOfInvalid);
2369 DynLocation* incDecLoc =
2370 t.newDynLocation(ni->inputs[0]->location, rtt);
2371 assert(incDecLoc->location.isLocal());
2372 ni->outLocal = incDecLoc;
2373 continue; // Doesn't mutate a loc's types for int. Carry on.
2375 if (op == OpSetG || op == OpSetOpG ||
2376 op == OpUnsetG || op == OpBindG ||
2377 op == OpIncDecG) {
2378 continue;
2380 if (op == OpUnsetL) {
2381 assert(ni->inputs.size() == 1);
2382 DynLocation* inLoc = ni->inputs[0];
2383 assert(inLoc->location.isLocal());
2384 RuntimeType newLhsRtt = RuntimeType(KindOfUninit);
2385 Location locLocation = inLoc->location;
2386 SKTRACE(2, ni->source, "(%s, %d) <- type %d\n",
2387 locLocation.spaceName(), locLocation.offset,
2388 newLhsRtt.valueType());
2389 DynLocation* unsetLoc = t.newDynLocation(locLocation, newLhsRtt);
2390 assert(unsetLoc->location.isLocal());
2391 ni->outLocal = unsetLoc;
2392 continue;
2394 if (op == OpStaticLocInit || op == OpInitThisLoc) {
2395 ni->outLocal = t.newDynLocation(Location(Location::Local,
2396 ni->imm[0].u_OA),
2397 KindOfInvalid);
2398 continue;
2400 if (op == OpSetM || op == OpSetOpM || op == OpVGetM || op == OpBindM) {
2401 // TODO(#1069330): This code assumes that the location is
2402 // LH. We need to figure out how to handle cases where the
2403 // location is LN or LG or LR.
2404 // XXX: analogous garbage needed for OpSetOpM.
2405 if (ni->immVec.locationCode() == LL) {
2406 const int kVecStart = (op == OpSetM ||
2407 op == OpSetOpM ||
2408 op == OpBindM) ?
2409 1 : 0; // 0 is rhs for SetM/SetOpM
2410 DynLocation* inLoc = ni->inputs[kVecStart];
2411 assert(inLoc->location.isLocal());
2412 Location locLoc = inLoc->location;
2413 if (inLoc->rtt.isString() ||
2414 inLoc->rtt.valueType() == KindOfBoolean) {
2415 // Strings and bools produce value-dependent results; "" and
2416 // false upgrade to an array successfully, while other values
2417 // fail and leave the lhs unmodified.
2418 DynLocation* baseLoc = t.newDynLocation(locLoc, KindOfInvalid);
2419 assert(baseLoc->isLocal());
2420 ni->outLocal = baseLoc;
2421 } else if (inLoc->rtt.valueType() == KindOfUninit ||
2422 inLoc->rtt.valueType() == KindOfNull) {
2423 RuntimeType newLhsRtt = inLoc->rtt.setValueType(
2424 mcodeMaybePropName(ni->immVecM[0]) ?
2425 KindOfObject : KindOfArray);
2426 SKTRACE(2, ni->source, "(%s, %d) <- type %d\n",
2427 locLoc.spaceName(), locLoc.offset, newLhsRtt.valueType());
2428 DynLocation* baseLoc = t.newDynLocation(locLoc, newLhsRtt);
2429 assert(baseLoc->location.isLocal());
2430 ni->outLocal = baseLoc;
2432 // Note (if we start translating pseudo-mains):
2434 // A SetM in pseudo-main might alias a local whose type we're
2435 // remembering:
2437 // $GLOBALS['a'] = 123; // $a :: Int
2439 // and more deviously:
2441 // $loc['b'][17] = $GLOBALS; $x = 'b'; $y = 17;
2442 // $loc[$x][$y]['a'] = 123; // $a :: Int
2444 continue;
2446 if (op == OpSetOpL) {
2447 const int kLocIdx = 1;
2448 DynLocation* inLoc = ni->inputs[kLocIdx];
2449 assert(inLoc->location.isLocal());
2450 DynLocation* dl = t.newDynLocation();
2451 dl->location = inLoc->location;
2452 dl->rtt = setOpOutputType(ni, ni->inputs);
2453 if (inLoc->isRef()) {
2454 dl->rtt = dl->rtt.box();
2456 SKTRACE(2, ni->source, "(%s, %d) <- type %d\n",
2457 inLoc->location.spaceName(), inLoc->location.offset,
2458 dl->rtt.valueType());
2459 assert(dl->location.isLocal());
2460 ni->outLocal = dl;
2461 continue;
2463 if (op >= OpIterInit && op <= OpMIterNextK) {
2464 assert(op == OpIterInit || op == OpIterInitK ||
2465 op == OpMIterInit || op == OpMIterInitK ||
2466 op == OpIterNext || op == OpIterNextK ||
2467 op == OpMIterNext || op == OpMIterNextK);
2468 const int kValImmIdx = 2;
2469 const int kKeyImmIdx = 3;
2470 DynLocation* outVal = t.newDynLocation();
2471 int off = ni->imm[kValImmIdx].u_IVA;
2472 outVal->location = Location(Location::Local, off);
2473 if (op == OpMIterInit || op == OpMIterInitK ||
2474 op == OpMIterNext || op == OpMIterNextK) {
2475 outVal->rtt = RuntimeType(KindOfRef, KindOfInvalid);
2476 } else {
2477 outVal->rtt = RuntimeType(KindOfInvalid);
2479 ni->outLocal = outVal;
2480 if (op == OpIterInitK || op == OpIterNextK) {
2481 DynLocation* outKey = t.newDynLocation();
2482 int keyOff = getImm(ni->pc(), kKeyImmIdx).u_IVA;
2483 outKey->location = Location(Location::Local, keyOff);
2484 outKey->rtt = RuntimeType(KindOfInvalid);
2485 ni->outLocal2 = outKey;
2486 } else if (op == OpMIterInitK || op == OpMIterNextK) {
2487 DynLocation* outKey = t.newDynLocation();
2488 int keyOff = getImm(ni->pc(), kKeyImmIdx).u_IVA;
2489 outKey->location = Location(Location::Local, keyOff);
2490 outKey->rtt = RuntimeType(KindOfRef, KindOfInvalid);
2491 ni->outLocal2 = outKey;
2493 continue;
2495 assert(ni->inputs.size() == 2);
2496 const int kValIdx = 0;
2497 const int kLocIdx = 1;
2498 DynLocation* inLoc = ni->inputs[kLocIdx];
2499 DynLocation* inVal = ni->inputs[kValIdx];
2500 Location locLocation = inLoc->location;
2501 // Variant RHS possible only when binding.
2502 assert(inVal->rtt.isVagueValue() ||
2503 (op == OpBindL) ==
2504 (inVal->rtt.outerType() == KindOfRef));
2505 assert(!inVal->location.isLocal());
2506 assert(inLoc->location.isLocal());
2507 RuntimeType newLhsRtt = inVal->rtt.isVagueValue() || op == OpBindL ?
2508 inVal->rtt :
2509 inLoc->rtt.setValueType(inVal->rtt.outerType());
2510 if (inLoc->rtt.outerType() == KindOfRef) {
2511 assert(newLhsRtt.outerType() == KindOfRef);
2512 } else {
2513 assert(op == OpBindL ||
2514 newLhsRtt.outerType() != KindOfRef);
2516 SKTRACE(2, ni->source, "(%s, %d) <- type %d\n",
2517 locLocation.spaceName(), locLocation.offset,
2518 inVal->rtt.valueType());
2519 DynLocation* outLhsLoc = t.newDynLocation(locLocation, newLhsRtt);
2520 assert(outLhsLoc->location.isLocal());
2521 ni->outLocal = outLhsLoc;
2522 } continue; // already pushed an output for the local
2524 case Stack1:
2525 case Stack2:
2526 loc = Location(Location::Stack, currentStackOffset++);
2527 break;
2528 case StackIns1: {
2529 // First stack output is where the inserted element will go.
2530 // The output code for the instruction will affect what we
2531 // think about this location.
2532 loc = Location(Location::Stack, currentStackOffset++);
2534 // The existing top is just being moved up a notch. This one
2535 // always functions as if it were OutSameAsInput.
2536 assert(ni->inputs.size() >= 1);
2537 ni->outStack2 = t.newDynLocation(
2538 Location(Location::Stack, currentStackOffset++),
2539 ni->inputs[0]->rtt
2541 } break;
2542 case StackIns2: {
2543 // Similar to StackIns1.
2544 loc = Location(Location::Stack, currentStackOffset++);
2546 // Move the top two locations up a slot.
2547 assert(ni->inputs.size() >= 2);
2548 ni->outStack2 = t.newDynLocation(
2549 Location(Location::Stack, currentStackOffset++),
2550 ni->inputs[1]->rtt
2552 ni->outStack3 = t.newDynLocation(
2553 Location(Location::Stack, currentStackOffset++),
2554 ni->inputs[0]->rtt
2556 } break;
2557 default:
2558 not_reached();
2560 DynLocation* dl = t.newDynLocation();
2561 dl->location = loc;
2562 dl->rtt = getDynLocType(inputs, t, op, ni, (Operands)opnd,
2563 typeInfo, dl);
2564 SKTRACE(2, ni->source, "recording output t(%d->%d) #(%s, %d)\n",
2565 dl->rtt.outerType(), dl->rtt.innerType(),
2566 dl->location.spaceName(), dl->location.offset);
2567 assert(dl->location.isStack());
2568 ni->outStack = dl;
2572 void
2573 Translator::findImmable(ImmStack &stack,
2574 NormalizedInstruction* ni) {
2575 switch (ni->op()) {
2576 case OpInt:
2577 stack.pushInt(getImm(ni->pc(), 0).u_I64A);
2578 return;
2580 case OpString:
2581 stack.pushLitstr(getImm(ni->pc(), 0).u_SA);
2582 return;
2584 // For binary ops we assume that only one of the two is an immediate
2585 // because if both were immediates then hopefully the second pass
2586 // optimized away this instruction. However, if a binary op has two
2587 // immediates, we won't generate incorrect code: instead it will
2588 // merely be suboptimal.
2590 // we can only handle an immediate if it's the second immediate
2591 case OpAdd:
2592 case OpSub:
2593 if (stack.isInt(0)) {
2594 SKTRACE(1, ni->source, "marking for immediate elision\n");
2595 ni->hasConstImm = true;
2596 ni->constImm.u_I64A = stack.get(0).i64a;
2597 // We don't currently remove the OpInt instruction that produced
2598 // this integer. We should update the translator to correctly support
2599 // removing instructions from the tracelet.
2601 break;
2603 case OpFPassM:
2604 case OpCGetM:
2605 case OpSetM:
2606 case OpIssetM: {
2607 // If this is "<VecInstr>M <... EC>"
2608 const ImmVector& iv = ni->immVec;
2609 assert(iv.isValid());
2610 MemberCode mc;
2611 StringData* str;
2612 int64_t strId;
2613 if (iv.size() > 1 &&
2614 iv.decodeLastMember(curUnit(), str, mc, &strId) &&
2615 mc == MET) {
2617 * If the operand takes a literal string that's not strictly an
2618 * integer, we can call into array-access helper functions that
2619 * don't bother with the integer check.
2621 int64_t lval;
2622 if (LIKELY(!str->isStrictlyInteger(lval))) {
2623 ni->hasConstImm = true;
2624 ni->constImm.u_SA = strId;
2627 } break;
2629 default: ;
2632 stack.processOpcode(ni->pc());
2635 void
2636 Translator::requestResetHighLevelTranslator() {
2637 if (dbgTranslateCoin) {
2638 dbgTranslateCoin->reset();
2642 bool DynLocation::canBeAliased() const {
2643 return isValue() &&
2644 ((Translator::liveFrameIsPseudoMain() && isLocal()) || isRef());
2647 // Test the type of a location without recording it as a read yet.
2648 RuntimeType TraceletContext::currentType(const Location& l) const {
2649 DynLocation* dl;
2650 if (!mapGet(m_currentMap, l, &dl)) {
2651 assert(!mapContains(m_deletedSet, l));
2652 assert(!mapContains(m_changeSet, l));
2653 return tx64->liveType(l, *curUnit());
2655 return dl->rtt;
2658 DynLocation* TraceletContext::recordRead(const InputInfo& ii,
2659 bool useHHIR,
2660 DataType staticType) {
2661 DynLocation* dl;
2662 const Location& l = ii.loc;
2663 if (!mapGet(m_currentMap, l, &dl)) {
2664 // We should never try to read a location that has been deleted
2665 assert(!mapContains(m_deletedSet, l));
2666 // If the given location was not in m_currentMap, then it shouldn't
2667 // be in m_changeSet either
2668 assert(!mapContains(m_changeSet, l));
2669 if (ii.dontGuard && !l.isLiteral()) {
2670 assert(!useHHIR || staticType != KindOfRef);
2671 dl = m_t->newDynLocation(l, RuntimeType(staticType));
2672 if (useHHIR && staticType != KindOfInvalid) {
2673 m_resolvedDeps[l] = dl;
2675 } else {
2676 RuntimeType rtt = tx64->liveType(l, *curUnit());
2677 assert(rtt.isIter() || !rtt.isVagueValue());
2678 // Allocate a new DynLocation to represent this and store it in the
2679 // current map.
2680 dl = m_t->newDynLocation(l, rtt);
2682 if (!l.isLiteral()) {
2683 if (m_varEnvTaint && dl->isValue() && dl->isLocal()) {
2684 dl->rtt = RuntimeType(KindOfInvalid);
2685 } else if ((m_aliasTaint && dl->canBeAliased()) ||
2686 (rtt.isValue() && rtt.isRef() && ii.dontGuardInner)) {
2687 dl->rtt = rtt.setValueType(KindOfInvalid);
2689 // Record that we depend on the live type of the specified location
2690 // as well (and remember what the live type was)
2691 m_dependencies[l] = dl;
2694 m_currentMap[l] = dl;
2696 TRACE(2, "recordRead: %s : %s\n", l.pretty().c_str(),
2697 dl->rtt.pretty().c_str());
2698 return dl;
2701 void TraceletContext::recordWrite(DynLocation* dl,
2702 NormalizedInstruction* source) {
2703 TRACE(2, "recordWrite: %s : %s\n", dl->location.pretty().c_str(),
2704 dl->rtt.pretty().c_str());
2705 dl->source = source;
2706 m_currentMap[dl->location] = dl;
2707 m_changeSet.insert(dl->location);
2708 m_deletedSet.erase(dl->location);
2711 void TraceletContext::recordDelete(const Location& l) {
2712 // We should not be trying to delete the rtt of location that is
2713 // not in m_currentMap
2714 TRACE(2, "recordDelete: %s\n", l.pretty().c_str());
2715 m_currentMap.erase(l);
2716 m_changeSet.erase(l);
2717 m_deletedSet.insert(l);
2720 void TraceletContext::aliasTaint() {
2721 m_aliasTaint = true;
2722 for (ChangeMap::iterator it = m_currentMap.begin();
2723 it != m_currentMap.end(); ++it) {
2724 DynLocation* dl = it->second;
2725 if (dl->canBeAliased()) {
2726 TRACE(1, "(%s, %" PRId64 ") <- inner type invalidated\n",
2727 it->first.spaceName(), it->first.offset);
2728 RuntimeType newRtt = dl->rtt.setValueType(KindOfInvalid);
2729 it->second = m_t->newDynLocation(dl->location, newRtt);
2734 void TraceletContext::varEnvTaint() {
2735 m_varEnvTaint = true;
2736 for (ChangeMap::iterator it = m_currentMap.begin();
2737 it != m_currentMap.end(); ++it) {
2738 DynLocation* dl = it->second;
2739 if (dl->isValue() && dl->isLocal()) {
2740 TRACE(1, "(%s, %" PRId64 ") <- type invalidated\n",
2741 it->first.spaceName(), it->first.offset);
2742 it->second = m_t->newDynLocation(dl->location,
2743 RuntimeType(KindOfInvalid));
2748 void TraceletContext::recordJmp() {
2749 m_numJmps++;
2753 * Helpers for recovering context of this instruction.
2755 Op NormalizedInstruction::op() const {
2756 uchar op = *pc();
2757 assert(isValidOpcode(op));
2758 return (Op)op;
2761 Op NormalizedInstruction::mInstrOp() const {
2762 Op opcode = op();
2763 #define MII(instr, a, b, i, v, d) case Op##instr##M: return opcode;
2764 switch (opcode) {
2765 MINSTRS
2766 case OpFPassM:
2767 return preppedByRef ? OpVGetM : OpCGetM;
2768 default:
2769 not_reached();
2771 #undef MII
2774 PC NormalizedInstruction::pc() const {
2775 return unit()->at(source.offset());
2778 const Unit* NormalizedInstruction::unit() const {
2779 return m_unit;
2782 Offset NormalizedInstruction::offset() const {
2783 return source.offset();
2786 std::string NormalizedInstruction::toString() const {
2787 return instrToString(pc(), unit());
2790 void Translator::postAnalyze(NormalizedInstruction* ni, SrcKey& sk,
2791 Tracelet& t, TraceletContext& tas) {
2792 if (ni->op() == OpBareThis &&
2793 ni->outStack->rtt.isVagueValue()) {
2794 SrcKey src = sk;
2795 const Unit* unit = ni->m_unit;
2796 src.advance(unit);
2797 Opcode next = *unit->at(src.offset());
2798 if (next == OpInstanceOfD || next == OpIsNullC) {
2799 ni->outStack->rtt = RuntimeType(KindOfObject);
2801 return;
2805 NormalizedInstruction::OutputUse
2806 NormalizedInstruction::getOutputUsage(DynLocation* output,
2807 bool ignorePops /* =false */) const {
2808 for (NormalizedInstruction* succ = next;
2809 succ; succ = succ->next) {
2810 if (ignorePops &&
2811 (succ->op() == OpPopC ||
2812 succ->op() == OpPopV ||
2813 succ->op() == OpPopR)) {
2814 continue;
2816 for (size_t i = 0; i < succ->inputs.size(); ++i) {
2817 if (succ->inputs[i] == output) {
2818 if (succ->inputWasInferred(i)) {
2819 return OutputInferred;
2821 if (Translator::Get()->dontGuardAnyInputs(succ->op())) {
2822 /* the consumer doesnt care about its inputs
2823 but we may still have inferred something about
2824 its outputs that a later instruction may depend on
2826 if (!outputDependsOnInput(succ->op()) ||
2827 !(succ->outStack && !succ->outStack->rtt.isVagueValue() &&
2828 succ->getOutputUsage(succ->outStack) != OutputUsed) ||
2829 !(succ->outLocal && !succ->outLocal->rtt.isVagueValue() &&
2830 succ->getOutputUsage(succ->outLocal)) != OutputUsed) {
2831 return OutputDoesntCare;
2834 return OutputUsed;
2838 return OutputUnused;
2841 GuardType::GuardType(DataType outer, DataType inner)
2842 : outerType(outer), innerType(inner) {
2845 GuardType::GuardType(const RuntimeType& rtt) {
2846 assert(rtt.isValue());
2847 outerType = rtt.outerType();
2848 innerType = rtt.innerType();
2851 GuardType::GuardType(const GuardType& other) {
2852 *this = other;
2856 const DataType GuardType::getOuterType() const {
2857 return outerType;
2860 const DataType GuardType::getInnerType() const {
2861 return innerType;
2864 bool GuardType::isSpecific() const {
2865 return outerType > KindOfInvalid;
2868 bool GuardType::isRelaxed() const {
2869 switch (outerType) {
2870 case KindOfAny:
2871 case KindOfUncounted:
2872 case KindOfUncountedInit:
2873 return true;
2874 default:
2875 return false;
2879 bool GuardType::isGeneric() const {
2880 return outerType == KindOfAny;
2883 bool GuardType::isCounted() const {
2884 switch (outerType) {
2885 case KindOfAny:
2886 case KindOfStaticString:
2887 case KindOfString:
2888 case KindOfArray:
2889 case KindOfObject:
2890 case KindOfRef:
2891 return true;
2892 default:
2893 return false;
2897 bool GuardType::isMoreRefinedThan(const GuardType& other) const {
2898 return getCategory() > other.getCategory();
2901 DataTypeCategory GuardType::getCategory() const {
2902 switch (outerType) {
2903 case KindOfAny: return DataTypeGeneric;
2904 case KindOfUncounted: return DataTypeCountness;
2905 case KindOfUncountedInit: return DataTypeCountnessInit;
2906 default: return DataTypeSpecific;
2910 bool GuardType::mayBeUninit() const {
2911 switch (outerType) {
2912 case KindOfAny:
2913 case KindOfUncounted:
2914 case KindOfUninit:
2915 return true;
2916 default:
2917 return false;
2921 GuardType GuardType::getCountness() const {
2922 // Note that translations need to be able to handle KindOfString and
2923 // KindOfStaticString interchangeably. This implies that KindOfStaticString
2924 // needs to be treated as KindOfString, i.e. as possibly counted.
2925 assert(isSpecific());
2926 switch (outerType) {
2927 case KindOfUninit:
2928 case KindOfNull:
2929 case KindOfBoolean:
2930 case KindOfInt64:
2931 case KindOfDouble: return GuardType(KindOfUncounted);
2932 default: return *this;
2936 GuardType GuardType::getCountnessInit() const {
2937 assert(isSpecific());
2938 switch (outerType) {
2939 case KindOfNull:
2940 case KindOfBoolean:
2941 case KindOfInt64:
2942 case KindOfDouble: return GuardType(KindOfUncountedInit);
2943 default: return *this;
2948 DataTypeCategory
2949 Translator::getOperandConstraintCategory(NormalizedInstruction* instr,
2950 size_t opndIdx) {
2951 static const NormalizedInstruction::OutputUse kOutputUsed =
2952 NormalizedInstruction::OutputUsed;
2953 switch (instr->op()) {
2954 case OpSetL : {
2955 assert(opndIdx < 2);
2956 if (opndIdx == 0) { // stack value
2957 auto stackValUsage = instr->getOutputUsage(instr->outStack, true);
2958 if ((instr->outStack && stackValUsage == kOutputUsed) ||
2959 (instr->getOutputUsage(instr->outLocal) == kOutputUsed)) {
2960 return DataTypeSpecific;
2962 return DataTypeGeneric;
2963 } else { // old local value
2964 return DataTypeCountness;
2967 case OpCGetL : {
2968 if (!instr->outStack || (instr->getOutputUsage(instr->outStack, true) ==
2969 NormalizedInstruction::OutputUsed)) {
2970 return DataTypeSpecific;
2972 return DataTypeCountnessInit;
2974 case OpRetC :
2975 case OpRetV : {
2976 return DataTypeCountness;
2978 default: return DataTypeSpecific;
2983 GuardType Translator::getOperandConstraintType(NormalizedInstruction* instr,
2984 size_t opndIdx,
2985 const GuardType& specType) {
2986 DataTypeCategory dtCategory = getOperandConstraintCategory(instr, opndIdx);
2987 switch (dtCategory) {
2988 case DataTypeGeneric: return GuardType(KindOfAny);
2989 case DataTypeCountness: return specType.getCountness();
2990 case DataTypeCountnessInit: return specType.getCountnessInit();
2991 case DataTypeSpecific:
2992 default: return specType;
2996 void Translator::constrainOperandType(GuardType& relxType,
2997 NormalizedInstruction* instr,
2998 size_t opndIdx,
2999 const GuardType& specType) {
3000 if (relxType.isSpecific()) return; // Can't constrain any further
3002 switch (instr->op()) {
3003 case OpRetC :
3004 case OpRetV :
3005 case OpCGetL :
3006 case OpSetL :
3008 GuardType consType = getOperandConstraintType(instr, opndIdx, specType);
3009 if (consType.isMoreRefinedThan(relxType)) {
3010 relxType = consType;
3012 return;
3014 default: {
3015 relxType = specType;
3016 return;
3021 void Translator::reanalizeConsumers(Tracelet& tclet, DynLocation* depDynLoc) {
3022 for (auto& instr : tclet.m_instrs) {
3023 for (size_t i = 0; i < instr.inputs.size(); i++) {
3024 if (instr.inputs[i] == depDynLoc) {
3025 analyzeInstr(tclet, instr);
3033 * This method looks at all the uses of the tracelet dependencies in the
3034 * instruction stream and tries to relax the type associated with each location.
3036 void Translator::relaxDeps(Tracelet& tclet, TraceletContext& tctxt) {
3037 DynLocTypeMap locRelxTypeMap;
3038 DynLocTypeMap locSpecTypeMap;
3040 // Initialize type maps. Relaxed types start off very relaxed, and then
3041 // they may get more specific depending on how the instructions use them.
3042 DepMap& deps = tctxt.m_dependencies;
3043 for (auto depIt = deps.begin(); depIt != deps.end(); depIt++) {
3044 DynLocation* loc = depIt->second;
3045 const RuntimeType& rtt = depIt->second->rtt;
3046 if (rtt.isValue() && !rtt.isVagueValue() && !loc->location.isThis()) {
3047 locSpecTypeMap[loc] = GuardType(rtt);
3048 locRelxTypeMap[loc] = GuardType(KindOfAny);
3052 // Process the instruction stream, constraining the relaxed types
3053 // along the way
3054 for (NormalizedInstruction* instr = tclet.m_instrStream.first; instr;
3055 instr = instr->next) {
3056 for (size_t i = 0; i < instr->inputs.size(); i++) {
3057 DynLocation* loc = instr->inputs[i];
3058 auto it = locRelxTypeMap.find(loc);
3059 if (it != locRelxTypeMap.end()) {
3060 GuardType& relxType = it->second;
3061 constrainOperandType(relxType, instr, i, locSpecTypeMap[loc]);
3066 // For each dependency, if we found a more relaxed type for it, use
3067 // such type.
3068 for (auto& kv : locRelxTypeMap) {
3069 DynLocation* loc = kv.first;
3070 const GuardType& relxType = kv.second;
3071 if (relxType.isRelaxed()) {
3072 TRACE(1, "relaxDeps: Loc: %s oldType: %s => newType: %s\n",
3073 loc->location.pretty().c_str(),
3074 deps[loc->location]->rtt.pretty().c_str(),
3075 RuntimeType(relxType.getOuterType(),
3076 relxType.getInnerType()).pretty().c_str());
3077 assert(deps[loc->location] == loc);
3078 assert(relxType.getOuterType() != KindOfInvalid);
3079 deps[loc->location]->rtt = RuntimeType(relxType.getOuterType(),
3080 relxType.getInnerType());
3081 reanalizeConsumers(tclet, loc);
3086 static bool checkTaintFuncs(StringData* name) {
3087 static const StringData* s_extract =
3088 StringData::GetStaticString("extract");
3089 return name->isame(s_extract);
3093 * Check whether the a given FCall should be analyzed for possible
3094 * inlining or not.
3096 static bool shouldAnalyzeCallee(const NormalizedInstruction* fcall) {
3097 auto const numArgs = fcall->imm[0].u_IVA;
3098 auto const target = fcall->funcd;
3099 auto const fpi = curFunc()->findFPI(fcall->source.m_offset);
3100 auto const pushOp = curUnit()->getOpcode(fpi->m_fpushOff);
3102 if (!RuntimeOption::RepoAuthoritative) return false;
3104 // Note: the IR assumes that $this is available in all inlined object
3105 // methods, which will need to be updated when we support
3106 // OpFPushClsMethod here.
3107 if (pushOp != OpFPushFuncD && pushOp != OpFPushObjMethodD
3108 && pushOp != OpFPushCtorD && pushOp != OpFPushCtor) {
3109 FTRACE(1, "analyzeCallee: push op ({}) was not supported\n",
3110 opcodeToName(pushOp));
3111 return false;
3114 if (!target) {
3115 FTRACE(1, "analyzeCallee: target func not known\n");
3116 return false;
3118 if (target->isBuiltin()) {
3119 FTRACE(1, "analyzeCallee: target func is a builtin\n");
3120 return false;
3123 constexpr int kMaxSubtraceAnalysisDepth = 2;
3124 if (tx64->analysisDepth() + 1 >= kMaxSubtraceAnalysisDepth) {
3125 FTRACE(1, "analyzeCallee: max inlining depth reached\n");
3126 return false;
3129 if (numArgs != target->numParams()) {
3130 FTRACE(1, "analyzeCallee: param count mismatch {} != {}\n",
3131 numArgs, target->numParams());
3132 return false;
3134 if (target->numLocals() != target->numParams()) {
3135 FTRACE(1, "analyzeCallee: not inlining functions with more locals "
3136 "than params\n");
3137 return false;
3140 // Find the fpush and ensure it's in this tracelet---refuse to
3141 // inline if there are any calls in order to prepare arguments.
3142 for (auto* ni = fcall->prev; ni; ni = ni->prev) {
3143 if (ni->source.offset() == fpi->m_fpushOff) {
3144 return true;
3146 if (isFCallStar(ni->op()) || ni->op() == OpFCallBuiltin) {
3147 FTRACE(1, "analyzeCallee: fpi region contained other calls\n");
3148 return false;
3151 FTRACE(1, "analyzeCallee: push instruction was in a different "
3152 "tracelet\n");
3153 return false;
3156 extern bool shouldIRInline(const Func* curFunc,
3157 const Func* func,
3158 const Tracelet& callee);
3160 void Translator::analyzeCallee(TraceletContext& tas,
3161 Tracelet& parent,
3162 NormalizedInstruction* fcall) {
3163 if (!shouldAnalyzeCallee(fcall)) return;
3165 auto const numArgs = fcall->imm[0].u_IVA;
3166 auto const target = fcall->funcd;
3167 auto const callerFunc = curFunc();
3170 * Prepare a map for all the known information about the argument
3171 * types.
3173 * Also, fill out KindOfUninit for any remaining locals. The point
3174 * here is that the subtrace can't call liveType for a local or
3175 * stack location (since our ActRec is fake), so we need them all in
3176 * the TraceletContext.
3178 * If any of the argument types are unknown (including inner-types
3179 * of KindOfRefs), we don't really try to analyze the callee. It
3180 * might be possible to do this but we'll need to modify the
3181 * analyzer to support unknown input types before there are any
3182 * NormalizedInstructions in the Tracelet.
3184 TypeMap initialMap;
3185 LocationSet callerArgLocs;
3186 for (int i = 0; i < numArgs; ++i) {
3187 auto callerLoc = Location(Location::Stack, fcall->stackOff - i - 1);
3188 auto calleeLoc = Location(Location::Local, numArgs - i - 1);
3189 auto type = tas.currentType(callerLoc);
3191 callerArgLocs.insert(callerLoc);
3193 if (type.isVagueValue()) {
3194 FTRACE(1, "analyzeCallee: {} has unknown type\n", callerLoc.pretty());
3195 return;
3197 if (type.isValue() && type.isRef() &&
3198 type.innerType() == KindOfInvalid) {
3199 FTRACE(1, "analyzeCallee: {} has unknown inner-refdata type\n",
3200 callerLoc.pretty());
3201 return;
3204 FTRACE(2, "mapping arg{} locs {} -> {} :: {}\n",
3205 numArgs - i - 1,
3206 callerLoc.pretty(),
3207 calleeLoc.pretty(),
3208 type.pretty());
3209 initialMap[calleeLoc] = type;
3211 for (int i = numArgs; i < target->numLocals(); ++i) {
3212 initialMap[Location(Location::Local, i)] = RuntimeType(KindOfUninit);
3216 * When reentering analyze to generate a Tracelet for a callee,
3217 * currently we handle this by creating a fake ActRec on the stack.
3219 * This is mostly a compromise to deal with existing code during the
3220 * analysis phase which pretty liberally inspects live VM state.
3222 ActRec fakeAR;
3223 fakeAR.m_savedRbp = reinterpret_cast<uintptr_t>(curFrame());
3224 fakeAR.m_savedRip = 0xbaabaa; // should never be inspected
3225 fakeAR.m_func = fcall->funcd;
3226 fakeAR.m_soff = 0xb00b00; // should never be inspected
3227 fakeAR.m_numArgsAndCtorFlag = numArgs;
3228 fakeAR.m_varEnv = nullptr;
3231 * Even when inlining an object method, we can leave the m_this as
3232 * null. See outThisObjectType().
3234 fakeAR.m_this = nullptr;
3236 FTRACE(1, "analyzing sub trace =================================\n");
3237 auto const oldFP = vmfp();
3238 auto const oldSP = vmsp();
3239 auto const oldPC = vmpc();
3240 auto const oldAnalyzeCalleeDepth = m_analysisDepth++;
3241 vmpc() = nullptr; // should never be used
3242 vmsp() = nullptr; // should never be used
3243 vmfp() = reinterpret_cast<Cell*>(&fakeAR);
3244 auto restoreFrame = [&]{
3245 vmfp() = oldFP;
3246 vmsp() = oldSP;
3247 vmpc() = oldPC;
3248 m_analysisDepth = oldAnalyzeCalleeDepth;
3250 SCOPE_EXIT {
3251 // It's ok to restoreFrame() twice---we have it in this scope
3252 // handler to ensure it still happens if we exit via an exception.
3253 restoreFrame();
3254 FTRACE(1, "finished sub trace ===================================\n");
3257 auto subTrace = analyze(SrcKey(target, target->base()), initialMap);
3260 * Verify the target trace actually ended with a return, or we have
3261 * no business doing anything based on it right now.
3263 if (!subTrace->m_instrStream.last ||
3264 (subTrace->m_instrStream.last->op() != OpRetC &&
3265 subTrace->m_instrStream.last->op() != OpRetV)) {
3266 FTRACE(1, "analyzeCallee: callee did not end in a return\n");
3267 return;
3271 * If the IR can't inline this, give up now. Below we're going to
3272 * start making changes to the traclet that is making the call
3273 * (potentially increasing the specificity of guards), and we don't
3274 * want to do that unnecessarily.
3276 if (!shouldIRInline(callerFunc, target, *subTrace)) {
3277 return;
3281 * Disabled for now:
3283 * Propagate the return type to our caller. If the return type is
3284 * not vague, it will hold if we can inline the trace.
3286 * This isn't really a sensible thing to do if we aren't also going
3287 * to inline the callee, however, because the return type may only
3288 * be what it is due to other output predictions (CGetMs or FCall)
3289 * inside the callee. This means we would need to check the return
3290 * value in the caller still as if it were a predicted return type.
3292 Location retVal(Location::Stack, 0);
3293 auto it = subTrace->m_changes.find(retVal);
3294 assert(it != subTrace->m_changes.end());
3295 FTRACE(1, "subtrace return: {}\n", it->second->pretty());
3296 if (false) {
3297 if (!it->second->rtt.isVagueValue() && !it->second->rtt.isRef()) {
3298 FTRACE(1, "changing callee's return type from {} to {}\n",
3299 fcall->outStack->rtt.pretty(),
3300 it->second->pretty());
3302 fcall->outputPredicted = true;
3303 fcall->outputPredictionStatic = false;
3304 fcall->outStack = parent.newDynLocation(fcall->outStack->location,
3305 it->second->rtt);
3306 tas.recordWrite(fcall->outStack, fcall);
3311 * In order for relaxDeps not to relax guards on some things we may
3312 * potentially have depended on here, we need to ensure that the
3313 * call instruction depends on all the inputs we've used.
3315 * (We could do better by letting relaxDeps look through the
3316 * callee.)
3318 restoreFrame();
3319 for (auto& loc : callerArgLocs) {
3320 fcall->inputs.push_back(tas.recordRead(InputInfo(loc), true));
3323 FTRACE(1, "analyzeCallee: inline candidate\n");
3324 fcall->calleeTrace = std::move(subTrace);
3328 * analyze --
3330 * Given a sequence of bytecodes, return our tracelet IR.
3332 * The purposes of this analysis is to determine:
3334 * 1. Pre-conditions: What locations get read before they get written to:
3335 * we will need typechecks for these and we will want to load them into
3336 * registers. (m_dependencies)
3338 * 2. Post-conditions: the locations that have been written to and are
3339 * still live at the end of the tracelet. We need to allocate registers
3340 * of these and we need to spill them at the end of the tracelet.
3341 * (m_changes)
3343 * 3. Determine the runtime types for each instruction's input locations
3344 * and output locations.
3346 * The main analysis works by doing a single pass over the instructions. It
3347 * effectively simulates the execution of each instruction, updating its
3348 * knowledge about types as it goes.
3350 * The TraceletContext class is used to keep track of the current state of
3351 * the world. Initially it is empty, and when the inputs for the first
3352 * instruction are analyzed we call recordRead(). The recordRead() function
3353 * in turn inspects the live types of the inputs and adds them to the type
3354 * map. This serves two purposes: (1) it figures out what typechecks this
3355 * tracelet needs; and (2) it guarantees that the code we generate will
3356 * satisfy the live types that are about to be passed in.
3358 * Over time the TraceletContext's type map will change. However, we need to
3359 * record what the types _were_ right before and right after a given
3360 * instruction executes. This is where the NormalizedInstruction class comes
3361 * in. We store the RuntimeTypes from the TraceletContext right before an
3362 * instruction executes into the NormalizedInstruction's 'inputs' field, and
3363 * we store the RuntimeTypes from the TraceletContext right after the
3364 * instruction executes into the various output fields.
3366 std::unique_ptr<Tracelet> Translator::analyze(SrcKey sk,
3367 const TypeMap& initialTypes) {
3368 std::unique_ptr<Tracelet> retval(new Tracelet());
3369 auto& t = *retval;
3370 t.m_sk = sk;
3372 DEBUG_ONLY const char* file = curUnit()->filepath()->data();
3373 DEBUG_ONLY const int lineNum = curUnit()->getLineNumber(t.m_sk.offset());
3374 DEBUG_ONLY const char* funcName = curFunc()->fullName()->data();
3376 TRACE(1, "Translator::analyze %s:%d %s\n", file, lineNum, funcName);
3377 TraceletContext tas(&t, initialTypes);
3378 ImmStack immStack;
3379 int stackFrameOffset = 0;
3380 int oldStackFrameOffset = 0;
3382 // numOpcodes counts the original number of opcodes in a tracelet
3383 // before the translator does any optimization
3384 t.m_numOpcodes = 0;
3385 Unit::MetaHandle metaHand;
3387 const Unit *unit = curUnit();
3388 for (;; sk.advance(unit)) {
3389 head:
3390 NormalizedInstruction* ni = t.newNormalizedInstruction();
3391 ni->source = sk;
3392 ni->stackOff = stackFrameOffset;
3393 ni->funcd = (t.m_arState.getCurrentState() == ActRecState::KNOWN) ?
3394 t.m_arState.getCurrentFunc() : nullptr;
3395 ni->m_unit = unit;
3396 ni->preppedByRef = false;
3397 ni->breaksTracelet = false;
3398 ni->changesPC = opcodeChangesPC(ni->op());
3399 ni->manuallyAllocInputs = false;
3400 ni->fuseBranch = false;
3401 ni->outputPredicted = false;
3402 ni->outputPredictionStatic = false;
3404 assert(!t.m_analysisFailed);
3405 oldStackFrameOffset = stackFrameOffset;
3406 for (int i = 0; i < numImmediates(ni->op()); i++) {
3407 ni->imm[i] = getImm(ni->pc(), i);
3409 if (hasImmVector(*ni->pc())) {
3410 ni->immVec = getImmVector(ni->pc());
3412 if (ni->op() == OpFCallArray) {
3413 ni->imm[0].u_IVA = 1;
3416 // Use the basic block analyzer to follow the flow of immediate values.
3417 findImmable(immStack, ni);
3419 SKTRACE(1, sk, "stack args: virtual sfo now %d\n", stackFrameOffset);
3421 // Translation could fail entirely (because of an unknown opcode), or
3422 // encounter an input that cannot be computed.
3423 try {
3424 preInputApplyMetaData(metaHand, ni);
3425 InputInfos inputInfos;
3426 getInputs(t, ni, stackFrameOffset, inputInfos, tas);
3427 bool noOp = applyInputMetaData(metaHand, ni, tas, inputInfos);
3428 if (noOp) {
3429 t.m_instrStream.append(ni);
3430 ++t.m_numOpcodes;
3431 stackFrameOffset = oldStackFrameOffset;
3432 continue;
3434 if (inputInfos.needsRefCheck) {
3435 // Drive the arState machine; if it is going to throw an input
3436 // exception, do so here.
3437 int argNum = ni->imm[0].u_IVA;
3438 // instrSpToArDelta() returns the delta relative to the sp at the
3439 // beginning of the instruction, but getReffiness() wants the delta
3440 // relative to the sp at the beginning of the tracelet, so we adjust
3441 // by subtracting ni->stackOff
3442 int entryArDelta = instrSpToArDelta(ni->pc()) - ni->stackOff;
3443 ni->preppedByRef = t.m_arState.getReffiness(argNum,
3444 entryArDelta,
3445 &t.m_refDeps);
3446 SKTRACE(1, sk, "passing arg%d by %s\n", argNum,
3447 ni->preppedByRef ? "reference" : "value");
3450 for (unsigned int i = 0; i < inputInfos.size(); i++) {
3451 SKTRACE(2, sk, "typing input %d\n", i);
3452 const InputInfo& ii = inputInfos[i];
3453 DynLocation* dl = tas.recordRead(ii, true);
3454 const RuntimeType& rtt = dl->rtt;
3455 // Some instructions are able to handle an input with an unknown type
3456 if (!ii.dontBreak && !ii.dontGuard) {
3457 if (rtt.isVagueValue()) {
3458 // Consumed a "poisoned" output: e.g., result of an array
3459 // deref.
3460 throwUnknownInput();
3462 if (!ni->ignoreInnerType && !ii.dontGuardInner) {
3463 if (rtt.isValue() && rtt.isRef() &&
3464 rtt.innerType() == KindOfInvalid) {
3465 throwUnknownInput();
3469 ni->inputs.push_back(dl);
3471 } catch (TranslationFailedExc& tfe) {
3472 SKTRACE(1, sk, "Translator fail: %s:%d\n", tfe.m_file, tfe.m_line);
3473 if (!t.m_numOpcodes) {
3474 t.m_analysisFailed = true;
3475 t.m_instrStream.append(ni);
3476 ++t.m_numOpcodes;
3478 goto breakBB;
3479 } catch (UnknownInputExc& uie) {
3480 // Subtle: if this instruction consumes an unknown runtime type,
3481 // break the BB on the *previous* instruction. We know that a
3482 // previous instruction exists, because the KindOfInvalid must
3483 // have come from somewhere.
3484 always_assert(t.m_instrStream.last);
3485 SKTRACE(2, sk, "Consumed unknown input (%s:%d); breaking BB at "
3486 "predecessor\n", uie.m_file, uie.m_line);
3487 goto breakBB;
3490 SKTRACE(2, sk, "stack args: virtual sfo now %d\n", stackFrameOffset);
3492 bool doVarEnvTaint; // initialized by reference.
3494 try {
3495 getOutputs(t, ni, stackFrameOffset, doVarEnvTaint);
3496 } catch (TranslationFailedExc& tfe) {
3497 SKTRACE(1, sk, "Translator getOutputs fail: %s:%d\n",
3498 tfe.m_file, tfe.m_line);
3499 if (!t.m_numOpcodes) {
3500 t.m_analysisFailed = true;
3501 t.m_instrStream.append(ni);
3502 ++t.m_numOpcodes;
3504 goto breakBB;
3507 if (isFCallStar(ni->op())) {
3508 if (!doVarEnvTaint) {
3509 const FPIEnt *fpi = curFunc()->findFPI(ni->source.m_offset);
3510 assert(fpi);
3511 Offset fpushOff = fpi->m_fpushOff;
3512 PC fpushPc = curUnit()->at(fpushOff);
3513 if (*fpushPc == OpFPushFunc) {
3514 doVarEnvTaint = true;
3515 } else if (*fpushPc == OpFPushFuncD) {
3516 StringData *funcName =
3517 curUnit()->lookupLitstrId(getImm(fpushPc, 1).u_SA);
3518 doVarEnvTaint = checkTaintFuncs(funcName);
3519 } else if (*fpushPc == OpFPushFuncU) {
3520 StringData *fallbackName =
3521 curUnit()->lookupLitstrId(getImm(fpushPc, 2).u_SA);
3522 doVarEnvTaint = checkTaintFuncs(fallbackName);
3525 t.m_arState.pop();
3527 if (ni->op() == OpFCallBuiltin && !doVarEnvTaint) {
3528 StringData* funcName = curUnit()->lookupLitstrId(ni->imm[2].u_SA);
3529 doVarEnvTaint = checkTaintFuncs(funcName);
3531 if (doVarEnvTaint) {
3532 tas.varEnvTaint();
3535 DynLocation* outputs[] = { ni->outStack, ni->outLocal, ni->outLocal2,
3536 ni->outStack2, ni->outStack3 };
3537 for (size_t i = 0; i < sizeof(outputs) / sizeof(*outputs); ++i) {
3538 if (outputs[i]) {
3539 DynLocation* o = outputs[i];
3540 SKTRACE(2, sk, "inserting output t(%d->%d) #(%s, %d)\n",
3541 o->rtt.outerType(), o->rtt.innerType(),
3542 o->location.spaceName(), o->location.offset);
3543 tas.recordWrite(o, ni);
3547 SKTRACE(1, sk, "stack args: virtual sfo now %d\n", stackFrameOffset);
3549 // This assert failing means that your instruction has an
3550 // inconsistent row in the InstrInfo table; the stackDelta doesn't
3551 // agree with the inputs and outputs.
3552 assert(getStackDelta(*ni) == (stackFrameOffset - oldStackFrameOffset));
3553 // If this instruction decreased the depth of the stack, mark the
3554 // appropriate stack locations as "dead". But we need to leave
3555 // them in the TraceletContext until after analyzeCallee (if this
3556 // is an FCall).
3557 if (stackFrameOffset < oldStackFrameOffset) {
3558 for (int i = stackFrameOffset; i < oldStackFrameOffset; ++i) {
3559 ni->deadLocs.push_back(Location(Location::Stack, i));
3563 t.m_stackChange += getStackDelta(*ni);
3565 t.m_instrStream.append(ni);
3566 ++t.m_numOpcodes;
3569 * The annotation step attempts to track Func*'s associated with
3570 * given FCalls when the FPush is in a different tracelet.
3572 * When we're analyzing a callee, we can't do this because we may
3573 * have class information in some of our RuntimeTypes that is only
3574 * true because of who the caller was. (Normally it is only there
3575 * if it came from static analysis.)
3577 if (analysisDepth() == 0) {
3578 annotate(ni);
3581 analyzeInstr(t, *ni);
3582 if (ni->op() == OpFCall) {
3583 analyzeCallee(tas, t, ni);
3586 for (auto& l : ni->deadLocs) {
3587 tas.recordDelete(l);
3590 if (debug) {
3591 // The interpreter has lots of nice sanity assertions in debug mode
3592 // that the translator doesn't exercise. As a cross-check on the
3593 // translator's correctness, this is a debug-only facility for
3594 // sending a random selection of instructions through the
3595 // interpreter.
3597 // Set stress_txInterpPct to a value between 1 and 100. If you want
3598 // to reproduce a failing case, look for the seed in the log and set
3599 // stress_txInterpSeed accordingly.
3600 if (!dbgTranslateCoin) {
3601 dbgTranslateCoin = new BiasedCoin(Trace::stress_txInterpPct,
3602 Trace::stress_txInterpSeed);
3603 TRACE(1, "BiasedCoin(stress_txInterpPct,stress_txInterpSeed): "
3604 "pct %f, seed %d\n",
3605 dbgTranslateCoin->getPercent(), dbgTranslateCoin->getSeed());
3607 assert(dbgTranslateCoin);
3608 if (dbgTranslateCoin->flip()) {
3609 SKTRACE(3, ni->source, "stress interp\n");
3610 ni->m_txFlags = Interp;
3613 if ((ni->op()) > Trace::moduleLevel(Trace::tmp0) &&
3614 (ni->op()) < Trace::moduleLevel(Trace::tmp1)) {
3615 ni->m_txFlags = Interp;
3619 Op txOpBisectLowOp = (Op)moduleLevel(Trace::txOpBisectLow),
3620 txOpBisectHighOp = (Op)moduleLevel(Trace::txOpBisectHigh);
3621 if (txOpBisectLowOp > OpLowInvalid &&
3622 txOpBisectHighOp > OpLowInvalid &&
3623 txOpBisectHighOp < OpHighInvalid) {
3624 // If the user specified an operation bisection interval [Low, High]
3625 // that is strictly included in (OpLowInvalid, OpHighInvalid), then
3626 // only support the operations in that interval. Since the default
3627 // value of moduleLevel is 0 and OpLowInvalid is also 0, this ensures
3628 // that bisection is disabled by default.
3629 static_assert(OpLowInvalid >= 0,
3630 "OpLowInvalid must be nonnegative");
3631 if (ni->op() < txOpBisectLowOp ||
3632 ni->op() > txOpBisectHighOp)
3633 ni->m_txFlags = Interp;
3636 // Check if we need to break the tracelet.
3638 // If we've gotten this far, it mostly boils down to control-flow
3639 // instructions. However, we'll trace through a few unconditional jmps.
3640 if (ni->op() == OpJmp &&
3641 ni->imm[0].u_IA > 0 &&
3642 tas.m_numJmps < MaxJmpsTracedThrough) {
3643 // Continue tracing through jumps. To prevent pathologies, only trace
3644 // through a finite number of forward jumps.
3645 SKTRACE(1, sk, "greedily continuing through %dth jmp + %d\n",
3646 tas.m_numJmps, ni->imm[0].u_IA);
3647 tas.recordJmp();
3648 sk = SrcKey(curFunc(), sk.m_offset + ni->imm[0].u_IA);
3649 goto head; // don't advance sk
3650 } else if (opcodeBreaksBB(ni->op()) ||
3651 (ni->m_txFlags == Interp && opcodeChangesPC(ni->op()))) {
3652 SKTRACE(1, sk, "BB broken\n");
3653 sk.advance(unit);
3654 goto breakBB;
3656 postAnalyze(ni, sk, t, tas);
3658 breakBB:
3659 NormalizedInstruction* ni = t.m_instrStream.last;
3660 while (ni) {
3661 switch (ni->op()) {
3662 // We dont want to end a tracelet with a literal;
3663 // it will cause the literal to be pushed on the
3664 // stack, and the next tracelet will have to guard
3665 // on the type.
3666 case OpNull:
3667 case OpNullUninit:
3668 case OpTrue:
3669 case OpFalse:
3670 case OpInt:
3671 case OpDouble:
3672 case OpString:
3673 case OpArray:
3674 // Similarly, This, Self and Parent will lose
3675 // type information thats only useful in the
3676 // following tracelet.
3677 case OpThis:
3678 case OpSelf:
3679 case OpParent:
3680 ni = ni->prev;
3681 continue;
3682 default:
3683 break;
3685 break;
3687 if (ni) {
3688 while (ni != t.m_instrStream.last) {
3689 t.m_stackChange -= getStackDelta(*t.m_instrStream.last);
3690 sk = t.m_instrStream.last->source;
3691 t.m_instrStream.remove(t.m_instrStream.last);
3692 --t.m_numOpcodes;
3696 relaxDeps(t, tas);
3698 // Mark the last instruction appropriately
3699 assert(t.m_instrStream.last);
3700 t.m_instrStream.last->breaksTracelet = true;
3701 t.m_nextSk = sk;
3702 // Populate t.m_changes, t.intermediates, t.m_dependencies
3703 t.m_dependencies = tas.m_dependencies;
3704 t.m_resolvedDeps = tas.m_resolvedDeps;
3705 t.m_changes.clear();
3706 LocationSet::iterator it = tas.m_changeSet.begin();
3707 for (; it != tas.m_changeSet.end(); ++it) {
3708 t.m_changes[*it] = tas.m_currentMap[*it];
3711 t.constructLiveRanges();
3713 TRACE(1, "Tracelet done: stack delta %d\n", t.m_stackChange);
3714 return retval;
3717 Translator::Translator()
3718 : m_resumeHelper(nullptr)
3719 , m_createdTime(Timer::GetCurrentTimeMicros())
3720 , m_analysisDepth(0)
3722 initInstrInfo();
3725 Translator::~Translator() {
3728 Translator*
3729 Translator::Get() {
3730 return TranslatorX64::Get();
3733 bool
3734 Translator::isSrcKeyInBL(const Unit* unit, const SrcKey& sk) {
3735 Lock l(m_dbgBlacklistLock);
3736 if (m_dbgBLSrcKey.find(sk) != m_dbgBLSrcKey.end()) {
3737 return true;
3739 for (PC pc = unit->at(sk.m_offset); !opcodeBreaksBB(*pc);
3740 pc += instrLen(pc)) {
3741 if (m_dbgBLPC.checkPC(pc)) {
3742 m_dbgBLSrcKey.insert(sk);
3743 return true;
3746 return false;
3749 void
3750 Translator::clearDbgBL() {
3751 Lock l(m_dbgBlacklistLock);
3752 m_dbgBLSrcKey.clear();
3753 m_dbgBLPC.clear();
3756 bool
3757 Translator::addDbgBLPC(PC pc) {
3758 Lock l(m_dbgBlacklistLock);
3759 if (m_dbgBLPC.checkPC(pc)) {
3760 // already there
3761 return false;
3763 m_dbgBLPC.addPC(pc);
3764 return true;
3767 uint64_t* Translator::getTransCounterAddr() {
3768 if (!isTransDBEnabled()) return nullptr;
3770 TransID id = m_translations.size();
3772 // allocate a new chunk of counters if necessary
3773 if (id >= m_transCounters.size() * transCountersPerChunk) {
3774 uint32_t size = sizeof(uint64_t) * transCountersPerChunk;
3775 auto *chunk = (uint64_t*)malloc(size);
3776 bzero(chunk, size);
3777 m_transCounters.push_back(chunk);
3779 assert(id / transCountersPerChunk < m_transCounters.size());
3780 return &(m_transCounters[id / transCountersPerChunk]
3781 [id % transCountersPerChunk]);
3785 uint64_t Translator::getTransCounter(TransID transId) const {
3786 if (!isTransDBEnabled()) return -1ul;
3787 assert(transId < m_translations.size());
3789 uint64_t counter;
3791 if (transId / transCountersPerChunk >= m_transCounters.size()) {
3792 counter = 0;
3793 } else {
3794 counter = m_transCounters[transId / transCountersPerChunk]
3795 [transId % transCountersPerChunk];
3797 return counter;
3800 void Translator::setTransCounter(TransID transId, uint64_t value) {
3801 assert(transId < m_translations.size());
3802 assert(transId / transCountersPerChunk < m_transCounters.size());
3804 m_transCounters[transId / transCountersPerChunk]
3805 [transId % transCountersPerChunk] = value;
3808 static const char *transKindStr[] = {
3809 "Normal_Tx64",
3810 "Normal_HHIR",
3811 "Anchor",
3812 "Prologue",
3815 const char *getTransKindName(TransKind kind) {
3816 assert(kind >= 0 && kind <= TransProlog);
3817 return transKindStr[kind];
3820 string
3821 TransRec::print(uint64_t profCount) const {
3822 const size_t kBufSize = 1000;
3823 static char formatBuf[kBufSize];
3825 snprintf(formatBuf, kBufSize,
3826 "Translation %d {\n"
3827 " src.md5 = %s\n"
3828 " src.funcId = %u\n"
3829 " src.startOffset = 0x%x\n"
3830 " src.stopOffset = 0x%x\n"
3831 " kind = %u (%s)\n"
3832 " aStart = %p\n"
3833 " aLen = 0x%x\n"
3834 " stubStart = %p\n"
3835 " stubLen = 0x%x\n"
3836 " profCount = %" PRIu64 "\n"
3837 " bcMapping = %lu\n",
3838 id, md5.toString().c_str(), src.getFuncId(), src.offset(),
3839 bcStopOffset, kind, getTransKindName(kind), aStart, aLen,
3840 astubsStart, astubsLen, profCount, bcMapping.size());
3842 string ret(formatBuf);
3844 for (size_t i = 0; i < bcMapping.size(); i++) {
3845 snprintf(formatBuf, kBufSize, " 0x%x %p %p\n",
3846 bcMapping[i].bcStart,
3847 bcMapping[i].aStart,
3848 bcMapping[i].astubsStart);
3850 ret += string(formatBuf);
3853 ret += "}\n\n";
3854 return ret;
3857 void
3858 ActRecState::pushFuncD(const Func* func) {
3859 TRACE(2, "ActRecState: pushStatic func %p(%s)\n", func, func->name()->data());
3860 Record r;
3861 r.m_state = KNOWN;
3862 r.m_topFunc = func;
3863 r.m_entryArDelta = InvalidEntryArDelta;
3864 m_arStack.push_back(r);
3867 void
3868 ActRecState::pushDynFunc() {
3869 TRACE(2, "ActRecState: pushDynFunc\n");
3870 Record r;
3871 r.m_state = UNKNOWABLE;
3872 r.m_topFunc = nullptr;
3873 r.m_entryArDelta = InvalidEntryArDelta;
3874 m_arStack.push_back(r);
3877 void
3878 ActRecState::pop() {
3879 if (!m_arStack.empty()) {
3880 m_arStack.pop_back();
3885 * getReffiness() returns true if the parameter specified by argNum is pass
3886 * by reference, otherwise it returns false. This function may also throw an
3887 * UnknownInputException if the reffiness cannot be determined.
3889 * Note that the 'entryArDelta' parameter specifies the delta between sp at
3890 * the beginning of the tracelet and ar.
3892 bool
3893 ActRecState::getReffiness(int argNum, int entryArDelta, RefDeps* outRefDeps) {
3894 assert(outRefDeps);
3895 TRACE(2, "ActRecState: getting reffiness for arg %d\n", argNum);
3896 if (m_arStack.empty()) {
3897 // The ActRec in question was pushed before the beginning of the
3898 // tracelet, so we can make a guess about parameter reffiness and
3899 // record our assumptions about parameter reffiness as tracelet
3900 // guards.
3901 const ActRec* ar = arFromSpOffset((ActRec*)vmsp(), entryArDelta);
3902 Record r;
3903 r.m_state = GUESSABLE;
3904 r.m_entryArDelta = entryArDelta;
3905 r.m_topFunc = ar->m_func;
3906 m_arStack.push_back(r);
3908 Record& r = m_arStack.back();
3909 if (r.m_state == UNKNOWABLE) {
3910 TRACE(2, "ActRecState: unknowable, throwing in the towel\n");
3911 throwUnknownInput();
3912 not_reached();
3914 assert(r.m_topFunc);
3915 bool retval = r.m_topFunc->byRef(argNum);
3916 if (r.m_state == GUESSABLE) {
3917 assert(r.m_entryArDelta != InvalidEntryArDelta);
3918 TRACE(2, "ActRecState: guessing arg%d -> %d\n", argNum, retval);
3919 outRefDeps->addDep(r.m_entryArDelta, argNum, retval);
3921 return retval;
3924 const Func*
3925 ActRecState::getCurrentFunc() {
3926 if (m_arStack.empty()) return nullptr;
3927 return m_arStack.back().m_topFunc;
3930 ActRecState::State
3931 ActRecState::getCurrentState() {
3932 if (m_arStack.empty()) return GUESSABLE;
3933 return m_arStack.back().m_state;
3936 const Func* lookupImmutableMethod(const Class* cls, const StringData* name,
3937 bool& magicCall, bool staticLookup) {
3938 if (!cls || RuntimeOption::EvalJitEnableRenameFunction) return nullptr;
3939 bool privateOnly = false;
3940 if (!RuntimeOption::RepoAuthoritative ||
3941 !(cls->preClass()->attrs() & AttrUnique)) {
3942 Class* ctx = curFunc()->cls();
3943 if (!ctx || !ctx->classof(cls)) {
3944 return nullptr;
3946 if (!staticLookup) privateOnly = true;
3949 const Func* func;
3950 MethodLookup::LookupResult res = staticLookup ?
3951 g_vmContext->lookupClsMethod(func, cls, name, 0, false) :
3952 g_vmContext->lookupObjMethod(func, cls, name, false);
3954 if (res == MethodLookup::MethodNotFound) return nullptr;
3956 assert(res == MethodLookup::MethodFoundWithThis ||
3957 res == MethodLookup::MethodFoundNoThis ||
3958 (staticLookup ?
3959 res == MethodLookup::MagicCallStaticFound :
3960 res == MethodLookup::MagicCallFound));
3962 magicCall =
3963 res == MethodLookup::MagicCallStaticFound ||
3964 res == MethodLookup::MagicCallFound;
3966 if ((privateOnly && (!(func->attrs() & AttrPrivate) || magicCall)) ||
3967 func->isAbstract() ||
3968 func->attrs() & AttrDynamicInvoke) {
3969 return nullptr;
3972 if (staticLookup) {
3973 if (magicCall) {
3975 * i) We cant tell if a magic call would go to __call or __callStatic
3976 * - Could deal with this by checking for the existence of __call
3978 * ii) hphp semantics is that in the case of an object call, we look
3979 * for __call in the scope of the object (this is incompatible
3980 * with zend) which means we would have to know that there is no
3981 * __call higher up in the tree
3982 * - Could deal with this by checking for AttrNoOverride on the
3983 * class
3985 func = nullptr;
3987 } else if (!(func->attrs() & AttrPrivate)) {
3988 if (magicCall || func->attrs() & AttrStatic) {
3989 if (!(cls->preClass()->attrs() & AttrNoOverride)) {
3990 func = nullptr;
3992 } else if (!(func->attrs() & AttrNoOverride && !func->hasStaticLocals()) &&
3993 !(cls->preClass()->attrs() & AttrNoOverride)) {
3994 func = nullptr;
3997 return func;
4000 std::string traceletShape(const Tracelet& trace) {
4001 std::string ret;
4003 for (auto ni = trace.m_instrStream.first; ni; ni = ni->next) {
4004 using folly::toAppend;
4006 toAppend(opcodeToName(ni->op()), &ret);
4007 if (ni->immVec.isValid()) {
4008 toAppend(
4009 "<",
4010 locationCodeString(ni->immVec.locationCode()),
4011 &ret);
4012 for (auto& mc : ni->immVecM) {
4013 toAppend(" ", memberCodeString(mc), &ret);
4015 toAppend(">", &ret);
4017 toAppend(" ", &ret);
4020 return ret;