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-x64.h"
19 #include "hphp/runtime/base/header-kind.h"
20 #include "hphp/runtime/base/stats.h"
21 #include "hphp/runtime/vm/bytecode.h"
23 #include "hphp/runtime/vm/jit/types.h"
24 #include "hphp/runtime/vm/jit/abi-x64.h"
25 #include "hphp/runtime/vm/jit/align-x64.h"
26 #include "hphp/runtime/vm/jit/cg-meta.h"
27 #include "hphp/runtime/vm/jit/code-gen-cf.h"
28 #include "hphp/runtime/vm/jit/code-gen-helpers.h"
29 #include "hphp/runtime/vm/jit/fixup.h"
30 #include "hphp/runtime/vm/jit/phys-reg.h"
31 #include "hphp/runtime/vm/jit/phys-reg-saver.h"
32 #include "hphp/runtime/vm/jit/tc.h"
33 #include "hphp/runtime/vm/jit/translator-inline.h"
34 #include "hphp/runtime/vm/jit/unique-stubs.h"
35 #include "hphp/runtime/vm/jit/vasm-gen.h"
36 #include "hphp/runtime/vm/jit/vasm-instr.h"
38 #include "hphp/util/asm-x64.h"
39 #include "hphp/util/configs/hhir.h"
40 #include "hphp/util/data-block.h"
41 #include "hphp/util/trace.h"
45 TRACE_SET_MOD(ustubs
);
47 ///////////////////////////////////////////////////////////////////////////////
51 ///////////////////////////////////////////////////////////////////////////////
54 * Helper for the freeLocalsHelpers which does the actual work of decrementing
55 * a value's refcount or releasing it.
57 * This helper is reached via call from the various freeLocalHelpers.
58 * It expects `dataVal' to be a Value, and `typeVal' to be the the
59 * associated DataType (with refcounted type) (though it may be
60 * static, and we will do nothing in that case).
62 * The `live' registers must be preserved across any native calls (and
63 * generally left untouched).
65 static TCA
emitDecRefHelper(CodeBlock
& cb
, DataBlock
& data
,
66 PhysReg dataVal
, PhysReg typeVal
, RegSet live
) {
68 auto addr
= vwrap(cb
, data
, meta
, [&] (Vout
& v
) {
69 auto emit_destroy
= [&](Vout
& v
) {
70 // Note that the stack is aligned since we called to this helper from an
71 // stack-unaligned stub.
72 PhysRegSaver prs
{v
, live
};
74 // The refcount is exactly 1; release the value.
75 // Avoid 'this' pointer overwriting by reserving it as an argument.
76 assertx(dataVal
== rarg(0));
77 v
<< callm
{lookupDestructor(v
, typeVal
, true), arg_regs(1)};
79 // Between where %rsp is now and the saved RIP of the call into the
80 // freeLocalsHelpers stub, we have all the live regs we pushed, plus the
81 // saved RIP of the call from the stub to this helper.
82 v
<< syncpoint
{Fixup::indirect(prs
.qwordsPushed(), SBInvOffset
{0})};
85 auto const sf
= emitCmpRefCount(v
, OneReference
, dataVal
);
87 auto skipref
= v
.makeBlock();
88 auto destroy
= v
.makeBlock();
89 auto chkref
= v
.makeBlock();
90 auto decref
= v
.makeBlock();
92 // We can't quite get the layout we want from two nested ifThens, because
93 // we want the else case from the first to jmp to the middle of the then
94 // case of the second (we want to share the ret).
95 v
<< jcc
{CC_L
, sf
, {chkref
, skipref
}, StringTag
{}};
97 v
<< jcc
{CC_NE
, sf
, {destroy
, decref
}, StringTag
{}};
99 emitDecRefCount(v
, dataVal
);
107 }, nullptr, CodeKind::CrossTrace
, true);
109 meta
.process(nullptr);
113 TCA
emitFreeLocalsHelpers(CodeBlock
& cb
, DataBlock
& data
, UniqueStubs
& us
) {
114 // The address of the first local's type is passed in the second
115 // argument register. The address of the first local's value is
116 // passed in the third argument register. We use the first to store
117 // the loaded value (so its already in the right place when calling
118 // the release function). We use the fourth as a scratch register
119 // for loading the type and the fifth as a scratch register for
120 // storing the end pointer.
121 auto const dataVal
= rarg(0);
122 auto const typePtr
= rarg(1);
123 auto const dataPtr
= rarg(2);
124 auto const typeVal
= rarg(3);
125 auto const end
= rarg(4);
126 auto const start
= cb
.frontier();
127 // We want the release function to come last; we enter the slide at
128 // several different points, but always execute through to the end
129 // of the slide - so its better to put the end of the slide close to
130 // the release helper. Since we don't know exactly where the release
131 // helper will be, use a fake address that we can recognize and
132 // fixup after the fact.
133 auto const releaseFake
= start
- 1;
135 auto const decref_local
= [&] (Vout
& v
, Vptr d
, Vptr t
) {
136 auto const sf
= v
.makeReg();
138 // We can't do a byte load here---we have to sign-extend since we use
139 // `type' as a 64-bit array index to the destructor table.
140 v
<< loadsbq
{t
, typeVal
};
141 auto const cc
= emitIsTVTypeRefCounted(v
, sf
, typeVal
);
143 ifThen(v
, cc
, sf
, [&] (Vout
& v
) {
144 v
<< load
{d
, dataVal
};
145 v
<< call
{releaseFake
, dataVal
| typeVal
};
149 us
.freeManyLocalsHelper
= vwrap(cb
, data
, [&] (Vout
& v
) {
150 // We always unroll the final `kNumFreeLocalsHelpers' decrefs, so only loop
151 // until we hit that point.
152 v
<< lea
{ptrToLocalType(rvmfp(), kNumFreeLocalsHelpers
- 1), end
};
153 doWhile(v
, CC_NZ
, {}, [&](const VregList
&, const VregList
&) {
154 decref_local(v
, *dataPtr
, *typePtr
);
155 auto const sf
= v
.makeReg();
156 prevLocal(v
, typePtr
, dataPtr
, typePtr
, dataPtr
);
157 v
<< cmpq
{typePtr
, end
, sf
};
162 for (auto i
= kNumFreeLocalsHelpers
- 1; i
>= 0; --i
) {
163 us
.freeLocalsHelpers
[i
] = vwrap(cb
, data
, [&] (Vout
& v
) {
164 decref_local(v
, ptrToLocalData(rvmfp(), i
), ptrToLocalType(rvmfp(), i
));
166 // The helpers all fall through to each other. Only the last
175 auto const release
= emitDecRefHelper(
176 cb
, data
, dataVal
, typeVal
,
177 dataPtr
| typePtr
| end
179 // Now we know where release is, we can patch the calls to
180 // releaseFake and point them to the correct address.
181 for (auto addr
= start
; addr
< release
; ) {
182 x64::DecodedInstruction
di(addr
, addr
);
183 if (di
.hasPicOffset() && di
.picAddress() == releaseFake
) {
184 always_assert(di
.isCall());
185 di
.setPicAddress(release
);
193 ///////////////////////////////////////////////////////////////////////////////
196 void assert_tc_saved_rip(void* sp
) {
197 auto const saved_rip
= *reinterpret_cast<uint8_t**>(sp
);
198 auto const exittc
= tc::ustubs().enterTCExit
;
200 DecodedInstruction
di(saved_rip
);
201 auto const jmp_target
= [&] { return saved_rip
+ di
.size() + di
.offset(); };
203 // We should either be returning to enterTCExit, or to a jmp to enterTCExit.
204 always_assert(saved_rip
== exittc
|| (di
.isJmp() && jmp_target() == exittc
));
207 TCA
emitCallToExit(CodeBlock
& cb
, DataBlock
& /*data*/, const UniqueStubs
& us
) {
210 // Emit a byte of padding. This is a kind of hacky way to avoid
211 // hitting an assert in recordGdbStub when we call it with stub - 1
212 // as the start address.
215 auto const start
= a
.frontier();
216 if (Cfg::HHIR::GenerateAsserts
) {
217 always_assert(rarg(0) != rret(0) &&
219 a
.movq(rsp(), rarg(0));
221 // We need to spill the return registers around the assert call.
224 auto target
= TCA(assert_tc_saved_rip
);
225 if (a
.jmpDeltaFits(target
)) {
228 a
.emitImmReg(target
, reg::rax
);
235 // Emulate a ret to enterTCExit without actually doing one to avoid
236 // unbalancing the return stack buffer. The call from enterTCHelper() that
237 // got us into the TC was popped off the RSB by the ret that got us to this
240 if (a
.jmpDeltaFits(us
.enterTCExit
)) {
241 a
.jmp(us
.enterTCExit
);
243 // can't do a near jmp and a rip-relative load/jmp would require threading
244 // through extra state to allocate a literal. use an indirect jump through
246 a
.emitImmReg(us
.enterTCExit
, reg::rax
);
250 // On a backtrace, gdb tries to locate the calling frame at address
251 // returnRIP-1. However, for the first VM frame, there is no code at
252 // returnRIP-1, since the AR was set up manually. For this frame,
253 // record the tracelet address as starting from this callToExit-1,
254 // so gdb does not barf.
258 ///////////////////////////////////////////////////////////////////////////////