Better idx handling
[hiphop-php.git] / hphp / runtime / vm / jit / irgen-builtin.cpp
blob6e98a201bdc02b206e3e1407f5de1d77e81b9037
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-2016 Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
16 #include "hphp/runtime/vm/jit/irgen-builtin.h"
18 #include "hphp/runtime/base/array-init.h"
19 #include "hphp/runtime/base/collections.h"
20 #include "hphp/runtime/vm/jit/analysis.h"
21 #include "hphp/runtime/vm/jit/func-effects.h"
22 #include "hphp/runtime/vm/jit/type-constraint.h"
23 #include "hphp/runtime/vm/jit/type.h"
25 #include "hphp/runtime/vm/jit/irgen-ret.h"
26 #include "hphp/runtime/vm/jit/irgen-inlining.h"
27 #include "hphp/runtime/vm/jit/irgen-call.h"
28 #include "hphp/runtime/vm/jit/irgen-exit.h"
29 #include "hphp/runtime/vm/jit/irgen-interpone.h"
30 #include "hphp/runtime/vm/jit/irgen-types.h"
31 #include "hphp/runtime/vm/jit/irgen-internal.h"
32 #include "hphp/runtime/ext_zend_compat/hhvm/zend-wrap-func.h"
33 #include "hphp/runtime/base/file-util.h"
35 namespace HPHP { namespace jit { namespace irgen {
37 namespace {
39 //////////////////////////////////////////////////////////////////////
41 const StaticString
42 s_is_a("is_a"),
43 s_is_subclass_of("is_subclass_of"),
44 s_method_exists("method_exists"),
45 s_count("count"),
46 s_ini_get("ini_get"),
47 s_dirname("dirname"),
48 s_86metadata("86metadata"),
49 s_set_frame_metadata("hh\\set_frame_metadata"),
50 s_in_array("in_array"),
51 s_get_class("get_class"),
52 s_get_called_class("get_called_class"),
53 s_sqrt("sqrt"),
54 s_strlen("strlen"),
55 s_max2("__SystemLib\\max2"),
56 s_min2("__SystemLib\\min2"),
57 s_ceil("ceil"),
58 s_floor("floor"),
59 s_abs("abs"),
60 s_ord("ord"),
61 s_func_num_args("__SystemLib\\func_num_arg_"),
62 s_one("1"),
63 s_empty("");
65 //////////////////////////////////////////////////////////////////////
67 // Will turn into either an int or a double in zend_convert_scalar_to_number.
68 bool type_converts_to_number(Type ty) {
69 return ty.subtypeOfAny(
70 TDbl,
71 TInt,
72 TNull,
73 TObj,
74 TRes,
75 TStr,
76 TBool
80 //////////////////////////////////////////////////////////////////////
82 SSATmp* is_a_impl(IRGS& env, uint32_t numArgs, bool subclassOnly) {
83 if (numArgs != 3) return nullptr;
85 auto const allowString = topC(env, BCSPOffset{0});
86 auto const classname = topC(env, BCSPOffset{1});
87 auto const obj = topC(env, BCSPOffset{2});
89 if (!obj->isA(TObj) ||
90 !classname->hasConstVal(TStr) ||
91 !allowString->isA(TBool)) {
92 return nullptr;
95 auto const objCls = gen(env, LdObjClass, obj);
97 SSATmp* testCls = nullptr;
98 if (auto const cls = Unit::lookupClassOrUniqueClass(classname->strVal())) {
99 if (classIsUniqueOrCtxParent(env, cls)) testCls = cns(env, cls);
101 if (testCls == nullptr) return nullptr;
103 // is_a() finishes here.
104 if (!subclassOnly) return gen(env, InstanceOf, objCls, testCls);
106 // is_subclass_of() needs to check that the LHS doesn't have the same class as
107 // as the RHS.
108 return cond(
109 env,
110 [&] (Block* taken) {
111 auto const eq = gen(env, EqCls, objCls, testCls);
112 gen(env, JmpNZero, taken, eq);
114 [&] {
115 return gen(env, InstanceOf, objCls, testCls);
117 [&] {
118 return cns(env, false);
123 SSATmp* opt_is_a(IRGS& env, uint32_t numArgs) {
124 return is_a_impl(env, numArgs, false /* subclassOnly */);
127 SSATmp* opt_is_subclass_of(IRGS& env, uint32_t numArgs) {
128 return is_a_impl(env, numArgs, true /* subclassOnly */);
131 SSATmp* opt_method_exists(IRGS& env, uint32_t numArgs) {
132 if (numArgs != 2) return nullptr;
134 auto const meth = topC(env, BCSPOffset{0});
135 auto const obj = topC(env, BCSPOffset{1});
137 if (!obj->isA(TObj) || !meth->isA(TStr)) return nullptr;
139 auto const cls = gen(env, LdObjClass, obj);
140 return gen(env, MethodExists, cls, meth);
143 SSATmp* opt_count(IRGS& env, uint32_t numArgs) {
144 if (numArgs != 2) return nullptr;
146 auto const mode = topC(env, BCSPOffset{0});
147 auto const val = topC(env, BCSPOffset{1});
149 // Bail if we're trying to do a recursive count()
150 if (!mode->hasConstVal(0)) return nullptr;
152 return gen(env, Count, val);
155 SSATmp* opt_ord(IRGS& env, uint32_t numArgs) {
156 if (numArgs != 1) return nullptr;
158 auto const arg = topC(env, BCSPOffset{0});
159 auto const arg_type = arg->type();
160 if (arg_type <= TStr) {
161 return gen(env, OrdStr, arg);
164 // In strict mode type mismatches won't be coerced (for legacy reasons in HH
165 // files builtins are always weak).
166 if (curFunc(env)->unit()->useStrictTypes() &&
167 !curFunc(env)->unit()->isHHFile() &&
168 !RuntimeOption::EnableHipHopSyntax) {
169 return nullptr;
172 // intercept constant, non-string ord() here instead of OrdStr simplify stage.
173 // OrdStr depends on a string as input for its vasm implementation.
174 if (arg->hasConstVal(TBool)) {
175 // ord((string)true)===ord("1"), ord((string)false)===ord("")
176 return cns(env, int64_t{arg_type.boolVal() ? '1' : 0});
178 if (arg_type <= TNull) {
179 return cns(env, int64_t{0});
181 if (arg->hasConstVal(TInt)) {
182 const auto conv = folly::to<std::string>(arg_type.intVal());
183 return cns(env, int64_t{conv[0]});
185 if (arg->hasConstVal(TDbl)) {
186 const auto conv = folly::to<std::string>(arg_type.dblVal());
187 return cns(env, int64_t{conv[0]});
190 return nullptr;
193 SSATmp* opt_func_num_args(IRGS& env, uint32_t numArgs) {
194 if (numArgs != 0 || curFunc(env)->isPseudoMain()) return nullptr;
195 return gen(env, LdARNumParams, fp(env));
198 SSATmp* opt_ini_get(IRGS& env, uint32_t numArgs) {
199 if (numArgs != 1) return nullptr;
201 // Only generate the optimized version if the argument passed in is a
202 // static string with a constant literal value so we can get the string value
203 // at JIT time.
204 auto const argType = topType(env, BCSPOffset{0});
205 if (!(argType.hasConstVal(TStaticStr))) {
206 return nullptr;
209 // We can only optimize settings that are system wide since user level
210 // settings can be overridden during the execution of a request.
212 // TODO: the above is true for settings whose value we burn directly into the
213 // TC, but for non-system settings, we can optimize them as a load from the
214 // known static address or thread-local address of where the setting lives.
215 // This might be worth doing specifically for the zend.assertions setting,
216 // for which the emitter emits an ini_get around every call to assert().
217 auto const settingName = top(env,
218 BCSPOffset{0})->strVal()->toCppString();
219 IniSetting::Mode mode = IniSetting::PHP_INI_NONE;
220 if (!IniSetting::GetMode(settingName, mode)) {
221 return nullptr;
223 if (mode & ~IniSetting::PHP_INI_SYSTEM) {
224 return nullptr;
226 if (mode == IniSetting::PHP_INI_ALL) { /* PHP_INI_ALL has a weird encoding */
227 return nullptr;
230 Variant value;
231 IniSetting::Get(settingName, value);
232 // All scalar values are cast to a string before being returned.
233 if (value.isString()) {
234 return cns(env, makeStaticString(value.toString()));
236 if (value.isInteger()) {
237 return cns(env, makeStaticString(folly::to<std::string>(value.toInt64())));
239 if (value.isBoolean()) {
240 return cns(
241 env,
242 value.toBoolean() ? s_one.get() : s_empty.get()
245 // ini_get() is now enhanced to return more than strings.
246 // Get out of here if we are something else like an array.
247 return nullptr;
250 SSATmp* opt_dirname(IRGS& env, uint32_t numArgs) {
251 if (numArgs != 1) return nullptr;
253 // Only generate the optimized version if the argument passed in is a
254 // static string with a constant literal value so we can get the string value
255 // at JIT time.
256 auto const argType = topType(env, BCSPOffset{0});
257 if (!(argType.hasConstVal(TStaticStr))) {
258 return nullptr;
261 // Return the directory portion of the path
262 auto path = top(env, BCSPOffset{0})->strVal();
263 auto psize = path->size();
264 // Make a mutable copy for dirname_helper to modify
265 char *buf = strndup(path->data(), psize);
266 int len = FileUtil::dirname_helper(buf, psize);
267 SSATmp *ret = cns(env, makeStaticString(buf, len));
268 free(buf);
269 return ret;
273 * Transforms in_array with a static haystack argument into an AKExistsArr with
274 * the haystack flipped.
276 SSATmp* opt_in_array(IRGS& env, uint32_t numArgs) {
277 if (numArgs != 3) return nullptr;
279 // We will restrict this optimization to needles that are strings, and
280 // haystacks that have only non-numeric string keys. This avoids a bunch of
281 // complication around numeric-string array-index semantics.
282 if (!(topType(env, BCSPOffset{2}) <= TStr)) {
283 return nullptr;
286 auto const haystackType = topType(env, BCSPOffset{1});
287 if (!haystackType.hasConstVal(TStaticArr)) {
288 // Haystack isn't statically known
289 return nullptr;
292 auto const haystack = haystackType.arrVal();
293 if (haystack->size() == 0) {
294 return cns(env, false);
297 ArrayInit flipped{haystack->size(), ArrayInit::Map{}};
299 for (auto iter = ArrayIter{haystack}; iter; ++iter) {
300 auto const& key = iter.secondRef();
301 int64_t ignoredInt;
302 double ignoredDbl;
304 if (!key.isString() ||
305 key.asCStrRef().get()
306 ->isNumericWithVal(ignoredInt, ignoredDbl, false) != KindOfNull) {
307 // Numeric strings will complicate matters because the loose comparisons
308 // done with array keys are not quite the same as loose comparisons done
309 // by in_array. For example: in_array('0', array('0000')) is true, but
310 // doing array('0000' => true)['0'] will say "undefined index". This seems
311 // unlikely to affect real-world usage.
312 return nullptr;
315 flipped.set(key.asCStrRef(), init_null_variant);
318 auto const needle = topC(env, BCSPOffset{2});
319 auto const array = flipped.toArray();
320 return gen(
321 env,
322 AKExistsArr,
323 cns(env, ArrayData::GetScalarArray(array.get())),
324 needle
328 SSATmp* opt_get_class(IRGS& env, uint32_t numArgs) {
329 auto const curCls = curClass(env);
330 auto const curName = [&] {
331 return curCls != nullptr ? cns(env, curCls->name()) : nullptr;
333 if (numArgs == 0) return curName();
334 if (numArgs != 1) return nullptr;
336 auto const val = topC(env, BCSPOffset{0});
337 auto const ty = val->type();
338 if (ty <= TNull) return curName();
339 if (ty <= TObj) {
340 auto const cls = gen(env, LdObjClass, val);
341 return gen(env, LdClsName, cls);
344 return nullptr;
347 SSATmp* opt_get_called_class(IRGS& env, uint32_t numArgs) {
348 if (numArgs != 0) return nullptr;
349 if (!curClass(env)) return nullptr;
350 auto const ctx = ldCtx(env);
351 auto const cls = gen(env, LdClsCtx, ctx);
352 return gen(env, LdClsName, cls);
355 SSATmp* opt_sqrt(IRGS& env, uint32_t numArgs) {
356 if (numArgs != 1) return nullptr;
358 auto const val = topC(env);
359 auto const ty = val->type();
360 if (ty <= TDbl) return gen(env, Sqrt, val);
361 if (ty <= TInt) {
362 auto const conv = gen(env, ConvIntToDbl, val);
363 return gen(env, Sqrt, conv);
365 return nullptr;
368 SSATmp* opt_strlen(IRGS& env, uint32_t numArgs) {
369 if (numArgs != 1) return nullptr;
371 auto const val = topC(env);
372 auto const ty = val->type();
374 if (ty <= TStr) {
375 return gen(env, LdStrLen, val);
378 if (ty.subtypeOfAny(TNull, TBool)) {
379 return gen(env, ConvCellToInt, val);
382 if (ty.subtypeOfAny(TInt, TDbl)) {
383 auto str = gen(env, ConvCellToStr, val);
384 auto len = gen(env, LdStrLen, str);
385 decRef(env, str);
386 return len;
389 return nullptr;
392 SSATmp* minmax(IRGS& env, const bool is_max) {
393 auto const val1 = topC(env, BCSPOffset{0});
394 auto const ty1 = val1->type();
395 auto const val2 = topC(env, BCSPOffset{1});
396 auto const ty2 = val2->type();
398 // this optimization is only for 2 ints/doubles
399 if (!(ty1 <= TInt || ty1 <= TDbl) ||
400 !(ty2 <= TInt || ty2 <= TDbl)) return nullptr;
402 return cond(
403 env,
404 [&] (Block* taken) {
405 SSATmp* cmp;
406 if (ty1 <= TInt && ty2 <= TInt) {
407 cmp = gen(env, is_max ? GteInt : LteInt, val1, val2);
408 } else {
409 auto conv1 = (ty1 <= TDbl) ? val1 : gen(env, ConvIntToDbl, val1);
410 auto conv2 = (ty2 <= TDbl) ? val2 : gen(env, ConvIntToDbl, val2);
411 cmp = gen(env, is_max ? GteDbl : LteDbl, conv1, conv2);
413 gen(env, JmpZero, taken, cmp);
415 [&] {
416 return val1;
418 [&] {
419 return val2;
424 SSATmp* opt_max2(IRGS& env, uint32_t numArgs) {
425 // max2 is only called for 2 operands
426 return numArgs == 2 ? minmax(env, true) : nullptr;
429 SSATmp* opt_min2(IRGS& env, uint32_t numArgs) {
430 // min2 is only called for 2 operands
431 return numArgs == 2 ? minmax(env, false) : nullptr;
434 SSATmp* opt_ceil(IRGS& env, uint32_t numArgs) {
435 if (numArgs != 1) return nullptr;
436 if (!folly::CpuId().sse41()) return nullptr;
437 auto const val = topC(env);
438 if (!type_converts_to_number(val->type())) return nullptr;
439 auto const dbl = gen(env, ConvCellToDbl, val);
440 return gen(env, Ceil, dbl);
443 SSATmp* opt_floor(IRGS& env, uint32_t numArgs) {
444 if (numArgs != 1) return nullptr;
445 if (!folly::CpuId().sse41()) return nullptr;
446 auto const val = topC(env);
447 if (!type_converts_to_number(val->type())) return nullptr;
448 auto const dbl = gen(env, ConvCellToDbl, val);
449 return gen(env, Floor, dbl);
452 SSATmp* opt_abs(IRGS& env, uint32_t numArgs) {
453 if (numArgs != 1) return nullptr;
455 auto const value = topC(env);
456 if (value->type() <= TInt) {
457 // compute integer absolute value ((src>>63) ^ src) - (src>>63)
458 auto const t1 = gen(env, Shr, value, cns(env, 63));
459 auto const t2 = gen(env, XorInt, t1, value);
460 return gen(env, SubInt, t2, t1);
463 if (value->type() <= TDbl) return gen(env, AbsDbl, value);
464 if (value->type() <= TArr) return cns(env, false);
466 return nullptr;
469 SSATmp* opt_set_frame_metadata(IRGS& env, uint32_t numArgs) {
470 if (numArgs != 1) return nullptr;
471 auto func = curFunc(env);
472 if (func->isPseudoMain() || (func->attrs() & AttrMayUseVV)) return nullptr;
473 auto const local = func->lookupVarId(s_86metadata.get());
474 if (local == kInvalidId) return nullptr;
475 auto oldVal = ldLoc(env, local, nullptr, DataTypeCountness);
476 auto newVal = topC(env);
477 stLocRaw(env, local, fp(env), newVal);
478 decRef(env, oldVal);
479 gen(env, IncRef, newVal);
480 return cns(env, TInitNull);
483 //////////////////////////////////////////////////////////////////////
485 bool optimizedFCallBuiltin(IRGS& env,
486 const Func* func,
487 uint32_t numArgs,
488 uint32_t numNonDefault) {
489 auto const result = [&]() -> SSATmp* {
491 auto const fname = func->name();
492 #define X(x) \
493 if (fname->isame(s_##x.get())) return opt_##x(env, numArgs);
495 X(get_called_class)
496 X(get_class)
497 X(in_array)
498 X(ini_get)
499 X(count)
500 X(is_a)
501 X(is_subclass_of)
502 X(method_exists)
503 X(sqrt)
504 X(strlen)
505 X(max2)
506 X(ceil)
507 X(floor)
508 X(abs)
509 X(ord)
510 X(func_num_args)
511 X(max2)
512 X(min2)
513 X(dirname)
514 X(set_frame_metadata)
516 #undef X
518 return nullptr;
519 }();
521 if (result == nullptr) return false;
523 // Decref and free args
524 for (int i = 0; i < numArgs; i++) {
525 auto const arg = popR(env);
526 if (i >= numArgs - numNonDefault) {
527 decRef(env, arg);
531 push(env, result);
532 return true;
535 //////////////////////////////////////////////////////////////////////
538 * Return the type that a parameter to a builtin function is supposed to be
539 * coerced to. What this means depends on how the builtin is dealing with
540 * parameter coersion: new-style HNI builtins try to do a tvCoerceParamTo*,
541 * while older ones use tvCastTo* semantics.
543 * If the builtin parameter has no type hints to cause coercion, this function
544 * returns TBottom.
546 Type param_coerce_type(const Func* callee, uint32_t paramIdx) {
547 auto const& pi = callee->params()[paramIdx];
548 auto const& tc = pi.typeConstraint;
549 if (tc.isNullable() && !callee->byRef(paramIdx)) {
550 auto const dt = tc.underlyingDataType();
551 if (!dt) return TBottom;
552 return TNull | Type(*dt);
554 if (callee->byRef(paramIdx) && pi.nativeArg) {
555 return TBoxedCell;
557 if (!pi.builtinType) return TBottom;
558 if (pi.builtinType == KindOfObject &&
559 pi.defaultValue.m_type == KindOfNull) {
560 return TNullableObj;
562 return pi.builtinType ? Type(*pi.builtinType) : TBottom;
565 //////////////////////////////////////////////////////////////////////
567 struct ParamPrep {
568 explicit ParamPrep(size_t count) : info(count) {}
570 struct Info {
571 SSATmp* value{nullptr};
572 bool passByAddr{false};
573 bool needsConversion{false};
574 bool isOutputArg{false};
577 const Info& operator[](size_t idx) const { return info[idx]; }
578 Info& operator[](size_t idx) { return info[idx]; }
579 size_t size() const { return info.size(); }
581 SSATmp* thiz{nullptr}; // may be null if call is not a method
582 SSATmp* count{nullptr}; // if non-null, the count of arguments
583 jit::vector<Info> info;
584 uint32_t numByAddr{0};
586 // if set, coerceFailure determines the target of a failed coercion;
587 // if not set, we side-exit to the next byte-code instruction (only
588 // applies to an inlined NativeImpl, or an FCallBuiltin).
589 Block* coerceFailure{nullptr};
590 bool forNativeImpl{false};
594 * Collect parameters for a call to a builtin. Also determine which ones will
595 * need to be passed through the eval stack, and which ones will need
596 * conversions.
598 template<class LoadParam>
599 ParamPrep prepare_params(IRGS& env,
600 const Func* callee,
601 SSATmp* thiz,
602 SSATmp* numArgsExpr,
603 uint32_t numArgs,
604 uint32_t numNonDefault,
605 Block* coerceFailure,
606 LoadParam loadParam) {
607 auto ret = ParamPrep(numArgs);
608 ret.thiz = thiz;
609 ret.count = numArgsExpr;
610 ret.coerceFailure = coerceFailure;
611 ret.forNativeImpl = coerceFailure != nullptr;
613 // Fill in in reverse order, since they may come from popC's (depending on
614 // what loadParam wants to do).
615 for (auto offset = uint32_t{numArgs}; offset-- > 0;) {
616 auto const ty = param_coerce_type(callee, offset);
617 auto& cur = ret[offset];
618 auto& pi = callee->params()[offset];
620 cur.value = loadParam(offset, ty);
621 cur.isOutputArg = pi.nativeArg && ty == TBoxedCell;
622 // If ty > TBottom, it had some kind of type hint.
623 // A by-reference parameter thats defaulted will get a plain
624 // value (typically null), rather than a BoxedCell; so we still
625 // need to apply a conversion there.
626 cur.needsConversion = cur.isOutputArg ||
627 (offset < numNonDefault && ty > TBottom);
628 // We do actually mean exact type equality here. We're only capable of
629 // passing the following primitives through registers; everything else goes
630 // by address unless its flagged "nativeArg".
631 if (ty == TBool || ty == TInt || ty == TDbl || pi.nativeArg) {
632 continue;
635 ++ret.numByAddr;
636 cur.passByAddr = true;
639 return ret;
642 //////////////////////////////////////////////////////////////////////
645 * CatchMaker makes catch blocks for calling builtins. There's a fair bit of
646 * complexity here right now, for these reasons:
648 * o Sometimes we're 'logically' inlining a php-level call to a function
649 * that contains a NativeImpl opcode.
651 * But we implement this by generating all the relevant NativeImpl code
652 * after the InlineReturn for the callee, to make it easier for DCE to
653 * eliminate the code that constructs the callee's activation record.
654 * This means the unwinder is going to see our PC as equal to the FCallD
655 * for the call to the function, which will be inside the FPI region for
656 * the call, so it'll try to pop an ActRec, so we'll need to reconstruct
657 * one for it during unwinding.
659 * o HNI-style param coerce modes can force the entire function to return
660 * false or null if the coersions fail. This is implemented via a
661 * TVCoercionException, which is not a user-visible exception. So our
662 * catch blocks are sometimes handling a PHP exception, and sometimes a
663 * failure to coerce.
665 * o Both of these things may be relevant to the same catch block.
667 * Also, note that the CatchMaker keeps a pointer to the builtin call's
668 * ParamPrep, which will have its values mutated by realize_params as it's
669 * making coersions, so that we can see what's changed so far (and what to
670 * clean up on the offramps). Some values that were refcounted may become
671 * non-refcounted after conversions, and we can't DecRef things twice.
673 struct CatchMaker {
674 enum class Kind { NotInlining, Inlining };
676 explicit CatchMaker(IRGS& env,
677 Kind kind,
678 const Func* callee,
679 const ParamPrep* params)
680 : env(env)
681 , m_kind(kind)
682 , m_callee(callee)
683 , m_params(*params)
685 assertx(!m_params.thiz || m_params.forNativeImpl || inlining());
688 CatchMaker(const CatchMaker&) = delete;
689 CatchMaker(CatchMaker&&) = default;
691 bool inlining() const {
692 switch (m_kind) {
693 case Kind::NotInlining: return false;
694 case Kind::Inlining: return true;
696 not_reached();
699 Block* makeUnusualCatch() const {
700 auto const exit = defBlock(env, Block::Hint::Unlikely);
701 BlockPusher bp(*env.irb, makeMarker(env, bcOff(env)), exit);
702 gen(env, BeginCatch);
703 decRefForUnwind();
704 prepareForCatch();
705 gen(env, EndCatch, IRSPOffsetData { offsetFromIRSP(env, BCSPOffset{0}) },
706 fp(env), sp(env));
707 return exit;
710 Block* makeParamCoerceCatch() const {
711 auto const exit = defBlock(env, Block::Hint::Unlikely);
713 BlockPusher bp(*env.irb, makeMarker(env, bcOff(env)), exit);
714 gen(env, BeginCatch);
716 // Determine whether we're dealing with a TVCoercionException or a php
717 // exception. If it's a php-exception, we'll go to the taken block.
718 ifThen(
719 env,
720 [&] (Block* taken) {
721 gen(env, UnwindCheckSideExit, taken, fp(env), sp(env));
723 [&] {
724 hint(env, Block::Hint::Unused);
725 decRefForUnwind();
726 prepareForCatch();
727 gen(env, EndCatch,
728 IRSPOffsetData { offsetFromIRSP(env, BCSPOffset{0}) },
729 fp(env), sp(env));
733 // prepareForCatch() in the ifThen() above messed with irb's marker, so we
734 // have to update it on the fallthru path here.
735 updateMarker(env);
737 if (m_params.coerceFailure) {
738 gen(env, Jmp, m_params.coerceFailure);
739 } else {
740 // From here on we're on the side-exit path, due to a failure to coerce.
741 // We need to push the unwinder value and then side-exit to the next
742 // instruction.
743 hint(env, Block::Hint::Unlikely);
744 decRefForSideExit();
745 if (m_params.thiz && m_params.thiz->type() <= TObj) {
746 decRef(env, m_params.thiz);
749 auto const val = gen(env, LdUnwinderValue, TCell);
750 push(env, val);
751 gen(env, Jmp, makeExit(env, nextBcOff(env)));
754 return exit;
757 void decRefByPopping() const {
758 decRefForUnwind();
761 private:
762 void prepareForCatch() const {
763 if (inlining()) {
764 fpushActRec(env,
765 cns(env, m_callee),
766 m_params.thiz ? m_params.thiz : cns(env, TNullptr),
767 m_params.size(),
768 nullptr);
771 * We're potentially spilling to a different depth than the unwinder
772 * would've expected, so we need an eager sync. Even if we aren't inlining
773 * this can happen, because before doing the CallBuiltin we set the marker
774 * stack offset to only include the passed-through-stack args.
776 * So before we leave, update the marker to placate EndCatch assertions,
777 * which is trying to detect failure to do this properly.
779 auto const spOff = IRSPOffsetData { offsetFromIRSP(env, BCSPOffset{0}) };
780 gen(env, EagerSyncVMRegs, spOff, fp(env), sp(env));
781 updateMarker(env); // Mark the EndCatch safe, since we're eager syncing.
785 * For consistency with the interpreter, we need to decref things in a
786 * different order depending on whether we are unwinding, or planning to side
787 * exit.
789 * In either case, parameters that are not being passed through the stack
790 * still may need to be decref'd, because they may have been a reference
791 * counted type that was going to be converted to a non-reference counted
792 * type that we'd pass in a register. As we do the coersions, params.value
793 * gets updated so whenever we call these catch block creation functions it
794 * will only decref things that weren't yet converted.
797 void decRefForUnwind() const {
798 if (m_params.forNativeImpl) return;
799 for (auto i = m_params.size(); i--; ) {
800 auto const &pi = m_params[i];
801 if (pi.passByAddr) {
802 popDecRef(env);
803 } else {
804 decRef(env, pi.value);
809 // Same work as above, but opposite order.
810 void decRefForSideExit() const {
811 assertx(!m_params.forNativeImpl);
812 int32_t stackIdx = safe_cast<int32_t>(m_params.numByAddr);
814 // Make sure we have loads for all of the stack elements. We need to do
815 // this in forward order before we decref in backward order because
816 // extendStack will end up with values that are of type StkElem
817 // TODO(#6156498).
818 for (auto i = 0; i < stackIdx; ++i) {
819 top(env, BCSPOffset{i}, DataTypeGeneric);
822 for (auto i = m_params.size(); i-- > 0;) {
823 if (m_params[i].passByAddr) {
824 --stackIdx;
825 auto const val = top(env, BCSPOffset{stackIdx}, DataTypeGeneric);
826 decRef(env, val);
827 } else {
828 decRef(env, m_params[i].value);
831 discard(env, m_params.numByAddr);
834 private:
835 IRGS& env;
836 Kind const m_kind;
837 const Func* m_callee;
838 const ParamPrep& m_params;
841 //////////////////////////////////////////////////////////////////////
843 SSATmp* coerce_value(IRGS& env,
844 const Type& ty,
845 const Func* callee,
846 SSATmp* oldVal,
847 uint32_t paramIdx,
848 const CatchMaker& maker) {
849 auto const result = [&] () -> SSATmp* {
850 if (!callee->isParamCoerceMode()) {
851 if (ty <= TInt) {
852 return gen(env, ConvCellToInt, maker.makeUnusualCatch(), oldVal);
854 if (ty <= TDbl) {
855 return gen(env, ConvCellToDbl, maker.makeUnusualCatch(), oldVal);
857 if (ty <= TBool) {
858 return gen(env, ConvCellToBool, oldVal);
861 always_assert(false);
864 if (ty <= TInt) {
865 return gen(env,
866 CoerceCellToInt,
867 FuncArgData(callee, paramIdx + 1),
868 maker.makeParamCoerceCatch(),
869 oldVal);
871 if (ty <= TDbl) {
872 return gen(env,
873 CoerceCellToDbl,
874 FuncArgData(callee, paramIdx + 1),
875 maker.makeParamCoerceCatch(),
876 oldVal);
878 if (ty <= TBool) {
879 return gen(env,
880 CoerceCellToBool,
881 FuncArgData(callee, paramIdx + 1),
882 maker.makeParamCoerceCatch(),
883 oldVal);
886 return nullptr;
887 }();
889 if (result) {
890 decRef(env, oldVal);
891 return result;
894 always_assert(ty.subtypeOfAny(TArr, TStr, TObj, TRes) &&
895 callee->params()[paramIdx].nativeArg);
896 auto const misAddr = gen(env, LdMIStateAddr,
897 cns(env, offsetof(MInstrState, tvBuiltinReturn)));
898 gen(env, StMem, misAddr, oldVal);
899 gen(env, CoerceMem, ty,
900 CoerceMemData { callee, paramIdx + 1 },
901 maker.makeParamCoerceCatch(), misAddr);
902 return gen(env, LdMem, ty, misAddr);
905 void coerce_stack(IRGS& env,
906 const Type& ty,
907 const Func* callee,
908 uint32_t paramIdx,
909 BCSPOffset offset,
910 const CatchMaker& maker) {
911 if (callee->isParamCoerceMode()) {
912 always_assert(ty.isKnownDataType());
913 gen(env,
914 CoerceStk,
916 CoerceStkData { offsetFromIRSP(env, offset), callee, paramIdx + 1 },
917 maker.makeParamCoerceCatch(),
918 sp(env));
919 } else {
920 always_assert(ty.isKnownDataType() || ty <= TNullableObj);
921 gen(env,
922 CastStk,
924 IRSPOffsetData { offsetFromIRSP(env, offset) },
925 maker.makeUnusualCatch(),
926 sp(env));
930 * We can throw after writing to the stack above; inform IRBuilder about it.
931 * This is basically just for assertions right now.
933 env.irb->exceptionStackBoundary();
937 * Take the value in param, apply any needed conversions
938 * and return the value to be passed to CallBuiltin.
940 * checkType(ty, fail):
941 * verify that the param is of type ty, and branch to fail
942 * if not. If it results in a new SSATmp*, (as eg CheckType
943 * would), then that should be returned; otherwise it should
944 * return nullptr;
945 * convertParam(ty):
946 * convert the param to ty; failure should be handled by
947 * CatchMaker::makeParamCoerceCatch, and it should return
948 * a new SSATmp* (if appropriate) or nullptr.
949 * realize():
950 * return the SSATmp* needed by CallBuiltin for this parameter.
951 * if checkType and convertParam returned non-null values,
952 * param.value will have been updated with a phi of their results.
954 template<class V, class C, class R>
955 SSATmp* realize_param(IRGS& env,
956 ParamPrep::Info& param,
957 const Func* callee,
958 Type targetTy,
959 V checkType,
960 C convertParam,
961 R realize) {
962 if (param.needsConversion) {
963 auto const baseTy = targetTy - TNull;
964 assert(baseTy.isKnownDataType());
965 auto const convertTy =
966 (!callee->isParamCoerceMode() &&
967 targetTy == TNullableObj) ? targetTy : baseTy;
969 if (auto const value = cond(
970 env,
971 [&] (Block* convert) -> SSATmp* {
972 if (targetTy == baseTy ||
973 !callee->isParamCoerceMode()) {
974 return checkType(baseTy, convert);
976 return cond(
977 env,
978 [&] (Block* fail) { return checkType(baseTy, fail); },
979 [&] (SSATmp* v) { return v; },
980 [&] {
981 return checkType(TInitNull, convert);
984 [&] (SSATmp* v) { return v; },
985 [&] () -> SSATmp* {
986 return convertParam(convertTy);
988 )) {
989 // Heads up on non-local state here: we have to update
990 // the values inside ParamPrep so that the CatchMaker
991 // functions know about new potentially refcounted types
992 // to decref, or values that were already decref'd and
993 // replaced with things like ints.
994 param.value = value;
998 return realize();
1002 * Prepare the actual arguments to the CallBuiltin instruction, by converting a
1003 * ParamPrep into a vector of SSATmps to pass to CallBuiltin. If any of the
1004 * parameters needed type conversions, we need to do that here too.
1006 jit::vector<SSATmp*> realize_params(IRGS& env,
1007 const Func* callee,
1008 ParamPrep& params,
1009 const CatchMaker& maker) {
1010 auto const cbNumArgs = 2 + params.size() +
1011 (params.thiz ? 1 : 0) + (params.count ? 1 : 0);
1012 auto ret = jit::vector<SSATmp*>(cbNumArgs);
1013 auto argIdx = uint32_t{0};
1014 ret[argIdx++] = fp(env);
1015 ret[argIdx++] = sp(env);
1016 if (params.thiz) ret[argIdx++] = params.thiz;
1017 if (params.count) ret[argIdx++] = params.count;
1019 assertx(!params.count || callee->attrs() & AttrNumArgs);
1021 DEBUG_ONLY auto usedStack = false;
1022 auto stackIdx = uint32_t{0};
1023 for (auto paramIdx = uint32_t{0}; paramIdx < params.size(); ++paramIdx) {
1024 auto& param = params[paramIdx];
1025 auto const targetTy = param_coerce_type(callee, paramIdx);
1027 if (param.value->type() <= TPtrToGen) {
1028 ret[argIdx++] = realize_param(
1029 env, param, callee, targetTy,
1030 [&] (const Type& ty, Block* fail) -> SSATmp* {
1031 gen(env, CheckTypeMem, ty, fail, param.value);
1032 return param.isOutputArg ?
1033 gen(env, LdMem, TBoxedCell, param.value) : nullptr;
1035 [&] (const Type& ty) -> SSATmp* {
1036 hint(env, Block::Hint::Unlikely);
1037 if (param.isOutputArg) {
1038 return cns(env, TNullptr);
1040 if (callee->isParamCoerceMode()) {
1041 gen(env,
1042 CoerceMem,
1044 CoerceMemData { callee, paramIdx + 1 },
1045 maker.makeParamCoerceCatch(),
1046 param.value);
1047 } else {
1048 gen(env,
1049 CastMem,
1051 maker.makeUnusualCatch(),
1052 param.value);
1054 return nullptr;
1056 [&] {
1057 if (!param.passByAddr && !param.isOutputArg) {
1058 assertx(targetTy == TBool ||
1059 targetTy == TInt ||
1060 targetTy == TDbl ||
1061 callee->params()[paramIdx].nativeArg);
1062 return gen(env, LdMem,
1063 targetTy == TBool || targetTy == TBoxedCell ?
1064 TInt : targetTy == TBottom ? TCell : targetTy,
1065 param.value);
1067 return param.value;
1069 continue;
1072 if (!param.passByAddr) {
1073 auto const oldVal = params[paramIdx].value;
1074 ret[argIdx++] = realize_param(
1075 env, param, callee, targetTy,
1076 [&] (const Type& ty, Block* fail) {
1077 auto ret = gen(env, CheckType, ty, fail, param.value);
1078 env.irb->constrainValue(ret, DataTypeSpecific);
1079 return ret;
1081 [&] (const Type& ty) {
1082 if (param.isOutputArg) return cns(env, TNullptr);
1083 return coerce_value(
1084 env,
1086 callee,
1087 oldVal,
1088 paramIdx,
1089 maker
1092 [&] {
1094 * This gets tricky:
1095 * - if we had a ref-counted type, and it was converted
1096 * to a Bool, Int or Dbl above, we explicitly DecReffed it
1097 * (in coerce_value).
1098 * - if we had a non-RefData nativeArg, we did a CoerceMem
1099 * which implicitly DecReffed the old value
1100 * In either case, the old value is taken care of, and any future
1101 * DecRefs (from exceptions, or after the call on the normal flow
1102 * of execution) should DecRef param.value (ie the post-coercion
1103 * value).
1105 * But if we had an OutputArg, we did not DecRef the old value,
1106 * and the post-coercion value is a RefData* or nullptr.
1107 * If its a RefData*, we need to DecRef that - but in that case
1108 * the new value is the same as the old.
1109 * If its Nullptr, we need to DecRef the old value.
1111 * So in both cases we actually want to DecRef the *old* value, so
1112 * we have to restore it here (because realize_param replaced it
1113 * with the new value).
1115 auto v = param.value;
1116 if (param.isOutputArg) {
1117 param.value = oldVal;
1119 return v;
1121 continue;
1124 usedStack = true;
1125 auto const offset = BCSPOffset{safe_cast<int32_t>(
1126 params.numByAddr - stackIdx - 1)};
1128 ret[argIdx++] = realize_param(
1129 env, param, callee, targetTy,
1130 [&] (const Type& ty, Block* fail) -> SSATmp* {
1131 auto irspOff = offsetFromIRSP(env, offset);
1132 gen(env, CheckStk,
1133 RelOffsetData { offset, irspOff },
1134 ty, fail, sp(env));
1135 env.irb->constrainStack(irspOff, DataTypeSpecific);
1136 return nullptr;
1138 [&] (const Type& ty) -> SSATmp* {
1139 coerce_stack(env, ty, callee, paramIdx, offset, maker);
1140 return nullptr;
1142 [&] {
1143 return ldStkAddr(env, offset);
1145 ++stackIdx;
1148 assertx(!usedStack || stackIdx == params.numByAddr);
1149 assertx(argIdx == cbNumArgs);
1151 return ret;
1154 //////////////////////////////////////////////////////////////////////
1156 SSATmp* builtinCall(IRGS& env,
1157 const Func* callee,
1158 ParamPrep& params,
1159 int32_t numNonDefault,
1160 const CatchMaker& catchMaker) {
1161 if (!params.forNativeImpl) {
1163 * Everything that needs to be on the stack gets spilled now.
1165 * If we're not inlining, the reason we do this even when numByAddr is
1166 * zero is to make it so that in either case the stack depth when we enter
1167 * our catch blocks is always the same as the numByAddr value, in all
1168 * situations. If we didn't do this, then when we aren't inlining, and
1169 * numByAddr is zero, we'd have the stack depth be the total num
1170 * params (the depth before the FCallBuiltin), which would add more cases
1171 * to handle in the catch blocks.
1173 if (params.numByAddr != 0 || !catchMaker.inlining()) {
1174 for (auto i = uint32_t{0}; i < params.size(); ++i) {
1175 if (params[i].passByAddr) {
1176 push(env, params[i].value);
1180 * This marker update is to make sure rbx points to the bottom of our
1181 * stack if we enter a catch trace. It's also necessary because we might
1182 * run destructors as part of parameter coersions, which we don't want to
1183 * clobber our spilled stack.
1185 updateMarker(env);
1188 // If we are inlining, we've done various DefInlineFP-type stuff that can
1189 // affect stack depth.
1190 env.irb->exceptionStackBoundary();
1193 auto const retType = [&]() -> Type {
1194 auto const retDT = callee->returnType();
1195 auto const ret = retDT ? Type(*retDT) : TCell;
1196 if (callee->attrs() & AttrReference) {
1197 return ret.box() & TBoxedInitCell;
1199 return ret;
1200 }();
1202 // Make the actual call.
1203 auto realized = realize_params(env, callee, params, catchMaker);
1204 SSATmp** const decayedPtr = &realized[0];
1205 auto const ret = gen(
1206 env,
1207 CallBuiltin,
1208 retType,
1209 CallBuiltinData {
1210 offsetFromIRSP(env, BCSPOffset{0}),
1211 callee,
1212 params.count ? -1 : numNonDefault,
1213 builtinFuncDestroysLocals(callee),
1214 builtinFuncNeedsCallerFrame(callee)
1216 catchMaker.makeUnusualCatch(),
1217 std::make_pair(realized.size(), decayedPtr)
1220 if (!params.forNativeImpl) {
1221 if (params.thiz && params.thiz->type() <= TObj) {
1222 decRef(env, params.thiz);
1224 catchMaker.decRefByPopping();
1227 return ret;
1231 * When we're inlining a NativeImpl opcode, we know this is the only opcode in
1232 * the callee method body aside from AssertRATs (bytecode invariant). So in
1233 * order to make sure we can eliminate the SpillFrame, we do the CallBuiltin
1234 * instruction after we've left the inlined frame.
1236 * We may need to pass some arguments to the builtin through the stack (e.g. if
1237 * it takes const Variant&'s)---these are spilled to the stack after leaving
1238 * the callee.
1240 * To make this work, we need to do some weird things in the catch trace. ;)
1242 void nativeImplInlined(IRGS& env) {
1243 auto const callee = curFunc(env);
1244 assertx(callee->nativeFuncPtr());
1246 auto const numArgs = callee->numParams();
1247 auto const paramThis = [&] () -> SSATmp* {
1248 if (!callee->isMethod()) return nullptr;
1249 if (callee->isStatic() && !callee->isNative()) return nullptr;
1250 auto ctx = gen(env, LdCtx, fp(env));
1251 if (callee->isStatic()) return gen(env, LdClsCtx, ctx);
1252 return gen(env, CastCtxThis, ctx);
1253 }();
1255 auto numNonDefault = fp(env)->inst()->extra<DefInlineFP>()->numNonDefault;
1256 auto params = prepare_params(
1257 env,
1258 callee,
1259 paramThis,
1260 nullptr,
1261 numArgs,
1262 numNonDefault,
1263 nullptr,
1264 [&] (uint32_t i, const Type) {
1265 auto ret = ldLoc(env, i, nullptr, DataTypeSpecific);
1266 // These IncRefs must be 'inside' the callee: it may own the only
1267 // reference to the parameters. Normally they will cancel with the
1268 // DecRefs that we'll do in endInlinedCommon.
1269 gen(env, IncRef, ret);
1270 return ret;
1274 // For the same reason that we have to IncRef the locals above, we
1275 // need to grab one on the $this.
1276 if (paramThis && paramThis->type() <= TObj) gen(env, IncRef, paramThis);
1278 endInlinedCommon(env);
1280 auto const catcher = CatchMaker {
1281 env,
1282 CatchMaker::Kind::Inlining,
1283 callee,
1284 &params
1287 push(env, builtinCall(env, callee, params, numNonDefault, catcher));
1290 //////////////////////////////////////////////////////////////////////
1294 //////////////////////////////////////////////////////////////////////
1296 SSATmp* optimizedCallIsObject(IRGS& env, SSATmp* src) {
1297 if (src->isA(TObj) && src->type().clsSpec()) {
1298 auto const cls = src->type().clsSpec().cls();
1299 if (!env.irb->constrainValue(src, TypeConstraint(cls).setWeak())) {
1300 // If we know the class without having to specialize a guard
1301 // any further, use it.
1302 return cns(env, cls != SystemLib::s___PHP_Incomplete_ClassClass);
1306 if (!src->type().maybe(TObj)) {
1307 return cns(env, false);
1310 auto checkClass = [&] (SSATmp* obj) {
1311 auto cls = gen(env, LdObjClass, obj);
1312 auto testCls = SystemLib::s___PHP_Incomplete_ClassClass;
1313 auto eq = gen(env, EqCls, cls, cns(env, testCls));
1314 return gen(env, XorBool, eq, cns(env, true));
1317 return cond(
1318 env,
1319 [&] (Block* taken) {
1320 auto isObj = gen(env, IsType, TObj, src);
1321 gen(env, JmpZero, taken, isObj);
1323 [&] { // Next: src is an object
1324 auto obj = gen(env, AssertType, TObj, src);
1325 return checkClass(obj);
1327 [&] { // Taken: src is not an object
1328 return cns(env, false);
1333 //////////////////////////////////////////////////////////////////////
1335 void emitFCallBuiltin(IRGS& env,
1336 int32_t numArgs,
1337 int32_t numNonDefault,
1338 const StringData* funcName) {
1339 auto const callee = Unit::lookupFunc(funcName);
1341 if (optimizedFCallBuiltin(env, callee, numArgs, numNonDefault)) return;
1343 auto params = prepare_params(
1344 env,
1345 callee,
1346 nullptr, // no $this; FCallBuiltin never happens for methods
1347 nullptr, // count is constant numNonDefault
1348 numArgs,
1349 numNonDefault,
1350 nullptr,
1351 [&] (uint32_t i, const Type ty) {
1352 auto specificity =
1353 ty == TBottom ? DataTypeGeneric : DataTypeSpecific;
1354 return pop(env, specificity);
1358 auto const catcher = CatchMaker {
1359 env,
1360 CatchMaker::Kind::NotInlining,
1361 callee,
1362 &params
1365 push(env, builtinCall(env, callee, params, numNonDefault, catcher));
1368 void emitNativeImpl(IRGS& env) {
1369 if (isInlining(env)) return nativeImplInlined(env);
1371 auto genericNativeImpl = [&]() {
1372 gen(env, NativeImpl, fp(env), sp(env));
1373 auto const data = RetCtrlData { offsetToReturnSlot(env), false };
1374 gen(env, RetCtrl, data, sp(env), fp(env));
1377 auto callee = curFunc(env);
1378 if (!callee->nativeFuncPtr() || callee->builtinFuncPtr() == zend_wrap_func) {
1379 genericNativeImpl();
1380 return;
1383 // CallBuiltin doesn't understand IDL instance methods that have variable
1384 // arguments.
1385 if (auto const info = callee->methInfo()) {
1386 if (info->attribute & (ClassInfo::VariableArguments |
1387 ClassInfo::RefVariableArguments)) {
1388 genericNativeImpl();
1389 return;
1393 auto thiz = callee->isMethod() && (!callee->isStatic() || callee->isNative())
1394 ? gen(env, LdCtx, fp(env)) : nullptr;
1395 auto const numParams = gen(env, LdARNumParams, fp(env));
1397 ifThenElse(
1398 env,
1399 [&] (Block* fallback) {
1400 if (thiz) {
1401 if (callee->isStatic()) {
1402 thiz = gen(env, LdClsCtx, thiz);
1403 } else {
1404 gen(env, CheckCtxThis, fallback, thiz);
1405 thiz = gen(env, CastCtxThis, thiz);
1409 auto maxArgs = callee->numParams();
1410 auto minArgs = callee->numNonVariadicParams();
1411 while (minArgs) {
1412 auto const& pi = callee->params()[minArgs - 1];
1413 if (pi.funcletOff == InvalidAbsoluteOffset) {
1414 break;
1416 --minArgs;
1418 if (callee->hasVariadicCaptureParam()) {
1419 if (minArgs) {
1420 auto const check = gen(env, LtInt, numParams, cns(env, minArgs));
1421 gen(env, JmpNZero, fallback, check);
1423 } else {
1424 if (minArgs == maxArgs) {
1425 auto const check = gen(env, EqInt, numParams, cns(env, minArgs));
1426 gen(env, JmpZero, fallback, check);
1427 } else {
1428 if (minArgs) {
1429 auto const checkMin = gen(env, LtInt,
1430 numParams, cns(env, minArgs));
1431 gen(env, JmpNZero, fallback, checkMin);
1433 auto const checkMax = gen(env, GtInt, numParams, cns(env, maxArgs));
1434 gen(env, JmpNZero, fallback, checkMax);
1438 [&] {
1439 auto const ret = cond(
1440 env,
1441 [&] (Block* fail) {
1442 auto params = prepare_params(
1443 env,
1444 callee,
1445 thiz,
1446 callee->attrs() & AttrNumArgs ? numParams : nullptr,
1447 callee->numParams(),
1448 callee->numParams(),
1449 fail,
1450 [&] (uint32_t i, const Type) {
1451 return gen(env, LdLocAddr, LocalId(i), fp(env));
1454 auto const catcher = CatchMaker {
1455 env,
1456 CatchMaker::Kind::NotInlining,
1457 callee,
1458 &params
1461 return builtinCall(env, callee, params,
1462 callee->numParams(), catcher);
1464 [&] (SSATmp* ret) { return ret; },
1465 [&] {
1466 return callee->attrs() & AttrParamCoerceModeFalse ?
1467 cns(env, false) : cns(env, TInitNull);
1469 push(env, ret);
1470 emitRetC(env);
1472 [&] {
1473 hint(env, Block::Hint::Unlikely);
1474 genericNativeImpl();
1479 //////////////////////////////////////////////////////////////////////
1481 namespace {
1483 // Helper for doing array-style Idx translations, even if we're dealing with a
1484 // collection. The stack will still contain the collection in that case, and
1485 // loaded_collection_array will be non-nullptr. If we're really doing
1486 // ArrayIdx, it's nullptr.
1487 void implArrayIdx(IRGS& env, SSATmp* loaded_collection_array) {
1488 // These types are just used to decide what to do; once we know what we're
1489 // actually doing we constrain the values with the popC()s later on in this
1490 // function.
1491 auto const keyType = topC(env, BCSPOffset{1}, DataTypeGeneric)->type();
1493 if (keyType <= TNull) {
1494 auto const def = popC(env, DataTypeGeneric);
1495 auto const key = popC(env);
1496 auto const stack_base = popC(env);
1498 // if the key is null it will not be found so just return the default
1499 push(env, def);
1500 decRef(env, stack_base);
1501 decRef(env, key);
1502 return;
1504 if (!(keyType <= TInt || keyType <= TStr)) {
1505 interpOne(env, TCell, 3);
1506 return;
1509 auto const def = popC(env, DataTypeGeneric); // a helper will decref it but
1510 // the translated code doesn't
1511 // care about the type
1512 auto const key = popC(env);
1513 auto const stack_base = popC(env);
1514 auto const use_base = loaded_collection_array
1515 ? loaded_collection_array
1516 : stack_base;
1517 auto const value = gen(env, ArrayIdx, use_base, key, def);
1518 push(env, value);
1519 decRef(env, stack_base);
1520 decRef(env, key);
1521 decRef(env, def);
1524 void implMapIdx(IRGS& env) {
1525 auto const def = popC(env);
1526 auto const key = popC(env);
1527 auto const map = popC(env);
1528 auto const val = gen(env, MapIdx, map, key, def);
1529 push(env, val);
1530 decRef(env, map);
1531 decRef(env, key);
1532 decRef(env, def);
1535 void implGenericIdx(IRGS& env) {
1536 auto const stkptr = sp(env);
1537 auto const spOff = IRSPOffsetData { offsetFromIRSP(env, BCSPOffset{0}) };
1538 auto const def = popC(env, DataTypeSpecific);
1539 auto const key = popC(env, DataTypeSpecific);
1540 auto const arr = popC(env, DataTypeSpecific);
1541 push(env, gen(env, GenericIdx, spOff, arr, key, def, stkptr));
1542 decRef(env, arr);
1543 decRef(env, key);
1544 decRef(env, def);
1547 TypeConstraint idxBaseConstraint(Type baseType, Type keyType,
1548 bool& useCollection, bool& useMap) {
1549 if (baseType < TObj && baseType.clsSpec()) {
1550 auto const cls = baseType.clsSpec().cls();
1552 // To use ArrayIdx, we require either constant non-int keys or known
1553 // integer keys for Map, because integer-like strings behave differently.
1554 auto const isMap = collections::isType(cls, CollectionType::Map) ||
1555 collections::isType(cls, CollectionType::ImmMap);
1556 auto const isMapOrSet = isMap ||
1557 collections::isType(cls, CollectionType::Set) ||
1558 collections::isType(cls, CollectionType::ImmSet);
1559 auto const okMapOrSet = [&]() {
1560 if (!isMapOrSet) return false;
1561 if (keyType <= TInt) return true;
1562 if (!keyType.hasConstVal(TStr)) return false;
1563 int64_t dummy;
1564 if (keyType.strVal()->isStrictlyInteger(dummy)) return false;
1565 return true;
1566 }();
1567 // Similarly, Vector is only usable with int keys, so we can only do this
1568 // for Vector if it's an Int.
1569 auto const isVector = collections::isType(cls, CollectionType::Vector) ||
1570 collections::isType(cls, CollectionType::ImmVector);
1571 auto const okVector = isVector && keyType <= TInt;
1573 useCollection = okMapOrSet || okVector;
1574 // If it's a map with a non-static string key, we can do a map-specific
1575 // optimization.
1576 useMap = isMap && keyType <= TStr;
1578 if (useCollection || useMap) return TypeConstraint(cls);
1581 useCollection = useMap = false;
1582 return DataTypeSpecific;
1585 //////////////////////////////////////////////////////////////////////
1589 TypeConstraint idxBaseConstraint(Type baseType, Type keyType) {
1590 bool collection, map;
1591 return idxBaseConstraint(baseType, keyType, collection, map);
1594 void emitArrayIdx(IRGS& env) {
1595 auto const arrType = topC(env, BCSPOffset{2}, DataTypeGeneric)->type();
1596 if (!(arrType <= TArr)) {
1597 // raise fatal
1598 interpOne(env, TCell, 3);
1599 return;
1602 implArrayIdx(env, nullptr);
1605 void emitIdx(IRGS& env) {
1606 auto const key = topC(env, BCSPOffset{1}, DataTypeGeneric);
1607 auto const base = topC(env, BCSPOffset{2}, DataTypeGeneric);
1608 auto const keyType = key->type();
1609 auto const baseType = base->type();
1611 if (keyType <= TNull || !baseType.maybe(TArr | TObj | TStr)) {
1612 auto const def = popC(env, DataTypeGeneric);
1613 popC(env, keyType <= TNull ? DataTypeSpecific : DataTypeGeneric);
1614 popC(env, keyType <= TNull ? DataTypeGeneric : DataTypeSpecific);
1615 push(env, def);
1616 decRef(env, base);
1617 decRef(env, key);
1618 return;
1621 auto const simple_key =
1622 keyType <= TInt || keyType <= TStr;
1624 if (!simple_key) {
1625 implGenericIdx(env);
1626 return;
1629 if (baseType <= TArr) {
1630 emitArrayIdx(env);
1631 return;
1634 bool useCollection, useMap;
1635 auto const tc = idxBaseConstraint(baseType, keyType, useCollection, useMap);
1636 if (useCollection || useMap) {
1637 env.irb->constrainValue(base, tc);
1638 env.irb->constrainValue(key, DataTypeSpecific);
1640 if (useCollection) {
1641 auto const arr = gen(env, LdColArray, base);
1642 implArrayIdx(env, arr);
1643 } else {
1644 implMapIdx(env);
1646 return;
1649 implGenericIdx(env);
1652 void emitAKExists(IRGS& env) {
1653 auto const arr = popC(env);
1654 auto key = popC(env);
1656 if (!arr->isA(TArr) && !arr->isA(TObj)) PUNT(AKExists_badArray);
1658 if (key->isA(TInitNull)) {
1659 if (arr->isA(TObj)) {
1660 push(env, cns(env, false));
1661 decRef(env, arr);
1662 return;
1665 key = cns(env, staticEmptyString());
1668 if (!key->isA(TStr) && !key->isA(TInt)) PUNT(AKExists_badKey);
1670 auto const val =
1671 gen(env, arr->isA(TArr) ? AKExistsArr : AKExistsObj, arr, key);
1672 push(env, val);
1673 decRef(env, arr);
1674 decRef(env, key);
1677 void emitGetMemoKey(IRGS& env) {
1678 auto const inTy = topC(env)->type();
1679 if (inTy <= TInt) {
1680 // An int is already a valid key. No-op.
1681 return;
1683 if (inTy <= TNull) {
1684 auto input = popC(env);
1685 push(env, cns(env, s_empty.get()));
1686 decRef(env, input);
1687 return;
1690 auto const obj = popC(env);
1691 auto const key = gen(env, GetMemoKey, obj);
1692 push(env, key);
1693 decRef(env, obj);
1696 void emitSilence(IRGS& env, Id localId, SilenceOp subop) {
1697 // We can't generate direct StLoc and LdLocs in pseudomains (violates an IR
1698 // invariant).
1699 if (curFunc(env)->isPseudoMain()) PUNT(PseudoMain-Silence);
1701 switch (subop) {
1702 case SilenceOp::Start:
1703 // We assume that whatever is in the local is dead and doesn't need to be
1704 // refcounted before being overwritten.
1705 gen(env, AssertLoc, TUncounted, LocalId(localId), fp(env));
1706 gen(env, StLoc, LocalId(localId), fp(env), gen(env, ZeroErrorLevel));
1707 break;
1708 case SilenceOp::End:
1710 gen(env, AssertLoc, TInt, LocalId(localId), fp(env));
1711 auto const level = ldLoc(env, localId, makeExit(env), DataTypeGeneric);
1712 gen(env, RestoreErrorLevel, level);
1714 break;
1718 //////////////////////////////////////////////////////////////////////