Do not spill ActRec in catches of inlined builtins
[hiphop-php.git] / hphp / runtime / vm / jit / irgen-builtin.cpp
blob0fc9247be62a84d334d1ff12a1704ea4ca5882f6
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
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 {
51 namespace {
53 //////////////////////////////////////////////////////////////////////
55 struct ParamPrep {
56 explicit ParamPrep(size_t count) : info(count) {}
58 struct Info {
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 //////////////////////////////////////////////////////////////////////
79 const StaticString
80 s_is_a("is_a"),
81 s_is_subclass_of("is_subclass_of"),
82 s_method_exists("method_exists"),
83 s_count("count"),
84 s_sizeof("sizeof"),
85 s_ini_get("ini_get"),
86 s_in_array("in_array"),
87 s_get_class("get_class"),
88 s_sqrt("sqrt"),
89 s_strlen("strlen"),
90 s_clock_gettime_ns("clock_gettime_ns"),
91 s_microtime("microtime"),
92 s_max2("__SystemLib\\max2"),
93 s_min2("__SystemLib\\min2"),
94 s_ceil("ceil"),
95 s_floor("floor"),
96 s_abs("abs"),
97 s_ord("ord"),
98 s_chr("chr"),
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"),
102 s_one("1"),
103 s_empty(""),
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(
116 TDbl,
117 TInt,
118 TNull,
119 TObj,
120 TRes,
121 TStr,
122 TBool
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);
139 gen(env, EndCatch,
140 IRSPRelOffsetData { spOffBCFromIRSP(env) },
141 fp(env), sp(env));
142 return exit;
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)) {
155 return nullptr;
158 auto const objCls = gen(env, LdObjClass, obj);
160 auto const cls = Unit::lookupUniqueClassInContext(classname->strVal(),
161 curClass(env));
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
170 // as the RHS.
171 return cond(
172 env,
173 [&] (Block* taken) {
174 auto const eq = gen(env, EqCls, objCls, testCls);
175 gen(env, JmpNZero, taken, eq);
177 [&] {
178 return gen(env, InstanceOf, objCls, testCls);
180 [&] {
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;
217 // Count may throw
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);
234 return nullptr;
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);
246 return nullptr;
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
254 // at JIT time.
255 auto const argType = params[0].value->type();
256 if (!(argType.hasConstVal(TStaticStr))) {
257 return nullptr;
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)) {
271 return nullptr;
273 if (mode & ~IniSetting::PHP_INI_SYSTEM) {
274 return nullptr;
276 if (mode == IniSetting::PHP_INI_ALL) { /* PHP_INI_ALL has a weird encoding */
277 return nullptr;
280 Variant value;
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()) {
290 return cns(
291 env,
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.
297 return nullptr;
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)) {
311 return nullptr;
314 auto const haystackType = params[1].value->type();
315 if (!haystackType.hasConstVal(TStaticArr)) {
316 // Haystack isn't statically known
317 return nullptr;
320 auto const haystack = haystackType.arrVal();
321 if (haystack->size() == 0) {
322 return cns(env, false);
325 KeysetInit flipped{haystack->size()};
326 bool failed{false};
327 IterateVNoInc(
328 haystack,
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.
337 failed = true;
338 return true;
341 flipped.add(val(key).pstr);
342 return false;
345 if (failed) {
346 return nullptr;
349 return gen(
350 env,
351 AKExistsKeyset,
352 cns(env, ArrayData::GetScalarArray(flipped.toArray())),
353 needle
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) {
363 return curName();
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) {
370 return curName();
372 if (ty <= TObj) {
373 auto const cls = gen(env, LdObjClass, val);
374 return gen(env, LdClsName, cls);
377 return nullptr;
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);
386 return nullptr;
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();
395 if (ty <= TStr) {
396 return gen(env, LdStrLen, val);
399 return nullptr;
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);
412 return nullptr;
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);
424 return nullptr;
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);
440 } else {
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);
445 }();
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;
464 // May throw
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;
474 // May throw
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);
493 return nullptr;
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);
509 return nullptr;
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));
526 }();
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);
536 return cond(
537 env,
538 [&] (Block* taken) {
539 gen(env, CheckTypeMem, TUncountedInit, taken, ptr);
540 return gen(env, LdTypeCns, taken, gen(env, LdMem, TUncountedInit, ptr));
542 [&] (SSATmp* cns) { return cns; },
543 [&] /* taken */ {
544 return gen(
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
555 // bail out here.
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);
563 return nullptr;
566 SSATmp* opt_foldable(IRGS& env,
567 const Func* func,
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
598 // the call alone.
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)) {
603 return nullptr;
604 } else {
605 args.append(params[i].value->variantVal());
608 if (variadicArgs) {
609 for (auto i = 0; i < numVariadicArgs; i++) {
610 args.append(variadicArgs->get(i).tv());
614 try {
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),
636 nullptr, nullptr,
637 ExecutionContext::InvokeNormal,
638 !func->unit()->useStrictTypes(),
639 false);
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) {
648 case KindOfNull:
649 case KindOfBoolean:
650 case KindOfInt64:
651 case KindOfDouble:
652 return cns(env, retVal);
653 case KindOfPersistentString:
654 case KindOfString:
655 return cns(env, makeStaticString(retVal.m_data.pstr));
656 case KindOfPersistentVec:
657 case KindOfVec:
658 return cns(
659 env,
660 make_tv<KindOfPersistentVec>(scalar_array())
662 case KindOfPersistentDict:
663 case KindOfDict:
664 return cns(
665 env,
666 make_tv<KindOfPersistentDict>(scalar_array())
668 case KindOfPersistentKeyset:
669 case KindOfKeyset:
670 return cns(
671 env,
672 make_tv<KindOfPersistentKeyset>(scalar_array())
674 case KindOfPersistentShape:
675 case KindOfShape:
676 return cns(
677 env,
678 make_tv<KindOfPersistentShape>(scalar_array())
680 case KindOfPersistentArray:
681 case KindOfArray:
682 return cns(
683 env,
684 make_tv<KindOfPersistentArray>(scalar_array())
686 case KindOfUninit:
687 case KindOfObject:
688 case KindOfResource:
689 case KindOfRef:
690 // TODO (T29639296)
691 case KindOfFunc:
692 case KindOfClass:
693 case KindOfClsMeth:
694 case KindOfRecord: // TODO(arnabde)
695 return nullptr;
697 } catch (...) {
698 // If an exception or notice occurred, don't optimize
700 return nullptr;
704 * Container intrinsic for HH\traversable
706 SSATmp* opt_container_first(IRGS& env, const ParamPrep& params) {
707 if (params.size() != 1) {
708 return nullptr;
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);
714 gen(env, IncRef, r);
715 return r;
717 if (type <= TDict || type <= Type::Array(ArrayData::kMixedKind)) {
718 auto const r = gen(env, DictFirst, value);
719 gen(env, IncRef, r);
720 return r;
722 if (type <= TKeyset) {
723 auto const r = gen(env, KeysetFirst, value);
724 gen(env, IncRef, r);
725 return r;
727 return nullptr;
730 SSATmp* opt_container_last(IRGS& env, const ParamPrep& params) {
731 if (params.size() != 1) {
732 return nullptr;
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);
738 gen(env, IncRef, r);
739 return r;
741 if (type <= TDict || type <= Type::Array(ArrayData::kMixedKind)) {
742 auto const r = gen(env, DictLast, value);
743 gen(env, IncRef, r);
744 return r;
746 if (type <= TKeyset) {
747 auto const r = gen(env, KeysetLast, value);
748 gen(env, IncRef, r);
749 return r;
751 return nullptr;
754 SSATmp* opt_container_first_key(IRGS& env, const ParamPrep& params) {
755 if (params.size() != 1) {
756 return nullptr;
758 auto const value = params[0].value;
759 auto const type = value->type();
761 if (type <= TVec || type <= Type::Array(ArrayData::kPackedKind)) {
762 return cond(
763 env,
764 [&](Block* taken) {
765 auto const length = type <= TVec ?
766 gen(env, CountVec, value) : gen(env, CountArray, value);
767 gen(env, JmpZero, taken, length);
769 [&] {
770 return cns(env, 0);
772 [&] {
773 return cns(env, TInitNull);
777 if (type <= TDict || type <= Type::Array(ArrayData::kMixedKind)) {
778 auto const r = gen(env, DictFirstKey, value);
779 gen(env, IncRef, r);
780 return r;
782 if (type <= TKeyset) {
783 auto const r = gen(env, KeysetFirst, value);
784 gen(env, IncRef, r);
785 return r;
787 return nullptr;
790 SSATmp* opt_container_last_key(IRGS& env, const ParamPrep& params) {
791 if (params.size() != 1) {
792 return nullptr;
794 auto const value = params[0].value;
795 auto const type = value->type();
797 if (type <= TVec || type <= Type::Array(ArrayData::kPackedKind)) {
798 return cond(
799 env,
800 [&](Block* taken) {
801 auto const length = type <= TVec ?
802 gen(env, CountVec, value) : gen(env, CountArray, value);
803 gen(env, JmpZero, taken, length);
804 return length;
806 [&] (SSATmp* next) {
807 return gen(env, SubInt, next, cns(env, 1));
809 [&] {
810 return cns(env, TInitNull);
814 if (type <= TDict || type <= Type::Array(ArrayData::kMixedKind)) {
815 auto const r = gen(env, DictLastKey, value);
816 gen(env, IncRef, r);
817 return r;
819 if (type <= TKeyset) {
820 auto const r = gen(env, KeysetLast, value);
821 gen(env, IncRef, r);
822 return r;
824 return nullptr;
827 namespace {
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));
842 return nullptr;
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));
855 return nullptr;
858 //////////////////////////////////////////////////////////////////////
860 SSATmp* optimizedFCallBuiltin(IRGS& env,
861 const Func* func,
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)) {
869 return retVal;
871 #define X(x) \
872 if (fname->isame(s_##x.get())) return opt_##x(env, params);
874 X(get_class)
875 X(in_array)
876 X(ini_get)
877 X(count)
878 X(sizeof)
879 X(is_a)
880 X(is_subclass_of)
881 X(method_exists)
882 X(sqrt)
883 X(strlen)
884 X(clock_gettime_ns)
885 X(microtime)
886 X(max2)
887 X(ceil)
888 X(floor)
889 X(abs)
890 X(ord)
891 X(chr)
892 X(min2)
893 X(array_key_cast)
894 X(type_structure)
895 X(is_list_like)
896 X(container_first)
897 X(container_last)
898 X(container_first_key)
899 X(container_last_key)
900 X(class_meth_get_class)
901 X(class_meth_get_method)
903 #undef X
905 return nullptr;
906 }();
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);
922 return result;
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
934 * returns TBottom.
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) {
945 return TBoxedCell;
947 if (!pi.builtinType) return tc.isVArrayOrDArray() ? TArr : TBottom;
948 if (pi.builtinType == KindOfObject &&
949 pi.defaultValue.m_type == KindOfNull) {
950 return TNullableObj;
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
960 * conversions.
962 template <class LoadParam>
963 ParamPrep
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);
968 ret.thiz = thiz;
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) {
991 continue;
994 ++ret.numByAddr;
995 cur.passByAddr = true;
998 return ret;
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.
1032 struct CatchMaker {
1033 enum class Kind { NotInlining, Inlining };
1035 explicit CatchMaker(IRGS& env, Kind kind, const ParamPrep* params)
1036 : env(env)
1037 , m_kind(kind)
1038 , m_params(*params)
1040 assertx(!m_params.thiz || m_params.forNativeImpl || inlining());
1043 CatchMaker(const CatchMaker&) = delete;
1044 CatchMaker(CatchMaker&&) = default;
1046 bool inlining() const {
1047 switch (m_kind) {
1048 case Kind::NotInlining: return false;
1049 case Kind::Inlining: return true;
1051 not_reached();
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);
1058 decRefParams();
1059 prepareForCatch();
1060 gen(env, EndCatch,
1061 IRSPRelOffsetData { spOffBCFromIRSP(env) },
1062 fp(env), sp(env));
1063 return exit;
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) {
1081 popDecRef(env);
1082 } else {
1083 decRef(env, pi.value);
1088 private:
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.
1107 private:
1108 IRGS& env;
1109 Kind const m_kind;
1110 const ParamPrep& m_params;
1113 //////////////////////////////////////////////////////////////////////
1115 SSATmp* coerce_value(IRGS& env,
1116 const Type& ty,
1117 const Func* callee,
1118 SSATmp* oldVal,
1119 uint32_t paramIdx,
1120 const CatchMaker& maker) {
1121 auto const result = [&] () -> SSATmp* {
1122 if (ty <= TInt) {
1123 return gen(env,
1124 CoerceCellToInt,
1125 FuncArgData(callee, paramIdx + 1),
1126 maker.makeUnusualCatch(),
1127 oldVal);
1129 if (ty <= TDbl) {
1130 return gen(env,
1131 CoerceCellToDbl,
1132 FuncArgData(callee, paramIdx + 1),
1133 maker.makeUnusualCatch(),
1134 oldVal);
1136 if (ty <= TBool) {
1137 return gen(env,
1138 CoerceCellToBool,
1139 FuncArgData(callee, paramIdx + 1),
1140 maker.makeUnusualCatch(),
1141 oldVal);
1144 return nullptr;
1145 }();
1147 if (result) {
1148 decRef(env, oldVal);
1149 return result;
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,
1164 const Type& ty,
1165 const Func* callee,
1166 uint32_t paramIdx,
1167 BCSPRelOffset offset,
1168 const CatchMaker& maker) {
1169 always_assert(ty.isKnownDataType());
1170 gen(env,
1171 CoerceStk,
1173 CoerceStkData { offsetFromIRSP(env, offset), callee, paramIdx + 1 },
1174 maker.makeUnusualCatch(),
1175 sp(env));
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
1192 * return nullptr;
1193 * convertParam(ty):
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.
1197 * realize():
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,
1205 const Func* callee,
1206 Type targetTy,
1207 V checkType,
1208 C convertParam,
1209 R realize) {
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(
1216 env,
1217 [&] (Block* convert) -> SSATmp* {
1218 if (targetTy == baseTy) {
1219 return checkType(baseTy, convert);
1221 return cond(
1222 env,
1223 [&] (Block* fail) { return checkType(baseTy, fail); },
1224 [&] (SSATmp* v) { return v; },
1225 [&] {
1226 return checkType(TInitNull, convert);
1229 [&] (SSATmp* v) { return v; },
1230 [&] () -> SSATmp* {
1231 return convertParam(convertTy);
1233 )) {
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;
1243 return realize();
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,
1252 const Func* callee,
1253 ParamPrep& params,
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;
1269 return ty <= TArr;
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));
1285 ifThen(
1286 env,
1287 check,
1288 [&]{
1289 gen(
1290 env,
1291 RaiseHackArrParamNotice,
1292 RaiseHackArrParamNoticeData { tc.type(), int32_t(param), false },
1293 maker.makeUnusualCatch(),
1294 val,
1295 cns(env, callee)
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);
1328 return nullptr;
1330 [&] {
1331 if (!param.passByAddr && !param.isOutputArg) {
1332 assertx(targetTy == TBool ||
1333 targetTy == TInt ||
1334 targetTy == TDbl ||
1335 callee->params()[paramIdx].nativeArg);
1336 return gen(env, LdMem,
1337 targetTy == TBottom ? TCell : targetTy,
1338 param.value);
1340 return param.value;
1342 continue;
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);
1353 return ret;
1355 [&] (const Type& ty) {
1356 if (param.isOutputArg) return cns(env, TNullptr);
1357 return coerce_value(env, ty, callee, oldVal, paramIdx, maker);
1359 [&] {
1361 * This gets tricky:
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
1370 * value).
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;
1386 return v;
1388 continue;
1391 usedStack = true;
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)) {
1402 dvCheck(
1403 paramIdx,
1404 gen(env, LdStk, ty, IRSPRelOffsetData { irSPRel }, sp(env))
1407 return nullptr;
1409 [&] (const Type& ty) -> SSATmp* {
1410 coerce_stack(env, ty, callee, paramIdx, offset, maker);
1411 return nullptr;
1413 [&] {
1414 return ldStkAddr(env, offset);
1416 ++stackIdx;
1419 assertx(seenBottom || !usedStack || stackIdx == params.numByAddr);
1420 assertx(argIdx == cbNumArgs);
1422 return ret;
1425 //////////////////////////////////////////////////////////////////////
1427 SSATmp* builtinCall(IRGS& env,
1428 const Func* callee,
1429 ParamPrep& params,
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
1435 // builtin
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.
1463 updateMarker(env);
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(
1475 env,
1476 CallBuiltin,
1477 CallBuiltinData {
1478 spOffBCFromIRSP(env),
1479 callee,
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();
1494 return ret;
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
1505 * the callee.
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);
1519 }();
1521 auto numNonDefault = fp(env)->inst()->extra<DefInlineFP>()->numNonDefault;
1522 auto params = prepare_params(
1523 env,
1524 callee,
1525 paramThis,
1526 nullptr,
1527 numArgs,
1528 numNonDefault,
1529 false,
1530 [&] (uint32_t i, const Type) {
1531 return ldLoc(env, i, nullptr, DataTypeSpecific);
1535 implInlineReturn(env);
1537 auto const catcher = CatchMaker {
1538 env,
1539 CatchMaker::Kind::Inlining,
1540 &params
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));
1573 return cond(
1574 env,
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,
1592 uint32_t numArgs,
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(
1601 env, callee,
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) {
1605 auto specificity =
1606 ty == TBottom ? DataTypeGeneric : DataTypeSpecific;
1607 return pop(env, specificity);
1610 auto const catcher = CatchMaker {
1611 env,
1612 CatchMaker::Kind::NotInlining,
1613 &params
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();
1633 return;
1636 auto thiz = callee->isMethod() ? ldCtx(env) : nullptr;
1637 auto const numParams = gen(env, LdARNumParams, fp(env));
1639 ifThenElse(
1640 env,
1641 [&] (Block* fallback) {
1642 if (thiz) {
1643 if (!hasThis(env)) {
1644 thiz = gen(env, LdClsCtx, thiz);
1645 } else {
1646 thiz = castCtxThis(env, thiz);
1650 auto maxArgs = callee->numParams();
1651 auto minArgs = callee->numNonVariadicParams();
1652 while (minArgs) {
1653 auto const& pi = callee->params()[minArgs - 1];
1654 if (pi.funcletOff == InvalidAbsoluteOffset) {
1655 break;
1657 --minArgs;
1659 if (callee->hasVariadicCaptureParam()) {
1660 if (minArgs) {
1661 auto const check = gen(env, LtInt, numParams, cns(env, minArgs));
1662 gen(env, JmpNZero, fallback, check);
1664 } else {
1665 if (minArgs == maxArgs) {
1666 auto const check = gen(env, EqInt, numParams, cns(env, minArgs));
1667 gen(env, JmpZero, fallback, check);
1668 } else {
1669 if (minArgs) {
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);
1679 [&] {
1680 auto params = prepare_params(
1681 env,
1682 callee,
1683 thiz,
1684 nullptr,
1685 callee->numParams(),
1686 callee->numParams(),
1687 true,
1688 [&] (uint32_t i, const Type) {
1689 return gen(env, LdLocAddr, LocalId(i), fp(env));
1692 auto const catcher = CatchMaker {
1693 env,
1694 CatchMaker::Kind::NotInlining,
1695 &params
1698 auto const ret = builtinCall(env, callee, params,
1699 callee->numParams(), catcher);
1700 push(env, ret);
1701 emitRetC(env);
1703 [&] {
1704 hint(env, Block::Hint::Unlikely);
1705 genericNativeImpl();
1710 //////////////////////////////////////////////////////////////////////
1712 namespace {
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()) {
1730 return
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()) {
1741 return
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()) {
1751 return
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());
1759 return false;
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
1769 // type.
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).
1776 auto type = [&]{
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};
1791 return TInitCell;
1792 }();
1794 // "Reference" types (not boxed, types represented by a pointer) can always be
1795 // null.
1796 if (type.isReferenceType()) {
1797 type |= TInitNull;
1798 } else {
1799 assertx(type == TInitCell || type.isSimpleType());
1802 return type & TInitCell;
1805 /////////////////////////////////////////////////////////////////////
1807 namespace {
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
1812 // function.
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
1821 push(env, def);
1822 decRef(env, base);
1823 decRef(env, key);
1824 return;
1826 if (!(keyType <= TInt || keyType <= TStr)) {
1827 interpOne(env, TCell, 3);
1828 return;
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);
1841 [&] (SSATmp* 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);
1849 decRef(env, base);
1850 decRef(env, key);
1851 decRef(env, def);
1854 auto const pelem = profiledType(env, elem, [&] { finish(elem); });
1855 finish(pelem);
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);
1865 decRef(env, def);
1866 decRef(env, key);
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.
1875 finish(def);
1876 updateMarker(env);
1877 env.irb->exceptionStackBoundary();
1878 gen(env, ThrowInvalidArrayKey, stack_base, key);
1879 return;
1882 auto const use_base = loaded_collection_vec
1883 ? loaded_collection_vec
1884 : stack_base;
1885 assertx(use_base->isA(TVec));
1887 auto const elem = cond(
1888 env,
1889 [&] (Block* taken) {
1890 gen(env, CheckPackedArrayDataBounds, taken, use_base, key);
1892 [&] { return gen(env, LdVecElem, use_base, key); },
1893 [&] { return def; }
1896 auto const pelem = profiledType(env, elem, [&] { finish(elem); } );
1897 finish(pelem);
1900 void implDictKeysetIdx(IRGS& env,
1901 bool is_dict,
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);
1909 decRef(env, def);
1910 decRef(env, key);
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.
1919 finish(def);
1920 updateMarker(env);
1921 env.irb->exceptionStackBoundary();
1922 gen(env, ThrowInvalidArrayKey, stack_base, key);
1923 return;
1926 assertx(is_dict || !loaded_collection_dict);
1927 auto const use_base = loaded_collection_dict
1928 ? loaded_collection_dict
1929 : stack_base;
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 },
1935 base, key);
1937 [&] (SSATmp* key) {
1938 return gen(env, is_dict ? DictIdx : KeysetIdx, use_base, key, def);
1942 auto const pelem = profiledType(env, elem, [&] { finish(elem); });
1943 finish(pelem);
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
1963 * Idx bytecode.
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)) &&
1974 keyType <= TInt;
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)) {
2000 // raise fatal
2001 interpOne(env, TCell, 3);
2002 return;
2005 implArrayIdx(env);
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);
2022 push(env, def);
2023 decRef(env, base);
2024 decRef(env, key);
2025 return;
2028 auto const simple_key =
2029 keyType <= TInt || keyType <= TStr;
2031 if (!simple_key) {
2032 implGenericIdx(env);
2033 return;
2036 if (baseType <= TArr) {
2037 implArrayIdx(env);
2038 return;
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);
2047 if (useVec) {
2048 auto const vec = gen(env, LdColVec, base);
2049 implVecIdx(env, vec);
2050 } else {
2051 auto const dict = gen(env, LdColDict, base);
2052 implDictKeysetIdx(env, true, dict);
2054 return;
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));
2069 decRef(env, arr);
2070 decRef(env, key);
2071 updateMarker(env);
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(
2080 env,
2081 [&](Block* taken) {
2082 gen(env, CheckPackedArrayDataBounds, taken, arr, key);
2084 [&] { return cns(env, true); },
2085 [&] { return cns(env, false); }
2087 push(env, result);
2088 decRef(env, arr);
2091 if (arr->isA(TVec)) {
2092 if (key->isA(TNull | TStr)) {
2093 push(env, cns(env, false));
2094 decRef(env, arr);
2095 decRef(env, key);
2096 return;
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));
2107 decRef(env, arr);
2108 decRef(env, key);
2109 return;
2111 if (!key->isA(TInt) && !key->isA(TStr)) {
2112 return throwBadKey();
2114 auto const val = gen(
2115 env,
2116 arr->isA(TDict) ? AKExistsDict : AKExistsKeyset,
2117 arr,
2120 push(env, val);
2121 decRef(env, arr);
2122 decRef(env, key);
2123 return;
2126 if (!arr->isA(TArr) && !arr->isA(TObj)) PUNT(AKExists_badArray);
2128 if (key->isA(TInitNull) && arr->isA(TArr)) {
2129 if (checkHACArrayKeyCast()) {
2130 gen(
2131 env,
2132 RaiseHackArrCompatNotice,
2133 cns(
2134 env,
2135 makeStaticString(
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)) {
2150 auto const val =
2151 gen(env, CheckRange, key, gen(env, CountCollection, arr));
2152 push(env, val);
2153 decRef(env, arr);
2154 return;
2156 if (arr->isA(TArr) && key->isA(TInt) &&
2157 arr->type().arrSpec().kind() == ArrayData::kPackedKind) {
2158 return check_packed();
2161 auto const val =
2162 gen(env, arr->isA(TArr) ? AKExistsArr : AKExistsObj, arr, key);
2163 push(env, val);
2164 decRef(env, arr);
2165 decRef(env, 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(
2176 env,
2177 locId,
2178 makeExit(env),
2179 nullptr,
2180 DataTypeSpecific
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));
2188 namespace {
2190 void memoGetImpl(IRGS& env,
2191 Offset notfoundOff,
2192 Offset suspendedOff,
2193 LocalRange keys) {
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;
2201 if (type <= TStr) {
2202 types.emplace_back(true);
2203 } else if (type <= TInt) {
2204 types.emplace_back(false);
2205 } else {
2206 // Let it fatal from the interpreter
2207 PUNT(MemoGet);
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
2224 ? TInitCell
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) {
2238 return gen(
2239 env,
2240 MemoGetInstanceValue,
2241 MemoValueInstanceData { memoInfo.first, func, folly::none, loadAux },
2242 notFound,
2243 retTy,
2244 this_
2248 return gen(
2249 env,
2250 MemoGetInstanceCache,
2251 MemoCacheInstanceData {
2252 memoInfo.first,
2253 keys,
2254 types.data(),
2255 func,
2256 memoInfo.second,
2257 folly::none,
2258 loadAux
2260 notFound,
2261 retTy,
2262 fp(env),
2263 this_
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) {
2271 return gen(
2272 env,
2273 MemoGetLSBCache,
2274 MemoCacheStaticData {
2275 func,
2276 keys,
2277 types.data(),
2278 folly::none,
2279 loadAux
2281 notFound,
2282 retTy,
2283 fp(env),
2284 lsbCls
2287 return gen(
2288 env,
2289 MemoGetLSBValue,
2290 MemoValueStaticData { func, folly::none, loadAux },
2291 notFound,
2292 retTy,
2293 lsbCls
2297 /* Static (non-LSB) Memoization */
2298 if (keys.count > 0) {
2299 return gen(
2300 env,
2301 MemoGetStaticCache,
2302 MemoCacheStaticData { func, keys, types.data(), folly::none, loadAux },
2303 notFound,
2304 retTy,
2305 fp(env)
2308 return gen(
2309 env,
2310 MemoGetStaticValue,
2311 MemoValueStaticData { func, folly::none, loadAux },
2312 notFound,
2313 retTy
2315 }();
2317 if (!loadAux) {
2318 pushIncRef(env, val);
2319 return;
2322 ifThenElse(
2323 env,
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);
2329 [&] {
2330 pushIncRef(
2331 env,
2332 gen(
2333 env,
2334 AssertType,
2335 typeFromRAT(func->repoAwaitedReturnType(), curClass(env)) & TInitCell,
2340 [&] {
2341 hint(env, Block::Hint::Unlikely);
2342 pushIncRef(
2343 env,
2344 gen(
2345 env,
2346 AssertType,
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,
2363 Offset notfoundOff,
2364 Offset suspendedOff,
2365 LocalRange keys) {
2366 assertx(curFunc(env)->isAsyncFunction());
2367 assertx(resumeMode(env) == ResumeMode::None);
2368 memoGetImpl(env, notfoundOff, suspendedOff, keys);
2371 namespace {
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;
2381 if (type <= TStr) {
2382 types.emplace_back(true);
2383 } else if (type <= TInt) {
2384 types.emplace_back(false);
2385 } else {
2386 // Let it fatal from the interpreter
2387 PUNT(MemoSet);
2391 auto const ldVal = [&] (DataTypeCategory tc) {
2392 return gen(
2393 env,
2394 AssertType,
2395 TInitCell,
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;
2404 return eager;
2405 }();
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) {
2418 gen(
2419 env,
2420 MemoSetInstanceValue,
2421 MemoValueInstanceData { memoInfo.first, func, asyncEager, false },
2422 this_,
2423 ldVal(DataTypeCountness)
2425 return;
2428 gen(
2429 env,
2430 MemoSetInstanceCache,
2431 MemoCacheInstanceData {
2432 memoInfo.first,
2433 keys,
2434 types.data(),
2435 func,
2436 memoInfo.second,
2437 asyncEager,
2438 false
2440 fp(env),
2441 this_,
2442 ldVal(DataTypeGeneric)
2444 return;
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) {
2451 gen(
2452 env,
2453 MemoSetLSBCache,
2454 MemoCacheStaticData { func, keys, types.data(), asyncEager, false },
2455 fp(env),
2456 lsbCls,
2457 ldVal(DataTypeGeneric)
2459 return;
2462 gen(
2463 env,
2464 MemoSetLSBValue,
2465 MemoValueStaticData { func, asyncEager, false },
2466 ldVal(DataTypeCountness),
2467 lsbCls
2471 if (keys.count > 0) {
2472 gen(
2473 env,
2474 MemoSetStaticCache,
2475 MemoCacheStaticData { func, keys, types.data(), asyncEager, false },
2476 fp(env),
2477 ldVal(DataTypeGeneric)
2479 return;
2482 gen(
2483 env,
2484 MemoSetStaticValue,
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
2506 // invariant).
2507 if (curFunc(env)->isPseudoMain()) PUNT(PseudoMain-Silence);
2509 switch (subop) {
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));
2515 break;
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);
2522 break;
2526 //////////////////////////////////////////////////////////////////////