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 align(cb
, nullptr, Alignment::JmpTarget
, AlignContext::Dead
);
93 void assertNativeStackAligned(Vout
& v
) {
94 if (RuntimeOption::EvalHHIRGenerateAsserts
) {
95 v
<< call
{TCA(assert_native_stack_aligned
)};
100 * Load and store the VM registers from/to RDS.
102 void loadVMRegs(Vout
& v
) {
103 v
<< load
{rvmtl()[rds::kVmfpOff
], rvmfp()};
104 v
<< load
{rvmtl()[rds::kVmspOff
], rvmsp()};
106 void storeVMRegs(Vout
& v
) {
107 v
<< store
{rvmfp(), rvmtl()[rds::kVmfpOff
]};
108 v
<< store
{rvmsp(), rvmtl()[rds::kVmspOff
]};
112 * Load and store the PHP return registers from/to the top of the VM stack.
114 * Note that we don't do loadb{}/storeb{} for the type register, because we
115 * sometimes need to preserve the m_aux field across returns.
117 void loadReturnRegs(Vout
& v
) {
118 v
<< load
{rvmsp()[TVOFF(m_data
)], rret_data()};
119 v
<< load
{rvmsp()[TVOFF(m_type
)], rret_type()};
121 void storeReturnRegs(Vout
& v
) {
122 v
<< store
{rret_data(), rvmsp()[TVOFF(m_data
)]};
123 v
<< store
{rret_type(), rvmsp()[TVOFF(m_type
)]};
127 * Convenience wrapper around a simple vcall to `helper', with a single `arg'
128 * and a return value in `d'.
131 Vinstr
simplecall(Vout
& v
, F helper
, Vreg arg
, Vreg d
) {
133 CallSpec::direct(helper
),
134 v
.makeVcallArgs({{arg
}}),
142 * Convenience wrapper around a simple vcall to `helper', with a single `arg'
143 * and a pair of return values in `d1' and `d2'.
146 Vinstr
simplecall(Vout
& v
, F helper
, Vreg arg
, Vreg d1
, Vreg d2
) {
148 CallSpec::direct(helper
),
149 v
.makeVcallArgs({{arg
}}),
150 v
.makeTuple({d1
, d2
}),
157 * Emit a catch trace that unwinds a stub context back to the PHP context that
160 template<class GenFn
>
161 void emitStubCatch(Vout
& v
, const UniqueStubs
& us
, GenFn gen
) {
162 always_assert(us
.endCatchHelper
);
166 v
<< jmpi
{us
.endCatchHelper
};
169 ///////////////////////////////////////////////////////////////////////////////
171 TCA
emitFreeLocalsHelpers(CodeBlock
& cb
, DataBlock
& data
, UniqueStubs
& us
) {
172 return ARCH_SWITCH_CALL(emitFreeLocalsHelpers
, cb
, data
, us
);
175 TCA
emitCallToExit(CodeBlock
& cb
, DataBlock
& data
, UniqueStubs
& us
) {
176 return ARCH_SWITCH_CALL(emitCallToExit
, cb
, data
, us
);
179 ///////////////////////////////////////////////////////////////////////////////
181 struct FCallHelperRet
{
186 FCallHelperRet
fcallHelper(ActRec
* ar
) {
187 assert_native_stack_aligned();
188 assertx(!ar
->resumed());
190 if (LIKELY(!RuntimeOption::EvalFailJitPrologs
)) {
191 auto const tca
= mcgen::getFuncPrologue(
192 const_cast<Func
*>(ar
->func()),
195 if (tca
) return { tca
, nullptr };
198 // Check for stack overflow in the same place func prologues make their
199 // StackCheck::Early check (see irgen-func-prologue.cpp). This handler also
200 // cleans and syncs vmRegs for us.
201 if (checkCalleeStackOverflow(ar
)) handleStackOverflow(ar
);
203 // If doFCall indicates that the function was intercepted and should be
204 // skipped, it will have already torn down the callee's frame. So, we need to
205 // save the return value thats in it.
206 auto const retAddr
= (TCA
)ar
->m_savedRip
;
210 if (doFCall(ar
, vmpc())) {
211 return { tc::ustubs().resumeHelperRet
, nullptr };
213 // We've been asked to skip the function body (fb_intercept). The vmregs
214 // have already been fixed; indicate this with a nullptr return.
215 return { nullptr, retAddr
};
217 // The VMRegAnchor above took care of us, but we need to tell the unwinder
218 // (since ~VMRegAnchor() will have reset tl_regState).
219 tl_regState
= VMRegState::CLEAN
;
224 ///////////////////////////////////////////////////////////////////////////////
226 TCA
emitFuncPrologueRedispatch(CodeBlock
& cb
, DataBlock
& data
) {
229 return vwrap(cb
, data
, [] (Vout
& v
) {
230 auto const func
= v
.makeReg();
231 v
<< load
{rvmfp()[AROFF(m_func
)], func
};
233 auto const argc
= v
.makeReg();
234 auto const naaf
= v
.makeReg();
235 v
<< loadl
{rvmfp()[AROFF(m_numArgsAndFlags
)], naaf
};
236 v
<< andli
{ActRec::kNumArgsMask
, naaf
, argc
, v
.makeReg()};
238 auto const nparams
= v
.makeReg();
239 auto const pcounts
= v
.makeReg();
240 v
<< loadl
{func
[Func::paramCountsOff()], pcounts
};
241 v
<< shrli
{0x1, pcounts
, nparams
, v
.makeReg()};
243 auto const sf
= v
.makeReg();
244 v
<< cmpl
{argc
, nparams
, sf
};
246 auto const pTabOff
= safe_cast
<int32_t>(Func::prologueTableOff());
247 auto const ptrSize
= safe_cast
<int32_t>(sizeof(LowPtr
<uint8_t>));
249 // If we passed more args than declared, we need to dispatch to the
250 // "too many arguments" prologue.
251 ifThen(v
, CC_L
, sf
, [&] (Vout
& v
) {
252 auto const dest
= v
.makeReg();
254 auto const nargs
= v
.makeReg();
255 v
<< movzlq
{nparams
, nargs
};
257 emitLdLowPtr(v
, func
[nargs
* ptrSize
+ (pTabOff
+ ptrSize
)],
258 dest
, sizeof(LowPtr
<uint8_t>));
262 auto const nargs
= v
.makeReg();
263 v
<< movzlq
{argc
, nargs
};
265 auto const dest
= v
.makeReg();
266 emitLdLowPtr(v
, func
[nargs
* ptrSize
+ pTabOff
],
267 dest
, sizeof(LowPtr
<uint8_t>));
272 TCA
emitFCallHelperThunk(CodeBlock
& main
, CodeBlock
& cold
, DataBlock
& data
) {
273 alignJmpTarget(main
);
275 return vwrap2(main
, cold
, data
, [] (Vout
& v
, Vout
& vc
) {
276 v
<< phplogue
{rvmfp()};
278 // fcallHelper asserts native stack alignment for us.
279 FCallHelperRet (*helper
)(ActRec
*) = &fcallHelper
;
280 auto const dest
= v
.makeReg();
281 auto const saved_rip
= v
.makeReg();
282 v
<< simplecall(v
, helper
, rvmfp(), dest
, saved_rip
);
284 // Clobber rvmsp in debug builds.
285 if (debug
) v
<< copy
{v
.cns(0x1), rvmsp()};
287 auto const sf
= v
.makeReg();
288 v
<< testq
{dest
, dest
, sf
};
290 unlikelyIfThen(v
, vc
, CC_Z
, sf
, [&] (Vout
& v
) {
291 // A nullptr dest means the callee was intercepted and should be
292 // skipped. In that case, saved_rip will contain the return address that
293 // was in the callee's ActRec before it was torn down by the intercept.
297 // Return to the caller. This unbalances the return stack buffer, but if
298 // we're intercepting, we probably don't care.
299 v
<< jmpr
{saved_rip
};
302 // Jump to the func prologue.
303 v
<< tailcallphp
{dest
, rvmfp(), php_call_regs()};
307 TCA
emitFuncBodyHelperThunk(CodeBlock
& cb
, DataBlock
& data
) {
310 return vwrap(cb
, data
, [] (Vout
& v
) {
311 TCA (*helper
)(ActRec
*) = &svcreq::funcBodyHelper
;
312 auto const dest
= v
.makeReg();
313 v
<< simplecall(v
, helper
, rvmfp(), dest
);
318 TCA
emitFunctionEnterHelper(CodeBlock
& main
, CodeBlock
& cold
,
319 DataBlock
& data
, UniqueStubs
& us
) {
320 alignJmpTarget(main
);
324 auto const start
= vwrap2(main
, cold
, data
, meta
, [&] (Vout
& v
, Vout
& vc
) {
325 auto const ar
= v
.makeReg();
327 v
<< copy
{rvmfp(), ar
};
329 // Fully set up the call frame for the stub. We can't skip this like we do
330 // in other stubs because we need the return IP for this frame in the %rbp
331 // chain, in order to find the proper fixup for the VMRegAnchor in the
332 // intercept handler.
333 v
<< stublogue
{true};
334 v
<< copy
{rsp(), rvmfp()};
336 // When we call the event hook, it might tell us to skip the callee
337 // (because of fb_intercept). If that happens, we need to return to the
338 // caller, but the handler will have already popped the callee's frame.
339 // So, we need to save these values for later.
340 v
<< pushpm
{ar
[AROFF(m_savedRip
)], ar
[AROFF(m_sfp
)]};
342 v
<< copy2
{ar
, v
.cns(EventHook::NormalFunc
), rarg(0), rarg(1)};
344 auto const done
= v
.makeBlock();
345 auto const ctch
= vc
.makeBlock();
346 auto const should_continue
= v
.makeReg();
347 bool (*hook
)(const ActRec
*, int) = &EventHook::onFunctionCall
;
350 CallSpec::direct(hook
),
351 v
.makeVcallArgs({{ar
, v
.cns(EventHook::NormalFunc
)}}),
352 v
.makeTuple({should_continue
}),
359 emitStubCatch(vc
, us
, [] (Vout
& v
) {
360 // Skip past the stuff we saved for the intercept case.
361 v
<< lea
{rsp()[16], rsp()};
362 // Undo our stub frame, so that rvmfp() points to the parent VM frame.
363 v
<< load
{rsp()[AROFF(m_sfp
)], rvmfp()};
368 auto const sf
= v
.makeReg();
369 v
<< testb
{should_continue
, should_continue
, sf
};
371 unlikelyIfThen(v
, vc
, CC_Z
, sf
, [&] (Vout
& v
) {
372 auto const saved_rip
= v
.makeReg();
374 // The event hook has already cleaned up the stack and popped the
375 // callee's frame, so we're ready to continue from the original call
376 // site. We just need to grab the fp/rip of the original frame that we
377 // saved earlier, and sync rvmsp().
378 v
<< popp
{rvmfp(), saved_rip
};
380 // Drop our call frame; the stublogue{} instruction guarantees that this
381 // is exactly 16 bytes.
382 v
<< lea
{rsp()[kNativeFrameSize
], rsp()};
384 // Sync vmsp and the return regs.
385 v
<< load
{rvmtl()[rds::kVmspOff
], rvmsp()};
386 v
<< load
{rvmsp()[TVOFF(m_data
)], rret_data()};
387 v
<< load
{rvmsp()[TVOFF(m_type
)], rret_type()};
389 // Return to the caller. This unbalances the return stack buffer, but if
390 // we're intercepting, we probably don't care.
391 v
<< jmpr
{saved_rip
};
394 // Skip past the stuff we saved for the intercept case.
395 v
<< lea
{rsp()[16], rsp()};
397 // Restore rvmfp() and return to the callee's func prologue.
398 v
<< stubret
{RegSet(), true};
401 meta
.process(nullptr);
405 TCA
emitFunctionSurprisedOrStackOverflow(CodeBlock
& main
,
408 const UniqueStubs
& us
) {
409 alignJmpTarget(main
);
413 auto const start
= vwrap2(main
, cold
, data
, meta
, [&] (Vout
& v
, Vout
& vc
) {
416 auto const done
= v
.makeBlock();
417 auto const ctch
= vc
.makeBlock();
419 v
<< vinvoke
{CallSpec::direct(handlePossibleStackOverflow
),
420 v
.makeVcallArgs({{rvmfp()}}), v
.makeTuple({}),
423 emitStubCatch(vc
, us
, [](Vout
& /*v*/) {});
426 v
<< tailcallstub
{us
.functionEnterHelper
};
429 meta
.process(nullptr);
433 ///////////////////////////////////////////////////////////////////////////////
436 void loadGenFrame(Vout
& v
, Vreg d
) {
437 auto const arOff
= BaseGenerator::arOff() -
438 (async
? AsyncGenerator::objectOff() : Generator::objectOff());
440 auto const gen
= v
.makeReg();
442 // We have to get the Generator object from the current frame's $this, then
443 // load the embedded frame.
444 v
<< load
{rvmfp()[AROFF(m_thisUnsafe
)], gen
};
445 v
<< lea
{gen
[arOff
], d
};
448 void debuggerRetImpl(Vout
& v
, Vreg ar
) {
449 auto const soff
= v
.makeReg();
451 v
<< loadl
{ar
[AROFF(m_soff
)], soff
};
452 v
<< storel
{soff
, rvmtl()[unwinderDebuggerReturnOffOff()]};
453 v
<< store
{rvmsp(), rvmtl()[unwinderDebuggerReturnSPOff()]};
455 auto const ret
= v
.makeReg();
456 v
<< simplecall(v
, unstashDebuggerCatch
, ar
, ret
);
461 TCA
emitInterpRet(CodeBlock
& cb
, DataBlock
& data
) {
464 auto const start
= vwrap(cb
, data
, [] (Vout
& v
) {
465 // Sync return regs before calling native assert function.
467 assertNativeStackAligned(v
);
469 v
<< lea
{rvmsp()[-kArRetOff
], r_svcreq_arg(0)};
470 v
<< copy
{rvmfp(), r_svcreq_arg(1)};
472 svcreq::emit_persistent(cb
, data
, folly::none
, REQ_POST_INTERP_RET
);
477 TCA
emitInterpGenRet(CodeBlock
& cb
, DataBlock
& data
) {
480 auto const start
= vwrap(cb
, data
, [] (Vout
& v
) {
481 // Sync return regs before calling native assert function.
483 assertNativeStackAligned(v
);
485 loadGenFrame
<async
>(v
, r_svcreq_arg(0));
486 v
<< copy
{rvmfp(), r_svcreq_arg(1)};
488 svcreq::emit_persistent(cb
, data
, folly::none
, REQ_POST_INTERP_RET
);
492 TCA
emitDebuggerInterpRet(CodeBlock
& cb
, DataBlock
& data
) {
495 return vwrap(cb
, data
, [] (Vout
& v
) {
496 // Sync return regs before calling native assert function.
498 assertNativeStackAligned(v
);
500 auto const ar
= v
.makeReg();
501 v
<< lea
{rvmsp()[-kArRetOff
], ar
};
502 debuggerRetImpl(v
, ar
);
507 TCA
emitDebuggerInterpGenRet(CodeBlock
& cb
, DataBlock
& data
) {
510 return vwrap(cb
, data
, [] (Vout
& v
) {
511 assertNativeStackAligned(v
);
513 auto const ar
= v
.makeReg();
514 loadGenFrame
<async
>(v
, ar
);
515 debuggerRetImpl(v
, ar
);
519 ///////////////////////////////////////////////////////////////////////////////
521 template<bool immutable
>
522 TCA
emitBindCallStub(CodeBlock
& cb
, DataBlock
& data
) {
523 return vwrap(cb
, data
, [] (Vout
& v
) {
524 v
<< phplogue
{rvmfp()};
526 auto args
= VregList
{ v
.makeReg(), v
.makeReg(), v
.makeReg() };
528 // Reconstruct the address of the call from the saved RIP.
529 auto const savedRIP
= v
.makeReg();
530 auto const callLen
= safe_cast
<int>(smashableCallLen());
531 v
<< load
{rvmfp()[AROFF(m_savedRip
)], savedRIP
};
532 v
<< subqi
{callLen
, savedRIP
, args
[0], v
.makeReg()};
534 v
<< copy
{rvmfp(), args
[1]};
535 v
<< movb
{v
.cns(immutable
), args
[2]};
537 auto const ret
= v
.makeReg();
540 CallSpec::direct(svcreq::handleBindCall
),
541 v
.makeVcallArgs({args
}),
547 v
<< tailcallphp
{ret
, rvmfp(), php_call_regs()};
551 TCA
emitFCallArrayHelper(CodeBlock
& main
, CodeBlock
& cold
,
552 DataBlock
& data
, UniqueStubs
& us
) {
553 align(main
, nullptr, Alignment::CacheLine
, AlignContext::Dead
);
557 auto const ret
= vwrap(main
, data
, [] (Vout
& v
) {
558 v
<< movl
{v
.cns(0), rarg(2)};
561 us
.fcallUnpackHelper
= vwrap2(main
, cold
, data
, meta
,
562 [&] (Vout
& v
, Vout
& vc
) {
563 // We reach fcallArrayHelper in the same context as a func prologue, so
564 // this should really be a phplogue{}---but we don't need the return
565 // address in the ActRec until later, and in the event the callee is
566 // intercepted, we must save it on the stack because the callee frame will
567 // already have been popped. So use a stublogue and "convert" it manually
573 auto const func
= v
.makeReg();
574 auto const unit
= v
.makeReg();
575 auto const bc
= v
.makeReg();
577 // Load fp->m_func->m_unit->m_bc.
578 v
<< load
{rvmfp()[AROFF(m_func
)], func
};
579 v
<< load
{func
[Func::unitOff()], unit
};
580 v
<< load
{unit
[Unit::bcOff()], bc
};
582 auto const pc
= v
.makeReg();
583 auto const next
= v
.makeReg();
585 // Convert offsets into PCs, and sync the PC.
586 v
<< addq
{bc
, rarg(0), pc
, v
.makeReg()};
587 v
<< store
{pc
, rvmtl()[rds::kVmpcOff
]};
588 v
<< addq
{bc
, rarg(1), next
, v
.makeReg()};
590 auto const retAddr
= v
.makeReg();
591 v
<< loadstubret
{retAddr
};
593 auto const done
= v
.makeBlock();
594 auto const ctch
= vc
.makeBlock();
595 auto const should_continue
= v
.makeReg();
596 bool (*helper
)(PC
, int32_t, void*) = &doFCallArrayTC
;
599 CallSpec::direct(helper
),
600 v
.makeVcallArgs({{next
, rarg(2), retAddr
}}),
601 v
.makeTuple({should_continue
}),
607 emitStubCatch(vc
, us
, [] (Vout
& v
) { loadVMRegs(v
); });
611 // Load only rvmsp(); we need to wait to make sure we aren't skipping the
612 // callee before loading rvmfp().
613 v
<< load
{rvmtl()[rds::kVmspOff
], rvmsp()};
615 auto const sf
= v
.makeReg();
616 v
<< testb
{should_continue
, should_continue
, sf
};
618 unlikelyIfThen(v
, vc
, CC_Z
, sf
, [&] (Vout
& v
) {
619 // If false was returned, we should skip the callee. The interpreter
620 // will have popped the pre-live ActRec already, so we can just return to
621 // the caller after syncing the return regs.
625 v
<< load
{rvmtl()[rds::kVmfpOff
], rvmfp()};
627 // If true was returned, we're calling the callee, so undo the stublogue{}
628 // and convert to a phplogue{}.
629 v
<< stubtophp
{rvmfp()};
631 auto const callee
= v
.makeReg();
632 auto const body
= v
.makeReg();
634 v
<< load
{rvmfp()[AROFF(m_func
)], callee
};
635 emitLdLowPtr(v
, callee
[Func::funcBodyOff()], body
, sizeof(LowPtr
<uint8_t>));
637 // We jmp directly to the func body---this keeps the return stack buffer
638 // balanced between the call to this stub and the ret from the callee.
642 meta
.process(nullptr);
646 ///////////////////////////////////////////////////////////////////////////////
648 struct ResumeHelperEntryPoints
{
655 ResumeHelperEntryPoints
emitResumeHelpers(CodeBlock
& cb
, DataBlock
& data
) {
656 ResumeHelperEntryPoints rh
;
658 rh
.resumeHelperRet
= vwrap(cb
, data
, [] (Vout
& v
) {
659 v
<< phplogue
{rvmfp()};
661 rh
.resumeHelper
= vwrap(cb
, data
, [] (Vout
& v
) {
662 v
<< ldimmb
{0, rarg(0)};
665 rh
.handleResume
= vwrap(cb
, data
, [] (Vout
& v
) {
666 v
<< load
{rvmtl()[rds::kVmfpOff
], rvmfp()};
668 auto const handler
= reinterpret_cast<TCA
>(svcreq::handleResume
);
669 v
<< call
{handler
, arg_regs(2)};
672 rh
.reenterTC
= vwrap(cb
, data
, [] (Vout
& v
) {
673 // Save the return of handleResume(), then sync regs.
674 auto const target
= v
.makeReg();
675 v
<< copy
{rret(), target
};
678 loadReturnRegs(v
); // spurious load if we're not returning
686 TCA
emitResumeInterpHelpers(CodeBlock
& cb
, DataBlock
& data
, UniqueStubs
& us
,
687 ResumeHelperEntryPoints
& rh
) {
690 rh
= emitResumeHelpers(cb
, data
);
692 us
.resumeHelperRet
= rh
.resumeHelperRet
;
693 us
.resumeHelper
= rh
.resumeHelper
;
695 us
.interpHelper
= vwrap(cb
, data
, [] (Vout
& v
) {
696 v
<< store
{rarg(0), rvmtl()[rds::kVmpcOff
]};
698 us
.interpHelperSyncedPC
= vwrap(cb
, data
, [&] (Vout
& v
) {
700 v
<< ldimmb
{1, rarg(0)};
701 v
<< jmpi
{rh
.handleResume
, RegSet(rarg(0))};
704 us
.fcallAwaitSuspendHelper
= vwrap(cb
, data
, [&] (Vout
& v
) {
705 v
<< load
{rvmtl()[rds::kVmfpOff
], rvmfp()};
707 auto const handler
= reinterpret_cast<TCA
>(svcreq::handleFCallAwaitSuspend
);
708 v
<< call
{handler
, arg_regs(2)};
709 v
<< jmpi
{rh
.reenterTC
, RegSet()};
712 return us
.resumeHelperRet
;
715 TCA
emitInterpOneCFHelper(CodeBlock
& cb
, DataBlock
& data
, Op op
,
716 const ResumeHelperEntryPoints
& rh
) {
719 return vwrap(cb
, data
, [&] (Vout
& v
) {
720 v
<< copy2
{rvmfp(), rvmsp(), rarg(0), rarg(1)};
721 // rarg(2) is set at the stub callsite.
723 auto const handler
= reinterpret_cast<TCA
>(
724 interpOneEntryPoints
[static_cast<size_t>(op
)]
726 v
<< call
{handler
, arg_regs(3)};
728 auto const sf
= v
.makeReg();
729 auto const next
= v
.makeBlock();
731 v
<< testq
{rret(), rret(), sf
};
732 v
<< jcci
{CC_NZ
, sf
, next
, rh
.reenterTC
};
734 v
<< jmpi
{rh
.resumeHelper
};
738 void emitInterpOneCFHelpers(CodeBlock
& cb
, DataBlock
& data
, UniqueStubs
& us
,
739 const ResumeHelperEntryPoints
& rh
,
740 const CodeCache
& code
, Debug::DebugInfo
& dbg
) {
743 auto const emit
= [&] (Op op
, const char* name
) {
744 auto const stub
= emitInterpOneCFHelper(cb
, data
, op
, rh
);
745 us
.interpOneCFHelpers
[op
] = stub
;
746 us
.add(name
, stub
, code
, dbg
);
749 #define O(name, imm, in, out, flags) \
750 if (((flags) & CF) || ((flags) & TF)) { \
751 emit(Op::name, "interpOneCFHelper"#name); \
756 // Exit is a very special snowflake. Because it can appear in PHP
757 // expressions, the emitter pretends that it pushed a value on the eval stack
758 // (and iopExit actually does push Null right before throwing). Marking it
759 // as TF would mess up any bytecodes that want to consume its output value,
760 // so we can't do that. But we also don't want to extend regions past it, so
761 // the JIT treats it as terminal and uses InterpOneCF to execute it.
762 emit(Op::Exit
, "interpOneCFHelperExit");
765 ///////////////////////////////////////////////////////////////////////////////
767 TCA
emitDecRefGeneric(CodeBlock
& cb
, DataBlock
& data
) {
770 auto const start
= vwrap(cb
, data
, meta
, [] (Vout
& v
) {
774 auto const rdata
= rarg(0);
775 auto const rtype
= rarg(1);
777 auto const destroy
= [&] (Vout
& v
) {
778 // decRefGeneric is called via callfaststub, whose ABI claims that all
779 // registers are preserved. This is true in the fast path, but in the
780 // slow path we need to manually save caller-saved registers.
781 auto const callerSaved
= abi().gpUnreserved
- abi().calleeSaved
;
782 PhysRegSaver prs
{v
, callerSaved
};
784 // As a consequence of being called via callfaststub, we can't safely use
785 // any Vregs here except for status flags registers, at least not with
786 // the default vwrap() ABI. Just use the argument registers instead.
787 assertx(callerSaved
.contains(rdata
));
788 assertx(callerSaved
.contains(rtype
));
790 auto const dtor
= lookupDestructor(v
, rtype
);
791 v
<< callm
{dtor
, arg_regs(1)};
793 // The stub frame's saved RIP is at %rsp[8] before we saved the
794 // caller-saved registers.
795 v
<< syncpoint
{makeIndirectFixup(prs
.dwordsPushed())};
798 emitDecRefWork(v
, v
, rdata
, destroy
, false);
803 meta
.process(nullptr);
807 ///////////////////////////////////////////////////////////////////////////////
809 TCA
emitEnterTCExit(CodeBlock
& cb
, DataBlock
& data
, UniqueStubs
& /*us*/) {
810 return vwrap(cb
, data
, [&] (Vout
& v
) {
811 // Eagerly save VM regs.
814 // Realign the native stack.
818 v
<< lea
{rsp()[8], rsp()};
824 // Store the return value on the top of the eval stack. Whenever we get to
825 // enterTCExit, we're semantically executing some PHP construct that sends
826 // a return value out of a function (either a RetC, or a Yield, or an Await
827 // that's suspending, etc), and moreover, we must be executing the return
828 // that leaves this level of VM reentry (i.e. the only way we get here is
829 // by coming from the callToExit stub or by a phpret{} or leavetc{} that
830 // undoes the calltc{} or resumetc{} in enterTCHelper).
832 // Either way, we have a live PHP return value in the return registers,
833 // which we need to put on the top of the evaluation stack.
836 // Perform a native return.
838 // On PPC64, as there is no new frame created when entering the VM, the FP
839 // must not be saved.
840 v
<< stubret
{RegSet(), arch() != Arch::PPC64
};
844 TCA
emitEnterTCHelper(CodeBlock
& cb
, DataBlock
& data
, UniqueStubs
& us
) {
847 auto const sp
= rarg(0);
848 auto const fp
= rarg(1);
849 auto const start
= rarg(2);
850 auto const firstAR
= rarg(3);
852 auto const tl
= reg::r10
;
853 auto const calleeAR
= reg::r11
;
855 auto const tl
= rarg(4);
856 auto const calleeAR
= rarg(5);
859 return vwrap2(cb
, cb
, data
, [&] (Vout
& v
, Vout
& vc
) {
860 // Architecture-specific setup for entering the TC.
863 // Native func prologue.
864 v
<< stublogue
{arch() != Arch::PPC64
};
867 // Windows hates argument registers.
868 v
<< load
{rsp()[0x28], reg::r10
};
869 v
<< load
{rsp()[0x30], reg::r11
};
872 // Set up linkage with the top VM frame in this nesting.
873 v
<< store
{rsp(), firstAR
[AROFF(m_sfp
)]};
875 // Set up the VM registers.
876 v
<< copy
{fp
, rvmfp()};
877 v
<< copy
{sp
, rvmsp()};
878 v
<< copy
{tl
, rvmtl()};
880 // Unalign the native stack.
884 v
<< lea
{rsp()[-8], rsp()};
890 // Check if `calleeAR' was set.
891 auto const sf
= v
.makeReg();
892 v
<< testq
{calleeAR
, calleeAR
, sf
};
894 // We mark this block as unlikely in order to coax the emitter into
895 // ordering this block last. This is an important optimization for x64;
896 // without it, both the jcc for the branch and the jmp for the resumetc{}
897 // will end up in the same 16-byte extent of code, which messes up the
899 unlikelyIfThen(v
, vc
, CC_Z
, sf
, [&] (Vout
& v
) {
900 // No callee means we're resuming in the middle of a TC function.
901 v
<< resumetc
{start
, us
.enterTCExit
, vm_regs_with_sp()};
904 // We have a callee; set rvmfp() and call it.
905 v
<< copy
{calleeAR
, rvmfp()};
906 v
<< calltc
{start
, rvmfp(), us
.enterTCExit
, vm_regs_with_sp()};
910 TCA
emitHandleSRHelper(CodeBlock
& cb
, DataBlock
& data
) {
913 return vwrap(cb
, data
, [] (Vout
& v
) {
916 // Pack the service request args into a svcreq::ReqInfo on the stack.
917 assertx(!(svcreq::kMaxArgs
& 1));
918 for (auto i
= svcreq::kMaxArgs
; i
>= 2; i
-= 2) {
919 v
<< pushp
{r_svcreq_arg(i
- 1), r_svcreq_arg(i
- 2)};
921 v
<< pushp
{r_svcreq_stub(), r_svcreq_req()};
923 // Call mcg->handleServiceRequest(rsp()).
924 auto const sp
= v
.makeReg();
925 v
<< copy
{rsp(), sp
};
927 auto const ret
= v
.makeReg();
930 CallSpec::direct(svcreq::handleServiceRequest
),
931 v
.makeVcallArgs({{sp
}}),
937 // Pop the ReqInfo off the stack.
938 auto const reqinfo_sz
= static_cast<int>(sizeof(svcreq::ReqInfo
));
939 v
<< lea
{rsp()[reqinfo_sz
], rsp()};
941 // rvmtl() was preserved by the callee, but rvmsp() and rvmfp() might've
942 // changed if we interpreted anything. Reload them. Also load the return
943 // regs; if we're not returning, it's a spurious load.
951 ///////////////////////////////////////////////////////////////////////////////
953 TCA
emitEndCatchHelper(CodeBlock
& cb
, DataBlock
& data
, UniqueStubs
& us
) {
954 auto const udrspo
= rvmtl()[unwinderDebuggerReturnSPOff()];
956 auto const debuggerReturn
= vwrap(cb
, data
, [&] (Vout
& v
) {
957 v
<< load
{udrspo
, rvmsp()};
958 v
<< storeqi
{0, udrspo
};
960 svcreq::emit_persistent(cb
, data
, folly::none
, REQ_POST_DEBUGGER_RET
);
964 auto const resumeCPPUnwind
= vwrap(cb
, data
, meta
, [&] (Vout
& v
) {
965 static_assert(sizeof(tl_regState
) == 8,
966 "The following store must match the size of tl_regState.");
967 auto const regstate
= emitTLSAddr(v
, tls_datum(tl_regState
));
968 v
<< storeqi
{static_cast<int32_t>(VMRegState::CLEAN
), regstate
};
970 v
<< load
{rvmtl()[unwinderExnOff()], rarg(0)};
971 v
<< call
{TCA(_Unwind_Resume
), arg_regs(1), &us
.endCatchHelperPast
};
974 meta
.process(nullptr);
978 return vwrap(cb
, data
, [&] (Vout
& v
) {
979 auto const done1
= v
.makeBlock();
980 auto const sf1
= v
.makeReg();
982 v
<< cmpqim
{0, udrspo
, sf1
};
983 v
<< jcci
{CC_NE
, sf1
, done1
, debuggerReturn
};
986 // Normal end catch situation: call back to tc_unwind_resume, which returns
987 // the catch trace (or null) in the first return register, and the new vmfp
989 v
<< copy
{rvmfp(), rarg(0)};
990 v
<< call
{TCA(tc_unwind_resume
), arg_regs(1)};
991 v
<< copy
{rret(1), rvmfp()};
993 auto const done2
= v
.makeBlock();
994 auto const sf2
= v
.makeReg();
996 v
<< testq
{rret(0), rret(0), sf2
};
997 v
<< jcci
{CC_Z
, sf2
, done2
, resumeCPPUnwind
};
1004 TCA
emitUnknownExceptionHandler(CodeBlock
& cb
,
1010 auto const ret
= vwrap(cb
, data
, meta
, [&] (Vout
& v
) {
1012 TCA(unknownExceptionHandler
), {}, &us
.unknownExceptionHandlerPast
1015 meta
.process(nullptr);
1020 TCA
emitThrowSwitchMode(CodeBlock
& cb
, DataBlock
& data
) {
1023 return vwrap(cb
, data
, [] (Vout
& v
) {
1024 v
<< call
{TCA(throwSwitchMode
)};
1030 TCA
emitHelperThunk(CodeCache
& code
, CodeBlock
& cb
, DataBlock
& data
, F
* func
) {
1031 // we only emit these calls into hot, main and cold.
1032 if (deltaFits(code
.base() - (TCA
)func
, sz::dword
) &&
1033 deltaFits(code
.frozen().base() - (TCA
)func
, sz::dword
)) {
1037 return vwrap(cb
, data
, [&] (Vout
& v
) {
1038 v
<< jmpi
{(TCA
)func
};
1042 ///////////////////////////////////////////////////////////////////////////////
1046 void UniqueStubs::emitAll(CodeCache
& code
, Debug::DebugInfo
& dbg
) {
1047 auto view
= code
.view();
1048 auto& main
= view
.main();
1049 auto& cold
= view
.cold();
1050 auto& frozen
= view
.frozen();
1051 auto& hotBlock
= code
.view(TransKind::Optimize
).main();
1052 auto& data
= view
.data();
1054 auto const hot
= [&]() -> CodeBlock
& {
1055 return hotBlock
.available() > 512 ? hotBlock
: main
;
1058 #define ADD(name, stub) name = add(#name, (stub), code, dbg)
1059 ADD(enterTCExit
, emitEnterTCExit(main
, data
, *this));
1061 decltype(enterTCHelper
)(add("enterTCHelper",
1062 emitEnterTCHelper(main
, data
, *this),
1066 // These guys are required by a number of other stubs.
1067 ADD(handleSRHelper
, emitHandleSRHelper(hot(), data
));
1068 ADD(endCatchHelper
, emitEndCatchHelper(frozen
, data
, *this));
1069 ADD(unknownExceptionHandler
, emitUnknownExceptionHandler(cold
, data
, *this));
1071 ADD(funcPrologueRedispatch
, emitFuncPrologueRedispatch(hot(), data
));
1072 ADD(fcallHelperThunk
, emitFCallHelperThunk(cold
, frozen
, data
));
1073 ADD(funcBodyHelperThunk
, emitFuncBodyHelperThunk(cold
, data
));
1074 ADD(functionEnterHelper
, emitFunctionEnterHelper(cold
, frozen
, data
, *this));
1075 ADD(functionSurprisedOrStackOverflow
,
1076 emitFunctionSurprisedOrStackOverflow(cold
, frozen
, data
, *this));
1078 ADD(retHelper
, emitInterpRet(hot(), data
));
1079 ADD(genRetHelper
, emitInterpGenRet
<false>(cold
, data
));
1080 ADD(asyncGenRetHelper
, emitInterpGenRet
<true>(hot(), data
));
1081 ADD(retInlHelper
, emitInterpRet(hot(), data
));
1082 ADD(debuggerRetHelper
, emitDebuggerInterpRet(cold
, data
));
1083 ADD(debuggerGenRetHelper
, emitDebuggerInterpGenRet
<false>(cold
, data
));
1084 ADD(debuggerAsyncGenRetHelper
, emitDebuggerInterpGenRet
<true>(cold
, data
));
1086 ADD(bindCallStub
, emitBindCallStub
<false>(cold
, data
));
1087 ADD(immutableBindCallStub
, emitBindCallStub
<true>(cold
, data
));
1088 ADD(fcallArrayHelper
, emitFCallArrayHelper(hot(), frozen
, data
, *this));
1090 ADD(decRefGeneric
, emitDecRefGeneric(cold
, data
));
1092 ADD(callToExit
, emitCallToExit(main
, data
, *this));
1093 ADD(throwSwitchMode
, emitThrowSwitchMode(frozen
, data
));
1095 ADD(handlePrimeCacheInit
,
1096 emitHelperThunk(code
, cold
, data
,
1097 MethodCache::handlePrimeCacheInit
<false>));
1098 ADD(handlePrimeCacheInitFatal
,
1099 emitHelperThunk(code
, cold
, data
,
1100 MethodCache::handlePrimeCacheInit
<true>));
1102 emitHelperThunk(code
, main
, data
,
1103 MethodCache::handleSlowPath
<false>));
1104 ADD(handleSlowPathFatal
,
1105 emitHelperThunk(code
, main
, data
,
1106 MethodCache::handleSlowPath
<true>));
1110 add("freeLocalsHelpers",
1111 emitFreeLocalsHelpers(hot(), data
, *this), code
, dbg
);
1113 ResumeHelperEntryPoints rh
;
1114 add("resumeInterpHelpers",
1115 emitResumeInterpHelpers(hot(), data
, *this, rh
),
1117 emitInterpOneCFHelpers(cold
, data
, *this, rh
, code
, dbg
);
1119 emitAllResumable(code
, dbg
);
1122 ///////////////////////////////////////////////////////////////////////////////
1124 TCA
UniqueStubs::add(const char* name
, TCA start
,
1125 const CodeCache
& code
, Debug::DebugInfo
& dbg
) {
1126 if (!code
.isValidCodeAddress(start
)) return start
;
1128 auto& cb
= code
.blockFor(start
);
1129 auto const end
= cb
.frontier();
1131 FTRACE(1, "unique stub: {} @ {} -- {:4} bytes: {}\n",
1133 static_cast<void*>(start
),
1134 static_cast<size_t>(end
- start
),
1139 std::ostringstream os
;
1140 disasmRange(os
, start
, end
);
1141 FTRACE(2, "{}\n", os
.str());
1145 if (!RuntimeOption::EvalJitNoGdb
) {
1146 dbg
.recordStub(Debug::TCRange(start
, end
, &cb
== &code
.cold()),
1147 folly::sformat("HHVM::{}", name
));
1149 if (RuntimeOption::EvalJitUseVtuneAPI
) {
1150 reportHelperToVtune(folly::sformat("HHVM::{}", name
).c_str(),
1154 if (RuntimeOption::EvalPerfPidMap
) {
1155 dbg
.recordPerfMap(Debug::TCRange(start
, end
, &cb
== &code
.cold()),
1160 folly::sformat("HHVM::{}", name
));
1163 auto const newStub
= StubRange
{name
, start
, end
};
1164 auto lower
= std::lower_bound(m_ranges
.begin(), m_ranges
.end(), newStub
);
1166 // We assume ranges are non-overlapping.
1167 assertx(lower
== m_ranges
.end() || newStub
.end
<= lower
->start
);
1168 assertx(lower
== m_ranges
.begin() || (lower
- 1)->end
<= newStub
.start
);
1169 m_ranges
.insert(lower
, newStub
);
1173 std::string
UniqueStubs::describe(TCA address
) const {
1174 auto raw
= [address
] { return folly::sformat("{}", address
); };
1175 if (m_ranges
.empty()) return raw();
1177 auto const dummy
= StubRange
{"", address
, nullptr};
1178 auto lower
= std::upper_bound(m_ranges
.begin(), m_ranges
.end(), dummy
);
1179 if (lower
== m_ranges
.begin()) return raw();
1182 if (lower
->contains(address
)) {
1183 return folly::sformat("{}+{:#x}", lower
->name
, address
- lower
->start
);
1188 ///////////////////////////////////////////////////////////////////////////////
1190 RegSet
interp_one_cf_regs() {
1191 return vm_regs_with_sp() | rarg(2);
1194 void emitInterpReq(Vout
& v
, SrcKey sk
, FPInvOffset spOff
) {
1195 if (sk
.resumeMode() == ResumeMode::None
) {
1196 v
<< lea
{rvmfp()[-cellsToBytes(spOff
.offset
)], rvmsp()};
1198 v
<< copy
{v
.cns(sk
.pc()), rarg(0)};
1199 v
<< jmpi
{tc::ustubs().interpHelper
, arg_regs(1)};
1202 ///////////////////////////////////////////////////////////////////////////////
1204 void enterTCImpl(TCA start
, ActRec
* stashedAR
) {
1205 // We have to force C++ to spill anything that might be in a callee-saved
1206 // register (aside from rvmfp()), since enterTCHelper does not save them.
1207 CALLEE_SAVED_BARRIER();
1208 auto& regs
= vmRegsUnsafe();
1209 tc::ustubs().enterTCHelper(regs
.stack
.top(), regs
.fp
, start
,
1210 vmFirstAR(), rds::tl_base
, stashedAR
);
1211 CALLEE_SAVED_BARRIER();
1214 ///////////////////////////////////////////////////////////////////////////////