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