2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-2016 Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
16 #include "hphp/runtime/vm/jit/irgen-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
{
39 //////////////////////////////////////////////////////////////////////
43 s_is_subclass_of("is_subclass_of"),
44 s_method_exists("method_exists"),
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"),
55 s_max2("__SystemLib\\max2"),
56 s_min2("__SystemLib\\min2"),
61 s_func_num_args("__SystemLib\\func_num_arg_"),
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(
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
)) {
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
111 auto const eq
= gen(env
, EqCls
, objCls
, testCls
);
112 gen(env
, JmpNZero
, taken
, eq
);
115 return gen(env
, InstanceOf
, objCls
, testCls
);
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
) {
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]});
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
204 auto const argType
= topType(env
, BCSPOffset
{0});
205 if (!(argType
.hasConstVal(TStaticStr
))) {
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
)) {
223 if (mode
& ~IniSetting::PHP_INI_SYSTEM
) {
226 if (mode
== IniSetting::PHP_INI_ALL
) { /* PHP_INI_ALL has a weird encoding */
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()) {
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.
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
256 auto const argType
= topType(env
, BCSPOffset
{0});
257 if (!(argType
.hasConstVal(TStaticStr
))) {
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
));
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
)) {
286 auto const haystackType
= topType(env
, BCSPOffset
{1});
287 if (!haystackType
.hasConstVal(TStaticArr
)) {
288 // Haystack isn't statically known
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();
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.
315 flipped
.set(key
.asCStrRef(), init_null_variant
);
318 auto const needle
= topC(env
, BCSPOffset
{2});
319 auto const array
= flipped
.toArray();
323 cns(env
, ArrayData::GetScalarArray(array
.get())),
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();
340 auto const cls
= gen(env
, LdObjClass
, val
);
341 return gen(env
, LdClsName
, cls
);
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
);
362 auto const conv
= gen(env
, ConvIntToDbl
, val
);
363 return gen(env
, Sqrt
, conv
);
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();
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
);
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;
406 if (ty1
<= TInt
&& ty2
<= TInt
) {
407 cmp
= gen(env
, is_max
? GteInt
: LteInt
, val1
, val2
);
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
);
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);
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
);
479 gen(env
, IncRef
, newVal
);
480 return cns(env
, TInitNull
);
483 //////////////////////////////////////////////////////////////////////
485 bool optimizedFCallBuiltin(IRGS
& env
,
488 uint32_t numNonDefault
) {
489 auto const result
= [&]() -> SSATmp
* {
491 auto const fname
= func
->name();
493 if (fname->isame(s_##x.get())) return opt_##x(env, numArgs);
514 X(set_frame_metadata
)
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
) {
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
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
) {
557 if (!pi
.builtinType
) return TBottom
;
558 if (pi
.builtinType
== KindOfObject
&&
559 pi
.defaultValue
.m_type
== KindOfNull
) {
562 return pi
.builtinType
? Type(*pi
.builtinType
) : TBottom
;
565 //////////////////////////////////////////////////////////////////////
568 explicit ParamPrep(size_t count
) : info(count
) {}
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
598 template<class LoadParam
>
599 ParamPrep
prepare_params(IRGS
& env
,
604 uint32_t numNonDefault
,
605 Block
* coerceFailure
,
606 LoadParam loadParam
) {
607 auto ret
= ParamPrep(numArgs
);
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
) {
636 cur
.passByAddr
= true;
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
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.
674 enum class Kind
{ NotInlining
, Inlining
};
676 explicit CatchMaker(IRGS
& env
,
679 const ParamPrep
* params
)
685 assertx(!m_params
.thiz
|| m_params
.forNativeImpl
|| inlining());
688 CatchMaker(const CatchMaker
&) = delete;
689 CatchMaker(CatchMaker
&&) = default;
691 bool inlining() const {
693 case Kind::NotInlining
: return false;
694 case Kind::Inlining
: return true;
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
);
705 gen(env
, EndCatch
, IRSPOffsetData
{ offsetFromIRSP(env
, BCSPOffset
{0}) },
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.
721 gen(env
, UnwindCheckSideExit
, taken
, fp(env
), sp(env
));
724 hint(env
, Block::Hint::Unused
);
728 IRSPOffsetData
{ offsetFromIRSP(env
, BCSPOffset
{0}) },
733 // prepareForCatch() in the ifThen() above messed with irb's marker, so we
734 // have to update it on the fallthru path here.
737 if (m_params
.coerceFailure
) {
738 gen(env
, Jmp
, m_params
.coerceFailure
);
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
743 hint(env
, Block::Hint::Unlikely
);
745 if (m_params
.thiz
&& m_params
.thiz
->type() <= TObj
) {
746 decRef(env
, m_params
.thiz
);
749 auto const val
= gen(env
, LdUnwinderValue
, TCell
);
751 gen(env
, Jmp
, makeExit(env
, nextBcOff(env
)));
757 void decRefByPopping() const {
762 void prepareForCatch() const {
766 m_params
.thiz
? m_params
.thiz
: cns(env
, TNullptr
),
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
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
];
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
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
) {
825 auto const val
= top(env
, BCSPOffset
{stackIdx
}, DataTypeGeneric
);
828 decRef(env
, m_params
[i
].value
);
831 discard(env
, m_params
.numByAddr
);
837 const Func
* m_callee
;
838 const ParamPrep
& m_params
;
841 //////////////////////////////////////////////////////////////////////
843 SSATmp
* coerce_value(IRGS
& env
,
848 const CatchMaker
& maker
) {
849 auto const result
= [&] () -> SSATmp
* {
850 if (!callee
->isParamCoerceMode()) {
852 return gen(env
, ConvCellToInt
, maker
.makeUnusualCatch(), oldVal
);
855 return gen(env
, ConvCellToDbl
, maker
.makeUnusualCatch(), oldVal
);
858 return gen(env
, ConvCellToBool
, oldVal
);
861 always_assert(false);
867 FuncArgData(callee
, paramIdx
+ 1),
868 maker
.makeParamCoerceCatch(),
874 FuncArgData(callee
, paramIdx
+ 1),
875 maker
.makeParamCoerceCatch(),
881 FuncArgData(callee
, paramIdx
+ 1),
882 maker
.makeParamCoerceCatch(),
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
,
910 const CatchMaker
& maker
) {
911 if (callee
->isParamCoerceMode()) {
912 always_assert(ty
.isKnownDataType());
916 CoerceStkData
{ offsetFromIRSP(env
, offset
), callee
, paramIdx
+ 1 },
917 maker
.makeParamCoerceCatch(),
920 always_assert(ty
.isKnownDataType() || ty
<= TNullableObj
);
924 IRSPOffsetData
{ offsetFromIRSP(env
, offset
) },
925 maker
.makeUnusualCatch(),
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
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.
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
,
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(
971 [&] (Block
* convert
) -> SSATmp
* {
972 if (targetTy
== baseTy
||
973 !callee
->isParamCoerceMode()) {
974 return checkType(baseTy
, convert
);
978 [&] (Block
* fail
) { return checkType(baseTy
, fail
); },
979 [&] (SSATmp
* v
) { return v
; },
981 return checkType(TInitNull
, convert
);
984 [&] (SSATmp
* v
) { return v
; },
986 return convertParam(convertTy
);
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.
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
,
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()) {
1044 CoerceMemData
{ callee
, paramIdx
+ 1 },
1045 maker
.makeParamCoerceCatch(),
1051 maker
.makeUnusualCatch(),
1057 if (!param
.passByAddr
&& !param
.isOutputArg
) {
1058 assertx(targetTy
== TBool
||
1061 callee
->params()[paramIdx
].nativeArg
);
1062 return gen(env
, LdMem
,
1063 targetTy
== TBool
|| targetTy
== TBoxedCell
?
1064 TInt
: targetTy
== TBottom
? TCell
: targetTy
,
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
);
1081 [&] (const Type
& ty
) {
1082 if (param
.isOutputArg
) return cns(env
, TNullptr
);
1083 return coerce_value(
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
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
;
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
);
1133 RelOffsetData
{ offset
, irspOff
},
1135 env
.irb
->constrainStack(irspOff
, DataTypeSpecific
);
1138 [&] (const Type
& ty
) -> SSATmp
* {
1139 coerce_stack(env
, ty
, callee
, paramIdx
, offset
, maker
);
1143 return ldStkAddr(env
, offset
);
1148 assertx(!usedStack
|| stackIdx
== params
.numByAddr
);
1149 assertx(argIdx
== cbNumArgs
);
1154 //////////////////////////////////////////////////////////////////////
1156 SSATmp
* builtinCall(IRGS
& env
,
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.
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
;
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(
1210 offsetFromIRSP(env
, BCSPOffset
{0}),
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();
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
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
);
1255 auto numNonDefault
= fp(env
)->inst()->extra
<DefInlineFP
>()->numNonDefault
;
1256 auto params
= prepare_params(
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
);
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
{
1282 CatchMaker::Kind::Inlining
,
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));
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
,
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(
1346 nullptr, // no $this; FCallBuiltin never happens for methods
1347 nullptr, // count is constant numNonDefault
1351 [&] (uint32_t i
, const Type ty
) {
1353 ty
== TBottom
? DataTypeGeneric
: DataTypeSpecific
;
1354 return pop(env
, specificity
);
1358 auto const catcher
= CatchMaker
{
1360 CatchMaker::Kind::NotInlining
,
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();
1383 // CallBuiltin doesn't understand IDL instance methods that have variable
1385 if (auto const info
= callee
->methInfo()) {
1386 if (info
->attribute
& (ClassInfo::VariableArguments
|
1387 ClassInfo::RefVariableArguments
)) {
1388 genericNativeImpl();
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
));
1399 [&] (Block
* fallback
) {
1401 if (callee
->isStatic()) {
1402 thiz
= gen(env
, LdClsCtx
, thiz
);
1404 gen(env
, CheckCtxThis
, fallback
, thiz
);
1405 thiz
= gen(env
, CastCtxThis
, thiz
);
1409 auto maxArgs
= callee
->numParams();
1410 auto minArgs
= callee
->numNonVariadicParams();
1412 auto const& pi
= callee
->params()[minArgs
- 1];
1413 if (pi
.funcletOff
== InvalidAbsoluteOffset
) {
1418 if (callee
->hasVariadicCaptureParam()) {
1420 auto const check
= gen(env
, LtInt
, numParams
, cns(env
, minArgs
));
1421 gen(env
, JmpNZero
, fallback
, check
);
1424 if (minArgs
== maxArgs
) {
1425 auto const check
= gen(env
, EqInt
, numParams
, cns(env
, minArgs
));
1426 gen(env
, JmpZero
, fallback
, check
);
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
);
1439 auto const ret
= cond(
1442 auto params
= prepare_params(
1446 callee
->attrs() & AttrNumArgs
? numParams
: nullptr,
1447 callee
->numParams(),
1448 callee
->numParams(),
1450 [&] (uint32_t i
, const Type
) {
1451 return gen(env
, LdLocAddr
, LocalId(i
), fp(env
));
1454 auto const catcher
= CatchMaker
{
1456 CatchMaker::Kind::NotInlining
,
1461 return builtinCall(env
, callee
, params
,
1462 callee
->numParams(), catcher
);
1464 [&] (SSATmp
* ret
) { return ret
; },
1466 return callee
->attrs() & AttrParamCoerceModeFalse
?
1467 cns(env
, false) : cns(env
, TInitNull
);
1473 hint(env
, Block::Hint::Unlikely
);
1474 genericNativeImpl();
1479 //////////////////////////////////////////////////////////////////////
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
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
1500 decRef(env
, stack_base
);
1504 if (!(keyType
<= TInt
|| keyType
<= TStr
)) {
1505 interpOne(env
, TCell
, 3);
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
1517 auto const value
= gen(env
, ArrayIdx
, use_base
, key
, def
);
1519 decRef(env
, stack_base
);
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
);
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
));
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;
1564 if (keyType
.strVal()->isStrictlyInteger(dummy
)) return false;
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
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
)) {
1598 interpOne(env
, TCell
, 3);
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
);
1621 auto const simple_key
=
1622 keyType
<= TInt
|| keyType
<= TStr
;
1625 implGenericIdx(env
);
1629 if (baseType
<= TArr
) {
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
);
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));
1665 key
= cns(env
, staticEmptyString());
1668 if (!key
->isA(TStr
) && !key
->isA(TInt
)) PUNT(AKExists_badKey
);
1671 gen(env
, arr
->isA(TArr
) ? AKExistsArr
: AKExistsObj
, arr
, key
);
1677 void emitGetMemoKey(IRGS
& env
) {
1678 auto const inTy
= topC(env
)->type();
1680 // An int is already a valid key. No-op.
1683 if (inTy
<= TNull
) {
1684 auto input
= popC(env
);
1685 push(env
, cns(env
, s_empty
.get()));
1690 auto const obj
= popC(env
);
1691 auto const key
= gen(env
, GetMemoKey
, obj
);
1696 void emitSilence(IRGS
& env
, Id localId
, SilenceOp subop
) {
1697 // We can't generate direct StLoc and LdLocs in pseudomains (violates an IR
1699 if (curFunc(env
)->isPseudoMain()) PUNT(PseudoMain
-Silence
);
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
));
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
);
1718 //////////////////////////////////////////////////////////////////////