2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
17 #include "hphp/runtime/vm/jit/irgen-minstr.h"
18 #include "hphp/runtime/vm/jit/irgen.h"
20 #include "hphp/runtime/base/strings.h"
21 #include "hphp/runtime/base/collections.h"
22 #include "hphp/runtime/vm/native-prop-handler.h"
24 #include "hphp/runtime/vm/jit/array-kind-profile.h"
25 #include "hphp/runtime/vm/jit/array-offset-profile.h"
26 #include "hphp/runtime/vm/jit/normalized-instruction.h"
27 #include "hphp/runtime/vm/jit/minstr-effects.h"
28 #include "hphp/runtime/vm/jit/target-profile.h"
29 #include "hphp/runtime/vm/jit/type-array-elem.h"
30 #include "hphp/runtime/vm/jit/type-constraint.h"
31 #include "hphp/runtime/vm/jit/type.h"
33 #include "hphp/runtime/vm/jit/irgen-arith.h"
34 #include "hphp/runtime/vm/jit/irgen-exit.h"
35 #include "hphp/runtime/vm/jit/irgen-incdec.h"
36 #include "hphp/runtime/vm/jit/irgen-interpone.h"
37 #include "hphp/runtime/vm/jit/irgen-sprop-global.h"
39 #include "hphp/runtime/vm/jit/irgen-internal.h"
41 #include "hphp/runtime/ext/collections/ext_collections-map.h"
42 #include "hphp/runtime/ext/collections/ext_collections-pair.h"
43 #include "hphp/runtime/ext/collections/ext_collections-vector.h"
45 #include "hphp/util/safe-cast.h"
47 #include <folly/Optional.h>
51 namespace HPHP
{ namespace jit
{ namespace irgen
{
55 //////////////////////////////////////////////////////////////////////
57 const StaticString
s_ArrayKindProfile("ArrayKindProfile");
59 //////////////////////////////////////////////////////////////////////
61 bool wantPropSpecializedWarnings() {
62 return !RuntimeOption::RepoAuthoritative
||
63 !RuntimeOption::EvalDisableSomeRepoAuthNotices
;
66 //////////////////////////////////////////////////////////////////////
77 Vector
, // c_Vector* or c_ImmVector*
82 //////////////////////////////////////////////////////////////////////
83 // Property information.
87 explicit PropInfo(int offset
,
89 RepoAuthType repoAuthType
,
90 const Class
* objClass
,
91 const Class
* propClass
)
93 , immutable
{immutable
}
94 , repoAuthType
{repoAuthType
}
96 , propClass
{propClass
}
100 bool immutable
{false};
101 RepoAuthType repoAuthType
{};
102 const Class
* objClass
{nullptr};
103 const Class
* propClass
{nullptr};
107 * Try to find a property offset for the given key in baseClass. Will return a
108 * PropInfo with an offset of -1 if the mapping from baseClass's name to the
109 * Class* can change (which happens in sandbox mode when the ctx class is
110 * unrelated to baseClass).
112 PropInfo
getPropertyOffset(IRGS
& /*env*/, const Class
* ctx
,
113 const Class
* baseClass
, Type keyType
) {
114 if (!baseClass
) return PropInfo();
116 if (!keyType
.hasConstVal(TStr
)) return PropInfo();
117 auto const name
= keyType
.strVal();
119 // If we are not in repo-authoriative mode, we need to check that baseClass
120 // cannot change in between requests.
121 if (!RuntimeOption::RepoAuthoritative
||
122 !(baseClass
->preClass()->attrs() & AttrUnique
)) {
123 if (!ctx
) return PropInfo();
124 if (!ctx
->classof(baseClass
)) {
125 if (baseClass
->classof(ctx
)) {
126 // baseClass can change on us in between requests, but since
127 // ctx is an ancestor of baseClass we can make the weaker
128 // assumption that the object is an instance of ctx
131 // baseClass can change on us in between requests and it is
132 // not related to ctx, so bail out
138 // Lookup the index of the property based on ctx and baseClass
139 auto const lookup
= baseClass
->getDeclPropIndex(ctx
, name
);
140 auto const idx
= lookup
.prop
;
142 // If we couldn't find a property that is accessible in the current context,
144 if (idx
== kInvalidSlot
|| !lookup
.accessible
) return PropInfo();
146 // If it's a declared property we're good to go: even if a subclass redefines
147 // an accessible property with the same name it's guaranteed to be at the same
150 baseClass
->declPropOffset(idx
),
151 bool(baseClass
->declProperties()[idx
].attrs
& AttrIsImmutable
),
152 baseClass
->declPropRepoAuthType(idx
),
154 baseClass
->declProperties()[idx
].cls
159 * Returns true iff a Prop{X,DX,Q} operation with the given base and key will
160 * not write to its tvRef src, using the same set of conditions checked in
161 * ObjectData::propImpl(). This allows us to skip the very expensive ratchet
162 * operation after intermediate operations.
164 bool prop_ignores_tvref(IRGS
& env
, SSATmp
* base
, const SSATmp
* key
) {
165 // Make sure it's an object of a known class.
166 if (!base
->isA(TObj
) || !base
->type().clsSpec().cls()) return false;
168 auto cls
= base
->type().clsSpec().cls();
169 auto propType
= TGen
;
170 auto isDeclared
= false;
171 auto propClass
= cls
;
173 // If the property name is known, try to look it up and get its RAT.
174 if (key
->hasConstVal(TStr
)) {
175 auto const keyStr
= key
->strVal();
176 auto const ctx
= curClass(env
);
177 auto const lookup
= cls
->getDeclPropIndex(ctx
, keyStr
);
178 if (lookup
.prop
!= kInvalidSlot
) {
180 propClass
= cls
->declProperties()[lookup
.prop
].cls
;
181 if (RuntimeOption::RepoAuthoritative
) {
182 propType
= typeFromRAT(cls
->declPropRepoAuthType(lookup
.prop
), nullptr);
187 // Magic getters/setters use tvRef if the property is unset.
188 if (classMayHaveMagicPropMethods(cls
) && propType
.maybe(TUninit
)) {
192 // Native prop handlers never kick in for declared properties, even if
194 if (!isDeclared
&& cls
->hasNativePropHandler()) return false;
196 if (propClass
== cls
||
197 env
.irb
->constrainValue(base
, TypeConstraint(propClass
).setWeak())) {
198 env
.irb
->constrainValue(base
, TypeConstraint(cls
));
203 //////////////////////////////////////////////////////////////////////
205 folly::Optional
<TypeConstraint
> simpleOpConstraint(SimpleOp op
) {
210 case SimpleOp::Array
:
211 case SimpleOp::ProfiledPackedArray
:
212 case SimpleOp::VecArray
:
214 case SimpleOp::Keyset
:
215 case SimpleOp::String
:
216 return TypeConstraint(DataTypeSpecific
);
218 case SimpleOp::PackedArray
:
219 return TypeConstraint(DataTypeSpecialized
).setWantArrayKind();
221 case SimpleOp::Vector
:
222 return TypeConstraint(c_Vector::classof());
225 return TypeConstraint(c_Map::classof());
228 return TypeConstraint(c_Pair::classof());
231 always_assert(false);
234 //////////////////////////////////////////////////////////////////////
237 * Obtain the member base pointer.
239 * Note that the LdMBase may get preOptimize'd away, or might have its type
240 * refined, based on earlier tracked updates to the member base.
242 SSATmp
* ldMBase(IRGS
& env
) {
243 return gen(env
, LdMBase
, TPtrToGen
);
247 * Returns a pointer to a specific value in MInstrState.
249 SSATmp
* misLea(IRGS
& env
, int32_t offset
) {
250 env
.irb
->fs().setNeedRatchet(true);
251 return gen(env
, LdMIStateAddr
, cns(env
, offset
));
254 SSATmp
* tvRefPtr(IRGS
& env
) {
255 return misLea(env
, offsetof(MInstrState
, tvRef
));
258 SSATmp
* propTvRefPtr(IRGS
& env
, SSATmp
* base
, const SSATmp
* key
) {
259 return prop_ignores_tvref(env
, base
, key
)
260 ? cns(env
, Type::cns(nullptr, TPtrToMISGen
))
264 SSATmp
* tvRef2Ptr(IRGS
& env
) {
265 return misLea(env
, offsetof(MInstrState
, tvRef2
));
268 SSATmp
* ptrToInitNull(IRGS
& env
) {
269 // Nothing is allowed to write anything to the init null variant, so this
270 // inner type is always true.
271 return cns(env
, Type::cns(&immutable_null_base
, TPtrToOtherInitNull
));
274 SSATmp
* ptrToUninit(IRGS
& env
) {
275 // Nothing can write to the uninit null variant either, so the inner type
276 // here is also always true.
277 return cns(env
, Type::cns(&immutable_uninit_base
, TPtrToOtherUninit
));
280 bool mightCallMagicPropMethod(MOpMode mode
, PropInfo propInfo
) {
281 if (!typeFromRAT(propInfo
.repoAuthType
, nullptr).maybe(TUninit
)) {
284 auto const cls
= propInfo
.objClass
;
285 if (!cls
) return true;
286 // NB: this function can't yet be used for unset or isset contexts. Just get
288 auto const relevant_attrs
=
289 // Magic getters can be invoked both in define contexts and non-define
291 AttrNoOverrideMagicGet
|
292 // But magic setters are only possible in define contexts.
293 (mode
== MOpMode::Define
? AttrNoOverrideMagicSet
: AttrNone
);
294 return (cls
->attrs() & relevant_attrs
) != relevant_attrs
;
297 //////////////////////////////////////////////////////////////////////
300 * This is called in a few places to be consistent with old minstrs, and should
301 * be revisited once they're gone. It probably doesn't make sense to always
302 * guard on an object class when we have one.
304 void specializeObjBase(IRGS
& env
, SSATmp
* base
) {
305 if (base
&& base
->isA(TObj
) && base
->type().clsSpec().cls()) {
306 env
.irb
->constrainValue(base
, TypeConstraint(base
->type().clsSpec().cls()));
310 //////////////////////////////////////////////////////////////////////
313 PropInfo
getCurrentPropertyOffset(IRGS
& env
, SSATmp
* base
, Type keyType
,
315 // We allow the use of clases from nullable objects because
316 // emitPropSpecialized() explicitly checks for null (when needed) before
317 // doing the property access.
318 auto const baseType
= base
->type().derefIfPtr();
319 if (!(baseType
< (TObj
| TInitNull
) && baseType
.clsSpec())) return PropInfo
{};
321 auto const baseCls
= baseType
.clsSpec().cls();
322 auto const info
= getPropertyOffset(env
, curClass(env
), baseCls
, keyType
);
323 if (info
.offset
== -1) return info
;
325 if (env
.irb
->constrainValue(base
,
326 TypeConstraint(info
.propClass
).setWeak())) {
328 // We can't use this specialized class without making a guard more
329 // expensive, so don't do it.
332 specializeObjBase(env
, base
);
339 * Helper for emitPropSpecialized to check if a property is Uninit. It returns
340 * a pointer to the property's address, or &immutable_null_base if the property
341 * was Uninit and doWarn is true.
343 * We can omit the uninit check for properties that we know may not be uninit
344 * due to the frontend's type inference.
346 SSATmp
* checkInitProp(IRGS
& env
,
352 assertx(key
->isA(TStaticStr
));
353 assertx(baseAsObj
->isA(TObj
));
354 assertx(propAddr
->type() <= TPtrToGen
);
355 assertx(!doWarn
|| !doDefine
);
357 auto const needsCheck
= doWarn
&& propAddr
->type().deref().maybe(TUninit
);
358 if (!needsCheck
) return propAddr
;
363 gen(env
, CheckInitMem
, taken
, propAddr
);
365 [&] { // Next: Property isn't Uninit. Do nothing.
368 [&] { // Taken: Property is Uninit. Raise a warning and return
369 // &immutable_null_base.
370 hint(env
, Block::Hint::Unlikely
);
371 if (wantPropSpecializedWarnings()) {
372 gen(env
, RaiseUndefProp
, baseAsObj
, key
);
374 return ptrToInitNull(env
);
379 SSATmp
* emitPropSpecialized(
387 assertx(mode
!= MOpMode::Warn
|| mode
!= MOpMode::Unset
);
388 auto const doWarn
= mode
== MOpMode::Warn
;
389 auto const doDefine
= mode
== MOpMode::Define
|| mode
== MOpMode::Unset
;
391 auto const initNull
= ptrToInitNull(env
);
392 auto const baseType
= base
->type();
395 * Normal case, where the base is an object (and not a pointer to
396 * something)---just do a lea with the type information we got from static
397 * analysis. The caller of this function will use it to know whether it can
398 * avoid a generic incref, unbox, etc.
400 if (baseType
<= TObj
) {
401 auto const propAddr
= gen(
404 ByteOffsetData
{ propInfo
.offset
},
405 typeFromRAT(propInfo
.repoAuthType
, curClass(env
)).ptr(Ptr::Prop
),
408 return checkInitProp(env
, base
, propAddr
, key
, doWarn
, doDefine
);
412 * We also support nullable objects for the base. This is a frequent result
413 * of static analysis on multi-dim property accesses ($foo->bar->baz), since
414 * hhbbc doesn't try to prove __construct must be run or that sort of thing
415 * (so every object-holding object property can also be null).
417 * After a null check, if it's actually an object we can just do LdPropAddr,
418 * otherwise we just give out &immutable_null_base (after raising the
419 * appropriate warnings).
424 gen(env
, CheckTypeMem
, TObj
, taken
, base
);
427 // Next: Base is an object. Load property and check for uninit.
428 auto const obj
= gen(env
, LdMem
, baseType
.deref() & TObj
, base
);
429 auto const propAddr
= gen(
432 ByteOffsetData
{ propInfo
.offset
},
433 typeFromRAT(propInfo
.repoAuthType
, curClass(env
)).ptr(Ptr::Prop
),
436 return checkInitProp(env
, obj
, propAddr
, key
, doWarn
, doDefine
);
438 [&] { // Taken: Base is Null. Raise warnings/errors and return InitNull.
439 hint(env
, Block::Hint::Unlikely
);
440 if (!nullsafe
&& doWarn
) {
441 auto const msg
= makeStaticString(
442 "Cannot access property on non-object");
443 gen(env
, RaiseNotice
, cns(env
, msg
));
447 * This case is where we're logically supposed to do stdClass
448 * promotion. However, it's impossible that we're going to be asked to
449 * do this with the way type inference works ahead of time right now:
451 * o In defining minstrs, the only way hhbbc will know that an object
452 * type is nullable and also specialized is if the only type it can
453 * be is ?Obj=stdClass. (This is because it does object property
454 * inference in a control flow insensitive way, so if null is
455 * possible stdClass must be added to the type, and there are no
456 * unions of multiple specialized object types.)
458 * o On the other hand, if the type was really ?Obj=stdClass, we
459 * wouldn't have gotten a known property offset for any properties,
460 * because stdClass has no declared properties, so we can't be
463 * We could punt, but it's better to assert for now because if we
464 * change this in hhbbc it will be on-purpose...
468 "Static analysis error: we would've needed to generate "
469 "stdClass-promotion code in the JIT, which is unexpected."
477 //////////////////////////////////////////////////////////////////////
478 // "Simple op" handlers.
480 void checkCollectionBounds(IRGS
& env
, SSATmp
* base
,
481 SSATmp
* idx
, SSATmp
* limit
) {
482 assertx(base
->isA(TObj
));
487 auto ok
= gen(env
, CheckRange
, idx
, limit
);
488 gen(env
, JmpZero
, taken
, ok
);
491 hint(env
, Block::Hint::Unlikely
);
492 gen(env
, ThrowOutOfBounds
, base
, idx
);
497 void checkVecBounds(IRGS
& env
, SSATmp
* base
, SSATmp
* idx
) {
498 assertx(base
->isA(TVec
));
503 gen(env
, CheckPackedArrayDataBounds
, taken
, base
, idx
);
506 hint(env
, Block::Hint::Unlikely
);
507 gen(env
, ThrowOutOfBounds
, base
, idx
);
512 template<class Finish
>
513 SSATmp
* emitPackedArrayGet(IRGS
& env
, SSATmp
* base
, SSATmp
* key
, MOpMode mode
,
515 assertx(base
->isA(TArr
) &&
516 base
->type().arrSpec().kind() == ArrayData::kPackedKind
&&
519 auto finishMe
= [&](SSATmp
* elem
) {
520 auto unboxed
= unbox(env
, elem
, nullptr);
521 gen(env
, IncRef
, unboxed
);
528 gen(env
, CheckPackedArrayDataBounds
, taken
, base
, key
);
531 auto const res
= gen(env
, LdPackedElem
, base
, key
);
532 auto const pres
= profiledType(env
, res
, [&] { finish(finishMe(res
)); });
533 return finishMe(pres
);
536 hint(env
, Block::Hint::Unlikely
);
537 if (mode
== MOpMode::Warn
|| mode
== MOpMode::InOut
) {
538 auto const inout
= mode
== MOpMode::InOut
;
539 gen(env
, RaiseArrayIndexNotice
,
540 RaiseArrayIndexNoticeData
{ inout
}, key
);
542 return cns(env
, TInitNull
);
547 template<class Finish
>
548 SSATmp
* emitVecArrayGet(IRGS
& env
, SSATmp
* base
, SSATmp
* key
,
550 assertx(base
->isA(TVec
));
552 if (!key
->isA(TInt
)) {
553 gen(env
, ThrowInvalidArrayKey
, base
, key
);
554 return cns(env
, TBottom
);
557 checkVecBounds(env
, base
, key
);
559 auto finishMe
= [&](SSATmp
* elem
) {
560 gen(env
, IncRef
, elem
);
564 auto const elem
= gen(env
, LdVecElem
, base
, key
);
565 auto const pelem
= profiledType(env
, elem
, [&] { finish(finishMe(elem
)); });
566 return finishMe(pelem
);
569 template<class Finish
>
570 SSATmp
* emitVecArrayQuietGet(IRGS
& env
, SSATmp
* base
, SSATmp
* key
,
572 assertx(base
->isA(TVec
));
574 if (key
->isA(TStr
)) return cns(env
, TInitNull
);
575 if (!key
->isA(TInt
)) {
576 gen(env
, ThrowInvalidArrayKey
, base
, key
);
577 return cns(env
, TBottom
);
580 auto const elem
= cond(
583 gen(env
, CheckPackedArrayDataBounds
, taken
, base
, key
);
585 [&] { return gen(env
, LdVecElem
, base
, key
); },
586 [&] { return cns(env
, TInitNull
); }
589 auto finishMe
= [&](SSATmp
* element
) {
590 gen(env
, IncRef
, element
);
594 auto const pelem
= profiledType(env
, elem
, [&] { finish(finishMe(elem
)); });
595 return finishMe(pelem
);
598 template<class Finish
>
599 SSATmp
* emitDictKeysetGet(IRGS
& env
, SSATmp
* base
, SSATmp
* key
,
600 bool quiet
, bool is_dict
, Finish finish
) {
601 assertx(base
->isA(is_dict
? TDict
: TKeyset
));
603 if (!key
->isA(TInt
| TStr
)) {
604 gen(env
, ThrowInvalidArrayKey
, base
, key
);
605 return cns(env
, TBottom
);
608 auto finishMe
= [&](SSATmp
* elem
) {
609 gen(env
, IncRef
, elem
);
613 auto const elem
= profiledArrayAccess(
615 [&] (SSATmp
* base
, SSATmp
* key
, uint32_t pos
) {
616 return gen(env
, is_dict
? DictGetK
: KeysetGetK
, IndexData
{ pos
},
623 ? (quiet
? DictGetQuiet
: DictGet
)
624 : (quiet
? KeysetGetQuiet
: KeysetGet
),
630 auto const pelem
= profiledType(env
, elem
, [&] { finish(finishMe(elem
)); });
631 return finishMe(pelem
);
634 template<class Finish
>
635 SSATmp
* emitArrayGet(IRGS
& env
, SSATmp
* base
, SSATmp
* key
, MOpMode mode
,
637 auto const elem
= profiledArrayAccess(env
, base
, key
,
638 [&] (SSATmp
* arr
, SSATmp
* key
, uint32_t pos
) {
639 return gen(env
, MixedArrayGetK
, IndexData
{ pos
}, arr
, key
);
642 return gen(env
, ArrayGet
, MOpModeData
{ mode
}, base
, key
);
645 auto finishMe
= [&](SSATmp
* element
) {
646 auto const cell
= unbox(env
, element
, nullptr);
647 gen(env
, IncRef
, cell
);
650 auto const pelem
= profiledType(env
, elem
, [&] { finish(finishMe(elem
)); });
651 return finishMe(pelem
);
654 template<class Finish
>
655 SSATmp
* emitProfiledPackedArrayGet(IRGS
& env
, SSATmp
* base
, SSATmp
* key
,
656 MOpMode mode
, Finish finish
) {
657 TargetProfile
<ArrayKindProfile
> prof(env
.context
,
658 env
.irb
->curMarker(),
659 s_ArrayKindProfile
.get());
660 if (prof
.profiling()) {
661 gen(env
, ProfileArrayKind
, RDSHandleData
{prof
.handle()}, base
);
662 return emitArrayGet(env
, base
, key
, mode
, finish
);
665 if (prof
.optimizing()) {
666 auto const data
= prof
.data(ArrayKindProfile::reduce
);
667 auto const typePackedArr
= Type::Array(ArrayData::kPackedKind
);
668 if (base
->type().maybe(typePackedArr
) &&
669 (data
.fraction(ArrayData::kPackedKind
) == 1.0 ||
670 RuntimeOption::EvalJitPGOArrayGetStress
)) {
671 // It's safe to side-exit still because we only do these profiled array
672 // gets on the first element, with simple bases and single-element dims.
673 // See computeSimpleCollectionOp.
674 auto const exit
= makeExit(env
);
675 base
= gen(env
, CheckType
, typePackedArr
, exit
, base
);
676 env
.irb
->constrainValue(
678 TypeConstraint(DataTypeSpecialized
).setWantArrayKind()
680 return emitPackedArrayGet(env
, base
, key
, mode
, finish
);
684 // Fall back to a generic array get.
685 return emitArrayGet(env
, base
, key
, mode
, finish
);
688 SSATmp
* emitVectorGet(IRGS
& env
, SSATmp
* base
, SSATmp
* key
) {
689 auto const size
= gen(env
, LdVectorSize
, base
);
690 checkCollectionBounds(env
, base
, key
, size
);
691 base
= gen(env
, LdVectorBase
, base
);
692 static_assert(sizeof(TypedValue
) == 16,
693 "TypedValue size expected to be 16 bytes");
694 auto idx
= gen(env
, Shl
, key
, cns(env
, 4));
695 auto result
= gen(env
, LdElem
, base
, idx
);
696 gen(env
, IncRef
, result
);
700 SSATmp
* emitPairGet(IRGS
& env
, SSATmp
* base
, SSATmp
* key
) {
701 assertx(key
->isA(TInt
));
703 auto const idx
= [&] {
704 if (key
->hasConstVal()) {
705 auto keyVal
= key
->intVal();
706 if (keyVal
< 0 || keyVal
> 1) PUNT(emitPairGet
);
708 // no reason to check bounds
709 return cns(env
, keyVal
* sizeof(TypedValue
));
712 static_assert(sizeof(TypedValue
) == 16,
713 "TypedValue size expected to be 16 bytes");
714 checkCollectionBounds(env
, base
, key
, cns(env
, 2));
715 return gen(env
, Shl
, key
, cns(env
, 4));
718 auto const pairBase
= gen(env
, LdPairBase
, base
);
719 auto const result
= gen(env
, LdElem
, pairBase
, idx
);
720 gen(env
, IncRef
, result
);
724 SSATmp
* emitPackedArrayIsset(IRGS
& env
, SSATmp
* base
, SSATmp
* key
) {
725 assertx(base
->type().arrSpec().kind() == ArrayData::kPackedKind
);
727 auto const elem
= arrElemType(base
->type(), key
->type(), curClass(env
));
728 if (elem
.first
<= TNull
) {
729 return cns(env
, false);
730 } else if (elem
.second
) {
731 return cns(env
, true);
737 gen(env
, CheckPackedArrayDataBounds
, taken
, base
, key
);
740 auto const packedElem
= gen(env
, LdPackedElem
, base
, key
);
741 return gen(env
, IsNType
, TNull
, packedElem
);
744 return cns(env
, false);
749 SSATmp
* emitVecArrayIsset(IRGS
& env
, SSATmp
* base
, SSATmp
* key
) {
750 assertx(base
->isA(TVec
));
752 if (key
->isA(TStr
)) return cns(env
, false);
753 if (!key
->isA(TInt
)) {
754 gen(env
, ThrowInvalidArrayKey
, base
, key
);
755 return cns(env
, TBottom
);
761 gen(env
, CheckPackedArrayDataBounds
, taken
, base
, key
);
764 auto const elem
= gen(env
, LdVecElem
, base
, key
);
765 return gen(env
, IsNType
, TInitNull
, elem
);
767 [&] { return cns(env
, false); }
771 SSATmp
* emitDictIsset(IRGS
& env
, SSATmp
* base
, SSATmp
* key
) {
772 assertx(base
->isA(TDict
));
773 if (!key
->isA(TInt
| TStr
)) {
774 gen(env
, ThrowInvalidArrayKey
, base
, key
);
775 return cns(env
, TBottom
);
777 return gen(env
, DictIsset
, base
, key
);
780 SSATmp
* emitKeysetIsset(IRGS
& env
, SSATmp
* base
, SSATmp
* key
) {
781 assertx(base
->isA(TKeyset
));
782 if (!key
->isA(TInt
| TStr
)) {
783 gen(env
, ThrowInvalidArrayKey
, base
, key
);
784 return cns(env
, TBottom
);
786 return gen(env
, KeysetIsset
, base
, key
);
789 SSATmp
* emitVecArrayEmptyElem(IRGS
& env
, SSATmp
* base
, SSATmp
* key
) {
790 assertx(base
->isA(TVec
));
792 if (key
->isA(TStr
)) return cns(env
, true);
793 if (!key
->isA(TInt
)) {
794 gen(env
, ThrowInvalidArrayKey
, base
, key
);
795 return cns(env
, TBottom
);
801 gen(env
, CheckPackedArrayDataBounds
, taken
, base
, key
);
804 auto const elem
= gen(env
, LdVecElem
, base
, key
);
805 auto const b
= gen(env
, ConvCellToBool
, elem
);
806 return gen(env
, XorBool
, b
, cns(env
, true));
808 [&] { return cns(env
, true); }
812 SSATmp
* emitDictEmptyElem(IRGS
& env
, SSATmp
* base
, SSATmp
* key
) {
813 assertx(base
->isA(TDict
));
814 if (!key
->isA(TInt
| TStr
)) {
815 gen(env
, ThrowInvalidArrayKey
, base
, key
);
816 return cns(env
, TBottom
);
818 return gen(env
, DictEmptyElem
, base
, key
);
821 SSATmp
* emitKeysetEmptyElem(IRGS
& env
, SSATmp
* base
, SSATmp
* key
) {
822 assertx(base
->isA(TKeyset
));
823 if (!key
->isA(TInt
| TStr
)) {
824 gen(env
, ThrowInvalidArrayKey
, base
, key
);
825 return cns(env
, TBottom
);
827 return gen(env
, KeysetEmptyElem
, base
, key
);
830 void emitVectorSet(IRGS
& env
, SSATmp
* base
, SSATmp
* key
, SSATmp
* value
) {
831 auto const size
= gen(env
, LdVectorSize
, base
);
832 checkCollectionBounds(env
, base
, key
, size
);
837 gen(env
, VectorHasImmCopy
, taken
, base
);
840 hint(env
, Block::Hint::Unlikely
);
841 gen(env
, VectorDoCow
, base
);
845 gen(env
, IncRef
, value
);
846 auto const vecBase
= gen(env
, LdVectorBase
, base
);
847 static_assert(sizeof(TypedValue
) == 16,
848 "TypedValue size expected to be 16 bytes");
849 auto const idx
= gen(env
, Shl
, key
, cns(env
, 4));
850 auto const oldVal
= gen(env
, LdElem
, vecBase
, idx
);
851 gen(env
, StElem
, vecBase
, idx
, value
);
855 //////////////////////////////////////////////////////////////////////
857 SSATmp
* emitIncDecProp(IRGS
& env
, IncDecOp op
, SSATmp
* base
, SSATmp
* key
) {
858 auto const propInfo
= getCurrentPropertyOffset(env
, base
, key
->type(), false);
860 if (RuntimeOption::RepoAuthoritative
&&
861 propInfo
.offset
!= -1 &&
862 !propInfo
.immutable
&&
863 !mightCallMagicPropMethod(MOpMode::None
, propInfo
) &&
864 !mightCallMagicPropMethod(MOpMode::Define
, propInfo
)) {
866 // Special case for when the property is known to be an int.
867 if (base
->isA(TObj
) &&
868 propInfo
.repoAuthType
.tag() == RepoAuthType::Tag::Int
) {
869 base
= emitPropSpecialized(env
, base
, key
, false,
870 MOpMode::Define
, propInfo
);
871 auto const prop
= gen(env
, LdMem
, TInt
, base
);
872 auto const result
= incDec(env
, op
, prop
);
873 assertx(result
!= nullptr);
874 gen(env
, StMem
, base
, result
);
875 return isPre(op
) ? result
: prop
;
879 return gen(env
, IncDecProp
, IncDecData
{op
}, base
, key
);
882 template<class Finish
>
883 SSATmp
* emitCGetElem(IRGS
& env
, SSATmp
* base
, SSATmp
* key
,
884 MOpMode mode
, SimpleOp simpleOp
, Finish finish
) {
885 assertx(mode
== MOpMode::Warn
|| mode
== MOpMode::InOut
);
887 case SimpleOp::Array
:
888 return emitArrayGet(env
, base
, key
, mode
, finish
);
889 case SimpleOp::PackedArray
:
890 return emitPackedArrayGet(env
, base
, key
, mode
, finish
);
891 case SimpleOp::ProfiledPackedArray
:
892 return emitProfiledPackedArrayGet(env
, base
, key
, mode
, finish
);
893 case SimpleOp::VecArray
:
894 return emitVecArrayGet(env
, base
, key
, finish
);
896 return emitDictKeysetGet(env
, base
, key
, false, true, finish
);
897 case SimpleOp::Keyset
:
898 return emitDictKeysetGet(env
, base
, key
, false, false, finish
);
899 case SimpleOp::String
:
900 assertx(mode
!= MOpMode::InOut
);
901 return gen(env
, StringGet
, base
, key
);
902 case SimpleOp::Vector
:
903 assertx(mode
!= MOpMode::InOut
);
904 return emitVectorGet(env
, base
, key
);
906 assertx(mode
!= MOpMode::InOut
);
907 return emitPairGet(env
, base
, key
);
909 assertx(mode
!= MOpMode::InOut
);
910 return gen(env
, MapGet
, base
, key
);
912 return gen(env
, CGetElem
, MOpModeData
{mode
}, base
, key
);
914 always_assert(false);
917 template<class Finish
>
918 SSATmp
* emitCGetElemQuiet(IRGS
& env
, SSATmp
* base
, SSATmp
* key
,
919 MOpMode mode
, SimpleOp simpleOp
, Finish finish
) {
920 assertx(mode
!= MOpMode::Warn
);
922 case SimpleOp::VecArray
:
923 return emitVecArrayQuietGet(env
, base
, key
, finish
);
925 return emitDictKeysetGet(env
, base
, key
, true, true, finish
);
926 case SimpleOp::Keyset
:
927 return emitDictKeysetGet(env
, base
, key
, true, false, finish
);
928 case SimpleOp::Array
:
929 return emitArrayGet(env
, base
, key
, mode
, finish
);
930 case SimpleOp::PackedArray
:
931 return emitPackedArrayGet(env
, base
, key
, mode
, finish
);
932 case SimpleOp::ProfiledPackedArray
:
933 return emitProfiledPackedArrayGet(env
, base
, key
, mode
, finish
);
934 case SimpleOp::String
:
935 case SimpleOp::Vector
:
938 assertx(mode
!= MOpMode::InOut
);
940 return gen(env
, CGetElem
, MOpModeData
{mode
}, ldMBase(env
), key
);
942 always_assert(false);
945 SSATmp
* emitIssetElem(IRGS
& env
, SSATmp
* base
, SSATmp
* key
, SimpleOp simpleOp
) {
947 case SimpleOp::Array
:
948 case SimpleOp::ProfiledPackedArray
:
949 return gen(env
, ArrayIsset
, base
, key
);
950 case SimpleOp::PackedArray
:
951 return emitPackedArrayIsset(env
, base
, key
);
952 case SimpleOp::VecArray
:
953 return emitVecArrayIsset(env
, base
, key
);
955 return emitDictIsset(env
, base
, key
);
956 case SimpleOp::Keyset
:
957 return emitKeysetIsset(env
, base
, key
);
958 case SimpleOp::String
:
959 return gen(env
, StringIsset
, base
, key
);
960 case SimpleOp::Vector
:
961 return gen(env
, VectorIsset
, base
, key
);
963 return gen(env
, PairIsset
, base
, key
);
965 return gen(env
, MapIsset
, base
, key
);
967 return gen(env
, IssetElem
, base
, key
);
970 always_assert(false);
973 SSATmp
* emitEmptyElem(IRGS
& env
, SSATmp
* base
,
974 SSATmp
* key
, SimpleOp simpleOp
) {
976 case SimpleOp::VecArray
:
977 return emitVecArrayEmptyElem(env
, base
, key
);
979 return emitDictEmptyElem(env
, base
, key
);
980 case SimpleOp::Keyset
:
981 return emitKeysetEmptyElem(env
, base
, key
);
982 case SimpleOp::Array
:
983 case SimpleOp::PackedArray
:
984 case SimpleOp::ProfiledPackedArray
:
985 case SimpleOp::String
:
986 case SimpleOp::Vector
:
990 return gen(env
, EmptyElem
, ldMBase(env
), key
);
993 always_assert(false);
996 void setWithRefImpl(IRGS
& env
, int32_t keyLoc
, SSATmp
* value
) {
997 auto const key
= ldLoc(env
, keyLoc
, nullptr, DataTypeGeneric
);
998 gen(env
, SetWithRefElem
, ldMBase(env
), key
, value
);
1002 * Determine which simple collection op to use for the given base and key
1005 SimpleOp
simpleCollectionOp(Type baseType
, Type keyType
, bool readInst
,
1007 if (inOut
&& !(baseType
<= TArrLike
)) return SimpleOp::None
;
1009 if (baseType
<= TArr
) {
1010 auto isPacked
= false;
1011 if (auto arrSpec
= baseType
.arrSpec()) {
1012 isPacked
= arrSpec
.kind() == ArrayData::kPackedKind
;
1014 if (keyType
<= TInt
|| keyType
<= TStr
) {
1015 if (readInst
&& keyType
<= TInt
) {
1016 return isPacked
? SimpleOp::PackedArray
: SimpleOp::ProfiledPackedArray
;
1018 return SimpleOp::Array
;
1020 } else if (baseType
<= TVec
) {
1021 return SimpleOp::VecArray
;
1022 } else if (baseType
<= TDict
) {
1023 return SimpleOp::Dict
;
1024 } else if (baseType
<= TKeyset
) {
1025 return SimpleOp::Keyset
;
1026 } else if (baseType
<= TStr
) {
1027 if (keyType
<= TInt
) {
1028 // Don't bother with SetM on strings, because profile data shows it
1029 // basically never happens.
1030 if (readInst
) return SimpleOp::String
;
1032 } else if (baseType
< TObj
&& baseType
.clsSpec()) {
1033 const Class
* klass
= baseType
.clsSpec().cls();
1034 auto const isVector
= collections::isType(klass
, CollectionType::Vector
);
1035 auto const isImmVector
=
1036 collections::isType(klass
, CollectionType::ImmVector
);
1037 auto const isPair
= collections::isType(klass
, CollectionType::Pair
);
1038 auto const isMap
= collections::isType(klass
, CollectionType::Map
);
1039 auto const isImmMap
= collections::isType(klass
, CollectionType::ImmMap
);
1041 if (isVector
|| isPair
|| (isImmVector
&& readInst
)) {
1042 if (keyType
<= TInt
) {
1043 // We don't specialize setting pair elements.
1044 if (isPair
&& !readInst
) return SimpleOp::None
;
1046 return (isImmVector
|| isVector
) ? SimpleOp::Vector
: SimpleOp::Pair
;
1048 } else if ((isMap
|| (isImmMap
&& readInst
)) &&
1049 (keyType
<= TInt
|| keyType
<= TStr
)) {
1050 return SimpleOp::Map
;
1054 return SimpleOp::None
;
1058 * Store Uninit to tvRef and tvRef2.
1060 void initTvRefs(IRGS
& env
) {
1061 gen(env
, StMem
, tvRefPtr(env
), cns(env
, TUninit
));
1062 gen(env
, StMem
, tvRef2Ptr(env
), cns(env
, TUninit
));
1066 * DecRef tvRef and tvRef2.
1068 void cleanTvRefs(IRGS
& env
) {
1069 for (auto ptr
: {tvRefPtr(env
), tvRef2Ptr(env
)}) {
1070 decRef(env
, gen(env
, LdMem
, TGen
, ptr
));
1075 * If tvRef is not Uninit, DecRef tvRef2 and move tvRef's value to tvRef2,
1076 * storing Uninit to tvRef. Returns the adjusted base, which may point to
1079 SSATmp
* ratchetRefs(IRGS
& env
, SSATmp
* base
) {
1080 if (!env
.irb
->fs().needRatchet()) {
1084 auto tvRef
= tvRefPtr(env
);
1088 [&] (Block
* taken
) {
1089 gen(env
, CheckTypeMem
, taken
, TUninit
, tvRef
);
1091 [&] { // Next: tvRef is Uninit. Do nothing.
1094 [&] { // Taken: tvRef isn't Uninit. Ratchet the refs
1095 auto tvRef2
= tvRef2Ptr(env
);
1096 // Clean up tvRef2 before overwriting it.
1097 auto const oldRef2
= gen(env
, LdMem
, TGen
, tvRef2
);
1098 decRef(env
, oldRef2
);
1100 // Copy tvRef to tvRef2.
1101 auto const tvRefVal
= gen(env
, LdMem
, TGen
, tvRef
);
1102 gen(env
, StMem
, tvRef2
, tvRefVal
);
1104 gen(env
, StMem
, tvRef
, cns(env
, TUninit
));
1106 // Adjust base pointer. Don't use 'tvRef2' here so that we don't reuse
1107 // the temp. This will let us elide uses of the register for 'tvRef2',
1108 // until the Jmp we're going to emit here.
1109 return tvRef2Ptr(env
);
1114 void baseGImpl(IRGS
& env
, SSATmp
* name
, MOpMode mode
) {
1115 if (!name
->isA(TStr
)) PUNT(BaseG
-non
-string
-name
);
1116 auto base_mode
= mode
!= MOpMode::Unset
? mode
: MOpMode::None
;
1117 auto gblPtr
= gen(env
, BaseG
, MOpModeData
{base_mode
}, name
);
1118 gen(env
, StMBase
, gblPtr
);
1121 void baseSImpl(IRGS
& env
, SSATmp
* name
, uint32_t clsRefSlot
) {
1122 if (!name
->isA(TStr
)) PUNT(BaseS
-non
-string
-name
);
1123 auto const cls
= takeClsRef(env
, clsRefSlot
);
1124 auto const spropPtr
= ldClsPropAddr(env
, cls
, name
, true);
1125 gen(env
, StMBase
, spropPtr
);
1128 ///////////////////////////////////////////////////////////////////////////////
1131 * Punt if the given base type isn't known to be boxed or unboxed.
1133 void puntGenBase(Type baseType
) {
1134 if (baseType
.maybe(TCell
) && baseType
.maybe(TBoxedCell
)) {
1135 PUNT(MInstr
-GenBase
);
1140 * Update FrameState for a base at a known location.
1142 void simpleBaseImpl(IRGS
& env
, SSATmp
* base
, Location l
) {
1143 puntGenBase(base
->type());
1145 auto const predicted
= base
->isA(TBoxedCell
)
1146 ? folly::make_optional(env
.irb
->fs().predictedTypeOf(l
))
1148 env
.irb
->fs().setMemberBase(base
, predicted
);
1152 * Load and fully unpack---i.e., dereference and unbox---the member base.
1154 * Also constrains the base value (and, if applicable, its inner value) to
1155 * DataTypeSpecific; it's expected that the caller only uses extractBase() when
1156 * it has a certain useful type.
1158 SSATmp
* extractBase(IRGS
& env
) {
1159 auto const& mbase
= env
.irb
->fs().mbase();
1160 puntGenBase(mbase
.type
);
1162 env
.irb
->constrainLocation(Location::MBase
{}, DataTypeSpecific
);
1164 auto const base
= mbase
.value
1166 : gen(env
, LdMem
, mbase
.type
, ldMBase(env
));
1168 env
.irb
->constrainValue(base
, DataTypeSpecific
);
1170 if (base
->isA(TBoxedCell
)) {
1171 auto const innerTy
= env
.irb
->predictedMBaseInnerType();
1172 gen(env
, CheckRefInner
, innerTy
, makeExit(env
), base
);
1174 auto const inner
= gen(env
, LdRef
, innerTy
, base
);
1175 env
.irb
->constrainValue(inner
, DataTypeSpecific
);
1183 * Constrain the member base's types.
1185 * This does all the type constraint work of extractBase(). It's used in
1186 * situations where we need to know the fully-unpacked base's type, but only
1187 * need the base pointer.
1189 void constrainBase(IRGS
& env
) { extractBase(env
); }
1192 * Type of extractBase().
1194 * Used to determine whether to actually unpack the member base (and thus
1195 * constrain types) for a given minstr implementation.
1197 Type
predictedBaseType(const IRGS
& env
) {
1198 auto const baseType
= env
.irb
->fs().mbase().type
;
1200 return baseType
<= TBoxedCell
1201 ? env
.irb
->predictedMBaseInnerType()
1206 * Return the extracted object base if the predicted type is TObj, else just
1207 * return the base pointer.
1209 SSATmp
* extractBaseIfObj(IRGS
& env
) {
1210 auto const baseType
= predictedBaseType(env
);
1211 return baseType
<= TObj
? extractBase(env
) : ldMBase(env
);
1214 ///////////////////////////////////////////////////////////////////////////////
1217 s_NULLSAFE_PROP_WRITE_ERROR(Strings::NULLSAFE_PROP_WRITE_ERROR
);
1219 SSATmp
* propGenericImpl(IRGS
& env
, MOpMode mode
, SSATmp
* base
, SSATmp
* key
,
1221 auto const define
= mode
== MOpMode::Define
;
1222 if (define
&& nullsafe
) {
1223 gen(env
, RaiseError
, cns(env
, s_NULLSAFE_PROP_WRITE_ERROR
.get()));
1224 return ptrToInitNull(env
);
1227 auto const modeData
= MOpModeData
{mode
};
1229 auto const tvRef
= propTvRefPtr(env
, base
, key
);
1231 ? gen(env
, PropQ
, base
, key
, tvRef
)
1232 : gen(env
, define
? PropDX
: PropX
, modeData
, base
, key
, tvRef
);
1235 SSATmp
* propImpl(IRGS
& env
, MOpMode mode
, SSATmp
* key
, bool nullsafe
) {
1236 auto const baseType
= predictedBaseType(env
);
1238 if (mode
== MOpMode::Unset
&& !baseType
.maybe(TObj
)) {
1240 return ptrToInitNull(env
);
1243 auto const base
= extractBaseIfObj(env
);
1245 auto const propInfo
= getCurrentPropertyOffset(env
, base
, key
->type(), true);
1246 if (propInfo
.offset
== -1 ||
1247 propInfo
.immutable
||
1248 mode
== MOpMode::Unset
||
1249 mightCallMagicPropMethod(mode
, propInfo
)) {
1250 return propGenericImpl(env
, mode
, base
, key
, nullsafe
);
1253 return emitPropSpecialized(env
, base
, key
, nullsafe
, mode
, propInfo
);
1256 SSATmp
* vecElemImpl(IRGS
& env
, MOpMode mode
, Type baseType
, SSATmp
* key
) {
1257 assertx(baseType
<= TVec
);
1258 assertx(key
->isA(TInt
) || key
->isA(TStr
) ||
1259 !key
->type().maybe(TInt
| TStr
));
1261 auto const warn
= mode
== MOpMode::Warn
|| mode
== MOpMode::InOut
;
1262 auto const unset
= mode
== MOpMode::Unset
;
1263 auto const define
= mode
== MOpMode::Define
;
1265 auto const invalid_key
= [&] {
1266 gen(env
, ThrowInvalidArrayKey
, extractBase(env
), key
);
1267 return cns(env
, TBottom
);
1271 return key
->isA(TInt
)
1272 ? gen(env
, ElemVecD
, baseType
, ldMBase(env
), key
)
1277 return key
->isA(TInt
) ? gen(env
, ElemVecU
, baseType
, ldMBase(env
), key
) :
1278 key
->isA(TStr
) ? ptrToInitNull(env
)
1279 /* invalid */ : invalid_key();
1283 if (key
->isA(TInt
)) {
1284 auto const base
= extractBase(env
);
1285 checkVecBounds(env
, base
, key
);
1286 auto const elemType
= vecElemType(
1290 ).first
.ptr(Ptr::Elem
);
1291 return gen(env
, LdPackedArrayDataElemAddr
, elemType
, base
, key
);
1293 return invalid_key();
1296 if (key
->isA(TInt
)) {
1297 auto const base
= extractBase(env
);
1300 [&] (Block
* taken
) {
1301 gen(env
, CheckPackedArrayDataBounds
, taken
, base
, key
);
1304 auto const elemType
= vecElemType(
1308 ).first
.ptr(Ptr::Elem
);
1309 return gen(env
, LdPackedArrayDataElemAddr
, elemType
, base
, key
);
1311 [&] { return ptrToInitNull(env
); }
1315 if (key
->isA(TStr
)) return ptrToInitNull(env
);
1317 return invalid_key();
1320 SSATmp
* dictElemImpl(IRGS
& env
, MOpMode mode
, Type baseType
, SSATmp
* key
) {
1321 assertx(baseType
<= TDict
);
1323 auto const unset
= mode
== MOpMode::Unset
;
1324 auto const define
= mode
== MOpMode::Define
;
1326 auto const base
= extractBase(env
);
1328 if (!key
->isA(TInt
| TStr
)) {
1329 gen(env
, ThrowInvalidArrayKey
, base
, key
);
1330 return cns(env
, TBottom
);
1333 return profiledArrayAccess(
1335 [&] (SSATmp
* dict
, SSATmp
* key
, uint32_t pos
) {
1336 return gen(env
, ElemDictK
, IndexData
{ pos
}, dict
, key
);
1339 if (define
|| unset
) {
1340 return gen(env
, unset
? ElemDictU
: ElemDictD
,
1341 baseType
, ldMBase(env
), key
);
1344 mode
== MOpMode::Warn
||
1345 mode
== MOpMode::None
||
1346 mode
== MOpMode::InOut
1348 return gen(env
, ElemDictX
, MOpModeData
{ mode
}, base
, key
);
1350 define
|| unset
// cow check
1354 SSATmp
* keysetElemImpl(IRGS
& env
, MOpMode mode
, Type baseType
, SSATmp
* key
) {
1355 assertx(baseType
<= TKeyset
);
1357 auto const unset
= mode
== MOpMode::Unset
;
1358 auto const define
= mode
== MOpMode::Define
;
1360 auto const base
= extractBase(env
);
1362 if (!key
->isA(TInt
| TStr
)) {
1363 gen(env
, ThrowInvalidArrayKey
, base
, key
);
1364 return cns(env
, TBottom
);
1370 ThrowInvalidOperation
,
1371 cns(env
, s_InvalidKeysetOperationMsg
.get())
1373 return cns(env
, TBottom
);
1376 return profiledArrayAccess(
1378 [&] (SSATmp
* keyset
, SSATmp
* key
, uint32_t pos
) {
1379 return gen(env
, ElemKeysetK
, IndexData
{ pos
}, keyset
, key
);
1382 if (unset
) return gen(env
, ElemKeysetU
, baseType
, ldMBase(env
), key
);
1384 mode
== MOpMode::Warn
||
1385 mode
== MOpMode::None
||
1386 mode
== MOpMode::InOut
1388 return gen(env
, ElemKeysetX
, MOpModeData
{ mode
}, base
, key
);
1394 const StaticString
s_OP_NOT_SUPPORTED_STRING(Strings::OP_NOT_SUPPORTED_STRING
);
1396 SSATmp
* elemImpl(IRGS
& env
, MOpMode mode
, SSATmp
* key
) {
1397 DEBUG_ONLY
auto const warn
= mode
== MOpMode::Warn
;
1398 auto const unset
= mode
== MOpMode::Unset
;
1399 auto const define
= mode
== MOpMode::Define
;
1401 auto const baseType
= predictedBaseType(env
);
1403 assertx(!define
|| !unset
);
1404 assertx(!define
|| !warn
);
1406 if (baseType
<= TVec
) return vecElemImpl(env
, mode
, baseType
, key
);
1407 if (baseType
<= TDict
) return dictElemImpl(env
, mode
, baseType
, key
);
1408 if (baseType
<= TKeyset
) return keysetElemImpl(env
, mode
, baseType
, key
);
1410 if (baseType
<= TArr
&& key
->type().subtypeOfAny(TInt
, TStr
)) {
1411 auto const base
= extractBase(env
);
1413 return profiledArrayAccess(env
, base
, key
,
1414 [&] (SSATmp
* arr
, SSATmp
* key
, uint32_t pos
) {
1415 return gen(env
, ElemMixedArrayK
, IndexData
{ pos
}, arr
, key
);
1418 if (define
|| unset
) {
1419 return gen(env
, unset
? ElemArrayU
: ElemArrayD
,
1420 base
->type(), ldMBase(env
), key
);
1423 mode
== MOpMode::Warn
||
1424 mode
== MOpMode::None
||
1425 mode
== MOpMode::InOut
1427 return gen(env
, ElemArrayX
, MOpModeData
{ mode
}, base
, key
);
1429 define
|| unset
// cow check
1435 if (baseType
<= TStr
) {
1436 gen(env
, RaiseError
, cns(env
, s_OP_NOT_SUPPORTED_STRING
.get()));
1437 return ptrToUninit(env
);
1440 if (!baseType
.maybe(TArrLike
| TObj
)) {
1441 return ptrToUninit(env
);
1445 auto const op
= define
? ElemDX
: unset
? ElemUX
: ElemX
;
1446 return gen(env
, op
, MOpModeData
{ mode
}, ldMBase(env
), key
, tvRefPtr(env
));
1450 * Pop nDiscard elements from the stack, push the result (if present), DecRef
1451 * tvRef(2), and mark the member operation as complete.
1453 void mFinalImpl(IRGS
& env
, int32_t nDiscard
, SSATmp
* result
) {
1454 for (auto i
= 0; i
< nDiscard
; ++i
) popDecRef(env
);
1456 if (result
) push(env
, result
);
1458 gen(env
, FinishMemberOp
);
1461 SSATmp
* cGetPropImpl(IRGS
& env
, SSATmp
* base
, SSATmp
* key
,
1462 MOpMode mode
, bool nullsafe
) {
1463 auto const propInfo
= getCurrentPropertyOffset(env
, base
, key
->type(), true);
1465 if (propInfo
.offset
!= -1 &&
1466 !mightCallMagicPropMethod(MOpMode::None
, propInfo
)) {
1469 emitPropSpecialized(env
, base
, key
, nullsafe
, mode
, propInfo
);
1470 auto const ty
= propAddr
->type().deref();
1471 auto const cellPtr
=
1472 ty
.maybe(TBoxedCell
) ? gen(env
, UnboxPtr
, propAddr
) : propAddr
;
1473 auto const result
= gen(env
, LdMem
, ty
.unbox(), cellPtr
);
1474 gen(env
, IncRef
, result
);
1478 // No warning takes precedence over nullsafe.
1479 if (!nullsafe
|| mode
!= MOpMode::Warn
) {
1480 return gen(env
, CGetProp
, MOpModeData
{mode
}, base
, key
);
1482 return gen(env
, CGetPropQ
, base
, key
);
1485 Block
* makeCatchSet(IRGS
& env
, bool isSetWithRef
= false) {
1486 auto block
= defBlock(env
, Block::Hint::Unused
);
1488 BlockPusher
bp(*env
.irb
, makeMarker(env
, bcOff(env
)), block
);
1489 gen(env
, BeginCatch
);
1493 [&] (Block
* taken
) {
1494 gen(env
, UnwindCheckSideExit
, taken
, fp(env
), sp(env
));
1497 hint(env
, Block::Hint::Unused
);
1499 IRSPRelOffsetData
{ spOffBCFromIRSP(env
) },
1504 // Fallthrough from here on is side-exiting due to an InvalidSetMException.
1505 hint(env
, Block::Hint::Unused
);
1507 // For consistency with the interpreter, decref the rhs before we decref the
1508 // stack inputs, and decref the ratchet storage after the stack inputs.
1509 if (!isSetWithRef
) {
1510 popDecRef(env
, DataTypeGeneric
);
1512 auto const nDiscard
= env
.currentNormalizedInstruction
->imm
[0].u_IVA
;
1513 for (int i
= 0; i
< nDiscard
; ++i
) {
1514 popDecRef(env
, DataTypeGeneric
);
1517 if (!isSetWithRef
) {
1518 auto const val
= gen(env
, LdUnwinderValue
, TCell
);
1522 // The minstr is done here, so we want to drop a FinishMemberOp to kill off
1523 // stores to MIState.
1524 gen(env
, FinishMemberOp
);
1526 gen(env
, Jmp
, makeExit(env
, nextBcOff(env
)));
1530 SSATmp
* setPropImpl(IRGS
& env
, SSATmp
* key
) {
1531 auto const value
= topC(env
, BCSPRelOffset
{0}, DataTypeGeneric
);
1533 auto const base
= extractBaseIfObj(env
);
1535 auto const mode
= MOpMode::Define
;
1536 auto const propInfo
= getCurrentPropertyOffset(env
, base
, key
->type(), true);
1538 if (propInfo
.offset
!= -1 &&
1539 !propInfo
.immutable
&&
1540 !mightCallMagicPropMethod(mode
, propInfo
)) {
1541 auto propPtr
= emitPropSpecialized(env
, base
, key
, false, mode
, propInfo
);
1542 auto propTy
= propPtr
->type().deref();
1544 if (propTy
.maybe(TBoxedCell
)) {
1545 propTy
= propTy
.unbox();
1546 propPtr
= gen(env
, UnboxPtr
, propPtr
);
1549 env
.irb
->constrainValue(value
, DataTypeBoxAndCountness
);
1550 auto const oldVal
= gen(env
, LdMem
, propTy
, propPtr
);
1551 gen(env
, IncRef
, value
);
1552 gen(env
, StMem
, propPtr
, value
);
1553 decRef(env
, oldVal
);
1555 gen(env
, SetProp
, makeCatchSet(env
), base
, key
, value
);
1561 void handleStrTestResult(IRGS
& env
, SSATmp
* strTestResult
) {
1562 // We expected SetElem's base to not be a Str but might be wrong. Make an
1563 // exit trace to side exit to the next instruction, replacing our guess with
1564 // the correct stack output.
1567 [&] (Block
* taken
) {
1568 gen(env
, CheckNullptr
, taken
, strTestResult
);
1571 hint(env
, Block::Hint::Unlikely
);
1572 auto const str
= gen(env
, AssertNonNull
, strTestResult
);
1573 popDecRef(env
, DataTypeSpecific
);
1574 auto const nDiscard
= env
.currentNormalizedInstruction
->imm
[0].u_IVA
;
1575 for (int i
= 0; i
< nDiscard
; ++i
) {
1576 popDecRef(env
, DataTypeSpecific
);
1580 gen(env
, FinishMemberOp
);
1581 gen(env
, Jmp
, makeExit(env
, nextBcOff(env
)));
1586 SSATmp
* emitArrayLikeSet(IRGS
& env
, SSATmp
* key
, SSATmp
* value
) {
1587 // We need to store to a local after doing some user-visible operations, so
1588 // don't go down this path for pseudomains.
1589 if (curFunc(env
)->isPseudoMain()) return nullptr;
1591 auto const baseType
= predictedBaseType(env
);
1592 auto const base
= extractBase(env
);
1593 assertx(baseType
<= TArrLike
);
1595 auto const isVec
= baseType
<= TVec
;
1596 auto const isDict
= baseType
<= TDict
;
1597 auto const isKeyset
= baseType
<= TKeyset
;
1599 if ((isVec
&& !key
->isA(TInt
)) ||
1600 (isDict
&& !key
->isA(TInt
| TStr
))) {
1601 gen(env
, ThrowInvalidArrayKey
, base
, key
);
1602 return cns(env
, TBottom
);
1606 gen(env
, ThrowInvalidOperation
,
1607 cns(env
, s_InvalidKeysetOperationMsg
.get()));
1608 return cns(env
, TBottom
);
1611 auto const baseLoc
= [&]() -> folly::Optional
<Location
> {
1612 auto const basePtr
= ldMBase(env
);
1613 auto const ptrInst
= basePtr
->inst();
1615 switch (ptrInst
->op()) {
1617 auto const locID
= ptrInst
->extra
<LocalId
>()->locId
;
1618 return folly::make_optional
<Location
>(Location::Local
{ locID
});
1621 auto const irSPRel
= ptrInst
->extra
<IRSPRelOffsetData
>()->offset
;
1622 auto const fpRel
= irSPRel
.to
<FPInvOffset
>(env
.irb
->fs().irSPOff());
1623 return folly::make_optional
<Location
>(Location::Stack
{ fpRel
});
1629 if (!baseLoc
) return nullptr;
1631 // base may be from inside a RefData inside a stack/local, so to determine
1632 // setRef we must check the actual value of the stack/local.
1633 auto const rawBaseType
= provenType(env
, *baseLoc
);
1634 auto const setRef
= rawBaseType
<= TBoxedCell
;
1637 auto const box
= [&] {
1638 switch (baseLoc
->tag()) {
1640 return ldLoc(env
, baseLoc
->localId(), nullptr, DataTypeSpecific
);
1642 return top(env
, offsetFromBCSP(env
, baseLoc
->stackIdx()));
1645 always_assert(false);
1650 isVec
? VecSetRef
: isDict
? DictSetRef
: ArraySetRef
,
1651 base
, key
, value
, box
);
1653 // Unlike the non-ref case, we don't need to do anything to the stack/local
1654 // because any load of the box will be guarded.
1658 auto const newArr
= gen(env
,
1659 isVec
? VecSet
: isDict
? DictSet
: ArraySet
,
1662 // Update the base's location with the new array.
1663 switch (baseLoc
->tag()) {
1665 // We know it's not boxed (setRef above handles that), and the helper has
1666 // already decref'd the old array and incref'd newArr.
1667 gen(env
, StLoc
, LocalId
{ baseLoc
->localId() }, fp(env
), newArr
);
1671 IRSPRelOffsetData
{ offsetFromIRSP(env
, baseLoc
->stackIdx()) },
1676 always_assert(false);
1681 void setNewElemPackedArrayDataImpl(IRGS
& env
, SSATmp
* basePtr
, Type baseType
,
1686 auto const base
= extractBase(env
);
1688 if ((baseType
<= TArr
&& value
->type() <= TArr
) ||
1689 (baseType
<= TVec
&& value
->type() <= TVec
)) {
1690 auto const appendToSelf
= gen(env
, EqArrayDataPtr
, base
, value
);
1691 gen(env
, JmpNZero
, taken
, appendToSelf
);
1693 gen(env
, CheckArrayCOW
, taken
, base
);
1694 auto const offset
= gen(env
, ReservePackedArrayDataNewElem
, taken
, base
);
1695 auto const elemPtr
= gen(
1697 LdPackedArrayDataElemAddr
,
1702 gen(env
, IncRef
, value
);
1703 gen(env
, StMem
, elemPtr
, value
);
1706 if (baseType
<= Type::Array(ArrayData::kPackedKind
)) {
1707 gen(env
, SetNewElemArray
, makeCatchSet(env
), basePtr
, value
);
1708 } else if (baseType
<= TVec
) {
1709 gen(env
, SetNewElemVec
, makeCatchSet(env
), basePtr
, value
);
1711 always_assert(false);
1717 SSATmp
* setNewElemImpl(IRGS
& env
) {
1718 auto const value
= topC(env
);
1720 auto const baseType
= predictedBaseType(env
);
1722 // We load the member base pointer before calling makeCatchSet() to avoid
1723 // mismatched in-states for any catch block edges we emit later on.
1724 auto const basePtr
= ldMBase(env
);
1726 auto const tc
= TypeConstraint(DataTypeSpecialized
).setWantArrayKind();
1727 env
.irb
->constrainLocation(Location::MBase
{}, tc
);
1729 if (baseType
<= Type::Array(ArrayData::kPackedKind
) || baseType
<= TVec
) {
1730 setNewElemPackedArrayDataImpl(env
, basePtr
, baseType
, value
);
1731 } else if (baseType
<= TArr
) {
1733 gen(env
, SetNewElemArray
, makeCatchSet(env
), basePtr
, value
);
1734 } else if (baseType
<= TKeyset
) {
1736 if (!value
->isA(TInt
| TStr
)) {
1737 auto const base
= extractBase(env
);
1738 gen(env
, ThrowInvalidArrayKey
, makeCatchSet(env
), base
, value
);
1740 gen(env
, SetNewElemKeyset
, makeCatchSet(env
), basePtr
, value
);
1743 gen(env
, SetNewElem
, makeCatchSet(env
), basePtr
, value
);
1748 SSATmp
* setElemImpl(IRGS
& env
, SSATmp
* key
) {
1749 auto value
= topC(env
, BCSPRelOffset
{0}, DataTypeGeneric
);
1751 auto const baseType
= predictedBaseType(env
);
1752 auto const simpleOp
= simpleCollectionOp(baseType
, key
->type(), false, false);
1754 if (auto tc
= simpleOpConstraint(simpleOp
)) {
1755 env
.irb
->constrainLocation(Location::MBase
{}, *tc
);
1759 case SimpleOp::PackedArray
:
1760 case SimpleOp::String
:
1761 always_assert(false && "Bad SimpleOp in setElemImpl");
1764 case SimpleOp::Vector
:
1765 emitVectorSet(env
, extractBase(env
), key
, value
);
1769 gen(env
, MapSet
, extractBase(env
), key
, value
);
1772 case SimpleOp::Array
:
1773 case SimpleOp::ProfiledPackedArray
:
1774 case SimpleOp::VecArray
:
1775 case SimpleOp::Dict
:
1776 case SimpleOp::Keyset
:
1777 if (auto result
= emitArrayLikeSet(env
, key
, value
)) {
1780 // If we couldn't emit ArraySet, fall through to the generic path.
1782 case SimpleOp::Pair
:
1783 case SimpleOp::None
:
1784 // We load the member base pointer before calling makeCatchSet() to avoid
1785 // mismatched in-states for any catch block edges we emit later on.
1786 auto const basePtr
= ldMBase(env
);
1787 auto const result
= gen(env
, SetElem
, makeCatchSet(env
),
1788 basePtr
, key
, value
);
1789 auto const t
= result
->type();
1790 if (t
== TNullptr
) {
1791 // Base is not a string. Result is always value.
1792 } else if (t
== TCountedStr
) {
1793 // Base is a string. Stack result is a new string so we're responsible
1794 // for decreffing value.
1795 env
.irb
->constrainValue(value
, DataTypeBoxAndCountness
);
1799 assertx(t
== (TCountedStr
| TNullptr
));
1800 // Base might be a string. Emit a check to verify the result before
1801 // returning the optimistic result.
1802 handleStrTestResult(env
, result
);
1810 SSATmp
* memberKey(IRGS
& env
, MemberKey mk
) {
1815 return ldLocInnerWarn(env
, mk
.iva
, makeExit(env
),
1816 makePseudoMainExit(env
), DataTypeSpecific
);
1818 return topC(env
, BCSPRelOffset
{int32_t(mk
.iva
)});
1820 return cns(env
, mk
.int64
);
1821 case MET
: case MPT
: case MQT
:
1822 return cns(env
, mk
.litstr
);
1827 MOpMode
fpassFlags(IRGS
& env
, int32_t /*idx*/) {
1828 if (env
.currentNormalizedInstruction
->preppedByRef
) {
1829 return MOpMode::Define
;
1831 return MOpMode::Warn
;
1834 //////////////////////////////////////////////////////////////////////
1838 void emitBaseNC(IRGS
& env
, uint32_t /*idx*/, MOpMode
/*mode*/) {
1839 interpOne(env
, *env
.currentNormalizedInstruction
);
1842 void emitBaseNL(IRGS
& env
, int32_t /*locId*/, MOpMode
/*mode*/) {
1843 interpOne(env
, *env
.currentNormalizedInstruction
);
1846 void emitFPassBaseNC(IRGS
& env
, uint32_t arg
, uint32_t idx
) {
1847 emitBaseNC(env
, idx
, fpassFlags(env
, arg
));
1850 void emitFPassBaseNL(IRGS
& env
, uint32_t arg
, int32_t locId
) {
1851 emitBaseNL(env
, locId
, fpassFlags(env
, arg
));
1854 void emitBaseGC(IRGS
& env
, uint32_t idx
, MOpMode mode
) {
1856 auto name
= top(env
, BCSPRelOffset
{safe_cast
<int32_t>(idx
)});
1857 baseGImpl(env
, name
, mode
);
1860 void emitBaseGL(IRGS
& env
, int32_t locId
, MOpMode mode
) {
1862 auto name
= ldLocInner(env
, locId
, makeExit(env
), makePseudoMainExit(env
),
1864 baseGImpl(env
, name
, mode
);
1867 void emitFPassBaseGC(IRGS
& env
, uint32_t arg
, uint32_t idx
) {
1868 emitBaseGC(env
, idx
, fpassFlags(env
, arg
));
1871 void emitFPassBaseGL(IRGS
& env
, uint32_t arg
, int32_t locId
) {
1872 emitBaseGL(env
, locId
, fpassFlags(env
, arg
));
1875 void emitBaseSC(IRGS
& env
, uint32_t propIdx
, uint32_t slot
) {
1877 auto name
= top(env
, BCSPRelOffset
{safe_cast
<int32_t>(propIdx
)});
1878 baseSImpl(env
, name
, slot
);
1881 void emitBaseSL(IRGS
& env
, int32_t locId
, uint32_t slot
) {
1883 auto name
= ldLocInner(env
, locId
, makeExit(env
), makePseudoMainExit(env
),
1885 baseSImpl(env
, name
, slot
);
1888 void emitBaseL(IRGS
& env
, int32_t locId
, MOpMode mode
) {
1890 gen(env
, StMBase
, ldLocAddr(env
, locId
));
1892 auto base
= ldLoc(env
, locId
, makePseudoMainExit(env
), DataTypeGeneric
);
1894 if (base
->isA(TUninit
) && mode
== MOpMode::Warn
) {
1895 env
.irb
->constrainLocal(locId
, DataTypeSpecific
,
1896 "emitBaseL: Uninit base local");
1897 gen(env
, RaiseUninitLoc
, cns(env
, curFunc(env
)->localVarName(locId
)));
1900 simpleBaseImpl(env
, base
, Location::Local
{ safe_cast
<uint32_t>(locId
) });
1903 void emitFPassBaseL(IRGS
& env
, uint32_t arg
, int32_t locId
) {
1904 emitBaseL(env
, locId
, fpassFlags(env
, arg
));
1907 void emitBaseC(IRGS
& env
, uint32_t idx
) {
1910 auto const bcOff
= BCSPRelOffset
{safe_cast
<int32_t>(idx
)};
1911 auto const irOff
= offsetFromIRSP(env
, bcOff
);
1912 gen(env
, StMBase
, ldStkAddr(env
, bcOff
));
1914 auto base
= top(env
, bcOff
);
1915 simpleBaseImpl(env
, base
, Location::Stack
{ offsetFromFP(env
, irOff
) });
1918 void emitBaseR(IRGS
& env
, uint32_t idx
) {
1919 emitBaseC(env
, idx
);
1922 void emitBaseH(IRGS
& env
) {
1923 if (!curClass(env
)) return interpOne(env
, *env
.currentNormalizedInstruction
);
1926 auto base
= ldThis(env
);
1927 auto scratchPtr
= misLea(env
, offsetof(MInstrState
, tvTempBase
));
1928 gen(env
, StMem
, scratchPtr
, base
);
1929 gen(env
, StMBase
, scratchPtr
);
1930 env
.irb
->fs().setMemberBase(base
);
1933 void emitDim(IRGS
& env
, MOpMode mode
, MemberKey mk
) {
1934 // Eagerly mark us as not needing ratchets. If the intermediate operation
1935 // ends up calling misLea(), this will be set to true.
1936 env
.irb
->fs().setNeedRatchet(false);
1938 auto key
= memberKey(env
, mk
);
1939 auto newBase
= [&] {
1940 if (mcodeIsProp(mk
.mcode
)) {
1941 return propImpl(env
, mode
, key
, mk
.mcode
== MQT
);
1943 if (mcodeIsElem(mk
.mcode
)) {
1944 return elemImpl(env
, mode
, key
);
1949 newBase
= ratchetRefs(env
, newBase
);
1950 gen(env
, StMBase
, newBase
);
1953 void emitFPassDim(IRGS
& env
, uint32_t arg
, MemberKey mk
) {
1954 emitDim(env
, fpassFlags(env
, arg
), mk
);
1957 void emitQueryM(IRGS
& env
, uint32_t nDiscard
, QueryMOp query
, MemberKey mk
) {
1958 if (mk
.mcode
== MW
) PUNT(QueryNewElem
);
1960 auto const baseType
= predictedBaseType(env
);
1961 auto key
= memberKey(env
, mk
);
1962 auto simpleOp
= SimpleOp::None
;
1964 if (mcodeIsElem(mk
.mcode
)) {
1965 simpleOp
= simpleCollectionOp(baseType
, key
->type(), true,
1966 query
== QueryMOp::InOut
);
1968 if (simpleOp
!= SimpleOp::None
) {
1969 if (auto const tc
= simpleOpConstraint(simpleOp
)) {
1970 env
.irb
->constrainLocation(Location::MBase
{}, *tc
);
1975 auto const maybeExtractBase
= [simpleOp
] (IRGS
& environment
) {
1976 return simpleOp
== SimpleOp::None
1977 ? ldMBase(environment
)
1978 : extractBase(environment
);
1981 auto const result
= [&]() -> SSATmp
* {
1983 case QueryMOp::InOut
:
1984 case QueryMOp::CGet
: {
1985 auto const mode
= getQueryMOpMode(query
);
1987 mode
!= MOpMode::InOut
|| mcodeIsElem(mk
.mcode
),
1988 "QueryOp InOut can only be used with Elem codes"
1990 return mcodeIsProp(mk
.mcode
)
1991 ? cGetPropImpl(env
, extractBaseIfObj(env
), key
,
1992 mode
, mk
.mcode
== MQT
)
1993 : emitCGetElem(env
, maybeExtractBase(env
), key
, mode
, simpleOp
,
1994 [&](SSATmp
* el
) { mFinalImpl(env
, nDiscard
, el
); });
1997 case QueryMOp::CGetQuiet
: {
1998 auto const mode
= getQueryMOpMode(query
);
1999 return mcodeIsProp(mk
.mcode
)
2001 env
, extractBaseIfObj(env
), key
, mode
, mk
.mcode
== MQT
2003 : emitCGetElemQuiet(
2004 env
, maybeExtractBase(env
), key
, mode
, simpleOp
,
2005 [&](SSATmp
* el
) { mFinalImpl(env
, nDiscard
, el
); }
2009 case QueryMOp::Isset
:
2010 return mcodeIsProp(mk
.mcode
)
2011 ? gen(env
, IssetProp
, extractBaseIfObj(env
), key
)
2012 : emitIssetElem(env
, maybeExtractBase(env
), key
, simpleOp
);
2014 case QueryMOp::Empty
:
2015 return mcodeIsProp(mk
.mcode
)
2016 ? gen(env
, EmptyProp
, extractBaseIfObj(env
), key
)
2017 : emitEmptyElem(env
, maybeExtractBase(env
), key
, simpleOp
);
2022 mFinalImpl(env
, nDiscard
, result
);
2025 void emitVGetM(IRGS
& env
, uint32_t nDiscard
, MemberKey mk
) {
2026 auto key
= memberKey(env
, mk
);
2028 auto const result
= [&] {
2029 if (mcodeIsProp(mk
.mcode
)) {
2030 if (mk
.mcode
== MQT
) {
2031 gen(env
, RaiseError
, cns(env
, s_NULLSAFE_PROP_WRITE_ERROR
.get()));
2033 return gen(env
, VGetProp
, extractBaseIfObj(env
), key
);
2035 if (mcodeIsElem(mk
.mcode
)) {
2036 return gen(env
, VGetElem
, ldMBase(env
), key
);
2041 mFinalImpl(env
, nDiscard
, result
);
2044 void emitFPassM(IRGS
& env
, uint32_t arg
, uint32_t nDiscard
, MemberKey mk
,
2046 if (fpassFlags(env
, arg
) == MOpMode::Warn
) {
2047 checkFPassHint(env
, arg
, arg
+ nDiscard
, hint
, false);
2048 return emitQueryM(env
, nDiscard
, QueryMOp::CGet
, mk
);
2050 checkFPassHint(env
, arg
, arg
+ nDiscard
, hint
, true);
2051 emitVGetM(env
, nDiscard
, mk
);
2054 void emitSetM(IRGS
& env
, uint32_t nDiscard
, MemberKey mk
) {
2055 auto const key
= memberKey(env
, mk
);
2057 mk
.mcode
== MW
? setNewElemImpl(env
) :
2058 mcodeIsElem(mk
.mcode
) ? setElemImpl(env
, key
) :
2059 setPropImpl(env
, key
);
2061 popC(env
, DataTypeGeneric
);
2062 mFinalImpl(env
, nDiscard
, result
);
2065 void emitIncDecM(IRGS
& env
, uint32_t nDiscard
, IncDecOp incDec
, MemberKey mk
) {
2066 auto key
= memberKey(env
, mk
);
2068 auto const result
= [&] {
2069 if (mcodeIsProp(mk
.mcode
)) {
2070 return emitIncDecProp(env
, incDec
, extractBaseIfObj(env
), key
);
2072 if (mcodeIsElem(mk
.mcode
)) {
2073 return gen(env
, IncDecElem
, IncDecData
{incDec
}, ldMBase(env
), key
);
2075 PUNT(IncDecNewElem
);
2078 mFinalImpl(env
, nDiscard
, result
);
2082 * If the op and operand types are a supported combination, return the modified
2083 * value. Otherwise, return nullptr.
2085 * If the resulting value is a refcounted type, it will have one unconsumed
2088 SSATmp
* inlineSetOp(IRGS
& env
, SetOpOp op
, SSATmp
* lhs
, SSATmp
* rhs
) {
2089 auto const maybeOp
= [&]() -> folly::Optional
<Op
> {
2091 case SetOpOp::PlusEqual
: return Op::Add
;
2092 case SetOpOp::MinusEqual
: return Op::Sub
;
2093 case SetOpOp::MulEqual
: return Op::Mul
;
2094 case SetOpOp::PlusEqualO
: return folly::none
;
2095 case SetOpOp::MinusEqualO
: return folly::none
;
2096 case SetOpOp::MulEqualO
: return folly::none
;
2097 case SetOpOp::DivEqual
: return folly::none
;
2098 case SetOpOp::ConcatEqual
: return folly::none
;
2099 case SetOpOp::ModEqual
: return folly::none
;
2100 case SetOpOp::PowEqual
: return folly::none
;
2101 case SetOpOp::AndEqual
: return Op::BitAnd
;
2102 case SetOpOp::OrEqual
: return Op::BitOr
;
2103 case SetOpOp::XorEqual
: return Op::BitXor
;
2104 case SetOpOp::SlEqual
: return folly::none
;
2105 case SetOpOp::SrEqual
: return folly::none
;
2110 if (!maybeOp
) return nullptr;
2112 auto const bcOp
= *maybeOp
;
2113 if (!areBinaryArithTypesSupported(bcOp
, lhs
->type(), rhs
->type())) {
2117 lhs
= promoteBool(env
, lhs
);
2118 rhs
= promoteBool(env
, rhs
);
2120 auto const hhirOp
= isBitOp(bcOp
) ? bitOp(bcOp
)
2121 : promoteBinaryDoubles(env
, bcOp
, lhs
, rhs
);
2122 return gen(env
, hhirOp
, lhs
, rhs
);
2125 SSATmp
* setOpPropImpl(IRGS
& env
, SetOpOp op
, SSATmp
* base
,
2126 SSATmp
* key
, SSATmp
* rhs
) {
2127 auto const propInfo
= getCurrentPropertyOffset(env
, base
, key
->type(), false);
2129 if (propInfo
.offset
!= -1 &&
2130 !propInfo
.immutable
&&
2131 !mightCallMagicPropMethod(MOpMode::Define
, propInfo
)) {
2133 emitPropSpecialized(env
, base
, key
, false, MOpMode::Define
, propInfo
);
2134 propPtr
= gen(env
, UnboxPtr
, propPtr
);
2136 auto const lhs
= gen(env
, LdMem
, propPtr
->type().deref(), propPtr
);
2137 if (auto const result
= inlineSetOp(env
, op
, lhs
, rhs
)) {
2138 gen(env
, StMem
, propPtr
, result
);
2139 gen(env
, DecRef
, DecRefData
{}, lhs
);
2140 gen(env
, IncRef
, result
);
2144 gen(env
, SetOpCell
, SetOpData
{op
}, propPtr
, rhs
);
2145 auto newVal
= gen(env
, LdMem
, propPtr
->type().deref(), propPtr
);
2146 gen(env
, IncRef
, newVal
);
2150 return gen(env
, SetOpProp
, SetOpData
{op
}, base
, key
, rhs
);
2153 void emitSetOpM(IRGS
& env
, uint32_t nDiscard
, SetOpOp op
, MemberKey mk
) {
2154 auto key
= memberKey(env
, mk
);
2155 auto rhs
= topC(env
);
2157 auto const result
= [&] {
2158 if (mcodeIsProp(mk
.mcode
)) {
2159 return setOpPropImpl(env
, op
, extractBaseIfObj(env
), key
, rhs
);
2161 if (mcodeIsElem(mk
.mcode
)) {
2162 return gen(env
, SetOpElem
, SetOpData
{op
}, ldMBase(env
), key
, rhs
);
2168 mFinalImpl(env
, nDiscard
, result
);
2171 void emitBindM(IRGS
& env
, uint32_t nDiscard
, MemberKey mk
) {
2172 auto key
= memberKey(env
, mk
);
2173 auto rhs
= topV(env
);
2175 if (mcodeIsProp(mk
.mcode
)) {
2176 gen(env
, BindProp
, extractBaseIfObj(env
), key
, rhs
);
2177 } else if (mcodeIsElem(mk
.mcode
)) {
2178 gen(env
, BindElem
, ldMBase(env
), key
, rhs
);
2180 gen(env
, BindNewElem
, ldMBase(env
), rhs
);
2184 mFinalImpl(env
, nDiscard
, rhs
);
2187 void emitUnsetM(IRGS
& env
, uint32_t nDiscard
, MemberKey mk
) {
2188 auto key
= memberKey(env
, mk
);
2190 if (mcodeIsProp(mk
.mcode
)) {
2191 gen(env
, UnsetProp
, extractBaseIfObj(env
), key
);
2193 assertx(mcodeIsElem(mk
.mcode
));
2194 gen(env
, UnsetElem
, ldMBase(env
), key
);
2197 mFinalImpl(env
, nDiscard
, nullptr);
2200 void emitSetWithRefLML(IRGS
& env
, int32_t keyLoc
, int32_t valLoc
) {
2201 setWithRefImpl(env
, keyLoc
, ldLoc(env
, valLoc
, nullptr, DataTypeGeneric
));
2202 mFinalImpl(env
, 0, nullptr);
2205 void emitSetWithRefRML(IRGS
& env
, int32_t keyLoc
) {
2206 setWithRefImpl(env
, keyLoc
, top(env
, BCSPRelOffset
{0}, DataTypeGeneric
));
2208 mFinalImpl(env
, 0, nullptr);
2211 //////////////////////////////////////////////////////////////////////
2213 void emitMemoGet(IRGS
& env
, uint32_t ndiscard
, LocalRange locals
) {
2214 assertx(curFunc(env
)->isMemoizeWrapper());
2215 assertx(!curFunc(env
)->isReturnRef());
2216 assertx(locals
.first
+ locals
.restCount
< curFunc(env
)->numLocals());
2218 auto const base
= ldMBase(env
);
2219 auto const ret
= gen(env
, MemoGet
, MemoData
{ locals
}, fp(env
), base
);
2221 // The returned type is always the return type of the wrapped function, plus
2222 // TUninit. HHBBC should always derive the same return type for the wrapper
2223 // (this function) and the wrapped function, so for simplicity just use our
2224 // own return type. Even without HHBBC inference, we know the return type will
2225 // be at least TInitCell (memoize invariant).
2227 (typeFromRAT(curFunc(env
)->repoReturnType(), curClass(env
)) & TInitCell
) |
2230 mFinalImpl(env
, ndiscard
, gen(env
, AssertType
, retTy
, ret
));
2233 void emitMemoSet(IRGS
& env
, uint32_t ndiscard
, LocalRange locals
) {
2234 assertx(curFunc(env
)->isMemoizeWrapper());
2235 assertx(locals
.first
+ locals
.restCount
< curFunc(env
)->numLocals());
2237 auto const value
= topC(env
, BCSPRelOffset
{0}, DataTypeGeneric
);
2238 gen(env
, MemoSet
, MemoData
{ locals
}, fp(env
), ldMBase(env
), value
);
2239 popC(env
, DataTypeGeneric
);
2240 mFinalImpl(env
, ndiscard
, value
);
2243 //////////////////////////////////////////////////////////////////////