2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
17 #include "hphp/runtime/vm/jit/unique-stubs.h"
19 #include "hphp/runtime/base/datatype.h"
20 #include "hphp/runtime/base/rds-header.h"
21 #include "hphp/runtime/base/runtime-option.h"
22 #include "hphp/runtime/base/surprise-flags.h"
23 #include "hphp/runtime/base/tv-mutate.h"
24 #include "hphp/runtime/base/tv-variant.h"
25 #include "hphp/runtime/vm/bytecode.h"
26 #include "hphp/runtime/vm/debug/debug.h"
27 #include "hphp/runtime/vm/event-hook.h"
28 #include "hphp/runtime/vm/hhbc.h"
29 #include "hphp/runtime/vm/interp-helpers.h"
30 #include "hphp/runtime/vm/srckey.h"
31 #include "hphp/runtime/vm/vm-regs.h"
33 #include "hphp/runtime/vm/jit/types.h"
34 #include "hphp/runtime/vm/jit/abi.h"
35 #include "hphp/runtime/vm/jit/align.h"
36 #include "hphp/runtime/vm/jit/cg-meta.h"
37 #include "hphp/runtime/vm/jit/code-cache.h"
38 #include "hphp/runtime/vm/jit/code-gen-cf.h"
39 #include "hphp/runtime/vm/jit/code-gen-helpers.h"
40 #include "hphp/runtime/vm/jit/code-gen-tls.h"
41 #include "hphp/runtime/vm/jit/debugger.h"
42 #include "hphp/runtime/vm/jit/fixup.h"
43 #include "hphp/runtime/vm/jit/mcgen.h"
44 #include "hphp/runtime/vm/jit/phys-reg.h"
45 #include "hphp/runtime/vm/jit/phys-reg-saver.h"
46 #include "hphp/runtime/vm/jit/print.h"
47 #include "hphp/runtime/vm/jit/service-request-handlers.h"
48 #include "hphp/runtime/vm/jit/service-requests.h"
49 #include "hphp/runtime/vm/jit/smashable-instr.h"
50 #include "hphp/runtime/vm/jit/stack-offsets.h"
51 #include "hphp/runtime/vm/jit/stack-overflow.h"
52 #include "hphp/runtime/vm/jit/target-cache.h"
53 #include "hphp/runtime/vm/jit/tc.h"
54 #include "hphp/runtime/vm/jit/translator-inline.h"
55 #include "hphp/runtime/vm/jit/unique-stubs-arm.h"
56 #include "hphp/runtime/vm/jit/unique-stubs-ppc64.h"
57 #include "hphp/runtime/vm/jit/unique-stubs-x64.h"
58 #include "hphp/runtime/vm/jit/unwind-itanium.h"
59 #include "hphp/runtime/vm/jit/vasm-gen.h"
60 #include "hphp/runtime/vm/jit/vasm-instr.h"
61 #include "hphp/runtime/vm/jit/vasm-reg.h"
62 #include "hphp/runtime/vm/jit/vtune-jit.h"
64 #include "hphp/runtime/ext/asio/ext_async-generator.h"
65 #include "hphp/runtime/ext/generator/ext_generator.h"
67 #include "hphp/util/abi-cxx.h"
68 #include "hphp/util/arch.h"
69 #include "hphp/util/asm-x64.h"
70 #include "hphp/util/data-block.h"
71 #include "hphp/util/trace.h"
73 #include <folly/Format.h>
77 namespace HPHP
{ namespace jit
{
79 ///////////////////////////////////////////////////////////////////////////////
81 TRACE_SET_MOD(ustubs
);
83 ///////////////////////////////////////////////////////////////////////////////
87 ///////////////////////////////////////////////////////////////////////////////
89 void alignJmpTarget(CodeBlock
& cb
) {
90 if (RuntimeOption::EvalJitAlignUniqueStubs
) {
91 align(cb
, nullptr, Alignment::JmpTarget
, AlignContext::Dead
);
95 void alignCacheLine(CodeBlock
& cb
) {
96 if (RuntimeOption::EvalJitAlignUniqueStubs
) {
97 align(cb
, nullptr, Alignment::CacheLine
, AlignContext::Dead
);
101 void assertNativeStackAligned(Vout
& v
) {
102 if (RuntimeOption::EvalHHIRGenerateAsserts
) {
103 v
<< call
{TCA(assert_native_stack_aligned
)};
108 * Load and store the VM registers from/to RDS.
110 void loadVMRegs(Vout
& v
) {
111 v
<< load
{rvmtl()[rds::kVmfpOff
], rvmfp()};
112 v
<< load
{rvmtl()[rds::kVmspOff
], rvmsp()};
114 void storeVMRegs(Vout
& v
) {
115 v
<< store
{rvmfp(), rvmtl()[rds::kVmfpOff
]};
116 v
<< store
{rvmsp(), rvmtl()[rds::kVmspOff
]};
120 * Load and store the PHP return registers from/to the top of the VM stack.
122 * Note that we don't do loadb{}/storeb{} for the type register, because we
123 * sometimes need to preserve the m_aux field across returns.
125 void loadReturnRegs(Vout
& v
) {
126 v
<< load
{rvmsp()[TVOFF(m_data
)], rret_data()};
127 v
<< load
{rvmsp()[TVOFF(m_type
)], rret_type()};
129 void storeReturnRegs(Vout
& v
) {
130 v
<< store
{rret_data(), rvmsp()[TVOFF(m_data
)]};
131 v
<< store
{rret_type(), rvmsp()[TVOFF(m_type
)]};
135 * Convenience wrapper around a simple vcall to `helper', with a single `arg'
136 * and a return value in `d'.
139 Vinstr
simplecall(Vout
& v
, F helper
, Vreg arg
, Vreg d
) {
141 CallSpec::direct(helper
),
142 v
.makeVcallArgs({{arg
}}),
150 * Convenience wrapper around a simple vcall to `helper', with a single `arg'
151 * and a pair of return values in `d1' and `d2'.
154 Vinstr
simplecall(Vout
& v
, F helper
, Vreg arg
, Vreg d1
, Vreg d2
) {
156 CallSpec::direct(helper
),
157 v
.makeVcallArgs({{arg
}}),
158 v
.makeTuple({d1
, d2
}),
165 * Emit a catch trace that unwinds a stub context back to the PHP context that
168 template<class GenFn
>
169 void emitStubCatch(Vout
& v
, const UniqueStubs
& us
, GenFn gen
) {
170 always_assert(us
.endCatchHelper
);
174 v
<< jmpi
{us
.endCatchHelper
};
177 ///////////////////////////////////////////////////////////////////////////////
179 TCA
emitFreeLocalsHelpers(CodeBlock
& cb
, DataBlock
& data
, UniqueStubs
& us
) {
181 return ARCH_SWITCH_CALL(emitFreeLocalsHelpers
, cb
, data
, us
);
184 TCA
emitCallToExit(CodeBlock
& cb
, DataBlock
& data
, UniqueStubs
& us
) {
186 return ARCH_SWITCH_CALL(emitCallToExit
, cb
, data
, us
);
189 ///////////////////////////////////////////////////////////////////////////////
191 struct FCallHelperRet
{
196 FCallHelperRet
fcallHelper(ActRec
* ar
) {
197 assert_native_stack_aligned();
198 assertx(!ar
->resumed());
200 if (LIKELY(!RuntimeOption::EvalFailJitPrologs
)) {
201 auto const tca
= mcgen::getFuncPrologue(
202 const_cast<Func
*>(ar
->func()),
205 if (tca
) return { tca
, nullptr };
208 // Check for stack overflow in the same place func prologues make their
209 // StackCheck::Early check (see irgen-func-prologue.cpp). This handler also
210 // cleans and syncs vmRegs for us.
211 if (checkCalleeStackOverflow(ar
)) handleStackOverflow(ar
);
213 // If doFCall indicates that the function was intercepted and should be
214 // skipped, it will have already torn down the callee's frame. So, we need to
215 // save the return value thats in it.
216 auto const retAddr
= (TCA
)ar
->m_savedRip
;
220 if (doFCall(ar
, vmpc())) {
221 return { tc::ustubs().resumeHelperRet
, nullptr };
223 // We've been asked to skip the function body (fb_intercept). The vmregs
224 // have already been fixed; indicate this with a nullptr return.
225 return { nullptr, retAddr
};
227 // The VMRegAnchor above took care of us, but we need to tell the unwinder
228 // (since ~VMRegAnchor() will have reset tl_regState).
229 tl_regState
= VMRegState::CLEAN
;
234 ///////////////////////////////////////////////////////////////////////////////
236 TCA
emitFuncPrologueRedispatch(CodeBlock
& cb
, DataBlock
& data
) {
239 return vwrap(cb
, data
, [] (Vout
& v
) {
240 auto const func
= v
.makeReg();
241 v
<< load
{rvmfp()[AROFF(m_func
)], func
};
243 auto const argc
= v
.makeReg();
244 auto const naaf
= v
.makeReg();
245 v
<< loadl
{rvmfp()[AROFF(m_numArgsAndFlags
)], naaf
};
246 v
<< andli
{ActRec::kNumArgsMask
, naaf
, argc
, v
.makeReg()};
248 auto const nparams
= v
.makeReg();
249 auto const pcounts
= v
.makeReg();
250 v
<< loadl
{func
[Func::paramCountsOff()], pcounts
};
251 v
<< shrli
{0x1, pcounts
, nparams
, v
.makeReg()};
253 auto const sf
= v
.makeReg();
254 v
<< cmpl
{argc
, nparams
, sf
};
256 auto const pTabOff
= safe_cast
<int32_t>(Func::prologueTableOff());
257 auto const ptrSize
= safe_cast
<int32_t>(sizeof(LowPtr
<uint8_t>));
259 // If we passed more args than declared, we need to dispatch to the
260 // "too many arguments" prologue.
261 ifThen(v
, CC_L
, sf
, [&] (Vout
& v
) {
262 auto const dest
= v
.makeReg();
264 auto const nargs
= v
.makeReg();
265 v
<< movzlq
{nparams
, nargs
};
267 emitLdLowPtr(v
, func
[nargs
* ptrSize
+ (pTabOff
+ ptrSize
)],
268 dest
, sizeof(LowPtr
<uint8_t>));
272 auto const nargs
= v
.makeReg();
273 v
<< movzlq
{argc
, nargs
};
275 auto const dest
= v
.makeReg();
276 emitLdLowPtr(v
, func
[nargs
* ptrSize
+ pTabOff
],
277 dest
, sizeof(LowPtr
<uint8_t>));
282 TCA
emitFCallHelperThunk(CodeBlock
& main
, CodeBlock
& cold
, DataBlock
& data
) {
283 alignJmpTarget(main
);
285 return vwrap2(main
, cold
, data
, [] (Vout
& v
, Vout
& vc
) {
286 v
<< phplogue
{rvmfp()};
288 // fcallHelper asserts native stack alignment for us.
289 FCallHelperRet (*helper
)(ActRec
*) = &fcallHelper
;
290 auto const dest
= v
.makeReg();
291 auto const saved_rip
= v
.makeReg();
292 v
<< simplecall(v
, helper
, rvmfp(), dest
, saved_rip
);
294 // Clobber rvmsp in debug builds.
295 if (debug
) v
<< copy
{v
.cns(0x1), rvmsp()};
297 auto const sf
= v
.makeReg();
298 v
<< testq
{dest
, dest
, sf
};
300 unlikelyIfThen(v
, vc
, CC_Z
, sf
, [&] (Vout
& v
) {
301 // A nullptr dest means the callee was intercepted and should be
302 // skipped. In that case, saved_rip will contain the return address that
303 // was in the callee's ActRec before it was torn down by the intercept.
307 // Return to the caller. This unbalances the return stack buffer, but if
308 // we're intercepting, we probably don't care.
309 v
<< jmpr
{saved_rip
};
312 // Jump to the func prologue.
313 v
<< tailcallphp
{dest
, rvmfp(), php_call_regs()};
317 TCA
emitFuncBodyHelperThunk(CodeBlock
& cb
, DataBlock
& data
) {
320 return vwrap(cb
, data
, [] (Vout
& v
) {
321 TCA (*helper
)(ActRec
*) = &svcreq::funcBodyHelper
;
322 auto const dest
= v
.makeReg();
323 v
<< simplecall(v
, helper
, rvmfp(), dest
);
328 TCA
emitFunctionEnterHelper(CodeBlock
& main
, CodeBlock
& cold
,
329 DataBlock
& data
, UniqueStubs
& us
) {
330 alignCacheLine(main
);
334 auto const start
= vwrap2(main
, cold
, data
, meta
, [&] (Vout
& v
, Vout
& vc
) {
335 auto const ar
= v
.makeReg();
337 v
<< copy
{rvmfp(), ar
};
339 // Fully set up the call frame for the stub. We can't skip this like we do
340 // in other stubs because we need the return IP for this frame in the %rbp
341 // chain, in order to find the proper fixup for the VMRegAnchor in the
342 // intercept handler.
343 v
<< stublogue
{true};
344 v
<< copy
{rsp(), rvmfp()};
346 // When we call the event hook, it might tell us to skip the callee
347 // (because of fb_intercept). If that happens, we need to return to the
348 // caller, but the handler will have already popped the callee's frame.
349 // So, we need to save these values for later.
350 v
<< pushpm
{ar
[AROFF(m_savedRip
)], ar
[AROFF(m_sfp
)]};
352 v
<< copy2
{ar
, v
.cns(EventHook::NormalFunc
), rarg(0), rarg(1)};
354 auto const done
= v
.makeBlock();
355 auto const ctch
= vc
.makeBlock();
356 auto const should_continue
= v
.makeReg();
357 bool (*hook
)(const ActRec
*, int) = &EventHook::onFunctionCall
;
360 CallSpec::direct(hook
),
361 v
.makeVcallArgs({{ar
, v
.cns(EventHook::NormalFunc
)}}),
362 v
.makeTuple({should_continue
}),
369 emitStubCatch(vc
, us
, [] (Vout
& v
) {
370 // Skip past the stuff we saved for the intercept case.
371 v
<< lea
{rsp()[16], rsp()};
372 // Undo our stub frame, so that rvmfp() points to the parent VM frame.
373 v
<< load
{rsp()[AROFF(m_sfp
)], rvmfp()};
378 auto const sf
= v
.makeReg();
379 v
<< testb
{should_continue
, should_continue
, sf
};
381 unlikelyIfThen(v
, vc
, CC_Z
, sf
, [&] (Vout
& v
) {
382 auto const saved_rip
= v
.makeReg();
384 // The event hook has already cleaned up the stack and popped the
385 // callee's frame, so we're ready to continue from the original call
386 // site. We just need to grab the fp/rip of the original frame that we
387 // saved earlier, and sync rvmsp().
388 v
<< popp
{rvmfp(), saved_rip
};
390 // Drop our call frame; the stublogue{} instruction guarantees that this
391 // is exactly 16 bytes.
392 v
<< lea
{rsp()[kNativeFrameSize
], rsp()};
394 // Sync vmsp and the return regs.
395 v
<< load
{rvmtl()[rds::kVmspOff
], rvmsp()};
396 v
<< load
{rvmsp()[TVOFF(m_data
)], rret_data()};
397 v
<< load
{rvmsp()[TVOFF(m_type
)], rret_type()};
399 // Return to the caller. This unbalances the return stack buffer, but if
400 // we're intercepting, we probably don't care.
401 v
<< jmpr
{saved_rip
};
404 // Skip past the stuff we saved for the intercept case.
405 v
<< lea
{rsp()[16], rsp()};
407 // Restore rvmfp() and return to the callee's func prologue.
408 v
<< stubret
{RegSet(), true};
411 meta
.process(nullptr);
415 TCA
emitFunctionSurprisedOrStackOverflow(CodeBlock
& main
,
418 const UniqueStubs
& us
) {
419 alignJmpTarget(main
);
423 auto const start
= vwrap2(main
, cold
, data
, meta
, [&] (Vout
& v
, Vout
& vc
) {
426 auto const done
= v
.makeBlock();
427 auto const ctch
= vc
.makeBlock();
429 v
<< vinvoke
{CallSpec::direct(handlePossibleStackOverflow
),
430 v
.makeVcallArgs({{rvmfp()}}), v
.makeTuple({}),
433 emitStubCatch(vc
, us
, [](Vout
& /*v*/) {});
436 v
<< tailcallstub
{us
.functionEnterHelper
};
439 meta
.process(nullptr);
443 ///////////////////////////////////////////////////////////////////////////////
446 void loadGenFrame(Vout
& v
, Vreg d
) {
447 auto const arOff
= BaseGenerator::arOff() -
448 (async
? AsyncGenerator::objectOff() : Generator::objectOff());
450 auto const gen
= v
.makeReg();
452 // We have to get the Generator object from the current frame's $this, then
453 // load the embedded frame.
454 v
<< load
{rvmfp()[AROFF(m_thisUnsafe
)], gen
};
455 v
<< lea
{gen
[arOff
], d
};
458 void debuggerRetImpl(Vout
& v
, Vreg ar
) {
459 auto const soff
= v
.makeReg();
461 v
<< loadl
{ar
[AROFF(m_soff
)], soff
};
462 v
<< storel
{soff
, rvmtl()[unwinderDebuggerReturnOffOff()]};
463 v
<< store
{rvmsp(), rvmtl()[unwinderDebuggerReturnSPOff()]};
465 auto const ret
= v
.makeReg();
466 v
<< simplecall(v
, unstashDebuggerCatch
, ar
, ret
);
471 TCA
emitInterpRet(CodeBlock
& cb
, DataBlock
& data
) {
474 auto const start
= vwrap(cb
, data
, [] (Vout
& v
) {
475 // Sync return regs before calling native assert function.
477 assertNativeStackAligned(v
);
479 v
<< lea
{rvmsp()[-kArRetOff
], r_svcreq_arg(0)};
480 v
<< copy
{rvmfp(), r_svcreq_arg(1)};
482 svcreq::emit_persistent(cb
, data
, folly::none
, REQ_POST_INTERP_RET
);
487 TCA
emitInterpGenRet(CodeBlock
& cb
, DataBlock
& data
) {
490 auto const start
= vwrap(cb
, data
, [] (Vout
& v
) {
491 // Sync return regs before calling native assert function.
493 assertNativeStackAligned(v
);
495 loadGenFrame
<async
>(v
, r_svcreq_arg(0));
496 v
<< copy
{rvmfp(), r_svcreq_arg(1)};
498 svcreq::emit_persistent(cb
, data
, folly::none
, REQ_POST_INTERP_RET
);
502 TCA
emitDebuggerInterpRet(CodeBlock
& cb
, DataBlock
& data
) {
505 return vwrap(cb
, data
, [] (Vout
& v
) {
506 // Sync return regs before calling native assert function.
508 assertNativeStackAligned(v
);
510 auto const ar
= v
.makeReg();
511 v
<< lea
{rvmsp()[-kArRetOff
], ar
};
512 debuggerRetImpl(v
, ar
);
517 TCA
emitDebuggerInterpGenRet(CodeBlock
& cb
, DataBlock
& data
) {
520 return vwrap(cb
, data
, [] (Vout
& v
) {
521 assertNativeStackAligned(v
);
523 auto const ar
= v
.makeReg();
524 loadGenFrame
<async
>(v
, ar
);
525 debuggerRetImpl(v
, ar
);
529 ///////////////////////////////////////////////////////////////////////////////
531 template<bool immutable
>
532 TCA
emitBindCallStub(CodeBlock
& cb
, DataBlock
& data
) {
533 return vwrap(cb
, data
, [] (Vout
& v
) {
534 v
<< phplogue
{rvmfp()};
536 auto args
= VregList
{ v
.makeReg(), v
.makeReg(), v
.makeReg() };
538 // Reconstruct the address of the call from the saved RIP.
539 auto const savedRIP
= v
.makeReg();
540 auto const callLen
= safe_cast
<int>(smashableCallLen());
541 v
<< load
{rvmfp()[AROFF(m_savedRip
)], savedRIP
};
542 v
<< subqi
{callLen
, savedRIP
, args
[0], v
.makeReg()};
544 v
<< copy
{rvmfp(), args
[1]};
545 v
<< movb
{v
.cns(immutable
), args
[2]};
547 auto const ret
= v
.makeReg();
550 CallSpec::direct(svcreq::handleBindCall
),
551 v
.makeVcallArgs({args
}),
557 v
<< tailcallphp
{ret
, rvmfp(), php_call_regs()};
561 TCA
emitFCallUnpackHelper(CodeBlock
& main
, CodeBlock
& cold
,
562 DataBlock
& data
, UniqueStubs
& us
) {
563 alignCacheLine(main
);
567 auto const ret
= vwrap2(main
, cold
, data
, meta
, [&] (Vout
& v
, Vout
& vc
) {
568 // We reach fcallUnpackHelper in the same context as a func prologue, so
569 // this should really be a phplogue{}---but we don't need the return
570 // address in the ActRec until later, and in the event the callee is
571 // intercepted, we must save it on the stack because the callee frame will
572 // already have been popped. So use a stublogue and "convert" it manually
578 auto const func
= v
.makeReg();
579 auto const unit
= v
.makeReg();
580 auto const bc
= v
.makeReg();
582 // Load fp->m_func->m_unit->m_bc.
583 v
<< load
{rvmfp()[AROFF(m_func
)], func
};
584 v
<< load
{func
[Func::unitOff()], unit
};
585 v
<< load
{unit
[Unit::bcOff()], bc
};
587 auto const pc
= v
.makeReg();
588 auto const next
= v
.makeReg();
590 // Convert offsets into PCs, and sync the PC.
591 v
<< addq
{bc
, rarg(0), pc
, v
.makeReg()};
592 v
<< store
{pc
, rvmtl()[rds::kVmpcOff
]};
593 v
<< addq
{bc
, rarg(1), next
, v
.makeReg()};
595 auto const retAddr
= v
.makeReg();
596 v
<< loadstubret
{retAddr
};
598 auto const done
= v
.makeBlock();
599 auto const ctch
= vc
.makeBlock();
600 auto const should_continue
= v
.makeReg();
601 bool (*helper
)(PC
, int32_t, void*) = &doFCallUnpackTC
;
604 CallSpec::direct(helper
),
605 v
.makeVcallArgs({{next
, rarg(2), retAddr
}}),
606 v
.makeTuple({should_continue
}),
612 emitStubCatch(vc
, us
, [] (Vout
& v
) { loadVMRegs(v
); });
616 // Load only rvmsp(); we need to wait to make sure we aren't skipping the
617 // callee before loading rvmfp().
618 v
<< load
{rvmtl()[rds::kVmspOff
], rvmsp()};
620 auto const sf
= v
.makeReg();
621 v
<< testb
{should_continue
, should_continue
, sf
};
623 unlikelyIfThen(v
, vc
, CC_Z
, sf
, [&] (Vout
& v
) {
624 // If false was returned, we should skip the callee. The interpreter
625 // will have popped the pre-live ActRec already, so we can just return to
626 // the caller after syncing the return regs.
630 v
<< load
{rvmtl()[rds::kVmfpOff
], rvmfp()};
632 // If true was returned, we're calling the callee, so undo the stublogue{}
633 // and convert to a phplogue{}.
634 v
<< stubtophp
{rvmfp()};
636 auto const callee
= v
.makeReg();
637 auto const body
= v
.makeReg();
639 v
<< load
{rvmfp()[AROFF(m_func
)], callee
};
640 emitLdLowPtr(v
, callee
[Func::funcBodyOff()], body
, sizeof(LowPtr
<uint8_t>));
642 // We jmp directly to the func body---this keeps the return stack buffer
643 // balanced between the call to this stub and the ret from the callee.
647 meta
.process(nullptr);
651 ///////////////////////////////////////////////////////////////////////////////
653 struct ResumeHelperEntryPoints
{
660 ResumeHelperEntryPoints
emitResumeHelpers(CodeBlock
& cb
, DataBlock
& data
) {
661 ResumeHelperEntryPoints rh
;
663 rh
.resumeHelperRet
= vwrap(cb
, data
, [] (Vout
& v
) {
664 v
<< phplogue
{rvmfp()};
666 rh
.resumeHelper
= vwrap(cb
, data
, [] (Vout
& v
) {
667 v
<< ldimmb
{0, rarg(0)};
670 rh
.handleResume
= vwrap(cb
, data
, [] (Vout
& v
) {
671 v
<< load
{rvmtl()[rds::kVmfpOff
], rvmfp()};
673 auto const handler
= reinterpret_cast<TCA
>(svcreq::handleResume
);
674 v
<< call
{handler
, arg_regs(2)};
677 rh
.reenterTC
= vwrap(cb
, data
, [] (Vout
& v
) {
678 // Save the return of handleResume(), then sync regs.
679 auto const target
= v
.makeReg();
680 v
<< copy
{rret(), target
};
683 loadReturnRegs(v
); // spurious load if we're not returning
691 TCA
emitResumeInterpHelpers(CodeBlock
& cb
, DataBlock
& data
, UniqueStubs
& us
,
692 ResumeHelperEntryPoints
& rh
) {
695 rh
= emitResumeHelpers(cb
, data
);
697 us
.resumeHelperRet
= rh
.resumeHelperRet
;
698 us
.resumeHelper
= rh
.resumeHelper
;
700 us
.interpHelper
= vwrap(cb
, data
, [] (Vout
& v
) {
701 v
<< store
{rarg(0), rvmtl()[rds::kVmpcOff
]};
703 us
.interpHelperSyncedPC
= vwrap(cb
, data
, [&] (Vout
& v
) {
705 v
<< ldimmb
{1, rarg(0)};
706 v
<< jmpi
{rh
.handleResume
, RegSet(rarg(0))};
709 us
.fcallAwaitSuspendHelper
= vwrap(cb
, data
, [&] (Vout
& v
) {
710 v
<< load
{rvmtl()[rds::kVmfpOff
], rvmfp()};
712 auto const handler
= reinterpret_cast<TCA
>(svcreq::handleFCallAwaitSuspend
);
713 v
<< call
{handler
, arg_regs(2)};
714 v
<< jmpi
{rh
.reenterTC
, RegSet()};
717 return us
.resumeHelperRet
;
720 TCA
emitInterpOneCFHelper(CodeBlock
& cb
, DataBlock
& data
, Op op
,
721 const ResumeHelperEntryPoints
& rh
) {
724 return vwrap(cb
, data
, [&] (Vout
& v
) {
725 v
<< copy2
{rvmfp(), rvmsp(), rarg(0), rarg(1)};
726 // rarg(2) is set at the stub callsite.
728 auto const handler
= reinterpret_cast<TCA
>(
729 interpOneEntryPoints
[static_cast<size_t>(op
)]
731 v
<< call
{handler
, arg_regs(3)};
733 auto const sf
= v
.makeReg();
734 auto const next
= v
.makeBlock();
736 v
<< testq
{rret(), rret(), sf
};
737 v
<< jcci
{CC_NZ
, sf
, next
, rh
.reenterTC
};
739 v
<< jmpi
{rh
.resumeHelper
};
743 void emitInterpOneCFHelpers(CodeBlock
& cb
, DataBlock
& data
, UniqueStubs
& us
,
744 const ResumeHelperEntryPoints
& rh
,
745 const CodeCache
& code
, Debug::DebugInfo
& dbg
) {
748 auto const emit
= [&] (Op op
, const char* name
) {
749 auto const stub
= emitInterpOneCFHelper(cb
, data
, op
, rh
);
750 us
.interpOneCFHelpers
[op
] = stub
;
751 us
.add(name
, stub
, code
, dbg
);
754 #define O(name, imm, in, out, flags) \
755 if (((flags) & CF) || ((flags) & TF)) { \
756 emit(Op::name, "interpOneCFHelper"#name); \
761 // Exit is a very special snowflake. Because it can appear in PHP
762 // expressions, the emitter pretends that it pushed a value on the eval stack
763 // (and iopExit actually does push Null right before throwing). Marking it
764 // as TF would mess up any bytecodes that want to consume its output value,
765 // so we can't do that. But we also don't want to extend regions past it, so
766 // the JIT treats it as terminal and uses InterpOneCF to execute it.
767 emit(Op::Exit
, "interpOneCFHelperExit");
770 ///////////////////////////////////////////////////////////////////////////////
772 TCA
emitDecRefGeneric(CodeBlock
& cb
, DataBlock
& data
) {
776 auto const start
= vwrap(cb
, data
, meta
, [] (Vout
& v
) {
778 auto const fullFrame
= [&] {
788 v
<< stublogue
{fullFrame
};
790 v
<< copy
{rsp(), rvmfp()};
793 auto const rdata
= rarg(0);
794 auto const rtype
= rarg(1);
796 auto const destroy
= [&] (Vout
& v
) {
797 // decRefGeneric is called via callfaststub, whose ABI claims that all
798 // registers are preserved. This is true in the fast path, but in the
799 // slow path we need to manually save caller-saved registers.
800 auto const callerSaved
= abi().gpUnreserved
- abi().calleeSaved
;
801 PhysRegSaver prs
{v
, callerSaved
};
803 // As a consequence of being called via callfaststub, we can't safely use
804 // any Vregs here except for status flags registers, at least not with
805 // the default vwrap() ABI. Just use the argument registers instead.
806 assertx(callerSaved
.contains(rdata
));
807 assertx(callerSaved
.contains(rtype
));
809 auto const dtor
= lookupDestructor(v
, rtype
);
810 v
<< callm
{dtor
, arg_regs(1)};
813 // The stub frame's saved RIP is at %rsp[8] before we saved the
814 // caller-saved registers.
815 v
<< syncpoint
{makeIndirectFixup(prs
.dwordsPushed())};
819 emitDecRefWork(v
, v
, rdata
, destroy
, false, TRAP_REASON
);
821 v
<< stubret
{{}, fullFrame
};
824 meta
.process(nullptr);
828 ///////////////////////////////////////////////////////////////////////////////
830 TCA
emitEnterTCExit(CodeBlock
& cb
, DataBlock
& data
, UniqueStubs
& /*us*/) {
833 return vwrap(cb
, data
, [&] (Vout
& v
) {
834 // Eagerly save VM regs.
837 // Realign the native stack.
841 v
<< lea
{rsp()[8], rsp()};
847 // Store the return value on the top of the eval stack. Whenever we get to
848 // enterTCExit, we're semantically executing some PHP construct that sends
849 // a return value out of a function (either a RetC, or a Yield, or an Await
850 // that's suspending, etc), and moreover, we must be executing the return
851 // that leaves this level of VM reentry (i.e. the only way we get here is
852 // by coming from the callToExit stub or by a phpret{} or leavetc{} that
853 // undoes the calltc{} or resumetc{} in enterTCHelper).
855 // Either way, we have a live PHP return value in the return registers,
856 // which we need to put on the top of the evaluation stack.
859 // Perform a native return.
861 // On PPC64, as there is no new frame created when entering the VM, the FP
862 // must not be saved.
863 v
<< stubret
{RegSet(), arch() != Arch::PPC64
};
867 TCA
emitEnterTCHelper(CodeBlock
& cb
, DataBlock
& data
, UniqueStubs
& us
) {
870 auto const sp
= rarg(0);
871 auto const fp
= rarg(1);
872 auto const start
= rarg(2);
873 auto const firstAR
= rarg(3);
875 auto const tl
= reg::r10
;
876 auto const calleeAR
= reg::r11
;
878 auto const tl
= rarg(4);
879 auto const calleeAR
= rarg(5);
882 return vwrap2(cb
, cb
, data
, [&] (Vout
& v
, Vout
& vc
) {
883 // Architecture-specific setup for entering the TC.
886 // Native func prologue.
887 v
<< stublogue
{arch() != Arch::PPC64
};
890 // Windows hates argument registers.
891 v
<< load
{rsp()[0x28], reg::r10
};
892 v
<< load
{rsp()[0x30], reg::r11
};
895 // Set up linkage with the top VM frame in this nesting.
896 v
<< store
{rsp(), firstAR
[AROFF(m_sfp
)]};
898 // Set up the VM registers.
899 v
<< copy
{fp
, rvmfp()};
900 v
<< copy
{sp
, rvmsp()};
901 v
<< copy
{tl
, rvmtl()};
903 // Unalign the native stack.
907 v
<< lea
{rsp()[-8], rsp()};
913 // Check if `calleeAR' was set.
914 auto const sf
= v
.makeReg();
915 v
<< testq
{calleeAR
, calleeAR
, sf
};
917 // We mark this block as unlikely in order to coax the emitter into
918 // ordering this block last. This is an important optimization for x64;
919 // without it, both the jcc for the branch and the jmp for the resumetc{}
920 // will end up in the same 16-byte extent of code, which messes up the
922 unlikelyIfThen(v
, vc
, CC_Z
, sf
, [&] (Vout
& v
) {
923 // No callee means we're resuming in the middle of a TC function.
924 v
<< resumetc
{start
, us
.enterTCExit
, vm_regs_with_sp()};
927 // We have a callee; set rvmfp() and call it.
928 v
<< copy
{calleeAR
, rvmfp()};
929 v
<< calltc
{start
, rvmfp(), us
.enterTCExit
, vm_regs_with_sp()};
933 TCA
emitHandleSRHelper(CodeBlock
& cb
, DataBlock
& data
) {
936 return vwrap(cb
, data
, [] (Vout
& v
) {
939 // Pack the service request args into a svcreq::ReqInfo on the stack.
940 assertx(!(svcreq::kMaxArgs
& 1));
941 for (auto i
= svcreq::kMaxArgs
; i
>= 2; i
-= 2) {
942 v
<< pushp
{r_svcreq_arg(i
- 1), r_svcreq_arg(i
- 2)};
944 v
<< pushp
{r_svcreq_stub(), r_svcreq_req()};
946 // Call mcg->handleServiceRequest(rsp()).
947 auto const sp
= v
.makeReg();
948 v
<< copy
{rsp(), sp
};
950 auto const ret
= v
.makeReg();
953 CallSpec::direct(svcreq::handleServiceRequest
),
954 v
.makeVcallArgs({{sp
}}),
960 // Pop the ReqInfo off the stack.
961 auto const reqinfo_sz
= static_cast<int>(sizeof(svcreq::ReqInfo
));
962 v
<< lea
{rsp()[reqinfo_sz
], rsp()};
964 // rvmtl() was preserved by the callee, but rvmsp() and rvmfp() might've
965 // changed if we interpreted anything. Reload them. Also load the return
966 // regs; if we're not returning, it's a spurious load.
974 ///////////////////////////////////////////////////////////////////////////////
976 TCA
emitEndCatchHelper(CodeBlock
& cb
, DataBlock
& data
, UniqueStubs
& us
) {
979 auto const udrspo
= rvmtl()[unwinderDebuggerReturnSPOff()];
981 auto const debuggerReturn
= vwrap(cb
, data
, [&] (Vout
& v
) {
982 v
<< load
{udrspo
, rvmsp()};
983 v
<< storeqi
{0, udrspo
};
985 svcreq::emit_persistent(cb
, data
, folly::none
, REQ_POST_DEBUGGER_RET
);
989 auto const resumeCPPUnwind
= vwrap(cb
, data
, meta
, [&] (Vout
& v
) {
990 static_assert(sizeof(tl_regState
) == 8,
991 "The following store must match the size of tl_regState.");
992 auto const regstate
= emitTLSAddr(v
, tls_datum(tl_regState
));
993 v
<< storeqi
{static_cast<int32_t>(VMRegState::CLEAN
), regstate
};
995 v
<< load
{rvmtl()[unwinderExnOff()], rarg(0)};
996 v
<< call
{TCA(_Unwind_Resume
), arg_regs(1), &us
.endCatchHelperPast
};
997 v
<< trap
{TRAP_REASON
};
999 meta
.process(nullptr);
1003 return vwrap(cb
, data
, [&] (Vout
& v
) {
1004 auto const done1
= v
.makeBlock();
1005 auto const sf1
= v
.makeReg();
1007 v
<< cmpqim
{0, udrspo
, sf1
};
1008 v
<< jcci
{CC_NE
, sf1
, done1
, debuggerReturn
};
1011 // Normal end catch situation: call back to tc_unwind_resume, which returns
1012 // the catch trace (or null) in the first return register, and the new vmfp
1014 v
<< copy
{rvmfp(), rarg(0)};
1015 v
<< call
{TCA(tc_unwind_resume
), arg_regs(1)};
1016 v
<< copy
{rret(1), rvmfp()};
1018 auto const done2
= v
.makeBlock();
1019 auto const sf2
= v
.makeReg();
1021 v
<< testq
{rret(0), rret(0), sf2
};
1022 v
<< jcci
{CC_Z
, sf2
, done2
, resumeCPPUnwind
};
1029 TCA
emitUnknownExceptionHandler(CodeBlock
& cb
,
1035 auto const ret
= vwrap(cb
, data
, meta
, [&] (Vout
& v
) {
1037 TCA(unknownExceptionHandler
), {}, &us
.unknownExceptionHandlerPast
1040 meta
.process(nullptr);
1045 TCA
emitThrowSwitchMode(CodeBlock
& cb
, DataBlock
& data
) {
1048 return vwrap(cb
, data
, [] (Vout
& v
) {
1049 v
<< call
{TCA(throwSwitchMode
)};
1050 v
<< trap
{TRAP_REASON
};
1055 TCA
emitHelperThunk(CodeCache
& code
, CodeBlock
& cb
, DataBlock
& data
, F
* func
) {
1056 // we only emit these calls into hot, main and cold.
1057 if (deltaFits(code
.base() - (TCA
)func
, sz::dword
) &&
1058 deltaFits(code
.frozen().base() - (TCA
)func
, sz::dword
)) {
1062 return vwrap(cb
, data
, [&] (Vout
& v
) {
1063 v
<< jmpi
{(TCA
)func
};
1067 ///////////////////////////////////////////////////////////////////////////////
1071 void UniqueStubs::emitAll(CodeCache
& code
, Debug::DebugInfo
& dbg
) {
1072 auto view
= code
.view();
1073 auto& main
= view
.main();
1074 auto& cold
= view
.cold();
1075 auto& frozen
= view
.frozen();
1076 auto& hotBlock
= code
.view(TransKind::Optimize
).main();
1077 auto& data
= view
.data();
1079 auto const hot
= [&]() -> CodeBlock
& {
1080 return hotBlock
.available() > 512 ? hotBlock
: main
;
1083 #define ADD(name, stub) name = add(#name, (stub), code, dbg)
1084 ADD(enterTCExit
, emitEnterTCExit(main
, data
, *this));
1086 decltype(enterTCHelper
)(add("enterTCHelper",
1087 emitEnterTCHelper(main
, data
, *this),
1091 // These guys are required by a number of other stubs.
1092 ADD(handleSRHelper
, emitHandleSRHelper(hot(), data
));
1093 ADD(endCatchHelper
, emitEndCatchHelper(hot(), data
, *this));
1094 ADD(unknownExceptionHandler
, emitUnknownExceptionHandler(cold
, data
, *this));
1096 ADD(funcPrologueRedispatch
, emitFuncPrologueRedispatch(hot(), data
));
1097 ADD(fcallHelperThunk
, emitFCallHelperThunk(cold
, frozen
, data
));
1098 ADD(funcBodyHelperThunk
, emitFuncBodyHelperThunk(cold
, data
));
1099 ADD(functionEnterHelper
, emitFunctionEnterHelper(cold
, frozen
, data
, *this));
1100 ADD(functionSurprisedOrStackOverflow
,
1101 emitFunctionSurprisedOrStackOverflow(cold
, frozen
, data
, *this));
1103 ADD(retHelper
, emitInterpRet(hot(), data
));
1104 ADD(genRetHelper
, emitInterpGenRet
<false>(cold
, data
));
1105 ADD(asyncGenRetHelper
, emitInterpGenRet
<true>(hot(), data
));
1106 ADD(retInlHelper
, emitInterpRet(hot(), data
));
1107 ADD(debuggerRetHelper
, emitDebuggerInterpRet(cold
, data
));
1108 ADD(debuggerGenRetHelper
, emitDebuggerInterpGenRet
<false>(cold
, data
));
1109 ADD(debuggerAsyncGenRetHelper
, emitDebuggerInterpGenRet
<true>(cold
, data
));
1111 ADD(bindCallStub
, emitBindCallStub
<false>(cold
, data
));
1112 ADD(immutableBindCallStub
, emitBindCallStub
<true>(cold
, data
));
1113 ADD(fcallUnpackHelper
, emitFCallUnpackHelper(hot(), cold
, data
, *this));
1115 ADD(decRefGeneric
, emitDecRefGeneric(hot(), data
));
1117 ADD(callToExit
, emitCallToExit(hot(), data
, *this));
1118 ADD(throwSwitchMode
, emitThrowSwitchMode(frozen
, data
));
1120 ADD(handlePrimeCacheInit
,
1121 emitHelperThunk(code
, cold
, data
,
1122 MethodCache::handlePrimeCacheInit
<false>));
1123 ADD(handlePrimeCacheInitFatal
,
1124 emitHelperThunk(code
, cold
, data
,
1125 MethodCache::handlePrimeCacheInit
<true>));
1127 emitHelperThunk(code
, main
, data
,
1128 MethodCache::handleSlowPath
<false>));
1129 ADD(handleSlowPathFatal
,
1130 emitHelperThunk(code
, main
, data
,
1131 MethodCache::handleSlowPath
<true>));
1135 add("freeLocalsHelpers",
1136 emitFreeLocalsHelpers(hot(), data
, *this), code
, dbg
);
1138 ResumeHelperEntryPoints rh
;
1139 add("resumeInterpHelpers",
1140 emitResumeInterpHelpers(hot(), data
, *this, rh
),
1142 emitInterpOneCFHelpers(cold
, data
, *this, rh
, code
, dbg
);
1144 emitAllResumable(code
, dbg
);
1147 ///////////////////////////////////////////////////////////////////////////////
1149 TCA
UniqueStubs::add(const char* name
, TCA start
,
1150 const CodeCache
& code
, Debug::DebugInfo
& dbg
) {
1151 if (!code
.isValidCodeAddress(start
)) return start
;
1153 auto& cb
= code
.blockFor(start
);
1154 auto const end
= cb
.frontier();
1156 FTRACE(1, "unique stub: {} @ {} -- {:4} bytes: {}\n",
1158 static_cast<void*>(start
),
1159 static_cast<size_t>(end
- start
),
1164 std::ostringstream os
;
1165 disasmRange(os
, start
, end
);
1166 FTRACE(2, "{}\n", os
.str());
1170 if (!RuntimeOption::EvalJitNoGdb
) {
1171 dbg
.recordStub(Debug::TCRange(start
, end
, &cb
== &code
.cold()),
1172 folly::sformat("HHVM::{}", name
));
1174 if (RuntimeOption::EvalJitUseVtuneAPI
) {
1175 reportHelperToVtune(folly::sformat("HHVM::{}", name
).c_str(),
1179 if (RuntimeOption::EvalPerfPidMap
) {
1180 dbg
.recordPerfMap(Debug::TCRange(start
, end
, &cb
== &code
.cold()),
1185 folly::sformat("HHVM::{}", name
));
1188 auto const newStub
= StubRange
{name
, start
, end
};
1189 auto lower
= std::lower_bound(m_ranges
.begin(), m_ranges
.end(), newStub
);
1191 // We assume ranges are non-overlapping.
1192 assertx(lower
== m_ranges
.end() || newStub
.end
<= lower
->start
);
1193 assertx(lower
== m_ranges
.begin() || (lower
- 1)->end
<= newStub
.start
);
1194 m_ranges
.insert(lower
, newStub
);
1198 std::string
UniqueStubs::describe(TCA address
) const {
1199 auto raw
= [address
] { return folly::sformat("{}", address
); };
1200 if (m_ranges
.empty()) return raw();
1202 auto const dummy
= StubRange
{"", address
, nullptr};
1203 auto lower
= std::upper_bound(m_ranges
.begin(), m_ranges
.end(), dummy
);
1204 if (lower
== m_ranges
.begin()) return raw();
1207 if (lower
->contains(address
)) {
1208 return folly::sformat("{}+{:#x}", lower
->name
, address
- lower
->start
);
1213 ///////////////////////////////////////////////////////////////////////////////
1215 RegSet
interp_one_cf_regs() {
1216 return vm_regs_with_sp() | rarg(2);
1219 void emitInterpReq(Vout
& v
, SrcKey sk
, FPInvOffset spOff
) {
1220 if (sk
.resumeMode() == ResumeMode::None
) {
1221 v
<< lea
{rvmfp()[-cellsToBytes(spOff
.offset
)], rvmsp()};
1223 v
<< copy
{v
.cns(sk
.pc()), rarg(0)};
1224 v
<< jmpi
{tc::ustubs().interpHelper
, arg_regs(1)};
1227 ///////////////////////////////////////////////////////////////////////////////
1229 void enterTCImpl(TCA start
, ActRec
* stashedAR
) {
1230 // We have to force C++ to spill anything that might be in a callee-saved
1231 // register (aside from rvmfp()), since enterTCHelper does not save them.
1232 CALLEE_SAVED_BARRIER();
1233 auto& regs
= vmRegsUnsafe();
1234 tc::ustubs().enterTCHelper(regs
.stack
.top(), regs
.fp
, start
,
1235 vmFirstAR(), rds::tl_base
, stashedAR
);
1236 CALLEE_SAVED_BARRIER();
1239 ///////////////////////////////////////////////////////////////////////////////