Add new shim types for floating point version of `folly::toAppend`
[hiphop-php.git] / hphp / runtime / vm / jit / unique-stubs-x64.cpp
blob2973d833f421c785da7b49c2812cc6fa67774dd1
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
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"
43 namespace HPHP::jit {
45 TRACE_SET_MOD(ustubs);
47 ///////////////////////////////////////////////////////////////////////////////
49 namespace x64 {
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) {
67 CGMeta meta;
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{}};
96 v = chkref;
97 v << jcc{CC_NE, sf, {destroy, decref}, StringTag{}};
98 v = decref;
99 emitDecRefCount(v, dataVal);
100 v << jmp{skipref};
101 v = skipref;
102 v << ret{live};
103 v = destroy;
104 emit_destroy(v);
106 v << ret{live};
107 }, nullptr, CodeKind::CrossTrace, true);
109 meta.process(nullptr);
110 return addr;
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};
158 return sf;
160 }, nullptr, true);
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));
165 if (i == 0) {
166 // The helpers all fall through to each other. Only the last
167 // one needs a ret.
168 v << ret{};
169 } else {
170 v << fallthru{};
172 }, nullptr, true);
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);
187 addr += di.size();
190 return start;
193 ///////////////////////////////////////////////////////////////////////////////
195 EXTERNALLY_VISIBLE
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) {
208 X64Assembler a(cb);
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.
213 a.emitNop(1);
215 auto const start = a.frontier();
216 if (Cfg::HHIR::GenerateAsserts) {
217 always_assert(rarg(0) != rret(0) &&
218 rarg(0) != rret(1));
219 a.movq(rsp(), rarg(0));
221 // We need to spill the return registers around the assert call.
222 a.push(rret(0));
223 a.push(rret(1));
224 auto target = TCA(assert_tc_saved_rip);
225 if (a.jmpDeltaFits(target)) {
226 a.call(target);
227 } else {
228 a.emitImmReg(target, reg::rax);
229 a.call(reg::rax);
231 a.pop(rret(1));
232 a.pop(rret(0));
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
238 // stub.
239 a.addq(8, rsp());
240 if (a.jmpDeltaFits(us.enterTCExit)) {
241 a.jmp(us.enterTCExit);
242 } else {
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
245 // a register
246 a.emitImmReg(us.enterTCExit, reg::rax);
247 a.jmp(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.
255 return start;
258 ///////////////////////////////////////////////////////////////////////////////