Codemod asserts to assertxs in the runtime
[hiphop-php.git] / hphp / runtime / vm / jit / irgen-minstr.cpp
blob385ef41d2bce549dae74fd5ca787e0830c021fb8
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
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>
49 #include <sstream>
51 namespace HPHP { namespace jit { namespace irgen {
53 namespace {
55 //////////////////////////////////////////////////////////////////////
57 const StaticString s_ArrayKindProfile("ArrayKindProfile");
59 //////////////////////////////////////////////////////////////////////
61 bool wantPropSpecializedWarnings() {
62 return !RuntimeOption::RepoAuthoritative ||
63 !RuntimeOption::EvalDisableSomeRepoAuthNotices;
66 //////////////////////////////////////////////////////////////////////
68 enum class SimpleOp {
69 None,
70 Array,
71 ProfiledPackedArray,
72 PackedArray,
73 VecArray,
74 Dict,
75 Keyset,
76 String,
77 Vector, // c_Vector* or c_ImmVector*
78 Map, // c_Map*
79 Pair, // c_Pair*
82 //////////////////////////////////////////////////////////////////////
83 // Property information.
85 struct PropInfo {
86 PropInfo() = default;
87 explicit PropInfo(int offset,
88 bool immutable,
89 RepoAuthType repoAuthType,
90 const Class* objClass,
91 const Class* propClass)
92 : offset{offset}
93 , immutable{immutable}
94 , repoAuthType{repoAuthType}
95 , objClass{objClass}
96 , propClass{propClass}
99 int offset{-1};
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
129 baseClass = ctx;
130 } else {
131 // baseClass can change on us in between requests and it is
132 // not related to ctx, so bail out
133 return PropInfo();
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,
143 // bail out
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
148 // offset.
149 return PropInfo(
150 baseClass->declPropOffset(idx),
151 bool(baseClass->declProperties()[idx].attrs & AttrIsImmutable),
152 baseClass->declPropRepoAuthType(idx),
153 baseClass,
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) {
179 isDeclared = true;
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)) {
189 return false;
192 // Native prop handlers never kick in for declared properties, even if
193 // they're unset.
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));
200 return true;
203 //////////////////////////////////////////////////////////////////////
205 folly::Optional<TypeConstraint> simpleOpConstraint(SimpleOp op) {
206 switch (op) {
207 case SimpleOp::None:
208 return folly::none;
210 case SimpleOp::Array:
211 case SimpleOp::ProfiledPackedArray:
212 case SimpleOp::VecArray:
213 case SimpleOp::Dict:
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());
224 case SimpleOp::Map:
225 return TypeConstraint(c_Map::classof());
227 case SimpleOp::Pair:
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))
261 : tvRefPtr(env);
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)) {
282 return false;
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
287 // and set.
288 auto const relevant_attrs =
289 // Magic getters can be invoked both in define contexts and non-define
290 // contexts.
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 //////////////////////////////////////////////////////////////////////
311 // Intermediate ops
313 PropInfo getCurrentPropertyOffset(IRGS& env, SSATmp* base, Type keyType,
314 bool constrain) {
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())) {
327 if (!constrain) {
328 // We can't use this specialized class without making a guard more
329 // expensive, so don't do it.
330 return PropInfo{};
332 specializeObjBase(env, base);
335 return info;
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,
347 SSATmp* baseAsObj,
348 SSATmp* propAddr,
349 SSATmp* key,
350 bool doWarn,
351 bool doDefine) {
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;
360 return cond(
361 env,
362 [&] (Block* taken) {
363 gen(env, CheckInitMem, taken, propAddr);
365 [&] { // Next: Property isn't Uninit. Do nothing.
366 return propAddr;
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(
380 IRGS& env,
381 SSATmp* base,
382 SSATmp* key,
383 bool nullsafe,
384 MOpMode mode,
385 PropInfo propInfo
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(
402 env,
403 LdPropAddr,
404 ByteOffsetData { propInfo.offset },
405 typeFromRAT(propInfo.repoAuthType, curClass(env)).ptr(Ptr::Prop),
406 base
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).
421 return cond(
422 env,
423 [&] (Block* taken) {
424 gen(env, CheckTypeMem, TObj, taken, base);
426 [&] {
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(
430 env,
431 LdPropAddr,
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));
445 if (doDefine) {
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
461 * here.
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...
466 always_assert_flog(
467 false,
468 "Static analysis error: we would've needed to generate "
469 "stdClass-promotion code in the JIT, which is unexpected."
472 return initNull;
477 //////////////////////////////////////////////////////////////////////
478 // "Simple op" handlers.
480 void checkCollectionBounds(IRGS& env, SSATmp* base,
481 SSATmp* idx, SSATmp* limit) {
482 assertx(base->isA(TObj));
484 ifThen(
485 env,
486 [&](Block* taken) {
487 auto ok = gen(env, CheckRange, idx, limit);
488 gen(env, JmpZero, taken, ok);
490 [&] {
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));
500 ifThen(
501 env,
502 [&](Block* taken) {
503 gen(env, CheckPackedArrayDataBounds, taken, base, idx);
505 [&] {
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,
514 Finish finish) {
515 assertx(base->isA(TArr) &&
516 base->type().arrSpec().kind() == ArrayData::kPackedKind &&
517 key->isA(TInt));
519 auto finishMe = [&](SSATmp* elem) {
520 auto unboxed = unbox(env, elem, nullptr);
521 gen(env, IncRef, unboxed);
522 return unboxed;
525 return cond(
526 env,
527 [&] (Block* taken) {
528 gen(env, CheckPackedArrayDataBounds, taken, base, key);
530 [&] { // Next:
531 auto const res = gen(env, LdPackedElem, base, key);
532 auto const pres = profiledType(env, res, [&] { finish(finishMe(res)); });
533 return finishMe(pres);
535 [&] { // Taken:
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,
549 Finish finish) {
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);
561 return 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,
571 Finish finish) {
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(
581 env,
582 [&] (Block* taken) {
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);
591 return 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);
610 return elem;
613 auto const elem = profiledArrayAccess(
614 env, base, key,
615 [&] (SSATmp* base, SSATmp* key, uint32_t pos) {
616 return gen(env, is_dict ? DictGetK : KeysetGetK, IndexData { pos },
617 base, key);
619 [&] (SSATmp* key) {
620 return gen(
621 env,
622 is_dict
623 ? (quiet ? DictGetQuiet : DictGet)
624 : (quiet ? KeysetGetQuiet : KeysetGet),
625 base,
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,
636 Finish finish) {
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);
641 [&] (SSATmp* 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);
648 return 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(
677 base,
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);
697 return 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));
716 }();
718 auto const pairBase = gen(env, LdPairBase, base);
719 auto const result = gen(env, LdElem, pairBase, idx);
720 gen(env, IncRef, result);
721 return 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);
734 return cond(
735 env,
736 [&] (Block* taken) {
737 gen(env, CheckPackedArrayDataBounds, taken, base, key);
739 [&] { // Next:
740 auto const packedElem = gen(env, LdPackedElem, base, key);
741 return gen(env, IsNType, TNull, packedElem);
743 [&] { // Taken:
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);
758 return cond(
759 env,
760 [&] (Block* taken) {
761 gen(env, CheckPackedArrayDataBounds, taken, base, key);
763 [&] {
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);
798 return cond(
799 env,
800 [&] (Block* taken) {
801 gen(env, CheckPackedArrayDataBounds, taken, base, key);
803 [&] {
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);
834 ifThen(
835 env,
836 [&] (Block* taken) {
837 gen(env, VectorHasImmCopy, taken, base);
839 [&] {
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);
852 decRef(env, oldVal);
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);
886 switch (simpleOp) {
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);
895 case SimpleOp::Dict:
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);
905 case SimpleOp::Pair:
906 assertx(mode != MOpMode::InOut);
907 return emitPairGet(env, base, key);
908 case SimpleOp::Map:
909 assertx(mode != MOpMode::InOut);
910 return gen(env, MapGet, base, key);
911 case SimpleOp::None:
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);
921 switch (simpleOp) {
922 case SimpleOp::VecArray:
923 return emitVecArrayQuietGet(env, base, key, finish);
924 case SimpleOp::Dict:
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:
936 case SimpleOp::Pair:
937 case SimpleOp::Map:
938 assertx(mode != MOpMode::InOut);
939 case SimpleOp::None:
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) {
946 switch (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);
954 case SimpleOp::Dict:
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);
962 case SimpleOp::Pair:
963 return gen(env, PairIsset, base, key);
964 case SimpleOp::Map:
965 return gen(env, MapIsset, base, key);
966 case SimpleOp::None:
967 return gen(env, IssetElem, base, key);
970 always_assert(false);
973 SSATmp* emitEmptyElem(IRGS& env, SSATmp* base,
974 SSATmp* key, SimpleOp simpleOp) {
975 switch (simpleOp) {
976 case SimpleOp::VecArray:
977 return emitVecArrayEmptyElem(env, base, key);
978 case SimpleOp::Dict:
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:
987 case SimpleOp::Pair:
988 case SimpleOp::Map:
989 case SimpleOp::None:
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
1003 * types.
1005 SimpleOp simpleCollectionOp(Type baseType, Type keyType, bool readInst,
1006 bool inOut) {
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
1077 * tvRef2.
1079 SSATmp* ratchetRefs(IRGS& env, SSATmp* base) {
1080 if (!env.irb->fs().needRatchet()) {
1081 return base;
1084 auto tvRef = tvRefPtr(env);
1086 return cond(
1087 env,
1088 [&] (Block* taken) {
1089 gen(env, CheckTypeMem, taken, TUninit, tvRef);
1091 [&] { // Next: tvRef is Uninit. Do nothing.
1092 return base;
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);
1103 // Reset tvRef.
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))
1147 : folly::none;
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
1165 ? 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);
1176 return inner;
1179 return base;
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()
1202 : baseType;
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 ///////////////////////////////////////////////////////////////////////////////
1216 const StaticString
1217 s_NULLSAFE_PROP_WRITE_ERROR(Strings::NULLSAFE_PROP_WRITE_ERROR);
1219 SSATmp* propGenericImpl(IRGS& env, MOpMode mode, SSATmp* base, SSATmp* key,
1220 bool nullsafe) {
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);
1230 return nullsafe
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)) {
1239 constrainBase(env);
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);
1270 if (define) {
1271 return key->isA(TInt)
1272 ? gen(env, ElemVecD, baseType, ldMBase(env), key)
1273 : invalid_key();
1276 if (unset) {
1277 return key->isA(TInt) ? gen(env, ElemVecU, baseType, ldMBase(env), key) :
1278 key->isA(TStr) ? ptrToInitNull(env)
1279 /* invalid */ : invalid_key();
1282 if (warn) {
1283 if (key->isA(TInt)) {
1284 auto const base = extractBase(env);
1285 checkVecBounds(env, base, key);
1286 auto const elemType = vecElemType(
1287 base->type(),
1288 key->type(),
1289 curClass(env)
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);
1298 return cond(
1299 env,
1300 [&] (Block* taken) {
1301 gen(env, CheckPackedArrayDataBounds, taken, base, key);
1303 [&] {
1304 auto const elemType = vecElemType(
1305 base->type(),
1306 key->type(),
1307 curClass(env)
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(
1334 env, base, key,
1335 [&] (SSATmp* dict, SSATmp* key, uint32_t pos) {
1336 return gen(env, ElemDictK, IndexData { pos }, dict, key);
1338 [&] (SSATmp* key) {
1339 if (define || unset) {
1340 return gen(env, unset ? ElemDictU : ElemDictD,
1341 baseType, ldMBase(env), key);
1343 assertx(
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);
1367 if (define) {
1368 gen(
1369 env,
1370 ThrowInvalidOperation,
1371 cns(env, s_InvalidKeysetOperationMsg.get())
1373 return cns(env, TBottom);
1376 return profiledArrayAccess(
1377 env, base, key,
1378 [&] (SSATmp* keyset, SSATmp* key, uint32_t pos) {
1379 return gen(env, ElemKeysetK, IndexData { pos }, keyset, key);
1381 [&] (SSATmp* key) {
1382 if (unset) return gen(env, ElemKeysetU, baseType, ldMBase(env), key);
1383 assertx(
1384 mode == MOpMode::Warn ||
1385 mode == MOpMode::None ||
1386 mode == MOpMode::InOut
1388 return gen(env, ElemKeysetX, MOpModeData { mode }, base, key);
1390 unset // cow check
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);
1417 [&] (SSATmp* key) {
1418 if (define || unset) {
1419 return gen(env, unset ? ElemArrayU : ElemArrayD,
1420 base->type(), ldMBase(env), key);
1422 assertx(
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
1433 if (unset) {
1434 constrainBase(env);
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);
1455 cleanTvRefs(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)) {
1468 auto propAddr =
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);
1475 return 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);
1491 ifThen(
1492 env,
1493 [&] (Block* taken) {
1494 gen(env, UnwindCheckSideExit, taken, fp(env), sp(env));
1496 [&] {
1497 hint(env, Block::Hint::Unused);
1498 gen(env, EndCatch,
1499 IRSPRelOffsetData { spOffBCFromIRSP(env) },
1500 fp(env), sp(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);
1516 cleanTvRefs(env);
1517 if (!isSetWithRef) {
1518 auto const val = gen(env, LdUnwinderValue, TCell);
1519 push(env, val);
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)));
1527 return block;
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);
1554 } else {
1555 gen(env, SetProp, makeCatchSet(env), base, key, value);
1558 return 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.
1565 ifThen(
1566 env,
1567 [&] (Block* taken) {
1568 gen(env, CheckNullptr, taken, strTestResult);
1570 [&] {
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);
1578 cleanTvRefs(env);
1579 push(env, str);
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);
1605 if (isKeyset) {
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()) {
1616 case LdLocAddr: {
1617 auto const locID = ptrInst->extra<LocalId>()->locId;
1618 return folly::make_optional<Location>(Location::Local { locID });
1620 case LdStkAddr: {
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 });
1625 default:
1626 return folly::none;
1628 }();
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;
1636 if (setRef) {
1637 auto const box = [&] {
1638 switch (baseLoc->tag()) {
1639 case LTag::Local:
1640 return ldLoc(env, baseLoc->localId(), nullptr, DataTypeSpecific);
1641 case LTag::Stack:
1642 return top(env, offsetFromBCSP(env, baseLoc->stackIdx()));
1643 case LTag::MBase:
1644 case LTag::CSlot:
1645 always_assert(false);
1647 not_reached();
1648 }();
1649 gen(env,
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.
1655 return value;
1658 auto const newArr = gen(env,
1659 isVec ? VecSet : isDict ? DictSet : ArraySet,
1660 base, key, value);
1662 // Update the base's location with the new array.
1663 switch (baseLoc->tag()) {
1664 case LTag::Local:
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);
1668 break;
1669 case LTag::Stack:
1670 gen(env, StStk,
1671 IRSPRelOffsetData { offsetFromIRSP(env, baseLoc->stackIdx()) },
1672 sp(env), newArr);
1673 break;
1674 case LTag::MBase:
1675 case LTag::CSlot:
1676 always_assert(false);
1678 return value;
1681 void setNewElemPackedArrayDataImpl(IRGS& env, SSATmp* basePtr, Type baseType,
1682 SSATmp* value) {
1683 ifThen(
1684 env,
1685 [&](Block* taken) {
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(
1696 env,
1697 LdPackedArrayDataElemAddr,
1698 TPtrToElemUninit,
1699 base,
1700 offset
1702 gen(env, IncRef, value);
1703 gen(env, StMem, elemPtr, value);
1705 [&] {
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);
1710 } else {
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) {
1732 constrainBase(env);
1733 gen(env, SetNewElemArray, makeCatchSet(env), basePtr, value);
1734 } else if (baseType <= TKeyset) {
1735 constrainBase(env);
1736 if (!value->isA(TInt | TStr)) {
1737 auto const base = extractBase(env);
1738 gen(env, ThrowInvalidArrayKey, makeCatchSet(env), base, value);
1739 } else {
1740 gen(env, SetNewElemKeyset, makeCatchSet(env), basePtr, value);
1742 } else {
1743 gen(env, SetNewElem, makeCatchSet(env), basePtr, value);
1745 return 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);
1758 switch (simpleOp) {
1759 case SimpleOp::PackedArray:
1760 case SimpleOp::String:
1761 always_assert(false && "Bad SimpleOp in setElemImpl");
1762 break;
1764 case SimpleOp::Vector:
1765 emitVectorSet(env, extractBase(env), key, value);
1766 break;
1768 case SimpleOp::Map:
1769 gen(env, MapSet, extractBase(env), key, value);
1770 break;
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)) {
1778 return result;
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);
1796 decRef(env, value);
1797 value = result;
1798 } else {
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);
1804 break;
1807 return value;
1810 SSATmp* memberKey(IRGS& env, MemberKey mk) {
1811 switch (mk.mcode) {
1812 case MW:
1813 return nullptr;
1814 case MEL: case MPL:
1815 return ldLocInnerWarn(env, mk.iva, makeExit(env),
1816 makePseudoMainExit(env), DataTypeSpecific);
1817 case MEC: case MPC:
1818 return topC(env, BCSPRelOffset{int32_t(mk.iva)});
1819 case MEI:
1820 return cns(env, mk.int64);
1821 case MET: case MPT: case MQT:
1822 return cns(env, mk.litstr);
1824 not_reached();
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) {
1855 initTvRefs(env);
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) {
1861 initTvRefs(env);
1862 auto name = ldLocInner(env, locId, makeExit(env), makePseudoMainExit(env),
1863 DataTypeSpecific);
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) {
1876 initTvRefs(env);
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) {
1882 initTvRefs(env);
1883 auto name = ldLocInner(env, locId, makeExit(env), makePseudoMainExit(env),
1884 DataTypeSpecific);
1885 baseSImpl(env, name, slot);
1888 void emitBaseL(IRGS& env, int32_t locId, MOpMode mode) {
1889 initTvRefs(env);
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) {
1908 initTvRefs(env);
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);
1925 initTvRefs(env);
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);
1946 PUNT(DimNewElem);
1947 }();
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* {
1982 switch (query) {
1983 case QueryMOp::InOut:
1984 case QueryMOp::CGet: {
1985 auto const mode = getQueryMOpMode(query);
1986 always_assert_flog(
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)
2000 ? cGetPropImpl(
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);
2019 not_reached();
2020 }();
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);
2038 PUNT(VGetNewElem);
2039 }();
2041 mFinalImpl(env, nDiscard, result);
2044 void emitFPassM(IRGS& env, uint32_t arg, uint32_t nDiscard, MemberKey mk,
2045 FPassHint hint) {
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);
2056 auto const result =
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);
2076 }();
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
2086 * reference.
2088 SSATmp* inlineSetOp(IRGS& env, SetOpOp op, SSATmp* lhs, SSATmp* rhs) {
2089 auto const maybeOp = [&]() -> folly::Optional<Op> {
2090 switch (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;
2107 not_reached();
2108 }();
2110 if (!maybeOp) return nullptr;
2112 auto const bcOp = *maybeOp;
2113 if (!areBinaryArithTypesSupported(bcOp, lhs->type(), rhs->type())) {
2114 return nullptr;
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)) {
2132 auto propPtr =
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);
2141 return result;
2144 gen(env, SetOpCell, SetOpData{op}, propPtr, rhs);
2145 auto newVal = gen(env, LdMem, propPtr->type().deref(), propPtr);
2146 gen(env, IncRef, newVal);
2147 return 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);
2164 PUNT(SetOpNewElem);
2165 }();
2167 popDecRef(env);
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);
2179 } else {
2180 gen(env, BindNewElem, ldMBase(env), rhs);
2183 popV(env);
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);
2192 } else {
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));
2207 popDecRef(env);
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).
2226 auto const retTy =
2227 (typeFromRAT(curFunc(env)->repoReturnType(), curClass(env)) & TInitCell) |
2228 TUninit;
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 //////////////////////////////////////////////////////////////////////