2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-2016 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/irgen-call.h"
18 #include "hphp/runtime/base/stats.h"
20 #include "hphp/runtime/vm/jit/func-effects.h"
21 #include "hphp/runtime/vm/jit/mc-generator.h"
22 #include "hphp/runtime/vm/jit/normalized-instruction.h"
23 #include "hphp/runtime/vm/jit/target-profile.h"
24 #include "hphp/runtime/vm/jit/type-constraint.h"
25 #include "hphp/runtime/vm/jit/type.h"
27 #include "hphp/runtime/vm/jit/irgen-exit.h"
28 #include "hphp/runtime/vm/jit/irgen-create.h"
29 #include "hphp/runtime/vm/jit/irgen-internal.h"
31 namespace HPHP
{ namespace jit
{ namespace irgen
{
35 //////////////////////////////////////////////////////////////////////
37 const StaticString
s_self("self");
38 const StaticString
s_parent("parent");
39 const StaticString
s_static("static");
41 //////////////////////////////////////////////////////////////////////
43 const Func
* findCuf(Op op
,
52 const StringData
* str
=
53 callable
->hasConstVal(TStr
) ? callable
->strVal() : nullptr;
54 const ArrayData
* arr
=
55 callable
->hasConstVal(TArr
) ? callable
->arrVal() : nullptr;
57 StringData
* sclass
= nullptr;
58 StringData
* sname
= nullptr;
60 Func
* f
= Unit::lookupFunc(str
);
62 String
name(const_cast<StringData
*>(str
));
63 int pos
= name
.find("::");
64 if (pos
<= 0 || pos
+ 2 >= name
.size() ||
65 name
.find("::", pos
+ 2) != String::npos
) {
68 sclass
= makeStaticString(name
.substr(0, pos
).get());
69 sname
= makeStaticString(name
.substr(pos
+ 2).get());
71 if (arr
->size() != 2) return nullptr;
72 const Variant
& e0
= arr
->get(int64_t(0), false);
73 const Variant
& e1
= arr
->get(int64_t(1), false);
74 if (!e0
.isString() || !e1
.isString()) return nullptr;
75 sclass
= e0
.getStringData();
76 sname
= e1
.getStringData();
78 if (name
.find("::") != String::npos
) return nullptr;
83 if (sclass
->isame(s_self
.get())) {
84 if (!ctx
) return nullptr;
87 } else if (sclass
->isame(s_parent
.get())) {
88 if (!ctx
|| !ctx
->parent()) return nullptr;
91 } else if (sclass
->isame(s_static
.get())) {
94 cls
= Unit::lookupClassOrUniqueClass(sclass
);
95 if (!cls
) return nullptr;
98 bool magicCall
= false;
99 const Func
* f
= lookupImmutableMethod(cls
, sname
, magicCall
,
100 /* staticLookup = */ true, ctx
);
101 if (!f
|| (forward
&& !ctx
->classof(f
->cls()))) {
103 * To preserve the invariant that the lsb class
104 * is an instance of the context class, we require
105 * that f's class is an instance of the context class.
106 * This is conservative, but without it, we would need
107 * a runtime check to decide whether or not to forward
112 if (magicCall
) invName
= sname
;
116 bool canInstantiateClass(const Class
* cls
) {
117 return cls
&& isNormalClass(cls
) && !isAbstract(cls
);
120 //////////////////////////////////////////////////////////////////////
122 // Pushing for object method when we don't know the Func* statically.
123 void fpushObjMethodUnknown(IRGS
& env
,
125 const StringData
* methodName
,
128 emitIncStat(env
, Stats::ObjMethod_cached
, 1);
130 cns(env
, TNullptr
), // Will be set by LdObjMethod
134 auto const objCls
= gen(env
, LdObjClass
, obj
);
136 // This is special. We need to move the stackpointer in case LdObjMethod
137 // calls a destructor. Otherwise it would clobber the ActRec we just pushed.
139 env
.irb
->exceptionStackBoundary();
144 offsetFromIRSP(env
, BCSPOffset
{0}), methodName
, shouldFatal
151 * Returns true iff a method named methodName appears in iface or any of its
152 * implemented (parent) interfaces. vtableSlot and func will be initialized to
153 * the appropriate vtable slot and interface Func when true is returned;
154 * otherwise their contents are undefined.
156 bool findInterfaceVtableSlot(IRGS
& env
,
158 const StringData
* methodName
,
161 vtableSlot
= iface
->preClass()->ifaceVtableSlot();
163 if (vtableSlot
!= kInvalidSlot
) {
164 auto res
= g_context
->lookupObjMethod(func
, iface
, methodName
,
165 curClass(env
), false);
166 if (res
== LookupResult::MethodFoundWithThis
||
167 res
== LookupResult::MethodFoundNoThis
) {
172 for (auto pface
: iface
->allInterfaces().range()) {
173 if (findInterfaceVtableSlot(env
, pface
, methodName
, vtableSlot
, func
)) {
181 void fpushObjMethodExactFunc(
184 const Class
* baseClass
,
186 const StringData
* methodName
,
190 * lookupImmutableMethod will return Funcs from AttrUnique classes, but in
191 * this case, we have an object, so there's no need to check that the class
194 * Static function: store base class into this slot instead of obj and decref
195 * the obj that was pushed as the this pointer since the obj won't be in the
196 * actrec and thus MethodCache::lookup won't decref it.
198 * Static closure body: we still need to pass the object instance for the
199 * closure prologue to properly do its dispatch (and extract use vars). It
200 * will decref it and put the class on the actrec before entering the "real"
201 * cloned closure body.
203 SSATmp
* objOrCls
= obj
;
204 emitIncStat(env
, Stats::ObjMethod_known
, 1);
205 if (func
->isStatic() && !func
->isClosureBody()) {
208 objOrCls
= cns(env
, baseClass
);
219 const Func
* lookupExactFuncForFPushObjMethod(
222 const Class
* baseClass
,
223 const StringData
* methodName
,
226 const Func
* func
= lookupImmutableMethod(
230 /* staticLookup: */false,
234 if (func
) return func
;
235 if (!baseClass
|| (baseClass
->attrs() & AttrInterface
)) {
239 auto const res
= g_context
->lookupObjMethod(func
, baseClass
, methodName
,
240 curClass(env
), /* raise */false);
242 if (res
!= LookupResult::MethodFoundWithThis
&&
243 res
!= LookupResult::MethodFoundNoThis
) {
244 // Method lookup did not find anything.
249 * The only way this could be a magic call is if the LookupResult indicated
250 * as much. Since we just checked that res is either MethodFoundWithThis or
251 * MethodFoundNoThis, magicCall must be false.
256 * If we found the func in baseClass and it's private, this is always
257 * going to be the called function.
259 if (func
->attrs() & AttrPrivate
) return func
;
262 * If it's not private it could be overridden, so we don't have an exact func.
267 const Func
* lookupInterfaceFuncForFPushObjMethod(
269 const Class
* baseClass
,
270 const StringData
* methodName
,
271 const bool isMonomorphic
273 if (!baseClass
) return nullptr;
274 if (isMonomorphic
) return nullptr;
275 if (!classIsUniqueInterface(baseClass
)) return nullptr;
278 const Func
* ifaceFunc
;
279 if (findInterfaceVtableSlot(env
, baseClass
, methodName
,
280 vtableSlot
, ifaceFunc
)) {
287 void fpushObjMethodInterfaceFunc(
290 const Class
* baseClass
,
291 const StringData
* methodName
,
295 const Func
* ifaceFunc
;
296 findInterfaceVtableSlot(env
, baseClass
, methodName
, vtableSlot
, ifaceFunc
);
298 emitIncStat(env
, Stats::ObjMethod_ifaceslot
, 1);
299 auto cls
= gen(env
, LdObjClass
, obj
);
300 auto func
= gen(env
, LdIfaceMethod
,
301 IfaceMethodData
{vtableSlot
, ifaceFunc
->methodSlot()},
303 SSATmp
* objOrCls
= obj
;
304 if (ifaceFunc
->attrs() & AttrStatic
) {
308 fpushActRec(env
, func
, objOrCls
, numParams
, /* invName */nullptr);
313 const Func
* lookupNonExactFuncForFPushObjMethod(
315 const Class
* baseClass
,
316 const StringData
* methodName
318 if (!baseClass
) return nullptr;
320 const Func
* func
= nullptr;
321 auto const res
= g_context
->lookupObjMethod(func
, baseClass
, methodName
,
322 curClass(env
), /* raise */false);
324 if (res
!= LookupResult::MethodFoundWithThis
&&
325 res
!= LookupResult::MethodFoundNoThis
) {
326 // Method lookup did not find anything.
331 * If we found the func in baseClass and it's private, this would have already
332 * been handled as an exact Func.
334 assertx(!(func
->attrs() & AttrPrivate
));
337 * If we found the func in the baseClass and it's not private, any
338 * derived class must have a func that matches in staticness
339 * and is at least as accessible (and in particular, you can't
340 * override a public/protected method with a private method). In
341 * this case, we emit code to dynamically lookup the method given
342 * the Object and the method slot, which is the same as func's.
347 void fpushObjMethodNonExactFunc(
350 const Class
* baseClass
,
354 emitIncStat(env
, Stats::ObjMethod_methodslot
, 1);
355 auto const clsTmp
= gen(env
, LdObjClass
, obj
);
356 auto const funcTmp
= gen(
360 cns(env
, -(func
->methodSlot() + 1))
362 SSATmp
* objOrCls
= obj
;
363 if (func
->attrs() & AttrStatic
&& !func
->isClosureBody()) {
375 void fpushObjMethodWithBaseClass(
378 const Class
* baseClass
,
379 const StringData
* methodName
,
384 bool magicCall
= false;
385 if (auto const exactFunc
= lookupExactFuncForFPushObjMethod(
386 env
, obj
, baseClass
, methodName
, magicCall
)) {
387 fpushObjMethodExactFunc(env
, obj
, baseClass
, exactFunc
,
388 magicCall
? methodName
: nullptr,
393 if (lookupInterfaceFuncForFPushObjMethod(env
, baseClass
, methodName
,
395 fpushObjMethodInterfaceFunc(env
, obj
, baseClass
, methodName
, numParams
);
399 if (auto const nonExactFunc
= lookupNonExactFuncForFPushObjMethod(
400 env
, baseClass
, methodName
)) {
401 fpushObjMethodNonExactFunc(env
, obj
, baseClass
, nonExactFunc
, numParams
);
405 fpushObjMethodUnknown(env
, obj
, methodName
, numParams
, shouldFatal
);
408 static const StringData
* classProfileKey
= makeStaticString(
409 "ClassProfile-FPushObjMethod"
412 void fpushObjMethod(IRGS
& env
,
414 const StringData
* methodName
,
418 emitIncStat(env
, Stats::ObjMethod_total
, 1);
420 if (auto cls
= obj
->type().clsSpec().cls()) {
421 if (!env
.irb
->constrainValue(obj
, TypeConstraint(cls
).setWeak())) {
422 // If we know the class without having to specialize a guard any further,
424 fpushObjMethodWithBaseClass(env
, obj
, cls
, methodName
,
425 numParams
, shouldFatal
, false);
430 TargetProfile
<ClassProfile
> profile(env
.context
, env
.irb
->curMarker(),
432 if (profile
.profiling()) {
433 gen(env
, ProfileObjClass
, RDSHandleData
{ profile
.handle() }, obj
);
436 const bool shouldTryToOptimize
= !env
.transFlags
.noProfiledFPush
439 auto isMonomorphic
= false;
440 if (profile
.optimizing() && shouldTryToOptimize
) {
441 ClassProfile data
= profile
.data(ClassProfile::reduce
);
443 if (data
.isMonomorphic()) {
444 isMonomorphic
= true;
445 auto baseClass
= data
.getClass(0);
446 if (baseClass
->attrs() & AttrNoOverride
) {
447 auto refinedObj
= gen(env
, CheckType
, Type::ExactObj(baseClass
),
449 env
.irb
->constrainValue(refinedObj
, TypeConstraint(baseClass
));
450 fpushObjMethodWithBaseClass(env
, refinedObj
, baseClass
, methodName
,
451 numParams
, shouldFatal
, true);
457 fpushObjMethodWithBaseClass(env
, obj
, nullptr, methodName
, numParams
,
458 shouldFatal
, isMonomorphic
);
461 void fpushFuncObj(IRGS
& env
, int32_t numParams
) {
462 auto const slowExit
= makeExitSlow(env
);
463 auto const obj
= popC(env
);
464 auto const cls
= gen(env
, LdObjClass
, obj
);
465 auto const func
= gen(env
, LdObjInvoke
, slowExit
, cls
);
466 fpushActRec(env
, func
, obj
, numParams
, nullptr);
469 void fpushFuncArr(IRGS
& env
, int32_t numParams
) {
470 auto const thisAR
= fp(env
);
472 auto const arr
= popC(env
);
481 // This is special. We need to move the stackpointer incase LdArrFuncCtx
482 // calls a destructor. Otherwise it would clobber the ActRec we just
485 env
.irb
->exceptionStackBoundary();
487 gen(env
, LdArrFuncCtx
, IRSPOffsetData
{ offsetFromIRSP(env
, BCSPOffset
{0}) },
488 arr
, sp(env
), thisAR
);
492 // FPushCuf when the callee is not known at compile time.
493 void fpushCufUnknown(IRGS
& env
, Op op
, int32_t numParams
) {
494 if (op
!= Op::FPushCuf
) {
495 PUNT(fpushCufUnknown
-nonFPushCuf
);
498 if (topC(env
)->isA(TObj
)) return fpushFuncObj(env
, numParams
);
500 if (!topC(env
)->type().subtypeOfAny(TArr
, TStr
)) {
501 PUNT(fpushCufUnknown
);
504 auto const callable
= popC(env
);
514 * This is a similar case to lookup for functions in FPushFunc or
515 * FPushObjMethod. We can throw in a weird situation where the
516 * ActRec is already on the stack, but this bytecode isn't done
517 * executing yet. See arPreliveOverwriteCells for details about why
518 * we need this marker.
521 env
.irb
->exceptionStackBoundary();
523 auto const opcode
= callable
->isA(TArr
) ? LdArrFPushCuf
525 gen(env
, opcode
, IRSPOffsetData
{ offsetFromIRSP(env
, BCSPOffset
{0}) },
526 callable
, sp(env
), fp(env
));
527 decRef(env
, callable
);
530 SSATmp
* clsMethodCtx(IRGS
& env
, const Func
* callee
, const Class
* cls
) {
531 bool mustBeStatic
= true;
533 if (!(callee
->attrs() & AttrStatic
) &&
534 !(curFunc(env
)->attrs() & AttrStatic
) &&
536 if (curClass(env
)->classof(cls
)) {
537 // In this case, it might not be static, but we can be sure
538 // we're going to forward $this if thisAvailable.
539 mustBeStatic
= false;
540 } else if (cls
->classof(curClass(env
))) {
541 // Unlike the above, we might be calling down to a subclass that
542 // is not related to the current instance. To know whether this
543 // call forwards $this requires a runtime type check, so we have
544 // to punt instead of trying the thisAvailable path below.
545 PUNT(getClsMethodCtx
-PossibleStaticRelatedCall
);
550 return ldCls(env
, cns(env
, cls
->name()));
552 if (env
.irb
->fs().thisAvailable()) {
553 // might not be a static call and $this is available, so we know it's
554 // definitely not static
555 assertx(curClass(env
));
556 auto this_
= ldThis(env
);
557 gen(env
, IncRef
, this_
);
560 // might be a non-static call. we have to inspect the func at runtime
561 PUNT(getClsMethodCtx
-MightNotBeStatic
);
564 void implFPushCufOp(IRGS
& env
, Op op
, int32_t numArgs
) {
565 const bool safe
= op
== OpFPushCufSafe
;
566 bool forward
= op
== OpFPushCufF
;
567 SSATmp
* callable
= topC(env
, BCSPOffset
{safe
? 1 : 0});
569 const Class
* cls
= nullptr;
570 StringData
* invName
= nullptr;
571 auto const callee
= findCuf(op
, callable
, curClass(env
), cls
, invName
,
573 if (!callee
) return fpushCufUnknown(env
, op
, numArgs
);
576 auto const safeFlag
= cns(env
, true); // This is always true until the slow
577 // exits below are implemented
578 auto func
= cns(env
, callee
);
580 auto const exitSlow
= makeExitSlow(env
);
581 if (!rds::isPersistentHandle(cls
->classHandle())) {
582 // The miss path is complicated and rare. Punt for now. This must be
583 // checked before we IncRef the context below, because the slow exit will
584 // want to do that same IncRef via InterpOne.
585 auto const clsOrNull
= gen(env
, LdClsCachedSafe
, cns(env
, cls
->name()));
586 gen(env
, CheckNonNull
, exitSlow
, clsOrNull
);
591 ctx
= gen(env
, GetCtxFwdCall
, ctx
, cns(env
, callee
));
593 ctx
= clsMethodCtx(env
, callee
, cls
);
596 ctx
= cns(env
, TNullptr
);
597 if (!rds::isPersistentHandle(callee
->funcHandle())) {
598 // The miss path is complicated and rare. Punt for now.
599 func
= gen(env
, LdFuncCachedSafe
, LdFuncCachedData(callee
->name()));
600 func
= gen(env
, CheckNonNull
, makeExitSlow(env
), func
);
604 auto const defaultVal
= safe
? popC(env
) : nullptr;
605 popDecRef(env
); // callable
607 push(env
, defaultVal
);
611 fpushActRec(env
, func
, ctx
, numArgs
, invName
);
614 void fpushFuncCommon(IRGS
& env
,
616 const StringData
* name
,
617 const StringData
* fallback
) {
618 if (auto const func
= Unit::lookupFunc(name
)) {
619 if (func
->isNameBindingImmutable(curUnit(env
))) {
629 auto const ssaFunc
= fallback
630 ? gen(env
, LdFuncCachedU
, LdFuncCachedUData
{ name
, fallback
})
631 : gen(env
, LdFuncCached
, LdFuncCachedData
{ name
});
639 void implUnboxR(IRGS
& env
) {
640 auto const exit
= makeExit(env
);
641 auto const srcBox
= popR(env
);
642 auto const unboxed
= unbox(env
, srcBox
, exit
);
643 if (unboxed
== srcBox
) {
644 // If the Unbox ended up being a noop, don't bother refcounting
647 pushIncRef(env
, unboxed
);
652 //////////////////////////////////////////////////////////////////////
656 //////////////////////////////////////////////////////////////////////
658 void fpushActRec(IRGS
& env
,
662 const StringData
* invName
) {
664 info
.spOffset
= offsetFromIRSP(env
, BCSPOffset
{-int32_t{kNumActRecCells
}});
665 info
.invName
= invName
;
666 info
.numArgs
= numArgs
;
678 //////////////////////////////////////////////////////////////////////
680 void emitFPushCufIter(IRGS
& env
, int32_t numParams
, int32_t itId
) {
685 offsetFromIRSP(env
, BCSPOffset
{-int32_t{kNumActRecCells
}}),
686 static_cast<uint32_t>(numParams
),
694 void emitFPushCuf(IRGS
& env
, int32_t numArgs
) {
695 implFPushCufOp(env
, Op::FPushCuf
, numArgs
);
697 void emitFPushCufF(IRGS
& env
, int32_t numArgs
) {
698 implFPushCufOp(env
, Op::FPushCufF
, numArgs
);
700 void emitFPushCufSafe(IRGS
& env
, int32_t numArgs
) {
701 implFPushCufOp(env
, Op::FPushCufSafe
, numArgs
);
704 void emitFPushCtor(IRGS
& env
, int32_t numParams
) {
705 auto const cls
= popA(env
);
706 auto const func
= gen(env
, LdClsCtor
, cls
, fp(env
));
707 auto const obj
= gen(env
, AllocObj
, cls
);
708 pushIncRef(env
, obj
);
709 fpushActRec(env
, func
, obj
, numParams
, nullptr);
712 void emitFPushCtorD(IRGS
& env
,
714 const StringData
* className
) {
715 auto const cls
= Unit::lookupClassOrUniqueClass(className
);
716 bool const uniqueCls
= classIsUnique(cls
);
717 bool const persistentCls
= classHasPersistentRDS(cls
);
718 bool const canInstantiate
= canInstantiateClass(cls
);
719 bool const fastAlloc
=
722 !cls
->callsCustomInstanceInit() &&
723 !cls
->hasNativePropHandler();
725 auto const func
= lookupImmutableCtor(cls
, curClass(env
));
727 auto ssaCls
= persistentCls
729 : gen(env
, LdClsCached
, cns(env
, className
));
730 if (!ssaCls
->hasConstVal() && uniqueCls
) {
731 // If the Class is unique but not persistent, it's safe to use it as a
732 // const after the LdClsCached, which will throw if the class can't be
734 ssaCls
= cns(env
, cls
);
737 auto const ssaFunc
= func
? cns(env
, func
)
738 : gen(env
, LdClsCtor
, ssaCls
, fp(env
));
739 auto const obj
= fastAlloc
? allocObjFast(env
, cls
)
740 : gen(env
, AllocObj
, ssaCls
);
741 pushIncRef(env
, obj
);
742 fpushActRec(env
, ssaFunc
, obj
, numParams
, nullptr);
745 void emitFPushFuncD(IRGS
& env
, int32_t nargs
, const StringData
* name
) {
746 fpushFuncCommon(env
, nargs
, name
, nullptr);
749 void emitFPushFuncU(IRGS
& env
,
751 const StringData
* name
,
752 const StringData
* fallback
) {
753 fpushFuncCommon(env
, nargs
, name
, fallback
);
756 void emitFPushFunc(IRGS
& env
, int32_t numParams
) {
757 if (topC(env
)->isA(TObj
)) return fpushFuncObj(env
, numParams
);
758 if (topC(env
)->isA(TArr
)) return fpushFuncArr(env
, numParams
);
760 if (!topC(env
)->isA(TStr
)) {
761 PUNT(FPushFunc_not_Str
);
764 auto const funcName
= popC(env
);
766 gen(env
, LdFunc
, funcName
),
772 void emitFPushObjMethodD(IRGS
& env
,
774 const StringData
* methodName
,
777 trFlags
.noProfiledFPush
= true;
778 auto sideExit
= makeExit(env
, trFlags
);
780 auto const obj
= popC(env
);
782 if (obj
->type() <= TObj
) {
783 fpushObjMethod(env
, obj
, methodName
, numParams
,
784 true /* shouldFatal */, sideExit
);
788 if (obj
->type() <= TInitNull
&& subop
== ObjMethodOp::NullSafe
) {
791 cns(env
, SystemLib::s_nullFunc
),
798 PUNT(FPushObjMethodD
-nonObj
);
801 static void checkImmutableClsMethod(IRGS
& env
, Func
const* func
) {
802 if (!classHasPersistentRDS(func
->cls())) {
803 // we're only guaranteed uniqueness of the class. If its
804 // not persistent, we must make sure its loaded.
805 auto clsName
= cns(env
, func
->cls()->name());
807 [&] (Block
* notLoaded
) {
808 auto const clsOrNull
= gen(env
, LdClsCachedSafe
, clsName
);
809 gen(env
, CheckNonNull
, notLoaded
, clsOrNull
);
812 hint(env
, Block::Hint::Unlikely
);
813 gen(env
, LdClsCached
, clsName
);
818 void emitFPushClsMethodD(IRGS
& env
,
820 const StringData
* methodName
,
821 const StringData
* className
) {
822 auto const baseClass
= Unit::lookupClassOrUniqueClass(className
);
823 bool magicCall
= false;
825 if (auto const func
= lookupImmutableMethod(baseClass
,
828 true /* staticLookup */,
830 checkImmutableClsMethod(env
, func
);
831 auto const objOrCls
= clsMethodCtx(env
, func
, baseClass
);
836 func
&& magicCall
? methodName
: nullptr);
840 auto const slowExit
= makeExitSlow(env
);
841 auto const ne
= NamedEntity::get(className
);
842 auto const data
= ClsMethodData
{ className
, methodName
, ne
};
844 // Look up the Func* in the targetcache. If it's not there, try the slow
845 // path. If that fails, slow exit.
846 auto const func
= cond(
849 auto const mcFunc
= gen(env
, LdClsMethodCacheFunc
, data
);
850 return gen(env
, CheckNonNull
, taken
, mcFunc
);
852 [&] (SSATmp
* func
) { // next
856 hint(env
, Block::Hint::Unlikely
);
857 auto const result
= gen(env
, LookupClsMethodCache
, data
, fp(env
));
858 return gen(env
, CheckNonNull
, slowExit
, result
);
861 auto const clsCtx
= gen(env
, LdClsMethodCacheCls
, data
);
870 void emitFPushClsMethod(IRGS
& env
, int32_t numParams
) {
871 auto const clsVal
= popA(env
);
872 auto const methVal
= popC(env
);
874 if (!methVal
->isA(TStr
) || !clsVal
->isA(TCls
)) {
875 PUNT(FPushClsMethod
-unknownType
);
878 if (methVal
->hasConstVal()) {
879 const Class
* cls
= nullptr;
880 if (clsVal
->hasConstVal()) {
881 cls
= clsVal
->clsVal();
882 } else if (clsVal
->inst()->is(LdClsCtx
, LdClsCctx
)) {
884 * Optimize FPushClsMethod when the method is a known static
885 * string and the input class is the context. The common bytecode
886 * pattern here is LateBoundCls ; FPushClsMethod.
888 * This logic feels like it belongs in the simplifier, but the
889 * generated code for this case is pretty different, since we
890 * don't need the pre-live ActRec trick.
898 g_context
->lookupClsMethod(func
,
904 if (res
== LookupResult::MethodFoundNoThis
&& func
->isStatic()) {
905 auto funcTmp
= clsVal
->hasConstVal()
907 : gen(env
, LdClsMethod
, clsVal
, cns(env
, -(func
->methodSlot() + 1)));
908 fpushActRec(env
, funcTmp
, clsVal
, numParams
, nullptr);
921 * Similar to FPushFunc/FPushObjMethod, we have an incomplete ActRec on the
922 * stack and must handle that properly if we throw or re-enter.
925 env
.irb
->exceptionStackBoundary();
927 gen(env
, LookupClsMethod
,
928 IRSPOffsetData
{ offsetFromIRSP(env
, BCSPOffset
{0}) },
929 clsVal
, methVal
, sp(env
), fp(env
));
930 decRef(env
, methVal
);
933 void emitFPushClsMethodF(IRGS
& env
, int32_t numParams
) {
934 auto const exitBlock
= makeExitSlow(env
);
936 auto classTmp
= top(env
);
937 auto methodTmp
= topC(env
, BCSPOffset
{1}, DataTypeGeneric
);
938 assertx(classTmp
->isA(TCls
));
939 if (!classTmp
->hasConstVal() || !methodTmp
->hasConstVal(TStr
)) {
940 PUNT(FPushClsMethodF
-unknownClassOrMethod
);
942 env
.irb
->constrainValue(methodTmp
, DataTypeSpecific
);
944 auto const cls
= classTmp
->clsVal();
945 auto const methName
= methodTmp
->strVal();
947 bool magicCall
= false;
948 auto const vmfunc
= lookupImmutableMethod(cls
,
951 true /* staticLookup */,
955 auto const curCtxTmp
= ldCtx(env
);
957 checkImmutableClsMethod(env
, vmfunc
);
958 auto const funcTmp
= cns(env
, vmfunc
);
959 auto const newCtxTmp
= gen(env
, GetCtxFwdCall
, curCtxTmp
, funcTmp
);
960 fpushActRec(env
, funcTmp
, newCtxTmp
, numParams
,
961 magicCall
? methName
: nullptr);
965 auto const data
= ClsMethodData
{cls
->name(), methName
};
966 auto const funcTmp
= cond(
969 auto const fcacheFunc
= gen(env
, LdClsMethodFCacheFunc
, data
);
970 return gen(env
, CheckNonNull
, taken
, fcacheFunc
);
972 [&](SSATmp
* func
) { // next
976 hint(env
, Block::Hint::Unlikely
);
977 auto const result
= gen(
979 LookupClsMethodFCache
,
984 return gen(env
, CheckNonNull
, exitBlock
, result
);
988 auto const ctx
= gen(env
, GetCtxFwdCallDyn
, data
, curCtxTmp
);
993 magicCall
? methName
: nullptr);
996 //////////////////////////////////////////////////////////////////////
999 * All fpass instructions spill the stack after they execute, because we are
1000 * sure to need that value in memory, regardless of whether we side-exit or
1001 * throw. At the level of HHBC semantics, it's illegal to pop them from the
1002 * stack until we've left the FPI region, and we will be spilling the whole
1003 * stack when we get to the FCall{D,} at the end of the region. This should
1004 * also potentially reduce the number of live registers during call sequences.
1006 * Note: there is a general problem with the spillStack mechanism, in that it
1007 * may sink stores that are not profitable to sink, but in this case we can
1008 * work around it easily.
1011 void emitFPassL(IRGS
& env
, int32_t argNum
, int32_t id
) {
1012 if (env
.currentNormalizedInstruction
->preppedByRef
) {
1019 void emitFPassS(IRGS
& env
, int32_t argNum
) {
1020 if (env
.currentNormalizedInstruction
->preppedByRef
) {
1027 void emitFPassG(IRGS
& env
, int32_t argNum
) {
1028 if (env
.currentNormalizedInstruction
->preppedByRef
) {
1035 void emitFPassR(IRGS
& env
, int32_t argNum
) {
1036 if (env
.currentNormalizedInstruction
->preppedByRef
) {
1043 void emitUnboxR(IRGS
& env
) { implUnboxR(env
); }
1045 void emitFPassV(IRGS
& env
, int32_t argNum
) {
1046 if (env
.currentNormalizedInstruction
->preppedByRef
) {
1047 // FPassV is a no-op when the callee expects by ref.
1051 auto const tmp
= popV(env
);
1052 pushIncRef(env
, gen(env
, LdRef
, TInitCell
, tmp
));
1056 void emitFPassCE(IRGS
& env
, int32_t argNum
) {
1057 if (env
.currentNormalizedInstruction
->preppedByRef
) {
1058 // Need to raise an error
1059 PUNT(FPassCE
-byRef
);
1063 void emitFPassCW(IRGS
& env
, int32_t argNum
) {
1064 if (env
.currentNormalizedInstruction
->preppedByRef
) {
1065 // Need to raise a warning
1066 PUNT(FPassCW
-byRef
);
1070 //////////////////////////////////////////////////////////////////////
1072 void emitFCallArray(IRGS
& env
) {
1073 auto const data
= CallArrayData
{
1074 offsetFromIRSP(env
, BCSPOffset
{0}),
1078 callDestroysLocals(*env
.currentNormalizedInstruction
, curFunc(env
))
1080 gen(env
, CallArray
, data
, sp(env
), fp(env
));
1083 void emitFCallUnpack(IRGS
& env
, int32_t numParams
) {
1084 auto const data
= CallArrayData
{
1085 offsetFromIRSP(env
, BCSPOffset
{0}),
1089 callDestroysLocals(*env
.currentNormalizedInstruction
, curFunc(env
))
1091 gen(env
, CallArray
, data
, sp(env
), fp(env
));
1094 void emitFCallD(IRGS
& env
,
1097 const StringData
*) {
1098 emitFCall(env
, numParams
);
1101 void emitFCall(IRGS
& env
, int32_t numParams
) {
1102 auto const returnBcOffset
= nextBcOff(env
) - curFunc(env
)->base();
1103 auto const callee
= env
.currentNormalizedInstruction
->funcd
;
1105 auto const destroyLocals
= callee
1106 ? callee
->isCPPBuiltin() && builtinFuncDestroysLocals(callee
)
1107 : callDestroysLocals(
1108 *env
.currentNormalizedInstruction
,
1111 auto const needsCallerFrame
= callee
1112 ? callee
->isCPPBuiltin() && builtinFuncNeedsCallerFrame(callee
)
1113 : callNeedsCallerFrame(
1114 *env
.currentNormalizedInstruction
,
1118 auto op
= curFunc(env
)->unit()->getOp(bcOff(env
));
1124 offsetFromIRSP(env
, BCSPOffset
{0}),
1125 static_cast<uint32_t>(numParams
),
1130 op
== Op::FCallAwait
1137 void emitDirectCall(IRGS
& env
, Func
* callee
, int32_t numParams
,
1138 SSATmp
* const* const args
) {
1139 auto const returnBcOffset
= nextBcOff(env
) - curFunc(env
)->base();
1141 env
.irb
->fs().setFPushOverride(Op::FPushFuncD
);
1142 fpushActRec(env
, cns(env
, callee
), cns(env
, TNullptr
), numParams
, nullptr);
1143 assertx(!env
.irb
->fs().hasFPushOverride());
1145 for (int32_t i
= 0; i
< numParams
; i
++) {
1149 env
.irb
->exceptionStackBoundary();
1154 offsetFromIRSP(env
, BCSPOffset
{0}),
1155 static_cast<uint32_t>(numParams
),
1167 //////////////////////////////////////////////////////////////////////