2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-2013 Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
16 #include "hphp/runtime/vm/jit/hhbc-translator.h"
18 #include "folly/CpuId.h"
19 #include "folly/Optional.h"
21 #include "hphp/util/trace.h"
22 #include "hphp/runtime/ext/ext_closure.h"
23 #include "hphp/runtime/ext/ext_continuation.h"
24 #include "hphp/runtime/ext/asio/wait_handle.h"
25 #include "hphp/runtime/base/stats.h"
26 #include "hphp/runtime/vm/repo.h"
27 #include "hphp/runtime/vm/unit.h"
28 #include "hphp/runtime/vm/instance-bits.h"
29 #include "hphp/runtime/vm/runtime.h"
30 #include "hphp/runtime/vm/jit/code-gen-helpers.h"
31 #include "hphp/runtime/vm/jit/ir-unit.h"
32 #include "hphp/runtime/vm/jit/normalized-instruction.h"
33 #include "hphp/runtime/vm/jit/translator-inline.h"
34 #include "hphp/runtime/vm/jit/translator-x64.h"
36 // Include last to localize effects to this file
37 #include "hphp/util/assert-throw.h"
45 //////////////////////////////////////////////////////////////////////
49 bool classIsUnique(const Class
* cls
) {
50 return RuntimeOption::RepoAuthoritative
&&
52 (cls
->attrs() & AttrUnique
);
55 bool classIsUniqueNormalClass(const Class
* cls
) {
56 return classIsUnique(cls
) &&
57 !(cls
->attrs() & (AttrInterface
| AttrTrait
));
60 bool classIsUniqueInterface(const Class
* cls
) {
61 return classIsUnique(cls
) &&
62 (cls
->attrs() & AttrInterface
);
67 //////////////////////////////////////////////////////////////////////
69 HhbcTranslator::HhbcTranslator(Offset startOffset
,
70 uint32_t initialSpOffsetFromFp
,
73 , m_tb(new TraceBuilder(startOffset
,
74 initialSpOffsetFromFp
,
77 , m_bcStateStack
{BcState(startOffset
, func
)}
78 , m_startBcOff(startOffset
)
85 auto const fp
= gen(DefFP
);
86 gen(DefSP
, StackOffset(initialSpOffsetFromFp
), fp
);
89 bool HhbcTranslator::classIsUniqueOrCtxParent(const Class
* cls
) const {
90 if (!cls
) return false;
91 if (classIsUnique(cls
)) return true;
92 if (!curClass()) return false;
93 return curClass()->classof(cls
);
96 bool HhbcTranslator::classIsPersistentOrCtxParent(const Class
* cls
) const {
97 if (!cls
) return false;
98 if (classHasPersistentRDS(cls
)) return true;
99 if (!curClass()) return false;
100 return curClass()->classof(cls
);
103 ArrayData
* HhbcTranslator::lookupArrayId(int arrId
) {
104 return curUnit()->lookupArrayId(arrId
);
107 StringData
* HhbcTranslator::lookupStringId(int strId
) {
108 return curUnit()->lookupLitstrId(strId
);
111 Func
* HhbcTranslator::lookupFuncId(int funcId
) {
112 return curUnit()->lookupFuncId(funcId
);
115 PreClass
* HhbcTranslator::lookupPreClassId(int preClassId
) {
116 return curUnit()->lookupPreClassId(preClassId
);
119 const NamedEntityPair
& HhbcTranslator::lookupNamedEntityPairId(int id
) {
120 return curUnit()->lookupNamedEntityPairId(id
);
123 const NamedEntity
* HhbcTranslator::lookupNamedEntityId(int id
) {
124 return curUnit()->lookupNamedEntityId(id
);
127 SSATmp
* HhbcTranslator::push(SSATmp
* tmp
) {
129 FTRACE(2, "HhbcTranslator pushing {}\n", *tmp
->inst());
130 m_evalStack
.push(tmp
);
134 void HhbcTranslator::refineType(SSATmp
* tmp
, Type type
) {
135 // If type is more refined than tmp's type, reset tmp's type to type
136 IRInstruction
* inst
= tmp
->inst();
137 if (type
.strictSubtypeOf(tmp
->type())) {
138 // If tmp is incref or move, then chase down its src
139 Opcode opc
= inst
->op();
141 refineType(inst
->src(0), type
);
142 tmp
->setType(outputType(inst
));
143 } else if (tmp
->type().isNull() && type
.isNull()) {
144 // Refining Null to Uninit or InitNull is supported
146 } else if (tmp
->type().isArray() && type
.isArray()) {
150 // At this point, we have no business refining the type of any
151 // instructions other than the following, which all control
152 // their destination type via a type parameter.
154 // FIXME: I think most of these shouldn't be possible still
155 // (except LdStack?).
156 assert(opc
== LdLoc
|| opc
== LdStack
||
157 opc
== LdMem
|| opc
== LdProp
||
159 inst
->setTypeParam(type
);
161 assert(outputType(inst
) == type
);
166 SSATmp
* HhbcTranslator::pop(Type type
, TypeConstraint tc
) {
167 SSATmp
* opnd
= m_evalStack
.pop(tc
);
169 if (opnd
== nullptr) {
170 uint32_t stackOff
= m_stackDeficit
;
172 m_tb
->constrainStack(stackOff
, tc
);
173 auto value
= gen(LdStack
, type
, StackOffset(stackOff
), m_tb
->sp());
174 FTRACE(2, "HhbcTranslator popping {}\n", *value
->inst());
178 // Refine the type of the temp given the information we have from
179 // `type'. This case can occur if we did an extendStack() and
180 // didn't know the type of the intermediate values yet (see below).
181 refineType(opnd
, type
);
182 FTRACE(2, "HhbcTranslator popping {}\n", *opnd
->inst());
186 void HhbcTranslator::discard(unsigned n
) {
187 for (unsigned i
= 0; i
< n
; ++i
) {
188 pop(Type::StackElem
, DataTypeGeneric
); // don't care about the values
192 // type is the type expected on the stack.
193 void HhbcTranslator::popDecRef(Type type
, TypeConstraint tc
) {
194 if (SSATmp
* src
= m_evalStack
.pop(tc
)) {
199 m_tb
->constrainStack(m_stackDeficit
, tc
);
200 gen(DecRefStack
, StackOffset(m_stackDeficit
), type
, m_tb
->sp());
204 // We don't know what type description to expect for the stack
205 // locations before index, so we use a generic type when popping the
206 // intermediate values. If it ends up creating a new LdStack,
207 // refineType during a later pop() or top() will fix up the type to
209 void HhbcTranslator::extendStack(uint32_t index
, Type type
) {
210 // DataTypeGeneric is used in here because nobody's actually looking at the
211 // values, we're just inserting LdStacks into m_evalStack to be consumed
214 push(pop(type
, DataTypeGeneric
));
218 SSATmp
* tmp
= pop(Type::StackElem
, DataTypeGeneric
);
219 extendStack(index
- 1, type
);
223 SSATmp
* HhbcTranslator::top(Type type
, uint32_t index
,
224 TypeConstraint constraint
) {
225 SSATmp
* tmp
= m_evalStack
.top(constraint
, index
);
227 extendStack(index
, type
);
228 tmp
= m_evalStack
.top(constraint
, index
);
231 refineType(tmp
, type
);
235 void HhbcTranslator::replace(uint32_t index
, SSATmp
* tmp
) {
236 m_evalStack
.replace(index
, tmp
);
239 Type
HhbcTranslator::topType(uint32_t idx
, TypeConstraint constraint
) const {
240 FTRACE(5, "Asking for type of stack elem {}\n", idx
);
241 if (idx
< m_evalStack
.size()) {
242 return m_evalStack
.top(constraint
, idx
)->type();
244 auto absIdx
= idx
- m_evalStack
.size() + m_stackDeficit
;
245 auto stkVal
= getStackValue(m_tb
->sp(), absIdx
);
246 m_tb
->constrainStack(absIdx
, constraint
);
247 return stkVal
.knownType
;
251 size_t HhbcTranslator::spOffset() const {
252 return m_tb
->spOffset() + m_evalStack
.size() - m_stackDeficit
;
256 * When doing gen-time inlining, we set up a series of IR instructions
257 * that looks like this:
260 * sp0 = DefSP<offset>
262 * // ... normal stuff happens ...
263 * // sp_pre = some SpillStack, or maybe the DefSP
266 * [ StashGeneratorSP fp0 sp0 ]
267 * sp1 = SpillStack sp_pre, ...
268 * sp2 = SpillFrame sp1, ...
269 * // ... possibly more spillstacks due to argument expressions
270 * sp3 = SpillStack sp2, -argCount
271 * fp2 = DefInlineFP<func,retBC,retSP> sp2 sp1
272 * sp4 = DefInlineSP<numLocals> sp1 fp2
274 * // ... callee body ...
278 * [ sp5 = ReDefGeneratorSP<spansCall> sp1 fp0 ]
279 * [ sp5 = ReDefSP<frameOffset,spOffset,spansCall> sp1 fp0 ]
281 * The rest of the code then depends on sp5, and not any of the StkPtr
282 * tree going through the callee body. The sp5 tmp has the same view
283 * of the stack as sp1 did, which represents what the stack looks like
284 * before the return address is pushed but after the activation record
287 * In DCE we attempt to remove the SpillFrame/InlineReturn/DefInlineFP/
288 * DefInlineSP instructions if they aren't needed. DefInlineSP and DefInlineFP
289 * become PassSP and PassFP respectively to avoid the need to relabel inlined
290 * IR instructions that refer to them.
292 * In the case of generators StashGeneratorSP and ReDefGeneratorSP are used to
293 * store/extract the value of the StkPtr from a field in the continuation class.
294 * This behavior is important because the StkPtr cannot be computed from the
295 * FramePtr and if a call occurs it cannot live across the FCall in a register.
297 * ReDefSP and ReDefGeneratorSP both take sp1, the stack pointer from before the
298 * inlined frame. While this SSATmp may be dead if an FCall occurs in the
299 * inlined frame it is still useful for determining stack types in the
300 * simplifier. Additionally these instructions both take an extradata
301 * `spansCall' which is true iff an FCall occurs anywhere between the start and
302 * end of the inlined function. This is information is also used in the
303 * simplifier to determine when an SSATmp may be used in lieu of a load from
306 * At this time StLoc, ReDefSP, DefInlineSP, PassSP, SpillFrame, and DefInlineFP
307 * are all considered weak references to a frame pointer. Additionally any
308 * instruction which calls native or may raise an error is considered a
309 * reference to the FP and prevent it from being elided. This is done by
310 * inserting an InlineFPAnchor instruction when they are encountered. These
311 * instructions are inserted initially by trace-builder and later removed and
312 * re-inserted during the reoptimize pass to ensure that they are not associated
313 * with instructions that have been removed in DCE or modified in the
316 void HhbcTranslator::beginInlining(unsigned numParams
,
318 Offset returnBcOffset
) {
319 assert(!m_fpiStack
.empty() &&
320 "Inlining does not support calls with the FPush* in a different Tracelet");
321 assert(!target
->isGenerator() && "Generator stack handling not implemented");
322 assert(returnBcOffset
>= 0 && "returnBcOffset before beginning of caller");
323 assert(curFunc()->base() + returnBcOffset
< curFunc()->past() &&
324 "returnBcOffset past end of caller");
326 FTRACE(1, "[[[ begin inlining: {}\n", target
->fullName()->data());
328 SSATmp
* params
[numParams
];
329 for (unsigned i
= 0; i
< numParams
; ++i
) {
330 params
[numParams
- i
- 1] = popF();
333 auto const prevSP
= m_fpiStack
.top().first
;
334 auto const prevSPOff
= m_fpiStack
.top().second
;
335 auto const calleeSP
= spillStack();
337 DefInlineFPData data
;
338 data
.target
= target
;
339 data
.retBCOff
= returnBcOffset
;
340 data
.retSPOff
= prevSPOff
;
342 // Push state and update the marker before emitting any instructions so
343 // they're all given markers in the callee.
344 m_bcStateStack
.emplace_back(target
->base(), target
);
347 auto const calleeFP
= gen(DefInlineFP
, data
, calleeSP
, prevSP
, m_tb
->fp());
348 gen(DefInlineSP
, StackOffset(target
->numLocals()), m_tb
->sp(), m_tb
->fp());
350 profileFunctionEntry("Inline");
352 for (unsigned i
= 0; i
< numParams
; ++i
) {
353 gen(StLoc
, LocalId(i
), calleeFP
, params
[i
]);
355 for (unsigned i
= numParams
; i
< target
->numLocals(); ++i
) {
357 * Here we need to be generating hopefully-dead stores to
358 * initialize non-parameter locals to KindOfUninit in case we have
359 * to leave the trace.
361 gen(StLoc
, LocalId(i
), calleeFP
, m_tb
->genDefUninit());
364 m_fpiActiveStack
.push(std::move(m_fpiStack
.top()));
368 bool HhbcTranslator::isInlining() const {
369 return m_bcStateStack
.size() > 1;
372 int HhbcTranslator::inliningDepth() const {
373 return m_bcStateStack
.size() - 1;
376 BCMarker
HhbcTranslator::makeMarker(Offset bcOff
) {
377 int32_t stackOff
= m_tb
->spOffset() +
378 m_evalStack
.numCells() - m_stackDeficit
;
380 FTRACE(2, "makeMarker: bc {} sp {} fn {}\n",
381 bcOff
, stackOff
, curFunc()->fullName()->data());
383 return BCMarker
{ curFunc(), bcOff
, stackOff
};
386 void HhbcTranslator::updateMarker() {
387 m_tb
->setMarker(makeMarker(bcOff()));
390 void HhbcTranslator::profileFunctionEntry(const char* category
) {
391 static const bool enabled
= Stats::enabledAny() &&
392 getenv("HHVM_STATS_FUNCENTRY");
393 if (!enabled
) return;
397 cns(makeStaticString("FunctionEntry")),
398 cns(makeStaticString(category
)),
403 void HhbcTranslator::profileInlineFunctionShape(const std::string
& str
) {
406 cns(makeStaticString("InlineShape")),
407 cns(makeStaticString(str
)),
412 void HhbcTranslator::profileSmallFunctionShape(const std::string
& str
) {
415 cns(makeStaticString("SmallFunctions")),
416 cns(makeStaticString(str
)),
421 void HhbcTranslator::profileFailedInlShape(const std::string
& str
) {
424 cns(makeStaticString("FailedInl")),
425 cns(makeStaticString(str
)),
430 void HhbcTranslator::setBcOff(Offset newOff
, bool lastBcOff
) {
431 if (isInlining()) assert(!lastBcOff
);
433 m_bcStateStack
.back().bcOff
= newOff
;
435 m_lastBcOff
= lastBcOff
;
438 void HhbcTranslator::emitPrint() {
439 Type type
= topC()->type();
440 if (type
.subtypeOfAny(Type::Int
, Type::Bool
, Type::Null
, Type::Str
)) {
441 auto const cell
= popC();
444 if (type
.isString()) {
446 } else if (type
<= Type::Int
) {
448 } else if (type
<= Type::Bool
) {
451 assert(type
.isNull());
454 // the print helpers decref their arg, so don't decref pop'ed value
460 emitInterpOne(Type::Int
, 1);
464 void HhbcTranslator::emitUnboxRAux() {
465 Block
* exit
= makeExit();
466 SSATmp
* srcBox
= popR();
467 SSATmp
* unboxed
= gen(Unbox
, exit
, srcBox
);
468 if (unboxed
== srcBox
) {
469 // If the Unbox ended up being a noop, don't bother refcounting
477 void HhbcTranslator::emitUnboxR() {
481 void HhbcTranslator::emitThis() {
483 emitInterpOne(Type::Obj
, 0); // will throw a fatal
486 pushIncRef(gen(LdThis
, makeExitSlow(), m_tb
->fp()));
489 void HhbcTranslator::emitCheckThis() {
491 emitInterpOne(Type::None
, 0); // will throw a fatal
494 gen(LdThis
, makeExitSlow(), m_tb
->fp());
497 void HhbcTranslator::emitRB(Trace::RingBufferType t
, SrcKey sk
, int level
) {
498 if (!Trace::moduleEnabledRelease(Trace::ringbuffer
, level
)) return;
500 gen(RBTrace
, RBTraceData(t
, sk
));
503 void HhbcTranslator::emitRB(Trace::RingBufferType t
, const StringData
* msg
,
505 if (!Trace::moduleEnabledRelease(Trace::ringbuffer
, level
)) return;
507 gen(RBTrace
, RBTraceData(t
, msg
));
510 void HhbcTranslator::emitDbgAssertRetAddr() {
511 gen(DbgAssertRetAddr
);
514 void HhbcTranslator::emitBareThis(int notice
) {
515 // We just exit the trace in the case $this is null. Before exiting
516 // the trace, we could also push null onto the stack and raise a
517 // notice if the notice argument is set. By exiting the trace when
518 // $this is null, we can be sure in the rest of the trace that we
519 // have the this object on top of the stack, and we can eliminate
520 // further null checks of this.
522 emitInterpOne(Type::InitNull
, 0); // will raise notice and push null
525 if (notice
== static_cast<int>(BareThisOp::NeverNull
)) {
528 pushIncRef(gen(LdThis
, makeExitSlow(), m_tb
->fp()));
531 void HhbcTranslator::emitArray(int arrayId
) {
532 push(cns(lookupArrayId(arrayId
)));
535 void HhbcTranslator::emitNewArrayReserve(int capacity
) {
537 push(cns(HphpArray::GetStaticEmptyArray()));
539 push(gen(NewArray
, cns(capacity
)));
543 void HhbcTranslator::emitNewPackedArray(int numArgs
) {
544 // The NewPackedArray opcode's helper needs array values passed to it
545 // via the stack. We use spillStack() to flush the eval stack and
546 // obtain a pointer to the topmost item; if over-flushing becomes
547 // a problem then we should refactor the NewPackedArray opcode to
548 // take its values directly as SSA operands.
550 // Before the spillStack() we touch all of the incoming stack
551 // arguments so that they are available to later optimizations via
553 for (int i
= 0; i
< numArgs
; i
++) topC(i
);
554 SSATmp
* sp
= spillStack();
555 for (int i
= 0; i
< numArgs
; i
++) popC();
556 push(gen(NewPackedArray
, cns(numArgs
), sp
));
559 void HhbcTranslator::emitNewStructArray(uint32_t numArgs
, StringData
** keys
) {
560 // The NewPackedArray opcode's helper needs array values passed to it
561 // via the stack. We use spillStack() to flush the eval stack and
562 // obtain a pointer to the topmost item; if over-flushing becomes
563 // a problem then we should refactor the NewPackedArray opcode to
564 // take its values directly as SSA operands.
565 SSATmp
* sp
= spillStack();
566 for (int i
= 0; i
< numArgs
; i
++) popC();
568 extra
.numKeys
= numArgs
;
569 extra
.keys
= new (m_unit
.arena()) StringData
*[numArgs
];
570 memcpy(extra
.keys
, keys
, numArgs
* sizeof(*keys
));
571 push(gen(NewStructArray
, extra
, sp
));
574 void HhbcTranslator::emitArrayAdd() {
575 auto catchBlock
= makeCatch();
576 Type type1
= topC(0)->type();
577 Type type2
= topC(1)->type();
578 if (!type1
.isArray() || !type2
.isArray()) {
579 // This happens when we have a prior spillstack that optimizes away
580 // its spilled values because they were already on the stack. This
581 // prevents us from getting to type of the SSATmps popped from the
582 // eval stack. Most likely we had an interpone before this
584 emitInterpOne(Type::Arr
, 2);
589 // The ArrayAdd helper decrefs its args, so don't decref pop'ed values.
590 push(gen(ArrayAdd
, catchBlock
, tl
, tr
));
593 void HhbcTranslator::emitAddElemC() {
594 // This is just to peek at the type; it'll be consumed for real down below and
595 // we don't want to constrain it if we're just going to InterpOne.
596 auto kt
= topC(1, DataTypeGeneric
)->type();
598 if (kt
<= Type::Int
) {
600 } else if (kt
.isString()) {
603 emitInterpOne(Type::Arr
, 3);
607 // val is teleported from the stack to the array, so we don't have to do any
609 auto const val
= popC(DataTypeGeneric
);
610 auto const key
= popC();
611 auto const arr
= popC();
612 // The AddElem* instructions decref their args, so don't decref pop'ed
614 push(gen(op
, arr
, key
, val
));
617 void HhbcTranslator::emitAddNewElemC() {
618 if (!topC(1)->isA(Type::Arr
)) {
619 return emitInterpOne(Type::Arr
, 2);
622 auto const val
= popC();
623 auto const arr
= popC();
624 // The AddNewElem helper decrefs its args, so don't decref pop'ed values.
625 push(gen(AddNewElem
, arr
, val
));
628 void HhbcTranslator::emitNewCol(int type
, int size
) {
629 push(gen(NewCol
, cns(type
), cns(size
)));
632 void HhbcTranslator::emitClone() {
633 if (!topC()->isA(Type::Obj
)) PUNT(Clone
-NonObj
);
634 auto const catchTrace
= makeCatch();
635 auto const obj
= popC();
636 push(gen(Clone
, catchTrace
, obj
));
640 void HhbcTranslator::emitColAddElemC() {
641 if (!topC(2)->isA(Type::Obj
)) {
642 return emitInterpOne(Type::Obj
, 3);
644 auto kt
= topC(1, DataTypeGeneric
)->type();
645 if (!(kt
<= Type::Int
) && !kt
.isString()) {
646 emitInterpOne(Type::Obj
, 3);
650 auto* catchBlock
= makeCatch();
651 auto const val
= popC(DataTypeGeneric
);
652 auto const key
= popC();
653 auto const coll
= popC();
654 push(gen(ColAddElemC
, catchBlock
, coll
, key
, val
));
658 void HhbcTranslator::emitColAddNewElemC() {
659 if (!topC(1)->isA(Type::Obj
)) {
660 return emitInterpOne(Type::Obj
, 2);
663 auto* catchBlock
= makeCatch();
664 auto const val
= popC();
665 auto const coll
= popC();
666 // The AddNewElem helper decrefs its args, so don't decref pop'ed values.
667 push(gen(ColAddNewElemC
, catchBlock
, coll
, val
));
670 void HhbcTranslator::emitCnsCommon(uint32_t id
,
673 assert(fallback
== kInvalidId
|| !error
);
674 StringData
* name
= curUnit()->lookupLitstrId(id
);
675 SSATmp
* cnsNameTmp
= cns(name
);
676 const TypedValue
* tv
= Unit::lookupPersistentCns(name
);
677 SSATmp
* result
= nullptr;
679 SSATmp
* fallbackNameTmp
= nullptr;
680 if (fallback
!= kInvalidId
) {
681 StringData
* fallbackName
= curUnit()->lookupLitstrId(fallback
);
682 fallbackNameTmp
= cns(fallbackName
);
685 if (tv
->m_type
== KindOfUninit
) {
686 // KindOfUninit is a dynamic system constant. always a slow
688 assert(!fallbackNameTmp
);
690 result
= gen(LookupCnsE
, cnsNameTmp
);
692 result
= gen(LookupCns
, makeCatch(), cnsNameTmp
);
695 result
= staticTVCns(tv
);
698 SSATmp
* c1
= gen(LdCns
, cnsNameTmp
);
700 [&] (Block
* taken
) { // branch
701 gen(CheckInit
, taken
, c1
);
703 [&] { // Next: LdCns hit in TC
706 [&] { // Taken: miss in TC, do lookup & init
707 m_tb
->hint(Block::Hint::Unlikely
);
708 if (fallbackNameTmp
) {
709 return gen(LookupCnsU
, makeCatch(),
710 cnsNameTmp
, fallbackNameTmp
);
713 return gen(LookupCnsE
, makeCatch(), cnsNameTmp
);
715 return gen(LookupCns
, makeCatch(), cnsNameTmp
);
722 void HhbcTranslator::emitCns(uint32_t id
) {
723 emitCnsCommon(id
, kInvalidId
, false);
726 void HhbcTranslator::emitCnsE(uint32_t id
) {
727 emitCnsCommon(id
, kInvalidId
, true);
730 void HhbcTranslator::emitCnsU(uint32_t id
, uint32_t fallbackId
) {
731 emitCnsCommon(id
, fallbackId
, false);
734 void HhbcTranslator::emitDefCns(uint32_t id
) {
735 emitInterpOne(Type::Bool
, 1);
738 void HhbcTranslator::emitConcat() {
739 auto const catchBlock
= makeCatch();
742 // Concat consumes only first ref, never second
743 push(gen(ConcatCellCell
, catchBlock
, tl
, tr
));
744 // so we need to consume second ref ourselves
748 void HhbcTranslator::emitDefCls(int cid
, Offset after
) {
749 emitInterpOne(Type::None
, 0);
752 void HhbcTranslator::emitDefFunc(int fid
) {
753 emitInterpOne(Type::None
, 0);
756 void HhbcTranslator::emitLateBoundCls() {
757 Class
* clss
= curClass();
759 // no static context class, so this will raise an error
760 emitInterpOne(Type::Cls
, 0);
763 auto const ctx
= gen(LdCtx
, FuncData(curFunc()), m_tb
->fp());
764 push(gen(LdClsCtx
, ctx
));
767 void HhbcTranslator::emitSelf() {
768 Class
* clss
= curClass();
769 if (clss
== nullptr) {
770 emitInterpOne(Type::Cls
, 0);
776 void HhbcTranslator::emitParent() {
777 auto const clss
= curClass();
778 if (clss
== nullptr || clss
->parent() == nullptr) {
779 emitInterpOne(Type::Cls
, 0);
781 push(cns(clss
->parent()));
785 void HhbcTranslator::emitString(int strId
) {
786 push(cns(lookupStringId(strId
)));
789 void HhbcTranslator::emitInt(int64_t val
) {
793 void HhbcTranslator::emitDouble(double val
) {
797 void HhbcTranslator::emitNullUninit() {
798 push(m_tb
->genDefUninit());
801 void HhbcTranslator::emitNull() {
802 push(m_tb
->genDefInitNull());
805 void HhbcTranslator::emitTrue() {
809 void HhbcTranslator::emitFalse() {
813 void HhbcTranslator::emitInitThisLoc(int32_t id
) {
815 // Do nothing if this is null
818 auto const tmpThis
= gen(LdThis
, makeExitSlow(), m_tb
->fp());
819 gen(IncRef
, tmpThis
);
820 gen(StLoc
, LocalId(id
), m_tb
->fp(), tmpThis
);
823 void HhbcTranslator::emitCGetL(int32_t id
) {
824 auto exit
= makeExit();
825 pushIncRef(ldLocInnerWarn(id
, exit
, DataTypeCountnessInit
));
828 void HhbcTranslator::emitPushL(uint32_t id
) {
829 assertTypeLocal(id
, Type::InitCell
);
830 auto* locVal
= ldLoc(id
, DataTypeGeneric
);
832 gen(StLoc
, LocalId(id
), m_tb
->fp(), m_tb
->genDefUninit());
835 void HhbcTranslator::emitCGetL2(int32_t id
) {
836 auto exitBlock
= makeExit();
837 auto catchBlock
= makeCatch();
838 SSATmp
* oldTop
= pop(Type::StackElem
);
839 pushIncRef(ldLocInnerWarn(id
, exitBlock
, DataTypeCountnessInit
, catchBlock
));
843 void HhbcTranslator::emitVGetL(int32_t id
) {
844 auto value
= ldLoc(id
, DataTypeSpecific
);
845 if (!value
->type().isBoxed()) {
846 if (value
->isA(Type::Uninit
)) {
847 value
= m_tb
->genDefInitNull();
849 value
= gen(Box
, value
);
850 gen(StLoc
, LocalId(id
), m_tb
->fp(), value
);
855 void HhbcTranslator::emitUnsetL(int32_t id
) {
856 auto const prev
= ldLoc(id
, DataTypeCountness
);
857 gen(StLoc
, LocalId(id
), m_tb
->fp(), m_tb
->genDefUninit());
861 void HhbcTranslator::emitBindL(int32_t id
) {
862 auto const newValue
= popV();
863 // Note that the IncRef must happen first, for correctness in a
864 // pseudo-main: the destructor could decref the value again after
865 // we've stored it into the local.
866 pushIncRef(newValue
);
867 auto const oldValue
= ldLoc(id
, DataTypeGeneric
);
868 gen(StLoc
, LocalId(id
), m_tb
->fp(), newValue
);
869 gen(DecRef
, oldValue
);
872 void HhbcTranslator::emitSetL(int32_t id
) {
873 auto const exit
= makeExit();
875 // since we're just storing the value in a local, this function doesn't care
876 // about the type of the value. stLoc needs to IncRef the value so it may
877 // constrain it further.
878 auto const src
= popC(DataTypeGeneric
);
879 pushStLoc(id
, exit
, src
);
882 void HhbcTranslator::emitIncDecL(bool pre
, bool inc
, uint32_t id
) {
883 auto const exit
= makeExit();
884 auto const src
= ldLocInnerWarn(id
, exit
, DataTypeSpecific
);
886 if (src
->isA(Type::Bool
)) {
891 if (src
->type().subtypeOfAny(Type::Arr
, Type::Obj
)) {
896 if (src
->isA(Type::Null
)) {
899 stLoc(id
, exit
, cns(1));
906 if (!src
->type().subtypeOfAny(Type::Int
, Type::Dbl
)) {
910 auto const res
= emitIncDec(pre
, inc
, src
);
911 stLoc(id
, exit
, res
);
914 // only handles integer or double inc/dec
915 SSATmp
* HhbcTranslator::emitIncDec(bool pre
, bool inc
, SSATmp
* src
) {
916 assert(src
->isA(Type::Int
) || src
->isA(Type::Dbl
));
917 SSATmp
* one
= src
->isA(Type::Int
) ? cns(1) : cns(1.0);
918 SSATmp
* res
= inc
? gen(Add
, src
, one
) : gen(Sub
, src
, one
);
919 // no incref necessary on push since result is an int
920 push(pre
? res
: src
);
924 static bool areBinaryArithTypesSupported(Opcode opc
, Type t1
, Type t2
) {
928 case Mul
: return t1
.subtypeOfAny(Type::Int
, Type::Bool
, Type::Dbl
) &&
929 t2
.subtypeOfAny(Type::Int
, Type::Bool
, Type::Dbl
);
934 return t1
.subtypeOfAny(Type::Int
, Type::Bool
) &&
935 t2
.subtypeOfAny(Type::Int
, Type::Bool
);
941 void HhbcTranslator::emitSetOpL(Opcode subOpc
, uint32_t id
) {
943 * Handle array addition first because we don't want to bother with
947 (m_tb
->localType(id
, DataTypeSpecific
) <= Type::Arr
) &&
948 topC()->isA(Type::Arr
)) {
950 * ArrayAdd decrefs its sources and returns a new array with
951 * refcount == 1. That covers the local, so incref once more for
954 auto const catchBlock
= makeCatch();
955 auto const loc
= ldLoc(id
, DataTypeSpecific
);
956 auto const val
= popC();
957 auto const result
= gen(ArrayAdd
, catchBlock
, loc
, val
);
958 gen(StLoc
, LocalId(id
), m_tb
->fp(), result
);
963 auto const exitBlock
= makeExit();
964 auto const catchBlock
= makeCatch();
965 auto const loc
= ldLocInnerWarn(id
, exitBlock
, DataTypeSpecific
,
967 if (subOpc
== ConcatCellCell
) {
969 * The concat helpers incref their results, which will be consumed by
970 * the stloc. We need an extra incref for the push onto the stack.
972 auto const val
= popC();
973 auto const result
= gen(ConcatCellCell
, catchBlock
, loc
, val
);
974 pushIncRef(stLocNRC(id
, nullptr, result
));
975 // ConcatCellCell does not DecRef its second argument,
976 // so we need to do it here
981 if (areBinaryArithTypesSupported(subOpc
, loc
->type(), topC()->type())) {
982 auto const val
= popC();
983 auto const result
= gen(
985 loc
->isA(Type::Bool
) ? gen(ConvBoolToInt
, loc
) : loc
,
986 val
->isA(Type::Bool
) ? gen(ConvBoolToInt
, val
) : val
988 pushStLoc(id
, nullptr, result
);
995 void HhbcTranslator::classExistsImpl(ClassKind kind
) {
996 auto const catchTrace
= makeCatch();
997 auto const tAutoload
= topC(0);
998 auto const tCls
= topC(1);
1000 if (!tCls
->isA(Type::Str
) ||
1001 !tAutoload
->isConst() ||
1002 !tAutoload
->isA(Type::Bool
) ||
1003 !tAutoload
->getValBool()) {
1004 return emitInterpOne(Type::Bool
, 2);
1008 gen(ThingExists
, catchTrace
, ClassKindData
{ kind
}, tCls
);
1009 popC(); popC(); push(exists
);
1013 void HhbcTranslator::emitClassExists() {
1014 classExistsImpl(ClassKind::Class
);
1017 void HhbcTranslator::emitInterfaceExists() {
1018 classExistsImpl(ClassKind::Interface
);
1021 void HhbcTranslator::emitTraitExists() {
1022 classExistsImpl(ClassKind::Trait
);
1025 void HhbcTranslator::emitStaticLocInit(uint32_t locId
, uint32_t litStrId
) {
1026 auto const name
= lookupStringId(litStrId
);
1027 auto const value
= popC();
1029 // Closures and generators from closures don't satisfy the "one static per
1030 // source location" rule that the inline fastpath requires
1031 auto const box
= [&]{
1032 if (curFunc()->isClosureBody() || curFunc()->isGeneratorFromClosure()) {
1033 return gen(ClosureStaticLocInit
, cns(name
), m_tb
->fp(), value
);
1036 auto const cachedBox
=
1037 gen(LdStaticLocCached
, StaticLocName
{ curFunc(), name
});
1039 [&] (Block
* taken
) {
1040 gen(CheckStaticLocInit
, taken
, cachedBox
);
1043 m_tb
->hint(Block::Hint::Unlikely
);
1044 gen(StaticLocInitCached
, cachedBox
, value
);
1050 auto const oldValue
= ldLoc(locId
, DataTypeCountness
);
1051 gen(StLoc
, LocalId(locId
), m_tb
->fp(), box
);
1052 gen(DecRef
, oldValue
);
1053 // We don't need to decref value---it's a bytecode invariant that
1054 // our Cell was not ref-counted.
1057 void HhbcTranslator::emitStaticLoc(uint32_t locId
, uint32_t litStrId
) {
1058 auto const name
= lookupStringId(litStrId
);
1060 if (curFunc()->isClosureBody() || curFunc()->isGeneratorFromClosure()) {
1061 auto const box
= gen(
1062 ClosureStaticLocInit
, cns(name
), m_tb
->fp(), m_tb
->genDefNull()
1065 gen(StLoc
, LocalId(locId
), m_tb
->fp(), box
);
1069 auto const box
= gen(LdStaticLocCached
, StaticLocName
{ curFunc(), name
});
1070 auto const res
= m_tb
->cond(
1071 [&] (Block
* taken
) {
1072 gen(CheckStaticLocInit
, taken
, box
);
1074 [&] { // Next: the static local is already initialized
1075 return m_tb
->genLdConst(true);
1077 [&] { // Taken: need to initialize the static local
1079 * Even though this path is "cold", we're not marking it
1080 * unlikely because the size of the instructions this will
1081 * generate is about 10 bytes, which is not much larger than the
1082 * 5 byte jump to astubs would be.
1084 * One note about StaticLoc: we're literally always going to
1085 * generate a fallthrough trace here that is cold (the code that
1086 * initializes the static local). TODO(#2894612).
1088 gen(StaticLocInitCached
, box
, m_tb
->genDefNull());
1089 return m_tb
->genLdConst(false);
1093 auto const oldValue
= ldLoc(locId
, DataTypeCountness
);
1094 gen(StLoc
, LocalId(locId
), m_tb
->fp(), box
);
1095 gen(DecRef
, oldValue
);
1099 template<class Lambda
>
1100 SSATmp
* HhbcTranslator::emitIterInitCommon(int offset
, Lambda genFunc
,
1102 SSATmp
* src
= popC();
1103 Type type
= src
->type();
1104 if (!type
.isArray() && type
!= Type::Obj
) {
1107 SSATmp
* res
= genFunc(src
);
1108 return emitJmpCondHelper(offset
, !invertCond
, res
);
1111 template<class Lambda
>
1112 SSATmp
* HhbcTranslator::emitMIterInitCommon(int offset
, Lambda genFunc
) {
1113 auto exit
= makeExit();
1115 SSATmp
* src
= topV();
1116 Type type
= src
->type();
1118 assert(type
.isBoxed());
1119 gen(LdRef
, type
.innerType(), exit
, src
);
1120 SSATmp
* res
= genFunc(src
);
1121 SSATmp
* out
= popV();
1123 return emitJmpCondHelper(offset
, true, res
);
1127 void constrainIterLocals(TraceBuilder
& tb
) {}
1129 template<typename
... Args
>
1130 void constrainIterLocals(TraceBuilder
& tb
, uint32_t locId
, Args
... args
) {
1131 tb
.constrainLocal(locId
, DataTypeCountness
, "Iter*");
1132 constrainIterLocals(tb
, args
...);
1136 void HhbcTranslator::emitIterInit(uint32_t iterId
,
1138 uint32_t valLocalId
,
1140 constrainIterLocals(*m_tb
, valLocalId
);
1142 auto catchBlock
= makeCatch();
1143 emitIterInitCommon(offset
, [&] (SSATmp
* src
) {
1144 return gen(IterInit
,
1155 void HhbcTranslator::emitIterInitK(uint32_t iterId
,
1157 uint32_t valLocalId
,
1158 uint32_t keyLocalId
,
1160 constrainIterLocals(*m_tb
, valLocalId
, keyLocalId
);
1162 auto catchBlock
= makeCatch();
1163 emitIterInitCommon(offset
, [&] (SSATmp
* src
) {
1164 return gen(IterInitK
,
1176 void HhbcTranslator::emitIterNext(uint32_t iterId
,
1178 uint32_t valLocalId
,
1180 constrainIterLocals(*m_tb
, valLocalId
);
1190 emitJmpCondHelper(offset
, invertCond
, res
);
1193 void HhbcTranslator::emitIterNextK(uint32_t iterId
,
1195 uint32_t valLocalId
,
1196 uint32_t keyLocalId
,
1198 constrainIterLocals(*m_tb
, valLocalId
, keyLocalId
);
1209 emitJmpCondHelper(offset
, invertCond
, res
);
1212 void HhbcTranslator::emitWIterInit(uint32_t iterId
,
1214 uint32_t valLocalId
,
1216 constrainIterLocals(*m_tb
, valLocalId
);
1218 auto catchBlock
= makeCatch();
1220 offset
, [&] (SSATmp
* src
) {
1221 return gen(WIterInit
,
1232 void HhbcTranslator::emitWIterInitK(uint32_t iterId
,
1234 uint32_t valLocalId
,
1235 uint32_t keyLocalId
,
1237 constrainIterLocals(*m_tb
, valLocalId
, keyLocalId
);
1239 auto catchBlock
= makeCatch();
1241 offset
, [&] (SSATmp
* src
) {
1242 return gen(WIterInitK
,
1254 void HhbcTranslator::emitWIterNext(uint32_t iterId
,
1256 uint32_t valLocalId
,
1258 constrainIterLocals(*m_tb
, valLocalId
);
1268 emitJmpCondHelper(offset
, invertCond
, res
);
1271 void HhbcTranslator::emitWIterNextK(uint32_t iterId
,
1273 uint32_t valLocalId
,
1274 uint32_t keyLocalId
,
1276 constrainIterLocals(*m_tb
, valLocalId
, keyLocalId
);
1287 emitJmpCondHelper(offset
, invertCond
, res
);
1290 void HhbcTranslator::emitMIterInit(uint32_t iterId
,
1292 uint32_t valLocalId
) {
1293 constrainIterLocals(*m_tb
, valLocalId
);
1295 auto catchBlock
= makeCatch();
1296 emitMIterInitCommon(offset
, [&] (SSATmp
* src
) {
1309 void HhbcTranslator::emitMIterInitK(uint32_t iterId
,
1311 uint32_t valLocalId
,
1312 uint32_t keyLocalId
) {
1313 constrainIterLocals(*m_tb
, valLocalId
, keyLocalId
);
1315 auto catchBlock
= makeCatch();
1316 emitMIterInitCommon(offset
, [&] (SSATmp
* src
) {
1330 void HhbcTranslator::emitMIterNext(uint32_t iterId
,
1332 uint32_t valLocalId
) {
1333 constrainIterLocals(*m_tb
, valLocalId
);
1342 emitJmpCondHelper(offset
, false, res
);
1345 void HhbcTranslator::emitMIterNextK(uint32_t iterId
,
1347 uint32_t valLocalId
,
1348 uint32_t keyLocalId
) {
1349 constrainIterLocals(*m_tb
, valLocalId
, keyLocalId
);
1359 emitJmpCondHelper(offset
, false, res
);
1362 void HhbcTranslator::emitIterFree(uint32_t iterId
) {
1363 gen(IterFree
, IterId(iterId
), m_tb
->fp());
1366 void HhbcTranslator::emitMIterFree(uint32_t iterId
) {
1367 gen(MIterFree
, IterId(iterId
), m_tb
->fp());
1370 void HhbcTranslator::emitDecodeCufIter(uint32_t iterId
, int offset
) {
1371 SSATmp
* src
= popC();
1372 Type type
= src
->type();
1373 if (type
.subtypeOfAny(Type::Arr
, Type::Str
, Type::Obj
)) {
1374 SSATmp
* res
= gen(DecodeCufIter
, Type::Bool
,
1375 IterId(iterId
), src
, m_tb
->fp());
1377 emitJmpCondHelper(offset
, true, res
);
1380 emitJmp(offset
, true, false);
1384 void HhbcTranslator::emitCIterFree(uint32_t iterId
) {
1385 gen(CIterFree
, IterId(iterId
), m_tb
->fp());
1388 void HhbcTranslator::emitIterBreak(const ImmVector
& iv
,
1390 bool breakTracelet
) {
1392 for (iterIndex
= 0; iterIndex
< iv
.size(); iterIndex
+= 2) {
1393 IterKind iterKind
= (IterKind
)iv
.vec32()[iterIndex
];
1394 Id iterId
= iv
.vec32()[iterIndex
+ 1];
1396 case KindOfIter
: gen(IterFree
, IterId(iterId
), m_tb
->fp()); break;
1397 case KindOfMIter
: gen(MIterFree
, IterId(iterId
), m_tb
->fp()); break;
1398 case KindOfCIter
: gen(CIterFree
, IterId(iterId
), m_tb
->fp()); break;
1402 if (!breakTracelet
) return;
1403 gen(Jmp
, makeExit(offset
));
1406 void HhbcTranslator::emitCreateCont() {
1407 gen(ExitOnVarEnv
, makeExitSlow(), m_tb
->fp());
1409 auto const origFunc
= curFunc();
1410 auto const genFunc
= origFunc
->getGeneratorBody();
1412 auto const cont
= origFunc
->isMethod()
1415 CreateContData
{ genFunc
},
1416 gen(LdCtx
, FuncData(curFunc()), m_tb
->fp())
1420 CreateContData
{ genFunc
}
1423 static auto const thisStr
= makeStaticString("this");
1424 Id thisId
= kInvalidId
;
1425 const bool fillThis
= origFunc
->isMethod() &&
1426 !origFunc
->isStatic() &&
1427 ((thisId
= genFunc
->lookupVarId(thisStr
)) != kInvalidId
) &&
1428 (origFunc
->lookupVarId(thisStr
) == kInvalidId
);
1430 SSATmp
* contAR
= gen(LdContActRec
, Type::PtrToGen
, cont
);
1431 for (int i
= 0; i
< origFunc
->numNamedLocals(); ++i
) {
1432 assert(i
== genFunc
->lookupVarId(origFunc
->localVarName(i
)));
1433 // Copy the value of the local to the cont object and set the local to
1434 // uninit so that we don't need to change refcounts. We pass
1435 // DataTypeGeneric to ldLoc because we're just teleporting the value.
1436 gen(StMem
, contAR
, cns(-cellsToBytes(i
+ 1)),
1437 ldLoc(i
, DataTypeGeneric
));
1438 gen(StLoc
, LocalId(i
), m_tb
->fp(), m_tb
->genDefUninit());
1441 assert(thisId
!= kInvalidId
);
1442 auto const thisObj
= gen(LdThis
, m_tb
->fp());
1443 gen(IncRef
, thisObj
);
1444 gen(StMem
, contAR
, cns(-cellsToBytes(thisId
+ 1)), thisObj
);
1450 void HhbcTranslator::emitContEnter(int32_t returnBcOffset
) {
1451 // make sure the value to be sent is on the actual stack
1455 SSATmp
* cont
= gen(LdThis
, m_tb
->fp());
1456 SSATmp
* contAR
= gen(LdContActRec
, Type::FramePtr
, cont
);
1458 SSATmp
* funcBody
= gen(
1459 LdRaw
, Type::TCA
, cont
, cns(RawMemSlot::ContEntry
)
1462 // The top of the stack will be consumed by the callee, so discard
1463 // it without decreffing.
1464 popC(DataTypeGeneric
);
1470 cns(returnBcOffset
),
1475 void HhbcTranslator::emitContReturnControl() {
1476 auto const sp
= spillStack();
1477 emitRetSurpriseCheck(m_tb
->genDefNull(), true);
1479 auto const retAddr
= gen(LdRetAddr
, m_tb
->fp());
1480 auto const fp
= gen(FreeActRec
, m_tb
->fp());
1482 gen(RetCtrl
, InGeneratorData(true), sp
, fp
, retAddr
);
1486 void HhbcTranslator::emitUnpackCont() {
1487 push(gen(LdContArRaw
, Type::Int
, m_tb
->fp(), cns(RawMemSlot::ContLabel
)));
1490 void HhbcTranslator::emitContSuspendImpl(int64_t labelId
) {
1491 // set m_value = popC();
1492 auto const oldValue
= gen(LdContArValue
, Type::Cell
, m_tb
->fp());
1493 gen(StContArValue
, m_tb
->fp(), popC(DataTypeGeneric
)); // teleporting value
1494 gen(DecRef
, oldValue
);
1496 // set m_label = labelId;
1497 gen(StContArRaw
, m_tb
->fp(), cns(RawMemSlot::ContLabel
), cns(labelId
));
1500 void HhbcTranslator::emitContSuspend(int64_t labelId
) {
1501 emitContSuspendImpl(labelId
);
1503 // take a fast path if this generator has no yield k => v;
1504 if (curFunc()->isPairGenerator()) {
1505 // this needs optimization
1506 auto const idx
= gen(LdContArRaw
, Type::Int
,
1507 m_tb
->fp(), cns(RawMemSlot::ContIndex
));
1508 auto const newIdx
= gen(Add
, idx
, cns(1));
1509 gen(StContArRaw
, m_tb
->fp(), cns(RawMemSlot::ContIndex
), newIdx
);
1511 auto const oldKey
= gen(LdContArKey
, Type::Cell
, m_tb
->fp());
1512 gen(StContArKey
, m_tb
->fp(), newIdx
);
1513 gen(DecRef
, oldKey
);
1515 // we're guaranteed that the key is an int
1516 gen(ContArIncKey
, m_tb
->fp());
1520 emitContReturnControl();
1523 void HhbcTranslator::emitContSuspendK(int64_t labelId
) {
1524 emitContSuspendImpl(labelId
);
1526 auto const newKey
= popC();
1527 auto const oldKey
= gen(LdContArKey
, Type::Cell
, m_tb
->fp());
1528 gen(StContArKey
, m_tb
->fp(), newKey
);
1529 gen(DecRef
, oldKey
);
1531 auto const keyType
= newKey
->type();
1532 if (keyType
<= Type::Int
) {
1533 gen(ContArUpdateIdx
, m_tb
->fp(), newKey
);
1537 emitContReturnControl();
1540 void HhbcTranslator::emitContRetC() {
1541 // set state to done
1542 gen(StContArRaw
, m_tb
->fp(), cns(RawMemSlot::ContState
),
1543 cns(c_Continuation::Done
));
1545 // set m_value = popC();
1546 auto const oldValue
= gen(LdContArValue
, Type::Cell
, m_tb
->fp());
1547 gen(StContArValue
, m_tb
->fp(), popC(DataTypeGeneric
)); // teleporting value
1548 gen(DecRef
, oldValue
);
1551 emitContReturnControl();
1554 void HhbcTranslator::emitContCheck(bool checkStarted
) {
1556 SSATmp
* cont
= gen(LdThis
, m_tb
->fp());
1558 gen(ContStartedCheck
, makeExitSlow(), cont
);
1560 gen(ContPreNext
, makeExitSlow(), cont
);
1563 void HhbcTranslator::emitContRaise() {
1565 SSATmp
* cont
= gen(LdThis
, m_tb
->fp());
1566 SSATmp
* label
= gen(LdRaw
, Type::Int
, cont
, cns(RawMemSlot::ContLabel
));
1567 label
= gen(Sub
, label
, cns(1));
1568 gen(StRaw
, cont
, cns(RawMemSlot::ContLabel
), label
);
1571 void HhbcTranslator::emitContValid() {
1573 SSATmp
* cont
= gen(LdThis
, m_tb
->fp());
1574 push(gen(ContValid
, cont
));
1577 void HhbcTranslator::emitContKey() {
1579 SSATmp
* cont
= gen(LdThis
, m_tb
->fp());
1580 gen(ContStartedCheck
, makeExitSlow(), cont
);
1581 SSATmp
* offset
= cns(CONTOFF(m_key
));
1582 SSATmp
* value
= gen(LdProp
, Type::Cell
, cont
, offset
);
1586 void HhbcTranslator::emitContCurrent() {
1588 SSATmp
* cont
= gen(LdThis
, m_tb
->fp());
1589 gen(ContStartedCheck
, makeExitSlow(), cont
);
1590 SSATmp
* offset
= cns(CONTOFF(m_value
));
1591 SSATmp
* value
= gen(LdProp
, Type::Cell
, cont
, offset
);
1595 void HhbcTranslator::emitContStopped() {
1597 SSATmp
* cont
= gen(LdThis
, m_tb
->fp());
1599 gen(ContSetRunning
, cont
, cns(false));
1602 void HhbcTranslator::emitAsyncAwait() {
1603 auto const exitSlow
= makeExitSlow();
1604 if (!topC()->isA(Type::Obj
)) PUNT(AsyncAwait
-NonObject
);
1606 auto const obj
= popC();
1607 auto const isWH
= gen(IsWaitHandle
, obj
);
1608 gen(JmpZero
, exitSlow
, isWH
);
1610 // cns() would ODR-use these
1611 auto const kFailed
= c_WaitHandle::STATE_FAILED
;
1612 auto const kSucceeded
= c_WaitHandle::STATE_SUCCEEDED
;
1614 auto const state
= gen(LdWHState
, obj
);
1615 gen(JmpEq
, exitSlow
, state
, cns(kFailed
));
1617 auto const toPush
= m_tb
->cond(
1618 [&] (Block
* taken
) {
1619 gen(JmpEq
, taken
, state
, cns(kSucceeded
));
1621 [&] { // Next: the wait handle isn't done. We'll push false and
1622 // the same WaitHandle object.
1625 [&] { // Taken: retrieve the result from the wait handle
1626 auto const res
= gen(LdWHResult
, obj
);
1634 push(gen(Not
, gen(ConvIntToBool
, state
)));
1637 void HhbcTranslator::emitAsyncESuspend(int64_t label
, int numIters
) {
1638 auto const exitSlow
= makeExitSlow();
1639 auto const catchBlock
= makeCatch();
1640 auto const child
= popC();
1641 assert(child
->isA(Type::Obj
));
1643 gen(ExitOnVarEnv
, exitSlow
, m_tb
->fp());
1645 auto const origFunc
= curFunc();
1646 auto const genFunc
= origFunc
->getGeneratorBody();
1648 auto const waitHandle
= origFunc
->isMethod()
1652 CreateContData
{ genFunc
},
1653 gen(LdCtx
, FuncData(curFunc()), m_tb
->fp()),
1660 CreateContData
{ genFunc
},
1665 static auto const thisStr
= makeStaticString("this");
1666 Id thisId
= kInvalidId
;
1667 const bool fillThis
= origFunc
->isMethod() &&
1668 !origFunc
->isStatic() &&
1669 ((thisId
= genFunc
->lookupVarId(thisStr
)) != kInvalidId
) &&
1670 (origFunc
->lookupVarId(thisStr
) == kInvalidId
);
1672 SSATmp
* asyncAR
= gen(LdAFWHActRec
, Type::PtrToGen
, waitHandle
);
1673 for (int i
= 0; i
< origFunc
->numNamedLocals(); ++i
) {
1674 assert(i
== genFunc
->lookupVarId(origFunc
->localVarName(i
)));
1675 // We must generate an AssertLoc because we don't have tracelet
1676 // guards on the object type in these outer generator functions.
1677 gen(AssertLoc
, Type::Gen
, LocalId(i
), m_tb
->fp());
1678 // Copy the value of the local to the async function wait handle
1679 // object and set the local to uninit so that we don't need to
1680 // change refcounts.
1681 gen(StMem
, asyncAR
, cns(-cellsToBytes(i
+ 1)),
1682 ldLoc(i
, DataTypeGeneric
));
1683 gen(StLoc
, LocalId(i
), m_tb
->fp(), m_tb
->genDefUninit());
1686 for (int i
= 0; i
< numIters
; ++i
) {
1689 cns(origFunc
->numLocals() * sizeof(TypedValue
) + (i
+1) * sizeof(Iter
)),
1691 cns(genFunc
->numLocals() * sizeof(TypedValue
) + (i
+1) * sizeof(Iter
)));
1695 assert(thisId
!= kInvalidId
);
1696 auto const thisObj
= gen(LdThis
, m_tb
->fp());
1697 gen(IncRef
, thisObj
);
1698 gen(StMem
, asyncAR
, cns(-cellsToBytes(thisId
+ 1)), thisObj
);
1704 void HhbcTranslator::emitAsyncWrapResult() {
1705 push(gen(CreateSRWH
, popC()));
1708 void HhbcTranslator::emitAsyncWrapException() {
1709 push(gen(CreateSEWH
, pop(Type::Obj
)));
1712 void HhbcTranslator::emitStrlen() {
1713 Type inType
= topC()->type();
1715 if (inType
.isString()) {
1716 SSATmp
* input
= popC();
1717 if (input
->isConst()) {
1718 // static string; fold its strlen operation
1719 push(cns(input
->getValStr()->size()));
1721 push(gen(LdRaw
, Type::Int
, input
, cns(RawMemSlot::StrLen
)));
1724 } else if (inType
.isNull()) {
1727 } else if (inType
== Type::Bool
) {
1728 // strlen(true) == 1, strlen(false) == 0.
1729 push(gen(ConvBoolToInt
, popC()));
1731 emitInterpOne(Type::Int
| Type::InitNull
, 1);
1735 void HhbcTranslator::emitIncStat(int32_t counter
, int32_t value
, bool force
) {
1736 if (Stats::enabled() || force
) {
1737 gen(IncStat
, cns(counter
), cns(value
), cns(force
));
1741 void HhbcTranslator::emitIdx() {
1742 Type keyType
= topC(1, DataTypeGeneric
)->type();
1743 SSATmp
* base
= topC(2, DataTypeGeneric
);
1744 Type baseType
= base
->type();
1746 if (baseType
<= Type::Arr
&&
1747 (keyType
<= Type::Int
|| keyType
<= Type::Str
)) {
1750 emitIdxCommon(GenericIdx
, makeCatch());
1754 // NOTE: #3233688 talks about making an idx fast path for collections and
1755 // that is where this function will be used and make more sense. It's only
1757 void HhbcTranslator::emitIdxCommon(Opcode opc
, Block
* catchBlock
) {
1758 SSATmp
* def
= popC(DataTypeGeneric
); // def is just pushed back on the stack
1759 SSATmp
* key
= popC(DataTypeGeneric
);
1760 SSATmp
* arr
= popC(DataTypeGeneric
);
1761 push(gen(opc
, catchBlock
, arr
, key
, def
));
1767 void HhbcTranslator::emitArrayIdx() {
1768 // These types are just used to decide what to do; once we know what we're
1769 // actually doing we constrain the values with the popC()s later on in this
1771 Type keyType
= topC(1, DataTypeGeneric
)->type();
1772 Type arrType
= topC(2, DataTypeGeneric
)->type();
1774 if (!(arrType
<= Type::Arr
)) {
1776 emitInterpOne(Type::Cell
, 3);
1780 if (keyType
<= Type::Null
) {
1781 SSATmp
* def
= popC(DataTypeGeneric
); // def is just pushed back on the stack
1782 SSATmp
* key
= popC();
1783 SSATmp
* arr
= popC();
1785 // if the key is null it will not be found so just return the default
1791 if (!(keyType
<= Type::Int
|| keyType
<= Type::Str
)) {
1792 emitInterpOne(Type::Cell
, 3);
1796 SSATmp
* def
= popC(DataTypeGeneric
); // a helper will decref it but the
1797 // translated code doesn't care about
1799 SSATmp
* key
= popC();
1800 SSATmp
* arr
= popC();
1802 KeyType arrayKeyType
;
1804 checkStrictlyInteger(key
, arrayKeyType
, checkForInt
);
1808 opFunc
= (TCA
)&arrayIdxSi
;
1809 } else if (KeyType::Int
== arrayKeyType
) {
1810 opFunc
= (TCA
)&arrayIdxI
;
1812 assert(KeyType::Str
== arrayKeyType
);
1813 opFunc
= (TCA
)&arrayIdxS
;
1816 push(gen(ArrayIdx
, cns(opFunc
), arr
, key
, def
));
1822 void HhbcTranslator::emitIncTransCounter() {
1823 m_tb
->gen(IncTransCounter
);
1826 void HhbcTranslator::emitIncProfCounter(TransID transId
) {
1827 m_tb
->gen(IncProfCounter
, TransIDData(transId
));
1830 void HhbcTranslator::emitCheckCold(TransID transId
) {
1831 m_tb
->gen(CheckCold
, makeExitOpt(transId
), TransIDData(transId
));
1834 void HhbcTranslator::emitMInstr(const NormalizedInstruction
& ni
) {
1835 MInstrTranslator(ni
, *this).emit();
1839 * IssetL: return true if var is not uninit and !is_null(var)
1840 * Unboxes var if necessary when var is not uninit.
1842 void HhbcTranslator::emitIssetL(int32_t id
) {
1843 auto const exit
= makeExit();
1844 auto const ld
= ldLocInner(id
, exit
, DataTypeSpecific
);
1845 push(gen(IsNType
, Type::Null
, ld
));
1848 void HhbcTranslator::emitEmptyL(int32_t id
) {
1849 auto const exit
= makeExit();
1850 auto const ld
= ldLocInner(id
, exit
, DataTypeSpecific
);
1851 push(gen(Not
, gen(ConvCellToBool
, ld
)));
1854 void HhbcTranslator::emitIsTypeC(DataType t
) {
1855 SSATmp
* src
= popC();
1856 push(gen(IsType
, Type(t
), src
));
1860 void HhbcTranslator::emitIsTypeL(uint32_t id
, DataType t
) {
1861 auto exit
= makeExit();
1862 // TODO(t2598894) We should use the specific type if it's available but not
1864 push(gen(IsType
, Type(t
), ldLocInnerWarn(id
, exit
, DataTypeSpecific
)));
1867 void HhbcTranslator::emitIsScalarL(int id
) {
1868 SSATmp
* src
= ldLocInner(id
, makeExit(), DataTypeSpecific
);
1869 push(gen(IsScalarType
, src
));
1872 void HhbcTranslator::emitIsScalarC() {
1873 SSATmp
* src
= popC();
1874 push(gen(IsScalarType
, src
));
1878 void HhbcTranslator::emitPopA() { popA(); }
1880 void HhbcTranslator::emitPopC() {
1881 popDecRef(Type::Cell
, {DataTypeGeneric
, Type::Cell
});
1884 void HhbcTranslator::emitPopV() {
1885 popDecRef(Type::BoxedCell
, {DataTypeGeneric
, Type::BoxedCell
});
1888 void HhbcTranslator::emitPopR() {
1889 popDecRef(Type::Gen
, DataTypeGeneric
);
1892 void HhbcTranslator::emitDup() {
1896 void HhbcTranslator::emitJmp(int32_t offset
,
1899 // If surprise flags are set, exit trace and handle surprise
1900 bool backward
= static_cast<uint32_t>(offset
) <= bcOff();
1901 if (backward
&& !noSurprise
) {
1902 emitJmpSurpriseCheck();
1904 if (!breakTracelet
) return;
1905 gen(Jmp
, makeExit(offset
));
1908 SSATmp
* HhbcTranslator::emitJmpCondHelper(int32_t offset
,
1913 auto const target
= makeExit(offset
);
1914 auto const boolSrc
= gen(ConvCellToBool
, src
);
1916 return gen(negate
? JmpZero
: JmpNZero
, target
, boolSrc
);
1919 void HhbcTranslator::emitJmpZ(Offset taken
) {
1920 auto const src
= popC();
1921 emitJmpCondHelper(taken
, true, src
);
1924 void HhbcTranslator::emitJmpNZ(Offset taken
) {
1925 auto const src
= popC();
1926 emitJmpCondHelper(taken
, false, src
);
1929 // Objects compared with strings may involve calling a user-defined
1930 // __toString function.
1931 bool cmpOpTypesMayReenter(Type t0
, Type t1
) {
1932 assert(!t0
.equals(Type::Gen
) && !t1
.equals(Type::Gen
));
1933 return (t0
.maybe(Type::Obj
) && t1
.maybe(Type::Str
)) ||
1934 (t0
.maybe(Type::Str
) && t1
.maybe(Type::Obj
));
1937 Opcode
matchReentrantCmp(Opcode opc
) {
1939 case Gt
: return GtX
;
1940 case Gte
: return GteX
;
1941 case Lt
: return LtX
;
1942 case Lte
: return LteX
;
1943 case Eq
: return EqX
;
1944 case Neq
: return NeqX
;
1945 default: return opc
;
1949 void HhbcTranslator::emitCmp(Opcode opc
) {
1950 Block
* catchBlock
= nullptr;
1951 Opcode opc2
= matchReentrantCmp(opc
);
1952 // if the comparison operator could re-enter, convert it to the re-entrant
1953 // form and add the required catch block.
1954 // TODO #3446092 un-overload these opcodes.
1955 if (cmpOpTypesMayReenter(topC(0)->type(), topC(1)->type()) && opc2
!= opc
) {
1956 catchBlock
= makeCatch();
1960 SSATmp
* src1
= popC();
1961 SSATmp
* src2
= popC();
1962 push(gen(opc
, catchBlock
, src2
, src1
));
1967 // Return a constant SSATmp representing a static value held in a
1968 // TypedValue. The TypedValue may be a non-scalar, but it must have a
1970 SSATmp
* HhbcTranslator::staticTVCns(const TypedValue
* tv
) {
1971 switch (tv
->m_type
) {
1972 case KindOfNull
: return m_tb
->genDefInitNull();
1973 case KindOfBoolean
: return cns(!!tv
->m_data
.num
);
1974 case KindOfInt64
: return cns(tv
->m_data
.num
);
1976 case KindOfStaticString
: return cns(tv
->m_data
.pstr
);
1977 case KindOfDouble
: return cns(tv
->m_data
.dbl
);
1978 case KindOfArray
: return cns(tv
->m_data
.parr
);
1979 default: always_assert(0);
1983 void HhbcTranslator::emitClsCnsD(int32_t cnsNameId
, int32_t clsNameId
,
1985 auto const clsNameStr
= lookupStringId(clsNameId
);
1986 auto const cnsNameStr
= lookupStringId(cnsNameId
);
1987 auto const clsCnsName
= ClsCnsName
{ clsNameStr
, cnsNameStr
};
1989 // If we have to side exit, do the RDS lookup before chaining to
1990 // another Tracelet so forward progress still happens.
1991 auto catchBlock
= makeCatchNoSpill();
1992 auto const sideExit
= makeSideExit(
1995 return gen(LookupClsCns
, catchBlock
, clsCnsName
);
2000 * If the class is already defined in this request, and this
2001 * constant is a scalar constant, we can just compile it to a
2004 * We need to guard at runtime that the class is defined in this
2005 * request and has the Class* we expect. If the class is persistent
2006 * or a parent of the current context, we don't need the guard.
2008 if (auto const cls
= Unit::lookupClass(clsNameStr
)) {
2010 auto const tv
= cls
->cnsNameToTV(cnsNameStr
, ignore
);
2011 if (tv
&& tv
->m_type
!= KindOfUninit
) {
2012 if (!classIsPersistentOrCtxParent(cls
)) {
2013 gen(CheckDefinedClsEq
, CheckDefinedClsData
{clsNameStr
, cls
}, sideExit
);
2015 push(staticTVCns(tv
));
2020 auto guardType
= Type::UncountedInit
;
2021 if (outPred
.strictSubtypeOf(guardType
)) guardType
= outPred
;
2022 auto const cns
= gen(LdClsCns
, sideExit
, clsCnsName
, guardType
);
2026 void HhbcTranslator::emitAKExists() {
2027 SSATmp
* arr
= popC();
2028 SSATmp
* key
= popC();
2030 if (!arr
->isA(Type::Arr
) && !arr
->isA(Type::Obj
)) {
2031 PUNT(AKExists_badArray
);
2033 if (!key
->isString() && !key
->isA(Type::Int
) && !key
->isA(Type::Null
)) {
2034 PUNT(AKExists_badKey
);
2037 push(gen(AKExists
, arr
, key
));
2042 void HhbcTranslator::emitFPassR() {
2046 void HhbcTranslator::emitFPassCOp() {
2049 void HhbcTranslator::emitFPassV() {
2050 Block
* exit
= makeExit();
2051 SSATmp
* tmp
= popV();
2052 pushIncRef(gen(Unbox
, exit
, tmp
));
2056 void HhbcTranslator::emitFPushCufIter(int32_t numParams
,
2058 auto sp
= spillStack();
2059 m_fpiStack
.emplace(sp
, m_tb
->spOffset());
2060 gen(CufIterSpillFrame
,
2061 FPushCufData(numParams
, itId
),
2065 static const Func
* findCuf(Op op
,
2069 StringData
*& invName
) {
2070 bool forward
= (op
== OpFPushCufF
);
2074 const StringData
* str
=
2075 callable
->isA(Type::Str
) && callable
->isConst() ? callable
->getValStr()
2077 const ArrayData
* arr
=
2078 callable
->isA(Type::Arr
) && callable
->isConst() ? callable
->getValArr()
2081 StringData
* sclass
= nullptr;
2082 StringData
* sname
= nullptr;
2084 Func
* f
= Unit::lookupFunc(str
);
2086 String
name(const_cast<StringData
*>(str
));
2087 int pos
= name
.find("::");
2088 if (pos
<= 0 || pos
+ 2 >= name
.size() ||
2089 name
.find("::", pos
+ 2) != String::npos
) {
2092 sclass
= makeStaticString(name
.substr(0, pos
).get());
2093 sname
= makeStaticString(name
.substr(pos
+ 2).get());
2095 if (arr
->size() != 2) return nullptr;
2096 CVarRef e0
= arr
->get(int64_t(0), false);
2097 CVarRef e1
= arr
->get(int64_t(1), false);
2098 if (!e0
.isString() || !e1
.isString()) return nullptr;
2099 sclass
= e0
.getStringData();
2100 sname
= e1
.getStringData();
2102 if (name
.find("::") != String::npos
) return nullptr;
2107 if (sclass
->isame(s_self
.get())) {
2108 if (!ctx
) return nullptr;
2111 } else if (sclass
->isame(s_parent
.get())) {
2112 if (!ctx
|| !ctx
->parent()) return nullptr;
2113 cls
= ctx
->parent();
2115 } else if (sclass
->isame(s_static
.get())) {
2118 cls
= Unit::lookupUniqueClass(sclass
);
2119 if (!cls
) return nullptr;
2122 bool magicCall
= false;
2123 const Func
* f
= lookupImmutableMethod(cls
, sname
, magicCall
,
2124 /* staticLookup = */ true, ctx
);
2125 if (!f
|| (forward
&& !ctx
->classof(f
->cls()))) {
2127 * To preserve the invariant that the lsb class
2128 * is an instance of the context class, we require
2129 * that f's class is an instance of the context class.
2130 * This is conservative, but without it, we would need
2131 * a runtime check to decide whether or not to forward
2136 if (magicCall
) invName
= sname
;
2140 bool HhbcTranslator::emitFPushCufArray(SSATmp
* callable
, int32_t numParams
) {
2141 if (!callable
->isA(Type::Arr
)) return false;
2143 auto callableInst
= callable
->inst();
2144 if (!callableInst
->is(NewPackedArray
)) return false;
2146 auto callableSize
= callableInst
->src(0);
2147 if (!callableSize
->isConst() ||
2148 callableSize
->getValInt() != 2) {
2152 auto method
= getStackValue(m_tb
->sp(), 0).value
;
2153 auto object
= getStackValue(m_tb
->sp(), 1).value
;
2154 if (!method
|| !object
) return false;
2156 if (!method
->isConst() ||
2157 strstr(method
->getValStr()->data(), "::") != nullptr) {
2161 if (!object
->isA(Type::Obj
)) {
2162 if (!object
->type().equals(Type::Cell
)) return false;
2163 // This is probably an object, and we just haven't guarded on
2164 // the type. Do so now.
2165 auto exit
= makeExit();
2166 object
= gen(CheckType
, Type::Obj
, exit
, object
);
2167 m_tb
->constrainValue(object
, DataTypeSpecific
);
2172 gen(IncRef
, object
);
2173 emitFPushObjMethodCommon(object
,
2174 method
->getValStr(),
2176 false /* shouldFatal */,
2178 gen(DecRef
, callable
);
2182 // FPushCuf when the callee is not known at compile time.
2183 void HhbcTranslator::emitFPushCufUnknown(Op op
, int32_t numParams
) {
2184 if (op
!= Op::FPushCuf
) {
2185 PUNT(emitFPushCufUnknown
-nonFPushCuf
);
2188 if (topC()->isA(Type::Obj
)) {
2189 return emitFPushFuncObj(numParams
);
2192 if (!topC()->type().subtypeOfAny(Type::Arr
, Type::Str
)) {
2193 PUNT(emitFPushCufUnknown
);
2196 // Peek at the top of the stack before deciding to pop it.
2197 auto const callable
= topC();
2198 if (emitFPushCufArray(callable
, numParams
)) return;
2204 m_tb
->genDefInitNull(),
2208 auto const actRec
= spillStack();
2211 * This is a similar case to lookup for functions in FPushFunc or
2212 * FPushObjMethod. We can throw in a weird situation where the
2213 * ActRec is already on the stack, but this bytecode isn't done
2214 * executing yet. See arPreliveOverwriteCells for details about why
2215 * we need this marker.
2219 auto const opcode
= callable
->isA(Type::Arr
) ? LdArrFPushCuf
2221 gen(opcode
, makeCatch({callable
}), callable
, actRec
, m_tb
->fp());
2222 gen(DecRef
, callable
);
2225 void HhbcTranslator::emitFPushCufOp(Op op
, int32_t numArgs
) {
2226 const bool safe
= op
== OpFPushCufSafe
;
2227 const bool forward
= op
== OpFPushCufF
;
2228 SSATmp
* callable
= topC(safe
? 1 : 0);
2230 Class
* cls
= nullptr;
2231 StringData
* invName
= nullptr;
2232 auto const callee
= findCuf(op
, callable
, curClass(), cls
, invName
);
2233 if (!callee
) return emitFPushCufUnknown(op
, numArgs
);
2236 SSATmp
* safeFlag
= cns(true); // This is always true until the slow exits
2237 // below are implemented
2238 SSATmp
* func
= cns(callee
);
2240 auto const exitSlow
= makeExitSlow();
2241 if (!RDS::isPersistentHandle(cls
->classHandle())) {
2242 // The miss path is complicated and rare. Punt for now. This
2243 // must be checked before we IncRef the context below, because
2244 // the slow exit will want to do that same IncRef via InterpOne.
2245 gen(LdClsCachedSafe
, exitSlow
, cns(cls
->name()));
2249 ctx
= gen(LdCtx
, FuncData(curFunc()), m_tb
->fp());
2250 ctx
= gen(GetCtxFwdCall
, ctx
, cns(callee
));
2252 ctx
= genClsMethodCtx(callee
, cls
);
2255 ctx
= m_tb
->genDefInitNull();
2256 if (!RDS::isPersistentHandle(callee
->funcHandle())) {
2257 // The miss path is complicated and rare. Punt for now.
2259 LdFuncCachedSafe
, LdFuncCachedData(callee
->name()), makeExitSlow()
2264 SSATmp
* defaultVal
= safe
? popC() : nullptr;
2265 popDecRef(Type::Cell
); // callable
2271 emitFPushActRec(func
, ctx
, numArgs
, invName
);
2274 void HhbcTranslator::emitNativeImpl() {
2275 gen(NativeImpl
, cns(curFunc()), m_tb
->fp());
2276 SSATmp
* sp
= gen(RetAdjustStack
, m_tb
->fp());
2277 SSATmp
* retAddr
= gen(LdRetAddr
, m_tb
->fp());
2278 SSATmp
* fp
= gen(FreeActRec
, m_tb
->fp());
2279 gen(RetCtrl
, InGeneratorData(false), sp
, fp
, retAddr
);
2281 // Flag that this trace has a Ret instruction so no ExitTrace is needed
2285 void HhbcTranslator::emitFPushActRec(SSATmp
* func
,
2288 const StringData
* invName
) {
2290 * Before allocating an ActRec, we do a spillStack so we'll have a
2291 * StkPtr that represents what the stack will look like after the
2294 auto actualStack
= spillStack();
2295 auto returnSp
= actualStack
;
2297 if (curFunc()->isGenerator()) {
2298 gen(StashGeneratorSP
, m_tb
->fp(), m_tb
->sp());
2301 m_fpiStack
.emplace(returnSp
, m_tb
->spOffset());
2304 info
.numArgs
= numArgs
;
2305 info
.invName
= invName
;
2309 // Using actualStack instead of returnSp so SpillFrame still gets
2310 // the src in rVmSp. (TODO(#2288359).)
2316 assert(m_stackDeficit
== 0);
2319 void HhbcTranslator::emitFPushCtorCommon(SSATmp
* cls
,
2322 int32_t numParams
) {
2324 SSATmp
* fn
= nullptr;
2328 fn
= gen(LdClsCtor
, makeCatch(), cls
);
2331 int32_t numArgsAndCtorFlag
= ActRec::encodeNumArgs(numParams
, true);
2332 emitFPushActRec(fn
, obj
, numArgsAndCtorFlag
, nullptr);
2335 void HhbcTranslator::emitFPushCtor(int32_t numParams
) {
2336 SSATmp
* cls
= popA();
2337 SSATmp
* obj
= gen(AllocObj
, makeCatch(), cls
);
2339 emitFPushCtorCommon(cls
, obj
, nullptr, numParams
);
2342 static bool canInstantiateClass(const Class
* cls
) {
2344 !(cls
->attrs() & (AttrAbstract
| AttrInterface
| AttrTrait
));
2347 void HhbcTranslator::emitFPushCtorD(int32_t numParams
, int32_t classNameStrId
) {
2348 const StringData
* className
= lookupStringId(classNameStrId
);
2350 const Class
* cls
= Unit::lookupUniqueClass(className
);
2351 bool uniqueCls
= classIsUnique(cls
);
2352 bool persistentCls
= classHasPersistentRDS(cls
);
2353 bool canInstantiate
= canInstantiateClass(cls
);
2355 !RuntimeOption::EnableObjDestructCall
&&
2358 !cls
->callsCustomInstanceInit();
2360 const Func
* func
= uniqueCls
? cls
->getCtor() : nullptr;
2361 if (func
&& !(func
->attrs() & AttrPublic
)) {
2362 Class
* ctx
= curClass();
2365 } else if (ctx
!= cls
) {
2366 if ((func
->attrs() & AttrPrivate
) ||
2367 !(ctx
->classof(cls
) || cls
->classof(ctx
))) {
2374 persistentCls
? cns(cls
)
2375 : gen(LdClsCached
, makeCatch(), cns(className
));
2377 fastAlloc
? gen(AllocObjFast
, ClassData(cls
))
2378 : gen(AllocObj
, makeCatch(), ssaCls
);
2380 emitFPushCtorCommon(ssaCls
, obj
, func
, numParams
);
2383 const StaticString
s_uuinvoke("__invoke");
2386 * The CreateCl opcode is specified as not being allowed before the
2387 * class it creates exists, and closure classes are always unique.
2389 * This means even if we're not in RepoAuthoritative mode, as long as
2390 * this code is reachable it will always use the same closure Class*,
2391 * so we can just burn it into the TC without using RDS.
2393 void HhbcTranslator::emitCreateCl(int32_t numParams
, int32_t funNameStrId
) {
2394 auto const cls
= Unit::lookupUniqueClass(lookupStringId(funNameStrId
));
2395 auto const invokeFunc
= cls
->lookupMethod(s_uuinvoke
.get());
2396 auto const clonedFunc
= invokeFunc
->cloneAndSetClass(curClass());
2397 assert(cls
&& (cls
->attrs() & AttrUnique
));
2399 // Although closures can't have destructors, destructing the
2400 // captured values (or captured $this) can lead to user-visible
2401 // side-effects, so we can't use AllocObjFast if
2402 // EnableObjDestructCall is on.
2403 auto const closure
=
2404 RuntimeOption::EnableObjDestructCall
? gen(AllocObj
, makeCatch(), cns(cls
))
2405 : gen(AllocObjFast
, ClassData(cls
));
2406 gen(IncRef
, closure
);
2408 auto const ctx
= [&]{
2409 if (!curClass()) return cns(nullptr);
2410 auto const ldctx
= gen(LdCtx
, FuncData(curFunc()), m_tb
->fp());
2411 if (invokeFunc
->attrs() & AttrStatic
) {
2412 return gen(ConvClsToCctx
, gen(LdClsCtx
, ldctx
));
2414 gen(IncRefCtx
, ldctx
);
2417 gen(StClosureCtx
, closure
, ctx
);
2418 gen(StClosureFunc
, FuncData(clonedFunc
), closure
);
2420 SSATmp
* args
[numParams
];
2421 for (int32_t i
= 0; i
< numParams
; ++i
) {
2422 args
[numParams
- i
- 1] = popF();
2426 for (; propId
< numParams
; ++propId
) {
2429 PropByteOffset(cls
->declPropOffset(propId
)),
2435 // Closure static variables are per instance, and need to start
2436 // uninitialized. After numParams use vars, the remaining instance
2437 // properties hold any static locals.
2438 assert(cls
->numDeclProperties() ==
2439 clonedFunc
->numStaticLocals() + numParams
);
2440 for (int32_t numDeclProperties
= cls
->numDeclProperties();
2441 propId
< numDeclProperties
;
2445 PropByteOffset(cls
->declPropOffset(propId
)),
2447 m_tb
->genDefUninit()
2454 void HhbcTranslator::emitFPushFuncCommon(const Func
* func
,
2455 const StringData
* name
,
2456 const StringData
* fallback
,
2457 int32_t numParams
) {
2460 if (func
->isNameBindingImmutable(curUnit())) {
2461 emitFPushActRec(cns(func
),
2462 m_tb
->genDefInitNull(),
2469 // LdFuncCached can throw
2470 auto const catchBlock
= makeCatch();
2471 auto const ssaFunc
= fallback
2472 ? gen(LdFuncCachedU
,
2473 LdFuncCachedUData
{ name
, fallback
},
2476 LdFuncCachedData
{ name
},
2478 emitFPushActRec(ssaFunc
,
2479 m_tb
->genDefInitNull(),
2484 void HhbcTranslator::emitFPushFuncD(int32_t numParams
, int32_t funcId
) {
2485 const NamedEntityPair
& nep
= lookupNamedEntityPairId(funcId
);
2486 const StringData
* name
= nep
.first
;
2487 const Func
* func
= Unit::lookupFunc(nep
.second
);
2488 emitFPushFuncCommon(func
, name
, nullptr, numParams
);
2491 void HhbcTranslator::emitFPushFuncU(int32_t numParams
,
2493 int32_t fallbackFuncId
) {
2494 const NamedEntityPair
& nep
= lookupNamedEntityPairId(funcId
);
2495 const StringData
* name
= nep
.first
;
2496 const Func
* func
= Unit::lookupFunc(nep
.second
);
2497 const NamedEntityPair
& fallbackNep
= lookupNamedEntityPairId(fallbackFuncId
);
2498 const StringData
* fallbackName
= fallbackNep
.first
;
2499 emitFPushFuncCommon(func
, name
, fallbackName
, numParams
);
2502 void HhbcTranslator::emitFPushFunc(int32_t numParams
) {
2503 if (topC()->isA(Type::Obj
)) {
2504 return emitFPushFuncObj(numParams
);
2507 if (topC()->isA(Type::Arr
)) {
2508 return emitFPushFuncArr(numParams
);
2511 if (!topC()->isA(Type::Str
)) {
2512 PUNT(FPushFunc_not_Str
);
2515 auto const catchBlock
= makeCatch();
2516 auto const funcName
= popC();
2517 emitFPushActRec(gen(LdFunc
, catchBlock
, funcName
),
2518 m_tb
->genDefInitNull(),
2523 void HhbcTranslator::emitFPushFuncObj(int32_t numParams
) {
2524 auto const slowExit
= makeExitSlow();
2525 auto const obj
= popC();
2526 auto const cls
= gen(LdObjClass
, obj
);
2527 auto const func
= gen(LdObjInvoke
, slowExit
, cls
);
2528 emitFPushActRec(func
, obj
, numParams
, nullptr);
2531 void HhbcTranslator::emitFPushFuncArr(int32_t numParams
) {
2532 auto const thisAR
= m_tb
->fp();
2534 auto const catchBlock
= makeCatch();
2535 auto const arr
= popC();
2538 m_tb
->genDefInitNull(),
2541 auto const actRec
= spillStack();
2543 // This is special. We need to move the stackpointer incase LdArrFuncCtx
2544 // calls a destructor. Otherwise it would clobber the ActRec we just
2548 gen(LdArrFuncCtx
, catchBlock
, arr
, actRec
, thisAR
);
2552 void HhbcTranslator::emitFPushObjMethodCommon(SSATmp
* obj
,
2553 const StringData
* methodName
,
2556 SSATmp
* extraSpill
) {
2557 SSATmp
* objOrCls
= obj
;
2558 const Class
* baseClass
= nullptr;
2559 if (obj
->type().isSpecialized() &&
2560 !m_tb
->constrainValue(obj
, TypeConstraint(DataTypeSpecialized
,
2561 Type::Cell
).setWeak())) {
2562 // If we know the class without having to specialize a guard any
2564 baseClass
= obj
->type().getClass();
2567 bool magicCall
= false;
2568 const Func
* func
= HPHP::JIT::lookupImmutableMethod(baseClass
,
2576 if (baseClass
&& !(baseClass
->attrs() & AttrInterface
)) {
2578 g_vmContext
->lookupObjMethod(func
, baseClass
, methodName
, curClass(),
2580 if ((res
== LookupResult::MethodFoundWithThis
||
2581 res
== LookupResult::MethodFoundNoThis
) &&
2582 !func
->isAbstract()) {
2584 * If we found the func in baseClass, then either:
2585 * a) its private, and this is always going to be the
2586 * called function. This case is handled further down.
2588 * b) any derived class must have a func that matches in staticness
2589 * and is at least as accessible (and in particular, you can't
2590 * override a public/protected method with a private method).
2591 * In this case, we emit code to dynamically lookup the method
2592 * given the Object and the method slot, which is the same as func's.
2594 if (!(func
->attrs() & AttrPrivate
)) {
2595 SSATmp
* clsTmp
= gen(LdObjClass
, obj
);
2596 SSATmp
* funcTmp
= gen(
2597 LdClsMethod
, clsTmp
, cns(func
->methodSlot())
2599 if (res
== LookupResult::MethodFoundNoThis
) {
2603 emitFPushActRec(funcTmp
, objOrCls
, numParams
,
2604 magicCall
? methodName
: nullptr);
2608 // method lookup did not find anything
2609 func
= nullptr; // force lookup
2614 if (func
!= nullptr) {
2615 if (func
->attrs() & AttrStatic
) {
2616 assert(baseClass
); // This assert may be too strong, but be aggressive
2617 // static function: store base class into this slot instead of obj
2618 // and decref the obj that was pushed as the this pointer since
2619 // the obj won't be in the actrec and thus MethodCache::lookup won't
2622 objOrCls
= cns(baseClass
);
2624 emitFPushActRec(cns(func
),
2627 magicCall
? methodName
: nullptr);
2630 std::vector
<SSATmp
*> spill
;
2631 if (extraSpill
) spill
.push_back(extraSpill
);
2632 auto catchBlock
= makeCatch(spill
);
2633 emitFPushActRec(m_tb
->genDefNull(),
2637 auto const actRec
= spillStack();
2638 auto const objCls
= gen(LdObjClass
, obj
);
2640 // This is special. We need to move the stackpointer incase LdObjMethod
2641 // calls a destructor. Otherwise it would clobber the ActRec we just pushed.
2645 LdObjMethodData(shouldFatal
),
2653 void HhbcTranslator::emitFPushObjMethodD(int32_t numParams
,
2654 int32_t methodNameStrId
) {
2655 SSATmp
* obj
= popC();
2656 if (!obj
->isA(Type::Obj
)) PUNT(FPushObjMethodD
-nonObj
);
2657 const StringData
* methodName
= lookupStringId(methodNameStrId
);
2658 emitFPushObjMethodCommon(obj
, methodName
, numParams
, true /* shouldFatal */);
2661 SSATmp
* HhbcTranslator::genClsMethodCtx(const Func
* callee
, const Class
* cls
) {
2662 bool mightNotBeStatic
= false;
2664 if (!(callee
->attrs() & AttrStatic
) &&
2665 !(curFunc()->attrs() & AttrStatic
) &&
2667 curClass()->classof(cls
)) {
2668 mightNotBeStatic
= true;
2671 if (!mightNotBeStatic
) {
2672 // static function: ctx is just the Class*. LdCls will simplify to a
2673 // DefConst or LdClsCached.
2674 return gen(LdCls
, makeCatch(), cns(cls
->name()), cns(curClass()));
2676 if (m_tb
->thisAvailable()) {
2677 // might not be a static call and $this is available, so we know it's
2678 // definitely not static
2680 auto this_
= gen(LdThis
, m_tb
->fp());
2684 // might be a non-static call. we have to inspect the func at runtime
2685 PUNT(getClsMethodCtx
-MightNotBeStatic
);
2688 void HhbcTranslator::emitFPushClsMethodD(int32_t numParams
,
2689 int32_t methodNameStrId
,
2690 int32_t clssNamedEntityPairId
) {
2692 const StringData
* methodName
= lookupStringId(methodNameStrId
);
2693 const NamedEntityPair
& np
= lookupNamedEntityPairId(clssNamedEntityPairId
);
2694 const StringData
* className
= np
.first
;
2695 const Class
* baseClass
= Unit::lookupUniqueClass(np
.second
);
2696 bool magicCall
= false;
2697 const Func
* func
= HPHP::JIT::lookupImmutableMethod(baseClass
,
2704 SSATmp
* objOrCls
= genClsMethodCtx(func
, baseClass
);
2705 emitFPushActRec(cns(func
),
2708 func
&& magicCall
? methodName
: nullptr);
2710 auto slowExit
= makeExitSlow();
2711 auto const data
= ClsMethodData
{className
, methodName
};
2713 // Look up the Func* in the targetcache. If it's not there, try the slow
2714 // path. If that fails, slow exit.
2715 auto func
= m_tb
->cond(
2717 return gen(CheckNonNull
, taken
, gen(LdClsMethodCacheFunc
, data
));
2719 [&](SSATmp
* func
) { // next
2723 m_tb
->hint(Block::Hint::Unlikely
);
2724 auto result
= gen(LookupClsMethodCache
, makeCatch(), data
,
2725 cns(np
.second
), m_tb
->fp());
2726 return gen(CheckNonNull
, slowExit
, result
);
2729 auto clsCtx
= gen(LdClsMethodCacheCls
, data
);
2731 emitFPushActRec(func
,
2738 void HhbcTranslator::emitFPushClsMethod(int32_t numParams
) {
2739 auto const clsVal
= popA();
2740 auto const methVal
= popC();
2742 if (!methVal
->isString() || !clsVal
->isA(Type::Cls
)) {
2743 PUNT(FPushClsMethod
-unknownType
);
2746 if (methVal
->isConst() &&
2747 clsVal
->inst()->op() == LdClsCctx
) {
2749 * Optimize FPushClsMethod when the method is a known static
2750 * string and the input class is the context. The common bytecode
2751 * pattern here is LateBoundCls ; FPushClsMethod.
2753 * This logic feels like it belongs in the simplifier, but the
2754 * generated code for this case is pretty different, since we
2755 * don't need the pre-live ActRec trick.
2757 auto const cls
= curClass();
2760 g_vmContext
->lookupClsMethod(func
,
2762 methVal
->getValStr(),
2766 if (res
== LookupResult::MethodFoundNoThis
) {
2767 auto const funcTmp
= gen(LdClsMethod
, clsVal
, cns(func
->methodSlot()));
2768 emitFPushActRec(funcTmp
, clsVal
, numParams
, nullptr);
2773 emitFPushActRec(m_tb
->genDefNull(),
2774 m_tb
->genDefInitNull(),
2777 auto const actRec
= spillStack();
2780 * Similar to FPushFunc/FPushObjMethod, we have an incomplete ActRec
2781 * on the stack and must handle that properly if we throw.
2785 gen(LookupClsMethod
, makeCatch({clsVal
, methVal
}), clsVal
, methVal
, actRec
,
2787 gen(DecRef
, methVal
);
2790 void HhbcTranslator::emitFPushClsMethodF(int32_t numParams
) {
2791 Block
* exitBlock
= makeExitSlow();
2793 auto classTmp
= popA();
2794 auto methodTmp
= popC();
2795 assert(classTmp
->isA(Type::Cls
));
2796 if (!classTmp
->isConst() || !methodTmp
->isString() || !methodTmp
->isConst()) {
2797 PUNT(FPushClsMethodF
-unknownClassOrMethod
);
2800 auto const cls
= classTmp
->getValClass();
2801 auto const methName
= methodTmp
->getValStr();
2803 bool magicCall
= false;
2804 const Func
* func
= lookupImmutableMethod(cls
, methName
, magicCall
,
2805 true /* staticLookup */,
2807 SSATmp
* curCtxTmp
= gen(LdCtx
, FuncData(curFunc()), m_tb
->fp());
2809 SSATmp
* funcTmp
= cns(func
);
2810 SSATmp
* newCtxTmp
= gen(GetCtxFwdCall
, curCtxTmp
, funcTmp
);
2812 emitFPushActRec(funcTmp
, newCtxTmp
, numParams
,
2813 (magicCall
? methName
: nullptr));
2816 auto const data
= ClsMethodData
{cls
->name(), methName
};
2817 auto func
= m_tb
->cond(
2819 return gen(CheckNonNull
, taken
, gen(LdClsMethodFCacheFunc
, data
));
2821 [&](SSATmp
* func
) { // next
2825 m_tb
->hint(Block::Hint::Unlikely
);
2826 auto result
= gen(LookupClsMethodFCache
, makeCatch(), data
,
2827 cns(cls
), m_tb
->fp());
2828 return gen(CheckNonNull
, exitBlock
, result
);
2831 auto ctx
= gen(GetCtxFwdCallDyn
, data
, curCtxTmp
);
2833 emitFPushActRec(func
,
2836 (magicCall
? methName
: nullptr));
2840 void HhbcTranslator::emitFCallArray(const Offset pcOffset
,
2842 bool destroyLocals
) {
2843 SSATmp
* stack
= spillStack();
2844 gen(CallArray
, CallArrayData(pcOffset
, after
, destroyLocals
), stack
);
2847 void HhbcTranslator::emitFCall(uint32_t numParams
,
2848 Offset returnBcOffset
,
2850 bool destroyLocals
) {
2851 SSATmp
* params
[numParams
+ 3];
2852 std::memset(params
, 0, sizeof params
);
2853 for (uint32_t i
= 0; i
< numParams
; i
++) {
2854 // DataTypeGeneric is used because the Call instruction just spills the
2855 // values to the stack unmodified.
2856 params
[numParams
+ 3 - i
- 1] = popF(DataTypeGeneric
);
2859 params
[0] = spillStack();
2860 params
[1] = cns(returnBcOffset
);
2861 params
[2] = callee
? cns(callee
) : m_tb
->genDefNull();
2863 if (RuntimeOption::EvalRuntimeTypeProfile
) {
2864 for (uint32_t i
= 0; i
< numParams
; i
++) {
2865 if (callee
!= nullptr &&
2866 params
[numParams
+ 3 - i
- 1]) {
2867 gen(TypeProfileFunc
, TypeProfileData(i
),
2868 params
[numParams
+ 3 - i
- 1], cns(callee
));
2870 SSATmp
* func
= gen(LdARFuncPtr
, m_tb
->sp(), cns(0));
2871 gen(TypeProfileFunc
, TypeProfileData(i
),
2872 params
[numParams
+ 3 - i
- 1], func
);
2877 SSATmp
** decayedPtr
= params
;
2878 gen(Call
, CallData(destroyLocals
), std::make_pair(numParams
+ 3, decayedPtr
));
2879 if (!m_fpiStack
.empty()) {
2884 void HhbcTranslator::emitFCallBuiltin(uint32_t numArgs
,
2885 uint32_t numNonDefault
,
2887 bool destroyLocals
) {
2888 const NamedEntity
* ne
= lookupNamedEntityId(funcId
);
2889 const Func
* callee
= Unit::lookupFunc(ne
);
2893 // spill args to stack. We need to spill these for two resons:
2894 // 1. some of the arguments may be passed by reference, for which
2895 // case we will pass a stack address.
2896 // 2. type conversions of the arguments (using tvCast* helpers)
2897 // may throw an exception, so we either need to have the VM stack
2898 // in a clean state at that point or give each helper a catch
2899 // trace. Since we have to spillstack anyway, the catch trace
2900 // would be overkill.
2903 bool zendParamMode
=
2904 !callee
->methInfo() ||
2905 (callee
->methInfo()->attribute
&
2906 (ClassInfo::ZendParamModeNull
| ClassInfo::ZendParamModeFalse
));
2908 // Convert types if needed.
2909 for (int i
= 0; i
< numNonDefault
; i
++) {
2910 const Func::ParamInfo
& pi
= callee
->params()[i
];
2911 switch (pi
.builtinType()) {
2916 case KindOfResource
:
2918 if (zendParamMode
) {
2920 Type(pi
.builtinType()),
2921 StackOffset(numArgs
- i
- 1),
2927 Type(pi
.builtinType()),
2928 StackOffset(numArgs
- i
- 1),
2932 case KindOfDouble
: not_reached();
2933 case KindOfUnknown
: break;
2934 default: not_reached();
2938 // Pass arguments for CallBuiltin.
2939 const int argsSize
= numArgs
+ 2;
2940 SSATmp
* args
[argsSize
];
2941 args
[0] = cns(callee
);
2942 args
[1] = m_tb
->sp();
2943 for (int i
= numArgs
- 1; i
>= 0; i
--) {
2944 const Func::ParamInfo
& pi
= callee
->params()[i
];
2945 switch (pi
.builtinType()) {
2948 args
[i
+ 2] = top(Type(pi
.builtinType()),
2951 case KindOfDouble
: not_reached();
2953 args
[i
+ 2] = ldStackAddr(numArgs
- i
- 1, DataTypeSpecific
);
2958 // Generate call and set return type
2959 auto const retDt
= callee
->returnType();
2960 auto retType
= retDt
== KindOfUnknown
? Type::Cell
: Type(retDt
);
2961 if (callee
->attrs() & ClassInfo::IsReference
) retType
= retType
.box();
2963 auto const ret
= gen(
2966 CallData(destroyLocals
),
2968 std::make_pair(argsSize
, (SSATmp
**)&args
)
2971 // Decref and free args
2972 for (int i
= 0; i
< numArgs
; i
++) {
2973 auto const arg
= popR();
2974 if (i
>= numArgs
- numNonDefault
) {
2982 void HhbcTranslator::emitRetFromInlined(Type type
) {
2983 SSATmp
* retVal
= pop(type
);
2985 assert(!(curFunc()->attrs() & AttrMayUseVV
));
2986 assert(!curFunc()->isPseudoMain());
2987 assert(!m_fpiActiveStack
.empty());
2989 auto useRet
= emitDecRefLocalsInline(retVal
);
2992 * Pop the ActRec and restore the stack and frame pointers. It's
2993 * important that this does endInlining before pushing the return
2994 * value so stack offsets are properly tracked.
2996 gen(InlineReturn
, m_tb
->fp());
2998 // Return to the caller function. Careful between here and the
2999 // updateMarker() below, where the caller state isn't entirely set up.
3000 m_bcStateStack
.pop_back();
3001 m_fpiActiveStack
.pop();
3004 // See the comment in beginInlining about generator frames.
3005 if (curFunc()->isGenerator()) {
3006 gen(ReDefGeneratorSP
,
3007 ReDefGeneratorSPData(m_tb
->inlinedFrameSpansCall()),
3008 m_tb
->sp(), m_tb
->fp());
3010 smart::vector
<ReDefSPData::Frame
> frames
;
3011 m_tb
->state().forEachFrame([&frames
](SSATmp
* fp
, int32_t off
) {
3012 frames
.emplace_back(frameRoot(fp
->inst())->dst(), off
);
3014 gen(ReDefSP
, ReDefSPData(frames
.size(), frames
.data(),
3016 m_tb
->inlinedFrameSpansCall()),
3017 m_tb
->sp(), m_tb
->fp());
3021 * After the end of inlining, we are restoring to a previously
3022 * defined stack that we know is entirely materialized. TODO:
3023 * explain this better.
3025 * The push of the return value below is not yet materialized.
3027 assert(m_evalStack
.numCells() == 0);
3030 FTRACE(1, "]]] end inlining: {}\n", curFunc()->fullName()->data());
3034 SSATmp
* HhbcTranslator::emitDecRefLocalsInline(SSATmp
* retVal
) {
3035 const Func
* curFunc
= this->curFunc();
3037 if (curFunc
->mayHaveThis()) {
3038 gen(DecRefThis
, m_tb
->fp());
3042 * Note: this is currently off for isInlining() because the shuffle
3043 * was preventing a decref elimination due to ordering. Currently
3044 * we don't inline anything with parameters, though, so it doesn't
3045 * matter. This will need to be revisted then.
3047 for (int id
= curFunc
->numLocals() - 1; id
>= 0; --id
) {
3048 gen(DecRefLoc
, Type::Gen
, LocalId(id
), m_tb
->fp());
3054 void HhbcTranslator::emitRet(Type type
, bool freeInline
) {
3056 return emitRetFromInlined(type
);
3059 const Func
* curFunc
= this->curFunc();
3060 bool mayUseVV
= (curFunc
->attrs() & AttrMayUseVV
);
3063 // Note: this has to be the first thing, because we cannot bail after
3064 // we start decRefing locs because then there'll be no corresponding
3065 // bytecode boundaries until the end of RetC
3066 gen(ReleaseVVOrExit
, makeExitSlow(), m_tb
->fp());
3069 // The return value is teleported to its place in memory so we don't care
3071 SSATmp
* retVal
= pop(type
, DataTypeGeneric
);
3072 if (RuntimeOption::EvalRuntimeTypeProfile
) {
3073 gen(TypeProfileFunc
, TypeProfileData(-1), retVal
, cns(curFunc
));
3077 retVal
= emitDecRefLocalsInline(retVal
);
3078 for (unsigned i
= 0; i
< curFunc
->numLocals(); ++i
) {
3079 m_tb
->constrainLocal(i
, DataTypeCountness
, "inlined RetC/V");
3081 gen(StRetVal
, m_tb
->fp(), retVal
);
3082 sp
= gen(RetAdjustStack
, m_tb
->fp());
3084 if (curFunc
->mayHaveThis()) {
3085 gen(DecRefThis
, m_tb
->fp());
3087 sp
= gen(GenericRetDecRefs
, m_tb
->fp());
3088 gen(StRetVal
, m_tb
->fp(), retVal
);
3091 emitRetSurpriseCheck(retVal
, false);
3093 // Free ActRec, and return control to caller.
3094 SSATmp
* retAddr
= gen(LdRetAddr
, m_tb
->fp());
3095 SSATmp
* fp
= gen(FreeActRec
, m_tb
->fp());
3096 gen(RetCtrl
, InGeneratorData(false), sp
, fp
, retAddr
);
3098 // Flag that this trace has a Ret instruction, so that no ExitTrace is needed
3102 void HhbcTranslator::emitJmpSurpriseCheck() {
3103 auto catchTrace
= makeCatch();
3105 m_tb
->ifThen([&](Block
* taken
) {
3106 gen(CheckSurpriseFlags
, taken
);
3109 m_tb
->hint(Block::Hint::Unlikely
);
3110 gen(SurpriseHook
, catchTrace
);
3114 void HhbcTranslator::emitRetSurpriseCheck(SSATmp
* retVal
, bool inGenerator
) {
3115 emitRB(Trace::RBTypeFuncExit
, curFunc()->fullName());
3116 auto catchBlock
= makeCatch();
3118 m_tb
->ifThen([&](Block
* taken
) {
3119 gen(CheckSurpriseFlags
, taken
);
3122 m_tb
->hint(Block::Hint::Unlikely
);
3123 gen(FunctionExitSurpriseHook
, InGeneratorData(inGenerator
),
3124 catchBlock
, m_tb
->fp(), m_tb
->sp(), retVal
);
3129 void HhbcTranslator::emitSwitch(const ImmVector
& iv
,
3132 int nTargets
= bounded
? iv
.size() - 2 : iv
.size();
3134 auto catchBlock
= topC()->isA(Type::Obj
) ? makeCatch() : nullptr;
3135 SSATmp
* const switchVal
= popC();
3136 Type type
= switchVal
->type();
3137 assert(IMPLIES(!type
.equals(Type::Int
), bounded
));
3138 assert(IMPLIES(bounded
, iv
.size() > 2));
3140 SSATmp
* ssabase
= cns(base
);
3141 SSATmp
* ssatargets
= cns(nTargets
);
3143 Offset defaultOff
= bcOff() + iv
.vec32()[iv
.size() - 1];
3145 if (base
<= 0 && (base
+ nTargets
) > 0) {
3146 zeroOff
= bcOff() + iv
.vec32()[0 - base
];
3148 zeroOff
= defaultOff
;
3151 if (type
<= Type::Null
) {
3152 gen(Jmp
, makeExit(zeroOff
));
3155 if (type
<= Type::Bool
) {
3156 Offset nonZeroOff
= bcOff() + iv
.vec32()[iv
.size() - 2];
3157 gen(JmpNZero
, makeExit(nonZeroOff
), switchVal
);
3158 gen(Jmp
, makeExit(zeroOff
));
3162 if (type
<= Type::Int
) {
3163 // No special treatment needed
3165 } else if (type
<= Type::Dbl
) {
3166 // switch(Double|String|Obj)Helper do bounds-checking for us, so
3167 // we need to make sure the default case is in the jump table,
3168 // and don't emit our own bounds-checking code
3170 index
= gen(LdSwitchDblIndex
, switchVal
, ssabase
, ssatargets
);
3171 } else if (type
<= Type::Str
) {
3173 index
= gen(LdSwitchStrIndex
, switchVal
, ssabase
, ssatargets
);
3174 } else if (type
<= Type::Obj
) {
3175 // switchObjHelper can throw exceptions and reenter the VM so we use the
3176 // catch block here.
3178 index
= gen(LdSwitchObjIndex
, catchBlock
, switchVal
, ssabase
, ssatargets
);
3179 } else if (type
<= Type::Arr
) {
3180 gen(DecRef
, switchVal
);
3181 gen(Jmp
, makeExit(defaultOff
));
3184 PUNT(Switch
-UnknownType
);
3187 std::vector
<Offset
> targets(iv
.size());
3188 for (int i
= 0; i
< iv
.size(); i
++) {
3189 targets
[i
] = bcOff() + iv
.vec32()[i
];
3193 data
.func
= curFunc();
3195 data
.bounded
= bounded
;
3196 data
.cases
= iv
.size();
3197 data
.defaultOff
= defaultOff
;
3198 data
.targets
= &targets
[0];
3200 auto const stack
= spillStack();
3201 gen(SyncABIRegs
, m_tb
->fp(), stack
);
3203 gen(JmpSwitchDest
, data
, index
);
3207 void HhbcTranslator::emitSSwitch(const ImmVector
& iv
) {
3208 const int numCases
= iv
.size() - 1;
3211 * We use a fast path translation with a hashtable if none of the
3212 * cases are numeric strings and if the input is actually a string.
3214 * Otherwise we do a linear search through the cases calling string
3215 * conversion routines.
3217 const bool fastPath
=
3218 topC()->isA(Type::Str
) &&
3219 std::none_of(iv
.strvec(), iv
.strvec() + numCases
,
3220 [&](const StrVecItem
& item
) {
3221 return curUnit()->lookupLitstrId(item
.str
)->isNumeric();
3225 Block
* catchBlock
= nullptr;
3226 // The slow path can throw exceptions and reenter the VM.
3227 if (!fastPath
) catchBlock
= makeCatch();
3229 auto const testVal
= popC();
3231 std::vector
<LdSSwitchData::Elm
> cases(numCases
);
3232 for (int i
= 0; i
< numCases
; ++i
) {
3233 auto const& kv
= iv
.strvec()[i
];
3234 cases
[i
].str
= curUnit()->lookupLitstrId(kv
.str
);
3235 cases
[i
].dest
= bcOff() + kv
.dest
;
3239 data
.func
= curFunc();
3240 data
.numCases
= numCases
;
3241 data
.cases
= &cases
[0];
3242 data
.defaultOff
= bcOff() + iv
.strvec()[iv
.size() - 1].dest
;
3244 SSATmp
* dest
= gen(fastPath
? LdSSwitchDestFast
3245 : LdSSwitchDestSlow
,
3249 gen(DecRef
, testVal
);
3250 auto const stack
= spillStack();
3251 gen(SyncABIRegs
, m_tb
->fp(), stack
);
3252 gen(JmpIndirect
, dest
);
3256 void HhbcTranslator::emitRetC(bool freeInline
) {
3257 emitRet(Type::Cell
, freeInline
);
3260 void HhbcTranslator::emitRetV(bool freeInline
) {
3261 emitRet(Type::BoxedCell
, freeInline
);
3264 void HhbcTranslator::setThisAvailable() {
3265 m_tb
->setThisAvailable();
3268 void HhbcTranslator::guardTypeLocal(uint32_t locId
, Type type
, bool outerOnly
) {
3269 gen(GuardLoc
, type
, LocalId(locId
), m_tb
->fp());
3270 if (!outerOnly
&& type
.isBoxed() && type
.unbox() < Type::Cell
) {
3271 gen(LdRef
, type
.unbox(), makeExit(), ldLoc(locId
, DataTypeSpecific
));
3275 void HhbcTranslator::guardTypeLocation(const RegionDesc::Location
& loc
,
3276 Type type
, bool outerOnly
) {
3277 assert(type
<= Type::Gen
);
3278 typedef RegionDesc::Location::Tag T
;
3279 switch (loc
.tag()) {
3280 case T::Stack
: guardTypeStack(loc
.stackOffset(), type
, outerOnly
); break;
3281 case T::Local
: guardTypeLocal(loc
.localId(), type
, outerOnly
); break;
3285 void HhbcTranslator::checkTypeLocal(uint32_t locId
, Type type
,
3286 Offset dest
/* = -1 */) {
3287 gen(CheckLoc
, type
, LocalId(locId
), makeExit(dest
), m_tb
->fp());
3290 void HhbcTranslator::assertTypeLocal(uint32_t locId
, Type type
) {
3291 gen(AssertLoc
, type
, LocalId(locId
), m_tb
->fp());
3294 void HhbcTranslator::checkType(const RegionDesc::Location
& loc
,
3295 Type type
, Offset dest
) {
3296 assert(type
<= Type::Gen
);
3297 typedef RegionDesc::Location::Tag T
;
3298 switch (loc
.tag()) {
3299 case T::Stack
: checkTypeStack(loc
.stackOffset(), type
, dest
); break;
3300 case T::Local
: checkTypeLocal(loc
.localId(), type
, dest
); break;
3304 void HhbcTranslator::assertType(const RegionDesc::Location
& loc
,
3306 assert(type
<= Type::StackElem
);
3307 typedef RegionDesc::Location::Tag T
;
3308 switch (loc
.tag()) {
3309 case T::Stack
: assertTypeStack(loc
.stackOffset(), type
); break;
3310 case T::Local
: assertTypeLocal(loc
.localId(), type
); break;
3314 void HhbcTranslator::guardTypeStack(uint32_t stackIndex
, Type type
,
3316 assert(type
<= Type::Gen
);
3317 assert(m_evalStack
.size() == 0);
3318 assert(m_stackDeficit
== 0); // This should only be called at the beginning
3319 // of a trace, with a clean stack.
3320 auto stackOff
= StackOffset(stackIndex
);
3321 gen(GuardStk
, type
, stackOff
, m_tb
->sp());
3322 if (!outerOnly
&& type
.isBoxed() && type
.unbox() < Type::Cell
) {
3323 auto stk
= gen(LdStack
, Type::BoxedCell
, stackOff
, m_tb
->sp());
3324 m_tb
->constrainValue(stk
, DataTypeSpecific
);
3325 gen(LdRef
, type
.unbox(), makeExit(), stk
);
3329 void HhbcTranslator::checkTypeStack(uint32_t idx
, Type type
, Offset dest
) {
3330 assert(type
<= Type::Gen
);
3331 auto exit
= makeExit(dest
);
3332 if (idx
< m_evalStack
.size()) {
3333 FTRACE(1, "checkTypeStack({}): generating CheckType for {}\n",
3334 idx
, type
.toString());
3335 // CheckType only cares about its input type if the simplifier does
3336 // something with it and that's handled if and when it happens.
3337 SSATmp
* tmp
= m_evalStack
.top(DataTypeGeneric
, idx
);
3339 m_evalStack
.replace(idx
, gen(CheckType
, type
, exit
, tmp
));
3341 FTRACE(1, "checkTypeStack({}): no tmp: {}\n", idx
, type
.toString());
3342 // Just like CheckType, CheckStk only cares about its input type if the
3343 // simplifier does something with it.
3344 gen(CheckStk
, type
, exit
,
3345 StackOffset(idx
- m_evalStack
.size() + m_stackDeficit
), m_tb
->sp());
3349 void HhbcTranslator::checkTypeTopOfStack(Type type
, Offset nextByteCode
) {
3350 checkTypeStack(0, type
, nextByteCode
);
3353 void HhbcTranslator::assertTypeStack(uint32_t idx
, Type type
) {
3354 if (idx
< m_evalStack
.size()) {
3355 // We're asserting a new type so we don't care about the previous type.
3356 SSATmp
* tmp
= m_evalStack
.top(DataTypeGeneric
, idx
);
3358 m_evalStack
.replace(idx
, gen(AssertType
, type
, tmp
));
3360 gen(AssertStk
, type
,
3361 StackOffset(idx
- m_evalStack
.size() + m_stackDeficit
),
3366 void HhbcTranslator::assertClass(const RegionDesc::Location
& loc
,
3369 typedef RegionDesc::Location::Tag T
;
3370 switch (loc
.tag()) {
3372 curType
= topType(loc
.stackOffset(), DataTypeSpecific
);
3376 curType
= m_tb
->localType(loc
.localId(), DataTypeSpecific
);
3380 if (curType
.canSpecializeClass() && curType
.isSpecialized()) {
3381 curType
= curType
.unspecialize();
3383 assert(curType
.isBoxed() || curType
.notBoxed());
3384 if (curType
.canSpecializeClass()) {
3385 assertType(loc
, curType
.specialize(cls
) |
3386 (curType
.isBoxed() ? Type::BoxedInitNull
: Type::InitNull
));
3391 * Creates a RuntimeType struct from a program location. This needs access to
3392 * more than just the location's type because RuntimeType includes known
3393 * constant values. All accesses to the stack and locals use DataTypeGeneric so
3394 * this function should only be used for inspecting state; when the values are
3395 * actually used they must be constrained further.
3397 RuntimeType
HhbcTranslator::rttFromLocation(const Location
& loc
) {
3399 SSATmp
* val
= nullptr;
3400 switch (loc
.space
) {
3401 case Location::Stack
: {
3402 auto i
= loc
.offset
;
3404 if (i
< m_evalStack
.size()) {
3405 val
= m_evalStack
.top(DataTypeGeneric
, i
);
3408 auto stackVal
= getStackValue(m_tb
->sp(),
3409 i
- m_evalStack
.size() + m_stackDeficit
);
3410 val
= stackVal
.value
;
3411 t
= stackVal
.knownType
;
3412 if (!val
&& t
== Type::StackElem
) return RuntimeType(KindOfAny
);
3415 case Location::Local
: {
3416 auto l
= loc
.offset
;
3417 val
= m_tb
->localValue(l
, DataTypeGeneric
);
3418 t
= val
? val
->type() : m_tb
->localType(l
, DataTypeGeneric
);
3420 case Location::Litstr
:
3421 return RuntimeType(curUnit()->lookupLitstrId(loc
.offset
));
3422 case Location::Litint
:
3423 return RuntimeType(loc
.offset
);
3424 case Location::This
:
3425 return RuntimeType(KindOfObject
, KindOfNone
, curFunc()->cls());
3426 case Location::Invalid
:
3427 case Location::Iter
:
3431 assert(IMPLIES(val
, val
->type().equals(t
)));
3432 if (val
&& val
->isConst()) {
3433 // RuntimeType holds constant Bool, Int, Str, and Cls.
3434 if (val
->type().isBool()) return RuntimeType(val
->getValBool());
3435 if (val
->type().isInt()) return RuntimeType(val
->getValInt());
3436 if (val
->type().isString()) return RuntimeType(val
->getValStr());
3437 if (val
->type().isCls()) return RuntimeType(val
->getValClass());
3440 assert(t
!= Type::None
);
3441 return t
.toRuntimeType();
3444 static uint64_t packBitVec(const std::vector
<bool>& bits
, unsigned i
) {
3445 uint64_t retval
= 0;
3446 assert(i
% 64 == 0);
3447 assert(i
< bits
.size());
3448 while (i
< bits
.size()) {
3449 retval
|= bits
[i
] << (i
% 64);
3450 if ((++i
% 64) == 0) {
3457 void HhbcTranslator::guardRefs(int64_t entryArDelta
,
3458 const std::vector
<bool>& mask
,
3459 const std::vector
<bool>& vals
) {
3460 int32_t actRecOff
= cellsToBytes(entryArDelta
);
3461 SSATmp
* funcPtr
= gen(LdARFuncPtr
, m_tb
->sp(), cns(actRecOff
));
3462 SSATmp
* nParams
= nullptr;
3464 for (unsigned i
= 0; i
< mask
.size(); i
+= 64) {
3465 assert(i
< vals
.size());
3467 uint64_t mask64
= packBitVec(mask
, i
);
3471 uint64_t vals64
= packBitVec(vals
, i
);
3475 } else if (i
== 64) {
3477 LdRaw
, Type::Int
, funcPtr
, cns(RawMemSlot::FuncNumParams
)
3480 SSATmp
* maskTmp
= !(mask64
>>32) ? cns(mask64
) : m_tb
->genLdConst(mask64
);
3481 SSATmp
* valsTmp
= !(vals64
>>32) ? cns(vals64
) : m_tb
->genLdConst(vals64
);
3493 void HhbcTranslator::emitVerifyParamType(int32_t paramId
) {
3494 const Func
* func
= curFunc();
3495 auto const& tc
= func
->params()[paramId
].typeConstraint();
3496 auto locVal
= ldLoc(paramId
, DataTypeSpecific
);
3497 Type locType
= locVal
->type().unbox();
3498 if (!locType
.isKnownDataType()) {
3499 // This is supposed to be impossible, but it does happen in a rare case
3500 // with the legacy region selector. Until it's figured out, punt in release
3504 return folly::format("Bad type {} for local {}:\n\n{}\n",
3505 locType
, paramId
, m_tb
->unit().toString()).str();
3507 emitInterpOne(Type::None
, 0);
3511 if (!RuntimeOption::EvalCheckExtendedTypeHints
&& tc
.isExtended()) {
3514 if (tc
.isTypeVar()) {
3517 if (tc
.isNullable() && locType
.isNull()) {
3520 if (tc
.isCallable()) {
3521 locVal
= gen(Unbox
, makeExit(), locVal
);
3522 gen(VerifyParamCallable
, makeCatch(), locVal
, cns(paramId
));
3526 // For non-object guards, we rely on what we know from the tracelet
3527 // guards and never have to do runtime checks.
3528 if (!tc
.isObjectOrTypeAlias()) {
3529 if (locVal
->type().isBoxed()) {
3530 locVal
= gen(LdRef
, locVal
->type().innerType(), makeExit(), locVal
);
3532 if (!tc
.checkPrimitive(locType
.toDataType())) {
3533 gen(VerifyParamFail
, makeCatch(), cns(paramId
));
3540 * If the parameter is an object, we check the object in one of
3541 * various ways (similar to instance of). If the parameter is not
3542 * an object, it still might pass the VerifyParamType if the
3543 * constraint is a typedef.
3545 * For now we just interp that case.
3547 if (!locType
.isObj()) {
3548 emitInterpOne(Type::None
, 0);
3552 const StringData
* clsName
;
3553 const Class
* knownConstraint
= nullptr;
3554 if (!tc
.isSelf() && !tc
.isParent()) {
3555 clsName
= tc
.typeName();
3556 knownConstraint
= Unit::lookupClass(clsName
);
3559 tc
.selfToClass(curFunc(), &knownConstraint
);
3560 } else if (tc
.isParent()) {
3561 tc
.parentToClass(curFunc(), &knownConstraint
);
3563 if (knownConstraint
) {
3564 clsName
= knownConstraint
->preClass()->name();
3566 // The hint was self or parent and there's no corresponding
3567 // class for the current func. This typehint will always fail.
3568 gen(VerifyParamFail
, makeCatch(), cns(paramId
));
3574 // We can only burn in the Class* if it's unique or in the
3575 // inheritance hierarchy of our context. It's ok if the class isn't
3576 // defined yet - all paths below are tolerant of a null constraint.
3577 if (!classIsUniqueOrCtxParent(knownConstraint
)) knownConstraint
= nullptr;
3580 * If the local is a specialized object type, we can avoid emitting
3581 * runtime checks if we know the thing would pass. If we don't
3582 * know, we still have to emit them because locType might be a
3583 * subtype of its specialized object type.
3585 if (locType
.strictSubtypeOf(Type::Obj
)) {
3586 auto const cls
= locType
.getClass();
3587 if (knownConstraint
&& cls
->classof(knownConstraint
)) return;
3588 if (cls
->name()->isame(clsName
)) return;
3591 InstanceBits::init();
3592 bool haveBit
= InstanceBits::lookup(clsName
) != 0;
3593 SSATmp
* constraint
= knownConstraint
? cns(knownConstraint
)
3594 : gen(LdClsCachedSafe
, cns(clsName
));
3595 locVal
= gen(Unbox
, makeExit(), locVal
);
3596 SSATmp
* objClass
= gen(LdObjClass
, locVal
);
3597 if (haveBit
|| classIsUniqueNormalClass(knownConstraint
)) {
3598 SSATmp
* isInstance
= haveBit
3599 ? gen(InstanceOfBitmask
, objClass
, cns(clsName
))
3600 : gen(ExtendsClass
, objClass
, constraint
);
3601 m_tb
->ifThen([&](Block
* taken
) {
3602 gen(JmpZero
, taken
, isInstance
);
3604 [&] { // taken: the param type does not match
3605 m_tb
->hint(Block::Hint::Unlikely
);
3606 gen(VerifyParamFail
, makeCatch(), cns(paramId
));
3615 cns(uintptr_t(&tc
)));
3619 void HhbcTranslator::emitInstanceOfD(int classNameStrId
) {
3620 const StringData
* className
= lookupStringId(classNameStrId
);
3621 SSATmp
* src
= popC();
3624 * InstanceOfD is always false if it's not an object.
3626 * We're prepared to generate translations for known non-object
3627 * types, but if it's Gen/Cell we're going to PUNT because it's
3628 * natural to translate that case with control flow TODO(#2020251)
3630 if (Type::Obj
.strictSubtypeOf(src
->type())) {
3631 PUNT(InstanceOfD_MaybeObj
);
3633 if (!src
->isA(Type::Obj
)) {
3634 bool res
= ((src
->isA(Type::Arr
) && interface_supports_array(className
))) ||
3635 (src
->isA(Type::Str
) && interface_supports_string(className
)) ||
3636 (src
->isA(Type::Int
) && interface_supports_int(className
)) ||
3637 (src
->isA(Type::Dbl
) && interface_supports_double(className
));
3643 SSATmp
* objClass
= gen(LdObjClass
, src
);
3644 SSATmp
* ssaClassName
= cns(className
);
3646 InstanceBits::init();
3647 const bool haveBit
= InstanceBits::lookup(className
) != 0;
3649 Class
* const maybeCls
= Unit::lookupUniqueClass(className
);
3650 const bool isNormalClass
= classIsUniqueNormalClass(maybeCls
);
3651 const bool isUnique
= classIsUnique(maybeCls
);
3654 * If the class is a unique interface, we can just hit the class's
3655 * interfaces map and call it a day.
3657 if (!haveBit
&& classIsUniqueInterface(maybeCls
)) {
3658 push(gen(InstanceOfIface
, objClass
, ssaClassName
));
3664 * If the class is unique or a parent of the current context, we
3665 * don't need to load it out of RDS because it must already exist
3668 * Otherwise, we only use LdClsCachedSafe---instanceof with an
3669 * undefined class doesn't invoke autoload.
3671 SSATmp
* checkClass
=
3672 isUnique
|| (maybeCls
&& curClass() && curClass()->classof(maybeCls
))
3674 : gen(LdClsCachedSafe
, ssaClassName
);
3677 haveBit
? gen(InstanceOfBitmask
, objClass
, ssaClassName
)
3678 : isUnique
&& isNormalClass
? gen(ExtendsClass
, objClass
, checkClass
)
3679 : gen(InstanceOf
, objClass
, checkClass
)
3684 void HhbcTranslator::emitInstanceOf() {
3685 auto const t1
= popC();
3686 auto const t2
= popC(); // t2 instanceof t1
3688 if (t1
->isA(Type::Obj
) && t2
->isA(Type::Obj
)) {
3689 auto const c2
= gen(LdObjClass
, t2
);
3690 auto const c1
= gen(LdObjClass
, t1
);
3691 push(gen(InstanceOf
, c2
, c1
));
3697 if (!t1
->isA(Type::Str
)) PUNT(InstanceOf
-NotStr
);
3699 if (t2
->isA(Type::Obj
)) {
3700 auto const rds
= gen(LookupClsRDSHandle
, t1
);
3701 auto const c1
= gen(DerefClsRDSHandle
, rds
);
3702 auto const c2
= gen(LdObjClass
, t2
);
3703 push(gen(InstanceOf
, c2
, c1
));
3710 t2
->isA(Type::Arr
) ? gen(InterfaceSupportsArr
, t1
) :
3711 t2
->isA(Type::Int
) ? gen(InterfaceSupportsInt
, t1
) :
3712 t2
->isA(Type::Str
) ? gen(InterfaceSupportsStr
, t1
) :
3713 t2
->isA(Type::Dbl
) ? gen(InterfaceSupportsDbl
, t1
) :
3720 void HhbcTranslator::emitCastArray() {
3721 // Turns the castArray BC operation into a type specialized
3722 // IR operation. The IR operation might end up being simplified
3723 // into a constant, but if not, it simply turns into a helper
3724 // call when translated to machine code. The main benefit from
3725 // separate IR instructions is that they can have different flags,
3726 // principally to distinguish the instructions that (may) hold on to a
3727 // reference to argument, from instructions that do not.
3729 // In the future, if this instruction occurs in a hot trace,
3730 // it might be better to expand it into a series of primitive
3731 // IR instructions so that the object allocation is exposed to
3732 // the optimizer and becomes eligible for removal if it does not
3733 // escape the trace.
3735 auto catchBlock
= makeCatch();
3736 SSATmp
* src
= popC();
3737 Type fromType
= src
->type();
3738 if (fromType
.isArray()) {
3740 } else if (fromType
.isNull()) {
3741 push(cns(HphpArray::GetStaticEmptyArray()));
3742 } else if (fromType
.isBool()) {
3743 push(gen(ConvBoolToArr
, src
));
3744 } else if (fromType
.isDbl()) {
3745 push(gen(ConvDblToArr
, src
));
3746 } else if (fromType
.isInt()) {
3747 push(gen(ConvIntToArr
, src
));
3748 } else if (fromType
.isString()) {
3749 push(gen(ConvStrToArr
, src
));
3750 } else if (fromType
.isObj()) {
3751 push(gen(ConvObjToArr
, catchBlock
, src
));
3753 push(gen(ConvCellToArr
, catchBlock
, src
));
3757 void HhbcTranslator::emitCastBool() {
3758 auto const src
= popC();
3759 push(gen(ConvCellToBool
, src
));
3763 void HhbcTranslator::emitCastDouble() {
3764 auto const catchBlock
= makeCatch();
3765 auto const src
= popC();
3766 push(gen(ConvCellToDbl
, catchBlock
, src
));
3770 void HhbcTranslator::emitCastInt() {
3771 auto const catchBlock
= makeCatch();
3772 auto const src
= popC();
3773 push(gen(ConvCellToInt
, catchBlock
, src
));
3777 void HhbcTranslator::emitCastObject() {
3778 SSATmp
* src
= popC();
3779 Type srcType
= src
->type();
3780 if (srcType
.isObj()) {
3783 push(gen(ConvCellToObj
, src
));
3787 void HhbcTranslator::emitCastString() {
3788 auto const catchBlock
= makeCatch();
3789 auto const src
= popC();
3790 push(gen(ConvCellToStr
, catchBlock
, src
));
3794 static bool isSupportedAGet(SSATmp
* classSrc
) {
3795 return (classSrc
->isA(Type::Obj
) || classSrc
->isA(Type::Str
));
3798 void HhbcTranslator::emitAGet(SSATmp
* classSrc
, Block
* catchBlock
) {
3799 if (classSrc
->isA(Type::Str
)) {
3800 push(gen(LdCls
, catchBlock
, classSrc
, cns(curClass())));
3801 } else if (classSrc
->isA(Type::Obj
)) {
3802 push(gen(LdObjClass
, classSrc
));
3808 void HhbcTranslator::emitAGetC() {
3809 auto const name
= topC();
3810 if (isSupportedAGet(name
)) {
3812 emitAGet(name
, makeCatch({name
}));
3815 emitInterpOne(Type::Cls
, 1);
3819 void HhbcTranslator::emitAGetL(int id
) {
3820 auto const src
= ldLocInner(id
, makeExit(), DataTypeSpecific
);
3821 if (isSupportedAGet(src
)) {
3822 emitAGet(src
, makeCatch());
3824 PUNT(AGetL
); // need to teach interpone about local uses
3828 void HhbcTranslator::emitBindMem(SSATmp
* ptr
, SSATmp
* src
) {
3829 SSATmp
* prevValue
= gen(LdMem
, ptr
->type().deref(), ptr
, cns(0));
3832 gen(StMem
, ptr
, cns(0), src
);
3833 gen(DecRef
, prevValue
);
3836 void HhbcTranslator::emitEmptyMem(SSATmp
* ptr
) {
3837 SSATmp
* ld
= gen(LdMem
, Type::Cell
, gen(UnboxPtr
, ptr
), cns(0));
3838 push(gen(Not
, gen(ConvCellToBool
, ld
)));
3841 void HhbcTranslator::destroyName(SSATmp
* name
) {
3842 assert(name
== topC());
3843 popDecRef(name
->type());
3846 SSATmp
* HhbcTranslator::ldClsPropAddr(Block
* catchBlock
,
3850 * We currently can use LdClsPropAddrCached (which makes use of
3851 * SPropCache) if either we know which property it is and that it is
3852 * visible && accessible, or we know it is a property on this class
3853 * itself, provided in either case that the class has no 86sinit
3856 * TODO(#3575370): we should get the target cache lookup not to have
3857 * to do accessibility checks by doing them here.
3859 bool const useSpropCache
= [&] {
3860 if (!ssaName
->isConst()) return false;
3861 auto const propName
= ssaName
->getValStr();
3862 auto const clsName
= findClassName(ssaCls
);
3863 if (clsName
&& curClass() && curClass()->name()->isame(clsName
)) {
3867 if (!ssaCls
->isConst()) return false;
3868 auto const cls
= ssaCls
->getValClass();
3869 if (!classIsPersistentOrCtxParent(cls
)) return false;
3871 // TODO(#3575370): we should only check for 86sinit
3872 if (cls
->hasInitMethods()) return false;
3874 bool visible
, accessible
;
3875 cls
->getSProp(curClass(), propName
, visible
, accessible
);
3876 return visible
&& accessible
;
3879 auto const repoTy
= [&] {
3880 if (!useSpropCache
||
3881 !RuntimeOption::RepoAuthoritative
||
3882 !Repo::get().global().UsedHHBBC
) {
3883 return RepoAuthType
{};
3885 auto const slot
= ssaCls
->getValClass()->lookupSProp(ssaName
->getValStr());
3886 return ssaCls
->getValClass()->staticPropRepoAuthType(slot
);
3889 auto const ptrTy
= convertToType(repoTy
).ptr();
3890 if (useSpropCache
) {
3891 return gen(LdClsPropAddrCached
, ptrTy
,
3895 cns(findClassName(ssaCls
)),
3898 return gen(LdClsPropAddr
, catchBlock
, ssaCls
, ssaName
, cns(curClass()));
3901 void HhbcTranslator::emitCGetS() {
3902 auto const catchBlock
= makeCatch();
3903 auto const ssaPropName
= topC(1);
3905 if (!ssaPropName
->isA(Type::Str
)) {
3906 PUNT(CGetS
-PropNameNotString
);
3909 auto const ssaCls
= popA();
3910 auto const propAddr
= ldClsPropAddr(catchBlock
, ssaCls
, ssaPropName
);
3911 auto const unboxed
= gen(UnboxPtr
, propAddr
);
3912 auto const ldMem
= gen(LdMem
, unboxed
->type().deref(), unboxed
, cns(0));
3914 destroyName(ssaPropName
);
3918 void HhbcTranslator::emitSetS() {
3919 auto const catchBlock
= makeCatch();
3920 auto const ssaPropName
= topC(2);
3922 if (!ssaPropName
->isA(Type::Str
)) {
3923 PUNT(SetS
-PropNameNotString
);
3926 auto const value
= popC(DataTypeCountness
);
3927 auto const ssaCls
= popA();
3928 auto const propAddr
= ldClsPropAddr(catchBlock
, ssaCls
, ssaPropName
);
3929 auto const ptr
= gen(UnboxPtr
, propAddr
);
3931 destroyName(ssaPropName
);
3932 emitBindMem(ptr
, value
);
3935 void HhbcTranslator::emitVGetS() {
3936 auto const catchBlock
= makeCatch();
3937 auto const ssaPropName
= topC(1);
3939 if (!ssaPropName
->isA(Type::Str
)) {
3940 PUNT(VGetS
-PropNameNotString
);
3943 auto const ssaCls
= popA();
3944 auto const propAddr
= ldClsPropAddr(catchBlock
, ssaCls
, ssaPropName
);
3946 destroyName(ssaPropName
);
3947 pushIncRef(gen(LdMem
, Type::BoxedCell
, gen(BoxPtr
, propAddr
), cns(0)));
3950 void HhbcTranslator::emitBindS() {
3951 auto const catchBlock
= makeCatch();
3952 auto const ssaPropName
= topC(2);
3954 if (!ssaPropName
->isA(Type::Str
)) {
3955 PUNT(BindS
-PropNameNotString
);
3958 auto const value
= popV();
3959 auto const ssaCls
= popA();
3960 auto const propAddr
= ldClsPropAddr(catchBlock
, ssaCls
, ssaPropName
);
3962 destroyName(ssaPropName
);
3963 emitBindMem(propAddr
, value
);
3966 void HhbcTranslator::emitIssetS() {
3967 auto const ssaPropName
= topC(1);
3968 if (!ssaPropName
->isA(Type::Str
)) {
3969 PUNT(IssetS
-PropNameNotString
);
3972 auto const ssaCls
= popA();
3973 auto const ret
= m_tb
->cond(
3974 [&] (Block
* taken
) {
3975 return ldClsPropAddr(taken
, ssaCls
, ssaPropName
);
3977 [&] (SSATmp
* ptr
) { // Next: property or global exists
3978 return gen(IsNTypeMem
, Type::Null
, gen(UnboxPtr
, ptr
));
3980 [&] { // Taken: LdClsPropAddr* branched because it isn't defined
3985 destroyName(ssaPropName
);
3989 void HhbcTranslator::emitEmptyS() {
3990 auto const ssaPropName
= topC(1);
3991 if (!ssaPropName
->isA(Type::Str
)) {
3992 PUNT(EmptyS
-PropNameNotString
);
3995 auto const ssaCls
= popA();
3996 auto const ret
= m_tb
->cond(
3997 [&] (Block
* taken
) {
3998 return ldClsPropAddr(taken
, ssaCls
, ssaPropName
);
4001 auto const unbox
= gen(UnboxPtr
, ptr
);
4002 auto const val
= gen(LdMem
, unbox
->type().deref(), unbox
, cns(0));
4003 return gen(Not
, gen(ConvCellToBool
, val
));
4005 [&] { // Taken: LdClsPropAddr* branched because it isn't defined
4010 destroyName(ssaPropName
);
4014 void HhbcTranslator::emitCGetG() {
4015 auto const exit
= makeExitSlow();
4016 auto const name
= topC();
4017 if (!name
->isA(Type::Str
)) PUNT(CGetG
-NonStrName
);
4018 auto const ptr
= gen(LdGblAddr
, exit
, name
);
4020 pushIncRef(gen(LdMem
, Type::Cell
, gen(UnboxPtr
, ptr
), cns(0)));
4023 void HhbcTranslator::emitVGetG() {
4024 auto const name
= topC();
4025 if (!name
->isA(Type::Str
)) PUNT(VGetG
-NonStrName
);
4026 auto const ptr
= gen(LdGblAddrDef
, name
);
4028 pushIncRef(gen(LdMem
, Type::BoxedCell
, gen(BoxPtr
, ptr
), cns(0)));
4031 void HhbcTranslator::emitBindG() {
4032 auto const name
= topC(1);
4033 if (!name
->isA(Type::Str
)) PUNT(BindG
-NameNotStr
);
4034 auto const box
= popV();
4035 auto const ptr
= gen(LdGblAddrDef
, name
);
4037 emitBindMem(ptr
, box
);
4040 void HhbcTranslator::emitSetG() {
4041 auto const name
= topC(1);
4042 if (!name
->isA(Type::Str
)) PUNT(SetG
-NameNotStr
);
4043 auto const value
= popC(DataTypeCountness
);
4044 auto const unboxed
= gen(UnboxPtr
, gen(LdGblAddrDef
, name
));
4046 emitBindMem(unboxed
, value
);
4049 void HhbcTranslator::emitIssetG() {
4050 auto const name
= topC(0);
4051 if (!name
->isA(Type::Str
)) PUNT(IssetG
-NameNotStr
);
4053 auto const ret
= m_tb
->cond(
4054 [&] (Block
* taken
) {
4055 return gen(LdGblAddr
, taken
, name
);
4057 [&] (SSATmp
* ptr
) { // Next: global exists
4058 return gen(IsNTypeMem
, Type::Null
, gen(UnboxPtr
, ptr
));
4060 [&] { // Taken: global doesn't exist
4068 void HhbcTranslator::emitEmptyG() {
4069 auto const name
= topC();
4070 if (!name
->isA(Type::Str
)) PUNT(EmptyG
-NameNotStr
);
4072 auto const ret
= m_tb
->cond(
4073 [&] (Block
* taken
) {
4074 return gen(LdGblAddr
, taken
, name
);
4076 [&] (SSATmp
* ptr
) { // Next: global exists
4077 auto const unboxed
= gen(UnboxPtr
, ptr
);
4078 auto const val
= gen(LdMem
, Type::Cell
, unboxed
, cns(0));
4079 return gen(Not
, gen(ConvCellToBool
, val
));
4081 [&] { // Taken: global doesn't exist
4089 void HhbcTranslator::emitBinaryArith(Opcode opc
) {
4090 bool isBitOp
= (opc
== BitAnd
|| opc
== BitOr
|| opc
== BitXor
);
4091 Type type1
= topC(0)->type();
4092 Type type2
= topC(1)->type();
4093 if (areBinaryArithTypesSupported(opc
, type1
, type2
)) {
4094 SSATmp
* tr
= popC();
4095 SSATmp
* tl
= popC();
4096 tr
= (tr
->isA(Type::Bool
) ? gen(ConvBoolToInt
, tr
) : tr
);
4097 tl
= (tl
->isA(Type::Bool
) ? gen(ConvBoolToInt
, tl
) : tl
);
4098 push(gen(opc
, tl
, tr
));
4100 Type type
= Type::Int
;
4102 if (type1
.isString() && type2
.isString()) {
4104 } else if ((type1
.needsReg() && (type2
.needsReg() || type2
.isString()))
4105 || (type2
.needsReg() && type1
.isString())) {
4106 // both types might be strings, but can't tell
4112 // either an int or a dbl, but can't tell
4115 emitInterpOne(type
, 2);
4119 void HhbcTranslator::emitNot() {
4120 SSATmp
* src
= popC();
4121 push(gen(Not
, gen(ConvCellToBool
, src
)));
4125 void HhbcTranslator::emitFloor() {
4126 // need SSE 4.1 support to use roundsd
4127 if (!folly::CpuId().sse41()) {
4131 auto catchBlock
= makeCatch();
4133 auto dblVal
= gen(ConvCellToDbl
, catchBlock
, val
);
4135 push(gen(Floor
, dblVal
));
4138 void HhbcTranslator::emitCeil() {
4139 // need SSE 4.1 support to use roundsd
4140 if (!folly::CpuId().sse41()) {
4144 auto catchBlock
= makeCatch();
4146 auto dblVal
= gen(ConvCellToDbl
, catchBlock
, val
);
4148 push(gen(Ceil
, dblVal
));
4151 static folly::Optional
<Type
> assertOpToType(AssertTOp op
) {
4153 case AssertTOp::Uninit
: return Type::Uninit
;
4154 case AssertTOp::InitNull
: return Type::InitNull
;
4155 case AssertTOp::Int
: return Type::Int
;
4156 case AssertTOp::Dbl
: return Type::Dbl
;
4157 case AssertTOp::Res
: return Type::Res
;
4158 case AssertTOp::Null
: return Type::Null
;
4159 case AssertTOp::Bool
: return Type::Bool
;
4160 case AssertTOp::Str
: return Type::Str
;
4161 case AssertTOp::Arr
: return Type::Arr
;
4162 case AssertTOp::Obj
: return Type::Obj
;
4163 case AssertTOp::SStr
: return Type::StaticStr
;
4164 case AssertTOp::SArr
: return Type::StaticArr
;
4166 // These aren't enabled yet:
4167 case AssertTOp::OptInt
:
4168 case AssertTOp::OptObj
:
4169 case AssertTOp::OptDbl
:
4170 case AssertTOp::OptBool
:
4171 case AssertTOp::OptSStr
:
4172 case AssertTOp::OptSArr
:
4173 case AssertTOp::OptStr
:
4174 case AssertTOp::OptArr
:
4175 case AssertTOp::OptRes
:
4178 // We always know this at JIT time right now.
4179 case AssertTOp::Cell
:
4180 case AssertTOp::Ref
:
4183 // The JIT can't currently handle the exact information in these
4184 // type assertions in some cases:
4185 case AssertTOp::InitUnc
: return folly::none
;
4186 case AssertTOp::Unc
: return folly::none
;
4187 case AssertTOp::InitCell
: return Type::Cell
; // - Type::Uninit
4192 void HhbcTranslator::emitAssertTL(int32_t id
, AssertTOp op
) {
4193 if (auto const t
= assertOpToType(op
)) {
4194 assertTypeLocal(id
, *t
);
4198 void HhbcTranslator::emitAssertTStk(int32_t offset
, AssertTOp op
) {
4199 if (auto const t
= assertOpToType(op
)) {
4200 assertTypeStack(offset
, *t
);
4204 void HhbcTranslator::emitPredictTL(int32_t id
, AssertTOp op
) {
4205 if (auto const t
= assertOpToType(op
)) {
4206 // Side exit to the next instruction to avoid redoing the failed
4208 auto const nextBc
= curSrcKey().advanced().offset();
4209 checkTypeLocal(id
, *t
, nextBc
);
4213 void HhbcTranslator::emitPredictTStk(int32_t offset
, AssertTOp op
) {
4214 if (auto const t
= assertOpToType(op
)) {
4215 // Side exit to the next instruction to avoid redoing the failed
4217 auto const nextBc
= curSrcKey().advanced().offset();
4218 checkTypeStack(offset
, *t
, nextBc
);
4222 Type
HhbcTranslator::assertObjType(const StringData
* name
) {
4223 auto const cls
= Unit::lookupUniqueClass(name
);
4224 return classIsUniqueOrCtxParent(cls
) ? Type::Obj
.specialize(cls
) : Type::Obj
;
4227 static bool is_nullable(AssertObjOp op
) {
4229 case AssertObjOp::Exact
:
4230 case AssertObjOp::Sub
:
4232 case AssertObjOp::OptExact
:
4233 case AssertObjOp::OptSub
:
4239 void HhbcTranslator::emitAssertObjL(int32_t loc
, Id id
, AssertObjOp op
) {
4240 auto ty
= assertObjType(lookupStringId(id
));
4241 if (is_nullable(op
)) ty
= ty
| Type::InitNull
;
4242 assertTypeLocal(loc
, ty
);
4245 void HhbcTranslator::emitAssertObjStk(int32_t offset
, Id id
, AssertObjOp op
) {
4246 auto ty
= assertObjType(lookupStringId(id
));
4247 if (is_nullable(op
)) ty
= ty
| Type::InitNull
;
4248 assertTypeStack(offset
, ty
);
4251 void HhbcTranslator::emitAbs() {
4252 auto value
= popC();
4254 if (value
->isA(Type::Int
)) {
4255 push(gen(AbsInt
, value
));
4259 if (value
->isA(Type::Dbl
)) {
4260 push(gen(AbsDbl
, value
));
4264 if (value
->isA(Type::Arr
)) {
4273 #define BINOP(Opp) \
4274 void HhbcTranslator::emit ## Opp() { \
4275 emitBinaryArith(Opp); \
4287 void HhbcTranslator::emitDiv() {
4288 auto divisorType
= topC(0)->type();
4289 auto dividendType
= topC(1)->type();
4291 auto isNumeric
= [&] (Type type
) {
4292 return type
.subtypeOfAny(Type::Int
, Type::Dbl
, Type::Bool
);
4295 // not going to bother with string division etc.
4296 if (!isNumeric(divisorType
) || !isNumeric(dividendType
)) {
4297 emitInterpOne(Type::Cell
, 2);
4301 auto divisor
= topC(0);
4302 auto dividend
= topC(1);
4304 // we can't codegen this but we may be able to special case it away
4305 if (!divisor
->isA(Type::Dbl
) && !dividend
->isA(Type::Dbl
)) {
4306 // TODO(#2570625): support integer-integer division, move this to simlifier:
4307 if (divisor
->isConst()) {
4309 if (divisor
->isA(Type::Int
)) {
4310 divisorVal
= divisor
->getValInt();
4312 assert(divisor
->isA(Type::Bool
));
4313 divisorVal
= divisor
->getValBool();
4316 if (divisorVal
== 0) {
4319 gen(RaiseWarning
, makeCatch(),
4320 cns(makeStaticString(Strings::DIVISION_BY_ZERO
)));
4325 if (dividend
->isConst()) {
4326 int64_t dividendVal
;
4327 if (dividend
->isA(Type::Int
)) {
4328 dividendVal
= dividend
->getValInt();
4330 assert(dividend
->isA(Type::Bool
));
4331 dividendVal
= dividend
->getValBool();
4335 if (dividendVal
== LLONG_MIN
|| dividendVal
% divisorVal
) {
4336 push(cns((double)dividendVal
/ divisorVal
));
4338 push(cns(dividendVal
/ divisorVal
));
4344 emitInterpOne(Type::Cell
, 2);
4348 auto make_double
= [&] (SSATmp
* src
) {
4349 if (src
->isA(Type::Int
)) {
4350 return gen(ConvIntToDbl
, src
);
4351 } else if (src
->isA(Type::Bool
)) {
4352 return gen(ConvBoolToDbl
, src
);
4354 assert(src
->isA(Type::Dbl
));
4358 divisor
= make_double(popC());
4359 dividend
= make_double(popC());
4361 // on division by zero we spill false and exit with a warning
4362 auto exitSpillValues
= peekSpillValues();
4363 exitSpillValues
.push_back(cns(false));
4365 auto const exit
= makeExitWarn(nextBcOff(), exitSpillValues
,
4366 makeStaticString(Strings::DIVISION_BY_ZERO
));
4368 assert(divisor
->isA(Type::Dbl
) && dividend
->isA(Type::Dbl
));
4369 push(gen(DivDbl
, exit
, dividend
, divisor
));
4372 void HhbcTranslator::emitMod() {
4373 auto catchBlock1
= makeCatch();
4374 auto catchBlock2
= makeCatch();
4375 SSATmp
* btr
= popC();
4376 SSATmp
* btl
= popC();
4377 SSATmp
* tr
= gen(ConvCellToInt
, catchBlock1
, btr
);
4378 SSATmp
* tl
= gen(ConvCellToInt
, catchBlock2
, btl
);
4380 // We only want to decref btr and btl if the ConvCellToInt operation gave us
4381 // a new value back.
4382 if (tr
!= btr
) gen(DecRef
, btr
);
4383 if (tl
!= btl
) gen(DecRef
, btl
);
4384 // Exit path spills an additional false
4385 auto exitSpillValues
= peekSpillValues();
4386 exitSpillValues
.push_back(cns(false));
4388 // Generate an exit for the rare case that r is zero. Interpreting
4389 // will raise a notice and produce the boolean false. Punch out
4390 // here and resume after the Mod instruction; this should be rare.
4391 auto const exit
= makeExitWarn(nextBcOff(), exitSpillValues
,
4392 makeStaticString(Strings::DIVISION_BY_ZERO
));
4393 gen(JmpZero
, exit
, tr
);
4395 // We unfortunately need to special-case r = -1 here. In two's
4396 // complement, trying to divide INT_MIN by -1 will cause an integer
4398 if (tr
->isConst()) {
4399 // This whole block only exists so m_tb->cond doesn't get mad when one
4400 // of the branches gets optimized out due to constant folding.
4401 if (tr
->getValInt() == -1LL) {
4403 } else if (tr
->getValInt() == 0) {
4404 // mod by zero is undefined. don't emit opmod for it because
4405 // this could cause issues in simplifier/codegen
4406 // this should never get reached anyway, we just need to dump
4407 // something on the stack
4410 push(gen(Mod
, tl
, tr
));
4415 // check for -1 (dynamic version)
4416 SSATmp
*res
= m_tb
->cond(
4417 [&] (Block
* taken
) {
4418 SSATmp
* negone
= gen(Eq
, tr
, cns(-1));
4419 gen(JmpNZero
, taken
, negone
);
4422 return gen(Mod
, tl
, tr
);
4425 m_tb
->hint(Block::Hint::Unlikely
);
4432 void HhbcTranslator::emitSqrt() {
4433 auto const srcType
= topC()->type();
4434 if (srcType
<= Type::Int
) {
4435 auto const src
= gen(ConvIntToDbl
, popC());
4436 push(gen(Sqrt
, src
));
4440 if (srcType
<= Type::Dbl
) {
4441 auto const src
= popC();
4442 push(gen(Sqrt
, src
));
4446 emitInterpOne(Type::Cell
, 1);
4449 void HhbcTranslator::emitBitNot() {
4450 auto const srcType
= topC()->type();
4451 if (srcType
<= Type::Int
) {
4452 auto const src
= popC();
4453 push(gen(BitNot
, src
));
4457 if (srcType
<= Type::Dbl
) {
4458 auto const src
= gen(ConvDblToInt
, popC());
4459 push(gen(BitNot
, src
));
4463 auto const resultType
=
4464 srcType
.isString() ? Type::Str
:
4465 srcType
.needsReg() ? Type::Cell
:
4467 emitInterpOne(resultType
, 1);
4470 void HhbcTranslator::emitXor() {
4471 SSATmp
* btr
= popC();
4472 SSATmp
* btl
= popC();
4473 SSATmp
* tr
= gen(ConvCellToBool
, btr
);
4474 SSATmp
* tl
= gen(ConvCellToBool
, btl
);
4475 push(gen(ConvCellToBool
, gen(LogicXor
, tl
, tr
)));
4480 void HhbcTranslator::emitShl() {
4481 auto catch1
= makeCatch();
4482 auto catch2
= makeCatch();
4483 auto shiftAmount
= popC();
4486 auto lhsInt
= gen(ConvCellToInt
, catch1
, lhs
);
4487 auto shiftAmountInt
= gen(ConvCellToInt
, catch2
, shiftAmount
);
4489 push(gen(Shl
, lhsInt
, shiftAmountInt
));
4491 gen(DecRef
, shiftAmount
);
4494 void HhbcTranslator::emitShr() {
4495 auto catch1
= makeCatch();
4496 auto catch2
= makeCatch();
4497 auto shiftAmount
= popC();
4500 auto lhsInt
= gen(ConvCellToInt
, catch1
, lhs
);
4501 auto shiftAmountInt
= gen(ConvCellToInt
, catch2
, shiftAmount
);
4503 push(gen(Shr
, lhsInt
, shiftAmountInt
));
4505 gen(DecRef
, shiftAmount
);
4510 Type
arithOpResult(Type t1
, Type t2
) {
4511 if (!t1
.isKnownDataType() || !t2
.isKnownDataType()) {
4515 auto both
= t1
| t2
;
4516 if (both
.maybe(Type::Dbl
)) return Type::Dbl
;
4517 if (both
.maybe(Type::Arr
)) return Type::Arr
;
4518 if (both
.maybe(Type::Str
)) return Type::Cell
;
4522 Type
bitOpResult(Type t1
, Type t2
) {
4523 if (!t1
.isKnownDataType() || !t2
.isKnownDataType()) {
4527 auto both
= t1
| t2
;
4528 if (both
<= Type::Str
) return Type::Str
;
4532 Type
setOpResult(Type locType
, Type valType
, SetOpOp op
) {
4534 case SetOpOp::PlusEqual
:
4535 case SetOpOp::MinusEqual
:
4536 case SetOpOp::MulEqual
: return arithOpResult(locType
.unbox(), valType
);
4537 case SetOpOp::ConcatEqual
: return Type::Str
;
4538 case SetOpOp::DivEqual
:
4539 case SetOpOp::ModEqual
: return Type::Cell
;
4540 case SetOpOp::AndEqual
:
4541 case SetOpOp::OrEqual
:
4542 case SetOpOp::XorEqual
: return bitOpResult(locType
.unbox(), valType
);
4543 case SetOpOp::SlEqual
:
4544 case SetOpOp::SrEqual
: return Type::Int
;
4549 uint32_t localInputId(const NormalizedInstruction
& inst
) {
4550 switch (inst
.op()) {
4551 case OpSetWithRefLM
:
4553 return inst
.imm
[1].u_LA
;
4556 return inst
.imm
[0].u_LA
;
4562 Type
HhbcTranslator::interpOutputType(
4563 const NormalizedInstruction
& inst
,
4564 folly::Optional
<Type
>& checkTypeType
) const {
4565 using namespace JIT::InstrFlags
;
4566 auto localType
= [&]{
4567 auto locId
= localInputId(inst
);
4568 assert(locId
>= 0 && locId
< curFunc()->numLocals());
4569 return m_tb
->localType(locId
, DataTypeSpecific
);
4571 auto boxed
= [](Type t
) {
4572 if (t
.equals(Type::Gen
)) return t
;
4573 assert(t
.isBoxed() || t
.notBoxed());
4574 return t
.isBoxed() ? t
: boxType(t
);
4577 if (inst
.outputPredicted
) return Type::Gen
;
4579 auto outFlag
= getInstrInfo(inst
.op()).type
;
4580 if (outFlag
== OutFInputL
) {
4581 outFlag
= inst
.preppedByRef
? OutVInputL
: OutCInputL
;
4582 } else if (outFlag
== OutFInputR
) {
4583 outFlag
= inst
.preppedByRef
? OutVInput
: OutCInput
;
4587 case OutNull
: return Type::InitNull
;
4588 case OutNullUninit
: return Type::Uninit
;
4589 case OutString
: return Type::Str
;
4590 case OutStringImm
: return Type::StaticStr
;
4591 case OutDouble
: return Type::Dbl
;
4593 case OutBooleanImm
: return Type::Bool
;
4594 case OutInt64
: return Type::Int
;
4595 case OutArray
: return Type::Arr
;
4596 case OutArrayImm
: return Type::Arr
; // Should be StaticArr: t2124292
4598 case OutThisObject
: return Type::Obj
;
4599 case OutResource
: return Type::Res
;
4601 case OutFDesc
: return Type::None
;
4602 case OutUnknown
: return Type::Gen
;
4603 case OutPred
: return inst
.outPred
;
4604 case OutCns
: return Type::Cell
;
4605 case OutVUnknown
: return Type::BoxedCell
;
4607 case OutSameAsInput
: return topType(0);
4608 case OutVInput
: return boxed(topType(0));
4609 case OutVInputL
: return boxed(localType());
4611 case OutFInputR
: not_reached();
4613 case OutArith
: return arithOpResult(topType(0), topType(1));
4615 return bitOpResult(topType(0),
4616 inst
.op() == HPHP::OpBitNot
? Type::Bottom
4618 case OutSetOp
: return setOpResult(localType(), topType(0),
4619 SetOpOp(inst
.imm
[1].u_OA
));
4620 case OutIncDec
: return localType().unbox().isInt() ? Type::Int
4622 case OutStrlen
: return topType(0).isString() ? Type::Int
: Type::Cell
;
4623 case OutClassRef
: return Type::Cls
;
4624 case OutFPushCufSafe
: return Type::None
;
4625 case OutAsyncAwait
: return Type::None
; // custom in getStackValue
4627 case OutNone
: return Type::None
;
4630 auto ttype
= topType(0);
4631 if (ttype
.notBoxed()) return ttype
;
4632 // All instructions that are OutCInput or OutCInputL cannot push uninit or
4633 // a ref, so only specific inner types need to be checked.
4634 if (ttype
.unbox().strictSubtypeOf(Type::InitCell
)) {
4635 checkTypeType
= ttype
.unbox();
4641 auto ltype
= localType();
4642 if (ltype
.notBoxed()) return ltype
;
4643 if (ltype
.unbox().strictSubtypeOf(Type::InitCell
)) {
4644 checkTypeType
= ltype
.unbox();
4652 smart::vector
<InterpOneData::LocalType
>
4653 HhbcTranslator::interpOutputLocals(const NormalizedInstruction
& inst
,
4654 bool& smashesAllLocals
,
4656 using namespace JIT::InstrFlags
;
4657 if (!(getInstrInfo(inst
.op()).out
& Local
)) return {};
4659 smart::vector
<InterpOneData::LocalType
> locals
;
4660 auto setLocType
= [&](uint32_t id
, Type t
) {
4661 locals
.emplace_back(id
, t
);
4663 auto setImmLocType
= [&](uint32_t id
, Type t
) {
4664 setLocType(inst
.imm
[id
].u_LA
, t
);
4667 switch (inst
.op()) {
4668 case OpCreateCont
: case OpAsyncESuspend
: {
4669 auto numLocals
= curFunc()->numLocals();
4670 for (unsigned i
= 0; i
< numLocals
; ++i
) {
4671 setLocType(i
, Type::Uninit
);
4681 smashesAllLocals
= true;
4686 auto locType
= m_tb
->localType(localInputId(inst
), DataTypeSpecific
);
4687 assert(locType
< Type::Gen
);
4689 auto stackType
= inst
.outputPredicted
? inst
.outPred
: pushedType
;
4690 setImmLocType(0, locType
.isBoxed() ? stackType
.box() : stackType
);
4694 case OpStaticLocInit
:
4695 setImmLocType(0, Type::BoxedCell
);
4699 setImmLocType(0, Type::Gen
);
4703 auto locType
= m_tb
->localType(localInputId(inst
), DataTypeSpecific
);
4704 auto stackType
= topType(0);
4705 // SetL preserves reffiness of a local.
4706 setImmLocType(0, locType
.isBoxed() ? boxType(stackType
) : stackType
);
4711 assert(pushedType
.isBoxed());
4712 setImmLocType(0, pushedType
);
4718 setImmLocType(0, Type::Uninit
);
4725 case OpSetWithRefLM
:
4726 case OpSetWithRefRM
:
4729 switch (inst
.immVec
.locationCode()) {
4731 auto const& mii
= getMInstrInfo(inst
.mInstrOp());
4732 auto const& base
= inst
.inputs
[mii
.valCount()]->location
;
4733 assert(base
.space
== Location::Local
);
4735 // MInstrEffects expects to be used in the context of a normally
4736 // translated instruction, not an interpOne. The two important
4737 // differences are that the base is normally a PtrTo* and we need to
4738 // supply an IR opcode representing the operation. SetWithRefElem is
4739 // used instead of SetElem because SetElem makes a few assumptions
4740 // about side exits that interpOne won't do.
4741 auto const baseType
= m_tb
->localType(base
.offset
,
4742 DataTypeSpecific
).ptr();
4743 auto const isUnset
= inst
.op() == OpUnsetM
;
4744 auto const isProp
= mcodeIsProp(inst
.immVecM
[0]);
4746 if (isUnset
&& isProp
) break;
4747 auto op
= isProp
? SetProp
: isUnset
? UnsetElem
: SetWithRefElem
;
4748 MInstrEffects
effects(op
, baseType
);
4749 if (effects
.baseValChanged
) {
4750 setLocType(base
.offset
, effects
.baseType
.deref());
4757 smashesAllLocals
= true;
4767 setImmLocType(3, Type::Cell
);
4770 setImmLocType(2, Type::BoxedCell
);
4777 setImmLocType(3, Type::Cell
);
4782 setImmLocType(2, Type::Gen
);
4792 void HhbcTranslator::emitInterpOne(const NormalizedInstruction
& inst
) {
4793 folly::Optional
<Type
> checkTypeType
;
4794 auto stackType
= interpOutputType(inst
, checkTypeType
);
4795 auto popped
= getStackPopped(inst
.pc());
4796 auto pushed
= getStackPushed(inst
.pc());
4797 FTRACE(1, "emitting InterpOne for {}, result = {}, popped {}, pushed {}\n",
4798 inst
.toString(), stackType
.toString(), popped
, pushed
);
4800 InterpOneData idata
;
4801 auto locals
= interpOutputLocals(inst
, idata
.smashesAllLocals
, stackType
);
4802 idata
.nChangedLocals
= locals
.size();
4803 idata
.changedLocals
= locals
.data();
4805 emitInterpOne(stackType
, popped
, pushed
, idata
);
4806 if (checkTypeType
) {
4807 checkTypeTopOfStack(*checkTypeType
, inst
.nextSk().offset());
4811 void HhbcTranslator::emitInterpOne(Type outType
, int popped
) {
4812 InterpOneData idata
;
4813 emitInterpOne(outType
, popped
, outType
.equals(Type::None
) ? 0 : 1, idata
);
4816 void HhbcTranslator::emitInterpOne(Type outType
, int popped
, int pushed
,
4817 InterpOneData
& idata
) {
4818 auto unit
= curFunc()->unit();
4819 auto sp
= spillStack();
4820 auto op
= unit
->getOpcode(bcOff());
4822 auto& iInfo
= getInstrInfo(op
);
4823 if (iInfo
.type
== JIT::InstrFlags::OutFDesc
) {
4824 m_fpiStack
.emplace(sp
, m_tb
->spOffset());
4825 } else if (isFCallStar(op
) && !m_fpiStack
.empty()) {
4829 idata
.bcOff
= bcOff();
4830 idata
.cellsPopped
= popped
;
4831 idata
.cellsPushed
= pushed
;
4834 auto const changesPC
= opcodeChangesPC(idata
.opcode
);
4835 gen(changesPC
? InterpOneCF
: InterpOne
, outType
,
4836 makeCatch(), idata
, sp
, m_tb
->fp());
4837 assert(m_stackDeficit
== 0);
4839 if (changesPC
) m_hasExit
= true;
4842 std::string
HhbcTranslator::showStack() const {
4844 return folly::format("{:*^60}\n",
4845 " I don't understand inlining stacks yet ").str();
4847 std::ostringstream out
;
4848 auto header
= [&](const std::string
& str
) {
4849 out
<< folly::format("+{:-^62}+\n", str
);
4852 const int32_t frameCells
=
4853 curFunc()->isGenerator() ? 0 : curFunc()->numSlotsInFrame();
4854 const int32_t stackDepth
=
4855 m_tb
->spOffset() + m_evalStack
.size() - m_stackDeficit
- frameCells
;
4856 auto spOffset
= stackDepth
;
4857 auto elem
= [&](const std::string
& str
) {
4858 out
<< folly::format("| {:<60} |\n",
4859 folly::format("{:>2}: {}",
4860 stackDepth
- spOffset
, str
));
4861 assert(spOffset
> 0);
4865 auto fpi
= curFunc()->findFPI(bcOff());
4866 auto checkFpi
= [&]() {
4867 if (fpi
&& spOffset
+ frameCells
== fpi
->m_fpOff
) {
4868 auto fpushOff
= fpi
->m_fpushOff
;
4869 auto after
= fpushOff
+ instrLen((Op
*)curUnit()->at(fpushOff
));
4870 std::ostringstream msg
;
4871 msg
<< "ActRec from ";
4872 curUnit()->prettyPrint(msg
, Unit::PrintOpts().range(fpushOff
, after
)
4876 auto msgStr
= msg
.str();
4877 assert(msgStr
.back() == '\n');
4878 msgStr
.erase(msgStr
.size() - 1);
4879 for (unsigned i
= 0; i
< kNumActRecCells
; ++i
) elem(msgStr
);
4880 fpi
= fpi
->m_parentIndex
!= -1 ? &curFunc()->fpitab()[fpi
->m_parentIndex
]
4887 header(folly::format(" {} stack element(s); m_evalStack: ",
4889 for (unsigned i
= 0; i
< m_evalStack
.size(); ++i
) {
4891 SSATmp
* value
= m_evalStack
.top(DataTypeGeneric
, i
); // debug-only
4892 elem(value
->inst()->toString());
4895 header(" in-memory ");
4896 for (unsigned i
= m_stackDeficit
; spOffset
> 0; ) {
4897 assert(i
< curFunc()->maxStackCells());
4899 i
+= kNumActRecCells
;
4903 auto stkVal
= getStackValue(m_tb
->sp(), i
);
4904 std::ostringstream elemStr
;
4905 if (stkVal
.knownType
== Type::StackElem
) elem("unknown");
4906 else if (stkVal
.value
) elem(stkVal
.value
->inst()->toString());
4907 else elem(stkVal
.knownType
.toString());
4917 * Get SSATmps representing all the information on the virtual eval
4918 * stack in preparation for a spill or exit trace. Top of stack will
4921 * Doesn't actually remove these values from the eval stack.
4923 std::vector
<SSATmp
*> HhbcTranslator::peekSpillValues() const {
4924 std::vector
<SSATmp
*> ret
;
4925 ret
.reserve(m_evalStack
.size());
4926 for (int i
= 0; i
< m_evalStack
.size(); ++i
) {
4927 // DataTypeGeneric is used here because SpillStack just teleports the
4928 // values to memory.
4929 SSATmp
* elem
= m_evalStack
.top(DataTypeGeneric
, i
);
4930 ret
.push_back(elem
);
4935 Block
* HhbcTranslator::makeExit(Offset targetBcOff
/* = -1 */) {
4936 auto spillValues
= peekSpillValues();
4937 return makeExit(targetBcOff
, spillValues
);
4940 Block
* HhbcTranslator::makeExit(Offset targetBcOff
,
4941 std::vector
<SSATmp
*>& spillValues
) {
4942 if (targetBcOff
== -1) targetBcOff
= bcOff();
4943 return makeExitImpl(targetBcOff
, ExitFlag::JIT
, spillValues
, CustomExit
{});
4946 Block
* HhbcTranslator::makeExitWarn(Offset targetBcOff
,
4947 std::vector
<SSATmp
*>& spillValues
,
4948 const StringData
* warning
) {
4949 assert(targetBcOff
!= -1);
4950 return makeExitImpl(targetBcOff
, ExitFlag::JIT
, spillValues
,
4952 gen(RaiseWarning
, makeCatchNoSpill(), cns(warning
));
4958 template<class ExitLambda
>
4959 Block
* HhbcTranslator::makeSideExit(Offset targetBcOff
, ExitLambda exit
) {
4960 auto spillValues
= peekSpillValues();
4961 return makeExitImpl(targetBcOff
, ExitFlag::DelayedMarker
, spillValues
, exit
);
4964 Block
* HhbcTranslator::makeExitSlow() {
4965 auto spillValues
= peekSpillValues();
4966 return makeExitImpl(bcOff(), ExitFlag::Interp
, spillValues
, CustomExit
{});
4969 Block
* HhbcTranslator::makeExitOpt(TransID transId
) {
4970 auto spillValues
= peekSpillValues();
4971 Offset targetBcOff
= bcOff();
4972 auto const exit
= m_tb
->makeExit();
4974 BCMarker exitMarker
;
4975 exitMarker
.bcOff
= targetBcOff
;
4976 exitMarker
.spOff
= m_tb
->spOffset() + spillValues
.size() - m_stackDeficit
;
4977 exitMarker
.func
= curFunc();
4979 BlockPusher
blockPusher(*m_tb
, exitMarker
, exit
);
4981 SSATmp
* stack
= nullptr;
4982 if (m_stackDeficit
!= 0 || !spillValues
.empty()) {
4983 spillValues
.insert(spillValues
.begin(),
4984 { m_tb
->sp(), cns(int64_t(m_stackDeficit
)) });
4985 stack
= gen(SpillStack
,
4986 std::make_pair(spillValues
.size(), &spillValues
[0]));
4991 gen(SyncABIRegs
, m_tb
->fp(), stack
);
4992 gen(ReqRetranslateOpt
, ReqRetransOptData(transId
, targetBcOff
));
4997 Block
* HhbcTranslator::makeExitImpl(Offset targetBcOff
, ExitFlag flag
,
4998 std::vector
<SSATmp
*>& stackValues
,
4999 const CustomExit
& customFn
) {
5000 BCMarker exitMarker
;
5001 exitMarker
.bcOff
= targetBcOff
;
5002 exitMarker
.spOff
= m_tb
->spOffset() + stackValues
.size() - m_stackDeficit
;
5003 exitMarker
.func
= curFunc();
5005 BCMarker currentMarker
= makeMarker(bcOff());
5007 auto const exit
= m_tb
->makeExit();
5008 BlockPusher
tp(*m_tb
,
5009 flag
== ExitFlag::DelayedMarker
? currentMarker
: exitMarker
,
5012 // The value we use for stack is going to depend on whether we have
5013 // to spillstack or what.
5014 auto stack
= m_tb
->sp();
5016 // TODO(#2404447) move this conditional to the simplifier?
5017 if (m_stackDeficit
!= 0 || !stackValues
.empty()) {
5019 stackValues
.begin(),
5020 { m_tb
->sp(), cns(int64_t(m_stackDeficit
)) }
5022 stack
= gen(SpillStack
,
5023 std::make_pair(stackValues
.size(), &stackValues
[0]));
5027 stack
= gen(ExceptionBarrier
, stack
);
5028 auto const customTmp
= customFn();
5030 SSATmp
* spill2
[] = { stack
, cns(0), customTmp
};
5031 stack
= gen(SpillStack
,
5032 std::make_pair(sizeof spill2
/ sizeof spill2
[0], spill2
)
5034 exitMarker
.spOff
+= 1;
5038 if (flag
== ExitFlag::DelayedMarker
) {
5039 m_tb
->setMarker(exitMarker
);
5042 gen(SyncABIRegs
, m_tb
->fp(), stack
);
5044 if (flag
== ExitFlag::Interp
) {
5045 auto interpSk
= SrcKey
{curFunc(), targetBcOff
};
5046 auto pc
= curUnit()->at(targetBcOff
);
5047 auto changesPC
= opcodeChangesPC(toOp(*pc
));
5048 auto interpOp
= changesPC
? InterpOneCF
: InterpOne
;
5050 InterpOneData idata
;
5051 idata
.bcOff
= targetBcOff
;
5052 idata
.cellsPopped
= getStackPopped(pc
);
5053 idata
.cellsPushed
= getStackPushed(pc
);
5054 idata
.opcode
= toOp(*pc
);
5056 // Blindly using None as the output type here might seem bogus, but since
5057 // this trace is about to end, it doesn't matter for downstream analysis.
5058 gen(interpOp
, Type::None
, idata
, makeCatchNoSpill(), stack
, m_tb
->fp());
5061 // If the op changes PC, InterpOneCF handles getting to the right place
5062 gen(ReqBindJmp
, BCOffset(interpSk
.advanced().offset()));
5067 if (!isInlining() && bcOff() == m_startBcOff
&& targetBcOff
== m_startBcOff
) {
5068 // Note that if we're inlining, then targetBcOff is in the inlined func,
5069 // while m_startBcOff is in the outer func, so bindJmp will always work
5070 // (and there's no guarantee that there is an anchor translation, so we
5071 // must not use ReqRetranslate).
5072 gen(ReqRetranslate
);
5074 gen(ReqBindJmp
, BCOffset(targetBcOff
));
5080 * Create a catch block with a user-defined body (usually empty or a
5081 * SpillStack). Regardless of what body() does, it must return the current
5082 * stack pointer. This is a block to be invoked by the unwinder while unwinding
5083 * through a call to C++ from translated code. When attached to an instruction
5084 * as its taken field, code will be generated and the block will be registered
5085 * with the unwinder automatically.
5087 template<typename Body
>
5088 Block
* HhbcTranslator::makeCatchImpl(Body body
) {
5089 auto exit
= m_tb
->makeExit();
5091 BlockPusher
bp(*m_tb
, makeMarker(bcOff()), exit
);
5094 gen(EndCatch
, m_tb
->fp(), sp
);
5100 * Create a catch block that spills the current state of the eval stack. The
5101 * incoming value of spillVals will be the top of the spilled stack: values in
5102 * m_evalStack will be appended to spillVals to form the sources for the
5105 Block
* HhbcTranslator::makeCatch(std::vector
<SSATmp
*> spillVals
) {
5106 return makeCatchImpl([&] {
5107 for (auto* val
: peekSpillValues()) spillVals
.push_back(val
);
5108 return emitSpillStack(m_tb
->sp(), spillVals
);
5113 * Create a catch block with no SpillStack. Some of our optimizations rely on
5114 * the ability to insert code on *every* path out of a trace, so we can't
5115 * simply elide the catch block in the cases that want an empty body.
5117 Block
* HhbcTranslator::makeCatchNoSpill() {
5118 return makeCatchImpl([&] { return m_tb
->sp(); });
5121 SSATmp
* HhbcTranslator::emitSpillStack(SSATmp
* sp
,
5122 const std::vector
<SSATmp
*>& spillVals
) {
5123 std::vector
<SSATmp
*> ssaArgs
{ sp
, cns(int64_t(m_stackDeficit
)) };
5124 ssaArgs
.insert(ssaArgs
.end(), spillVals
.begin(), spillVals
.end());
5126 auto args
= std::make_pair(ssaArgs
.size(), &ssaArgs
[0]);
5127 return gen(SpillStack
, args
);
5130 SSATmp
* HhbcTranslator::spillStack() {
5131 auto newSp
= emitSpillStack(m_tb
->sp(), peekSpillValues());
5132 m_evalStack
.clear();
5137 void HhbcTranslator::exceptionBarrier() {
5138 auto const sp
= spillStack();
5139 gen(ExceptionBarrier
, sp
);
5142 SSATmp
* HhbcTranslator::ldStackAddr(int32_t offset
, TypeConstraint tc
) {
5143 // You're almost certainly doing it wrong if you want to get the address of a
5144 // stack cell that's in m_evalStack.
5145 m_tb
->constrainStack(offset
, tc
);
5146 assert(offset
>= (int32_t)m_evalStack
.numCells());
5150 StackOffset(offset
+ m_stackDeficit
- m_evalStack
.numCells()),
5155 SSATmp
* HhbcTranslator::ldLoc(uint32_t locId
, TypeConstraint tc
) {
5156 m_tb
->constrainLocal(locId
, tc
, "LdLoc");
5157 return gen(LdLoc
, Type::Gen
,
5158 LocalData(locId
, m_tb
->localValueSource(locId
)),
5162 SSATmp
* HhbcTranslator::ldLocAddr(uint32_t locId
, TypeConstraint tc
) {
5163 m_tb
->constrainLocal(locId
, tc
, "LdLocAddr");
5164 return gen(LdLocAddr
, Type::PtrToGen
,
5165 LocalData(locId
, m_tb
->localValueSource(locId
)),
5170 * Load a local, and if it's boxed dereference to get the inner cell.
5172 * Note: For boxed values, this will generate a LdRef instruction which
5173 * takes the given exit trace in case the inner type doesn't match
5174 * the tracked type for this local. This check may be optimized away
5175 * if we can determine that the inner type must match the tracked type.
5177 SSATmp
* HhbcTranslator::ldLocInner(uint32_t locId
, Block
* exit
,
5178 TypeConstraint constraint
) {
5179 // We only care if the local is KindOfRef or not. DataTypeCountness
5181 auto loc
= ldLoc(locId
, DataTypeCountness
);
5182 assert((loc
->type().isBoxed() || loc
->type().notBoxed()) &&
5183 "Currently we don't handle traces where locals are maybeBoxed");
5184 auto value
= loc
->type().isBoxed()
5185 ? gen(LdRef
, loc
->type().innerType(), exit
, loc
)
5187 m_tb
->constrainValue(value
, constraint
);
5192 * This is a wrapper to ldLocInner that also emits the RaiseUninitLoc if the
5193 * local is uninitialized. The catchBlock argument may be provided if the
5194 * caller requires the catch trace to be generated at a point earlier than when
5195 * it calls this function.
5197 SSATmp
* HhbcTranslator::ldLocInnerWarn(uint32_t id
, Block
* target
,
5198 TypeConstraint constraint
,
5199 Block
* catchBlock
/* = nullptr */) {
5200 if (!catchBlock
) catchBlock
= makeCatch();
5201 auto const locVal
= ldLocInner(id
, target
, constraint
);
5203 if (locVal
->type() <= Type::Uninit
) {
5204 m_tb
->constrainLocal(id
, DataTypeCountnessInit
, "ldLocInnerWarn");
5205 gen(RaiseUninitLoc
, catchBlock
, cns(curFunc()->localVarName(id
)));
5206 return m_tb
->genDefInitNull();
5213 * Store to a local, if it's boxed set the value on the inner cell.
5215 * Returns the value that was stored to the local. Assumes that 'newVal'
5216 * has already been incremented, with this Store consuming the
5217 * ref-count increment. If the caller of this function needs to
5218 * push the stored value on stack, it is responsible for incrementing
5219 * the ref-count before doing the push.
5221 * Pre: !newVal->type().isBoxed() && !newVal->type().maybeBoxed()
5222 * Pre: exit != nullptr if the local may be boxed
5224 SSATmp
* HhbcTranslator::stLocImpl(uint32_t id
,
5228 assert(!newVal
->type().maybeBoxed());
5230 auto const oldLoc
= ldLoc(id
, doRefCount
? DataTypeCountness
5232 assert(oldLoc
->type().isBoxed() || oldLoc
->type().notBoxed());
5234 if (oldLoc
->type().notBoxed()) {
5235 gen(StLoc
, LocalId(id
), m_tb
->fp(), newVal
);
5237 gen(DecRef
, oldLoc
);
5242 // It's important that the IncRef happens after the LdRef, since the
5243 // LdRef is also a guard on the inner type and may side-exit.
5244 auto const innerCell
= gen(
5245 LdRef
, oldLoc
->type().innerType(), exit
, oldLoc
5247 gen(StRef
, oldLoc
, newVal
);
5249 m_tb
->constrainValue(newVal
, DataTypeCountness
);
5250 gen(DecRef
, innerCell
);
5256 SSATmp
* HhbcTranslator::pushStLoc(uint32_t id
, Block
* exit
, SSATmp
* newVal
) {
5257 const bool doRefCount
= true;
5258 SSATmp
* ret
= stLocImpl(id
, exit
, newVal
, doRefCount
);
5259 return pushIncRef(ret
);
5262 SSATmp
* HhbcTranslator::stLoc(uint32_t id
, Block
* exit
, SSATmp
* newVal
) {
5263 const bool doRefCount
= true;
5264 return stLocImpl(id
, exit
, newVal
, doRefCount
);
5267 SSATmp
* HhbcTranslator::stLocNRC(uint32_t id
, Block
* exit
, SSATmp
* newVal
) {
5268 const bool doRefCount
= false;
5269 return stLocImpl(id
, exit
, newVal
, doRefCount
);
5272 void HhbcTranslator::end() {
5273 auto const nextSk
= curSrcKey().advanced(curUnit());
5274 end(nextSk
.offset());
5277 void HhbcTranslator::end(Offset nextPc
) {
5278 if (m_hasExit
) return;
5280 if (nextPc
>= curFunc()->past()) {
5281 // We have fallen off the end of the func's bytecodes. This happens
5282 // when the function's bytecodes end with an unconditional
5283 // backwards jump so that nextPc is out of bounds and causes an
5284 // assertion failure in unit.cpp. The common case for this comes
5285 // from the default value funclets, which are placed after the end
5286 // of the function, with an unconditional branch back to the start
5287 // of the function. So you should see this in any function with
5291 setBcOff(nextPc
, true);
5292 auto const sp
= spillStack();
5293 gen(SyncABIRegs
, m_tb
->fp(), sp
);
5294 gen(ReqBindJmp
, BCOffset(nextPc
));
5298 void HhbcTranslator::checkStrictlyInteger(
5299 SSATmp
*& key
, KeyType
& keyType
, bool& checkForInt
) {
5300 checkForInt
= false;
5301 if (key
->isA(Type::Int
)) {
5302 keyType
= KeyType::Int
;
5304 assert(key
->isA(Type::Str
));
5305 keyType
= KeyType::Str
;
5306 if (key
->isConst()) {
5308 if (key
->getValStr()->isStrictlyInteger(i
)) {
5309 keyType
= KeyType::Int
;
5318 }} // namespace HPHP::JIT