Simplify region selector/pgo options, fix pgo for legacy/tracelet selectors
[hiphop-php.git] / hphp / runtime / vm / jit / hhbc-translator.cpp
blob29541482fdf5fd9189fe09e1d11a518b399b684e
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
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"
39 namespace HPHP {
40 namespace JIT {
42 TRACE_SET_MOD(hhir);
45 //////////////////////////////////////////////////////////////////////
47 namespace {
49 bool classIsUnique(const Class* cls) {
50 return RuntimeOption::RepoAuthoritative &&
51 cls &&
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,
71 const Func* func)
72 : m_unit(startOffset)
73 , m_tb(new TraceBuilder(startOffset,
74 initialSpOffsetFromFp,
75 m_unit,
76 func))
77 , m_bcStateStack {BcState(startOffset, func)}
78 , m_startBcOff(startOffset)
79 , m_lastBcOff(false)
80 , m_hasExit(false)
81 , m_stackDeficit(0)
82 , m_evalStack(*m_tb)
84 updateMarker();
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) {
128 assert(tmp);
129 FTRACE(2, "HhbcTranslator pushing {}\n", *tmp->inst());
130 m_evalStack.push(tmp);
131 return 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();
140 if (opc == Mov) {
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
145 tmp->setType(type);
146 } else if (tmp->type().isArray() && type.isArray()) {
147 // Refine array kind
148 tmp->setType(type);
149 } else {
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 ||
158 opc == LdRef);
159 inst->setTypeParam(type);
160 tmp->setType(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;
171 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());
175 return value;
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());
183 return opnd;
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)) {
195 gen(DecRef, src);
196 return;
199 m_tb->constrainStack(m_stackDeficit, tc);
200 gen(DecRefStack, StackOffset(m_stackDeficit), type, m_tb->sp());
201 m_stackDeficit++;
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
208 // the known type.
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
212 // elsewhere.
213 if (index == 0) {
214 push(pop(type, DataTypeGeneric));
215 return;
218 SSATmp* tmp = pop(Type::StackElem, DataTypeGeneric);
219 extendStack(index - 1, type);
220 push(tmp);
223 SSATmp* HhbcTranslator::top(Type type, uint32_t index,
224 TypeConstraint constraint) {
225 SSATmp* tmp = m_evalStack.top(constraint, index);
226 if (!tmp) {
227 extendStack(index, type);
228 tmp = m_evalStack.top(constraint, index);
230 assert(tmp);
231 refineType(tmp, type);
232 return tmp;
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();
243 } else {
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:
259 * fp0 = DefFP
260 * sp0 = DefSP<offset>
262 * // ... normal stuff happens ...
263 * // sp_pre = some SpillStack, or maybe the DefSP
265 * // FPI region:
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 ...
276 * = InlineReturn fp2
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
285 * is popped.
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
304 * the stack.
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
314 * simplifier.
316 void HhbcTranslator::beginInlining(unsigned numParams,
317 const Func* target,
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);
345 updateMarker();
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()));
365 m_fpiStack.pop();
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;
395 gen(
396 IncStatGrouped,
397 cns(makeStaticString("FunctionEntry")),
398 cns(makeStaticString(category)),
399 cns(1)
403 void HhbcTranslator::profileInlineFunctionShape(const std::string& str) {
404 gen(
405 IncStatGrouped,
406 cns(makeStaticString("InlineShape")),
407 cns(makeStaticString(str)),
408 cns(1)
412 void HhbcTranslator::profileSmallFunctionShape(const std::string& str) {
413 gen(
414 IncStatGrouped,
415 cns(makeStaticString("SmallFunctions")),
416 cns(makeStaticString(str)),
417 cns(1)
421 void HhbcTranslator::profileFailedInlShape(const std::string& str) {
422 gen(
423 IncStatGrouped,
424 cns(makeStaticString("FailedInl")),
425 cns(makeStaticString(str)),
426 cns(1)
430 void HhbcTranslator::setBcOff(Offset newOff, bool lastBcOff) {
431 if (isInlining()) assert(!lastBcOff);
433 m_bcStateStack.back().bcOff = newOff;
434 updateMarker();
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();
443 Opcode op;
444 if (type.isString()) {
445 op = PrintStr;
446 } else if (type <= Type::Int) {
447 op = PrintInt;
448 } else if (type <= Type::Bool) {
449 op = PrintBool;
450 } else {
451 assert(type.isNull());
452 op = Nop;
454 // the print helpers decref their arg, so don't decref pop'ed value
455 if (op != Nop) {
456 gen(op, cell);
458 push(cns(1));
459 } else {
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
470 push(unboxed);
471 } else {
472 pushIncRef(unboxed);
473 gen(DecRef, srcBox);
477 void HhbcTranslator::emitUnboxR() {
478 emitUnboxRAux();
481 void HhbcTranslator::emitThis() {
482 if (!curClass()) {
483 emitInterpOne(Type::Obj, 0); // will throw a fatal
484 return;
486 pushIncRef(gen(LdThis, makeExitSlow(), m_tb->fp()));
489 void HhbcTranslator::emitCheckThis() {
490 if (!curClass()) {
491 emitInterpOne(Type::None, 0); // will throw a fatal
492 return;
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,
504 int level) {
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.
521 if (!curClass()) {
522 emitInterpOne(Type::InitNull, 0); // will raise notice and push null
523 return;
525 if (notice == static_cast<int>(BareThisOp::NeverNull)) {
526 setThisAvailable();
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) {
536 if (capacity == 0) {
537 push(cns(HphpArray::GetStaticEmptyArray()));
538 } else {
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
552 // getStackValue().
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();
567 NewStructData extra;
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
583 // instruction.
584 emitInterpOne(Type::Arr, 2);
585 return;
587 SSATmp* tr = popC();
588 SSATmp* tl = popC();
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();
597 Opcode op;
598 if (kt <= Type::Int) {
599 op = AddElemIntKey;
600 } else if (kt.isString()) {
601 op = AddElemStrKey;
602 } else {
603 emitInterpOne(Type::Arr, 3);
604 return;
607 // val is teleported from the stack to the array, so we don't have to do any
608 // refcounting.
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
613 // values.
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));
637 gen(DecRef, 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);
647 return;
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));
655 gen(DecRef, key);
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,
671 uint32_t fallback,
672 bool error) {
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);
684 if (tv) {
685 if (tv->m_type == KindOfUninit) {
686 // KindOfUninit is a dynamic system constant. always a slow
687 // lookup.
688 assert(!fallbackNameTmp);
689 if (error) {
690 result = gen(LookupCnsE, cnsNameTmp);
691 } else {
692 result = gen(LookupCns, makeCatch(), cnsNameTmp);
694 } else {
695 result = staticTVCns(tv);
697 } else {
698 SSATmp* c1 = gen(LdCns, cnsNameTmp);
699 result = m_tb->cond(
700 [&] (Block* taken) { // branch
701 gen(CheckInit, taken, c1);
703 [&] { // Next: LdCns hit in TC
704 return c1;
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);
712 if (error) {
713 return gen(LookupCnsE, makeCatch(), cnsNameTmp);
715 return gen(LookupCns, makeCatch(), cnsNameTmp);
719 push(result);
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();
740 SSATmp* tr = popC();
741 SSATmp* tl = popC();
742 // Concat consumes only first ref, never second
743 push(gen(ConcatCellCell, catchBlock, tl, tr));
744 // so we need to consume second ref ourselves
745 gen(DecRef, tr);
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();
758 if (!clss) {
759 // no static context class, so this will raise an error
760 emitInterpOne(Type::Cls, 0);
761 return;
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);
771 } else {
772 push(cns(clss));
776 void HhbcTranslator::emitParent() {
777 auto const clss = curClass();
778 if (clss == nullptr || clss->parent() == nullptr) {
779 emitInterpOne(Type::Cls, 0);
780 } else {
781 push(cns(clss->parent()));
785 void HhbcTranslator::emitString(int strId) {
786 push(cns(lookupStringId(strId)));
789 void HhbcTranslator::emitInt(int64_t val) {
790 push(cns(val));
793 void HhbcTranslator::emitDouble(double val) {
794 push(cns(val));
797 void HhbcTranslator::emitNullUninit() {
798 push(m_tb->genDefUninit());
801 void HhbcTranslator::emitNull() {
802 push(m_tb->genDefInitNull());
805 void HhbcTranslator::emitTrue() {
806 push(cns(true));
809 void HhbcTranslator::emitFalse() {
810 push(cns(false));
813 void HhbcTranslator::emitInitThisLoc(int32_t id) {
814 if (!curClass()) {
815 // Do nothing if this is null
816 return;
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);
831 push(locVal);
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));
840 push(oldTop);
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);
852 pushIncRef(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());
858 gen(DecRef, prev);
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)) {
887 push(src);
888 return;
891 if (src->type().subtypeOfAny(Type::Arr, Type::Obj)) {
892 pushIncRef(src);
893 return;
896 if (src->isA(Type::Null)) {
897 if (inc) {
898 push(cns(1));
899 stLoc(id, exit, cns(1));
900 } else {
901 push(src);
903 return;
906 if (!src->type().subtypeOfAny(Type::Int, Type::Dbl)) {
907 PUNT(IncDecL);
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);
921 return res;
924 static bool areBinaryArithTypesSupported(Opcode opc, Type t1, Type t2) {
925 switch (opc) {
926 case Add:
927 case Sub:
928 case Mul: return t1.subtypeOfAny(Type::Int, Type::Bool, Type::Dbl) &&
929 t2.subtypeOfAny(Type::Int, Type::Bool, Type::Dbl);
931 case BitAnd:
932 case BitOr:
933 case BitXor:
934 return t1.subtypeOfAny(Type::Int, Type::Bool) &&
935 t2.subtypeOfAny(Type::Int, Type::Bool);
936 default:
937 not_reached();
941 void HhbcTranslator::emitSetOpL(Opcode subOpc, uint32_t id) {
943 * Handle array addition first because we don't want to bother with
944 * boxed locals.
946 if (subOpc == Add &&
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
952 * the stack.
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);
959 pushIncRef(result);
960 return;
963 auto const exitBlock = makeExit();
964 auto const catchBlock = makeCatch();
965 auto const loc = ldLocInnerWarn(id, exitBlock, DataTypeSpecific,
966 catchBlock);
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
977 gen(DecRef, val);
978 return;
981 if (areBinaryArithTypesSupported(subOpc, loc->type(), topC()->type())) {
982 auto const val = popC();
983 auto const result = gen(
984 subOpc,
985 loc->isA(Type::Bool) ? gen(ConvBoolToInt, loc) : loc,
986 val->isA(Type::Bool) ? gen(ConvBoolToInt, val) : val
988 pushStLoc(id, nullptr, result);
989 return;
992 PUNT(SetOpL);
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);
1007 auto const exists =
1008 gen(ThingExists, catchTrace, ClassKindData { kind }, tCls);
1009 popC(); popC(); push(exists);
1010 gen(DecRef, tCls);
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 });
1038 m_tb->ifThen(
1039 [&] (Block* taken) {
1040 gen(CheckStaticLocInit, taken, cachedBox);
1042 [&] {
1043 m_tb->hint(Block::Hint::Unlikely);
1044 gen(StaticLocInitCached, cachedBox, value);
1047 return cachedBox;
1048 }();
1049 gen(IncRef, box);
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()
1064 gen(IncRef, box);
1065 gen(StLoc, LocalId(locId), m_tb->fp(), box);
1066 push(cns(true));
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);
1092 gen(IncRef, box);
1093 auto const oldValue = ldLoc(locId, DataTypeCountness);
1094 gen(StLoc, LocalId(locId), m_tb->fp(), box);
1095 gen(DecRef, oldValue);
1096 push(res);
1099 template<class Lambda>
1100 SSATmp* HhbcTranslator::emitIterInitCommon(int offset, Lambda genFunc,
1101 bool invertCond) {
1102 SSATmp* src = popC();
1103 Type type = src->type();
1104 if (!type.isArray() && type != Type::Obj) {
1105 PUNT(IterInit);
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();
1122 gen(DecRef, out);
1123 return emitJmpCondHelper(offset, true, res);
1126 namespace {
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,
1137 int offset,
1138 uint32_t valLocalId,
1139 bool invertCond) {
1140 constrainIterLocals(*m_tb, valLocalId);
1142 auto catchBlock = makeCatch();
1143 emitIterInitCommon(offset, [&] (SSATmp* src) {
1144 return gen(IterInit,
1145 Type::Bool,
1146 catchBlock,
1147 src,
1148 m_tb->fp(),
1149 cns(iterId),
1150 cns(valLocalId));
1152 invertCond);
1155 void HhbcTranslator::emitIterInitK(uint32_t iterId,
1156 int offset,
1157 uint32_t valLocalId,
1158 uint32_t keyLocalId,
1159 bool invertCond) {
1160 constrainIterLocals(*m_tb, valLocalId, keyLocalId);
1162 auto catchBlock = makeCatch();
1163 emitIterInitCommon(offset, [&] (SSATmp* src) {
1164 return gen(IterInitK,
1165 Type::Bool,
1166 catchBlock,
1167 src,
1168 m_tb->fp(),
1169 cns(iterId),
1170 cns(valLocalId),
1171 cns(keyLocalId));
1173 invertCond);
1176 void HhbcTranslator::emitIterNext(uint32_t iterId,
1177 int offset,
1178 uint32_t valLocalId,
1179 bool invertCond) {
1180 constrainIterLocals(*m_tb, valLocalId);
1182 SSATmp* res = gen(
1183 IterNext,
1184 Type::Bool,
1185 makeCatch(),
1186 m_tb->fp(),
1187 cns(iterId),
1188 cns(valLocalId)
1190 emitJmpCondHelper(offset, invertCond, res);
1193 void HhbcTranslator::emitIterNextK(uint32_t iterId,
1194 int offset,
1195 uint32_t valLocalId,
1196 uint32_t keyLocalId,
1197 bool invertCond) {
1198 constrainIterLocals(*m_tb, valLocalId, keyLocalId);
1200 SSATmp* res = gen(
1201 IterNextK,
1202 Type::Bool,
1203 makeCatch(),
1204 m_tb->fp(),
1205 cns(iterId),
1206 cns(valLocalId),
1207 cns(keyLocalId)
1209 emitJmpCondHelper(offset, invertCond, res);
1212 void HhbcTranslator::emitWIterInit(uint32_t iterId,
1213 int offset,
1214 uint32_t valLocalId,
1215 bool invertCond) {
1216 constrainIterLocals(*m_tb, valLocalId);
1218 auto catchBlock = makeCatch();
1219 emitIterInitCommon(
1220 offset, [&] (SSATmp* src) {
1221 return gen(WIterInit,
1222 Type::Bool,
1223 catchBlock,
1224 src,
1225 m_tb->fp(),
1226 cns(iterId),
1227 cns(valLocalId));
1229 invertCond);
1232 void HhbcTranslator::emitWIterInitK(uint32_t iterId,
1233 int offset,
1234 uint32_t valLocalId,
1235 uint32_t keyLocalId,
1236 bool invertCond) {
1237 constrainIterLocals(*m_tb, valLocalId, keyLocalId);
1239 auto catchBlock = makeCatch();
1240 emitIterInitCommon(
1241 offset, [&] (SSATmp* src) {
1242 return gen(WIterInitK,
1243 Type::Bool,
1244 catchBlock,
1245 src,
1246 m_tb->fp(),
1247 cns(iterId),
1248 cns(valLocalId),
1249 cns(keyLocalId));
1251 invertCond);
1254 void HhbcTranslator::emitWIterNext(uint32_t iterId,
1255 int offset,
1256 uint32_t valLocalId,
1257 bool invertCond) {
1258 constrainIterLocals(*m_tb, valLocalId);
1260 SSATmp* res = gen(
1261 WIterNext,
1262 Type::Bool,
1263 makeCatch(),
1264 m_tb->fp(),
1265 cns(iterId),
1266 cns(valLocalId)
1268 emitJmpCondHelper(offset, invertCond, res);
1271 void HhbcTranslator::emitWIterNextK(uint32_t iterId,
1272 int offset,
1273 uint32_t valLocalId,
1274 uint32_t keyLocalId,
1275 bool invertCond) {
1276 constrainIterLocals(*m_tb, valLocalId, keyLocalId);
1278 SSATmp* res = gen(
1279 WIterNextK,
1280 Type::Bool,
1281 makeCatch(),
1282 m_tb->fp(),
1283 cns(iterId),
1284 cns(valLocalId),
1285 cns(keyLocalId)
1287 emitJmpCondHelper(offset, invertCond, res);
1290 void HhbcTranslator::emitMIterInit(uint32_t iterId,
1291 int offset,
1292 uint32_t valLocalId) {
1293 constrainIterLocals(*m_tb, valLocalId);
1295 auto catchBlock = makeCatch();
1296 emitMIterInitCommon(offset, [&] (SSATmp* src) {
1297 return gen(
1298 MIterInit,
1299 Type::Bool,
1300 catchBlock,
1301 src,
1302 m_tb->fp(),
1303 cns(iterId),
1304 cns(valLocalId)
1309 void HhbcTranslator::emitMIterInitK(uint32_t iterId,
1310 int offset,
1311 uint32_t valLocalId,
1312 uint32_t keyLocalId) {
1313 constrainIterLocals(*m_tb, valLocalId, keyLocalId);
1315 auto catchBlock = makeCatch();
1316 emitMIterInitCommon(offset, [&] (SSATmp* src) {
1317 return gen(
1318 MIterInitK,
1319 Type::Bool,
1320 catchBlock,
1321 src,
1322 m_tb->fp(),
1323 cns(iterId),
1324 cns(valLocalId),
1325 cns(keyLocalId)
1330 void HhbcTranslator::emitMIterNext(uint32_t iterId,
1331 int offset,
1332 uint32_t valLocalId) {
1333 constrainIterLocals(*m_tb, valLocalId);
1335 SSATmp* res = gen(
1336 MIterNext,
1337 Type::Bool,
1338 m_tb->fp(),
1339 cns(iterId),
1340 cns(valLocalId)
1342 emitJmpCondHelper(offset, false, res);
1345 void HhbcTranslator::emitMIterNextK(uint32_t iterId,
1346 int offset,
1347 uint32_t valLocalId,
1348 uint32_t keyLocalId) {
1349 constrainIterLocals(*m_tb, valLocalId, keyLocalId);
1351 SSATmp* res = gen(
1352 MIterNextK,
1353 Type::Bool,
1354 m_tb->fp(),
1355 cns(iterId),
1356 cns(valLocalId),
1357 cns(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());
1376 gen(DecRef, src);
1377 emitJmpCondHelper(offset, true, res);
1378 } else {
1379 gen(DecRef, src);
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,
1389 uint32_t offset,
1390 bool breakTracelet) {
1391 int iterIndex;
1392 for (iterIndex = 0; iterIndex < iv.size(); iterIndex += 2) {
1393 IterKind iterKind = (IterKind)iv.vec32()[iterIndex];
1394 Id iterId = iv.vec32()[iterIndex + 1];
1395 switch (iterKind) {
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()
1413 ? gen(
1414 CreateContMeth,
1415 CreateContData { genFunc },
1416 gen(LdCtx, FuncData(curFunc()), m_tb->fp())
1418 : gen(
1419 CreateContFunc,
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());
1440 if (fillThis) {
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);
1447 push(cont);
1450 void HhbcTranslator::emitContEnter(int32_t returnBcOffset) {
1451 // make sure the value to be sent is on the actual stack
1452 spillStack();
1454 assert(curClass());
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);
1466 gen(
1467 ContEnter,
1468 contAR,
1469 funcBody,
1470 cns(returnBcOffset),
1471 m_tb->fp()
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);
1483 m_hasExit = true;
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);
1514 } else {
1515 // we're guaranteed that the key is an int
1516 gen(ContArIncKey, m_tb->fp());
1519 // transfer control
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);
1536 // transfer control
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);
1550 // transfer control
1551 emitContReturnControl();
1554 void HhbcTranslator::emitContCheck(bool checkStarted) {
1555 assert(curClass());
1556 SSATmp* cont = gen(LdThis, m_tb->fp());
1557 if (checkStarted) {
1558 gen(ContStartedCheck, makeExitSlow(), cont);
1560 gen(ContPreNext, makeExitSlow(), cont);
1563 void HhbcTranslator::emitContRaise() {
1564 assert(curClass());
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() {
1572 assert(curClass());
1573 SSATmp* cont = gen(LdThis, m_tb->fp());
1574 push(gen(ContValid, cont));
1577 void HhbcTranslator::emitContKey() {
1578 assert(curClass());
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);
1583 pushIncRef(value);
1586 void HhbcTranslator::emitContCurrent() {
1587 assert(curClass());
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);
1592 pushIncRef(value);
1595 void HhbcTranslator::emitContStopped() {
1596 assert(curClass());
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.
1623 return obj;
1625 [&] { // Taken: retrieve the result from the wait handle
1626 auto const res = gen(LdWHResult, obj);
1627 gen(IncRef, res);
1628 gen(DecRef, obj);
1629 return res;
1633 push(toPush);
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()
1649 ? gen(
1650 CreateAFWHMeth,
1651 catchBlock,
1652 CreateContData { genFunc },
1653 gen(LdCtx, FuncData(curFunc()), m_tb->fp()),
1654 cns(label),
1655 child
1657 : gen(
1658 CreateAFWHFunc,
1659 catchBlock,
1660 CreateContData { genFunc },
1661 cns(label),
1662 child
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) {
1687 gen(IterCopy,
1688 m_tb->fp(),
1689 cns(origFunc->numLocals() * sizeof(TypedValue) + (i+1) * sizeof(Iter)),
1690 asyncAR,
1691 cns(genFunc->numLocals() * sizeof(TypedValue) + (i+1) * sizeof(Iter)));
1694 if (fillThis) {
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);
1701 push(waitHandle);
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()));
1720 } else {
1721 push(gen(LdRaw, Type::Int, input, cns(RawMemSlot::StrLen)));
1722 gen(DecRef, input);
1724 } else if (inType.isNull()) {
1725 popC();
1726 push(cns(0));
1727 } else if (inType == Type::Bool) {
1728 // strlen(true) == 1, strlen(false) == 0.
1729 push(gen(ConvBoolToInt, popC()));
1730 } else {
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)) {
1748 emitArrayIdx();
1749 } else {
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
1756 // called once now.
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));
1762 gen(DecRef, arr);
1763 gen(DecRef, key);
1764 gen(DecRef, 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
1770 // function.
1771 Type keyType = topC(1, DataTypeGeneric)->type();
1772 Type arrType = topC(2, DataTypeGeneric)->type();
1774 if (!(arrType <= Type::Arr)) {
1775 // raise fatal
1776 emitInterpOne(Type::Cell, 3);
1777 return;
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
1786 push(def);
1787 gen(DecRef, arr);
1788 gen(DecRef, key);
1789 return;
1791 if (!(keyType <= Type::Int || keyType <= Type::Str)) {
1792 emitInterpOne(Type::Cell, 3);
1793 return;
1796 SSATmp* def = popC(DataTypeGeneric); // a helper will decref it but the
1797 // translated code doesn't care about
1798 // the type
1799 SSATmp* key = popC();
1800 SSATmp* arr = popC();
1802 KeyType arrayKeyType;
1803 bool checkForInt;
1804 checkStrictlyInteger(key, arrayKeyType, checkForInt);
1806 TCA opFunc;
1807 if (checkForInt) {
1808 opFunc = (TCA)&arrayIdxSi;
1809 } else if (KeyType::Int == arrayKeyType) {
1810 opFunc = (TCA)&arrayIdxI;
1811 } else {
1812 assert(KeyType::Str == arrayKeyType);
1813 opFunc = (TCA)&arrayIdxS;
1816 push(gen(ArrayIdx, cns(opFunc), arr, key, def));
1817 gen(DecRef, arr);
1818 gen(DecRef, key);
1819 gen(DecRef, 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));
1857 gen(DecRef, 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
1863 // require it.
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));
1875 gen(DecRef, 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() {
1893 pushIncRef(topC());
1896 void HhbcTranslator::emitJmp(int32_t offset,
1897 bool breakTracelet,
1898 bool noSurprise) {
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,
1909 bool negate,
1910 SSATmp* src) {
1911 spillStack();
1913 auto const target = makeExit(offset);
1914 auto const boolSrc = gen(ConvCellToBool, src);
1915 gen(DecRef, 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) {
1938 switch (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();
1957 opc = opc2;
1959 // src2 opc src1
1960 SSATmp* src1 = popC();
1961 SSATmp* src2 = popC();
1962 push(gen(opc, catchBlock, src2, src1));
1963 gen(DecRef, src2);
1964 gen(DecRef, 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
1969 // static value.
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);
1975 case KindOfString:
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,
1984 Type outPred) {
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(
1993 nextBcOff(),
1994 [&] {
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
2002 * literal.
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)) {
2009 Slot ignore;
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));
2016 return;
2020 auto guardType = Type::UncountedInit;
2021 if (outPred.strictSubtypeOf(guardType)) guardType = outPred;
2022 auto const cns = gen(LdClsCns, sideExit, clsCnsName, guardType);
2023 push(cns);
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));
2038 gen(DecRef, arr);
2039 gen(DecRef, key);
2042 void HhbcTranslator::emitFPassR() {
2043 emitUnboxRAux();
2046 void HhbcTranslator::emitFPassCOp() {
2049 void HhbcTranslator::emitFPassV() {
2050 Block* exit = makeExit();
2051 SSATmp* tmp = popV();
2052 pushIncRef(gen(Unbox, exit, tmp));
2053 gen(DecRef, tmp);
2056 void HhbcTranslator::emitFPushCufIter(int32_t numParams,
2057 int32_t itId) {
2058 auto sp = spillStack();
2059 m_fpiStack.emplace(sp, m_tb->spOffset());
2060 gen(CufIterSpillFrame,
2061 FPushCufData(numParams, itId),
2062 sp, m_tb->fp());
2065 static const Func* findCuf(Op op,
2066 SSATmp* callable,
2067 Class* ctx,
2068 Class*& cls,
2069 StringData*& invName) {
2070 bool forward = (op == OpFPushCufF);
2071 cls = nullptr;
2072 invName = nullptr;
2074 const StringData* str =
2075 callable->isA(Type::Str) && callable->isConst() ? callable->getValStr()
2076 : nullptr;
2077 const ArrayData* arr =
2078 callable->isA(Type::Arr) && callable->isConst() ? callable->getValArr()
2079 : nullptr;
2081 StringData* sclass = nullptr;
2082 StringData* sname = nullptr;
2083 if (str) {
2084 Func* f = Unit::lookupFunc(str);
2085 if (f) return f;
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) {
2090 return nullptr;
2092 sclass = makeStaticString(name.substr(0, pos).get());
2093 sname = makeStaticString(name.substr(pos + 2).get());
2094 } else if (arr) {
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();
2101 String name(sname);
2102 if (name.find("::") != String::npos) return nullptr;
2103 } else {
2104 return nullptr;
2107 if (sclass->isame(s_self.get())) {
2108 if (!ctx) return nullptr;
2109 cls = ctx;
2110 forward = true;
2111 } else if (sclass->isame(s_parent.get())) {
2112 if (!ctx || !ctx->parent()) return nullptr;
2113 cls = ctx->parent();
2114 forward = true;
2115 } else if (sclass->isame(s_static.get())) {
2116 return nullptr;
2117 } else {
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
2132 * the lsb class
2134 return nullptr;
2136 if (magicCall) invName = sname;
2137 return f;
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) {
2149 return false;
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) {
2158 return false;
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);
2170 popC();
2172 gen(IncRef, object);
2173 emitFPushObjMethodCommon(object,
2174 method->getValStr(),
2175 numParams,
2176 false /* shouldFatal */,
2177 callable);
2178 gen(DecRef, callable);
2179 return true;
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;
2200 popC();
2202 emitFPushActRec(
2203 m_tb->genDefNull(),
2204 m_tb->genDefInitNull(),
2205 numParams,
2206 nullptr
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.
2217 updateMarker();
2219 auto const opcode = callable->isA(Type::Arr) ? LdArrFPushCuf
2220 : LdStrFPushCuf;
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);
2235 SSATmp* ctx;
2236 SSATmp* safeFlag = cns(true); // This is always true until the slow exits
2237 // below are implemented
2238 SSATmp* func = cns(callee);
2239 if (cls) {
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()));
2248 if (forward) {
2249 ctx = gen(LdCtx, FuncData(curFunc()), m_tb->fp());
2250 ctx = gen(GetCtxFwdCall, ctx, cns(callee));
2251 } else {
2252 ctx = genClsMethodCtx(callee, cls);
2254 } else {
2255 ctx = m_tb->genDefInitNull();
2256 if (!RDS::isPersistentHandle(callee->funcHandle())) {
2257 // The miss path is complicated and rare. Punt for now.
2258 func = gen(
2259 LdFuncCachedSafe, LdFuncCachedData(callee->name()), makeExitSlow()
2264 SSATmp* defaultVal = safe ? popC() : nullptr;
2265 popDecRef(Type::Cell); // callable
2266 if (safe) {
2267 push(defaultVal);
2268 push(safeFlag);
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
2282 m_hasExit = true;
2285 void HhbcTranslator::emitFPushActRec(SSATmp* func,
2286 SSATmp* objOrClass,
2287 int32_t numArgs,
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
2292 * ActRec is popped.
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());
2303 ActRecInfo info;
2304 info.numArgs = numArgs;
2305 info.invName = invName;
2306 gen(
2307 SpillFrame,
2308 info,
2309 // Using actualStack instead of returnSp so SpillFrame still gets
2310 // the src in rVmSp. (TODO(#2288359).)
2311 actualStack,
2312 m_tb->fp(),
2313 func,
2314 objOrClass
2316 assert(m_stackDeficit == 0);
2319 void HhbcTranslator::emitFPushCtorCommon(SSATmp* cls,
2320 SSATmp* obj,
2321 const Func* func,
2322 int32_t numParams) {
2323 push(obj);
2324 SSATmp* fn = nullptr;
2325 if (func) {
2326 fn = cns(func);
2327 } else {
2328 fn = gen(LdClsCtor, makeCatch(), cls);
2330 gen(IncRef, obj);
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);
2338 gen(IncRef, obj);
2339 emitFPushCtorCommon(cls, obj, nullptr, numParams);
2342 static bool canInstantiateClass(const Class* cls) {
2343 return 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);
2354 bool fastAlloc =
2355 !RuntimeOption::EnableObjDestructCall &&
2356 persistentCls &&
2357 canInstantiate &&
2358 !cls->callsCustomInstanceInit();
2360 const Func* func = uniqueCls ? cls->getCtor() : nullptr;
2361 if (func && !(func->attrs() & AttrPublic)) {
2362 Class* ctx = curClass();
2363 if (!ctx) {
2364 func = nullptr;
2365 } else if (ctx != cls) {
2366 if ((func->attrs() & AttrPrivate) ||
2367 !(ctx->classof(cls) || cls->classof(ctx))) {
2368 func = nullptr;
2373 auto const ssaCls =
2374 persistentCls ? cns(cls)
2375 : gen(LdClsCached, makeCatch(), cns(className));
2376 auto const obj =
2377 fastAlloc ? gen(AllocObjFast, ClassData(cls))
2378 : gen(AllocObj, makeCatch(), ssaCls);
2379 gen(IncRef, obj);
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);
2415 return ldctx;
2416 }();
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();
2425 int32_t propId = 0;
2426 for (; propId < numParams; ++propId) {
2427 gen(
2428 StClosureArg,
2429 PropByteOffset(cls->declPropOffset(propId)),
2430 closure,
2431 args[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;
2442 ++propId) {
2443 gen(
2444 StClosureArg,
2445 PropByteOffset(cls->declPropOffset(propId)),
2446 closure,
2447 m_tb->genDefUninit()
2451 push(closure);
2454 void HhbcTranslator::emitFPushFuncCommon(const Func* func,
2455 const StringData* name,
2456 const StringData* fallback,
2457 int32_t numParams) {
2458 if (func) {
2459 func->validate();
2460 if (func->isNameBindingImmutable(curUnit())) {
2461 emitFPushActRec(cns(func),
2462 m_tb->genDefInitNull(),
2463 numParams,
2464 nullptr);
2465 return;
2469 // LdFuncCached can throw
2470 auto const catchBlock = makeCatch();
2471 auto const ssaFunc = fallback
2472 ? gen(LdFuncCachedU,
2473 LdFuncCachedUData { name, fallback },
2474 catchBlock)
2475 : gen(LdFuncCached,
2476 LdFuncCachedData { name },
2477 catchBlock);
2478 emitFPushActRec(ssaFunc,
2479 m_tb->genDefInitNull(),
2480 numParams,
2481 nullptr);
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,
2492 int32_t funcId,
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(),
2519 numParams,
2520 nullptr);
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();
2536 emitFPushActRec(
2537 m_tb->genDefNull(),
2538 m_tb->genDefInitNull(),
2539 numParams,
2540 nullptr);
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
2545 // pushed.
2546 updateMarker();
2548 gen(LdArrFuncCtx, catchBlock, arr, actRec, thisAR);
2549 gen(DecRef, arr);
2552 void HhbcTranslator::emitFPushObjMethodCommon(SSATmp* obj,
2553 const StringData* methodName,
2554 int32_t numParams,
2555 bool shouldFatal,
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
2563 // further, use it.
2564 baseClass = obj->type().getClass();
2567 bool magicCall = false;
2568 const Func* func = HPHP::JIT::lookupImmutableMethod(baseClass,
2569 methodName,
2570 magicCall,
2571 /* staticLookup: */
2572 false,
2573 curClass());
2575 if (!func) {
2576 if (baseClass && !(baseClass->attrs() & AttrInterface)) {
2577 LookupResult res =
2578 g_vmContext->lookupObjMethod(func, baseClass, methodName, curClass(),
2579 false);
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.
2587 * OR
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) {
2600 gen(DecRef, obj);
2601 objOrCls = clsTmp;
2603 emitFPushActRec(funcTmp, objOrCls, numParams,
2604 magicCall ? methodName : nullptr);
2605 return;
2607 } else {
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
2620 // decref it
2621 gen(DecRef, obj);
2622 objOrCls = cns(baseClass);
2624 emitFPushActRec(cns(func),
2625 objOrCls,
2626 numParams,
2627 magicCall ? methodName : nullptr);
2628 } else {
2629 spillStack();
2630 std::vector<SSATmp*> spill;
2631 if (extraSpill) spill.push_back(extraSpill);
2632 auto catchBlock = makeCatch(spill);
2633 emitFPushActRec(m_tb->genDefNull(),
2634 obj,
2635 numParams,
2636 nullptr);
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.
2642 updateMarker();
2644 gen(LdObjMethod,
2645 LdObjMethodData(shouldFatal),
2646 catchBlock,
2647 objCls,
2648 cns(methodName),
2649 actRec);
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;
2663 assert(callee);
2664 if (!(callee->attrs() & AttrStatic) &&
2665 !(curFunc()->attrs() & AttrStatic) &&
2666 curClass() &&
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
2679 assert(curClass());
2680 auto this_ = gen(LdThis, m_tb->fp());
2681 gen(IncRef, this_);
2682 return this_;
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,
2698 methodName,
2699 magicCall,
2700 /* staticLookup: */
2701 true,
2702 curClass());
2703 if (func) {
2704 SSATmp* objOrCls = genClsMethodCtx(func, baseClass);
2705 emitFPushActRec(cns(func),
2706 objOrCls,
2707 numParams,
2708 func && magicCall ? methodName : nullptr);
2709 } else {
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(
2716 [&](Block* taken) {
2717 return gen(CheckNonNull, taken, gen(LdClsMethodCacheFunc, data));
2719 [&](SSATmp* func) { // next
2720 return func;
2722 [&] { // taken
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,
2732 clsCtx,
2733 numParams,
2734 nullptr);
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();
2758 const Func* func;
2759 auto res =
2760 g_vmContext->lookupClsMethod(func,
2761 cls,
2762 methVal->getValStr(),
2763 nullptr,
2764 cls,
2765 false);
2766 if (res == LookupResult::MethodFoundNoThis) {
2767 auto const funcTmp = gen(LdClsMethod, clsVal, cns(func->methodSlot()));
2768 emitFPushActRec(funcTmp, clsVal, numParams, nullptr);
2769 return;
2773 emitFPushActRec(m_tb->genDefNull(),
2774 m_tb->genDefInitNull(),
2775 numParams,
2776 nullptr);
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.
2783 updateMarker();
2785 gen(LookupClsMethod, makeCatch({clsVal, methVal}), clsVal, methVal, actRec,
2786 m_tb->fp());
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 */,
2806 curClass());
2807 SSATmp* curCtxTmp = gen(LdCtx, FuncData(curFunc()), m_tb->fp());
2808 if (func) {
2809 SSATmp* funcTmp = cns(func);
2810 SSATmp* newCtxTmp = gen(GetCtxFwdCall, curCtxTmp, funcTmp);
2812 emitFPushActRec(funcTmp, newCtxTmp, numParams,
2813 (magicCall ? methName : nullptr));
2815 } else {
2816 auto const data = ClsMethodData{cls->name(), methName};
2817 auto func = m_tb->cond(
2818 [&](Block* taken) {
2819 return gen(CheckNonNull, taken, gen(LdClsMethodFCacheFunc, data));
2821 [&](SSATmp* func) { // next
2822 return func;
2824 [&] { // taken
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,
2834 ctx,
2835 numParams,
2836 (magicCall ? methName : nullptr));
2840 void HhbcTranslator::emitFCallArray(const Offset pcOffset,
2841 const Offset after,
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,
2849 const Func* callee,
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));
2869 } else {
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()) {
2880 m_fpiStack.pop();
2884 void HhbcTranslator::emitFCallBuiltin(uint32_t numArgs,
2885 uint32_t numNonDefault,
2886 int32_t funcId,
2887 bool destroyLocals) {
2888 const NamedEntity* ne = lookupNamedEntityId(funcId);
2889 const Func* callee = Unit::lookupFunc(ne);
2891 callee->validate();
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.
2901 spillStack();
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()) {
2912 case KindOfBoolean:
2913 case KindOfInt64:
2914 case KindOfArray:
2915 case KindOfObject:
2916 case KindOfResource:
2917 case KindOfString:
2918 if (zendParamMode) {
2919 gen(CoerceStk,
2920 Type(pi.builtinType()),
2921 StackOffset(numArgs - i - 1),
2922 makeExitSlow(),
2923 m_tb->sp());
2924 } else {
2925 gen(CastStk,
2926 makeCatch(),
2927 Type(pi.builtinType()),
2928 StackOffset(numArgs - i - 1),
2929 m_tb->sp());
2931 break;
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()) {
2946 case KindOfBoolean:
2947 case KindOfInt64:
2948 args[i + 2] = top(Type(pi.builtinType()),
2949 numArgs - i - 1);
2950 break;
2951 case KindOfDouble: not_reached();
2952 default:
2953 args[i + 2] = ldStackAddr(numArgs - i - 1, DataTypeSpecific);
2954 break;
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(
2964 CallBuiltin,
2965 retType,
2966 CallData(destroyLocals),
2967 makeCatch(),
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) {
2975 gen(DecRef, arg);
2979 push(ret);
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();
3003 updateMarker();
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());
3009 } else {
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(),
3015 m_tb->spOffset(),
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);
3028 m_stackDeficit = 0;
3030 FTRACE(1, "]]] end inlining: {}\n", curFunc()->fullName()->data());
3031 push(useRet);
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());
3051 return retVal;
3054 void HhbcTranslator::emitRet(Type type, bool freeInline) {
3055 if (isInlining()) {
3056 return emitRetFromInlined(type);
3059 const Func* curFunc = this->curFunc();
3060 bool mayUseVV = (curFunc->attrs() & AttrMayUseVV);
3062 if (mayUseVV) {
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
3070 // about the type.
3071 SSATmp* retVal = pop(type, DataTypeGeneric);
3072 if (RuntimeOption::EvalRuntimeTypeProfile) {
3073 gen(TypeProfileFunc, TypeProfileData(-1), retVal, cns(curFunc));
3075 SSATmp* sp;
3076 if (freeInline) {
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());
3083 } else {
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
3099 m_hasExit = true;
3102 void HhbcTranslator::emitJmpSurpriseCheck() {
3103 auto catchTrace = makeCatch();
3105 m_tb->ifThen([&](Block* taken) {
3106 gen(CheckSurpriseFlags, taken);
3108 [&] {
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);
3121 [&] {
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,
3130 int64_t base,
3131 bool bounded) {
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));
3139 SSATmp* index;
3140 SSATmp* ssabase = cns(base);
3141 SSATmp* ssatargets = cns(nTargets);
3143 Offset defaultOff = bcOff() + iv.vec32()[iv.size() - 1];
3144 Offset zeroOff = 0;
3145 if (base <= 0 && (base + nTargets) > 0) {
3146 zeroOff = bcOff() + iv.vec32()[0 - base];
3147 } else {
3148 zeroOff = defaultOff;
3151 if (type <= Type::Null) {
3152 gen(Jmp, makeExit(zeroOff));
3153 return;
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));
3159 return;
3162 if (type <= Type::Int) {
3163 // No special treatment needed
3164 index = switchVal;
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
3169 bounded = false;
3170 index = gen(LdSwitchDblIndex, switchVal, ssabase, ssatargets);
3171 } else if (type <= Type::Str) {
3172 bounded = false;
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.
3177 bounded = false;
3178 index = gen(LdSwitchObjIndex, catchBlock, switchVal, ssabase, ssatargets);
3179 } else if (type <= Type::Arr) {
3180 gen(DecRef, switchVal);
3181 gen(Jmp, makeExit(defaultOff));
3182 return;
3183 } else {
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];
3192 JmpSwitchData data;
3193 data.func = curFunc();
3194 data.base = base;
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);
3204 m_hasExit = true;
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;
3238 LdSSwitchData data;
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,
3246 catchBlock,
3247 data,
3248 testVal);
3249 gen(DecRef, testVal);
3250 auto const stack = spillStack();
3251 gen(SyncABIRegs, m_tb->fp(), stack);
3252 gen(JmpIndirect, dest);
3253 m_hasExit = true;
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,
3305 Type type) {
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,
3315 bool outerOnly) {
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);
3338 assert(tmp);
3339 m_evalStack.replace(idx, gen(CheckType, type, exit, tmp));
3340 } else {
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);
3357 assert(tmp);
3358 m_evalStack.replace(idx, gen(AssertType, type, tmp));
3359 } else {
3360 gen(AssertStk, type,
3361 StackOffset(idx - m_evalStack.size() + m_stackDeficit),
3362 m_tb->sp());
3366 void HhbcTranslator::assertClass(const RegionDesc::Location& loc,
3367 const Class* cls) {
3368 Type curType;
3369 typedef RegionDesc::Location::Tag T;
3370 switch (loc.tag()) {
3371 case T::Stack:
3372 curType = topType(loc.stackOffset(), DataTypeSpecific);
3373 break;
3375 case T::Local:
3376 curType = m_tb->localType(loc.localId(), DataTypeSpecific);
3377 break;
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) {
3398 Type t;
3399 SSATmp* val = nullptr;
3400 switch (loc.space) {
3401 case Location::Stack: {
3402 auto i = loc.offset;
3403 assert(i >= 0);
3404 if (i < m_evalStack.size()) {
3405 val = m_evalStack.top(DataTypeGeneric, i);
3406 t = val->type();
3407 } else {
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);
3414 } break;
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);
3419 } break;
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:
3428 not_reached();
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) {
3451 break;
3454 return retval;
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);
3468 if (mask64 == 0) {
3469 continue;
3471 uint64_t vals64 = packBitVec(vals, i);
3473 if (i == 0) {
3474 nParams = cns(64);
3475 } else if (i == 64) {
3476 nParams = gen(
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);
3482 gen(
3483 GuardRefs,
3484 funcPtr,
3485 nParams,
3486 cns(i),
3487 maskTmp,
3488 valsTmp
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
3501 // builds. t3412704
3502 assert_log(false,
3503 [&] {
3504 return folly::format("Bad type {} for local {}:\n\n{}\n",
3505 locType, paramId, m_tb->unit().toString()).str();
3507 emitInterpOne(Type::None, 0);
3508 return;
3511 if (!RuntimeOption::EvalCheckExtendedTypeHints && tc.isExtended()) {
3512 return;
3514 if (tc.isTypeVar()) {
3515 return;
3517 if (tc.isNullable() && locType.isNull()) {
3518 return;
3520 if (tc.isCallable()) {
3521 locVal = gen(Unbox, makeExit(), locVal);
3522 gen(VerifyParamCallable, makeCatch(), locVal, cns(paramId));
3523 return;
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));
3534 return;
3536 return;
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);
3549 return;
3552 const StringData* clsName;
3553 const Class* knownConstraint = nullptr;
3554 if (!tc.isSelf() && !tc.isParent()) {
3555 clsName = tc.typeName();
3556 knownConstraint = Unit::lookupClass(clsName);
3557 } else {
3558 if (tc.isSelf()) {
3559 tc.selfToClass(curFunc(), &knownConstraint);
3560 } else if (tc.isParent()) {
3561 tc.parentToClass(curFunc(), &knownConstraint);
3563 if (knownConstraint) {
3564 clsName = knownConstraint->preClass()->name();
3565 } else {
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));
3569 return;
3572 assert(clsName);
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));
3609 } else {
3610 gen(VerifyParamCls,
3611 makeCatch(),
3612 objClass,
3613 constraint,
3614 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));
3638 push(cns(res));
3639 gen(DecRef, src);
3640 return;
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));
3659 gen(DecRef, src);
3660 return;
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
3666 * and be defined.
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))
3673 ? cns(maybeCls)
3674 : gen(LdClsCachedSafe, ssaClassName);
3676 push(
3677 haveBit ? gen(InstanceOfBitmask, objClass, ssaClassName)
3678 : isUnique && isNormalClass ? gen(ExtendsClass, objClass, checkClass)
3679 : gen(InstanceOf, objClass, checkClass)
3681 gen(DecRef, src);
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));
3692 gen(DecRef, t2);
3693 gen(DecRef, t1);
3694 return;
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));
3704 gen(DecRef, t2);
3705 gen(DecRef, t1);
3706 return;
3709 push(
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) :
3714 cns(false)
3716 gen(DecRef, t2);
3717 gen(DecRef, 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()) {
3739 push(src);
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));
3752 } else {
3753 push(gen(ConvCellToArr, catchBlock, src));
3757 void HhbcTranslator::emitCastBool() {
3758 auto const src = popC();
3759 push(gen(ConvCellToBool, src));
3760 gen(DecRef, src);
3763 void HhbcTranslator::emitCastDouble() {
3764 auto const catchBlock = makeCatch();
3765 auto const src = popC();
3766 push(gen(ConvCellToDbl, catchBlock, src));
3767 gen(DecRef, src);
3770 void HhbcTranslator::emitCastInt() {
3771 auto const catchBlock = makeCatch();
3772 auto const src = popC();
3773 push(gen(ConvCellToInt, catchBlock, src));
3774 gen(DecRef, src);
3777 void HhbcTranslator::emitCastObject() {
3778 SSATmp* src = popC();
3779 Type srcType = src->type();
3780 if (srcType.isObj()) {
3781 push(src);
3782 } else {
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));
3791 gen(DecRef, 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));
3803 } else {
3804 not_reached();
3808 void HhbcTranslator::emitAGetC() {
3809 auto const name = topC();
3810 if (isSupportedAGet(name)) {
3811 popC();
3812 emitAGet(name, makeCatch({name}));
3813 gen(DecRef, name);
3814 } else {
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());
3823 } else {
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));
3831 pushIncRef(src);
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,
3847 SSATmp* ssaCls,
3848 SSATmp* ssaName) {
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
3854 * method.
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)) {
3864 return true;
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;
3877 }();
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);
3887 }();
3889 auto const ptrTy = convertToType(repoTy).ptr();
3890 if (useSpropCache) {
3891 return gen(LdClsPropAddrCached, ptrTy,
3892 catchBlock,
3893 ssaCls,
3894 ssaName,
3895 cns(findClassName(ssaCls)),
3896 cns(curClass()));
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);
3915 pushIncRef(ldMem);
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
3981 return cns(false);
3985 destroyName(ssaPropName);
3986 push(ret);
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);
4000 [&] (SSATmp* ptr) {
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
4006 return cns(true);
4010 destroyName(ssaPropName);
4011 push(ret);
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);
4019 destroyName(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);
4027 destroyName(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);
4036 destroyName(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));
4045 destroyName(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
4061 return cns(false);
4064 destroyName(name);
4065 push(ret);
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
4082 return cns(true);
4085 destroyName(name);
4086 push(ret);
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));
4099 } else {
4100 Type type = Type::Int;
4101 if (isBitOp) {
4102 if (type1.isString() && type2.isString()) {
4103 type = Type::Str;
4104 } else if ((type1.needsReg() && (type2.needsReg() || type2.isString()))
4105 || (type2.needsReg() && type1.isString())) {
4106 // both types might be strings, but can't tell
4107 type = Type::Cell;
4108 } else {
4109 type = Type::Int;
4111 } else {
4112 // either an int or a dbl, but can't tell
4113 type = Type::Cell;
4115 emitInterpOne(type, 2);
4119 void HhbcTranslator::emitNot() {
4120 SSATmp* src = popC();
4121 push(gen(Not, gen(ConvCellToBool, src)));
4122 gen(DecRef, src);
4125 void HhbcTranslator::emitFloor() {
4126 // need SSE 4.1 support to use roundsd
4127 if (!folly::CpuId().sse41()) {
4128 PUNT(Floor);
4131 auto catchBlock = makeCatch();
4132 auto val = popC();
4133 auto dblVal = gen(ConvCellToDbl, catchBlock, val);
4134 gen(DecRef, val);
4135 push(gen(Floor, dblVal));
4138 void HhbcTranslator::emitCeil() {
4139 // need SSE 4.1 support to use roundsd
4140 if (!folly::CpuId().sse41()) {
4141 PUNT(Ceil);
4144 auto catchBlock = makeCatch();
4145 auto val = popC();
4146 auto dblVal = gen(ConvCellToDbl, catchBlock, val);
4147 gen(DecRef, val);
4148 push(gen(Ceil, dblVal));
4151 static folly::Optional<Type> assertOpToType(AssertTOp op) {
4152 switch (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:
4176 return folly::none;
4178 // We always know this at JIT time right now.
4179 case AssertTOp::Cell:
4180 case AssertTOp::Ref:
4181 return folly::none;
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
4189 not_reached();
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
4207 // prediction.
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
4216 // prediction.
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) {
4228 switch (op) {
4229 case AssertObjOp::Exact:
4230 case AssertObjOp::Sub:
4231 return false;
4232 case AssertObjOp::OptExact:
4233 case AssertObjOp::OptSub:
4234 return true;
4236 not_reached();
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));
4256 return;
4259 if (value->isA(Type::Dbl)) {
4260 push(gen(AbsDbl, value));
4261 return;
4264 if (value->isA(Type::Arr)) {
4265 gen(DecRef, value);
4266 push(cns(false));
4267 return;
4270 PUNT(Abs);
4273 #define BINOP(Opp) \
4274 void HhbcTranslator::emit ## Opp() { \
4275 emitBinaryArith(Opp); \
4278 BINOP(Add)
4279 BINOP(Sub)
4280 BINOP(Mul)
4281 BINOP(BitAnd)
4282 BINOP(BitOr)
4283 BINOP(BitXor)
4285 #undef BINOP
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);
4298 return;
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()) {
4308 int64_t divisorVal;
4309 if (divisor->isA(Type::Int)) {
4310 divisorVal = divisor->getValInt();
4311 } else {
4312 assert(divisor->isA(Type::Bool));
4313 divisorVal = divisor->getValBool();
4316 if (divisorVal == 0) {
4317 popC();
4318 popC();
4319 gen(RaiseWarning, makeCatch(),
4320 cns(makeStaticString(Strings::DIVISION_BY_ZERO)));
4321 push(cns(false));
4322 return;
4325 if (dividend->isConst()) {
4326 int64_t dividendVal;
4327 if (dividend->isA(Type::Int)) {
4328 dividendVal = dividend->getValInt();
4329 } else {
4330 assert(dividend->isA(Type::Bool));
4331 dividendVal = dividend->getValBool();
4333 popC();
4334 popC();
4335 if (dividendVal == LLONG_MIN || dividendVal % divisorVal) {
4336 push(cns((double)dividendVal / divisorVal));
4337 } else {
4338 push(cns(dividendVal / divisorVal));
4340 return;
4342 /* fall through */
4344 emitInterpOne(Type::Cell, 2);
4345 return;
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));
4355 return src;
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
4397 // overflow.
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) {
4402 push(cns(0));
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
4408 push(cns(false));
4409 } else {
4410 push(gen(Mod, tl, tr));
4412 return;
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);
4421 [&] {
4422 return gen(Mod, tl, tr);
4424 [&] {
4425 m_tb->hint(Block::Hint::Unlikely);
4426 return cns(0);
4429 push(res);
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));
4437 return;
4440 if (srcType <= Type::Dbl) {
4441 auto const src = popC();
4442 push(gen(Sqrt, src));
4443 return;
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));
4454 return;
4457 if (srcType <= Type::Dbl) {
4458 auto const src = gen(ConvDblToInt, popC());
4459 push(gen(BitNot, src));
4460 return;
4463 auto const resultType =
4464 srcType.isString() ? Type::Str :
4465 srcType.needsReg() ? Type::Cell :
4466 Type::Int;
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)));
4476 gen(DecRef, btl);
4477 gen(DecRef, btr);
4480 void HhbcTranslator::emitShl() {
4481 auto catch1 = makeCatch();
4482 auto catch2 = makeCatch();
4483 auto shiftAmount = popC();
4484 auto lhs = popC();
4486 auto lhsInt = gen(ConvCellToInt, catch1, lhs);
4487 auto shiftAmountInt = gen(ConvCellToInt, catch2, shiftAmount);
4489 push(gen(Shl, lhsInt, shiftAmountInt));
4490 gen(DecRef, lhs);
4491 gen(DecRef, shiftAmount);
4494 void HhbcTranslator::emitShr() {
4495 auto catch1 = makeCatch();
4496 auto catch2 = makeCatch();
4497 auto shiftAmount = popC();
4498 auto lhs = popC();
4500 auto lhsInt = gen(ConvCellToInt, catch1, lhs);
4501 auto shiftAmountInt = gen(ConvCellToInt, catch2, shiftAmount);
4503 push(gen(Shr, lhsInt, shiftAmountInt));
4504 gen(DecRef, lhs);
4505 gen(DecRef, shiftAmount);
4508 namespace {
4510 Type arithOpResult(Type t1, Type t2) {
4511 if (!t1.isKnownDataType() || !t2.isKnownDataType()) {
4512 return Type::Cell;
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;
4519 return Type::Int;
4522 Type bitOpResult(Type t1, Type t2) {
4523 if (!t1.isKnownDataType() || !t2.isKnownDataType()) {
4524 return Type::Cell;
4527 auto both = t1 | t2;
4528 if (both <= Type::Str) return Type::Str;
4529 return Type::Int;
4532 Type setOpResult(Type locType, Type valType, SetOpOp op) {
4533 switch (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;
4546 not_reached();
4549 uint32_t localInputId(const NormalizedInstruction& inst) {
4550 switch (inst.op()) {
4551 case OpSetWithRefLM:
4552 case OpFPassL:
4553 return inst.imm[1].u_LA;
4555 default:
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;
4586 switch (outFlag) {
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;
4592 case OutBoolean:
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
4597 case OutObject:
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());
4610 case OutFInputL:
4611 case OutFInputR: not_reached();
4613 case OutArith: return arithOpResult(topType(0), topType(1));
4614 case OutBitOp:
4615 return bitOpResult(topType(0),
4616 inst.op() == HPHP::OpBitNot ? Type::Bottom
4617 : topType(1));
4618 case OutSetOp: return setOpResult(localType(), topType(0),
4619 SetOpOp(inst.imm[1].u_OA));
4620 case OutIncDec: return localType().unbox().isInt() ? Type::Int
4621 : Type::Cell;
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;
4629 case OutCInput: {
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();
4637 return Type::Cell;
4640 case OutCInputL: {
4641 auto ltype = localType();
4642 if (ltype.notBoxed()) return ltype;
4643 if (ltype.unbox().strictSubtypeOf(Type::InitCell)) {
4644 checkTypeType = ltype.unbox();
4646 return Type::Cell;
4649 not_reached();
4652 smart::vector<InterpOneData::LocalType>
4653 HhbcTranslator::interpOutputLocals(const NormalizedInstruction& inst,
4654 bool& smashesAllLocals,
4655 Type pushedType) {
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);
4673 break;
4676 case OpSetN:
4677 case OpSetOpN:
4678 case OpIncDecN:
4679 case OpBindN:
4680 case OpUnsetN:
4681 smashesAllLocals = true;
4682 break;
4684 case OpSetOpL:
4685 case OpIncDecL: {
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);
4691 break;
4694 case OpStaticLocInit:
4695 setImmLocType(0, Type::BoxedCell);
4696 break;
4698 case OpInitThisLoc:
4699 setImmLocType(0, Type::Gen);
4700 break;
4702 case OpSetL: {
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);
4707 break;
4709 case OpVGetL:
4710 case OpBindL: {
4711 assert(pushedType.isBoxed());
4712 setImmLocType(0, pushedType);
4713 break;
4716 case OpUnsetL:
4717 case OpPushL:
4718 setImmLocType(0, Type::Uninit);
4719 break;
4721 case OpSetM:
4722 case OpSetOpM:
4723 case OpBindM:
4724 case OpVGetM:
4725 case OpSetWithRefLM:
4726 case OpSetWithRefRM:
4727 case OpUnsetM:
4728 case OpFPassM:
4729 switch (inst.immVec.locationCode()) {
4730 case LL: {
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());
4752 break;
4755 case LNL:
4756 case LNC:
4757 smashesAllLocals = true;
4758 break;
4760 default:
4761 break;
4763 break;
4765 case OpMIterInitK:
4766 case OpMIterNextK:
4767 setImmLocType(3, Type::Cell);
4768 case OpMIterInit:
4769 case OpMIterNext:
4770 setImmLocType(2, Type::BoxedCell);
4771 break;
4773 case OpIterInitK:
4774 case OpWIterInitK:
4775 case OpIterNextK:
4776 case OpWIterNextK:
4777 setImmLocType(3, Type::Cell);
4778 case OpIterInit:
4779 case OpWIterInit:
4780 case OpIterNext:
4781 case OpWIterNext:
4782 setImmLocType(2, Type::Gen);
4783 break;
4785 default:
4786 not_reached();
4789 return locals;
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()) {
4826 m_fpiStack.pop();
4829 idata.bcOff = bcOff();
4830 idata.cellsPopped = popped;
4831 idata.cellsPushed = pushed;
4832 idata.opcode = op;
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 {
4843 if (isInlining()) {
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);
4862 --spOffset;
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)
4873 .noLineNumbers()
4874 .indent(0)
4875 .noFuncs());
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]
4881 : nullptr;
4882 return true;
4884 return false;
4887 header(folly::format(" {} stack element(s); m_evalStack: ",
4888 stackDepth).str());
4889 for (unsigned i = 0; i < m_evalStack.size(); ++i) {
4890 while (checkFpi());
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());
4898 if (checkFpi()) {
4899 i += kNumActRecCells;
4900 continue;
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());
4909 ++i;
4912 header("");
4913 return out.str();
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
4919 * be at index 0.
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);
4932 return ret;
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,
4951 [&]() -> SSATmp* {
4952 gen(RaiseWarning, makeCatchNoSpill(), cns(warning));
4953 return nullptr;
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]));
4987 } else {
4988 stack = m_tb->sp();
4991 gen(SyncABIRegs, m_tb->fp(), stack);
4992 gen(ReqRetranslateOpt, ReqRetransOptData(transId, targetBcOff));
4994 return exit;
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,
5010 exit);
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()) {
5018 stackValues.insert(
5019 stackValues.begin(),
5020 { m_tb->sp(), cns(int64_t(m_stackDeficit)) }
5022 stack = gen(SpillStack,
5023 std::make_pair(stackValues.size(), &stackValues[0]));
5026 if (customFn) {
5027 stack = gen(ExceptionBarrier, stack);
5028 auto const customTmp = customFn();
5029 if (customTmp) {
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());
5060 if (!changesPC) {
5061 // If the op changes PC, InterpOneCF handles getting to the right place
5062 gen(ReqBindJmp, BCOffset(interpSk.advanced().offset()));
5064 return exit;
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);
5073 } else {
5074 gen(ReqBindJmp, BCOffset(targetBcOff));
5076 return exit;
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);
5092 gen(BeginCatch);
5093 auto sp = body();
5094 gen(EndCatch, m_tb->fp(), sp);
5096 return exit;
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
5103 * SpillStack.
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();
5133 m_stackDeficit = 0;
5134 return newSp;
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());
5147 return gen(
5148 LdStackAddr,
5149 Type::PtrToGen,
5150 StackOffset(offset + m_stackDeficit - m_evalStack.numCells()),
5151 m_tb->sp()
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)),
5159 m_tb->fp());
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)),
5166 m_tb->fp());
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
5180 // gets us that.
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)
5186 : loc;
5187 m_tb->constrainValue(value, constraint);
5188 return value;
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();
5209 return locVal;
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,
5225 Block* exit,
5226 SSATmp* newVal,
5227 bool doRefCount) {
5228 assert(!newVal->type().maybeBoxed());
5230 auto const oldLoc = ldLoc(id, doRefCount ? DataTypeCountness
5231 : DataTypeGeneric);
5232 assert(oldLoc->type().isBoxed() || oldLoc->type().notBoxed());
5234 if (oldLoc->type().notBoxed()) {
5235 gen(StLoc, LocalId(id), m_tb->fp(), newVal);
5236 if (doRefCount) {
5237 gen(DecRef, oldLoc);
5239 return newVal;
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);
5248 if (doRefCount) {
5249 m_tb->constrainValue(newVal, DataTypeCountness);
5250 gen(DecRef, innerCell);
5253 return newVal;
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
5288 // default params.
5289 return;
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;
5303 } else {
5304 assert(key->isA(Type::Str));
5305 keyType = KeyType::Str;
5306 if (key->isConst()) {
5307 int64_t i;
5308 if (key->getValStr()->isStrictlyInteger(i)) {
5309 keyType = KeyType::Int;
5310 key = cns(i);
5312 } else {
5313 checkForInt = true;
5318 }} // namespace HPHP::JIT