Remove JIT::RuntimeType
[hiphop-php.git] / hphp / runtime / vm / jit / minstr-translator.cpp
blob650b97a368ad211126f108823655da876fc4bd7b
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-2014 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 +----------------------------------------------------------------------+
18 #include "hphp/runtime/base/mixed-array-defs.h"
19 #include <algorithm>
20 #include <vector>
21 #include "hphp/runtime/base/strings.h"
22 #include "hphp/runtime/vm/member-operations.h"
23 #include "hphp/runtime/vm/jit/frame-state.h"
24 #include "hphp/runtime/vm/jit/hhbc-translator.h"
25 #include "hphp/runtime/vm/jit/ir-instruction.h"
26 #include "hphp/runtime/vm/jit/ir.h"
27 #include "hphp/runtime/vm/jit/normalized-instruction.h"
28 #include "hphp/runtime/vm/jit/mc-generator.h"
29 #include "hphp/runtime/vm/repo.h"
30 #include "hphp/runtime/vm/repo-global-data.h"
31 #include "hphp/runtime/vm/jit/target-profile.h"
33 // These files do ugly things with macros so include them last
34 #include "hphp/util/assert-throw.h"
35 #include "hphp/runtime/vm/jit/minstr-translator-internal.h"
37 namespace HPHP { namespace JIT {
39 TRACE_SET_MOD(hhir);
41 using HPHP::JIT::Location;
43 static bool wantPropSpecializedWarnings() {
44 return !RuntimeOption::RepoAuthoritative ||
45 !RuntimeOption::EvalDisableSomeRepoAuthNotices;
48 bool MInstrEffects::supported(Opcode op) {
49 return opcodeHasFlags(op, MInstrProp | MInstrElem);
51 bool MInstrEffects::supported(const IRInstruction* inst) {
52 return supported(inst->op());
55 void MInstrEffects::get(const IRInstruction* inst, LocalStateHook& hook) {
56 // If the base for this instruction is a local address, the helper call might
57 // have side effects on the local's value
58 SSATmp* base = inst->src(minstrBaseIdx(inst));
59 IRInstruction* locInstr = base->inst();
60 if (locInstr->op() == LdLocAddr) {
61 UNUSED Type baseType = locInstr->dst()->type();
62 assert(baseType.equals(base->type()));
63 assert(baseType.isPtr() || baseType.isKnownDataType());
64 int loc = locInstr->extra<LdLocAddr>()->locId;
66 MInstrEffects effects(inst);
67 if (effects.baseTypeChanged || effects.baseValChanged) {
68 hook.setLocalType(loc, effects.baseType.derefIfPtr());
73 MInstrEffects::MInstrEffects(const IRInstruction* inst) {
74 init(inst->op(), inst->src(minstrBaseIdx(inst))->type());
77 MInstrEffects::MInstrEffects(Opcode op, Type base) {
78 init(op, base);
81 MInstrEffects::MInstrEffects(Opcode op, SSATmp* base) {
82 assert(base != nullptr);
83 init(op, base->type());
86 MInstrEffects::MInstrEffects(Opcode opc, const std::vector<SSATmp*>& srcs) {
87 init(opc, srcs[minstrBaseIdx(opc)]->type());
90 namespace {
91 Opcode canonicalOp(Opcode op) {
92 if (op == ElemUX || op == ElemUXStk ||
93 op == UnsetElem || op == UnsetElemStk) {
94 return UnsetElem;
96 if (op == SetWithRefElem ||
97 op == SetWithRefElemStk ||
98 op == SetWithRefNewElem ||
99 op == SetWithRefNewElemStk) {
100 return SetWithRefElem;
102 return opcodeHasFlags(op, MInstrProp) ? SetProp
103 : opcodeHasFlags(op, MInstrElem) || op == ArraySet ? SetElem
104 : bad_value<Opcode>();
107 void getBaseType(Opcode rawOp, bool predict,
108 Type& baseType, bool& baseValChanged) {
109 always_assert(baseType.notBoxed());
110 auto const op = canonicalOp(rawOp);
112 // Deal with possible promotion to stdClass or array
113 if ((op == SetElem || op == SetProp || op == SetWithRefElem) &&
114 baseType.maybe(Type::Null | Type::Bool | Type::Str)) {
115 auto newBase = op == SetProp ? Type::Obj : Type::Arr;
117 if (predict) {
118 /* If the output type will be used as a prediction and not as fact, we
119 * can be optimistic here. Assume no promotion for string bases and
120 * promotion in other cases. */
121 baseType = baseType <= Type::Str ? Type::Str : newBase;
122 } else if (baseType <= Type::Str &&
123 (rawOp == SetElem || rawOp == SetElemStk)) {
124 /* If the base is known to be a string and the operation is exactly
125 * SetElem, we're guaranteed that either the base will end as a
126 * CountedStr or the instruction will throw an exception and side
127 * exit. */
128 baseType = Type::CountedStr;
129 } else if (baseType <= Type::Str &&
130 (rawOp == SetNewElem || rawOp == SetNewElemStk)) {
131 /* If the string base is empty, it will be promoted to an
132 * array. Otherwise the base will be left alone and we'll fatal. */
133 baseType = Type::Arr;
134 } else {
135 /* Regardless of whether or not promotion happens, we know the base
136 * cannot be Null after the operation. If the base was a subtype of Null
137 * this will give newBase. */
138 baseType = (baseType - Type::Null) | newBase;
141 baseValChanged = true;
144 if ((op == SetElem || op == UnsetElem || op == SetWithRefElem) &&
145 baseType.maybe(Type::Arr | Type::Str)) {
146 /* Modifying an array or string element, even when COW doesn't kick in,
147 * produces a new SSATmp for the base. StaticArr/StaticStr may be promoted
148 * to CountedArr/CountedStr. */
149 baseValChanged = true;
150 if (baseType.maybe(Type::StaticArr)) baseType |= Type::CountedArr;
151 if (baseType.maybe(Type::StaticStr)) baseType |= Type::CountedStr;
156 void MInstrEffects::init(const Opcode rawOp, const Type origBase) {
157 baseType = origBase;
158 always_assert(baseType.isPtr() ^ baseType.notPtr());
159 auto const basePtr = baseType.isPtr();
160 baseType = baseType.derefIfPtr();
162 // Only certain types of bases are supported now but this list may expand in
163 // the future.
164 assert_not_implemented(basePtr ||
165 baseType.subtypeOfAny(Type::Obj, Type::Arr));
167 baseTypeChanged = baseValChanged = false;
169 // Process the inner and outer types separately and then recombine them,
170 // since the minstr operations all operate on the inner cell of boxed
171 // bases. We treat the new inner type as a prediction because it will be
172 // verified the next time we load from the box.
173 auto inner = (baseType & Type::BoxedCell).innerType();
174 auto outer = baseType & Type::Cell;
175 getBaseType(rawOp, false, outer, baseValChanged);
176 getBaseType(rawOp, true, inner, baseValChanged);
178 baseType = inner.box() | outer;
179 baseType = basePtr ? baseType.ptr() : baseType;
181 baseTypeChanged = baseType != origBase;
183 /* Boxed bases may have their inner value changed but the value of the box
184 * will never change. */
185 baseValChanged = !origBase.isBoxed() && (baseValChanged || baseTypeChanged);
188 // minstrBaseIdx returns the src index for inst's base operand.
189 int minstrBaseIdx(Opcode opc) {
190 return opc == SetNewElem || opc == SetNewElemStk ? 0
191 : opc == SetNewElemArray || opc == SetNewElemArrayStk ? 0
192 : opc == BindNewElem || opc == BindNewElemStk ? 0
193 : opc == ArraySet ? 1
194 : opc == SetOpElem || opc == SetOpElemStk ? 0
195 : opc == IncDecElem || opc == IncDecElemStk ? 0
196 : opcodeHasFlags(opc, MInstrProp) ? 2
197 : opcodeHasFlags(opc, MInstrElem) ? 1
198 : bad_value<int>();
200 int minstrBaseIdx(const IRInstruction* inst) {
201 return minstrBaseIdx(inst->op());
204 HhbcTranslator::MInstrTranslator::MInstrTranslator(
205 const NormalizedInstruction& ni,
206 HhbcTranslator& ht)
207 : m_ni(ni)
208 , m_ht(ht)
209 , m_irb(*m_ht.m_irb)
210 , m_unit(m_ht.m_unit)
211 , m_mii(getMInstrInfo(ni.mInstrOp()))
212 , m_marker(ht.makeMarker(ht.bcOff()))
213 , m_iInd(m_mii.valCount())
214 , m_needMIS(true)
215 , m_misBase(nullptr)
216 , m_base(nullptr)
217 , m_result(nullptr)
218 , m_strTestResult(nullptr)
219 , m_failedSetBlock(nullptr)
223 template<typename... Srcs>
224 SSATmp* HhbcTranslator::MInstrTranslator::genStk(Opcode opc, Block* taken,
225 Srcs... srcs) {
226 assert(opcodeHasFlags(opc, HasStackVersion));
227 assert(!opcodeHasFlags(opc, ModifiesStack));
229 // We're going to make decisions based on the type of the base.
230 constrainBase(DataTypeSpecific);
232 std::vector<SSATmp*> srcVec({srcs...});
233 SSATmp* base = srcVec[minstrBaseIdx(opc)];
235 /* If the base is a pointer to a stack cell and the operation might change
236 * its type and/or value, use the version of the opcode that returns a new
237 * StkPtr. */
238 if (base->inst()->op() == LdStackAddr) {
239 MInstrEffects effects(opc, srcVec);
240 if (effects.baseTypeChanged || effects.baseValChanged) {
241 opc = getStackModifyingOpcode(opc);
245 return gen(opc, taken, srcs...);
248 void HhbcTranslator::MInstrTranslator::emit() {
249 // Assign stack slots to our stack inputs
250 numberStackInputs();
251 // Emit the base and every intermediate op
252 emitMPre();
253 // Emit the final operation
254 emitFinalMOp();
255 // Cleanup: decref inputs and scratch values
256 emitMPost();
259 // Returns a pointer to the base of the current MInstrState struct, or
260 // a null pointer if it's not needed.
261 SSATmp* HhbcTranslator::MInstrTranslator::genMisPtr() {
262 if (m_needMIS) {
263 return gen(LdMIStateAddr, m_misBase, cns(kReservedRSPSpillSpace));
264 } else {
265 return cns(Type::cns(nullptr, Type::PtrToUninit));
270 namespace {
271 bool mightCallMagicPropMethod(MInstrAttr mia, const Class* cls,
272 PropInfo propInfo) {
273 auto const objAttr = (mia & Define) ? ObjectData::UseSet : ObjectData::UseGet;
274 auto const clsHasMagicMethod = !cls || (cls->getODAttrs() & objAttr);
276 return clsHasMagicMethod &&
277 convertToType(propInfo.repoAuthType).maybe(Type::Uninit);
280 bool
281 mInstrHasUnknownOffsets(const NormalizedInstruction& ni, Class* context) {
282 const MInstrInfo& mii = getMInstrInfo(ni.mInstrOp());
283 unsigned mi = 0;
284 unsigned ii = mii.valCount() + 1;
285 for (; mi < ni.immVecM.size(); ++mi) {
286 MemberCode mc = ni.immVecM[mi];
287 if (mcodeIsProp(mc)) {
288 const Class* cls = nullptr;
289 auto propInfo = getPropertyOffset(ni, context, cls, mii, mi, ii);
290 if (propInfo.offset == -1 ||
291 mightCallMagicPropMethod(mii.getAttr(mc), cls, propInfo)) {
292 return true;
294 ++ii;
295 } else {
296 return true;
300 return false;
304 // Inspect the instruction we're about to translate and determine if
305 // it can be executed without using an MInstrState struct.
306 void HhbcTranslator::MInstrTranslator::checkMIState() {
307 if (m_ni.immVec.locationCode() == LNL || m_ni.immVec.locationCode() == LNC) {
308 // We're definitely going to punt in emitBaseN, so we might not
309 // have guarded the base's type.
310 return;
313 // DataTypeGeneric is used here because if we can't prove anything useful
314 // about the operation, this function doesn't care what the type is.
315 auto baseVal = getBase(DataTypeGeneric);
316 Type baseType = baseVal->type();
317 const bool baseArr = baseType <= Type::Arr;
318 const bool isCGetM = m_ni.mInstrOp() == OpCGetM;
319 const bool isSetM = m_ni.mInstrOp() == OpSetM;
320 const bool isIssetM = m_ni.mInstrOp() == OpIssetM;
321 const bool isUnsetM = m_ni.mInstrOp() == OpUnsetM;
322 const bool isSingle = m_ni.immVecM.size() == 1;
323 const bool unknownOffsets = mInstrHasUnknownOffsets(m_ni, contextClass());
325 if (baseType.maybeBoxed() && !baseType.isBoxed()) {
326 // We don't need to bother with weird base types.
327 return;
329 baseType = baseType.unbox();
332 // CGetM or SetM with no unknown property offsets
333 const bool simpleProp = !unknownOffsets && (isCGetM || isSetM);
335 // SetM with only one vector element, for props and elems
336 const bool singleSet = isSingle && isSetM;
338 // Element access with one element in the vector
339 const bool singleElem = isSingle && mcodeIsElem(m_ni.immVecM[0]);
341 // IssetM with one vector array element and an Arr base
342 const bool simpleArrayIsset = isIssetM && singleElem && baseArr;
344 // IssetM with one vector array element and a collection type
345 const bool simpleCollectionIsset = isIssetM && singleElem &&
346 baseType < Type::Obj && isOptimizableCollectionClass(baseType.getClass());
348 // UnsetM on an array with one vector element
349 const bool simpleArrayUnset = isUnsetM && singleElem &&
350 baseType <= Type::Arr;
352 // CGetM on an array with a base that won't use MInstrState. Str
353 // will use tvScratch and Obj will fatal or use tvRef.
354 const bool simpleArrayGet = isCGetM && singleElem &&
355 baseType.not(Type::Str | Type::Obj);
356 const bool simpleCollectionGet = isCGetM && singleElem &&
357 baseType < Type::Obj && isOptimizableCollectionClass(baseType.getClass());
358 const bool simpleStringOp = (isCGetM || isIssetM) && isSingle &&
359 isSimpleBase() && mcodeMaybeArrayIntKey(m_ni.immVecM[0]) &&
360 baseType <= Type::Str;
362 if (simpleProp || singleSet ||
363 simpleArrayGet || simpleCollectionGet ||
364 simpleArrayUnset || simpleCollectionIsset ||
365 simpleArrayIsset || simpleStringOp) {
366 setNoMIState();
367 if (simpleCollectionGet || simpleCollectionIsset) {
368 constrainBase(TypeConstraint(baseType.getClass()), baseVal);
369 } else {
370 constrainBase(DataTypeSpecific, baseVal);
375 void HhbcTranslator::MInstrTranslator::emitMPre() {
376 checkMIState();
378 if (HPHP::Trace::moduleEnabled(HPHP::Trace::minstr, 1)) {
379 emitMTrace();
382 if (m_needMIS) {
383 m_misBase = gen(DefMIStateBase);
384 SSATmp* uninit = cns(Type::Uninit);
386 if (nLogicalRatchets() > 0) {
387 gen(StMem, m_misBase, cns(MISOFF(tvRef)), uninit);
388 gen(StMem, m_misBase, cns(MISOFF(tvRef2)), uninit);
392 // The base location is input 0 or 1, and the location code is stored
393 // separately from m_ni.immVecM, so input indices (iInd) and member indices
394 // (mInd) commonly differ. Additionally, W members have no corresponding
395 // inputs, so it is necessary to track the two indices separately.
396 emitBaseOp();
397 ++m_iInd;
399 // Iterate over all but the last member, which is consumed by a final
400 // operation.
401 for (m_mInd = 0; m_mInd < m_ni.immVecM.size() - 1; ++m_mInd) {
402 emitIntermediateOp();
403 emitRatchetRefs();
407 void HhbcTranslator::MInstrTranslator::emitMTrace() {
408 auto rttStr = [this](int i) {
409 return Type(m_ni.inputs[i]->rtt).unbox().toString();
411 std::ostringstream shape;
412 int iInd = m_mii.valCount();
413 const char* separator = "";
415 shape << opcodeToName(m_ni.mInstrOp()) << " <";
416 auto baseLoc = m_ni.immVec.locationCode();
417 shape << folly::format("{}:{} ", locationCodeString(baseLoc), rttStr(iInd));
418 ++iInd;
420 for (int mInd = 0; mInd < m_ni.immVecM.size(); ++mInd) {
421 auto mcode = m_ni.immVecM[mInd];
422 shape << separator;
423 if (mcode == MW) {
424 shape << "MW";
425 } else if (mcodeIsElem(mcode)) {
426 shape << "ME:" << rttStr(iInd);
427 } else if (mcodeIsProp(mcode)) {
428 shape << "MP:" << rttStr(iInd);
429 } else {
430 not_reached();
432 if (mcode != MW) ++iInd;
433 separator = " ";
435 shape << '>';
436 gen(IncStatGrouped,
437 cns(makeStaticString("vector instructions")),
438 cns(makeStaticString(shape.str())),
439 cns(1));
442 // Build a map from (stack) input index to stack index.
443 void HhbcTranslator::MInstrTranslator::numberStackInputs() {
444 // Stack inputs are pushed in the order they appear in the vector
445 // from left to right, so earlier elements in the vector are at
446 // higher offsets in the stack. m_mii.valCount() tells us how many
447 // rvals the instruction takes on the stack; they're pushed after
448 // any vector elements and we want to ignore them here.
449 bool stackRhs = m_mii.valCount() &&
450 m_ni.inputs[0]->location.space == Location::Stack;
451 int stackIdx = (int)stackRhs + m_ni.immVec.numStackValues() - 1;
452 for (unsigned i = m_mii.valCount(); i < m_ni.inputs.size(); ++i) {
453 const Location& l = m_ni.inputs[i]->location;
454 switch (l.space) {
455 case Location::Stack:
456 assert(stackIdx >= 0);
457 m_stackInputs[i] = stackIdx--;
458 break;
460 default:
461 break;
464 assert(stackIdx == (stackRhs ? 0 : -1));
466 if (stackRhs) {
467 // If this instruction does have an RHS, it will be input 0 at
468 // stack offset 0.
469 assert(m_mii.valCount() == 1);
470 m_stackInputs[0] = 0;
474 SSATmp* HhbcTranslator::MInstrTranslator::getBase(TypeConstraint tc) {
475 assert(m_iInd == m_mii.valCount());
476 return getInput(m_iInd, tc);
479 SSATmp* HhbcTranslator::MInstrTranslator::getKey() {
480 SSATmp* key = getInput(m_iInd, DataTypeSpecific);
481 auto keyType = key->type();
482 assert(keyType.isBoxed() || keyType.notBoxed());
483 if (keyType.isBoxed()) {
484 key = gen(LdRef, Type::Cell, key);
486 return key;
489 SSATmp* HhbcTranslator::MInstrTranslator::getValue() {
490 // If an instruction takes an rhs, it's always input 0.
491 assert(m_mii.valCount() == 1);
492 const int kValIdx = 0;
493 return getInput(kValIdx, DataTypeSpecific);
496 SSATmp* HhbcTranslator::MInstrTranslator::getValAddr() {
497 assert(m_mii.valCount() == 1);
498 const DynLocation& dl = *m_ni.inputs[0];
499 const Location& l = dl.location;
500 if (l.space == Location::Local) {
501 assert(!m_stackInputs.count(0));
502 return m_ht.ldLocAddr(l.offset, DataTypeSpecific);
503 } else {
504 assert(l.space == Location::Stack);
505 assert(m_stackInputs.count(0));
506 m_ht.spillStack();
507 return m_ht.ldStackAddr(m_stackInputs[0], DataTypeSpecific);
511 void HhbcTranslator::MInstrTranslator::constrainBase(TypeConstraint tc,
512 SSATmp* value) {
513 if (!value) value = m_base;
515 // Member operations only care about the inner type of the base if it's
516 // boxed, so this handles the logic of using the inner constraint when
517 // appropriate.
518 auto baseType = value->type().derefIfPtr();
520 if (baseType.maybeBoxed()) {
521 tc.innerCat = tc.category;
522 tc.category = DataTypeCountness;
524 m_irb.constrainValue(value, tc);
527 SSATmp* HhbcTranslator::MInstrTranslator::getInput(unsigned i,
528 TypeConstraint tc) {
529 const DynLocation& dl = *m_ni.inputs[i];
530 const Location& l = dl.location;
532 assert(!!m_stackInputs.count(i) == (l.space == Location::Stack));
533 switch (l.space) {
534 case Location::Stack:
535 return m_ht.top(Type::StackElem, m_stackInputs[i], tc);
537 case Location::Local:
538 // N.B. Exit block for LdGbl is nullptr because we always InterpOne
539 // member instructions in pseudomains
540 return m_ht.ldLoc(l.offset, nullptr, tc);
542 case Location::Litstr:
543 return cns(m_ht.lookupStringId(l.offset));
545 case Location::Litint:
546 return cns(l.offset);
548 case Location::This:
549 // If we don't have a current class context, this instruction will be
550 // unreachable.
551 if (!m_ht.curClass()) PUNT(Unreachable-LdThis);
553 return gen(LdThis, m_irb.fp());
555 default: not_reached();
559 void HhbcTranslator::MInstrTranslator::emitBaseLCR() {
560 const MInstrAttr& mia = m_mii.getAttr(m_ni.immVec.locationCode());
561 const DynLocation& base = *m_ni.inputs[m_iInd];
562 // We use DataTypeGeneric here because we might not care about the type. If
563 // we do, it's constrained further.
564 auto baseType = getBase(DataTypeGeneric)->type();
566 if (base.location.isLocal()) {
567 // Check for Uninit and warn/promote to InitNull as appropriate
568 if (baseType <= Type::Uninit) {
569 if (mia & MIA_warn) {
570 gen(RaiseUninitLoc, makeEmptyCatch(),
571 cns(m_ht.curFunc()->localVarName(base.location.offset)));
573 if (mia & MIA_define) {
574 // We care whether or not the local is Uninit, and
575 // CountnessInit will tell us that.
576 m_irb.constrainLocal(base.location.offset, DataTypeSpecific,
577 "emitBaseLCR: Uninit base local");
578 gen(
579 StLoc,
580 LocalId(base.location.offset),
581 m_irb.fp(),
582 cns(Type::InitNull)
584 baseType = Type::InitNull;
589 // If the base is a box with a type that's changed, we need to bail out of
590 // the tracelet and retranslate. Doing an exit here is a little sketchy since
591 // we may have already emitted instructions with memory effects to initialize
592 // the MInstrState. These particular stores are harmless though, and the
593 // worst outcome here is that we'll end up doing the stores twice, once for
594 // this instruction and once at the beginning of the retranslation.
595 Block* failedRef = baseType.isBoxed() ? m_ht.makeExit() : nullptr;
596 if ((baseType.subtypeOfAny(Type::Obj, Type::BoxedObj) &&
597 mcodeIsProp(m_ni.immVecM[0])) ||
598 simpleCollectionOp() != SimpleOp::None) {
599 // In these cases we can pass the base by value, after unboxing if needed.
600 m_base = m_ht.unbox(getBase(DataTypeSpecific), failedRef);
602 // Register that we care about the specific type of the base, and might
603 // care about its specialized type.
604 constrainBase(DataTypeSpecific);
605 constrainCollectionOpBase();
606 } else {
607 // Everything else is passed by pointer. We don't have to worry about
608 // unboxing here, since all the generic helpers understand boxed bases.
609 if (baseType.isBoxed()) {
610 SSATmp* box = getBase(DataTypeSpecific);
611 Type innerType = baseType.innerType();
612 assert(box->isA(Type::BoxedCell));
613 assert(innerType.isKnownDataType() ||
614 innerType == Type::Cell || innerType == Type::InitCell);
616 // Guard that the inner type hasn't changed. Even though we don't use the
617 // unboxed value, some emit* methods change their behavior based on the
618 // inner type.
619 auto inner = gen(LdRef, innerType, failedRef, box);
621 // TODO(t2598894): We do this for consistency with the old guard
622 // relaxation code, but may change it in the future.
623 m_irb.constrainValue(inner, DataTypeSpecific);
626 if (base.location.space == Location::Local) {
627 m_base = m_ht.ldLocAddr(base.location.offset, DataTypeSpecific);
628 } else {
629 assert(base.location.space == Location::Stack);
630 // Make sure the stack is clean before getting a pointer to one of its
631 // elements.
632 m_ht.spillStack();
633 assert(m_stackInputs.count(m_iInd));
634 m_base = m_ht.ldStackAddr(m_stackInputs[m_iInd], DataTypeSpecific);
636 assert(m_base->type().isPtr());
639 // TODO(t2598894): We do this for consistency with the old guard relaxation
640 // code, but may change it in the future.
641 constrainBase(DataTypeSpecific);
644 // Is the current instruction a 1-element simple collection (includes Array),
645 // operation?
646 HhbcTranslator::MInstrTranslator::SimpleOp
647 HhbcTranslator::MInstrTranslator::simpleCollectionOp() {
648 // DataTypeGeneric is used in here to avoid constraining the base in case we
649 // end up not caring about the type. Consumers of the return value must
650 // constrain the base as appropriate.
652 SSATmp* base = getInput(m_mii.valCount(), DataTypeGeneric);
653 auto baseType = base->type().unbox();
654 HPHP::Op op = m_ni.mInstrOp();
655 bool readInst = (op == OpCGetM || op == OpIssetM);
656 if ((op == OpSetM || readInst) &&
657 isSimpleBase() &&
658 isSingleMember()) {
660 if (baseType <= Type::Arr) {
661 auto const isPacked = (baseType.hasArrayKind() &&
662 baseType.getArrayKind() == ArrayData::kPackedKind);
663 if (mcodeIsElem(m_ni.immVecM[0])) {
664 SSATmp* key = getInput(m_mii.valCount() + 1, DataTypeGeneric);
665 if (key->isA(Type::Int) || key->isA(Type::Str)) {
666 if (readInst && key->isA(Type::Int)) {
667 return isPacked ? SimpleOp::PackedArray
668 : SimpleOp::ProfiledArray;
670 return SimpleOp::Array;
673 } else if (baseType <= Type::Str &&
674 mcodeMaybeArrayIntKey(m_ni.immVecM[0])) {
675 SSATmp* key = getInput(m_mii.valCount() + 1, DataTypeGeneric);
676 if (key->isA(Type::Int)) {
677 // Don't bother with SetM on strings, because profile data
678 // shows it basically never happens.
679 if (readInst) {
680 return SimpleOp::String;
683 } else if (baseType.strictSubtypeOf(Type::Obj)) {
684 const Class* klass = baseType.getClass();
685 auto const isVector = klass == c_Vector::classof();
686 auto const isPair = klass == c_Pair::classof();
687 auto const isMap = klass == c_Map::classof();
688 // TODO: Add Frozen collections
690 if (isVector || isPair) {
691 if (mcodeMaybeVectorKey(m_ni.immVecM[0])) {
692 SSATmp* key = getInput(m_mii.valCount() + 1, DataTypeGeneric);
693 if (key->isA(Type::Int)) {
694 // We don't specialize setting pair elements.
695 if (isPair && op == OpSetM) return SimpleOp::None;
697 return isVector ? SimpleOp::Vector : SimpleOp::Pair;
700 } else if (isMap) {
701 if (mcodeIsElem(m_ni.immVecM[0])) {
702 SSATmp* key = getInput(m_mii.valCount() + 1, DataTypeGeneric);
703 if (key->isA(Type::Int) || key->isA(Type::Str)) {
704 return SimpleOp::Map;
711 return SimpleOp::None;
714 void HhbcTranslator::MInstrTranslator::constrainCollectionOpBase() {
715 auto type = simpleCollectionOp();
716 switch (type) {
717 case SimpleOp::None:
718 return;
720 case SimpleOp::Array:
721 case SimpleOp::ProfiledArray:
722 case SimpleOp::String:
723 m_irb.constrainValue(m_base, DataTypeSpecific);
724 return;
726 case SimpleOp::PackedArray:
727 constrainBase(TypeConstraint(DataTypeSpecialized).setWantArrayKind());
728 return;
730 case SimpleOp::Vector:
731 case SimpleOp::Map:
732 case SimpleOp::Pair: {
733 SSATmp* base = getInput(m_mii.valCount(), DataTypeGeneric);
734 auto baseType = base->type().unbox();
735 constrainBase(TypeConstraint(baseType.getClass()));
736 return;
740 not_reached();
743 // "Simple" bases are stack cells and locals.
744 bool HhbcTranslator::MInstrTranslator::isSimpleBase() {
745 LocationCode loc = m_ni.immVec.locationCode();
746 return loc == LL || loc == LC || loc == LR;
749 bool HhbcTranslator::MInstrTranslator::isSingleMember() {
750 return m_ni.immVecM.size() == 1;
753 void HhbcTranslator::MInstrTranslator::emitBaseH() {
754 m_base = gen(LdThis, m_irb.fp());
757 void HhbcTranslator::MInstrTranslator::emitBaseN() {
758 // If this is ever implemented, the check at the beginning of
759 // checkMIState must be removed/adjusted as appropriate.
760 PUNT(emitBaseN);
763 template <bool warn, bool define>
764 static inline TypedValue* baseGImpl(TypedValue key,
765 MInstrState* mis) {
766 TypedValue* base;
767 StringData* name = prepareKey(key);
768 SCOPE_EXIT { decRefStr(name); };
769 VarEnv* varEnv = g_context->m_globalVarEnv;
770 assert(varEnv != NULL);
771 base = varEnv->lookup(name);
772 if (base == NULL) {
773 if (warn) {
774 raise_notice(Strings::UNDEFINED_VARIABLE, name->data());
776 if (define) {
777 TypedValue tv;
778 tvWriteNull(&tv);
779 varEnv->set(name, &tv);
780 base = varEnv->lookup(name);
781 } else {
782 return const_cast<TypedValue*>(init_null_variant.asTypedValue());
785 if (base->m_type == KindOfRef) {
786 base = base->m_data.pref->tv();
788 return base;
791 namespace MInstrHelpers {
792 TypedValue* baseG(TypedValue key, MInstrState* mis) {
793 return baseGImpl<false, false>(key, mis);
796 TypedValue* baseGW(TypedValue key, MInstrState* mis) {
797 return baseGImpl<true, false>(key, mis);
800 TypedValue* baseGD(TypedValue key, MInstrState* mis) {
801 return baseGImpl<false, true>(key, mis);
804 TypedValue* baseGWD(TypedValue key, MInstrState* mis) {
805 return baseGImpl<true, true>(key, mis);
809 void HhbcTranslator::MInstrTranslator::emitBaseG() {
810 const MInstrAttr& mia = m_mii.getAttr(m_ni.immVec.locationCode());
811 typedef TypedValue* (*OpFunc)(TypedValue, MInstrState*);
812 using namespace MInstrHelpers;
813 static const OpFunc opFuncs[] = {baseG, baseGW, baseGD, baseGWD};
814 OpFunc opFunc = opFuncs[mia & MIA_base];
815 SSATmp* gblName = getBase(DataTypeSpecific);
816 if (!gblName->isA(Type::Str)) PUNT(BaseG-non-string-name);
818 m_base = gen(BaseG,
819 makeEmptyCatch(),
820 cns(reinterpret_cast<TCA>(opFunc)),
821 gblName,
822 genMisPtr());
825 void HhbcTranslator::MInstrTranslator::emitBaseS() {
826 const int kClassIdx = m_ni.inputs.size() - 1;
827 SSATmp* key = getKey();
828 SSATmp* clsRef = getInput(kClassIdx, DataTypeGeneric /* will be a Cls */);
831 * Note, the base may be a pointer to a boxed type after this. We
832 * don't unbox here, because we never are going to generate a
833 * special translation unless we know it's not boxed, and the C++
834 * helpers for generic dims currently always conditionally unbox.
836 m_base = m_ht.ldClsPropAddr(makeCatch(), clsRef, key, true);
839 void HhbcTranslator::MInstrTranslator::emitBaseOp() {
840 LocationCode lCode = m_ni.immVec.locationCode();
841 switch (lCode) {
842 case LL: case LC: case LR: emitBaseLCR(); break;
843 case LH: emitBaseH(); break;
844 case LGL: case LGC: emitBaseG(); break;
845 case LNL: case LNC: emitBaseN(); break;
846 case LSL: case LSC: emitBaseS(); break;
847 default: not_reached();
851 void HhbcTranslator::MInstrTranslator::emitIntermediateOp() {
852 switch (m_ni.immVecM[m_mInd]) {
853 case MEC: case MEL: case MET: case MEI: {
854 emitElem();
855 ++m_iInd;
856 break;
858 case MPC: case MPL: case MPT:
859 emitProp();
860 ++m_iInd;
861 break;
862 case MW:
863 assert(m_mii.newElem());
864 emitNewElem();
865 break;
866 default: not_reached();
870 PropInfo HhbcTranslator::MInstrTranslator::getCurrentPropertyOffset(
871 const Class*& knownCls
873 auto info = getPropertyOffset(m_ni, contextClass(), knownCls,
874 m_mii, m_mInd, m_iInd);
875 if (info.offset == -1) return info;
877 auto baseType = m_base->type().derefIfPtr();
878 always_assert_flog(
879 baseType < Type::Obj,
880 "Got property offset for base {} which isn't an object",
881 *m_base->inst()
884 auto baseCls = baseType.getClass();
886 // baseCls and knownCls may differ due to a number of factors but they must
887 // always be related to each other somehow.
888 always_assert_flog(
889 baseCls->classof(knownCls) || knownCls->classof(baseCls),
890 "Class mismatch between baseType({}) and knownCls({})",
891 baseCls->name()->data(), knownCls->name()->data()
894 if (m_irb.constrainValue(m_base, TypeConstraint(baseCls).setWeak())) {
895 // We can't use this specialized class without making a guard more
896 // expensive, so don't do it.
897 knownCls = nullptr;
898 return PropInfo{};
900 return info;
903 void HhbcTranslator::MInstrTranslator::emitProp() {
904 const Class* knownCls = nullptr;
905 const auto propInfo = getCurrentPropertyOffset(knownCls);
906 auto mia = m_mii.getAttr(m_ni.immVecM[m_mInd]);
907 if (propInfo.offset == -1 || (mia & Unset) ||
908 mightCallMagicPropMethod(mia, knownCls, propInfo)) {
909 emitPropGeneric();
910 } else {
911 emitPropSpecialized(mia, propInfo);
915 template <MInstrAttr attrs, bool isObj>
916 static inline TypedValue* propImpl(Class* ctx, TypedValue* base,
917 TypedValue key, MInstrState* mis) {
918 return Prop<WDU(attrs), isObj>(
919 mis->tvScratch, mis->tvRef, ctx, base, key);
922 #define HELPER_TABLE(m) \
923 /* name attrs isObj */ \
924 m(propC, None, false) \
925 m(propCD, Define, false) \
926 m(propCDO, Define, true) \
927 m(propCO, None, true) \
928 m(propCU, Unset, false) \
929 m(propCUO, Unset, true) \
930 m(propCW, Warn, false) \
931 m(propCWD, WarnDefine, false) \
932 m(propCWDO, WarnDefine, true) \
933 m(propCWO, Warn, true)
935 #define PROP(nm, ...) \
936 TypedValue* nm(Class* ctx, TypedValue* base, TypedValue key, \
937 MInstrState* mis) { \
938 return propImpl<__VA_ARGS__>(ctx, base, key, mis); \
940 namespace MInstrHelpers {
941 HELPER_TABLE(PROP)
943 #undef PROP
945 void HhbcTranslator::MInstrTranslator::emitPropGeneric() {
946 MemberCode mCode = m_ni.immVecM[m_mInd];
947 MInstrAttr mia = MInstrAttr(m_mii.getAttr(mCode) & MIA_intermediate_prop);
949 if ((mia & Unset) && m_base->type().strip().not(Type::Obj)) {
950 constrainBase(DataTypeSpecific);
951 m_base = m_irb.genPtrToInitNull();
952 return;
955 typedef TypedValue* (*OpFunc)(Class*, TypedValue*, TypedValue, MInstrState*);
956 SSATmp* key = getKey();
957 BUILD_OPTAB(mia, m_base->isA(Type::Obj));
958 if (mia & Define) {
959 m_base = genStk(PropDX, makeCatch(), cns((TCA)opFunc), CTX(),
960 m_base, key, genMisPtr());
961 } else {
962 m_base = gen(PropX, makeCatch(),
963 cns((TCA)opFunc), CTX(), m_base, key, genMisPtr());
966 #undef HELPER_TABLE
969 * Helper for emitPropSpecialized to check if a property is Uninit. It
970 * returns a pointer to the property's address, or init_null_variant
971 * if the property was Uninit and doDefine is false.
973 * We can omit the uninit check for properties that we know may not be
974 * uninit due to the frontend's type inference.
976 SSATmp* HhbcTranslator::MInstrTranslator::checkInitProp(
977 SSATmp* baseAsObj,
978 SSATmp* propAddr,
979 PropInfo propInfo,
980 bool doWarn,
981 bool doDefine) {
982 SSATmp* key = getKey();
983 assert(key->isA(Type::StaticStr));
984 assert(baseAsObj->isA(Type::Obj));
985 assert(propAddr->type().isPtr());
987 auto const needsCheck =
988 Type::Uninit <= convertToType(propInfo.repoAuthType) &&
989 // The m_mInd check is to avoid initializing a property to
990 // InitNull right before it's going to be set to something else.
991 (doWarn || (doDefine && m_mInd < m_ni.immVecM.size() - 1));
993 if (!needsCheck) return propAddr;
995 return m_irb.cond(
997 [&] (Block* taken) {
998 gen(CheckInitMem, taken, propAddr, cns(0));
1000 [&] { // Next: Property isn't Uninit. Do nothing.
1001 return propAddr;
1003 [&] { // Taken: Property is Uninit. Raise a warning and return
1004 // a pointer to InitNull, either in the object or
1005 // init_null_variant.
1006 m_irb.hint(Block::Hint::Unlikely);
1007 if (doWarn && wantPropSpecializedWarnings()) {
1008 gen(RaiseUndefProp, m_ht.makeCatch(), baseAsObj, key);
1010 if (doDefine) {
1011 gen(
1012 StProp,
1013 baseAsObj,
1014 cns(propInfo.offset),
1015 cns(Type::InitNull)
1017 return propAddr;
1019 return m_irb.genPtrToInitNull();
1023 Class* HhbcTranslator::MInstrTranslator::contextClass() const {
1024 return m_ht.curFunc()->cls();
1027 void HhbcTranslator::MInstrTranslator::emitPropSpecialized(const MInstrAttr mia,
1028 PropInfo propInfo) {
1029 assert(!(mia & MIA_warn) || !(mia & MIA_unset));
1030 const bool doWarn = mia & MIA_warn;
1031 const bool doDefine = mia & MIA_define || mia & MIA_unset;
1033 SSATmp* initNull = m_irb.genPtrToInitNull();
1036 * Type-inference from hphpc only tells us that this is either an object of a
1037 * given class type or null. If it's not an object, it has to be a null type
1038 * based on type inference. (It could be KindOfRef with an object inside,
1039 * except that this isn't inferred for object properties so we're fine not
1040 * checking KindOfRef in that case.)
1042 * On the other hand, if m_base->isA(Type::Obj), we're operating on the base
1043 * which was already guarded by tracelet guards (and may have been KindOfRef,
1044 * but the Base* op already handled this). So we only need to do a type
1045 * check against null here in the intermediate cases.
1047 if (m_base->isA(Type::Obj)) {
1048 SSATmp* propAddr = gen(LdPropAddr, m_base, cns(propInfo.offset));
1049 m_base = checkInitProp(m_base, propAddr, propInfo, doWarn, doDefine);
1050 } else {
1051 m_base = m_irb.cond(
1053 [&] (Block* taken) {
1054 return gen(LdMem, Type::Obj, taken, m_base, cns(0));
1056 [&] (SSATmp* baseAsObj) {
1057 // Next: Base is an object. Load property address and check for uninit
1058 return checkInitProp(baseAsObj,
1059 gen(LdPropAddr, baseAsObj,
1060 cns(propInfo.offset)),
1061 propInfo,
1062 doWarn,
1063 doDefine);
1065 [&] { // Taken: Base is Null. Raise warnings/errors and return InitNull.
1066 m_irb.hint(Block::Hint::Unlikely);
1067 if (doWarn) {
1068 gen(WarnNonObjProp, makeCatch());
1070 if (doDefine) {
1072 * NOTE:
1074 * This case logically is supposed to do a stdClass promotion. It
1075 * should ideally not be possible (since we have a known class type),
1076 * except that the static compiler doesn't correctly infer object
1077 * class types in some edge cases involving stdClass promotion.
1079 * This is impossible to handle "correctly" if we're in the middle of
1080 * a multi-dim property expression, because things further along may
1081 * also have type inference telling them that object properties are
1082 * at a given slot, but the object could actually be a stdClass
1083 * instead of the knownCls type if we were to promote here.
1085 * So, we throw a fatal error, which is what hphpc's generated C++
1086 * would do in this case too.
1088 * Relevant TODOs:
1089 * #1789661 (this can cause bugs if bytecode.cpp promotes)
1090 * #1124706 (we want to get rid of stdClass promotion in general)
1092 gen(ThrowNonObjProp, makeCatch());
1094 return initNull;
1098 // At this point m_base is either a pointer to init_null_variant or
1099 // a property in the object that we've verified isn't uninit.
1100 assert(m_base->type().isPtr());
1103 template <KeyType keyType, bool warn, bool define, bool reffy,
1104 bool unset>
1105 static inline TypedValue* elemImpl(TypedValue* base, key_type<keyType> key,
1106 MInstrState* mis) {
1107 if (unset) {
1108 return ElemU<keyType>(mis->tvScratch, mis->tvRef, base, key);
1110 if (define) {
1111 return ElemD<warn, reffy, keyType>(mis->tvScratch, mis->tvRef, base, key);
1113 // We won't really modify the TypedValue in the non-D case, so
1114 // this const_cast is safe.
1115 return const_cast<TypedValue*>(
1116 Elem<warn, keyType>(mis->tvScratch, mis->tvRef, base, key)
1120 #define HELPER_TABLE(m) \
1121 /* name keyType attrs */ \
1122 m(elemC, KeyType::Any, None) \
1123 m(elemCD, KeyType::Any, Define) \
1124 m(elemCDR, KeyType::Any, DefineReffy) \
1125 m(elemCU, KeyType::Any, Unset) \
1126 m(elemCW, KeyType::Any, Warn) \
1127 m(elemCWD, KeyType::Any, WarnDefine) \
1128 m(elemCWDR, KeyType::Any, WarnDefineReffy) \
1129 m(elemI, KeyType::Int, None) \
1130 m(elemID, KeyType::Int, Define) \
1131 m(elemIDR, KeyType::Int, DefineReffy) \
1132 m(elemIU, KeyType::Int, Unset) \
1133 m(elemIW, KeyType::Int, Warn) \
1134 m(elemIWD, KeyType::Int, WarnDefine) \
1135 m(elemIWDR, KeyType::Int, WarnDefineReffy) \
1136 m(elemS, KeyType::Str, None) \
1137 m(elemSD, KeyType::Str, Define) \
1138 m(elemSDR, KeyType::Str, DefineReffy) \
1139 m(elemSU, KeyType::Str, Unset) \
1140 m(elemSW, KeyType::Str, Warn) \
1141 m(elemSWD, KeyType::Str, WarnDefine) \
1142 m(elemSWDR, KeyType::Str, WarnDefineReffy)
1144 #define ELEM(nm, keyType, attrs) \
1145 TypedValue* nm(TypedValue* base, key_type<keyType> key, MInstrState* mis) { \
1146 return elemImpl<keyType, WDRU(attrs)>(base, key, mis); \
1148 namespace MInstrHelpers {
1149 HELPER_TABLE(ELEM)
1151 #undef ELEM
1153 void HhbcTranslator::MInstrTranslator::emitElem() {
1154 MemberCode mCode = m_ni.immVecM[m_mInd];
1155 MInstrAttr mia = MInstrAttr(m_mii.getAttr(mCode) & MIA_intermediate);
1156 SSATmp* key = getKey();
1158 // Fast path for the common/easy case
1159 const bool warn = mia & Warn;
1160 const bool unset = mia & Unset;
1161 const bool define = mia & Define;
1162 if (m_base->isA(Type::PtrToArr) &&
1163 !unset && !define &&
1164 (key->isA(Type::Int) || key->isA(Type::Str))) {
1165 constrainBase(DataTypeSpecific);
1166 emitElemArray(key, warn);
1167 return;
1170 assert(!(define && unset));
1171 if (unset) {
1172 SSATmp* uninit = m_irb.genPtrToUninit();
1173 Type baseType = m_base->type().strip();
1174 constrainBase(DataTypeSpecific);
1175 if (baseType <= Type::Str) {
1176 m_ht.exceptionBarrier();
1177 gen(
1178 RaiseError,
1179 makeCatch(),
1180 cns(makeStaticString(Strings::OP_NOT_SUPPORTED_STRING))
1182 m_base = uninit;
1183 return;
1185 if (baseType.not(Type::Arr | Type::Obj)) {
1186 m_base = uninit;
1187 return;
1191 typedef TypedValue* (*OpFunc)(TypedValue*, TypedValue, MInstrState*);
1192 BUILD_OPTAB(getKeyType(key), mia);
1193 if (define || unset) {
1194 m_base = genStk(define ? ElemDX : ElemUX, makeCatch(),
1195 cns((TCA)opFunc), m_base, key, genMisPtr());
1196 } else {
1197 m_base = gen(ElemX, makeCatch(),
1198 cns((TCA)opFunc), m_base, key, genMisPtr());
1201 #undef HELPER_TABLE
1203 template<bool warn>
1204 NEVER_INLINE
1205 static const TypedValue* elemArrayNotFound(int64_t k) {
1206 if (warn) {
1207 raise_notice("Undefined index: %" PRId64, k);
1209 return null_variant.asTypedValue();
1212 template<bool warn>
1213 NEVER_INLINE
1214 static const TypedValue* elemArrayNotFound(const StringData* k) {
1215 if (warn) {
1216 raise_notice("Undefined index: %s", k->data());
1218 return null_variant.asTypedValue();
1221 static inline const TypedValue* checkedGet(ArrayData* a, StringData* key) {
1222 int64_t i;
1223 return UNLIKELY(key->isStrictlyInteger(i)) ? a->nvGet(i) : a->nvGet(key);
1226 static inline const TypedValue* checkedGet(ArrayData* a, int64_t key) {
1227 not_reached();
1230 template<KeyType keyType, bool checkForInt, bool warn>
1231 static inline const TypedValue* elemArrayImpl(TypedValue* a,
1232 key_type<keyType> key) {
1233 assert(a->m_type == KindOfArray);
1234 auto const ad = a->m_data.parr;
1235 auto const ret = checkForInt ? checkedGet(ad, key)
1236 : ad->nvGet(key);
1237 return ret ? ret : elemArrayNotFound<warn>(key);
1240 #define HELPER_TABLE(m) \
1241 /* name keyType checkForInt warn */ \
1242 m(elemArrayS, KeyType::Str, false, false) \
1243 m(elemArraySi, KeyType::Str, true, false) \
1244 m(elemArrayI, KeyType::Int, false, false) \
1245 m(elemArraySW, KeyType::Str, false, true) \
1246 m(elemArraySiW, KeyType::Str, true, true) \
1247 m(elemArrayIW, KeyType::Int, false, true)
1249 #define ELEM(nm, keyType, checkForInt, warn) \
1250 const TypedValue* nm(TypedValue* a, key_type<keyType> key) { \
1251 return elemArrayImpl<keyType, checkForInt, warn>( \
1252 a, key); \
1254 namespace MInstrHelpers {
1255 HELPER_TABLE(ELEM)
1257 #undef ELEM
1259 void HhbcTranslator::MInstrTranslator::emitElemArray(SSATmp* key, bool warn) {
1260 KeyType keyType;
1261 bool checkForInt;
1262 m_ht.checkStrictlyInteger(key, keyType, checkForInt);
1264 typedef TypedValue* (*OpFunc)(ArrayData*, TypedValue*);
1265 BUILD_OPTAB(keyType, checkForInt, warn);
1266 m_base = gen(ElemArray, makeCatch(), cns((TCA)opFunc), m_base, key);
1268 #undef HELPER_TABLE
1270 void HhbcTranslator::MInstrTranslator::emitNewElem() {
1271 PUNT(emitNewElem);
1274 void HhbcTranslator::MInstrTranslator::emitRatchetRefs() {
1275 if (ratchetInd() < 0 || ratchetInd() >= int(nLogicalRatchets())) {
1276 return;
1279 m_base = m_irb.cond(
1281 [&] (Block* taken) {
1282 gen(CheckInitMem, taken, m_misBase, cns(MISOFF(tvRef)));
1284 [&] { // Next: tvRef isn't Uninit. Ratchet the refs
1285 // Clean up tvRef2 before overwriting it.
1286 if (ratchetInd() > 0) {
1287 gen(DecRefMem, Type::Gen, m_misBase, cns(MISOFF(tvRef2)));
1289 // Copy tvRef to tvRef2. Use mmx at some point
1290 SSATmp* tvRef = gen(LdMem, Type::Gen, m_misBase, cns(MISOFF(tvRef)));
1291 gen(StMem, m_misBase, cns(MISOFF(tvRef2)), tvRef);
1293 // Reset tvRef.
1294 gen(StMem, m_misBase, cns(MISOFF(tvRef)), cns(Type::Uninit));
1296 // Adjust base pointer.
1297 assert(m_base->type().isPtr());
1298 return gen(LdMIStateAddr, m_misBase, cns(MISOFF(tvRef2)));
1300 [&] { // Taken: tvRef is Uninit. Do nothing.
1301 return m_base;
1305 void HhbcTranslator::MInstrTranslator::emitFinalMOp() {
1306 typedef void (HhbcTranslator::MInstrTranslator::*MemFun)();
1308 switch (m_ni.immVecM[m_mInd]) {
1309 case MEC: case MEL: case MET: case MEI:
1310 static MemFun elemOps[] = {
1311 # define MII(instr, ...) &HhbcTranslator::MInstrTranslator::emit##instr##Elem,
1312 MINSTRS
1313 # undef MII
1315 (this->*elemOps[m_mii.instr()])();
1316 break;
1318 case MPC: case MPL: case MPT:
1319 static MemFun propOps[] = {
1320 # define MII(instr, ...) &HhbcTranslator::MInstrTranslator::emit##instr##Prop,
1321 MINSTRS
1322 # undef MII
1324 (this->*propOps[m_mii.instr()])();
1325 break;
1327 case MW:
1328 assert(m_mii.getAttr(MW) & MIA_final);
1329 static MemFun newOps[] = {
1330 # define MII(instr, attrs, bS, iS, vC, fN) \
1331 &HhbcTranslator::MInstrTranslator::emit##fN,
1332 MINSTRS
1333 # undef MII
1335 (this->*newOps[m_mii.instr()])();
1336 break;
1338 default: not_reached();
1342 template <KeyType keyType, bool isObj>
1343 static inline TypedValue cGetPropImpl(Class* ctx, TypedValue* base,
1344 key_type<keyType> key, MInstrState* mis) {
1345 TypedValue scratch;
1346 TypedValue* result = Prop<true, false, false, isObj, keyType>(
1347 scratch, mis->tvRef, ctx, base, key);
1349 if (result->m_type == KindOfRef) {
1350 result = result->m_data.pref->tv();
1352 tvRefcountedIncRef(result);
1353 return *result;
1356 #define HELPER_TABLE(m) \
1357 /* name keyType isObj */ \
1358 m(cGetPropC, KeyType::Any, false) \
1359 m(cGetPropCO, KeyType::Any, true) \
1360 m(cGetPropS, KeyType::Str, false) \
1361 m(cGetPropSO, KeyType::Str, true)
1363 #define PROP(nm, kt, isObj) \
1364 TypedValue nm(Class* ctx, TypedValue* base, key_type<kt> key, \
1365 MInstrState* mis) { \
1366 return cGetPropImpl<kt, isObj>(ctx, base, key, mis); \
1368 namespace MInstrHelpers {
1369 HELPER_TABLE(PROP)
1371 #undef PROP
1373 void HhbcTranslator::MInstrTranslator::emitCGetProp() {
1374 const Class* knownCls = nullptr;
1375 const auto propInfo = getCurrentPropertyOffset(knownCls);
1377 if (propInfo.offset != -1 &&
1378 !mightCallMagicPropMethod(None, knownCls, propInfo)) {
1379 emitPropSpecialized(MIA_warn, propInfo);
1381 if (!RuntimeOption::RepoAuthoritative) {
1382 SSATmp* cellPtr = gen(UnboxPtr, m_base);
1383 m_result = gen(LdMem, Type::Cell, cellPtr, cns(0));
1384 gen(IncRef, m_result);
1385 return;
1388 auto const ty = convertToType(propInfo.repoAuthType);
1389 auto const cellPtr = ty.maybeBoxed() ? gen(UnboxPtr, m_base) : m_base;
1390 m_result = gen(LdMem, ty.unbox(), cellPtr, cns(0));
1391 gen(IncRef, m_result);
1392 return;
1395 typedef TypedValue (*OpFunc)(Class*, TypedValue*, TypedValue, MInstrState*);
1396 SSATmp* key = getKey();
1397 auto keyType = getKeyTypeNoInt(key);
1398 BUILD_OPTAB(keyType, m_base->isA(Type::Obj));
1399 m_result = gen(CGetProp, makeCatch(),
1400 cns((TCA)opFunc), CTX(), m_base, key, genMisPtr());
1402 #undef HELPER_TABLE
1404 template <KeyType keyType, bool isObj>
1405 static inline RefData* vGetPropImpl(Class* ctx, TypedValue* base,
1406 key_type<keyType> key, MInstrState* mis) {
1407 TypedValue* result = Prop<false, true, false, isObj, keyType>(
1408 mis->tvScratch, mis->tvRef, ctx, base, key);
1410 if (result->m_type != KindOfRef) {
1411 tvBox(result);
1413 RefData* ref = result->m_data.pref;
1414 ref->incRefCount();
1415 return ref;
1418 #define HELPER_TABLE(m) \
1419 /* name keyType isObj */\
1420 m(vGetPropC, KeyType::Any, false) \
1421 m(vGetPropCO, KeyType::Any, true) \
1422 m(vGetPropS, KeyType::Str, false) \
1423 m(vGetPropSO, KeyType::Str, true)
1425 #define PROP(nm, kt, isObj) \
1426 RefData* nm(Class* ctx, TypedValue* base, key_type<kt> key, \
1427 MInstrState* mis) { \
1428 return vGetPropImpl<kt, isObj>(ctx, base, key, mis); \
1430 namespace MInstrHelpers {
1431 HELPER_TABLE(PROP)
1433 #undef PROP
1435 void HhbcTranslator::MInstrTranslator::emitVGetProp() {
1436 SSATmp* key = getKey();
1437 typedef RefData* (*OpFunc)(Class*, TypedValue*, TypedValue, MInstrState*);
1438 BUILD_OPTAB(getKeyTypeNoInt(key), m_base->isA(Type::Obj));
1439 m_result = genStk(VGetProp, makeCatch(), cns((TCA)opFunc), CTX(),
1440 m_base, key, genMisPtr());
1442 #undef HELPER_TABLE
1444 template <bool useEmpty, bool isObj>
1445 static inline bool issetEmptyPropImpl(Class* ctx, TypedValue* base,
1446 TypedValue key) {
1447 return HPHP::IssetEmptyProp<useEmpty, isObj>(ctx, base, key);
1450 #define HELPER_TABLE(m) \
1451 /* name useEmpty isObj */ \
1452 m(issetPropC, false, false) \
1453 m(issetPropCE, true, false) \
1454 m(issetPropCEO, true, true) \
1455 m(issetPropCO, false, true)
1457 #define ISSET(nm, ...) \
1458 /* This returns int64_t to ensure all 64 bits of rax are valid */ \
1459 uint64_t nm(Class* ctx, TypedValue* base, TypedValue key) { \
1460 return issetEmptyPropImpl<__VA_ARGS__>(ctx, base, key); \
1462 namespace MInstrHelpers {
1463 HELPER_TABLE(ISSET)
1465 #undef ISSET
1467 void HhbcTranslator::MInstrTranslator::emitIssetEmptyProp(bool isEmpty) {
1468 SSATmp* key = getKey();
1469 typedef uint64_t (*OpFunc)(Class*, TypedValue*, TypedValue);
1470 BUILD_OPTAB(isEmpty, m_base->isA(Type::Obj));
1471 m_result = gen(isEmpty ? EmptyProp : IssetProp, makeCatch(),
1472 cns((TCA)opFunc), CTX(), m_base, key);
1474 #undef HELPER_TABLE
1476 void HhbcTranslator::MInstrTranslator::emitIssetProp() {
1477 emitIssetEmptyProp(false);
1480 void HhbcTranslator::MInstrTranslator::emitEmptyProp() {
1481 emitIssetEmptyProp(true);
1484 template <bool isObj>
1485 static inline void setPropImpl(Class* ctx, TypedValue* base,
1486 TypedValue key, Cell val) {
1487 HPHP::SetProp<false, isObj>(ctx, base, key, &val);
1490 #define HELPER_TABLE(m) \
1491 /* name isObj */ \
1492 m(setPropC, false) \
1493 m(setPropCO, true)
1495 #define PROP(nm, ...) \
1496 void nm(Class* ctx, TypedValue* base, TypedValue key, Cell val) { \
1497 setPropImpl<__VA_ARGS__>(ctx, base, key, val); \
1499 namespace MInstrHelpers {
1500 HELPER_TABLE(PROP)
1502 #undef PROP
1504 void HhbcTranslator::MInstrTranslator::emitSetProp() {
1505 SSATmp* value = getValue();
1507 /* If we know the class for the current base, emit a direct property set. */
1508 const Class* knownCls = nullptr;
1509 const auto propInfo = getCurrentPropertyOffset(knownCls);
1511 if (propInfo.offset != -1 &&
1512 !mightCallMagicPropMethod(Define, knownCls, propInfo)) {
1513 emitPropSpecialized(MIA_define, propInfo);
1515 auto const propTy = convertToType(propInfo.repoAuthType);
1516 auto const cellTy = propTy.maybeBoxed() ? propTy.unbox() : propTy;
1517 auto const cellPtr = propTy.maybeBoxed() ? gen(UnboxPtr, m_base) : m_base;
1518 auto const oldVal = gen(LdMem, cellTy, cellPtr, cns(0));
1520 gen(IncRef, value);
1521 gen(StMem, cellPtr, cns(0), value);
1522 gen(DecRef, oldVal);
1523 m_result = value;
1524 return;
1527 // Emit the appropriate helper call.
1528 typedef void (*OpFunc)(Class*, TypedValue*, TypedValue, Cell);
1529 SSATmp* key = getKey();
1530 BUILD_OPTAB(m_base->isA(Type::Obj));
1531 genStk(SetProp, makeCatchSet(), cns((TCA)opFunc), CTX(),
1532 m_base, key, value);
1533 m_result = value;
1535 #undef HELPER_TABLE
1537 template <bool isObj>
1538 static inline TypedValue setOpPropImpl(Class* ctx, TypedValue* base,
1539 TypedValue key,
1540 Cell val, MInstrState* mis, SetOpOp op) {
1541 TypedValue* result = HPHP::SetOpProp<isObj>(
1542 mis->tvScratch, mis->tvRef, ctx, op, base, key, &val);
1544 Cell ret;
1545 cellDup(*tvToCell(result), ret);
1546 return ret;
1549 #define HELPER_TABLE(m) \
1550 /* name isObj */ \
1551 m(setOpPropC, false) \
1552 m(setOpPropCO, true)
1554 #define SETOP(nm, ...) \
1555 TypedValue nm(Class* ctx, TypedValue* base, TypedValue key, \
1556 Cell val, MInstrState* mis, SetOpOp op) { \
1557 return setOpPropImpl<__VA_ARGS__>(ctx, base, key, val, mis, op); \
1559 namespace MInstrHelpers {
1560 HELPER_TABLE(SETOP)
1562 #undef SETOP
1564 void HhbcTranslator::MInstrTranslator::emitSetOpProp() {
1565 SetOpOp op = SetOpOp(m_ni.imm[0].u_OA);
1566 SSATmp* key = getKey();
1567 SSATmp* value = getValue();
1568 typedef TypedValue (*OpFunc)(TypedValue*, TypedValue,
1569 Cell, MInstrState*, SetOpOp);
1570 BUILD_OPTAB(m_base->isA(Type::Obj));
1571 m_result = genStk(SetOpProp, makeCatch(), cns((TCA)opFunc),
1572 CTX(), m_base, key, value, genMisPtr(), cns(op));
1574 #undef HELPER_TABLE
1576 template <bool isObj>
1577 static inline TypedValue incDecPropImpl(Class* ctx, TypedValue* base,
1578 TypedValue key,
1579 MInstrState* mis, IncDecOp op) {
1580 TypedValue result;
1581 result.m_type = KindOfUninit;
1582 HPHP::IncDecProp<true, isObj>(
1583 mis->tvScratch, mis->tvRef, ctx, op, base, key, result);
1584 assert(result.m_type != KindOfRef);
1585 return result;
1589 #define HELPER_TABLE(m) \
1590 /* name isObj */ \
1591 m(incDecPropC, false) \
1592 m(incDecPropCO, true)
1594 #define INCDEC(nm, ...) \
1595 TypedValue nm(Class* ctx, TypedValue* base, TypedValue key, \
1596 MInstrState* mis, IncDecOp op) { \
1597 return incDecPropImpl<__VA_ARGS__>(ctx, base, key, mis, op); \
1599 namespace MInstrHelpers {
1600 HELPER_TABLE(INCDEC)
1602 #undef INCDEC
1604 void HhbcTranslator::MInstrTranslator::emitIncDecProp() {
1605 IncDecOp op = static_cast<IncDecOp>(m_ni.imm[0].u_OA);
1606 SSATmp* key = getKey();
1607 typedef TypedValue (*OpFunc)(TypedValue*, TypedValue,
1608 MInstrState*, IncDecOp);
1609 BUILD_OPTAB(m_base->isA(Type::Obj));
1610 m_result = genStk(IncDecProp, makeCatch(), cns((TCA)opFunc),
1611 CTX(), m_base, key, genMisPtr(), cns(op));
1613 #undef HELPER_TABLE
1615 template <bool isObj>
1616 static inline void bindPropImpl(Class* ctx, TypedValue* base, TypedValue key,
1617 RefData* val, MInstrState* mis) {
1618 TypedValue* prop = Prop<false, true, false, isObj>(
1619 mis->tvScratch, mis->tvRef, ctx, base, key);
1620 if (!(prop == &mis->tvScratch && prop->m_type == KindOfUninit)) {
1621 tvBindRef(val, prop);
1625 #define HELPER_TABLE(m) \
1626 /* name isObj */ \
1627 m(bindPropC, false) \
1628 m(bindPropCO, true)
1630 #define PROP(nm, ...) \
1631 void nm(Class* ctx, TypedValue* base, TypedValue key, \
1632 RefData* val, MInstrState* mis) { \
1633 bindPropImpl<__VA_ARGS__>(ctx, base, key, val, mis); \
1635 namespace MInstrHelpers {
1636 HELPER_TABLE(PROP)
1638 #undef PROP
1640 void HhbcTranslator::MInstrTranslator::emitBindProp() {
1641 SSATmp* key = getKey();
1642 SSATmp* box = getValue();
1643 typedef void (*OpFunc)(Class*, TypedValue*, TypedValue, RefData*,
1644 MInstrState*);
1645 BUILD_OPTAB(m_base->isA(Type::Obj));
1646 genStk(BindProp, makeCatch(), cns((TCA)opFunc), CTX(),
1647 m_base, key, box, genMisPtr());
1648 m_result = box;
1650 #undef HELPER_TABLE
1652 template <bool isObj>
1653 static inline void unsetPropImpl(Class* ctx, TypedValue* base,
1654 TypedValue key) {
1655 HPHP::UnsetProp<isObj>(ctx, base, key);
1658 #define HELPER_TABLE(m) \
1659 /* name isObj */ \
1660 m(unsetPropC, false) \
1661 m(unsetPropCO, true)
1663 #define PROP(nm, ...) \
1664 static void nm(Class* ctx, TypedValue* base, TypedValue key) { \
1665 unsetPropImpl<__VA_ARGS__>(ctx, base, key); \
1667 namespace MInstrHelpers {
1668 HELPER_TABLE(PROP)
1670 #undef PROP
1672 void HhbcTranslator::MInstrTranslator::emitUnsetProp() {
1673 SSATmp* key = getKey();
1675 if (m_base->type().strip().not(Type::Obj)) {
1676 // Noop
1677 constrainBase(DataTypeSpecific);
1678 return;
1681 typedef void (*OpFunc)(Class*, TypedValue*, TypedValue);
1682 BUILD_OPTAB(m_base->isA(Type::Obj));
1683 gen(UnsetProp, makeCatch(), cns((TCA)opFunc), CTX(), m_base, key);
1685 #undef HELPER_TABLE
1687 // Keep these error handlers in sync with ArrayData::getNotFound();
1688 NEVER_INLINE
1689 static TypedValue arrayGetNotFound(int64_t k) {
1690 raise_notice("Undefined index: %" PRId64, k);
1691 TypedValue v;
1692 tvWriteNull(&v);
1693 return v;
1696 NEVER_INLINE
1697 static TypedValue arrayGetNotFound(const StringData* k) {
1698 raise_notice("Undefined index: %s", k->data());
1699 TypedValue v;
1700 tvWriteNull(&v);
1701 return v;
1704 SSATmp* HhbcTranslator::MInstrTranslator::emitPackedArrayGet(SSATmp* base,
1705 SSATmp* key,
1706 bool profiled
1707 /* = false*/) {
1708 // We can call emitPackedArrayGet on arrays for which we do not statically
1709 // know that they are packed, if we have profile information for the array.
1710 assert(base->isA(Type::Arr) &&
1711 (profiled || base->type().getArrayKind() == ArrayData::kPackedKind));
1713 return m_irb.cond(
1715 [&] (Block* taken) {
1716 gen(CheckPackedArrayBounds, taken, base, key);
1718 [&] { // Next:
1719 auto res = gen(LdPackedArrayElem, base, key);
1720 auto unboxed = m_ht.unbox(res, nullptr);
1721 gen(IncRef, unboxed);
1722 return unboxed;
1724 [&] { // Taken:
1725 m_irb.hint(Block::Hint::Unlikely);
1726 gen(RaiseArrayIndexNotice, makeCatch(), key);
1727 return cns(Type::InitNull);
1732 template<KeyType keyType, bool checkForInt>
1733 static inline TypedValue arrayGetImpl(ArrayData* a, key_type<keyType> key) {
1734 auto ret = checkForInt ? checkedGet(a, key) : a->nvGet(key);
1735 if (ret) {
1736 ret = tvToCell(ret);
1737 tvRefcountedIncRef(ret);
1738 return *ret;
1740 return arrayGetNotFound(key);
1743 #define HELPER_TABLE(m) \
1744 /* name keyType checkForInt */\
1745 m(arrayGetS, KeyType::Str, false) \
1746 m(arrayGetSi, KeyType::Str, true) \
1747 m(arrayGetI, KeyType::Int, false)
1749 #define ELEM(nm, keyType, checkForInt) \
1750 TypedValue nm(ArrayData* a, key_type<keyType> key) { \
1751 return arrayGetImpl<keyType, checkForInt>(a, key); \
1753 namespace MInstrHelpers {
1754 HELPER_TABLE(ELEM)
1756 #undef ELEM
1758 SSATmp* HhbcTranslator::MInstrTranslator::emitArrayGet(SSATmp* key) {
1759 KeyType keyType;
1760 bool checkForInt;
1761 m_ht.checkStrictlyInteger(key, keyType, checkForInt);
1763 typedef TypedValue (*OpFunc)(ArrayData*, TypedValue*);
1764 BUILD_OPTAB(keyType, checkForInt);
1765 assert(m_base->isA(Type::Arr));
1766 return gen(ArrayGet, makeCatch(), cns((TCA)opFunc), m_base, key);
1768 #undef HELPER_TABLE
1770 const StaticString s_PackedArray("PackedArray");
1772 void HhbcTranslator::MInstrTranslator::emitProfiledArrayGet(SSATmp* key) {
1773 TargetProfile<NonPackedArrayProfile> prof(m_ht.m_context,
1774 m_irb.state().marker(),
1775 s_PackedArray.get());
1776 if (prof.profiling()) {
1777 gen(ProfileArray, RDSHandleData { prof.handle() }, m_base);
1778 m_result = emitArrayGet(key);
1779 return;
1782 m_ht.emitIncStat(Stats::ArrayGet_Total, 1, false);
1783 if (prof.optimizing()) {
1784 m_ht.emitIncStat(Stats::ArrayGet_Opt, 1, false);
1785 auto const data = prof.data(NonPackedArrayProfile::reduce);
1786 // NonPackedArrayProfile data counts how many times a non-packed
1787 // array was observed.
1788 if (data.count == 0) {
1789 m_ht.emitIncStat(Stats::ArrayGet_Mono, 1, false);
1790 m_result = m_irb.cond(
1792 [&] (Block* taken) {
1793 return gen(CheckType, Type::Arr.specialize(ArrayData::kPackedKind),
1794 taken, m_base);
1796 [&] (SSATmp* base) { // Next
1797 m_ht.emitIncStat(Stats::ArrayGet_Packed, 1, false);
1798 m_irb.constrainValue(
1799 base, TypeConstraint(DataTypeSpecialized).setWantArrayKind());
1800 return emitPackedArrayGet(base, key, true);
1802 [&] { // Taken
1803 m_irb.hint(Block::Hint::Unlikely);
1804 m_ht.emitIncStat(Stats::ArrayGet_Mixed, 1, false);
1805 return emitArrayGet(key);
1808 return;
1811 m_result = emitArrayGet(key);
1814 namespace MInstrHelpers {
1815 StringData* stringGetI(StringData* str, uint64_t x) {
1816 if (LIKELY(x < str->size())) {
1817 return str->getChar(x);
1819 if (RuntimeOption::EnableHipHopSyntax) {
1820 raise_warning("Out of bounds");
1822 return makeStaticString("");
1826 void HhbcTranslator::MInstrTranslator::emitStringGet(SSATmp* key) {
1827 assert(key->isA(Type::Int));
1828 m_result = gen(StringGet, makeCatch(),
1829 cns((TCA)MInstrHelpers::stringGetI), m_base, key);
1832 void HhbcTranslator::MInstrTranslator::emitVectorGet(SSATmp* key) {
1833 assert(key->isA(Type::Int));
1834 if (key->isConst() && key->intVal() < 0) {
1835 PUNT(emitVectorGet);
1837 SSATmp* size = gen(LdVectorSize, m_base);
1838 gen(CheckBounds, makeCatch(), key, size);
1839 SSATmp* base = gen(LdVectorBase, m_base);
1840 static_assert(sizeof(TypedValue) == 16,
1841 "TypedValue size expected to be 16 bytes");
1842 auto idx = gen(Shl, key, cns(4));
1843 m_result = gen(LdElem, base, idx);
1844 gen(IncRef, m_result);
1847 void HhbcTranslator::MInstrTranslator::emitPairGet(SSATmp* key) {
1848 assert(key->isA(Type::Int));
1849 static_assert(sizeof(TypedValue) == 16,
1850 "TypedValue size expected to be 16 bytes");
1851 if (key->isConst()) {
1852 auto idx = key->intVal();
1853 if (idx < 0 || idx > 1) {
1854 PUNT(emitPairGet);
1856 // no reason to check bounds
1857 SSATmp* base = gen(LdPairBase, m_base);
1858 auto index = cns(key->intVal() << 4);
1859 m_result = gen(LdElem, base, index);
1860 } else {
1861 gen(CheckBounds, makeCatch(), key, cns(1));
1862 SSATmp* base = gen(LdPairBase, m_base);
1863 auto idx = gen(Shl, key, cns(4));
1864 m_result = gen(LdElem, base, idx);
1866 gen(IncRef, m_result);
1869 template<KeyType keyType>
1870 static inline TypedValue mapGetImpl(c_Map* map, key_type<keyType> key) {
1871 TypedValue* ret = map->at(key);
1872 tvRefcountedIncRef(ret);
1873 return *ret;
1876 #define HELPER_TABLE(m) \
1877 /* name keyType */ \
1878 m(mapGetS, KeyType::Str) \
1879 m(mapGetI, KeyType::Int)
1881 #define ELEM(nm, keyType) \
1882 TypedValue nm(c_Map* map, key_type<keyType> key) { \
1883 return mapGetImpl<keyType>(map, key); \
1885 namespace MInstrHelpers {
1886 HELPER_TABLE(ELEM)
1888 #undef ELEM
1890 void HhbcTranslator::MInstrTranslator::emitMapGet(SSATmp* key) {
1891 assert(key->isA(Type::Int) || key->isA(Type::Str));
1892 KeyType keyType = key->isA(Type::Int) ? KeyType::Int : KeyType::Str;
1894 typedef TypedValue (*OpFunc)(c_Map*, TypedValue*);
1895 BUILD_OPTAB(keyType);
1896 m_result = gen(MapGet, makeCatch(), cns((TCA)opFunc), m_base, key);
1898 #undef HELPER_TABLE
1900 template <KeyType keyType>
1901 static inline TypedValue cGetElemImpl(TypedValue* base, key_type<keyType> key,
1902 MInstrState* mis) {
1903 TypedValue scratch;
1904 auto result = Elem<true, keyType>(scratch, mis->tvRef, base, key);
1905 result = tvToCell(result);
1906 tvRefcountedIncRef(result);
1907 return *result;
1910 #define HELPER_TABLE(m) \
1911 /* name key */ \
1912 m(cGetElemC, KeyType::Any) \
1913 m(cGetElemI, KeyType::Int) \
1914 m(cGetElemS, KeyType::Str)
1916 #define ELEM(nm, kt) \
1917 TypedValue nm(TypedValue* base, key_type<kt> key, MInstrState* mis) { \
1918 return cGetElemImpl<kt>(base, key, mis); \
1920 namespace MInstrHelpers {
1921 HELPER_TABLE(ELEM)
1923 #undef ELEM
1925 void HhbcTranslator::MInstrTranslator::emitCGetElem() {
1926 SSATmp* key = getKey();
1928 SimpleOp simpleOpType = simpleCollectionOp();
1929 switch (simpleOpType) {
1930 case SimpleOp::Array:
1931 m_result = emitArrayGet(key);
1932 break;
1933 case SimpleOp::PackedArray:
1934 m_result = emitPackedArrayGet(m_base, key);
1935 break;
1936 case SimpleOp::ProfiledArray:
1937 emitProfiledArrayGet(key);
1938 break;
1939 case SimpleOp::String:
1940 emitStringGet(key);
1941 break;
1942 case SimpleOp::Vector:
1943 emitVectorGet(key);
1944 break;
1945 case SimpleOp::Pair:
1946 emitPairGet(key);
1947 break;
1948 case SimpleOp::Map:
1949 emitMapGet(key);
1950 break;
1951 case SimpleOp::None:
1952 typedef TypedValue (*OpFunc)(TypedValue*, TypedValue, MInstrState*);
1953 BUILD_OPTAB(getKeyType(key));
1954 m_result = gen(CGetElem, makeCatch(), cns((TCA)opFunc),
1955 m_base, key, genMisPtr());
1956 break;
1959 #undef HELPER_TABLE
1961 template <KeyType keyType>
1962 static inline RefData* vGetElemImpl(TypedValue* base, key_type<keyType> key,
1963 MInstrState* mis) {
1964 TypedValue* result = HPHP::ElemD<false, true, keyType>(
1965 mis->tvScratch, mis->tvRef, base, key);
1967 if (result->m_type != KindOfRef) {
1968 tvBox(result);
1970 RefData* ref = result->m_data.pref;
1971 ref->incRefCount();
1972 return ref;
1975 #define HELPER_TABLE(m) \
1976 /* name keyType */ \
1977 m(vGetElemC, KeyType::Any) \
1978 m(vGetElemI, KeyType::Int) \
1979 m(vGetElemS, KeyType::Str)
1981 #define ELEM(nm, kt) \
1982 RefData* nm(TypedValue* base, key_type<kt> key, MInstrState* mis) { \
1983 return vGetElemImpl<kt>(base, key, mis); \
1985 namespace MInstrHelpers {
1986 HELPER_TABLE(ELEM)
1988 #undef ELEM
1990 void HhbcTranslator::MInstrTranslator::emitVGetElem() {
1991 SSATmp* key = getKey();
1992 typedef RefData* (*OpFunc)(TypedValue*, TypedValue, MInstrState*);
1993 BUILD_OPTAB(getKeyType(key));
1994 m_result = genStk(VGetElem, makeCatch(), cns((TCA)opFunc),
1995 m_base, key, genMisPtr());
1997 #undef HELPER_TABLE
1999 template <KeyType keyType, bool isEmpty>
2000 static inline bool issetEmptyElemImpl(TypedValue* base, key_type<keyType> key,
2001 MInstrState* mis) {
2002 // mis == nullptr if we proved that it won't be used. mis->tvScratch and
2003 // mis->tvRef are ok because those params are passed by
2004 // reference.
2005 return HPHP::IssetEmptyElem<isEmpty, keyType>(
2006 mis->tvScratch, mis->tvRef, base, key);
2009 #define HELPER_TABLE(m) \
2010 /* name keyType isEmpty */\
2011 m(issetElemC, KeyType::Any, false) \
2012 m(issetElemCE, KeyType::Any, true) \
2013 m(issetElemI, KeyType::Int, false) \
2014 m(issetElemIE, KeyType::Int, true) \
2015 m(issetElemS, KeyType::Str, false) \
2016 m(issetElemSE, KeyType::Str, true)
2018 #define ISSET(nm, kt, isEmpty) \
2019 uint64_t nm(TypedValue* base, key_type<kt> key, MInstrState* mis) { \
2020 return issetEmptyElemImpl<kt, isEmpty>(base, key, mis); \
2022 namespace MInstrHelpers {
2023 HELPER_TABLE(ISSET)
2025 #undef ISSET
2027 void HhbcTranslator::MInstrTranslator::emitIssetEmptyElem(bool isEmpty) {
2028 SSATmp* key = getKey();
2030 typedef uint64_t (*OpFunc)(TypedValue*, TypedValue, MInstrState*);
2031 BUILD_OPTAB(getKeyType(key), isEmpty);
2032 m_result = gen(isEmpty ? EmptyElem : IssetElem, makeCatch(),
2033 cns((TCA)opFunc), m_base, key, genMisPtr());
2035 #undef HELPER_TABLE
2037 void HhbcTranslator::MInstrTranslator::emitPackedArrayIsset() {
2038 assert(m_base->type().getArrayKind() == ArrayData::kPackedKind);
2039 SSATmp* key = getKey();
2040 m_result = m_irb.cond(
2042 [&] (Block* taken) {
2043 gen(CheckPackedArrayBounds, taken, m_base, key);
2045 [&] { // Next:
2046 return gen(CheckPackedArrayElemNull, m_base, key);
2048 [&] { // Taken:
2049 return cns(false);
2053 template<KeyType keyType, bool checkForInt>
2054 static inline uint64_t arrayIssetImpl(ArrayData* a, key_type<keyType> key) {
2055 auto const value = checkForInt ? checkedGet(a, key) : a->nvGet(key);
2056 if (!value) return 0;
2057 return !tvAsCVarRef(value).isNull();
2060 #define HELPER_TABLE(m) \
2061 /* name keyType checkForInt */\
2062 m(arrayIssetS, KeyType::Str, false) \
2063 m(arrayIssetSi, KeyType::Str, true) \
2064 m(arrayIssetI, KeyType::Int, false)
2066 #define ISSET(nm, keyType, checkForInt) \
2067 uint64_t nm(ArrayData* a, key_type<keyType> key) { \
2068 return arrayIssetImpl<keyType, checkForInt>(a, key); \
2070 namespace MInstrHelpers {
2071 HELPER_TABLE(ISSET)
2073 #undef ISSET
2075 void HhbcTranslator::MInstrTranslator::emitArrayIsset() {
2076 SSATmp* key = getKey();
2077 KeyType keyType;
2078 bool checkForInt;
2079 m_ht.checkStrictlyInteger(key, keyType, checkForInt);
2081 typedef uint64_t (*OpFunc)(ArrayData*, TypedValue*);
2082 BUILD_OPTAB(keyType, checkForInt);
2083 assert(m_base->isA(Type::Arr));
2084 m_result = gen(ArrayIsset, makeCatch(), cns((TCA)opFunc), m_base, key);
2086 #undef HELPER_TABLE
2088 void HhbcTranslator::MInstrTranslator::emitStringIsset() {
2089 auto key = getKey();
2090 m_result = gen(StringIsset, m_base, key);
2093 namespace MInstrHelpers {
2094 uint64_t vectorIsset(c_Vector* vec, int64_t index) {
2095 auto result = vec->get(index);
2096 return result ? !cellIsNull(result) : false;
2100 void HhbcTranslator::MInstrTranslator::emitVectorIsset() {
2101 SSATmp* key = getKey();
2102 assert(key->isA(Type::Int));
2103 m_result = gen(VectorIsset,
2104 cns((TCA)MInstrHelpers::vectorIsset),
2105 m_base,
2106 key);
2109 namespace MInstrHelpers {
2110 uint64_t pairIsset(c_Pair* pair, int64_t index) {
2111 auto result = pair->get(index);
2112 return result ? !cellIsNull(result) : false;
2116 void HhbcTranslator::MInstrTranslator::emitPairIsset() {
2117 SSATmp* key = getKey();
2118 assert(key->isA(Type::Int));
2119 m_result = gen(PairIsset,
2120 cns((TCA)MInstrHelpers::pairIsset),
2121 m_base,
2122 key);
2125 template<KeyType keyType>
2126 static inline uint64_t mapIssetImpl(c_Map* map, key_type<keyType> key) {
2127 auto result = map->get(key);
2128 return result ? !cellIsNull(result) : false;
2131 #define HELPER_TABLE(m) \
2132 /* name keyType */ \
2133 m(mapIssetS, KeyType::Str) \
2134 m(mapIssetI, KeyType::Int)
2136 #define ELEM(nm, keyType) \
2137 uint64_t nm(c_Map* map, key_type<keyType> key) { \
2138 return mapIssetImpl<keyType>(map, key); \
2140 namespace MInstrHelpers {
2141 HELPER_TABLE(ELEM)
2143 #undef ELEM
2145 void HhbcTranslator::MInstrTranslator::emitMapIsset() {
2146 SSATmp* key = getKey();
2147 assert(key->isA(Type::Int) || key->isA(Type::Str));
2148 KeyType keyType = key->isA(Type::Int) ? KeyType::Int : KeyType::Str;
2150 typedef TypedValue (*OpFunc)(c_Map*, TypedValue*);
2151 BUILD_OPTAB(keyType);
2152 m_result = gen(MapIsset, cns((TCA)opFunc), m_base, key);
2154 #undef HELPER_TABLE
2156 void HhbcTranslator::MInstrTranslator::emitIssetElem() {
2157 SimpleOp simpleOpType = simpleCollectionOp();
2158 switch (simpleOpType) {
2159 case SimpleOp::Array:
2160 case SimpleOp::ProfiledArray:
2161 emitArrayIsset();
2162 break;
2163 case SimpleOp::PackedArray:
2164 emitPackedArrayIsset();
2165 break;
2166 case SimpleOp::String:
2167 emitStringIsset();
2168 break;
2169 case SimpleOp::Vector:
2170 emitVectorIsset();
2171 break;
2172 case SimpleOp::Pair:
2173 emitPairIsset();
2174 break;
2175 case SimpleOp::Map:
2176 emitMapIsset();
2177 break;
2178 case SimpleOp::None:
2179 emitIssetEmptyElem(false);
2180 break;
2184 void HhbcTranslator::MInstrTranslator::emitEmptyElem() {
2185 emitIssetEmptyElem(true);
2188 static inline ArrayData* uncheckedSet(ArrayData* a,
2189 StringData* key,
2190 Cell value,
2191 bool copy) {
2192 return g_array_funcs.setStr[a->kind()](a, key, value, copy);
2195 static inline ArrayData* uncheckedSet(ArrayData* a,
2196 int64_t key,
2197 Cell value,
2198 bool copy) {
2199 return g_array_funcs.setInt[a->kind()](a, key, value, copy);
2202 static inline ArrayData* checkedSet(ArrayData* a,
2203 StringData* key,
2204 Cell value,
2205 bool copy) {
2206 int64_t i;
2207 return UNLIKELY(key->isStrictlyInteger(i))
2208 ? uncheckedSet(a, i, value, copy)
2209 : uncheckedSet(a, key, value, copy);
2212 static inline ArrayData* checkedSet(ArrayData* a,
2213 int64_t key,
2214 Cell value,
2215 bool copy) {
2216 not_reached();
2219 template<KeyType keyType, bool checkForInt, bool setRef>
2220 static inline typename ShuffleReturn<setRef>::return_type arraySetImpl(
2221 ArrayData* a, key_type<keyType> key,
2222 Cell value, RefData* ref) {
2223 static_assert(keyType != KeyType::Any,
2224 "KeyType::Any is not supported in arraySetMImpl");
2225 assert(cellIsPlausible(value));
2226 const bool copy = a->hasMultipleRefs();
2227 ArrayData* ret = checkForInt ? checkedSet(a, key, value, copy)
2228 : uncheckedSet(a, key, value, copy);
2230 return arrayRefShuffle<setRef>(a, ret, setRef ? ref->tv() : nullptr);
2233 #define HELPER_TABLE(m) \
2234 /* name keyType checkForInt setRef */ \
2235 m(arraySetS, KeyType::Str, false, false) \
2236 m(arraySetSi, KeyType::Str, true, false) \
2237 m(arraySetI, KeyType::Int, false, false) \
2238 m(arraySetSR, KeyType::Str, false, true) \
2239 m(arraySetSiR, KeyType::Str, true, true) \
2240 m(arraySetIR, KeyType::Int, false, true)
2242 #define ELEM(nm, keyType, checkForInt, setRef) \
2243 typename ShuffleReturn<setRef>::return_type \
2244 nm(ArrayData* a, key_type<keyType> key, Cell value, RefData* ref) { \
2245 return arraySetImpl<keyType, checkForInt, setRef>( \
2246 a, key, value, ref); \
2248 namespace MInstrHelpers {
2249 HELPER_TABLE(ELEM)
2251 #undef ELEM
2253 void HhbcTranslator::MInstrTranslator::emitArraySet(SSATmp* key,
2254 SSATmp* value) {
2255 assert(m_iInd == m_mii.valCount() + 1);
2256 const int baseStkIdx = m_mii.valCount();
2257 assert(key->type().notBoxed());
2258 assert(value->type().notBoxed());
2259 KeyType keyType;
2260 bool checkForInt;
2261 m_ht.checkStrictlyInteger(key, keyType, checkForInt);
2262 const DynLocation& base = *m_ni.inputs[m_mii.valCount()];
2263 bool setRef = base.rtt.isBoxed();
2264 typedef ArrayData* (*OpFunc)(ArrayData*, TypedValue*, TypedValue, RefData*);
2265 BUILD_OPTAB(keyType, checkForInt, setRef);
2267 // No catch trace below because the helper can't throw. It may reenter to
2268 // call destructors so it has a sync point in nativecalls.cpp, but exceptions
2269 // are swallowed at destructor boundaries right now: #2182869.
2270 if (setRef) {
2271 assert(base.location.space == Location::Local ||
2272 base.location.space == Location::Stack);
2273 SSATmp* box = getInput(baseStkIdx, DataTypeSpecific);
2274 gen(ArraySetRef, makeCatch(), cns((TCA)opFunc), m_base, key, value, box);
2275 // Unlike the non-ref case, we don't need to do anything to the stack
2276 // because any load of the box will be guarded.
2277 m_result = value;
2278 return;
2281 SSATmp* newArr = gen(ArraySet, makeCatch(), cns((TCA)opFunc), m_base, key,
2282 value);
2284 // Update the base's value with the new array
2285 if (base.location.space == Location::Local) {
2286 // We know it's not boxed (setRef above handles that), and
2287 // newArr has already been incref'd in the helper.
2288 gen(StLoc, LocalId(base.location.offset), m_irb.fp(), newArr);
2289 } else if (base.location.space == Location::Stack) {
2290 MInstrEffects effects(newArr->inst());
2291 assert(effects.baseValChanged);
2292 assert(effects.baseType <= Type::Arr);
2293 m_ht.extendStack(baseStkIdx, Type::Gen);
2294 m_ht.replace(baseStkIdx, newArr);
2295 } else {
2296 not_reached();
2299 m_result = value;
2301 #undef HELPER_TABLE
2303 namespace MInstrHelpers {
2304 void setWithRefElemC(TypedValue* base, TypedValue key, TypedValue* val,
2305 MInstrState* mis) {
2306 base = HPHP::ElemD<false, false>(mis->tvScratch, mis->tvRef, base, key);
2307 if (base != &mis->tvScratch) {
2308 tvDup(*val, *base);
2309 } else {
2310 assert(base->m_type == KindOfUninit);
2314 void setWithRefNewElem(TypedValue* base, TypedValue* val,
2315 MInstrState* mis) {
2316 base = NewElem(mis->tvScratch, mis->tvRef, base);
2317 if (base != &mis->tvScratch) {
2318 tvDup(*val, *base);
2319 } else {
2320 assert(base->m_type == KindOfUninit);
2325 void HhbcTranslator::MInstrTranslator::emitSetWithRefLElem() {
2326 SSATmp* key = getKey();
2327 SSATmp* locAddr = getValAddr();
2328 if (m_base->type().strip() <= Type::Arr &&
2329 !locAddr->type().deref().maybeBoxed()) {
2330 constrainBase(DataTypeSpecific);
2331 emitSetElem();
2332 assert(m_strTestResult == nullptr);
2333 } else {
2334 genStk(SetWithRefElem, makeCatch(),
2335 cns((TCA)MInstrHelpers::setWithRefElemC),
2336 m_base, key, locAddr, genMisPtr());
2338 m_result = nullptr;
2341 void HhbcTranslator::MInstrTranslator::emitSetWithRefLProp() {
2342 SPUNT(__func__);
2345 void HhbcTranslator::MInstrTranslator::emitSetWithRefRElem() {
2346 emitSetWithRefLElem();
2349 void HhbcTranslator::MInstrTranslator::emitSetWithRefRProp() {
2350 emitSetWithRefLProp();
2353 void HhbcTranslator::MInstrTranslator::emitSetWithRefNewElem() {
2354 if (m_base->type().strip() <= Type::Arr &&
2355 getValue()->type().notBoxed()) {
2356 constrainBase(DataTypeSpecific);
2357 emitSetNewElem();
2358 } else {
2359 genStk(SetWithRefNewElem, makeCatch(),
2360 cns((TCA)MInstrHelpers::setWithRefNewElem),
2361 m_base, getValAddr(), genMisPtr());
2363 m_result = nullptr;
2366 void HhbcTranslator::MInstrTranslator::emitVectorSet(
2367 SSATmp* key, SSATmp* value) {
2368 assert(key->isA(Type::Int));
2369 if (key->isConst() && key->intVal() < 0) {
2370 PUNT(emitVectorSet); // will throw
2372 SSATmp* size = gen(LdVectorSize, m_base);
2373 gen(CheckBounds, makeCatch(), key, size);
2375 m_irb.ifThen([&](Block* taken) {
2376 gen(VectorHasImmCopy, taken, m_base);
2378 [&] {
2379 m_irb.hint(Block::Hint::Unlikely);
2380 gen(VectorDoCow, m_base);
2383 gen(IncRef, value);
2384 SSATmp* vecBase = gen(LdVectorBase, m_base);
2385 SSATmp* oldVal;
2386 static_assert(sizeof(TypedValue) == 16,
2387 "TypedValue size expected to be 16 bytes");
2388 auto idx = gen(Shl, key, cns(4));
2389 oldVal = gen(LdElem, vecBase, idx);
2390 gen(StElem, vecBase, idx, value);
2391 gen(DecRef, oldVal);
2393 m_result = value;
2396 template<KeyType keyType>
2397 static inline void mapSetImpl(c_Map* map, key_type<keyType> key, Cell value) {
2398 map->set(key, &value);
2401 #define HELPER_TABLE(m) \
2402 /* name keyType */ \
2403 m(mapSetS, KeyType::Str) \
2404 m(mapSetI, KeyType::Int)
2406 #define ELEM(nm, keyType) \
2407 void nm(c_Map* map, key_type<keyType> key, Cell value) { \
2408 mapSetImpl<keyType>(map, key, value); \
2410 namespace MInstrHelpers {
2411 HELPER_TABLE(ELEM)
2413 #undef ELEM
2415 void HhbcTranslator::MInstrTranslator::emitMapSet(
2416 SSATmp* key, SSATmp* value) {
2417 assert(key->isA(Type::Int) || key->isA(Type::Str));
2418 KeyType keyType = key->isA(Type::Int) ? KeyType::Int : KeyType::Str;
2420 typedef TypedValue (*OpFunc)(c_Map*, TypedValue*, TypedValue*);
2421 BUILD_OPTAB(keyType);
2422 gen(MapSet, makeCatch(), cns((TCA)opFunc), m_base, key, value);
2423 m_result = value;
2425 #undef HELPER_TABLE
2427 template <KeyType keyType>
2428 static inline StringData* setElemImpl(TypedValue* base, key_type<keyType> key,
2429 Cell val) {
2430 return HPHP::SetElem<false, keyType>(base, key, &val);
2433 #define HELPER_TABLE(m) \
2434 /* name keyType */ \
2435 m(setElemC, KeyType::Any) \
2436 m(setElemI, KeyType::Int) \
2437 m(setElemS, KeyType::Str)
2439 #define ELEM(nm, kt) \
2440 StringData* nm(TypedValue* base, key_type<kt> key, Cell val) { \
2441 return setElemImpl<kt>(base, key, val); \
2443 namespace MInstrHelpers {
2444 HELPER_TABLE(ELEM)
2446 #undef ELEM
2448 void HhbcTranslator::MInstrTranslator::emitSetElem() {
2449 SSATmp* value = getValue();
2450 SSATmp* key = getKey();
2452 SimpleOp simpleOpType = simpleCollectionOp();
2453 assert(simpleOpType != SimpleOp::PackedArray);
2454 switch (simpleOpType) {
2455 case SimpleOp::Array:
2456 case SimpleOp::ProfiledArray:
2457 emitArraySet(key, value);
2458 break;
2459 case SimpleOp::PackedArray:
2460 not_reached();
2461 break;
2462 case SimpleOp::String:
2463 not_reached();
2464 break;
2465 case SimpleOp::Vector:
2466 emitVectorSet(key, value);
2467 break;
2468 case SimpleOp::Map:
2469 emitMapSet(key, value);
2470 break;
2471 case SimpleOp::Pair:
2472 case SimpleOp::None:
2473 // Emit the appropriate helper call.
2474 typedef StringData* (*OpFunc)(TypedValue*, TypedValue, Cell);
2475 BUILD_OPTAB(getKeyType(key));
2476 constrainBase(DataTypeSpecific);
2477 SSATmp* result = genStk(SetElem, makeCatchSet(), cns((TCA)opFunc),
2478 m_base, key, value);
2479 auto t = result->type();
2480 if (t == Type::Nullptr) {
2481 // Base is not a string. Result is always value.
2482 m_result = value;
2483 } else if (t == Type::CountedStr) {
2484 // Base is a string. Stack result is a new string so we're responsible for
2485 // decreffing value.
2486 m_result = result;
2487 gen(DecRef, value);
2488 } else {
2489 assert(t.equals(Type::CountedStr | Type::Nullptr));
2490 // Base might be a string. Assume the result is value, then inform
2491 // emitMPost that it needs to test the actual result.
2492 m_result = value;
2493 m_strTestResult = result;
2495 break;
2498 #undef HELPER_TABLE
2500 void HhbcTranslator::MInstrTranslator::emitSetOpElem() {
2501 SetOpOp op = static_cast<SetOpOp>(m_ni.imm[0].u_OA);
2502 m_result = genStk(SetOpElem, makeCatch(),
2503 m_base, getKey(), getValue(), genMisPtr(), cns(op));
2506 void HhbcTranslator::MInstrTranslator::emitIncDecElem() {
2507 IncDecOp op = static_cast<IncDecOp>(m_ni.imm[0].u_OA);
2508 m_result = genStk(IncDecElem, makeCatch(),
2509 m_base, getKey(), genMisPtr(), cns(op));
2512 namespace MInstrHelpers {
2513 void bindElemC(TypedValue* base, TypedValue key, RefData* val,
2514 MInstrState* mis) {
2515 base = HPHP::ElemD<false, true>(mis->tvScratch, mis->tvRef, base, key);
2516 if (!(base == &mis->tvScratch && base->m_type == KindOfUninit)) {
2517 tvBindRef(val, base);
2522 void HhbcTranslator::MInstrTranslator::emitBindElem() {
2523 SSATmp* key = getKey();
2524 SSATmp* box = getValue();
2525 genStk(BindElem, makeCatch(), cns((TCA)MInstrHelpers::bindElemC),
2526 m_base, key, box, genMisPtr());
2527 m_result = box;
2530 template <KeyType keyType>
2531 static inline void unsetElemImpl(TypedValue* base, key_type<keyType> key) {
2532 HPHP::UnsetElem<keyType>(base, key);
2535 #define HELPER_TABLE(m) \
2536 /* name keyType */ \
2537 m(unsetElemC, KeyType::Any) \
2538 m(unsetElemI, KeyType::Int) \
2539 m(unsetElemS, KeyType::Str)
2541 #define ELEM(nm, kt) \
2542 void nm(TypedValue* base, key_type<kt> key) { \
2543 unsetElemImpl<kt>(base, key); \
2545 namespace MInstrHelpers {
2546 HELPER_TABLE(ELEM)
2548 #undef ELEM
2550 void HhbcTranslator::MInstrTranslator::emitUnsetElem() {
2551 SSATmp* key = getKey();
2553 Type baseType = m_base->type().strip();
2554 constrainBase(DataTypeSpecific);
2555 if (baseType <= Type::Str) {
2556 gen(RaiseError, makeCatch(),
2557 cns(makeStaticString(Strings::CANT_UNSET_STRING)));
2558 return;
2560 if (baseType.not(Type::Arr | Type::Obj)) {
2561 // Noop
2562 return;
2565 typedef void (*OpFunc)(TypedValue*, TypedValue);
2566 BUILD_OPTAB(getKeyType(key));
2567 genStk(UnsetElem, makeCatch(), cns((TCA)opFunc), m_base, key);
2569 #undef HELPER_TABLE
2571 void HhbcTranslator::MInstrTranslator::emitNotSuppNewElem() {
2572 PUNT(NotSuppNewElem);
2575 void HhbcTranslator::MInstrTranslator::emitVGetNewElem() {
2576 SPUNT(__func__);
2579 void HhbcTranslator::MInstrTranslator::emitSetNewElem() {
2580 SSATmp* value = getValue();
2581 if (m_base->type() <= Type::PtrToArr) {
2582 constrainBase(DataTypeSpecific);
2583 gen(SetNewElemArray, makeCatchSet(), m_base, value);
2584 } else {
2585 gen(SetNewElem, makeCatchSet(), m_base, value);
2587 m_result = value;
2590 void HhbcTranslator::MInstrTranslator::emitSetOpNewElem() {
2591 SPUNT(__func__);
2594 void HhbcTranslator::MInstrTranslator::emitIncDecNewElem() {
2595 SPUNT(__func__);
2598 void HhbcTranslator::MInstrTranslator::emitBindNewElem() {
2599 SSATmp* box = getValue();
2600 genStk(BindNewElem, makeCatch(), m_base, box, genMisPtr());
2601 m_result = box;
2604 void HhbcTranslator::MInstrTranslator::emitMPost() {
2605 SSATmp* catchSp = nullptr;
2606 if (m_failedSetBlock) {
2607 auto* endCatch = &m_failedSetBlock->back();
2608 assert(endCatch->is(EndCatch, TryEndCatch));
2609 catchSp = endCatch->src(1);
2610 assert(catchSp->isA(Type::StkPtr));
2613 // Decref stack inputs. If we're translating a SetM or BindM, then input 0 is
2614 // both our input and output so leave its refcount alone. If m_failedSetBlock
2615 // is non-null, the final helper call may throw an InvalidSetMException. We
2616 // need to add instructions to m_failedSetBlock to finish the vector
2617 // instruction in case this happens, so any DecRefs emitted here are also
2618 // added to m_failedSetBlock.
2619 unsigned nStack =
2620 (m_ni.mInstrOp() == OpSetM || m_ni.mInstrOp() == OpBindM) ? 1 : 0;
2621 for (unsigned i = nStack; i < m_ni.inputs.size(); ++i) {
2622 const DynLocation& input = *m_ni.inputs[i];
2623 switch (input.location.space) {
2624 case Location::Stack: {
2625 ++nStack;
2626 auto input = getInput(i, DataTypeSpecific);
2627 if (input->isA(Type::Gen)) {
2628 gen(DecRef, input);
2629 if (m_failedSetBlock) {
2630 BlockPusher bp(m_irb, m_marker, m_failedSetBlock);
2631 gen(DecRefStack, StackOffset(m_stackInputs[i]), Type::Gen, catchSp);
2634 break;
2636 case Location::Local:
2637 case Location::Litstr:
2638 case Location::Litint:
2639 case Location::This: {
2640 // Do nothing.
2641 break;
2644 default: not_reached();
2648 // Pop off all stack inputs
2649 m_ht.discard(nStack);
2651 // Push result, if one was produced. If we have a predicted result use that
2652 // instead of the real result; its validity will be guarded later in this
2653 // function.
2654 if (m_result) {
2655 m_ht.push(m_result);
2656 } else {
2657 assert(m_ni.mInstrOp() == OpUnsetM ||
2658 m_ni.mInstrOp() == OpSetWithRefLM ||
2659 m_ni.mInstrOp() == OpSetWithRefRM);
2662 // Clean up tvRef(2): during exception handling any objects required only
2663 // during vector expansion need to be DecRef'd. There may be either one
2664 // or two such scratch objects, in the case of a Set the first of which will
2665 // always be tvRef2, in all other cases if only one scratch value is present
2666 // it will be stored in tvRef.
2667 static const size_t refOffs[] = { MISOFF(tvRef), MISOFF(tvRef2) };
2668 for (unsigned i = 0; i < std::min(nLogicalRatchets(), 2U); ++i) {
2669 IRInstruction* inst = m_unit.gen(DecRefMem, m_marker, Type::Gen, m_misBase,
2670 cns(refOffs[m_failedSetBlock ? 1 - i : i]));
2671 m_irb.add(inst);
2672 prependToTraces(inst);
2675 emitSideExits(catchSp, nStack);
2678 void HhbcTranslator::MInstrTranslator::emitSideExits(SSATmp* catchSp,
2679 int nStack) {
2680 auto const nextOff = m_ht.nextBcOff();
2681 auto const op = m_ni.mInstrOp();
2682 const bool isSetWithRef = op == OpSetWithRefLM || op == OpSetWithRefRM;
2684 if (m_failedSetBlock) {
2685 assert(bool(m_result) ^ isSetWithRef);
2686 // We need to emit code to clean up and side exit if the TryEndCatch falls
2687 // through because of an InvalidSetMException. If we're translating a
2688 // SetWithRef* bytecode we don't have to do anything special to the stack
2689 // since they have no stack output. Otherwise we need to pop our input
2690 // value and push the value from the exception to the stack (done with a
2691 // DecRefStack followed by a SpillStack).
2693 std::vector<SSATmp*> args{
2694 catchSp, // sp from the previous SpillStack
2695 cns(nStack), // cells popped since the last SpillStack
2698 BlockPusher bp(m_irb, m_marker, m_failedSetBlock);
2699 if (!isSetWithRef) {
2700 gen(DecRefStack, StackOffset(0), Type::Cell, catchSp);
2701 args.push_back(m_ht.gen(LdUnwinderValue, Type::Cell));
2704 SSATmp* sp = gen(SpillStack, std::make_pair(args.size(), &args[0]));
2705 gen(DeleteUnwinderException);
2706 gen(SyncABIRegs, m_irb.fp(), sp);
2707 gen(ReqBindJmp, ReqBindJmpData(nextOff));
2710 if (m_strTestResult) {
2711 assert(!isSetWithRef);
2712 // We expected SetElem's base to not be a Str but might be wrong. Make an
2713 // exit trace to side exit to the next instruction, replacing our guess
2714 // with the correct stack output.
2716 auto toSpill = m_ht.peekSpillValues();
2717 assert(toSpill.size());
2718 assert(toSpill.back() == m_result);
2719 SSATmp* str = m_unit.gen(AssertNonNull, m_marker, m_strTestResult)->dst();
2720 toSpill.back() = str;
2722 auto exit = m_ht.makeExit(nextOff, toSpill);
2724 BlockPusher tp(m_irb, m_marker, exit, exit->skipHeader());
2725 gen(IncStat, cns(Stats::TC_SetMStrGuess_Miss), cns(1), cns(false));
2726 gen(DecRef, m_result);
2727 m_irb.add(str->inst());
2729 gen(CheckNullptr, exit, m_strTestResult);
2730 gen(IncStat, cns(Stats::TC_SetMStrGuess_Hit), cns(1), cns(false));
2734 Block* HhbcTranslator::MInstrTranslator::makeEmptyCatch() {
2735 return m_ht.makeCatch();
2738 Block* HhbcTranslator::MInstrTranslator::makeCatch() {
2739 auto b = makeEmptyCatch();
2740 m_failedVec.push_back(b);
2741 return b;
2744 Block* HhbcTranslator::MInstrTranslator::makeCatchSet() {
2745 assert(!m_failedSetBlock);
2746 m_failedSetBlock = makeCatch();
2748 // This catch trace will be modified in emitMPost to end with a side
2749 // exit, and TryEndCatch will fall through to that side exit if an
2750 // InvalidSetMException is thrown.
2751 m_failedSetBlock->back().setOpcode(TryEndCatch);
2752 return m_failedSetBlock;
2755 void HhbcTranslator::MInstrTranslator::prependToTraces(IRInstruction* inst) {
2756 for (auto b : m_failedVec) {
2757 b->prepend(m_unit.cloneInstruction(inst));
2761 bool HhbcTranslator::MInstrTranslator::needFirstRatchet() const {
2762 if (m_ni.inputs[m_mii.valCount()]->rtt.unbox() <= Type::Arr) {
2763 switch (m_ni.immVecM[0]) {
2764 case MEC: case MEL: case MET: case MEI: return false;
2765 case MPC: case MPL: case MPT: case MW: return true;
2766 default: not_reached();
2769 return true;
2772 bool HhbcTranslator::MInstrTranslator::needFinalRatchet() const {
2773 return m_mii.finalGet();
2776 // Ratchet operations occur after each intermediate operation, except
2777 // possibly the first and last (see need{First,Final}Ratchet()). No actual
2778 // ratchet occurs after the final operation, but this means that both tvRef
2779 // and tvRef2 can contain references just after the final operation. Here we
2780 // pretend that a ratchet occurs after the final operation, i.e. a "logical"
2781 // ratchet. The reason for counting logical ratchets as part of the total is
2782 // the following case, in which the logical count is 0:
2784 // (base is array)
2785 // BaseL
2786 // IssetElemL
2787 // no logical ratchet
2789 // Following are a few more examples to make the algorithm clear:
2791 // (base is array) (base is object) (base is object)
2792 // BaseL BaseL BaseL
2793 // ElemL ElemL CGetPropL
2794 // no ratchet ratchet logical ratchet
2795 // ElemL PropL
2796 // ratchet ratchet
2797 // ElemL CGetElemL
2798 // ratchet logical ratchet
2799 // IssetElemL
2800 // logical ratchet
2802 // (base is array)
2803 // BaseL
2804 // ElemL
2805 // no ratchet
2806 // ElemL
2807 // ratchet
2808 // ElemL
2809 // logical ratchet
2810 // SetElemL
2811 // no ratchet
2812 unsigned HhbcTranslator::MInstrTranslator::nLogicalRatchets() const {
2813 // If we've proven elsewhere that we don't need an MInstrState struct, we
2814 // know this translation won't need any ratchets
2815 if (!m_needMIS) return 0;
2817 unsigned ratchets = m_ni.immVecM.size();
2818 if (!needFirstRatchet()) --ratchets;
2819 if (!needFinalRatchet()) --ratchets;
2820 return ratchets;
2823 int HhbcTranslator::MInstrTranslator::ratchetInd() const {
2824 return needFirstRatchet() ? int(m_mInd) : int(m_mInd) - 1;