2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present 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-builtin.h"
18 #include "hphp/runtime/base/array-init.h"
19 #include "hphp/runtime/base/collections.h"
20 #include "hphp/runtime/base/externals.h"
21 #include "hphp/runtime/base/file-util.h"
22 #include "hphp/runtime/base/tv-refcount.h"
23 #include "hphp/runtime/vm/repo.h"
24 #include "hphp/runtime/vm/repo-global-data.h"
25 #include "hphp/runtime/vm/vm-regs.h"
27 #include "hphp/runtime/vm/jit/analysis.h"
28 #include "hphp/runtime/vm/jit/guard-constraint.h"
29 #include "hphp/runtime/vm/jit/type.h"
30 #include "hphp/runtime/vm/jit/vm-protect.h"
32 #include "hphp/runtime/vm/jit/irgen-call.h"
33 #include "hphp/runtime/vm/jit/irgen-control.h"
34 #include "hphp/runtime/vm/jit/irgen-exit.h"
35 #include "hphp/runtime/vm/jit/irgen-inlining.h"
36 #include "hphp/runtime/vm/jit/irgen-internal.h"
37 #include "hphp/runtime/vm/jit/irgen-interpone.h"
38 #include "hphp/runtime/vm/jit/irgen-minstr.h"
39 #include "hphp/runtime/vm/jit/irgen-ret.h"
40 #include "hphp/runtime/vm/jit/irgen-types.h"
42 #include "hphp/runtime/ext/collections/ext_collections-map.h"
43 #include "hphp/runtime/ext/collections/ext_collections-set.h"
44 #include "hphp/runtime/ext/collections/ext_collections-vector.h"
45 #include "hphp/runtime/ext/hh/ext_hh.h"
47 #include "hphp/util/text-util.h"
49 namespace HPHP
{ namespace jit
{ namespace irgen
{
53 //////////////////////////////////////////////////////////////////////
56 explicit ParamPrep(size_t count
) : info(count
) {}
59 SSATmp
* value
{nullptr};
60 bool passByAddr
{false};
61 bool needsConversion
{false};
62 bool isOutputArg
{false};
65 const Info
& operator[](size_t idx
) const { return info
[idx
]; }
66 Info
& operator[](size_t idx
) { return info
[idx
]; }
67 size_t size() const { return info
.size(); }
69 SSATmp
* thiz
{nullptr}; // may be null if call is not a method
70 SSATmp
* count
{nullptr}; // if non-null, the count of arguments
71 jit::vector
<Info
> info
;
72 uint32_t numByAddr
{0};
74 bool forNativeImpl
{false};
77 //////////////////////////////////////////////////////////////////////
81 s_is_subclass_of("is_subclass_of"),
82 s_method_exists("method_exists"),
86 s_in_array("in_array"),
87 s_get_class("get_class"),
90 s_clock_gettime_ns("clock_gettime_ns"),
91 s_microtime("microtime"),
92 s_max2("__SystemLib\\max2"),
93 s_min2("__SystemLib\\min2"),
99 s_array_key_cast("hh\\array_key_cast"),
100 s_type_structure("hh\\type_structure"),
101 s_is_list_like("hh\\is_list_like"),
104 s_container_first("HH\\Lib\\_Private\\Native\\first"),
105 s_container_last("HH\\Lib\\_Private\\Native\\last"),
106 s_container_first_key("HH\\Lib\\_Private\\Native\\first_key"),
107 s_container_last_key("HH\\Lib\\_Private\\Native\\last_key"),
108 s_class_meth_get_class("HH\\class_meth_get_class"),
109 s_class_meth_get_method("HH\\class_meth_get_method");
111 //////////////////////////////////////////////////////////////////////
113 // Will turn into either an int or a double in zend_convert_scalar_to_number.
114 bool type_converts_to_number(Type ty
) {
115 return ty
.subtypeOfAny(
126 //////////////////////////////////////////////////////////////////////
128 Block
* make_opt_catch(IRGS
& env
, const ParamPrep
& params
) {
129 // The params have been popped and if we're inlining the ActRec is gone
130 env
.irb
->setCurMarker(makeMarker(env
, nextBcOff(env
)));
131 env
.irb
->exceptionStackBoundary();
133 auto const exit
= defBlock(env
, Block::Hint::Unlikely
);
134 BlockPusher
bp(*env
.irb
, makeMarker(env
, nextBcOff(env
)), exit
);
135 gen(env
, BeginCatch
);
136 for (auto i
= params
.size(); i
--; ) {
137 decRef(env
, params
[i
].value
);
140 IRSPRelOffsetData
{ spOffBCFromIRSP(env
) },
145 SSATmp
* is_a_impl(IRGS
& env
, const ParamPrep
& params
, bool subclassOnly
) {
146 if (params
.size() != 3) return nullptr;
148 auto const allowString
= params
[2].value
;
149 auto const classname
= params
[1].value
;
150 auto const obj
= params
[0].value
;
152 if (!obj
->isA(TObj
) ||
153 !classname
->hasConstVal(TStr
) ||
154 !allowString
->isA(TBool
)) {
158 auto const objCls
= gen(env
, LdObjClass
, obj
);
160 auto const cls
= Unit::lookupUniqueClassInContext(classname
->strVal(),
162 if (!cls
) return nullptr;
164 auto const testCls
= cns(env
, cls
);
166 // is_a() finishes here.
167 if (!subclassOnly
) return gen(env
, InstanceOf
, objCls
, testCls
);
169 // is_subclass_of() needs to check that the LHS doesn't have the same class as
174 auto const eq
= gen(env
, EqCls
, objCls
, testCls
);
175 gen(env
, JmpNZero
, taken
, eq
);
178 return gen(env
, InstanceOf
, objCls
, testCls
);
181 return cns(env
, false);
186 SSATmp
* opt_is_a(IRGS
& env
, const ParamPrep
& params
) {
187 return is_a_impl(env
, params
, false /* subclassOnly */);
190 SSATmp
* opt_is_subclass_of(IRGS
& env
, const ParamPrep
& params
) {
191 return is_a_impl(env
, params
, true /* subclassOnly */);
194 SSATmp
* opt_method_exists(IRGS
& env
, const ParamPrep
& params
) {
195 if (params
.size() != 2) return nullptr;
197 auto const meth
= params
[1].value
;
198 auto const obj
= params
[0].value
;
200 if (!obj
->isA(TObj
) || !meth
->isA(TStr
)) return nullptr;
202 auto const cls
= gen(env
, LdObjClass
, obj
);
203 return gen(env
, MethodExists
, cls
, meth
);
206 SSATmp
* opt_count(IRGS
& env
, const ParamPrep
& params
) {
207 if (params
.size() != 2) return nullptr;
209 auto const mode
= params
[1].value
;
210 auto const val
= params
[0].value
;
212 if (val
->isA(TClsMeth
)) return cns(env
, 2);
214 // Bail if we're trying to do a recursive count()
215 if (!mode
->hasConstVal(0)) return nullptr;
218 return gen(env
, Count
, make_opt_catch(env
, params
), val
);
221 SSATmp
* opt_sizeof(IRGS
& env
, const ParamPrep
& params
) {
222 return opt_count(env
, params
);
225 SSATmp
* opt_ord(IRGS
& env
, const ParamPrep
& params
) {
226 if (params
.size() != 1) return nullptr;
228 auto const arg
= params
[0].value
;
229 auto const arg_type
= arg
->type();
230 if (arg_type
<= TStr
) {
231 return gen(env
, OrdStr
, arg
);
237 SSATmp
* opt_chr(IRGS
& env
, const ParamPrep
& params
) {
238 if (params
.size() != 1) return nullptr;
240 auto const arg
= params
[0].value
;
241 auto const arg_type
= arg
->type();
242 if (arg_type
<= TInt
) {
243 return gen(env
, ChrInt
, arg
);
249 SSATmp
* opt_ini_get(IRGS
& env
, const ParamPrep
& params
) {
250 if (params
.size() != 1) return nullptr;
252 // Only generate the optimized version if the argument passed in is a
253 // static string with a constant literal value so we can get the string value
255 auto const argType
= params
[0].value
->type();
256 if (!(argType
.hasConstVal(TStaticStr
))) {
260 // We can only optimize settings that are system wide since user level
261 // settings can be overridden during the execution of a request.
263 // TODO: the above is true for settings whose value we burn directly into the
264 // TC, but for non-system settings, we can optimize them as a load from the
265 // known static address or thread-local address of where the setting lives.
266 // This might be worth doing specifically for the zend.assertions setting,
267 // for which the emitter emits an ini_get around every call to assertx().
268 auto const settingName
= params
[0].value
->strVal()->toCppString();
269 IniSetting::Mode mode
= IniSetting::PHP_INI_NONE
;
270 if (!IniSetting::GetMode(settingName
, mode
)) {
273 if (mode
& ~IniSetting::PHP_INI_SYSTEM
) {
276 if (mode
== IniSetting::PHP_INI_ALL
) { /* PHP_INI_ALL has a weird encoding */
281 IniSetting::Get(settingName
, value
);
282 // All scalar values are cast to a string before being returned.
283 if (value
.isString()) {
284 return cns(env
, makeStaticString(value
.toString()));
286 if (value
.isInteger()) {
287 return cns(env
, makeStaticString(folly::to
<std::string
>(value
.toInt64())));
289 if (value
.isBoolean()) {
292 value
.toBoolean() ? s_one
.get() : s_empty
.get()
295 // ini_get() is now enhanced to return more than strings.
296 // Get out of here if we are something else like an array.
301 * Transforms in_array with a static haystack argument into an AKExistsKeyset.
303 SSATmp
* opt_in_array(IRGS
& env
, const ParamPrep
& params
) {
304 if (params
.size() != 3 && params
.size() != 2) return nullptr;
306 // We will restrict this optimization to needles that are strings, and
307 // haystacks that have only non-numeric string keys. This avoids a bunch of
308 // complication around numeric-string array-index semantics.
309 auto const needle
= params
[0].value
;
310 if (!(needle
->type() <= TStr
)) {
314 auto const haystackType
= params
[1].value
->type();
315 if (!haystackType
.hasConstVal(TStaticArr
)) {
316 // Haystack isn't statically known
320 auto const haystack
= haystackType
.arrVal();
321 if (haystack
->size() == 0) {
322 return cns(env
, false);
325 KeysetInit flipped
{haystack
->size()};
329 [&](TypedValue key
) {
331 if (!isStringType(type(key
)) || val(key
).pstr
->isNumeric()) {
332 // Numeric strings will complicate matters because the loose comparisons
333 // done with array keys are not quite the same as loose comparisons done
334 // by in_array. For example: in_array('0', array('0000')) is true, but
335 // doing array('0000' => true)['0'] will say "undefined index".
336 // This seems unlikely to affect real-world usage.
341 flipped
.add(val(key
).pstr
);
352 cns(env
, ArrayData::GetScalarArray(flipped
.toArray())),
357 SSATmp
* opt_get_class(IRGS
& env
, const ParamPrep
& params
) {
358 auto const curCls
= !params
.forNativeImpl
? curClass(env
) : nullptr;
359 auto const curName
= [&] {
360 return curCls
!= nullptr ? cns(env
, curCls
->name()) : nullptr;
362 if (params
.size() == 0 && RuntimeOption::EvalGetClassBadArgument
== 0) {
365 if (params
.size() != 1) return nullptr;
367 auto const val
= params
[0].value
;
368 auto const ty
= val
->type();
369 if (ty
<= TNull
&& RuntimeOption::EvalGetClassBadArgument
== 0) {
373 auto const cls
= gen(env
, LdObjClass
, val
);
374 return gen(env
, LdClsName
, cls
);
380 SSATmp
* opt_sqrt(IRGS
& env
, const ParamPrep
& params
) {
381 if (params
.size() != 1) return nullptr;
383 auto const val
= params
[0].value
;
384 auto const ty
= val
->type();
385 if (ty
<= TDbl
) return gen(env
, Sqrt
, val
);
389 SSATmp
* opt_strlen(IRGS
& env
, const ParamPrep
& params
) {
390 if (params
.size() != 1) return nullptr;
392 auto const val
= params
[0].value
;
393 auto const ty
= val
->type();
396 return gen(env
, LdStrLen
, val
);
402 SSATmp
* opt_clock_gettime_ns(IRGS
& env
, const ParamPrep
& params
) {
403 if (params
.size() != 1) return nullptr;
405 auto const val
= params
[0].value
;
407 // CLOCK_THREAD_CPUTIME_ID needs special handling
408 if (val
->hasConstVal(TInt
) && val
->intVal() != CLOCK_THREAD_CPUTIME_ID
) {
409 return gen(env
, GetTimeNs
, val
);
415 SSATmp
* opt_microtime(IRGS
& env
, const ParamPrep
& params
) {
416 if (params
.size() != 1) return nullptr;
418 auto const val
= params
[0].value
;
420 if (val
->hasConstVal(true)) {
421 return gen(env
, GetTime
);
427 SSATmp
* minmax(IRGS
& env
, const ParamPrep
& params
, const bool is_max
) {
428 auto const val1
= params
[1].value
;
429 auto const ty1
= val1
->type();
430 auto const val2
= params
[0].value
;
431 auto const ty2
= val2
->type();
433 // this optimization is only for 2 ints/doubles
434 if (!(ty1
<= TInt
|| ty1
<= TDbl
) ||
435 !(ty2
<= TInt
|| ty2
<= TDbl
)) return nullptr;
437 auto const cmp
= [&]{
438 if (ty1
<= TInt
&& ty2
<= TInt
) {
439 return gen(env
, is_max
? GtInt
: LtInt
, val1
, val2
);
441 auto conv1
= (ty1
<= TDbl
) ? val1
: gen(env
, ConvIntToDbl
, val1
);
442 auto conv2
= (ty2
<= TDbl
) ? val2
: gen(env
, ConvIntToDbl
, val2
);
443 return gen(env
, is_max
? GtDbl
: LtDbl
, conv1
, conv2
);
446 return gen(env
, Select
, cmp
, val1
, val2
);
449 SSATmp
* opt_max2(IRGS
& env
, const ParamPrep
& params
) {
450 // max2 is only called for 2 operands
451 return params
.size() == 2 ? minmax(env
, params
, true) : nullptr;
454 SSATmp
* opt_min2(IRGS
& env
, const ParamPrep
& params
) {
455 // min2 is only called for 2 operands
456 return params
.size() == 2 ? minmax(env
, params
, false) : nullptr;
459 SSATmp
* opt_ceil(IRGS
& env
, const ParamPrep
& params
) {
460 if (params
.size() != 1) return nullptr;
461 if (!folly::CpuId().sse41()) return nullptr;
462 auto const val
= params
[0].value
;
463 if (!type_converts_to_number(val
->type())) return nullptr;
465 auto const dbl
= gen(env
, ConvCellToDbl
, make_opt_catch(env
, params
), val
);
466 return gen(env
, Ceil
, dbl
);
469 SSATmp
* opt_floor(IRGS
& env
, const ParamPrep
& params
) {
470 if (params
.size() != 1) return nullptr;
471 if (!folly::CpuId().sse41()) return nullptr;
472 auto const val
= params
[0].value
;
473 if (!type_converts_to_number(val
->type())) return nullptr;
475 auto const dbl
= gen(env
, ConvCellToDbl
, make_opt_catch(env
, params
), val
);
476 return gen(env
, Floor
, dbl
);
479 SSATmp
* opt_abs(IRGS
& env
, const ParamPrep
& params
) {
480 if (params
.size() != 1) return nullptr;
482 auto const value
= params
[0].value
;
483 if (value
->type() <= TInt
) {
484 // compute integer absolute value ((src>>63) ^ src) - (src>>63)
485 auto const t1
= gen(env
, Shr
, value
, cns(env
, 63));
486 auto const t2
= gen(env
, XorInt
, t1
, value
);
487 return gen(env
, SubInt
, t2
, t1
);
490 if (value
->type() <= TDbl
) return gen(env
, AbsDbl
, value
);
491 if (value
->type() <= TArrLike
) return cns(env
, false);
496 SSATmp
* opt_array_key_cast(IRGS
& env
, const ParamPrep
& params
) {
497 if (params
.size() != 1) return nullptr;
498 auto const value
= params
[0].value
;
500 env
.irb
->constrainValue(value
, DataTypeSpecific
);
502 if (value
->isA(TInt
)) return value
;
503 if (value
->isA(TNull
)) return cns(env
, staticEmptyString());
504 if (value
->isA(TBool
)) return gen(env
, ConvBoolToInt
, value
);
505 if (value
->isA(TDbl
)) return gen(env
, ConvDblToInt
, value
);
506 if (value
->isA(TRes
)) return gen(env
, ConvResToInt
, value
);
507 if (value
->isA(TStr
)) return gen(env
, StrictlyIntegerConv
, value
);
512 SSATmp
* opt_type_structure(IRGS
& env
, const ParamPrep
& params
) {
513 if (params
.size() != 2) return nullptr;
514 auto const clsNameTmp
= params
[0].value
;
515 auto const cnsNameTmp
= params
[1].value
;
517 if (!clsNameTmp
->isA(TStr
)) return nullptr;
518 if (!cnsNameTmp
->hasConstVal(TStaticStr
)) return nullptr;
519 auto const cnsName
= cnsNameTmp
->strVal();
521 auto const clsTmp
= [&] () -> SSATmp
* {
522 if (clsNameTmp
->inst()->is(LdClsName
)) {
523 return clsNameTmp
->inst()->src(0);
525 return ldCls(env
, clsNameTmp
, make_opt_catch(env
, params
));
528 if (!clsTmp
->type().clsSpec()) return nullptr;
529 auto const cls
= clsTmp
->type().clsSpec().cls();
531 auto const cnsSlot
= cls
->clsCnsSlot(cnsName
, true, true);
532 if (cnsSlot
== kInvalidSlot
) return nullptr;
534 auto const data
= LdSubClsCnsData
{ cnsName
, cnsSlot
};
535 auto const ptr
= gen(env
, LdSubClsCns
, data
, clsTmp
);
539 gen(env
, CheckTypeMem
, TUncountedInit
, taken
, ptr
);
540 return gen(env
, LdTypeCns
, taken
, gen(env
, LdMem
, TUncountedInit
, ptr
));
542 [&] (SSATmp
* cns
) { return cns
; },
545 env
, LdClsTypeCns
, make_opt_catch(env
, params
), clsTmp
, cnsNameTmp
551 SSATmp
* opt_is_list_like(IRGS
& env
, const ParamPrep
& params
) {
552 if (params
.size() != 1) return nullptr;
553 auto const type
= params
[0].value
->type();
554 // Type might be a Ptr here, so the maybe() below will go wrong if we don't
556 if (!(type
<= TInitCell
)) return nullptr;
557 if (type
<= TClsMeth
) return cns(env
, true);
558 if (!type
.maybe(TArrLike
)) return cns(env
, false);
559 if (type
<= TVec
|| type
<= Type::Array(ArrayData::kPackedKind
)) {
560 return cns(env
, true);
566 SSATmp
* opt_foldable(IRGS
& env
,
568 const ParamPrep
& params
,
569 uint32_t numNonDefaultArgs
) {
570 if (!func
->isFoldable()) return nullptr;
572 const Class
* cls
= nullptr;
573 if (func
->isMethod()) {
574 if (!params
.thiz
|| !func
->isStatic()) return nullptr;
575 cls
= params
.thiz
->type().clsSpec().exactCls();
576 if (!cls
) return nullptr;
579 ArrayData
* variadicArgs
= nullptr;
580 uint32_t numVariadicArgs
= 0;
581 if (numNonDefaultArgs
> func
->numNonVariadicParams()) {
582 assertx(params
.size() == func
->numParams());
583 auto const variadic
= params
.info
.back().value
;
584 auto const ty
= RuntimeOption::EvalHackArrDVArrs
? TVec
: TArr
;
585 if (!variadic
->type().hasConstVal(ty
)) return nullptr;
587 variadicArgs
= variadic
->variantVal().asCArrRef().get();
588 numVariadicArgs
= variadicArgs
->size();
590 if (numVariadicArgs
&& !variadicArgs
->isVecOrVArray()) return nullptr;
592 assertx(variadicArgs
->isStatic());
593 numNonDefaultArgs
= func
->numNonVariadicParams();
596 // Don't pop the args yet---if the builtin throws at compile time (because
597 // it would raise a warning or something at runtime) we're going to leave
599 VArrayInit
args(numNonDefaultArgs
+ numVariadicArgs
);
600 for (auto i
= 0; i
< numNonDefaultArgs
; ++i
) {
601 auto const t
= params
[i
].value
->type();
602 if (!t
.hasConstVal() && !t
.subtypeOfAny(TUninit
, TInitNull
, TNullptr
)) {
605 args
.append(params
[i
].value
->variantVal());
609 for (auto i
= 0; i
< numVariadicArgs
; i
++) {
610 args
.append(variadicArgs
->get(i
).tv());
615 // We don't know if notices would be enabled or not when this function
616 // would normally get called, so be safe and don't optimize any calls that
617 // COULD generate notices.
618 ThrowAllErrorsSetter taes
;
620 VMProtect::Pause deprot
;
621 always_assert(tl_regState
== VMRegState::CLEAN
);
623 // Even though tl_regState is marked clean, vmpc() has not necessarily been
624 // set to anything valid, so we need to do so here (for assertions and
625 // backtraces in the invocation, among other things).
626 auto const savedPC
= vmpc();
627 vmpc() = vmfp() ? vmfp()->m_func
->getEntry() : nullptr;
628 SCOPE_EXIT
{ vmpc() = savedPC
; };
630 assertx(!RID().getJitFolding());
631 RID().setJitFolding(true);
632 SCOPE_EXIT
{ RID().setJitFolding(false); };
634 auto retVal
= g_context
->invokeFunc(func
, args
.toArray(),
635 nullptr, const_cast<Class
*>(cls
),
637 ExecutionContext::InvokeNormal
,
638 !func
->unit()->useStrictTypes(),
640 SCOPE_EXIT
{ tvDecRefGen(retVal
); };
641 assertx(tvIsPlausible(retVal
));
643 auto scalar_array
= [&] {
644 return ArrayData::GetScalarArray(std::move(tvAsVariant(&retVal
)));
647 switch (retVal
.m_type
) {
652 return cns(env
, retVal
);
653 case KindOfPersistentString
:
655 return cns(env
, makeStaticString(retVal
.m_data
.pstr
));
656 case KindOfPersistentVec
:
660 make_tv
<KindOfPersistentVec
>(scalar_array())
662 case KindOfPersistentDict
:
666 make_tv
<KindOfPersistentDict
>(scalar_array())
668 case KindOfPersistentKeyset
:
672 make_tv
<KindOfPersistentKeyset
>(scalar_array())
674 case KindOfPersistentShape
:
678 make_tv
<KindOfPersistentShape
>(scalar_array())
680 case KindOfPersistentArray
:
684 make_tv
<KindOfPersistentArray
>(scalar_array())
694 case KindOfRecord
: // TODO(arnabde)
698 // If an exception or notice occurred, don't optimize
704 * Container intrinsic for HH\traversable
706 SSATmp
* opt_container_first(IRGS
& env
, const ParamPrep
& params
) {
707 if (params
.size() != 1) {
710 auto const value
= params
[0].value
;
711 auto const type
= value
->type();
712 if (type
<= TVec
|| type
<= Type::Array(ArrayData::kPackedKind
)) {
713 auto const r
= gen(env
, VecFirst
, value
);
717 if (type
<= TDict
|| type
<= Type::Array(ArrayData::kMixedKind
)) {
718 auto const r
= gen(env
, DictFirst
, value
);
722 if (type
<= TKeyset
) {
723 auto const r
= gen(env
, KeysetFirst
, value
);
730 SSATmp
* opt_container_last(IRGS
& env
, const ParamPrep
& params
) {
731 if (params
.size() != 1) {
734 auto const value
= params
[0].value
;
735 auto const type
= value
->type();
736 if (type
<= TVec
|| type
<= Type::Array(ArrayData::kPackedKind
)) {
737 auto const r
= gen(env
, VecLast
, value
);
741 if (type
<= TDict
|| type
<= Type::Array(ArrayData::kMixedKind
)) {
742 auto const r
= gen(env
, DictLast
, value
);
746 if (type
<= TKeyset
) {
747 auto const r
= gen(env
, KeysetLast
, value
);
754 SSATmp
* opt_container_first_key(IRGS
& env
, const ParamPrep
& params
) {
755 if (params
.size() != 1) {
758 auto const value
= params
[0].value
;
759 auto const type
= value
->type();
761 if (type
<= TVec
|| type
<= Type::Array(ArrayData::kPackedKind
)) {
765 auto const length
= type
<= TVec
?
766 gen(env
, CountVec
, value
) : gen(env
, CountArray
, value
);
767 gen(env
, JmpZero
, taken
, length
);
773 return cns(env
, TInitNull
);
777 if (type
<= TDict
|| type
<= Type::Array(ArrayData::kMixedKind
)) {
778 auto const r
= gen(env
, DictFirstKey
, value
);
782 if (type
<= TKeyset
) {
783 auto const r
= gen(env
, KeysetFirst
, value
);
790 SSATmp
* opt_container_last_key(IRGS
& env
, const ParamPrep
& params
) {
791 if (params
.size() != 1) {
794 auto const value
= params
[0].value
;
795 auto const type
= value
->type();
797 if (type
<= TVec
|| type
<= Type::Array(ArrayData::kPackedKind
)) {
801 auto const length
= type
<= TVec
?
802 gen(env
, CountVec
, value
) : gen(env
, CountArray
, value
);
803 gen(env
, JmpZero
, taken
, length
);
807 return gen(env
, SubInt
, next
, cns(env
, 1));
810 return cns(env
, TInitNull
);
814 if (type
<= TDict
|| type
<= Type::Array(ArrayData::kMixedKind
)) {
815 auto const r
= gen(env
, DictLastKey
, value
);
819 if (type
<= TKeyset
) {
820 auto const r
= gen(env
, KeysetLast
, value
);
828 const StaticString
s_CLASS_CONVERSION("Class to string conversion");
829 const StaticString
s_FUNC_CONVERSION("Func to string conversion");
832 SSATmp
* opt_class_meth_get_class(IRGS
& env
, const ParamPrep
& params
) {
833 if (params
.size() != 1) return nullptr;
834 auto const value
= params
[0].value
;
835 auto const type
= value
->type();
836 if (type
<= TClsMeth
) {
837 if (RuntimeOption::EvalRaiseClassConversionWarning
) {
838 gen(env
, RaiseNotice
, cns(env
, s_CLASS_CONVERSION
.get()));
840 return gen(env
, LdClsName
, gen(env
, LdClsFromClsMeth
, value
));
845 SSATmp
* opt_class_meth_get_method(IRGS
& env
, const ParamPrep
& params
) {
846 if (params
.size() != 1) return nullptr;
847 auto const value
= params
[0].value
;
848 auto const type
= value
->type();
849 if (type
<= TClsMeth
) {
850 if (RuntimeOption::EvalRaiseFuncConversionWarning
) {
851 gen(env
, RaiseNotice
, cns(env
, s_FUNC_CONVERSION
.get()));
853 return gen(env
, LdFuncName
, gen(env
, LdFuncFromClsMeth
, value
));
858 //////////////////////////////////////////////////////////////////////
860 SSATmp
* optimizedFCallBuiltin(IRGS
& env
,
862 const ParamPrep
& params
,
863 uint32_t numNonDefault
) {
864 auto const result
= [&]() -> SSATmp
* {
866 auto const fname
= func
->fullName();
868 if (auto const retVal
= opt_foldable(env
, func
, params
, numNonDefault
)) {
872 if (fname->isame(s_##x.get())) return opt_##x(env, params);
898 X(container_first_key
)
899 X(container_last_key
)
900 X(class_meth_get_class
)
901 X(class_meth_get_method
)
908 if (result
== nullptr) return nullptr;
910 // NativeImpl will do a RetC
911 if (!params
.forNativeImpl
) {
912 if (params
.thiz
&& params
.thiz
->type() <= TObj
) {
913 decRef(env
, params
.thiz
);
916 // Decref and free args
917 for (int i
= numNonDefault
- 1; i
>= 0; --i
) {
918 decRef(env
, params
[i
].value
);
925 //////////////////////////////////////////////////////////////////////
928 * Return the type that a parameter to a builtin function is supposed to be
929 * coerced to. What this means depends on how the builtin is dealing with
930 * parameter coersion: new-style HNI builtins try to do a tvCoerceParamTo*,
931 * while older ones use tvCastTo* semantics.
933 * If the builtin parameter has no type hints to cause coercion, this function
936 Type
param_coerce_type(const Func
* callee
, uint32_t paramIdx
) {
937 auto const& pi
= callee
->params()[paramIdx
];
938 auto const& tc
= pi
.typeConstraint
;
939 if (tc
.isNullable() && !callee
->byRef(paramIdx
)) {
940 auto const dt
= tc
.underlyingDataType();
941 if (!dt
) return TBottom
;
942 return TNull
| Type(*dt
);
944 if (callee
->byRef(paramIdx
) && pi
.nativeArg
) {
947 if (!pi
.builtinType
) return tc
.isVArrayOrDArray() ? TArr
: TBottom
;
948 if (pi
.builtinType
== KindOfObject
&&
949 pi
.defaultValue
.m_type
== KindOfNull
) {
952 return Type(*pi
.builtinType
);
955 //////////////////////////////////////////////////////////////////////
958 * Collect parameters for a call to a builtin. Also determine which ones will
959 * need to be passed through the eval stack, and which ones will need
962 template <class LoadParam
>
964 prepare_params(IRGS
& /*env*/, const Func
* callee
, SSATmp
* thiz
,
965 SSATmp
* numArgsExpr
, uint32_t numArgs
, uint32_t numNonDefault
,
966 bool forNativeImpl
, LoadParam loadParam
) {
967 auto ret
= ParamPrep(numArgs
);
969 ret
.count
= numArgsExpr
;
970 ret
.forNativeImpl
= forNativeImpl
;
972 // Fill in in reverse order, since they may come from popC's (depending on
973 // what loadParam wants to do).
974 for (auto offset
= uint32_t{numArgs
}; offset
-- > 0;) {
975 auto const ty
= param_coerce_type(callee
, offset
);
976 auto& cur
= ret
[offset
];
977 auto& pi
= callee
->params()[offset
];
979 cur
.value
= loadParam(offset
, ty
);
980 cur
.isOutputArg
= pi
.nativeArg
&& ty
== TBoxedCell
;
981 // If ty > TBottom, it had some kind of type hint.
982 // A by-reference parameter thats defaulted will get a plain
983 // value (typically null), rather than a BoxedCell; so we still
984 // need to apply a conversion there.
985 cur
.needsConversion
= cur
.isOutputArg
||
986 (offset
< numNonDefault
&& ty
> TBottom
);
987 // We do actually mean exact type equality here. We're only capable of
988 // passing the following primitives through registers; everything else goes
989 // by address unless its flagged "nativeArg".
990 if (ty
== TBool
|| ty
== TInt
|| ty
== TDbl
|| pi
.nativeArg
) {
995 cur
.passByAddr
= true;
1001 //////////////////////////////////////////////////////////////////////
1004 * CatchMaker makes catch blocks for calling builtins. There's a fair bit of
1005 * complexity here right now, for these reasons:
1007 * o Sometimes we're 'logically' inlining a php-level call to a function
1008 * that contains a NativeImpl opcode.
1010 * But we implement this by generating all the relevant NativeImpl code
1011 * after the InlineReturn for the callee, to make it easier for DCE to
1012 * eliminate the code that constructs the callee's activation record.
1013 * This means the unwinder is going to see our PC as equal to the FCall
1014 * for the call to the function, which will be inside the FPI region for
1015 * the call, so it'll try to pop an ActRec, so we'll need to reconstruct
1016 * one for it during unwinding.
1018 * o HNI-style param coerce modes can force the entire function to return
1019 * false or null if the coersions fail. This is implemented via a
1020 * TVCoercionException, which is not a user-visible exception. So our
1021 * catch blocks are sometimes handling a PHP exception, and sometimes a
1022 * failure to coerce.
1024 * o Both of these things may be relevant to the same catch block.
1026 * Also, note that the CatchMaker keeps a pointer to the builtin call's
1027 * ParamPrep, which will have its values mutated by realize_params as it's
1028 * making coersions, so that we can see what's changed so far (and what to
1029 * clean up on the offramps). Some values that were refcounted may become
1030 * non-refcounted after conversions, and we can't DecRef things twice.
1033 enum class Kind
{ NotInlining
, Inlining
};
1035 explicit CatchMaker(IRGS
& env
, Kind kind
, const ParamPrep
* params
)
1040 assertx(!m_params
.thiz
|| m_params
.forNativeImpl
|| inlining());
1043 CatchMaker(const CatchMaker
&) = delete;
1044 CatchMaker(CatchMaker
&&) = default;
1046 bool inlining() const {
1048 case Kind::NotInlining
: return false;
1049 case Kind::Inlining
: return true;
1054 Block
* makeUnusualCatch() const {
1055 auto const exit
= defBlock(env
, Block::Hint::Unlikely
);
1056 BlockPusher
bp(*env
.irb
, makeMarker(env
, bcOff(env
)), exit
);
1057 gen(env
, BeginCatch
);
1061 IRSPRelOffsetData
{ spOffBCFromIRSP(env
) },
1067 * DecRef the params in preparation for an exception or side
1068 * exit. Parameters that are not being passed through the stack
1069 * still may need to be decref'd, because they may have been a
1070 * reference counted type that was going to be converted to a
1071 * non-reference counted type that we'd pass in a register. As we
1072 * do the coersions, params.value gets updated so whenever we call
1073 * these catch block creation functions it will only decref things
1074 * that weren't yet converted.
1076 void decRefParams() const {
1077 if (m_params
.forNativeImpl
) return;
1078 for (auto i
= m_params
.size(); i
--; ) {
1079 auto const &pi
= m_params
[i
];
1080 if (pi
.passByAddr
) {
1083 decRef(env
, pi
.value
);
1089 void prepareForCatch() const {
1090 if (inlining() && m_params
.thiz
) {
1091 decRef(env
, m_params
.thiz
);
1094 * We're potentially spilling to a different depth than the unwinder
1095 * would've expected, so we need an eager sync. Even if we aren't inlining
1096 * this can happen, because before doing the CallBuiltin we set the marker
1097 * stack offset to only include the passed-through-stack args.
1099 * So before we leave, update the marker to placate EndCatch assertions,
1100 * which is trying to detect failure to do this properly.
1102 auto const spOff
= IRSPRelOffsetData
{ spOffBCFromIRSP(env
) };
1103 gen(env
, EagerSyncVMRegs
, spOff
, fp(env
), sp(env
));
1104 updateMarker(env
); // Mark the EndCatch safe, since we're eager syncing.
1110 const ParamPrep
& m_params
;
1113 //////////////////////////////////////////////////////////////////////
1115 SSATmp
* coerce_value(IRGS
& env
,
1120 const CatchMaker
& maker
) {
1121 auto const result
= [&] () -> SSATmp
* {
1125 FuncArgData(callee
, paramIdx
+ 1),
1126 maker
.makeUnusualCatch(),
1132 FuncArgData(callee
, paramIdx
+ 1),
1133 maker
.makeUnusualCatch(),
1139 FuncArgData(callee
, paramIdx
+ 1),
1140 maker
.makeUnusualCatch(),
1148 decRef(env
, oldVal
);
1152 always_assert(ty
.subtypeOfAny(TArr
, TStr
, TObj
, TRes
, TDict
, TKeyset
, TVec
) &&
1153 callee
->params()[paramIdx
].nativeArg
);
1154 auto const misAddr
= gen(env
, LdMIStateAddr
,
1155 cns(env
, offsetof(MInstrState
, tvBuiltinReturn
)));
1156 gen(env
, StMem
, misAddr
, oldVal
);
1157 gen(env
, CoerceMem
, ty
,
1158 CoerceMemData
{ callee
, paramIdx
+ 1 },
1159 maker
.makeUnusualCatch(), misAddr
);
1160 return gen(env
, LdMem
, ty
, misAddr
);
1163 void coerce_stack(IRGS
& env
,
1167 BCSPRelOffset offset
,
1168 const CatchMaker
& maker
) {
1169 always_assert(ty
.isKnownDataType());
1173 CoerceStkData
{ offsetFromIRSP(env
, offset
), callee
, paramIdx
+ 1 },
1174 maker
.makeUnusualCatch(),
1178 * We can throw after writing to the stack above; inform IRBuilder about it.
1179 * This is basically just for assertions right now.
1181 env
.irb
->exceptionStackBoundary();
1185 * Take the value in param, apply any needed conversions
1186 * and return the value to be passed to CallBuiltin.
1188 * checkType(ty, fail):
1189 * verify that the param is of type ty, and branch to fail
1190 * if not. If it results in a new SSATmp*, (as eg CheckType
1191 * would), then that should be returned; otherwise it should
1194 * convert the param to ty; failure should be handled by
1195 * CatchMaker::makeParamCoerceCatch, and it should return
1196 * a new SSATmp* (if appropriate) or nullptr.
1198 * return the SSATmp* needed by CallBuiltin for this parameter.
1199 * if checkType and convertParam returned non-null values,
1200 * param.value will have been updated with a phi of their results.
1202 template<class V
, class C
, class R
>
1203 SSATmp
* realize_param(IRGS
& env
,
1204 ParamPrep::Info
& param
,
1210 if (param
.needsConversion
) {
1211 auto const baseTy
= targetTy
- TNull
;
1212 assertx(baseTy
.isKnownDataType());
1213 auto const convertTy
= baseTy
;
1215 if (auto const value
= cond(
1217 [&] (Block
* convert
) -> SSATmp
* {
1218 if (targetTy
== baseTy
) {
1219 return checkType(baseTy
, convert
);
1223 [&] (Block
* fail
) { return checkType(baseTy
, fail
); },
1224 [&] (SSATmp
* v
) { return v
; },
1226 return checkType(TInitNull
, convert
);
1229 [&] (SSATmp
* v
) { return v
; },
1231 return convertParam(convertTy
);
1234 // Heads up on non-local state here: we have to update
1235 // the values inside ParamPrep so that the CatchMaker
1236 // functions know about new potentially refcounted types
1237 // to decref, or values that were already decref'd and
1238 // replaced with things like ints.
1239 param
.value
= value
;
1247 * Prepare the actual arguments to the CallBuiltin instruction, by converting a
1248 * ParamPrep into a vector of SSATmps to pass to CallBuiltin. If any of the
1249 * parameters needed type conversions, we need to do that here too.
1251 jit::vector
<SSATmp
*> realize_params(IRGS
& env
,
1254 const CatchMaker
& maker
) {
1255 auto const cbNumArgs
= 2 + params
.size() +
1256 (params
.thiz
? 1 : 0) + (params
.count
? 1 : 0);
1257 auto ret
= jit::vector
<SSATmp
*>(cbNumArgs
);
1258 auto argIdx
= uint32_t{0};
1259 ret
[argIdx
++] = fp(env
);
1260 ret
[argIdx
++] = sp(env
);
1261 if (params
.thiz
) ret
[argIdx
++] = params
.thiz
;
1262 if (params
.count
) ret
[argIdx
++] = params
.count
;
1264 assertx(!params
.count
);
1266 auto const needDVCheck
= [&](uint32_t param
, const Type
& ty
) {
1267 if (!RuntimeOption::EvalHackArrCompatTypeHintNotices
) return false;
1268 if (!callee
->params()[param
].typeConstraint
.isArray()) return false;
1272 auto const dvCheck
= [&](uint32_t param
, SSATmp
* val
) {
1273 assertx(needDVCheck(param
, val
->type()));
1274 auto const& tc
= callee
->params()[param
].typeConstraint
;
1276 auto const check
= [&](Block
* taken
) {
1277 if (tc
.isVArray()) return gen(env
, CheckVArray
, taken
, val
);
1278 if (tc
.isDArray()) return gen(env
, CheckDArray
, taken
, val
);
1279 if (tc
.isVArrayOrDArray()) {
1280 return gen(env
, JmpZero
, taken
, gen(env
, IsDVArray
, val
));
1282 return gen(env
, JmpNZero
, taken
, gen(env
, IsDVArray
, val
));
1291 RaiseHackArrParamNotice
,
1292 RaiseHackArrParamNoticeData
{ tc
.type(), int32_t(param
), false },
1293 maker
.makeUnusualCatch(),
1301 DEBUG_ONLY
auto seenBottom
= false;
1302 DEBUG_ONLY
auto usedStack
= false;
1303 auto stackIdx
= uint32_t{0};
1304 for (auto paramIdx
= uint32_t{0}; paramIdx
< params
.size(); ++paramIdx
) {
1305 auto& param
= params
[paramIdx
];
1306 auto const targetTy
= param_coerce_type(callee
, paramIdx
);
1308 seenBottom
|= (param
.value
->type() == TBottom
);
1310 if (param
.value
->type() <= TPtrToGen
) {
1311 ret
[argIdx
++] = realize_param(
1312 env
, param
, callee
, targetTy
,
1313 [&] (const Type
& ty
, Block
* fail
) -> SSATmp
* {
1314 gen(env
, CheckTypeMem
, ty
, fail
, param
.value
);
1315 if (needDVCheck(paramIdx
, ty
)) {
1316 dvCheck(paramIdx
, gen(env
, LdMem
, ty
, param
.value
));
1318 return param
.isOutputArg
?
1319 gen(env
, LdMem
, TBoxedCell
, param
.value
) : nullptr;
1321 [&] (const Type
& ty
) -> SSATmp
* {
1322 hint(env
, Block::Hint::Unlikely
);
1323 if (param
.isOutputArg
) {
1324 return cns(env
, TNullptr
);
1326 gen(env
, CoerceMem
, ty
, CoerceMemData
{ callee
, paramIdx
+ 1 },
1327 maker
.makeUnusualCatch(), param
.value
);
1331 if (!param
.passByAddr
&& !param
.isOutputArg
) {
1332 assertx(targetTy
== TBool
||
1335 callee
->params()[paramIdx
].nativeArg
);
1336 return gen(env
, LdMem
,
1337 targetTy
== TBottom
? TCell
: targetTy
,
1345 if (!param
.passByAddr
) {
1346 auto const oldVal
= params
[paramIdx
].value
;
1347 ret
[argIdx
++] = realize_param(
1348 env
, param
, callee
, targetTy
,
1349 [&] (const Type
& ty
, Block
* fail
) {
1350 auto ret
= gen(env
, CheckType
, ty
, fail
, param
.value
);
1351 env
.irb
->constrainValue(ret
, DataTypeSpecific
);
1352 if (needDVCheck(paramIdx
, ty
)) dvCheck(paramIdx
, ret
);
1355 [&] (const Type
& ty
) {
1356 if (param
.isOutputArg
) return cns(env
, TNullptr
);
1357 return coerce_value(env
, ty
, callee
, oldVal
, paramIdx
, maker
);
1362 * - if we had a ref-counted type, and it was converted
1363 * to a Bool, Int or Dbl above, we explicitly DecReffed it
1364 * (in coerce_value).
1365 * - if we had a non-RefData nativeArg, we did a CoerceMem
1366 * which implicitly DecReffed the old value
1367 * In either case, the old value is taken care of, and any future
1368 * DecRefs (from exceptions, or after the call on the normal flow
1369 * of execution) should DecRef param.value (ie the post-coercion
1372 * But if we had an OutputArg, we did not DecRef the old value,
1373 * and the post-coercion value is a RefData* or nullptr.
1374 * If its a RefData*, we need to DecRef that - but in that case
1375 * the new value is the same as the old.
1376 * If its Nullptr, we need to DecRef the old value.
1378 * So in both cases we actually want to DecRef the *old* value, so
1379 * we have to restore it here (because realize_param replaced it
1380 * with the new value).
1382 auto v
= param
.value
;
1383 if (param
.isOutputArg
) {
1384 param
.value
= oldVal
;
1392 auto const offset
= BCSPRelOffset
{safe_cast
<int32_t>(
1393 params
.numByAddr
- stackIdx
- 1)};
1395 ret
[argIdx
++] = realize_param(
1396 env
, param
, callee
, targetTy
,
1397 [&] (const Type
& ty
, Block
* fail
) -> SSATmp
* {
1398 auto irSPRel
= offsetFromIRSP(env
, offset
);
1399 gen(env
, CheckStk
, IRSPRelOffsetData
{ irSPRel
}, ty
, fail
, sp(env
));
1400 env
.irb
->constrainStack(irSPRel
, DataTypeSpecific
);
1401 if (needDVCheck(paramIdx
, ty
)) {
1404 gen(env
, LdStk
, ty
, IRSPRelOffsetData
{ irSPRel
}, sp(env
))
1409 [&] (const Type
& ty
) -> SSATmp
* {
1410 coerce_stack(env
, ty
, callee
, paramIdx
, offset
, maker
);
1414 return ldStkAddr(env
, offset
);
1419 assertx(seenBottom
|| !usedStack
|| stackIdx
== params
.numByAddr
);
1420 assertx(argIdx
== cbNumArgs
);
1425 //////////////////////////////////////////////////////////////////////
1427 SSATmp
* builtinCall(IRGS
& env
,
1430 int32_t numNonDefault
,
1431 const CatchMaker
& catchMaker
) {
1432 assertx(callee
->nativeFuncPtr());
1434 // Try to replace the builtin call with a specialized implementation of the
1436 auto optRet
= optimizedFCallBuiltin(env
, callee
, params
, numNonDefault
);
1437 if (optRet
) return optRet
;
1439 if (!params
.forNativeImpl
) {
1441 * Everything that needs to be on the stack gets spilled now.
1443 * If we're not inlining, the reason we do this even when numByAddr is
1444 * zero is to make it so that in either case the stack depth when we enter
1445 * our catch blocks is always the same as the numByAddr value, in all
1446 * situations. If we didn't do this, then when we aren't inlining, and
1447 * numByAddr is zero, we'd have the stack depth be the total num
1448 * params (the depth before the FCallBuiltin), which would add more cases
1449 * to handle in the catch blocks.
1451 if (params
.numByAddr
!= 0 || !catchMaker
.inlining()) {
1452 for (auto i
= uint32_t{0}; i
< params
.size(); ++i
) {
1453 if (params
[i
].passByAddr
) {
1454 push(env
, params
[i
].value
);
1458 * This marker update is to make sure rbx points to the bottom of our
1459 * stack if we enter a catch trace. It's also necessary because we might
1460 * run destructors as part of parameter coersions, which we don't want to
1461 * clobber our spilled stack.
1466 // If we are inlining, we've done various DefInlineFP-type stuff that can
1467 // affect stack depth.
1468 env
.irb
->exceptionStackBoundary();
1471 // Make the actual call.
1472 auto realized
= realize_params(env
, callee
, params
, catchMaker
);
1473 SSATmp
** const decayedPtr
= &realized
[0];
1474 auto const ret
= gen(
1478 spOffBCFromIRSP(env
),
1480 params
.count
? -1 : numNonDefault
,
1481 funcNeedsCallerFrame(callee
)
1483 catchMaker
.makeUnusualCatch(),
1484 std::make_pair(realized
.size(), decayedPtr
)
1487 if (!params
.forNativeImpl
) {
1488 if (params
.thiz
&& params
.thiz
->type() <= TObj
) {
1489 decRef(env
, params
.thiz
);
1491 catchMaker
.decRefParams();
1498 * When we're inlining a NativeImpl opcode, we know this is the only opcode in
1499 * the callee method body aside from AssertRATs (bytecode invariant). So in
1500 * order to make sure we can eliminate the SpillFrame, we do the CallBuiltin
1501 * instruction after we've left the inlined frame.
1503 * We may need to pass some arguments to the builtin through the stack (e.g. if
1504 * it takes const Variant&'s)---these are spilled to the stack after leaving
1507 * To make this work, we need to do some weird things in the catch trace. ;)
1509 void nativeImplInlined(IRGS
& env
) {
1510 auto const callee
= curFunc(env
);
1511 assertx(callee
->nativeFuncPtr());
1513 auto const numArgs
= callee
->numParams();
1514 auto const paramThis
= [&] () -> SSATmp
* {
1515 if (!callee
->isMethod()) return nullptr;
1516 auto ctx
= ldCtx(env
);
1517 if (callee
->isStatic()) return gen(env
, LdClsCtx
, ctx
);
1518 return castCtxThis(env
, ctx
);
1521 auto numNonDefault
= fp(env
)->inst()->extra
<DefInlineFP
>()->numNonDefault
;
1522 auto params
= prepare_params(
1530 [&] (uint32_t i
, const Type
) {
1531 return ldLoc(env
, i
, nullptr, DataTypeSpecific
);
1535 implInlineReturn(env
);
1537 auto const catcher
= CatchMaker
{
1539 CatchMaker::Kind::Inlining
,
1543 push(env
, builtinCall(env
, callee
, params
, numNonDefault
, catcher
));
1546 //////////////////////////////////////////////////////////////////////
1550 //////////////////////////////////////////////////////////////////////
1552 SSATmp
* optimizedCallIsObject(IRGS
& env
, SSATmp
* src
) {
1553 if (src
->isA(TObj
) && src
->type().clsSpec()) {
1554 auto const cls
= src
->type().clsSpec().cls();
1555 if (!env
.irb
->constrainValue(src
, GuardConstraint(cls
).setWeak())) {
1556 // If we know the class without having to specialize a guard
1557 // any further, use it.
1558 return cns(env
, cls
!= SystemLib::s___PHP_Incomplete_ClassClass
);
1562 if (!src
->type().maybe(TObj
)) {
1563 return cns(env
, false);
1566 auto checkClass
= [&] (SSATmp
* obj
) {
1567 auto cls
= gen(env
, LdObjClass
, obj
);
1568 auto testCls
= SystemLib::s___PHP_Incomplete_ClassClass
;
1569 auto eq
= gen(env
, EqCls
, cls
, cns(env
, testCls
));
1570 return gen(env
, XorBool
, eq
, cns(env
, true));
1575 [&] (Block
* taken
) {
1576 auto isObj
= gen(env
, IsType
, TObj
, src
);
1577 gen(env
, JmpZero
, taken
, isObj
);
1579 [&] { // Next: src is an object
1580 auto obj
= gen(env
, AssertType
, TObj
, src
);
1581 return checkClass(obj
);
1583 [&] { // Taken: src is not an object
1584 return cns(env
, false);
1589 //////////////////////////////////////////////////////////////////////
1591 void emitFCallBuiltin(IRGS
& env
,
1593 uint32_t numNonDefault
,
1594 const StringData
* funcName
) {
1595 auto const callee
= Unit::lookupBuiltin(funcName
);
1597 if (!callee
) PUNT(Missing
-builtin
);
1598 emitCallerRxChecks(env
, callee
, /* unused, known callee */ IRSPRelOffset
{});
1600 auto params
= prepare_params(
1602 nullptr, // no $this; FCallBuiltin never happens for methods
1603 nullptr, // count is constant numNonDefault
1604 numArgs
, numNonDefault
, false, [&](uint32_t /*i*/, const Type ty
) {
1606 ty
== TBottom
? DataTypeGeneric
: DataTypeSpecific
;
1607 return pop(env
, specificity
);
1610 auto const catcher
= CatchMaker
{
1612 CatchMaker::Kind::NotInlining
,
1616 push(env
, builtinCall(env
, callee
, params
, numNonDefault
, catcher
));
1619 void emitNativeImpl(IRGS
& env
) {
1620 if (isInlining(env
)) return nativeImplInlined(env
);
1622 auto const callee
= curFunc(env
);
1624 auto genericNativeImpl
= [&]() {
1625 gen(env
, NativeImpl
, fp(env
), sp(env
));
1626 auto const retVal
= gen(env
, LdRetVal
, callReturnType(callee
), fp(env
));
1627 auto const data
= RetCtrlData
{ offsetToReturnSlot(env
), false };
1628 gen(env
, RetCtrl
, data
, sp(env
), fp(env
), retVal
);
1631 if (!callee
->nativeFuncPtr()) {
1632 genericNativeImpl();
1636 auto thiz
= callee
->isMethod() ? ldCtx(env
) : nullptr;
1637 auto const numParams
= gen(env
, LdARNumParams
, fp(env
));
1641 [&] (Block
* fallback
) {
1643 if (!hasThis(env
)) {
1644 thiz
= gen(env
, LdClsCtx
, thiz
);
1646 thiz
= castCtxThis(env
, thiz
);
1650 auto maxArgs
= callee
->numParams();
1651 auto minArgs
= callee
->numNonVariadicParams();
1653 auto const& pi
= callee
->params()[minArgs
- 1];
1654 if (pi
.funcletOff
== InvalidAbsoluteOffset
) {
1659 if (callee
->hasVariadicCaptureParam()) {
1661 auto const check
= gen(env
, LtInt
, numParams
, cns(env
, minArgs
));
1662 gen(env
, JmpNZero
, fallback
, check
);
1665 if (minArgs
== maxArgs
) {
1666 auto const check
= gen(env
, EqInt
, numParams
, cns(env
, minArgs
));
1667 gen(env
, JmpZero
, fallback
, check
);
1670 auto const checkMin
= gen(env
, LtInt
,
1671 numParams
, cns(env
, minArgs
));
1672 gen(env
, JmpNZero
, fallback
, checkMin
);
1674 auto const checkMax
= gen(env
, GtInt
, numParams
, cns(env
, maxArgs
));
1675 gen(env
, JmpNZero
, fallback
, checkMax
);
1680 auto params
= prepare_params(
1685 callee
->numParams(),
1686 callee
->numParams(),
1688 [&] (uint32_t i
, const Type
) {
1689 return gen(env
, LdLocAddr
, LocalId(i
), fp(env
));
1692 auto const catcher
= CatchMaker
{
1694 CatchMaker::Kind::NotInlining
,
1698 auto const ret
= builtinCall(env
, callee
, params
,
1699 callee
->numParams(), catcher
);
1704 hint(env
, Block::Hint::Unlikely
);
1705 genericNativeImpl();
1710 //////////////////////////////////////////////////////////////////////
1714 const StaticString
s_add("add");
1715 const StaticString
s_addall("addall");
1716 const StaticString
s_append("append");
1717 const StaticString
s_clear("clear");
1718 const StaticString
s_remove("remove");
1719 const StaticString
s_removeall("removeall");
1720 const StaticString
s_removekey("removekey");
1721 const StaticString
s_set("set");
1722 const StaticString
s_setall("setall");
1724 // Whitelist of known collection methods that always return $this (ignoring
1725 // parameter coercion failure issues).
1726 bool collectionMethodReturnsThis(const Func
* callee
) {
1727 auto const cls
= callee
->implCls();
1729 if (cls
== c_Vector::classof()) {
1731 callee
->name()->isame(s_add
.get()) ||
1732 callee
->name()->isame(s_addall
.get()) ||
1733 callee
->name()->isame(s_append
.get()) ||
1734 callee
->name()->isame(s_clear
.get()) ||
1735 callee
->name()->isame(s_removekey
.get()) ||
1736 callee
->name()->isame(s_set
.get()) ||
1737 callee
->name()->isame(s_setall
.get());
1740 if (cls
== c_Map::classof()) {
1742 callee
->name()->isame(s_add
.get()) ||
1743 callee
->name()->isame(s_addall
.get()) ||
1744 callee
->name()->isame(s_clear
.get()) ||
1745 callee
->name()->isame(s_remove
.get()) ||
1746 callee
->name()->isame(s_set
.get()) ||
1747 callee
->name()->isame(s_setall
.get());
1750 if (cls
== c_Set::classof()) {
1752 callee
->name()->isame(s_add
.get()) ||
1753 callee
->name()->isame(s_addall
.get()) ||
1754 callee
->name()->isame(s_clear
.get()) ||
1755 callee
->name()->isame(s_remove
.get()) ||
1756 callee
->name()->isame(s_removeall
.get());
1764 Type
builtinReturnType(const Func
* builtin
) {
1765 // Why do we recalculate the type here than just using HHBBC's inferred type?
1766 // Unlike for regular PHP functions, we have access to all the same
1767 // information that HHBBC does, and the JIT type-system is slightly more
1768 // expressive. So, by doing it ourself, we can derive a slightly more precise
1770 assertx(builtin
->isCPPBuiltin());
1772 // NB: It is *not* safe to be pessimistic here and return TGen (or any other
1773 // approximation). The builtin's return type inferred here is used to control
1774 // code-gen when lowering the builtin call to vasm and must be no more general
1775 // than the HNI declaration (if present).
1777 // If this is a collection method which returns $this, use that fact to
1778 // infer the exact returning type. Otherwise try to use HNI declaration.
1779 if (collectionMethodReturnsThis(builtin
)) {
1780 assertx(builtin
->hniReturnType() == KindOfObject
);
1781 return Type::ExactObj(builtin
->implCls());
1783 if (auto const hniType
= builtin
->hniReturnType()) {
1784 if (isArrayType(*hniType
)) {
1785 auto const& constraint
= builtin
->returnTypeConstraint();
1786 if (constraint
.isVArray()) return Type::Array(ArrayData::kPackedKind
);
1787 if (constraint
.isDArray()) return Type::Array(ArrayData::kMixedKind
);
1789 return Type
{*hniType
};
1794 // "Reference" types (not boxed, types represented by a pointer) can always be
1796 if (type
.isReferenceType()) {
1799 assertx(type
== TInitCell
|| type
.isSimpleType());
1802 return type
& TInitCell
;
1805 /////////////////////////////////////////////////////////////////////
1809 void implArrayIdx(IRGS
& env
) {
1810 // These types are just used to decide what to do; once we know what we're
1811 // actually doing we constrain the values with the popC()s later on in this
1813 auto const keyType
= topC(env
, BCSPRelOffset
{1}, DataTypeGeneric
)->type();
1815 if (keyType
<= TNull
) {
1816 auto const def
= popC(env
, DataTypeGeneric
);
1817 auto const key
= popC(env
);
1818 auto const base
= popC(env
);
1820 // if the key is null it will not be found so just return the default
1826 if (!(keyType
<= TInt
|| keyType
<= TStr
)) {
1827 interpOne(env
, TCell
, 3);
1831 auto const def
= popC(env
, DataTypeGeneric
); // a helper will decref it but
1832 // the translated code doesn't
1833 // care about the type
1834 auto const key
= popC(env
);
1835 auto const base
= popC(env
);
1837 auto const elem
= profiledArrayAccess(env
, base
, key
,
1838 [&] (SSATmp
* arr
, SSATmp
* key
, uint32_t pos
) {
1839 return gen(env
, MixedArrayGetK
, IndexData
{ pos
}, arr
, key
);
1842 return gen(env
, ArrayIdx
, base
, key
, def
);
1846 auto finish
= [&](SSATmp
* tmp
) {
1847 auto const value
= unbox(env
, tmp
, nullptr);
1848 pushIncRef(env
, value
);
1854 auto const pelem
= profiledType(env
, elem
, [&] { finish(elem
); });
1858 void implVecIdx(IRGS
& env
, SSATmp
* loaded_collection_vec
) {
1859 auto const def
= popC(env
);
1860 auto const key
= popC(env
);
1861 auto const stack_base
= popC(env
);
1863 auto const finish
= [&](SSATmp
* elem
) {
1864 pushIncRef(env
, elem
);
1867 decRef(env
, stack_base
);
1870 if (key
->isA(TNull
| TStr
)) return finish(def
);
1872 if (!key
->isA(TInt
)) {
1873 // TODO(T11019533): Fix the underlying issue with unreachable code rather
1874 // than papering over it by pushing an unused value here.
1877 env
.irb
->exceptionStackBoundary();
1878 gen(env
, ThrowInvalidArrayKey
, stack_base
, key
);
1882 auto const use_base
= loaded_collection_vec
1883 ? loaded_collection_vec
1885 assertx(use_base
->isA(TVec
));
1887 auto const elem
= cond(
1889 [&] (Block
* taken
) {
1890 gen(env
, CheckPackedArrayDataBounds
, taken
, use_base
, key
);
1892 [&] { return gen(env
, LdVecElem
, use_base
, key
); },
1896 auto const pelem
= profiledType(env
, elem
, [&] { finish(elem
); } );
1900 void implDictKeysetIdx(IRGS
& env
,
1902 SSATmp
* loaded_collection_dict
) {
1903 auto const def
= popC(env
);
1904 auto const key
= popC(env
);
1905 auto const stack_base
= popC(env
);
1907 auto const finish
= [&](SSATmp
* elem
) {
1908 pushIncRef(env
, elem
);
1911 decRef(env
, stack_base
);
1914 if (key
->isA(TNull
)) return finish(def
);
1916 if (!key
->isA(TInt
) && !key
->isA(TStr
)) {
1917 // TODO(T11019533): Fix the underlying issue with unreachable code rather
1918 // than papering over it by pushing an unused value here.
1921 env
.irb
->exceptionStackBoundary();
1922 gen(env
, ThrowInvalidArrayKey
, stack_base
, key
);
1926 assertx(is_dict
|| !loaded_collection_dict
);
1927 auto const use_base
= loaded_collection_dict
1928 ? loaded_collection_dict
1930 assertx(use_base
->isA(is_dict
? TDict
: TKeyset
));
1932 auto const elem
= profiledArrayAccess(env
, use_base
, key
,
1933 [&] (SSATmp
* base
, SSATmp
* key
, uint32_t pos
) {
1934 return gen(env
, is_dict
? DictGetK
: KeysetGetK
, IndexData
{ pos
},
1938 return gen(env
, is_dict
? DictIdx
: KeysetIdx
, use_base
, key
, def
);
1942 auto const pelem
= profiledType(env
, elem
, [&] { finish(elem
); });
1946 const StaticString
s_idx("hh\\idx");
1948 void implGenericIdx(IRGS
& env
) {
1949 auto const def
= popC(env
, DataTypeSpecific
);
1950 auto const key
= popC(env
, DataTypeSpecific
);
1951 auto const base
= popC(env
, DataTypeSpecific
);
1953 SSATmp
* const args
[] = { base
, key
, def
};
1955 static auto func
= Unit::lookupBuiltin(s_idx
.get());
1956 assertx(func
&& func
->numParams() == 3);
1958 emitDirectCall(env
, func
, 3, args
);
1962 * Return the GuardConstraint that should be used to constrain baseType for an
1965 GuardConstraint
idxBaseConstraint(Type baseType
, Type keyType
,
1966 bool& useVec
, bool& useDict
) {
1967 if (baseType
< TObj
&& baseType
.clsSpec()) {
1968 auto const cls
= baseType
.clsSpec().cls();
1970 // Vector is only usable with int keys, so we can only optimize for
1971 // Vector if the key is an Int
1972 useVec
= (collections::isType(cls
, CollectionType::Vector
) ||
1973 collections::isType(cls
, CollectionType::ImmVector
)) &&
1976 useDict
= collections::isType(cls
, CollectionType::Map
) ||
1977 collections::isType(cls
, CollectionType::ImmMap
) ||
1978 collections::isType(cls
, CollectionType::Set
) ||
1979 collections::isType(cls
, CollectionType::ImmSet
);
1981 if (useVec
|| useDict
) return GuardConstraint(cls
);
1984 useVec
= useDict
= false;
1985 return DataTypeSpecific
;
1988 //////////////////////////////////////////////////////////////////////
1992 void emitArrayIdx(IRGS
& env
) {
1993 auto const arrType
= topC(env
, BCSPRelOffset
{2}, DataTypeGeneric
)->type();
1994 if (arrType
<= TVec
) return implVecIdx(env
, nullptr);
1995 if (arrType
<= TDict
) return implDictKeysetIdx(env
, true, nullptr);
1996 if (arrType
<= TKeyset
) return implDictKeysetIdx(env
, false, nullptr);
1997 if (arrType
<= TClsMeth
) PUNT(ArrayIdx_clsmeth
);
1999 if (!(arrType
<= TArr
)) {
2001 interpOne(env
, TCell
, 3);
2008 void emitIdx(IRGS
& env
) {
2009 auto const key
= topC(env
, BCSPRelOffset
{1}, DataTypeGeneric
);
2010 auto const base
= topC(env
, BCSPRelOffset
{2}, DataTypeGeneric
);
2011 auto const keyType
= key
->type();
2012 auto const baseType
= base
->type();
2014 if (baseType
<= TVec
) return implVecIdx(env
, nullptr);
2015 if (baseType
<= TDict
) return implDictKeysetIdx(env
, true, nullptr);
2016 if (baseType
<= TKeyset
) return implDictKeysetIdx(env
, false, nullptr);
2018 if (keyType
<= TNull
|| !baseType
.maybe(TArr
| TObj
| TStr
)) {
2019 auto const def
= popC(env
, DataTypeGeneric
);
2020 popC(env
, keyType
<= TNull
? DataTypeSpecific
: DataTypeGeneric
);
2021 popC(env
, keyType
<= TNull
? DataTypeGeneric
: DataTypeSpecific
);
2028 auto const simple_key
=
2029 keyType
<= TInt
|| keyType
<= TStr
;
2032 implGenericIdx(env
);
2036 if (baseType
<= TArr
) {
2041 bool useVec
, useDict
;
2042 auto const tc
= idxBaseConstraint(baseType
, keyType
, useVec
, useDict
);
2043 if (useVec
|| useDict
) {
2044 env
.irb
->constrainValue(base
, tc
);
2045 env
.irb
->constrainValue(key
, DataTypeSpecific
);
2048 auto const vec
= gen(env
, LdColVec
, base
);
2049 implVecIdx(env
, vec
);
2051 auto const dict
= gen(env
, LdColDict
, base
);
2052 implDictKeysetIdx(env
, true, dict
);
2057 implGenericIdx(env
);
2060 void emitAKExists(IRGS
& env
) {
2061 auto const arr
= popC(env
);
2062 auto key
= popC(env
);
2063 if (key
->isA(TFunc
) || key
->isA(TCls
)) PUNT(AKExists_func_cls_key
);
2065 auto throwBadKey
= [&] {
2066 // TODO(T11019533): Fix the underlying issue with unreachable code rather
2067 // than papering over it by pushing an unused value here.
2068 push(env
, cns(env
, false));
2072 env
.irb
->exceptionStackBoundary();
2073 gen(env
, ThrowInvalidArrayKey
, arr
, key
);
2076 auto const check_packed
= [&] {
2077 assertx(key
->isA(TInt
));
2079 auto const result
= cond(
2082 gen(env
, CheckPackedArrayDataBounds
, taken
, arr
, key
);
2084 [&] { return cns(env
, true); },
2085 [&] { return cns(env
, false); }
2091 if (arr
->isA(TVec
)) {
2092 if (key
->isA(TNull
| TStr
)) {
2093 push(env
, cns(env
, false));
2098 if (key
->isA(TInt
)) {
2099 return check_packed();
2101 return throwBadKey();
2104 if (arr
->isA(TDict
) || arr
->isA(TKeyset
)) {
2105 if (key
->isA(TNull
)) {
2106 push(env
, cns(env
, false));
2111 if (!key
->isA(TInt
) && !key
->isA(TStr
)) {
2112 return throwBadKey();
2114 auto const val
= gen(
2116 arr
->isA(TDict
) ? AKExistsDict
: AKExistsKeyset
,
2126 if (!arr
->isA(TArr
) && !arr
->isA(TObj
)) PUNT(AKExists_badArray
);
2128 if (key
->isA(TInitNull
) && arr
->isA(TArr
)) {
2129 if (checkHACArrayKeyCast()) {
2132 RaiseHackArrCompatNotice
,
2136 makeHackArrCompatImplicitArrayKeyMsg(uninit_variant
.asTypedValue())
2142 key
= cns(env
, staticEmptyString());
2145 if (!key
->isA(TStr
) && !key
->isA(TInt
)) PUNT(AKExists_badKey
);
2147 if (arr
->isA(TObj
) && key
->isA(TInt
) &&
2148 collections::isType(arr
->type().clsSpec().cls(), CollectionType::Vector
,
2149 CollectionType::ImmVector
)) {
2151 gen(env
, CheckRange
, key
, gen(env
, CountCollection
, arr
));
2156 if (arr
->isA(TArr
) && key
->isA(TInt
) &&
2157 arr
->type().arrSpec().kind() == ArrayData::kPackedKind
) {
2158 return check_packed();
2162 gen(env
, arr
->isA(TArr
) ? AKExistsArr
: AKExistsObj
, arr
, key
);
2168 //////////////////////////////////////////////////////////////////////
2170 void emitGetMemoKeyL(IRGS
& env
, int32_t locId
) {
2171 DEBUG_ONLY
auto const func
= curFunc(env
);
2172 assertx(func
->isMemoizeWrapper());
2173 assertx(!func
->anyByRef());
2175 auto const value
= ldLocInnerWarn(
2183 // Use the generic scheme, which is implemented by GetMemoKey. The simplifier
2184 // will catch any additional special cases.
2185 push(env
, gen(env
, GetMemoKey
, value
));
2190 void memoGetImpl(IRGS
& env
,
2192 Offset suspendedOff
,
2194 assertx(curFunc(env
)->isMemoizeWrapper());
2195 assertx(keys
.first
+ keys
.count
<= curFunc(env
)->numLocals());
2196 assertx(suspendedOff
== kInvalidOffset
|| curFunc(env
)->isAsyncFunction());
2198 CompactVector
<bool> types
;
2199 for (auto i
= keys
.count
; i
> 0; --i
) {
2200 auto const type
= env
.irb
->local(keys
.first
+ i
- 1, DataTypeSpecific
).type
;
2202 types
.emplace_back(true);
2203 } else if (type
<= TInt
) {
2204 types
.emplace_back(false);
2206 // Let it fatal from the interpreter
2211 auto const notFound
= getBlock(env
, bcOff(env
) + notfoundOff
);
2212 assertx(notFound
!= nullptr);
2214 auto const func
= curFunc(env
);
2216 auto const loadAux
= suspendedOff
!= kInvalidOffset
;
2218 auto const val
= [&]{
2219 // Any value we get from memoization must be the same type we return from
2220 // this function. If we need to load the aux field, force the type to be
2221 // InitCell so that we actually load the type. We'll assert the proper type
2222 // once we've checked aux.
2223 auto const retTy
= loadAux
2225 : typeFromRAT(func
->repoReturnType(), curClass(env
)) & TInitCell
;
2227 if (func
->isMethod() && !func
->isStatic()) {
2228 auto const cls
= func
->cls();
2229 assertx(cls
!= nullptr);
2230 assertx(cls
->hasMemoSlots());
2232 auto const this_
= checkAndLoadThis(env
);
2233 if (!this_
->isA(Type::SubObj(cls
))) PUNT(MemoGet
);
2235 auto const memoInfo
= cls
->memoSlotForFunc(func
->getFuncId());
2237 if (keys
.count
== 0 && !memoInfo
.second
) {
2240 MemoGetInstanceValue
,
2241 MemoValueInstanceData
{ memoInfo
.first
, func
, folly::none
, loadAux
},
2250 MemoGetInstanceCache
,
2251 MemoCacheInstanceData
{
2267 if (func
->isMemoizeWrapperLSB()) {
2268 /* For LSB memoization, we need the LSB class */
2269 auto const lsbCls
= gen(env
, LdClsCtx
, ldCtx(env
));
2270 if (keys
.count
> 0) {
2274 MemoCacheStaticData
{
2290 MemoValueStaticData
{ func
, folly::none
, loadAux
},
2297 /* Static (non-LSB) Memoization */
2298 if (keys
.count
> 0) {
2302 MemoCacheStaticData
{ func
, keys
, types
.data(), folly::none
, loadAux
},
2311 MemoValueStaticData
{ func
, folly::none
, loadAux
},
2318 pushIncRef(env
, val
);
2324 [&] (Block
* taken
) {
2325 auto const aux
= gen(env
, LdTVAux
, LdTVAuxData
{}, val
);
2326 auto const tst
= gen(env
, AndInt
, aux
, cns(env
, 1u << 31));
2327 gen(env
, JmpNZero
, taken
, tst
);
2335 typeFromRAT(func
->repoAwaitedReturnType(), curClass(env
)) & TInitCell
,
2341 hint(env
, Block::Hint::Unlikely
);
2347 typeFromRAT(func
->repoReturnType(), curClass(env
)) & TInitCell
,
2351 jmpImpl(env
, bcOff(env
) + suspendedOff
);
2358 void emitMemoGet(IRGS
& env
, Offset notfoundOff
, LocalRange keys
) {
2359 memoGetImpl(env
, notfoundOff
, kInvalidOffset
, keys
);
2362 void emitMemoGetEager(IRGS
& env
,
2364 Offset suspendedOff
,
2366 assertx(curFunc(env
)->isAsyncFunction());
2367 assertx(resumeMode(env
) == ResumeMode::None
);
2368 memoGetImpl(env
, notfoundOff
, suspendedOff
, keys
);
2373 void memoSetImpl(IRGS
& env
, LocalRange keys
, bool eager
) {
2374 assertx(curFunc(env
)->isMemoizeWrapper());
2375 assertx(keys
.first
+ keys
.count
<= curFunc(env
)->numLocals());
2376 assertx(!eager
|| curFunc(env
)->isAsyncFunction());
2378 CompactVector
<bool> types
;
2379 for (auto i
= keys
.count
; i
> 0; --i
) {
2380 auto const type
= env
.irb
->local(keys
.first
+ i
- 1, DataTypeSpecific
).type
;
2382 types
.emplace_back(true);
2383 } else if (type
<= TInt
) {
2384 types
.emplace_back(false);
2386 // Let it fatal from the interpreter
2391 auto const ldVal
= [&] (DataTypeCategory tc
) {
2396 topC(env
, BCSPRelOffset
{ 0 }, tc
)
2400 auto const func
= curFunc(env
);
2402 auto const asyncEager
= [&] () -> folly::Optional
<bool> {
2403 if (!func
->isAsyncFunction()) return folly::none
;
2407 if (func
->isMethod() && !func
->isStatic()) {
2408 auto const cls
= func
->cls();
2409 assertx(cls
!= nullptr);
2410 assertx(cls
->hasMemoSlots());
2412 auto const this_
= checkAndLoadThis(env
);
2413 if (!this_
->isA(Type::SubObj(cls
))) PUNT(MemoSet
);
2415 auto const memoInfo
= cls
->memoSlotForFunc(func
->getFuncId());
2417 if (keys
.count
== 0 && !memoInfo
.second
) {
2420 MemoSetInstanceValue
,
2421 MemoValueInstanceData
{ memoInfo
.first
, func
, asyncEager
, false },
2423 ldVal(DataTypeCountness
)
2430 MemoSetInstanceCache
,
2431 MemoCacheInstanceData
{
2442 ldVal(DataTypeGeneric
)
2447 if (func
->isMemoizeWrapperLSB()) {
2448 /* For LSB memoization, we need the LSB class */
2449 auto const lsbCls
= gen(env
, LdClsCtx
, ldCtx(env
));
2450 if (keys
.count
> 0) {
2454 MemoCacheStaticData
{ func
, keys
, types
.data(), asyncEager
, false },
2457 ldVal(DataTypeGeneric
)
2465 MemoValueStaticData
{ func
, asyncEager
, false },
2466 ldVal(DataTypeCountness
),
2471 if (keys
.count
> 0) {
2475 MemoCacheStaticData
{ func
, keys
, types
.data(), asyncEager
, false },
2477 ldVal(DataTypeGeneric
)
2485 MemoValueStaticData
{ func
, asyncEager
, false },
2486 ldVal(DataTypeCountness
)
2492 void emitMemoSet(IRGS
& env
, LocalRange keys
) {
2493 memoSetImpl(env
, keys
, false);
2496 void emitMemoSetEager(IRGS
& env
, LocalRange keys
) {
2497 assertx(curFunc(env
)->isAsyncFunction());
2498 assertx(resumeMode(env
) == ResumeMode::None
);
2499 memoSetImpl(env
, keys
, true);
2502 //////////////////////////////////////////////////////////////////////
2504 void emitSilence(IRGS
& env
, Id localId
, SilenceOp subop
) {
2505 // We can't generate direct StLoc and LdLocs in pseudomains (violates an IR
2507 if (curFunc(env
)->isPseudoMain()) PUNT(PseudoMain
-Silence
);
2510 case SilenceOp::Start
:
2511 // We assume that whatever is in the local is dead and doesn't need to be
2512 // refcounted before being overwritten.
2513 gen(env
, AssertLoc
, TUncounted
, LocalId(localId
), fp(env
));
2514 gen(env
, StLoc
, LocalId(localId
), fp(env
), gen(env
, ZeroErrorLevel
));
2516 case SilenceOp::End
:
2518 gen(env
, AssertLoc
, TInt
, LocalId(localId
), fp(env
));
2519 auto const level
= ldLoc(env
, localId
, makeExit(env
), DataTypeGeneric
);
2520 gen(env
, RestoreErrorLevel
, level
);
2526 //////////////////////////////////////////////////////////////////////