2 +----------------------------------------------------------------------+
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 +----------------------------------------------------------------------+
17 #include "hphp/runtime/vm/jit/code-gen-helpers-x64.h"
19 #include "hphp/util/asm-x64.h"
20 #include "hphp/util/trace.h"
22 #include "hphp/runtime/base/runtime-option.h"
23 #include "hphp/runtime/base/stats.h"
24 #include "hphp/runtime/base/types.h"
25 #include "hphp/runtime/vm/jit/translator-inline.h"
26 #include "hphp/runtime/vm/jit/translator-x64.h"
27 #include "hphp/runtime/vm/jit/translator-x64-internal.h"
28 #include "hphp/runtime/vm/jit/translator.h"
29 #include "hphp/runtime/vm/jit/x64-util.h"
30 #include "hphp/runtime/vm/jit/ir.h"
37 //////////////////////////////////////////////////////////////////////
40 using namespace Transl
;
41 using namespace Transl::reg
;
45 } // unnamed namespace
47 namespace CodeGenHelpersX64
{
50 * It's not normally ok to directly use tracelet abi registers in
51 * codegen, unless you're directly dealing with an instruction that
52 * does near-end-of-tracelet glue. (Or also we sometimes use them
53 * just for some static_assertions relating to calls to helpers from
54 * tx64 that hardcode these registers.)
60 * Satisfy an alignment constraint. If we're in a reachable section
61 * of code, bridge the gap with nops. Otherwise, int3's.
63 void moveToAlign(Asm
& aa
,
64 const size_t align
/* =kJmpTargetAlign */,
65 const bool unreachable
/* =true */) {
66 using namespace HPHP::Util
;
67 assert(isPowerOfTwo(align
));
68 size_t leftInBlock
= align
- ((align
- 1) & uintptr_t(aa
.frontier()));
69 if (leftInBlock
== align
) return;
71 if (leftInBlock
> 2) {
75 if (leftInBlock
> 0) {
76 aa
.emitInt3s(leftInBlock
);
80 aa
.emitNop(leftInBlock
);
83 void emitEagerSyncPoint(Asm
& as
, const HPHP::Opcode
* pc
, const Offset spDiff
) {
84 static COff spOff
= offsetof(VMExecutionContext
, m_stack
) +
85 Stack::topOfStackOffset();
86 static COff fpOff
= offsetof(VMExecutionContext
, m_fp
);
87 static COff pcOff
= offsetof(VMExecutionContext
, m_pc
);
89 /* we can't use rAsm because the pc store uses it as a
94 emitGetGContext(as
, rEC
);
95 as
. storeq(rVmFp
, rEC
[fpOff
]);
97 as
. lea(rVmSp
[spDiff
], rAsm
);
98 as
. storeq(rAsm
, rEC
[spOff
]);
100 as
. storeq(rVmSp
, rEC
[spOff
]);
102 as
. storeq(pc
, rEC
[pcOff
]);
106 // emitEagerVMRegSave --
107 // Inline. Saves regs in-place in the TC. This is an unusual need;
108 // you probably want to lazily save these regs via recordCall and
110 void emitEagerVMRegSave(Asm
& as
, RegSaveFlags flags
) {
111 bool saveFP
= bool(flags
& RegSaveFlags::SaveFP
);
112 bool savePC
= bool(flags
& RegSaveFlags::SavePC
);
113 assert((flags
& ~(RegSaveFlags::SavePC
| RegSaveFlags::SaveFP
)) ==
118 assert(!kSpecialCrossTraceRegs
.contains(rdi
));
120 emitGetGContext(as
, rEC
);
122 static COff spOff
= offsetof(VMExecutionContext
, m_stack
) +
123 Stack::topOfStackOffset();
124 static COff fpOff
= offsetof(VMExecutionContext
, m_fp
) - spOff
;
125 static COff pcOff
= offsetof(VMExecutionContext
, m_pc
) - spOff
;
128 as
. addq (spOff
, r64(rEC
));
129 as
. storeq (rVmSp
, *rEC
);
131 // We're going to temporarily abuse rVmSp to hold the current unit.
134 // m_fp -> m_func -> m_unit -> m_bc + pcReg
135 as
. loadq (rVmFp
[AROFF(m_func
)], rBC
);
136 as
. loadq (rBC
[Func::unitOff()], rBC
);
137 as
. loadq (rBC
[Unit::bcOff()], rBC
);
138 as
. addq (rBC
, pcReg
);
139 as
. storeq (pcReg
, rEC
[pcOff
]);
143 as
. storeq (rVmFp
, rEC
[fpOff
]);
147 void emitGetGContext(Asm
& as
, PhysReg dest
) {
148 emitTLSLoad
<ExecutionContext
>(as
, g_context
, dest
);
151 // IfCountNotStatic --
152 // Emits if (%reg->_count != RefCountStaticValue) { ... }.
153 // May short-circuit this check if the type is known to be
155 struct IfCountNotStatic
{
156 typedef CondBlock
<FAST_REFCOUNT_OFFSET
,
159 field_type(RefData
, m_count
)> NonStaticCondBlock
;
160 NonStaticCondBlock
*m_cb
; // might be null
161 IfCountNotStatic(Asm
& as
,
163 DataType t
= KindOfInvalid
) {
165 // Objects and variants cannot be static
166 if (t
!= KindOfObject
&& t
!= KindOfResource
&& t
!= KindOfRef
) {
167 m_cb
= new NonStaticCondBlock(as
, reg
);
173 ~IfCountNotStatic() {
178 void emitIncRef(Asm
& as
, PhysReg base
) {
179 if (RuntimeOption::EvalHHIRGenerateAsserts
) {
180 emitAssertRefCount(as
, base
);
183 as
.incl(base
[FAST_REFCOUNT_OFFSET
]);
184 if (RuntimeOption::EvalHHIRGenerateAsserts
) {
185 // Assert that the ref count is greater than zero
186 emitAssertFlagsNonNegative(as
);
190 void emitIncRefCheckNonStatic(Asm
& as
, PhysReg base
, DataType dtype
) {
192 IfCountNotStatic
ins(as
, base
, dtype
);
193 emitIncRef(as
, base
);
197 void emitIncRefGenericRegSafe(Asm
& as
, PhysReg base
, int disp
, PhysReg tmpReg
) {
199 IfRefCounted
irc(as
, base
, disp
);
200 as
. load_reg64_disp_reg64(base
, disp
+ TVOFF(m_data
),
203 IfCountNotStatic
ins(as
, tmpReg
);
204 as
. incl(tmpReg
[FAST_REFCOUNT_OFFSET
]);
209 void emitAssertFlagsNonNegative(Asm
& as
) {
210 ifThen(as
, CC_NGE
, [&] { as
.ud2(); });
213 void emitAssertRefCount(Asm
& as
, PhysReg base
) {
214 as
.cmpl(HPHP::RefCountStaticValue
, base
[FAST_REFCOUNT_OFFSET
]);
215 ifThen(as
, CC_NBE
, [&] { as
.ud2(); });
218 // Logical register move: ensures the value in src will be in dest
219 // after execution, but might do so in strange ways. Do not count on
220 // being able to smash dest to a different register in the future, e.g.
221 void emitMovRegReg(Asm
& as
, PhysReg srcReg
, PhysReg dstReg
) {
222 assert(srcReg
!= InvalidReg
);
223 assert(dstReg
!= InvalidReg
);
225 if (srcReg
== dstReg
) return;
228 if (dstReg
.isGP()) { // GP => GP
229 as
. movq(srcReg
, dstReg
);
230 } else { // GP => XMM
231 // This generates a movq x86 instruction, which zero extends
232 // the 64-bit value in srcReg into a 128-bit XMM register
233 as
. mov_reg64_xmm(srcReg
, dstReg
);
236 if (dstReg
.isGP()) { // XMM => GP
237 as
. mov_xmm_reg64(srcReg
, dstReg
);
238 } else { // XMM => XMM
239 // This copies all 128 bits in XMM,
240 // thus avoiding partial register stalls
241 as
. movdqa(srcReg
, dstReg
);
246 void emitLea(Asm
& as
, PhysReg base
, int disp
, PhysReg dest
) {
248 emitMovRegReg(as
, base
, dest
);
250 as
. lea(base
[disp
], dest
);
254 void emitLea(Asm
& as
, MemoryRef mr
, PhysReg dst
) {
255 if (dst
== InvalidReg
) return;
256 if (mr
.r
.disp
== 0) {
257 emitMovRegReg(as
, mr
.r
.base
, dst
);
263 void emitLdObjClass(Asm
& as
, PhysReg objReg
, PhysReg dstReg
) {
264 as
. loadq (objReg
[ObjectData::getVMClassOffset()], dstReg
);
267 void emitLdClsCctx(Asm
& as
, PhysReg srcReg
, PhysReg dstReg
) {
268 emitMovRegReg(as
, srcReg
, dstReg
);
272 void emitExitSlowStats(Asm
& as
, const Func
* func
, SrcKey dest
) {
273 if (RuntimeOption::EnableInstructionCounts
||
274 HPHP::Trace::moduleEnabled(HPHP::Trace::stats
, 3)) {
276 Stats::opcodeToIRPreStatCounter(
277 Op(*func
->unit()->at(dest
.offset()))),
284 void shuffle2(Asm
& as
, PhysReg s0
, PhysReg s1
, PhysReg d0
, PhysReg d1
) {
286 if (d0
== s1
&& d1
!= InvalidReg
) {
291 as
. movq (s1
, d1
); // save s1 first; d1 != s0
295 if (d0
!= InvalidReg
) emitMovRegReg(as
, s0
, d0
); // d0 != s1
296 if (d1
!= InvalidReg
) emitMovRegReg(as
, s1
, d1
);
300 void zeroExtendIfBool(CodeGenerator::Asm
& as
, const SSATmp
* src
, PhysReg reg
) {
301 if (src
->isA(Type::Bool
) && reg
!= InvalidReg
) {
302 // zero-extend the bool from a byte to a quad
303 // note: movzbl actually extends the value to 64 bits.
304 as
.movzbl(rbyte(reg
), r32(reg
));
308 ConditionCode
opToConditionCode(Opcode opc
) {
309 using namespace HPHP::Transl
;
312 case JmpGt
: return CC_G
;
313 case JmpGte
: return CC_GE
;
314 case JmpLt
: return CC_L
;
315 case JmpLte
: return CC_LE
;
316 case JmpEq
: return CC_E
;
317 case JmpNeq
: return CC_NE
;
318 case JmpSame
: return CC_E
;
319 case JmpNSame
: return CC_NE
;
320 case JmpInstanceOfBitmask
: return CC_NZ
;
321 case JmpNInstanceOfBitmask
: return CC_Z
;
322 case JmpIsType
: return CC_NZ
;
323 case JmpIsNType
: return CC_Z
;
324 case JmpZero
: return CC_Z
;
325 case JmpNZero
: return CC_NZ
;
326 case ReqBindJmpGt
: return CC_G
;
327 case ReqBindJmpGte
: return CC_GE
;
328 case ReqBindJmpLt
: return CC_L
;
329 case ReqBindJmpLte
: return CC_LE
;
330 case ReqBindJmpEq
: return CC_E
;
331 case ReqBindJmpNeq
: return CC_NE
;
332 case ReqBindJmpSame
: return CC_E
;
333 case ReqBindJmpNSame
: return CC_NE
;
334 case ReqBindJmpInstanceOfBitmask
: return CC_NZ
;
335 case ReqBindJmpNInstanceOfBitmask
: return CC_Z
;
336 case ReqBindJmpZero
: return CC_Z
;
337 case ReqBindJmpNZero
: return CC_NZ
;
344 * emitServiceReqWork --
346 * Call a translator service co-routine. The code emitted here
347 * reenters the enterTC loop, invoking the requested service. Control
348 * will be returned non-locally to the next logical instruction in
351 * Return value is a destination; we emit the bulky service
352 * request code into astubs.
354 * Returns a continuation that will run after the arguments have been
355 * emitted. This is gross, but is a partial workaround for the inability
356 * to capture argument packs in the version of gcc we're using.
359 emitServiceReqWork(Asm
& as
, TCA start
, bool persist
, SRFlags flags
,
360 ServiceRequest req
, const ServiceReqArgVec
& argv
) {
362 const bool align
= flags
& SRFlags::Align
;
365 * Remember previous state of the code cache.
367 boost::optional
<CodeCursor
> maybeCc
= boost::none
;
368 if (start
!= as
.frontier()) {
369 maybeCc
= boost::in_place
<CodeCursor
>(boost::ref(as
), start
);
372 /* max space for moving to align, saving VM regs plus emitting args */
376 kNumServiceRegs
= sizeof(serviceReqArgRegs
) / sizeof(PhysReg
),
377 kMaxStubSpace
= kJmpTargetAlign
- 1 + kVMRegSpace
+
378 kNumServiceRegs
* kMovSize
;
382 TCA retval
= as
.frontier();
383 TRACE(3, "Emit Service Req @%p %s(", start
, serviceReqName(req
));
385 * Move args into appropriate regs. Eager VMReg save may bash flags,
386 * so set the CondCode arguments first.
388 for (int i
= 0; i
< argv
.size(); ++i
) {
389 assert(i
< kNumServiceReqArgRegs
);
390 auto reg
= serviceReqArgRegs
[i
];
391 const auto& argInfo
= argv
[i
];
392 switch(argv
[i
].m_kind
) {
393 case ServiceReqArgInfo::Immediate
: {
394 TRACE(3, "%" PRIx64
", ", argInfo
.m_imm
);
395 as
. emitImmReg(argInfo
.m_imm
, reg
);
397 case ServiceReqArgInfo::CondCode
: {
398 // Already set before VM reg save.
399 DEBUG_ONLY TCA start
= as
.frontier();
400 as
. setcc(argInfo
.m_cc
, rbyte(reg
));
401 assert(start
- as
.frontier() <= kMovSize
);
402 TRACE(3, "cc(%x), ", argInfo
.m_cc
);
404 default: not_reached();
407 emitEagerVMRegSave(as
, JIT::RegSaveFlags::SaveFP
);
409 as
. emitImmReg(0, rAsm
);
411 as
. emitImmReg((uint64_t)start
, rAsm
);
414 as
. emitImmReg(req
, rdi
);
417 * Weird hand-shaking with enterTC: reverse-call a service routine.
419 * In the case of some special stubs (m_callToExit, m_retHelper), we
420 * have already unbalanced the return stack by doing a ret to
421 * something other than enterTCHelper. In that case
422 * SRJmpInsteadOfRet indicates to fake the return.
424 if (flags
& SRFlags::JmpInsteadOfRet
) {
431 // TODO(2796856): we should record an OpServiceRequest pseudo-bytecode here.
433 translator_not_reached(as
);
436 * Recycled stubs need to be uniformly sized. Make space for the
437 * maximal possible service requests.
439 assert(as
.frontier() - start
<= kMaxStubSpace
);
440 as
.emitNop(start
+ kMaxStubSpace
- as
.frontier());
441 assert(as
.frontier() - start
== kMaxStubSpace
);