Clean up irgen.h a bit
[hiphop-php.git] / hphp / runtime / vm / jit / irgen-call.cpp
blob628345703464dc17fd5cb72306fc3b81a9ef33e0
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
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 {
33 namespace {
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,
44 SSATmp* callable,
45 const Class* ctx,
46 const Class*& cls,
47 StringData*& invName,
48 bool& forward) {
49 cls = nullptr;
50 invName = nullptr;
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;
59 if (str) {
60 Func* f = Unit::lookupFunc(str);
61 if (f) return f;
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) {
66 return nullptr;
68 sclass = makeStaticString(name.substr(0, pos).get());
69 sname = makeStaticString(name.substr(pos + 2).get());
70 } else if (arr) {
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();
77 String name(sname);
78 if (name.find("::") != String::npos) return nullptr;
79 } else {
80 return nullptr;
83 if (sclass->isame(s_self.get())) {
84 if (!ctx) return nullptr;
85 cls = ctx;
86 forward = true;
87 } else if (sclass->isame(s_parent.get())) {
88 if (!ctx || !ctx->parent()) return nullptr;
89 cls = ctx->parent();
90 forward = true;
91 } else if (sclass->isame(s_static.get())) {
92 return nullptr;
93 } else {
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
108 * the lsb class
110 return nullptr;
112 if (magicCall) invName = sname;
113 return f;
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,
124 SSATmp* obj,
125 const StringData* methodName,
126 int32_t numParams,
127 bool shouldFatal) {
128 emitIncStat(env, Stats::ObjMethod_cached, 1);
129 fpushActRec(env,
130 cns(env, TNullptr), // Will be set by LdObjMethod
131 obj,
132 numParams,
133 nullptr);
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.
138 updateMarker(env);
139 env.irb->exceptionStackBoundary();
141 gen(env,
142 LdObjMethod,
143 LdObjMethodData {
144 offsetFromIRSP(env, BCSPOffset{0}), methodName, shouldFatal
146 objCls,
147 sp(env));
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,
157 const Class* iface,
158 const StringData* methodName,
159 Slot& vtableSlot,
160 const Func*& func) {
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) {
168 return true;
172 for (auto pface : iface->allInterfaces().range()) {
173 if (findInterfaceVtableSlot(env, pface, methodName, vtableSlot, func)) {
174 return true;
178 return false;
181 void fpushObjMethodExactFunc(
182 IRGS& env,
183 SSATmp* obj,
184 const Class* baseClass,
185 const Func* func,
186 const StringData* methodName,
187 int32_t numParams
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
192 * exists.
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()) {
206 assertx(baseClass);
207 decRef(env, obj);
208 objOrCls = cns(env, baseClass);
210 fpushActRec(
211 env,
212 cns(env, func),
213 objOrCls,
214 numParams,
215 methodName
219 const Func* lookupExactFuncForFPushObjMethod(
220 IRGS& env,
221 SSATmp* obj,
222 const Class* baseClass,
223 const StringData* methodName,
224 bool& magicCall
226 const Func* func = lookupImmutableMethod(
227 baseClass,
228 methodName,
229 magicCall,
230 /* staticLookup: */false,
231 curClass(env)
234 if (func) return func;
235 if (!baseClass || (baseClass->attrs() & AttrInterface)) {
236 return nullptr;
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.
245 return nullptr;
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.
253 assertx(!magicCall);
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.
264 return nullptr;
267 const Func* lookupInterfaceFuncForFPushObjMethod(
268 IRGS& env,
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;
277 Slot vtableSlot;
278 const Func* ifaceFunc;
279 if (findInterfaceVtableSlot(env, baseClass, methodName,
280 vtableSlot, ifaceFunc)) {
281 return ifaceFunc;
284 return nullptr;
287 void fpushObjMethodInterfaceFunc(
288 IRGS& env,
289 SSATmp* obj,
290 const Class* baseClass,
291 const StringData* methodName,
292 int32_t numParams
294 Slot vtableSlot;
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()},
302 cls);
303 SSATmp* objOrCls = obj;
304 if (ifaceFunc->attrs() & AttrStatic) {
305 decRef(env, obj);
306 objOrCls = cls;
308 fpushActRec(env, func, objOrCls, numParams, /* invName */nullptr);
309 return;
313 const Func* lookupNonExactFuncForFPushObjMethod(
314 IRGS& env,
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.
327 return nullptr;
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.
344 return func;
347 void fpushObjMethodNonExactFunc(
348 IRGS& env,
349 SSATmp* obj,
350 const Class* baseClass,
351 const Func* func,
352 int32_t numParams
354 emitIncStat(env, Stats::ObjMethod_methodslot, 1);
355 auto const clsTmp = gen(env, LdObjClass, obj);
356 auto const funcTmp = gen(
357 env,
358 LdClsMethod,
359 clsTmp,
360 cns(env, -(func->methodSlot() + 1))
362 SSATmp* objOrCls = obj;
363 if (func->attrs() & AttrStatic && !func->isClosureBody()) {
364 decRef(env, obj);
365 objOrCls = clsTmp;
367 fpushActRec(env,
368 funcTmp,
369 objOrCls,
370 numParams,
371 /* invName */nullptr
375 void fpushObjMethodWithBaseClass(
376 IRGS& env,
377 SSATmp* obj,
378 const Class* baseClass,
379 const StringData* methodName,
380 int32_t numParams,
381 bool shouldFatal,
382 bool isMonomorphic
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,
389 numParams);
390 return;
393 if (lookupInterfaceFuncForFPushObjMethod(env, baseClass, methodName,
394 isMonomorphic)) {
395 fpushObjMethodInterfaceFunc(env, obj, baseClass, methodName, numParams);
396 return;
399 if (auto const nonExactFunc = lookupNonExactFuncForFPushObjMethod(
400 env, baseClass, methodName)) {
401 fpushObjMethodNonExactFunc(env, obj, baseClass, nonExactFunc, numParams);
402 return;
405 fpushObjMethodUnknown(env, obj, methodName, numParams, shouldFatal);
408 static const StringData* classProfileKey = makeStaticString(
409 "ClassProfile-FPushObjMethod"
412 void fpushObjMethod(IRGS& env,
413 SSATmp* obj,
414 const StringData* methodName,
415 int32_t numParams,
416 bool shouldFatal,
417 Block* sideExit) {
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,
423 // use it.
424 fpushObjMethodWithBaseClass(env, obj, cls, methodName,
425 numParams, shouldFatal, false);
426 return;
430 TargetProfile<ClassProfile> profile(env.context, env.irb->curMarker(),
431 classProfileKey);
432 if (profile.profiling()) {
433 gen(env, ProfileObjClass, RDSHandleData { profile.handle() }, obj);
436 const bool shouldTryToOptimize = !env.transFlags.noProfiledFPush
437 || !env.firstBcInst;
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),
448 sideExit, obj);
449 env.irb->constrainValue(refinedObj, TypeConstraint(baseClass));
450 fpushObjMethodWithBaseClass(env, refinedObj, baseClass, methodName,
451 numParams, shouldFatal, true);
452 return;
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);
473 fpushActRec(
474 env,
475 cns(env, TNullptr),
476 cns(env, TNullptr),
477 numParams,
478 nullptr
481 // This is special. We need to move the stackpointer incase LdArrFuncCtx
482 // calls a destructor. Otherwise it would clobber the ActRec we just
483 // pushed.
484 updateMarker(env);
485 env.irb->exceptionStackBoundary();
487 gen(env, LdArrFuncCtx, IRSPOffsetData { offsetFromIRSP(env, BCSPOffset{0}) },
488 arr, sp(env), thisAR);
489 decRef(env, arr);
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);
505 fpushActRec(
506 env,
507 cns(env, TNullptr),
508 cns(env, TNullptr),
509 numParams,
510 nullptr
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.
520 updateMarker(env);
521 env.irb->exceptionStackBoundary();
523 auto const opcode = callable->isA(TArr) ? LdArrFPushCuf
524 : LdStrFPushCuf;
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) &&
535 curClass(env)) {
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);
549 if (mustBeStatic) {
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_);
558 return 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,
572 forward);
573 if (!callee) return fpushCufUnknown(env, op, numArgs);
575 SSATmp* ctx;
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);
579 if (cls) {
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);
589 if (forward) {
590 ctx = ldCtx(env);
591 ctx = gen(env, GetCtxFwdCall, ctx, cns(env, callee));
592 } else {
593 ctx = clsMethodCtx(env, callee, cls);
595 } else {
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
606 if (safe) {
607 push(env, defaultVal);
608 push(env, safeFlag);
611 fpushActRec(env, func, ctx, numArgs, invName);
614 void fpushFuncCommon(IRGS& env,
615 int32_t numParams,
616 const StringData* name,
617 const StringData* fallback) {
618 if (auto const func = Unit::lookupFunc(name)) {
619 if (func->isNameBindingImmutable(curUnit(env))) {
620 fpushActRec(env,
621 cns(env, func),
622 cns(env, TNullptr),
623 numParams,
624 nullptr);
625 return;
629 auto const ssaFunc = fallback
630 ? gen(env, LdFuncCachedU, LdFuncCachedUData { name, fallback })
631 : gen(env, LdFuncCached, LdFuncCachedData { name });
632 fpushActRec(env,
633 ssaFunc,
634 cns(env, TNullptr),
635 numParams,
636 nullptr);
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
645 push(env, unboxed);
646 } else {
647 pushIncRef(env, unboxed);
648 decRef(env, srcBox);
652 //////////////////////////////////////////////////////////////////////
656 //////////////////////////////////////////////////////////////////////
658 void fpushActRec(IRGS& env,
659 SSATmp* func,
660 SSATmp* objOrClass,
661 int32_t numArgs,
662 const StringData* invName) {
663 ActRecInfo info;
664 info.spOffset = offsetFromIRSP(env, BCSPOffset{-int32_t{kNumActRecCells}});
665 info.invName = invName;
666 info.numArgs = numArgs;
668 gen(
669 env,
670 SpillFrame,
671 info,
672 sp(env),
673 func,
674 objOrClass
678 //////////////////////////////////////////////////////////////////////
680 void emitFPushCufIter(IRGS& env, int32_t numParams, int32_t itId) {
681 gen(
682 env,
683 CufIterSpillFrame,
684 FPushCufData {
685 offsetFromIRSP(env, BCSPOffset{-int32_t{kNumActRecCells}}),
686 static_cast<uint32_t>(numParams),
687 itId
689 sp(env),
690 fp(env)
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,
713 int32_t numParams,
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 =
720 persistentCls &&
721 canInstantiate &&
722 !cls->callsCustomInstanceInit() &&
723 !cls->hasNativePropHandler();
725 auto const func = lookupImmutableCtor(cls, curClass(env));
727 auto ssaCls = persistentCls
728 ? cns(env, cls)
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
733 // defined.
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,
750 int32_t nargs,
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);
765 fpushActRec(env,
766 gen(env, LdFunc, funcName),
767 cns(env, TNullptr),
768 numParams,
769 nullptr);
772 void emitFPushObjMethodD(IRGS& env,
773 int32_t numParams,
774 const StringData* methodName,
775 ObjMethodOp subop) {
776 TransFlags trFlags;
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);
785 return;
788 if (obj->type() <= TInitNull && subop == ObjMethodOp::NullSafe) {
789 fpushActRec(
790 env,
791 cns(env, SystemLib::s_nullFunc),
792 cns(env, TNullptr),
793 numParams,
794 nullptr);
795 return;
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());
806 ifThen(env,
807 [&] (Block* notLoaded) {
808 auto const clsOrNull = gen(env, LdClsCachedSafe, clsName);
809 gen(env, CheckNonNull, notLoaded, clsOrNull);
811 [&] {
812 hint(env, Block::Hint::Unlikely);
813 gen(env, LdClsCached, clsName);
818 void emitFPushClsMethodD(IRGS& env,
819 int32_t numParams,
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,
826 methodName,
827 magicCall,
828 true /* staticLookup */,
829 curClass(env))) {
830 checkImmutableClsMethod(env, func);
831 auto const objOrCls = clsMethodCtx(env, func, baseClass);
832 fpushActRec(env,
833 cns(env, func),
834 objOrCls,
835 numParams,
836 func && magicCall ? methodName : nullptr);
837 return;
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(
847 env,
848 [&] (Block* taken) {
849 auto const mcFunc = gen(env, LdClsMethodCacheFunc, data);
850 return gen(env, CheckNonNull, taken, mcFunc);
852 [&] (SSATmp* func) { // next
853 return func;
855 [&] { // taken
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);
863 fpushActRec(env,
864 func,
865 clsCtx,
866 numParams,
867 nullptr);
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.
892 cls = curClass(env);
895 if (cls) {
896 const Func* func;
897 auto res =
898 g_context->lookupClsMethod(func,
899 cls,
900 methVal->strVal(),
901 nullptr,
902 cls,
903 false);
904 if (res == LookupResult::MethodFoundNoThis && func->isStatic()) {
905 auto funcTmp = clsVal->hasConstVal()
906 ? cns(env, func)
907 : gen(env, LdClsMethod, clsVal, cns(env, -(func->methodSlot() + 1)));
908 fpushActRec(env, funcTmp, clsVal, numParams, nullptr);
909 return;
914 fpushActRec(env,
915 cns(env, TNullptr),
916 cns(env, TNullptr),
917 numParams,
918 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.
924 updateMarker(env);
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,
949 methName,
950 magicCall,
951 true /* staticLookup */,
952 curClass(env));
953 discard(env, 2);
955 auto const curCtxTmp = ldCtx(env);
956 if (vmfunc) {
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);
962 return;
965 auto const data = ClsMethodData{cls->name(), methName};
966 auto const funcTmp = cond(
967 env,
968 [&](Block* taken) {
969 auto const fcacheFunc = gen(env, LdClsMethodFCacheFunc, data);
970 return gen(env, CheckNonNull, taken, fcacheFunc);
972 [&](SSATmp* func) { // next
973 return func;
975 [&] { // taken
976 hint(env, Block::Hint::Unlikely);
977 auto const result = gen(
978 env,
979 LookupClsMethodFCache,
980 data,
981 cns(env, cls),
982 fp(env)
984 return gen(env, CheckNonNull, exitBlock, result);
988 auto const ctx = gen(env, GetCtxFwdCallDyn, data, curCtxTmp);
989 fpushActRec(env,
990 funcTmp,
991 ctx,
992 numParams,
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) {
1013 emitVGetL(env, id);
1014 } else {
1015 emitCGetL(env, id);
1019 void emitFPassS(IRGS& env, int32_t argNum) {
1020 if (env.currentNormalizedInstruction->preppedByRef) {
1021 emitVGetS(env);
1022 } else {
1023 emitCGetS(env);
1027 void emitFPassG(IRGS& env, int32_t argNum) {
1028 if (env.currentNormalizedInstruction->preppedByRef) {
1029 emitVGetG(env);
1030 } else {
1031 emitCGetG(env);
1035 void emitFPassR(IRGS& env, int32_t argNum) {
1036 if (env.currentNormalizedInstruction->preppedByRef) {
1037 PUNT(FPassR-byRef);
1040 implUnboxR(env);
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.
1048 return;
1051 auto const tmp = popV(env);
1052 pushIncRef(env, gen(env, LdRef, TInitCell, tmp));
1053 decRef(env, 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}),
1076 bcOff(env),
1077 nextBcOff(env),
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}),
1086 numParams,
1087 bcOff(env),
1088 nextBcOff(env),
1089 callDestroysLocals(*env.currentNormalizedInstruction, curFunc(env))
1091 gen(env, CallArray, data, sp(env), fp(env));
1094 void emitFCallD(IRGS& env,
1095 int32_t numParams,
1096 const StringData*,
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,
1109 curFunc(env)
1111 auto const needsCallerFrame = callee
1112 ? callee->isCPPBuiltin() && builtinFuncNeedsCallerFrame(callee)
1113 : callNeedsCallerFrame(
1114 *env.currentNormalizedInstruction,
1115 curFunc(env)
1118 auto op = curFunc(env)->unit()->getOp(bcOff(env));
1120 gen(
1121 env,
1122 Call,
1123 CallData {
1124 offsetFromIRSP(env, BCSPOffset{0}),
1125 static_cast<uint32_t>(numParams),
1126 returnBcOffset,
1127 callee,
1128 destroyLocals,
1129 needsCallerFrame,
1130 op == Op::FCallAwait
1132 sp(env),
1133 fp(env)
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++) {
1146 push(env, args[i]);
1148 updateMarker(env);
1149 env.irb->exceptionStackBoundary();
1150 gen(
1151 env,
1152 Call,
1153 CallData {
1154 offsetFromIRSP(env, BCSPOffset{0}),
1155 static_cast<uint32_t>(numParams),
1156 returnBcOffset,
1157 callee,
1158 false,
1159 false,
1160 false
1162 sp(env),
1163 fp(env)
1167 //////////////////////////////////////////////////////////////////////