2 +----------------------------------------------------------------------+
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.
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"
54 static __thread BiasedCoin
*dbgTranslateCoin
;
56 Lease
Translator::s_writeLease
;
58 struct TraceletContext
{
59 TraceletContext() = delete;
61 TraceletContext(Tracelet
* t
, const TypeMap
& initialTypes
)
65 , m_varEnvTaint(false)
67 for (auto& kv
: initialTypes
) {
69 Trace::prettyNode("InitialType", kv
.first
, kv
.second
).c_str());
70 m_currentMap
[kv
.first
] = t
->newDynLocation(kv
.first
, kv
.second
);
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
;
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
);
94 void InstrStream::append(NormalizedInstruction
* ni
) {
110 void InstrStream::remove(NormalizedInstruction
* ni
) {
112 ni
->prev
->next
= ni
->next
;
117 ni
->next
->prev
= ni
->prev
;
125 void Tracelet::constructLiveRanges() {
127 auto considerLoc
= [this](DynLocation
* dloc
,
128 const NormalizedInstruction
* ni
,
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
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
);
171 DynLocation
* Tracelet::newDynLocation(Location l
, DataType t
) {
172 DynLocation
* dl
= new DynLocation(l
, t
);
173 m_dynlocs
.push_back(dl
);
177 DynLocation
* Tracelet::newDynLocation(Location l
, RuntimeType t
) {
178 DynLocation
* dl
= new DynLocation(l
, t
);
179 m_dynlocs
.push_back(dl
);
183 DynLocation
* Tracelet::newDynLocation() {
184 DynLocation
* dl
= new DynLocation();
185 m_dynlocs
.push_back(dl
);
189 void Tracelet::print() const {
193 void Tracelet::print(std::ostream
& out
) const {
194 const NormalizedInstruction
* i
= m_instrStream
.first
;
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
;
208 SrcKey::trace(const char *fmt
, ...) const {
209 if (!Trace::enabled
) {
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());
223 Trace::vtrace(fmt
, a
);
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
)
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
)
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
;
252 // Move over the current instruction pointer.
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
) {
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);
286 case Location::Local
: {
288 int offset
= locPhysicalOffset(l
);
289 base
= l
.space
== Location::Stack
? vmsp() : vmfp();
290 outer
= &base
[offset
];
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(),
296 return RuntimeType(it
);
298 case Location::Litstr
: {
299 return RuntimeType(u
.lookupLitstrId(l
.offset
));
301 case Location::Litint
: {
302 return RuntimeType(l
.offset
);
304 case Location::This
: {
305 return outThisObjectType();
311 assert(IS_REAL_TYPE(outer
->m_type
));
312 return liveType(outer
, l
);
316 Translator::liveType(const Cell
* outer
, const Location
& l
) {
317 always_assert(analysisDepth() == 0);
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.
341 klass
= valCell
->m_data
.pobj
->getVMClass();
344 TRACE(2, "liveType %d\n", outerType
);
345 RuntimeType retval
= RuntimeType(outerType
, KindOfInvalid
, klass
);
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;
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();
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
);
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
{
396 OutStringImm
, // String w/ precisely known immediate.
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
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).
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;
459 StackTop2
= Stack1
| Stack2
,
460 StackTop3
= Stack1
| Stack2
| Stack3
,
461 StackCufSafe
= StackIns1
| FStack
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
{
478 static DataType
inferType(const InferenceRule
* rules
,
479 const vector
<DynLocation
*>& inputs
) {
481 // We generate the inputMask by ORing together the mask for each input's
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
503 #define TYPE_MASK(name) \
504 static const int64_t name ## Mask = typeToMask(KindOf ## name);
509 static const int64_t IntMask
= typeToMask(KindOfInt64
);
511 static const int64_t StringMask
= typeToMask(KindOfString
) |
512 typeToMask(KindOfStaticString
);
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
},
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
533 static const InferenceRule BitOpRules
[] = {
534 { UninitMask
| NullMask
| BooleanMask
|
535 IntMask
| DoubleMask
| ArrayMask
| ObjectMask
,
537 { StringMask
, KindOfString
},
538 { 0, KindOfInvalid
},
541 static RuntimeType
bitOpType(DynLocation
* a
, DynLocation
* b
) {
542 vector
<DynLocation
*> ins
;
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;
562 isNormalPropertyAccess(const NormalizedInstruction
& i
,
565 const LocationCode lcode
= i
.immVec
.locationCode();
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
;
575 mInstrHasUnknownOffsets(const NormalizedInstruction
& ni
, Class
* context
) {
576 const MInstrInfo
& mii
= getMInstrInfo(ni
.mInstrOp());
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) {
595 PropInfo
getPropertyOffset(const NormalizedInstruction
& ni
,
597 const Class
*& baseClass
,
598 const MInstrInfo
& mii
,
599 unsigned mInd
, unsigned iInd
) {
601 auto const baseIndex
= mii
.valCount();
602 baseClass
= ni
.inputs
[baseIndex
]->rtt
.isObject()
603 ? ni
.inputs
[baseIndex
]->rtt
.valueClass()
606 baseClass
= ni
.immVecClasses
[mInd
- 1];
608 if (!baseClass
) return PropInfo();
610 if (!ni
.inputs
[iInd
]->rtt
.isString()) {
613 auto* const name
= ni
.inputs
[iInd
]->rtt
.valueString();
614 if (!name
) return PropInfo();
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
629 // baseClass can change on us in between requests and it is
630 // not related to ctx, so bail out
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
639 if (idx
== kInvalidSlot
|| !accessible
) {
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
646 baseClass
->declPropOffset(idx
),
647 baseClass
->declPropHphpcType(idx
)
651 PropInfo
getFinalPropertyOffset(const NormalizedInstruction
& ni
,
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
,
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
;
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",
685 return std::make_pair(KindOfInvalid
, 0.0);
691 * Provide a best guess for the output type of this instruction.
694 predictOutputs(const Tracelet
& t
,
695 NormalizedInstruction
* ni
) {
696 if (RuntimeOption::EvalJitStressTypePredPercent
&&
697 RuntimeOption::EvalJitStressTypePredPercent
> int(get_random() % 100)) {
698 ni
->outputPredicted
= true;
701 dt
= get_random() % (KindOfRef
+ 1);
711 // KindOfRef and KindOfUninit can't happen for lots of predicted
723 if (ni
->op() == OpMod
) {
724 // x % 0 returns boolean false, so we don't know for certain, but it's
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.
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();
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
);
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",
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
);
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());
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
);
804 case SetOpModEqual
: return RuntimeType(KindOfInvalid
);
807 case SetOpXorEqual
: return bitOpType(&locLocation
, inputs
[kValIdx
]);
809 case SetOpSrEqual
: return RuntimeType(KindOfInt64
);
814 return RuntimeType(KindOfInvalid
);
818 getDynLocType(const vector
<DynLocation
*>& inputs
,
821 NormalizedInstruction
* ni
,
823 OutTypeConstraints constraint
,
824 DynLocation
* outDynLoc
) {
825 assert(constraint
!= OutFInputL
);
827 switch (constraint
) {
828 #define CS(OutXLike, KindOfX) \
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
);
841 case OutPred
: return RuntimeType(predictOutputs(t
, ni
));
844 Op op
= Op(ni
->op());
845 if ((op
== OpAGetC
&& inputs
[0]->isString())) {
846 const StringData
*sd
= inputs
[0]->rtt
.valueString();
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();
858 return RuntimeType(clss
->parent());
860 return RuntimeType(KindOfClass
);
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
);
869 const TypedValue
* tv
= Unit::lookupPersistentCns(sd
);
871 return RuntimeType(tv
->m_type
);
873 tv
= Unit::lookupCns(sd
);
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
);
888 assert(ni
->op() == OpString
);
889 StringData
*sd
= curUnit()->lookupLitstrId(ni
->imm
[0].u_SA
);
891 return RuntimeType(sd
);
895 assert(ni
->op() == OpArray
);
896 ArrayData
*ad
= curUnit()->lookupArrayId(ni
->imm
[0].u_AA
);
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();
911 return RuntimeType(KindOfRef
, KindOfInvalid
);
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.
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;
945 switch (inputs
[1]->rtt
.valueType()) {
947 ok
= mcodeMaybePropName(ni
->immVecM
[0]);
950 ok
= mcodeMaybeArrayKey(ni
->immVecM
[0]);
960 for (int i
= inputs
.size(); --i
>= 2; ) {
961 switch (inputs
[i
]->rtt
.valueType()) {
973 ni
->outputPredicted
= true;
977 const int idx
= 0; // all currently supported cases.
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());
985 assert(inputs
[idx
]->rtt
.valueType() ==
986 inputs
[idx
]->rtt
.outerType());
990 return inputs
[idx
]->rtt
;
994 assert(inputs
.size() >= 1);
995 const DynLocation
* in
= inputs
[inputs
.size() - 1];
997 if (in
->rtt
.outerType() == KindOfUninit
) {
998 // Locals can be KindOfUninit, so we need to convert
999 // this to KindOfNull
1000 retval
= RuntimeType(KindOfNull
);
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());
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
);
1019 auto const& rtt
= ni
->inputs
[0]->rtt
;
1020 return RuntimeType(rtt
.isString() ? KindOfInt64
: KindOfInvalid
);
1024 assert(inputs
.size() >= 1);
1025 const DynLocation
* in
= inputs
[inputs
.size() - 1];
1026 if (in
->rtt
.outerType() == KindOfRef
) {
1027 return in
->rtt
.unbox();
1033 assert(inputs
.size() == 2 ||
1034 (inputs
.size() == 1 && opcode
== OpBitNot
));
1035 if (inputs
.size() == 2) {
1036 return bitOpType(inputs
[0], inputs
[1]);
1038 return bitOpType(inputs
[0], nullptr);
1043 return setOpOutputType(ni
, inputs
);
1048 return RuntimeType(KindOfInvalid
);
1053 * NB: this opcode structure is sparse; it cannot just be indexed by
1059 OutTypeConstraints type
; // How are outputs related to inputs?
1060 int stackDelta
; // Impact on stack: # cells *pushed*
1063 static const struct {
1066 } instrInfoSparse
[] = {
1068 // Op Inputs Outputs OutputTypes Stack delta
1069 // -- ------ ------- ----------- -----------
1071 /*** 1. Basic instructions ***/
1073 { OpNop
, {None
, None
, OutNone
, 0 }},
1075 DontGuardStack1
, None
, OutNone
, -1 }},
1078 IgnoreInnerType
, None
, OutNone
, -1 }},
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 ***/
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 }},
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 }},
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
,
1262 { OpFPushFuncU
, {None
, FStack
, OutFDesc
,
1265 {StackTop2
, FStack
, OutFDesc
,
1266 kNumActRecCells
- 2 }},
1267 { OpFPushObjMethodD
,
1268 {Stack1
, FStack
, OutFDesc
,
1269 kNumActRecCells
- 1 }},
1271 {StackTop2
, FStack
, OutFDesc
,
1272 kNumActRecCells
- 2 }},
1273 { OpFPushClsMethodF
,
1274 {StackTop2
, FStack
, OutFDesc
,
1275 kNumActRecCells
- 2 }},
1276 { OpFPushClsMethodD
,
1277 {None
, FStack
, OutFDesc
,
1279 { OpFPushCtor
, {Stack1
, Stack1
|FStack
,OutObject
,
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
,
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 }},
1349 {None
, Local
, OutUnknown
, 0 }},
1351 {None
, Stack1
, OutBoolean
, 1 }},
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]);
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;
1409 Opcode op
= ni
.op();
1412 int numArgs
= ni
.imm
[0].u_IVA
;
1413 return 1 - numArgs
- kNumActRecCells
;
1416 case OpFCallBuiltin
:
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
;
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();
1445 t
.m_instrStream
.remove(ni
);
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
;
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
;
1462 SKTRACE(1, ni
->source
, "CGetL: loading input\n");
1469 ni
->manuallyAllocInputs
= !seen
&& !ni
->inputs
[0]->rtt
.isUninit();
1470 SKTRACE(1, ni
->source
, "CGetL: manuallyAllocInputs: %d\n",
1471 ni
->manuallyAllocInputs
);
1475 NormalizedInstruction
* prev
= ni
->prev
;
1476 if (!prev
|| !(prev
->m_txFlags
& Supported
)) continue;
1477 const Opcode prevOp
= prev
->op();
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
);
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
||
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
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
&&
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 */
1533 t
.m_instrStream
.remove(ni
->next
);
1534 t
.m_instrStream
.remove(ni
);
1536 SKTRACE(1, prev
->source
, "FPushCtorD: killing ctor for %s in %s\n",
1537 np
.first
->data(), curFunc()->fullName()->data());
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.
1549 const bool isPop
= op
== OpPopC
|| op
== OpPopV
;
1550 const bool isOptimizable
= prevOp
== OpSetL
||
1551 prevOp
== OpBindL
||
1552 prevOp
== OpIncDecL
||
1553 prevOp
== OpPrint
||
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;
1585 * A Not instruction following an Is* instruction can
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
);
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
1619 prev
->ignoreInnerType
= true;
1621 prev
->manuallyAllocInputs
= true;
1625 if ((op
== OpInstanceOfD
|| op
== OpIsNullC
) &&
1626 (ni
->m_txFlags
& Supported
) &&
1627 (prevOp
== OpThis
|| prevOp
== OpBareThis
)) {
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
)) {
1642 /* Dont waste a register on the string */
1643 prev
->outStack
= nullptr;
1648 if (op
== OpRetC
&& (ni
->m_txFlags
& Supported
) &&
1649 (prevOp
== OpString
||
1653 prevOp
== OpFalse
||
1654 prevOp
== OpDouble
||
1655 prevOp
== OpArray
||
1657 prevOp
== OpBareThis
)) {
1658 assert(!ni
->outStack
);
1660 prev
->outStack
= nullptr;
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()) {
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;
1689 static NormalizedInstruction
* findInputSrc(NormalizedInstruction
* ni
,
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
) {
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;
1723 case Unit::MetaInfo::GuardedThis
:
1724 ni
->guardedThis
= true;
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
) {
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).
1759 if (iInfo
.in
== FuncdRef
) {
1761 * FPassC* pretend to have no inputs
1765 const int base
= !(iInfo
.in
& MVector
) ? 0 :
1766 !(iInfo
.in
& Stack1
) ? 0 :
1767 !(iInfo
.in
& Stack2
) ? 1 :
1768 !(iInfo
.in
& Stack3
) ? 2 : 3;
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;
1780 case Unit::MetaInfo::GuardedCls
:
1781 ni
->guardedCls
= true;
1783 case Unit::MetaInfo::ArrayCapacity
:
1784 ni
->imm
[0].u_IVA
= info
.m_data
;
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
,
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;
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;
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
1849 dl
->rtt
= RuntimeType((DataType
)info
.m_data
);
1850 ni
->markInputInferred(arg
);
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
);
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
);
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
) {
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
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
);
1911 dl
->rtt
= RuntimeType(KindOfObject
, KindOfInvalid
, metaCls
);
1917 case Unit::MetaInfo::MVecPropClass
: {
1918 const StringData
* metaName
= ni
->unit()->lookupLitstrId(info
.m_data
);
1919 Class
* metaCls
= Unit::lookupUniqueClass(metaName
);
1921 ni
->immVecClasses
[arg
] = metaCls
;
1926 case Unit::MetaInfo::NopOut
:
1927 // NopOut should always be the first and only annotation
1928 // and was handled above.
1931 case Unit::MetaInfo::GuardedThis
:
1932 case Unit::MetaInfo::NonRefCounted
:
1933 // fallthrough; these are handled in preInputApplyMetaData.
1934 case Unit::MetaInfo::None
:
1937 } while (metaHand
.nextArg(info
));
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
= [&] {
1956 inputs
.emplace_back(Location(Location::Stack
, localStackOffset
++));
1958 auto push_local
= [&] (int imm
) {
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
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
)) {
1985 inputs
.emplace_back(Location(Location::This
));
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
));
1996 // We'll get the trailing stack value after pushing all the
1997 // member vector elements.
1998 push_local(decodeVariableSizeImm(&vec
));
2005 if (!trailingClassRef
) {
2006 // This one is actually at the back.
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
);
2019 // No stack and no locals.
2020 } else if (memberCodeHasImm(mcode
)) {
2021 int64_t imm
= decodeMemberCodeImm(&vec
, mcode
);
2022 if (memberCodeImmIsLoc(mcode
)) {
2024 } else if (memberCodeImmIsString(mcode
)) {
2025 inputs
.emplace_back(Location(Location::Litstr
, imm
));
2027 assert(memberCodeImmIsInt(mcode
));
2028 inputs
.emplace_back(Location(Location::Litint
, imm
));
2033 inputs
.back().dontGuardInner
= true;
2036 if (trailingClassRef
) {
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
);
2051 * Returns locations for this instruction's inputs.
2054 * TranslationFailedExc:
2055 * Unimplemented functionality, probably an opcode.
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
,
2067 const TraceletContext
& tas
) {
2069 const SrcKey
& sk
= ni
->source
;
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()));
2077 const InstrInfo
& info
= instrInfo
[ni
->op()];
2078 Operands input
= info
.in
;
2079 if (input
& FuncdRef
) {
2080 inputs
.needsRefCheck
= true;
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,
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,
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
2141 loc
= ni
->imm
[1].u_IVA
;
2145 loc
= ni
->imm
[0].u_IVA
;
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) {
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
;
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;
2197 inputs
.emplace_back(Location(Location::This
));
2201 bool outputDependsOnInput(const Opcode instr
) {
2202 switch (instrInfo
[instr
].type
) {
2224 case OutSameAsInput
:
2242 * Builds a vector describing this instruction's outputs. Also
2243 * records any write to a value that *might* alias a local.
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
;
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
++);
2273 if (typeInfo
== OutVInputL
) {
2276 assert(typeInfo
== OutFInputL
|| typeInfo
== OutFInputR
);
2277 isRef
= ni
->preppedByRef
;
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
);
2285 outDynLoc
->rtt
= in
->rtt
.box();
2287 SKTRACE(1, ni
->source
, "boxed type: %d -> %d\n",
2288 outDynLoc
->rtt
.outerType(), outDynLoc
->rtt
.innerType());
2290 if (outDynLoc
->rtt
.outerType() == KindOfUninit
) {
2291 outDynLoc
->rtt
= RuntimeType(KindOfNull
);
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.
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
2325 // Pseudo-outputs that affect translator state
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
);
2336 t
.m_arState
.pushDynFunc();
2339 // Non-deterministic in some way
2340 t
.m_arState
.pushDynFunc();
2342 } continue; // no instr-associated output
2345 if (op
== OpSetN
|| op
== OpSetOpN
|| op
== OpIncDecN
||
2346 op
== OpBindN
|| op
== OpUnsetN
) {
2350 ASSERT_NOT_IMPLEMENTED(op
== OpSetOpL
||
2351 op
== OpSetM
|| op
== OpSetOpM
||
2353 op
== OpIncDecL
|| op
== OpIncDecG
||
2354 op
== OpUnsetG
|| op
== OpBindG
||
2355 op
== OpSetG
|| op
== OpSetOpG
||
2357 op
== OpStaticLocInit
|| op
== OpInitThisLoc
||
2358 op
== OpSetL
|| op
== OpBindL
||
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
||
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
;
2394 if (op
== OpStaticLocInit
|| op
== OpInitThisLoc
) {
2395 ni
->outLocal
= t
.newDynLocation(Location(Location::Local
,
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
||
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
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
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());
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
);
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
;
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() ||
2504 (inVal
->rtt
.outerType() == KindOfRef
));
2505 assert(!inVal
->location
.isLocal());
2506 assert(inLoc
->location
.isLocal());
2507 RuntimeType newLhsRtt
= inVal
->rtt
.isVagueValue() || op
== OpBindL
?
2509 inLoc
->rtt
.setValueType(inVal
->rtt
.outerType());
2510 if (inLoc
->rtt
.outerType() == KindOfRef
) {
2511 assert(newLhsRtt
.outerType() == KindOfRef
);
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
2526 loc
= Location(Location::Stack
, currentStackOffset
++);
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
++),
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
++),
2552 ni
->outStack3
= t
.newDynLocation(
2553 Location(Location::Stack
, currentStackOffset
++),
2560 DynLocation
* dl
= t
.newDynLocation();
2562 dl
->rtt
= getDynLocType(inputs
, t
, op
, ni
, (Operands
)opnd
,
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());
2573 Translator::findImmable(ImmStack
&stack
,
2574 NormalizedInstruction
* ni
) {
2577 stack
.pushInt(getImm(ni
->pc(), 0).u_I64A
);
2581 stack
.pushLitstr(getImm(ni
->pc(), 0).u_SA
);
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
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.
2607 // If this is "<VecInstr>M <... EC>"
2608 const ImmVector
& iv
= ni
->immVec
;
2609 assert(iv
.isValid());
2613 if (iv
.size() > 1 &&
2614 iv
.decodeLastMember(curUnit(), str
, mc
, &strId
) &&
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.
2622 if (LIKELY(!str
->isStrictlyInteger(lval
))) {
2623 ni
->hasConstImm
= true;
2624 ni
->constImm
.u_SA
= strId
;
2632 stack
.processOpcode(ni
->pc());
2636 Translator::requestResetHighLevelTranslator() {
2637 if (dbgTranslateCoin
) {
2638 dbgTranslateCoin
->reset();
2642 bool DynLocation::canBeAliased() const {
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 {
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());
2658 DynLocation
* TraceletContext::recordRead(const InputInfo
& ii
,
2660 DataType staticType
) {
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
;
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
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());
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() {
2753 * Helpers for recovering context of this instruction.
2755 Op
NormalizedInstruction::op() const {
2757 assert(isValidOpcode(op
));
2761 Op
NormalizedInstruction::mInstrOp() const {
2763 #define MII(instr, a, b, i, v, d) case Op##instr##M: return opcode;
2767 return preppedByRef
? OpVGetM
: OpCGetM
;
2774 PC
NormalizedInstruction::pc() const {
2775 return unit()->at(source
.offset());
2778 const Unit
* NormalizedInstruction::unit() const {
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()) {
2795 const Unit
* unit
= ni
->m_unit
;
2797 Opcode next
= *unit
->at(src
.offset());
2798 if (next
== OpInstanceOfD
|| next
== OpIsNullC
) {
2799 ni
->outStack
->rtt
= RuntimeType(KindOfObject
);
2805 NormalizedInstruction::OutputUse
2806 NormalizedInstruction::getOutputUsage(DynLocation
* output
,
2807 bool ignorePops
/* =false */) const {
2808 for (NormalizedInstruction
* succ
= next
;
2809 succ
; succ
= succ
->next
) {
2811 (succ
->op() == OpPopC
||
2812 succ
->op() == OpPopV
||
2813 succ
->op() == OpPopR
)) {
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
;
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
) {
2856 const DataType
GuardType::getOuterType() const {
2860 const DataType
GuardType::getInnerType() const {
2864 bool GuardType::isSpecific() const {
2865 return outerType
> KindOfInvalid
;
2868 bool GuardType::isRelaxed() const {
2869 switch (outerType
) {
2871 case KindOfUncounted
:
2872 case KindOfUncountedInit
:
2879 bool GuardType::isGeneric() const {
2880 return outerType
== KindOfAny
;
2883 bool GuardType::isCounted() const {
2884 switch (outerType
) {
2886 case KindOfStaticString
:
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
) {
2913 case KindOfUncounted
:
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
) {
2931 case KindOfDouble
: return GuardType(KindOfUncounted
);
2932 default: return *this;
2936 GuardType
GuardType::getCountnessInit() const {
2937 assert(isSpecific());
2938 switch (outerType
) {
2942 case KindOfDouble
: return GuardType(KindOfUncountedInit
);
2943 default: return *this;
2949 Translator::getOperandConstraintCategory(NormalizedInstruction
* instr
,
2951 static const NormalizedInstruction::OutputUse kOutputUsed
=
2952 NormalizedInstruction::OutputUsed
;
2953 switch (instr
->op()) {
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
;
2968 if (!instr
->outStack
|| (instr
->getOutputUsage(instr
->outStack
, true) ==
2969 NormalizedInstruction::OutputUsed
)) {
2970 return DataTypeSpecific
;
2972 return DataTypeCountnessInit
;
2976 return DataTypeCountness
;
2978 default: return DataTypeSpecific
;
2983 GuardType
Translator::getOperandConstraintType(NormalizedInstruction
* instr
,
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
,
2999 const GuardType
& specType
) {
3000 if (relxType
.isSpecific()) return; // Can't constrain any further
3002 switch (instr
->op()) {
3008 GuardType consType
= getOperandConstraintType(instr
, opndIdx
, specType
);
3009 if (consType
.isMoreRefinedThan(relxType
)) {
3010 relxType
= consType
;
3015 relxType
= specType
;
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
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
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
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
));
3115 FTRACE(1, "analyzeCallee: target func not known\n");
3118 if (target
->isBuiltin()) {
3119 FTRACE(1, "analyzeCallee: target func is a builtin\n");
3123 constexpr int kMaxSubtraceAnalysisDepth
= 2;
3124 if (tx64
->analysisDepth() + 1 >= kMaxSubtraceAnalysisDepth
) {
3125 FTRACE(1, "analyzeCallee: max inlining depth reached\n");
3129 if (numArgs
!= target
->numParams()) {
3130 FTRACE(1, "analyzeCallee: param count mismatch {} != {}\n",
3131 numArgs
, target
->numParams());
3134 if (target
->numLocals() != target
->numParams()) {
3135 FTRACE(1, "analyzeCallee: not inlining functions with more locals "
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
) {
3146 if (isFCallStar(ni
->op()) || ni
->op() == OpFCallBuiltin
) {
3147 FTRACE(1, "analyzeCallee: fpi region contained other calls\n");
3151 FTRACE(1, "analyzeCallee: push instruction was in a different "
3156 extern bool shouldIRInline(const Func
* curFunc
,
3158 const Tracelet
& callee
);
3160 void Translator::analyzeCallee(TraceletContext
& tas
,
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
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.
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());
3197 if (type
.isValue() && type
.isRef() &&
3198 type
.innerType() == KindOfInvalid
) {
3199 FTRACE(1, "analyzeCallee: {} has unknown inner-refdata type\n",
3200 callerLoc
.pretty());
3204 FTRACE(2, "mapping arg{} locs {} -> {} :: {}\n",
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.
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
= [&]{
3248 m_analysisDepth
= oldAnalyzeCalleeDepth
;
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.
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");
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
)) {
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());
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
,
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
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
);
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.
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());
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
);
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
3385 Unit::MetaHandle metaHand
;
3387 const Unit
*unit
= curUnit();
3388 for (;; sk
.advance(unit
)) {
3390 NormalizedInstruction
* ni
= t
.newNormalizedInstruction();
3392 ni
->stackOff
= stackFrameOffset
;
3393 ni
->funcd
= (t
.m_arState
.getCurrentState() == ActRecState::KNOWN
) ?
3394 t
.m_arState
.getCurrentFunc() : nullptr;
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.
3424 preInputApplyMetaData(metaHand
, ni
);
3425 InputInfos inputInfos
;
3426 getInputs(t
, ni
, stackFrameOffset
, inputInfos
, tas
);
3427 bool noOp
= applyInputMetaData(metaHand
, ni
, tas
, inputInfos
);
3429 t
.m_instrStream
.append(ni
);
3431 stackFrameOffset
= oldStackFrameOffset
;
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
,
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
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
);
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
);
3490 SKTRACE(2, sk
, "stack args: virtual sfo now %d\n", stackFrameOffset
);
3492 bool doVarEnvTaint
; // initialized by reference.
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
);
3507 if (isFCallStar(ni
->op())) {
3508 if (!doVarEnvTaint
) {
3509 const FPIEnt
*fpi
= curFunc()->findFPI(ni
->source
.m_offset
);
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
);
3527 if (ni
->op() == OpFCallBuiltin
&& !doVarEnvTaint
) {
3528 StringData
* funcName
= curUnit()->lookupLitstrId(ni
->imm
[2].u_SA
);
3529 doVarEnvTaint
= checkTaintFuncs(funcName
);
3531 if (doVarEnvTaint
) {
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
) {
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
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
);
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) {
3581 analyzeInstr(t
, *ni
);
3582 if (ni
->op() == OpFCall
) {
3583 analyzeCallee(tas
, t
, ni
);
3586 for (auto& l
: ni
->deadLocs
) {
3587 tas
.recordDelete(l
);
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
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
);
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");
3656 postAnalyze(ni
, sk
, t
, tas
);
3659 NormalizedInstruction
* ni
= t
.m_instrStream
.last
;
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
3674 // Similarly, This, Self and Parent will lose
3675 // type information thats only useful in the
3676 // following tracelet.
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
);
3698 // Mark the last instruction appropriately
3699 assert(t
.m_instrStream
.last
);
3700 t
.m_instrStream
.last
->breaksTracelet
= true;
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
);
3717 Translator::Translator()
3718 : m_resumeHelper(nullptr)
3719 , m_createdTime(Timer::GetCurrentTimeMicros())
3720 , m_analysisDepth(0)
3725 Translator::~Translator() {
3730 return TranslatorX64::Get();
3734 Translator::isSrcKeyInBL(const Unit
* unit
, const SrcKey
& sk
) {
3735 Lock
l(m_dbgBlacklistLock
);
3736 if (m_dbgBLSrcKey
.find(sk
) != m_dbgBLSrcKey
.end()) {
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
);
3750 Translator::clearDbgBL() {
3751 Lock
l(m_dbgBlacklistLock
);
3752 m_dbgBLSrcKey
.clear();
3757 Translator::addDbgBLPC(PC pc
) {
3758 Lock
l(m_dbgBlacklistLock
);
3759 if (m_dbgBLPC
.checkPC(pc
)) {
3763 m_dbgBLPC
.addPC(pc
);
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
);
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());
3791 if (transId
/ transCountersPerChunk
>= m_transCounters
.size()) {
3794 counter
= m_transCounters
[transId
/ transCountersPerChunk
]
3795 [transId
% transCountersPerChunk
];
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
[] = {
3815 const char *getTransKindName(TransKind kind
) {
3816 assert(kind
>= 0 && kind
<= TransProlog
);
3817 return transKindStr
[kind
];
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"
3828 " src.funcId = %u\n"
3829 " src.startOffset = 0x%x\n"
3830 " src.stopOffset = 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
);
3858 ActRecState::pushFuncD(const Func
* func
) {
3859 TRACE(2, "ActRecState: pushStatic func %p(%s)\n", func
, func
->name()->data());
3863 r
.m_entryArDelta
= InvalidEntryArDelta
;
3864 m_arStack
.push_back(r
);
3868 ActRecState::pushDynFunc() {
3869 TRACE(2, "ActRecState: pushDynFunc\n");
3871 r
.m_state
= UNKNOWABLE
;
3872 r
.m_topFunc
= nullptr;
3873 r
.m_entryArDelta
= InvalidEntryArDelta
;
3874 m_arStack
.push_back(r
);
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.
3893 ActRecState::getReffiness(int argNum
, int entryArDelta
, RefDeps
* 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
3901 const ActRec
* ar
= arFromSpOffset((ActRec
*)vmsp(), entryArDelta
);
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();
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
);
3925 ActRecState::getCurrentFunc() {
3926 if (m_arStack
.empty()) return nullptr;
3927 return m_arStack
.back().m_topFunc
;
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
)) {
3946 if (!staticLookup
) privateOnly
= true;
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
||
3959 res
== MethodLookup::MagicCallStaticFound
:
3960 res
== MethodLookup::MagicCallFound
));
3963 res
== MethodLookup::MagicCallStaticFound
||
3964 res
== MethodLookup::MagicCallFound
;
3966 if ((privateOnly
&& (!(func
->attrs() & AttrPrivate
) || magicCall
)) ||
3967 func
->isAbstract() ||
3968 func
->attrs() & AttrDynamicInvoke
) {
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
3987 } else if (!(func
->attrs() & AttrPrivate
)) {
3988 if (magicCall
|| func
->attrs() & AttrStatic
) {
3989 if (!(cls
->preClass()->attrs() & AttrNoOverride
)) {
3992 } else if (!(func
->attrs() & AttrNoOverride
&& !func
->hasStaticLocals()) &&
3993 !(cls
->preClass()->attrs() & AttrNoOverride
)) {
4000 std::string
traceletShape(const Tracelet
& trace
) {
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()) {
4010 locationCodeString(ni
->immVec
.locationCode()),
4012 for (auto& mc
: ni
->immVecM
) {
4013 toAppend(" ", memberCodeString(mc
), &ret
);
4015 toAppend(">", &ret
);
4017 toAppend(" ", &ret
);