2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-2014 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-x64.h"
24 #include "folly/ScopeGuard.h"
25 #include "folly/Format.h"
26 #include "hphp/util/trace.h"
27 #include "hphp/util/text-util.h"
28 #include "hphp/util/abi-cxx.h"
30 #include "hphp/runtime/base/mixed-array.h"
31 #include "hphp/runtime/base/comparisons.h"
32 #include "hphp/runtime/base/complex-types.h"
33 #include "hphp/runtime/base/runtime-option.h"
34 #include "hphp/runtime/base/string-data.h"
35 #include "hphp/runtime/base/types.h"
36 #include "hphp/runtime/ext/ext_closure.h"
37 #include "hphp/runtime/ext/ext_generator.h"
38 #include "hphp/runtime/ext/ext_collections.h"
39 #include "hphp/runtime/ext/asio/asio_blockable.h"
40 #include "hphp/runtime/ext/asio/wait_handle.h"
41 #include "hphp/runtime/ext/asio/async_function_wait_handle.h"
42 #include "hphp/runtime/vm/bytecode.h"
43 #include "hphp/runtime/vm/runtime.h"
44 #include "hphp/runtime/base/stats.h"
45 #include "hphp/runtime/base/rds.h"
46 #include "hphp/runtime/base/rds-util.h"
47 #include "hphp/runtime/vm/jit/arg-group.h"
48 #include "hphp/runtime/vm/jit/back-end-x64.h"
49 #include "hphp/runtime/vm/jit/cfg.h"
50 #include "hphp/runtime/vm/jit/code-gen-helpers-x64.h"
51 #include "hphp/runtime/vm/jit/ir.h"
52 #include "hphp/runtime/vm/jit/layout.h"
53 #include "hphp/runtime/vm/jit/mc-generator-internal.h"
54 #include "hphp/runtime/vm/jit/mc-generator.h"
55 #include "hphp/runtime/vm/jit/native-calls.h"
56 #include "hphp/runtime/vm/jit/print.h"
57 #include "hphp/runtime/vm/jit/prof-data.h"
58 #include "hphp/runtime/vm/jit/reg-algorithms.h"
59 #include "hphp/runtime/vm/jit/service-requests-inline.h"
60 #include "hphp/runtime/vm/jit/service-requests-x64.h"
61 #include "hphp/runtime/vm/jit/simplifier.h"
62 #include "hphp/runtime/vm/jit/target-cache.h"
63 #include "hphp/runtime/vm/jit/target-profile.h"
64 #include "hphp/runtime/vm/jit/timer.h"
65 #include "hphp/runtime/vm/jit/translator-inline.h"
66 #include "hphp/runtime/vm/jit/translator.h"
67 #include "hphp/runtime/vm/jit/types.h"
68 #include "hphp/runtime/vm/jit/vasm-x64.h"
72 namespace HPHP
{ namespace JIT
{ namespace X64
{
78 //////////////////////////////////////////////////////////////////////
80 using namespace JIT::reg
;
83 * It's not normally ok to directly use tracelet abi registers in
84 * codegen, unless you're directly dealing with an instruction that
85 * does near-end-of-tracelet glue. (Or also we sometimes use them
86 * just for some static_assertions relating to calls to helpers from
87 * mcg that hardcode these registers.)
90 void cgPunt(const char* file
, int line
, const char* func
, uint32_t bcOff
,
91 const Func
* vmFunc
, bool resumed
, TransID profTransId
) {
92 if (dumpIREnabled()) {
93 HPHP::Trace::trace("--------- CG_PUNT %s %d %s bcOff: %d \n",
94 file
, line
, func
, bcOff
);
96 throw FailedCodeGen(file
, line
, func
, bcOff
, vmFunc
, resumed
, profTransId
);
99 #define CG_PUNT(instr) \
100 cgPunt(__FILE__, __LINE__, #instr, m_curInst->marker().bcOff(), \
101 curFunc(), resumed(), m_curInst->marker().profTransId())
103 const char* getContextName(const Class
* ctx
) {
104 return ctx
? ctx
->name()->data() : ":anonymous:";
107 } // unnamed namespace
108 //////////////////////////////////////////////////////////////////////
110 template <class Block
>
111 void CodeGenerator::ifBlock(ConditionCode cc
, Block taken
,
113 if (unlikely
) return unlikelyIfBlock(cc
, taken
);
114 ifThen(m_as
, cc
, taken
);
117 template <class Then
>
118 void CodeGenerator::unlikelyIfBlock(Vout
& v
, Vout
& vcold
,
119 ConditionCode cc
, Then then
) {
120 auto unlikely
= vcold
.makeBlock();
121 auto done
= v
.makeBlock();
122 v
<< jcc
{cc
, {done
, unlikely
}};
125 if (!vcold
.closed()) vcold
<< jmp
{done
};
129 template <class Block
>
130 void CodeGenerator::ifBlock(Vout
& v
, Vout
& vcold
, ConditionCode cc
,
131 Block taken
, bool unlikely
) {
132 if (unlikely
) return unlikelyIfBlock(v
, vcold
, cc
, taken
);
133 auto takenLabel
= v
.makeBlock();
134 auto doneLabel
= v
.makeBlock();
135 v
<< jcc
{cc
, {doneLabel
, takenLabel
}};
138 if (!v
.closed()) v
<< jmp
{doneLabel
};
142 template <class Block
>
143 void CodeGenerator::unlikelyIfBlock(ConditionCode cc
, Block unlikely
) {
144 if (m_as
.base() == m_acold
.base()) {
145 ifThen(m_as
, cc
, unlikely
);
147 Label unlikelyLabel
, done
;
148 m_as
.jcc(cc
, unlikelyLabel
);
149 asm_label(m_acold
, unlikelyLabel
);
152 asm_label(m_as
, done
);
156 // Generate an if-then-else block
157 template <class Then
, class Else
>
158 void CodeGenerator::ifThenElse(Asm
& a
, ConditionCode cc
, Then thenBlock
,
160 Label elseLabel
, done
;
161 a
.jcc8(ccNegate(cc
), elseLabel
);
164 asm_label(a
, elseLabel
);
169 template <class Then
, class Else
>
170 void CodeGenerator::ifThenElse(ConditionCode cc
, Then thenBlock
,
171 Else elseBlock
, bool unlikely
) {
172 if (unlikely
) return unlikelyIfThenElse(cc
, thenBlock
, elseBlock
);
173 ifThenElse(m_as
, cc
, thenBlock
, elseBlock
);
176 template <class Then
, class Else
>
177 void CodeGenerator::ifThenElse(Vout
& v
, ConditionCode cc
,
178 Then thenBlock
, Else elseBlock
) {
179 auto thenLabel
= v
.makeBlock();
180 auto elseLabel
= v
.makeBlock();
181 auto done
= v
.makeBlock();
182 v
<< jcc
{cc
, {elseLabel
, thenLabel
}};
185 if (!v
.closed()) v
<< jmp
{done
};
188 if (!v
.closed()) v
<< jmp
{done
};
192 template <class Then
, class Else
>
193 void CodeGenerator::unlikelyIfThenElse(ConditionCode cc
, Then unlikely
,
195 if (m_as
.base() == m_acold
.base()) {
196 Label elseLabel
, done
;
197 m_as
.jcc8(ccNegate(cc
), elseLabel
);
200 asm_label(m_as
, elseLabel
);
202 asm_label(m_as
, done
);
204 Label unlikelyLabel
, done
;
205 m_as
.jcc(cc
, unlikelyLabel
);
207 asm_label(m_acold
, unlikelyLabel
);
210 asm_label(m_as
, done
);
214 template <class Then
, class Else
>
215 void CodeGenerator::ifThenElse(Vout
& v
, Vout
& vcold
, ConditionCode cc
,
216 Then thenBlock
, Else elseBlock
, bool unlikely
) {
217 if (unlikely
) return unlikelyIfThenElse(v
, vcold
, cc
, thenBlock
, elseBlock
);
218 ifThenElse(v
, cc
, thenBlock
, elseBlock
);
221 template <class Then
, class Else
>
222 void CodeGenerator::unlikelyIfThenElse(Vout
& v
, Vout
& vcold
, ConditionCode cc
,
223 Then unlikelyBlock
, Else elseBlock
) {
224 auto elseLabel
= v
.makeBlock();
225 auto unlikelyLabel
= vcold
.makeBlock();
226 auto done
= v
.makeBlock();
227 v
<< jcc
{cc
, {elseLabel
, unlikelyLabel
}};
230 if (!v
.closed()) v
<< jmp
{done
};
231 vcold
= unlikelyLabel
;
232 unlikelyBlock(vcold
);
233 if (!vcold
.closed()) vcold
<< jmp
{done
};
238 * Select a scratch register to use in the given instruction, prefering the
239 * lower registers which don't require a REX prefix. The selected register
240 * must not be any of the instructions inputs or outputs, and neither a register
241 * that is alive across this instruction.
243 PhysReg
CodeGenerator::selectScratchReg(IRInstruction
* inst
) {
244 // TODO: #3634984 this function must die.
245 static const RegSet kLowGPRegs
= RegSet()
252 RegSet liveRegs
= m_state
.liveRegs
[inst
];
253 liveRegs
|= m_state
.regs
.srcRegs(*inst
);
254 liveRegs
|= m_state
.regs
.dstRegs(*inst
);
256 if ((kLowGPRegs
- liveRegs
).findFirst(selectedReg
)) {
262 void CodeGenerator::cgInst(IRInstruction
* inst
) {
263 Opcode opc
= inst
->op();
265 m_instRegs
= &m_state
.regs
[inst
];
266 m_rScratch
= selectScratchReg(inst
);
267 SCOPE_EXIT
{ m_curInst
= nullptr; };
269 Vauto
vasm(&m_state
.meta
);
270 m_vmain
= &vasm
.main(m_mainCode
);
271 m_vcold
= &vasm
.cold(m_coldCode
);
272 m_vfrozen
= &vasm
.frozen(m_frozenCode
);
273 SCOPE_EXIT
{ m_vmain
= m_vcold
= m_vfrozen
= nullptr; };
275 #define O(name, dsts, srcs, flags) \
276 case name: FTRACE(7, "cg" #name "\n"); \
283 always_assert(false);
288 #define NOOP_OPCODE(opcode) \
289 void CodeGenerator::cg##opcode(IRInstruction*) {}
291 #define CALL_OPCODE(opcode) \
292 void CodeGenerator::cg##opcode(IRInstruction* i) { cgCallNative(vmain(), i); }
294 #define CALL_STK_OPCODE(opcode) \
295 CALL_OPCODE(opcode) \
296 CALL_OPCODE(opcode ## Stk)
298 NOOP_OPCODE(DefConst
)
300 NOOP_OPCODE(TrackLoc
)
301 NOOP_OPCODE(AssertLoc
)
302 NOOP_OPCODE(AssertStk
)
304 NOOP_OPCODE(DefLabel
)
305 NOOP_OPCODE(ExceptionBarrier
)
306 NOOP_OPCODE(TakeStack
)
308 NOOP_OPCODE(EndGuards
)
310 CALL_OPCODE(AddElemStrKey
)
311 CALL_OPCODE(AddElemIntKey
)
312 CALL_OPCODE(AddNewElem
)
313 CALL_OPCODE(ArrayAdd
)
315 CALL_OPCODE(ColAddElemC
)
316 CALL_OPCODE(ColAddNewElemC
)
318 CALL_OPCODE(ConvBoolToArr
);
319 CALL_OPCODE(ConvDblToArr
);
320 CALL_OPCODE(ConvIntToArr
);
321 CALL_OPCODE(ConvObjToArr
);
322 CALL_OPCODE(ConvStrToArr
);
323 CALL_OPCODE(ConvCellToArr
);
325 CALL_OPCODE(ConvStrToBool
);
326 CALL_OPCODE(ConvCellToBool
);
328 CALL_OPCODE(ConvArrToDbl
);
329 CALL_OPCODE(ConvObjToDbl
);
330 CALL_OPCODE(ConvStrToDbl
);
331 CALL_OPCODE(ConvCellToDbl
);
333 CALL_OPCODE(ConvArrToInt
);
334 CALL_OPCODE(ConvObjToInt
);
335 CALL_OPCODE(ConvStrToInt
);
336 CALL_OPCODE(ConvCellToInt
);
338 CALL_OPCODE(ConvCellToObj
);
340 CALL_OPCODE(ConvDblToStr
);
341 CALL_OPCODE(ConvIntToStr
);
342 CALL_OPCODE(ConvObjToStr
);
343 CALL_OPCODE(ConvResToStr
);
344 CALL_OPCODE(ConvCellToStr
);
346 CALL_OPCODE(ConcatStrStr
);
347 CALL_OPCODE(ConcatStrInt
);
348 CALL_OPCODE(ConcatIntStr
);
349 CALL_OPCODE(ConcatStr3
);
350 CALL_OPCODE(ConcatStr4
);
352 CALL_OPCODE(TypeProfileFunc
)
353 CALL_OPCODE(CreateCont
)
354 CALL_OPCODE(CreateAFWH
)
355 CALL_OPCODE(CreateSSWH
)
356 CALL_OPCODE(AFWHPrepareChild
)
357 CALL_OPCODE(ABCUnblock
)
358 CALL_OPCODE(NewArray
)
359 CALL_OPCODE(NewMixedArray
)
360 CALL_OPCODE(NewMIArray
)
361 CALL_OPCODE(NewMSArray
)
362 CALL_OPCODE(NewLikeArray
)
363 CALL_OPCODE(NewPackedArray
)
366 CALL_OPCODE(AllocObj
)
367 CALL_OPCODE(CustomInstanceInit
)
368 CALL_OPCODE(InitProps
)
369 CALL_OPCODE(InitSProps
)
370 CALL_OPCODE(LdClsCtor
)
371 CALL_OPCODE(LookupClsMethod
)
372 CALL_OPCODE(LookupClsRDSHandle
)
373 CALL_OPCODE(LdArrFuncCtx
)
374 CALL_OPCODE(LdArrFPushCuf
)
375 CALL_OPCODE(LdStrFPushCuf
)
376 CALL_OPCODE(PrintStr
)
377 CALL_OPCODE(PrintInt
)
378 CALL_OPCODE(PrintBool
)
379 CALL_OPCODE(DbgAssertPtr
)
380 CALL_OPCODE(LdSwitchDblIndex
)
381 CALL_OPCODE(LdSwitchStrIndex
)
382 CALL_OPCODE(LdSwitchObjIndex
)
383 CALL_OPCODE(VerifyParamCallable
)
384 CALL_OPCODE(VerifyParamFail
)
385 CALL_OPCODE(VerifyRetCallable
)
386 CALL_OPCODE(VerifyRetFail
)
387 CALL_OPCODE(RaiseUninitLoc
)
388 CALL_OPCODE(WarnNonObjProp
)
389 CALL_OPCODE(ThrowNonObjProp
)
390 CALL_OPCODE(RaiseUndefProp
)
391 CALL_OPCODE(RaiseError
)
392 CALL_OPCODE(RaiseWarning
)
393 CALL_OPCODE(RaiseNotice
)
394 CALL_OPCODE(RaiseArrayIndexNotice
)
395 CALL_OPCODE(IncStatGrouped
)
396 CALL_OPCODE(ClosureStaticLocInit
)
397 CALL_OPCODE(ArrayIdx
)
398 CALL_OPCODE(GenericIdx
)
399 CALL_OPCODE(LdClsPropAddrOrNull
)
400 CALL_OPCODE(LdClsPropAddrOrRaise
)
401 CALL_OPCODE(LdGblAddrDef
)
403 // Vector instruction helpers
406 CALL_STK_OPCODE(PropDX
)
407 CALL_OPCODE(CGetProp
)
408 CALL_STK_OPCODE(VGetProp
)
409 CALL_STK_OPCODE(BindProp
)
410 CALL_STK_OPCODE(SetProp
)
411 CALL_OPCODE(UnsetProp
)
412 CALL_STK_OPCODE(SetOpProp
)
413 CALL_STK_OPCODE(IncDecProp
)
414 CALL_OPCODE(EmptyProp
)
415 CALL_OPCODE(IssetProp
)
417 CALL_OPCODE(ElemArray
)
418 CALL_STK_OPCODE(ElemDX
)
419 CALL_STK_OPCODE(ElemUX
)
420 CALL_OPCODE(ArrayGet
)
421 CALL_OPCODE(StringGet
)
423 CALL_OPCODE(CGetElem
)
424 CALL_STK_OPCODE(VGetElem
)
425 CALL_STK_OPCODE(BindElem
)
426 CALL_STK_OPCODE(SetWithRefElem
)
427 CALL_STK_OPCODE(SetWithRefNewElem
)
428 CALL_OPCODE(ArraySet
)
430 CALL_OPCODE(ArraySetRef
)
431 CALL_STK_OPCODE(SetElem
)
432 CALL_STK_OPCODE(UnsetElem
)
433 CALL_STK_OPCODE(SetOpElem
)
434 CALL_STK_OPCODE(IncDecElem
)
435 CALL_STK_OPCODE(SetNewElem
)
436 CALL_STK_OPCODE(SetNewElemArray
)
437 CALL_STK_OPCODE(BindNewElem
)
438 CALL_OPCODE(ArrayIsset
)
439 CALL_OPCODE(VectorIsset
)
440 CALL_OPCODE(PairIsset
)
441 CALL_OPCODE(MapIsset
)
442 CALL_OPCODE(IssetElem
)
443 CALL_OPCODE(EmptyElem
)
445 CALL_OPCODE(InstanceOfIface
)
446 CALL_OPCODE(InterfaceSupportsArr
)
447 CALL_OPCODE(InterfaceSupportsStr
)
448 CALL_OPCODE(InterfaceSupportsInt
)
449 CALL_OPCODE(InterfaceSupportsDbl
)
451 CALL_OPCODE(ZeroErrorLevel
)
452 CALL_OPCODE(RestoreErrorLevel
)
456 CALL_OPCODE(SurpriseHook
)
457 CALL_OPCODE(FunctionSuspendHook
)
458 CALL_OPCODE(FunctionReturnHook
)
460 CALL_OPCODE(OODeclExists
)
464 // Thread chain of patch locations using the 4 byte space in each jmp/jcc
465 static void prependPatchAddr(CodegenState
& state
,
468 auto &patches
= state
.patches
;
469 ssize_t diff
= patches
[block
] ? (patchAddr
- (TCA
)patches
[block
]) : 0;
470 assert(deltaFits(diff
, sz::dword
));
471 *(int32_t*)(patchAddr
) = (int32_t)diff
;
472 patches
[block
] = patchAddr
;
475 static void emitFwdJmp(Asm
& a
, Block
* target
, CodegenState
& state
) {
476 if (auto addr
= state
.addresses
[target
]) {
477 return a
.jmpAuto(addr
);
480 // TODO(#2101926): it'd be nice to get 1-byte forward jumps here
482 TCA immPtr
= a
.frontier() - 4;
483 prependPatchAddr(state
, target
, immPtr
);
486 void emitFwdJmp(CodeBlock
& cb
, Block
* target
, CodegenState
& state
) {
488 emitFwdJmp(a
, target
, state
);
491 void CodeGenerator::emitFwdJcc(Asm
& a
, ConditionCode cc
, Block
* target
) {
492 if (auto addr
= m_state
.addresses
[target
]) {
493 return a
.jccAuto(cc
, addr
);
496 // TODO(#2101926): it'd be nice to get 1-byte forward jumps here
497 a
.jcc(cc
, a
.frontier());
498 TCA immPtr
= a
.frontier() - 4;
499 prependPatchAddr(m_state
, target
, immPtr
);
502 void CodeGenerator::emitFwdJcc(ConditionCode cc
, Block
* target
) {
503 emitFwdJcc(m_as
, cc
, target
);
506 void CodeGenerator::emitLoadImm(Asm
& as
, int64_t val
, PhysReg dstReg
) {
507 Vauto().main(as
) << ldimm
{val
, dstReg
};
511 * Returns a XMM register containing the value of SSATmp tmp,
512 * which can be either a bool, an int, or a double.
513 * If the value is already in a XMM register, simply returns it.
514 * Otherwise, the value is moved into rCgXMM, which is returned.
515 * If instructions to convert to a double at runtime are needed,
516 * they're emitted in 'as'.
517 * TODO: #3634984, #3727837 This function must die.
519 PhysReg
CodeGenerator::prepXMMReg(Vout
& v
, const SSATmp
* src
,
520 const PhysLoc
& srcLoc
, RegXMM rtmp
) {
521 assert(src
->isA(Type::Bool
) || src
->isA(Type::Int
) || src
->isA(Type::Dbl
));
522 always_assert(srcLoc
.reg() != InvalidReg
);
523 auto rsrc
= srcLoc
.reg();
525 // Case 1: src is already in a XMM register
530 // Case 2: src Dbl stored in GP reg
531 if (src
->isA(Type::Dbl
)) {
532 v
<< copy
{rsrc
, rtmp
};
536 // Case 2.b: Bool or Int stored in GP reg
537 zeroExtendIfBool(v
, src
, rsrc
);
538 v
<< cvtsi2sd
{rsrc
, rtmp
};
542 VregXMM
CodeGenerator::prepXMM(Vout
& v
, const SSATmp
* src
,
543 const PhysLoc
& srcLoc
) {
544 assert(src
->isA(Type::Bool
) || src
->isA(Type::Int
) || src
->isA(Type::Dbl
));
545 always_assert(srcLoc
.reg() != InvalidReg
);
546 auto rsrc
= srcLoc
.reg();
548 // Case 1: src is already in a XMM register
553 // Case 2: src Dbl stored in GP reg
554 if (src
->isA(Type::Dbl
)) {
555 auto rtmp
= v
.makeReg();
556 v
<< copy
{rsrc
, rtmp
};
560 // Case 2.b: Bool or Int stored in GP reg
561 zeroExtendIfBool(v
, src
, rsrc
);
562 auto rtmp
= v
.makeReg();
563 v
<< cvtsi2sd
{rsrc
, rtmp
};
567 PhysReg
CodeGenerator::prepXMMReg(Asm
& a
, const SSATmp
* src
,
568 const PhysLoc
& srcLoc
, RegXMM rtmp
) {
569 return prepXMMReg(Vauto().main(a
), src
, srcLoc
, rtmp
);
572 void CodeGenerator::emitCompare(IRInstruction
* inst
) {
573 emitCompare(Vauto().main(m_mainCode
), inst
);
576 void CodeGenerator::emitCompare(Vout
& v
, IRInstruction
* inst
) {
577 auto src0
= inst
->src(0);
578 auto src1
= inst
->src(1);
579 auto loc0
= srcLoc(0);
580 auto loc1
= srcLoc(1);
581 auto const type0
= src0
->type();
582 auto const type1
= src1
->type();
584 // can't generate CMP instructions correctly for anything that isn't
585 // a bool or a numeric, and we can't mix bool/numerics because
586 // -1 == true in PHP, but not in HHIR binary representation
587 if (!((type0
<= Type::Int
&& type1
<= Type::Int
) ||
588 (type0
<= Type::Bool
&& type1
<= Type::Bool
) ||
589 (type0
<= Type::Cls
&& type1
<= Type::Cls
))) {
590 CG_PUNT(emitCompare
);
592 auto reg0
= loc0
.reg();
593 auto reg1
= loc1
.reg();
595 if (reg1
== InvalidReg
) {
596 if (type0
<= Type::Bool
) {
597 v
<< cmpbi
{src1
->boolVal(), reg0
};
599 v
<< cmpqi
{safe_cast
<int32_t>(src1
->intVal()), reg0
};
602 // Note the reverse syntax in the assembler.
603 // This cmp will compute reg0 - reg1
604 if (type0
<= Type::Bool
) {
605 v
<< cmpb
{reg1
, reg0
};
607 v
<< cmpq
{reg1
, reg0
};
612 void CodeGenerator::emitCompareInt(IRInstruction
* inst
) {
613 emitCompareInt(Vauto().main(m_mainCode
), inst
);
616 void CodeGenerator::emitCompareInt(Vout
& v
, IRInstruction
* inst
) {
617 auto srcReg0
= srcLoc(0).reg();
618 auto srcReg1
= srcLoc(1).reg();
619 if (srcReg1
== InvalidReg
) {
620 v
<< cmpqi
{safe_cast
<int32_t>(inst
->src(1)->intVal()), srcReg0
};
622 // Note the reverse syntax in the assembler.
623 // This cmp will compute srcReg0 - srcReg1
624 v
<< cmpq
{srcReg1
, srcReg0
};
628 void CodeGenerator::emitReqBindJcc(Vout
& v
, ConditionCode cc
,
629 const ReqBindJccData
* extra
) {
630 v
<< bindjcc1
{cc
, {extra
->notTaken
, extra
->taken
}};
633 void CodeGenerator::cgDefSP(IRInstruction
* inst
) {
634 if (RuntimeOption::EvalHHIRGenerateAsserts
&& !inst
->marker().resumed()) {
635 // Verify that rbx == rbp - spOff
636 m_as
.lea(rbp
[-cellsToBytes(inst
->extra
<StackOffset
>()->offset
)],
638 m_as
.cmpq(m_rScratch
, rbx
);
639 ifBlock(CC_NE
, [](Asm
& a
) { a
.ud2(); });
643 void CodeGenerator::cgCheckNullptr(IRInstruction
* inst
) {
644 if (!inst
->taken()) return;
645 auto reg
= srcLoc(0).reg(0);
646 m_as
.testq (reg
, reg
);
647 emitFwdJcc(CC_NZ
, inst
->taken());
650 void CodeGenerator::cgCheckNonNull(IRInstruction
* inst
) {
651 auto srcReg
= srcLoc(0).reg();
652 auto dstReg
= dstLoc(0).reg();
653 auto taken
= inst
->taken();
656 m_as
.testq (srcReg
, srcReg
);
657 emitFwdJcc(CC_Z
, taken
);
658 if (dstReg
!= InvalidReg
) emitMovRegReg(m_as
, srcReg
, dstReg
);
661 void CodeGenerator::cgAssertNonNull(IRInstruction
* inst
) {
663 auto srcReg
= srcLoc(0).reg();
664 auto dstReg
= dstLoc(0).reg();
665 if (RuntimeOption::EvalHHIRGenerateAsserts
) {
666 v
<< testq
{srcReg
, srcReg
};
667 ifThen(v
, CC_Z
, [&](Vout
& v
) {
671 v
<< copy
{srcReg
, dstReg
};
674 void CodeGenerator::cgAssertType(IRInstruction
* inst
) {
675 auto const srcRegs
= srcLoc(0);
676 auto const dstRegs
= dstLoc(0);
677 shuffle2(vmain(), srcRegs
.reg(0), srcRegs
.reg(1),
678 dstRegs
.reg(0), dstRegs
.reg(1));
681 void CodeGenerator::cgLdUnwinderValue(IRInstruction
* inst
) {
682 cgLoad(inst
->dst(), dstLoc(0), rVmTl
[unwinderTvOff()], inst
->taken());
685 void CodeGenerator::cgBeginCatch(IRInstruction
* inst
) {
686 auto const& info
= m_state
.catches
[inst
->block()];
688 auto catchStart
= v
.makePoint();
689 v
<< point
{catchStart
};
690 v
<< fixupcatch
{Vpoint
{size_t(info
.afterCall
)}, catchStart
};
691 v
<< incstat
{Stats::TC_CatchTrace
};
693 // We want to restore state as though the call had completed
694 // successfully, so skip over any stack arguments and pop any
696 if (info
.rspOffset
) {
697 v
<< addqi
{info
.rspOffset
, rsp
, rsp
};
699 PhysRegSaverParity::emitPops(v
, info
.savedRegs
);
702 static void unwindResumeHelper(_Unwind_Exception
* data
) {
703 tl_regState
= VMRegState::CLEAN
;
704 _Unwind_Resume(data
);
707 static void callUnwindResumeHelper(Vout
& v
) {
708 v
<< loadq
{rVmTl
[unwinderScratchOff()], rdi
};
709 v
<< call
{(TCA
)unwindResumeHelper
}; // pass control back to the unwinder
713 void CodeGenerator::cgEndCatch(IRInstruction
* inst
) {
714 callUnwindResumeHelper(vmain());
717 void CodeGenerator::cgTryEndCatch(IRInstruction
* inst
) {
719 v
<< cmpbim
{0, rVmTl
[unwinderSideExitOff()]};
720 unlikelyIfBlock(v
, vcold(), CC_E
, callUnwindResumeHelper
);
722 // doSideExit == true, so fall through to the side exit code
723 v
<< incstat
{Stats::TC_CatchSideExit
};
726 void CodeGenerator::cgDeleteUnwinderException(IRInstruction
* inst
) {
728 v
<< loadq
{rVmTl
[unwinderScratchOff()], rdi
};
729 v
<< call
{(TCA
)_Unwind_DeleteException
};
732 void CodeGenerator::cgJcc(IRInstruction
* inst
) {
734 emitFwdJcc(opToConditionCode(inst
->op()), inst
->taken());
737 void CodeGenerator::cgJccInt(IRInstruction
* inst
) {
738 emitCompareInt(inst
);
739 emitFwdJcc(opToConditionCode(inst
->op()), inst
->taken());
742 void CodeGenerator::cgReqBindJcc(IRInstruction
* inst
) {
743 // TODO(#2404427): prepareForTestAndSmash?
745 emitCompare(v
, inst
);
746 emitReqBindJcc(v
, opToConditionCode(inst
->op()),
747 inst
->extra
<ReqBindJccData
>());
750 void CodeGenerator::cgReqBindJccInt(IRInstruction
* inst
) {
751 // TODO(#2404427): prepareForTestAndSmash?
753 emitCompareInt(v
, inst
);
754 emitReqBindJcc(v
, opToConditionCode(inst
->op()),
755 inst
->extra
<ReqBindJccData
>());
758 void CodeGenerator::cgJmpGt(IRInstruction
* i
) { cgJcc(i
); }
759 void CodeGenerator::cgJmpGte(IRInstruction
* i
) { cgJcc(i
); }
760 void CodeGenerator::cgJmpLt(IRInstruction
* i
) { cgJcc(i
); }
761 void CodeGenerator::cgJmpLte(IRInstruction
* i
) { cgJcc(i
); }
762 void CodeGenerator::cgJmpEq(IRInstruction
* i
) { cgJcc(i
); }
763 void CodeGenerator::cgJmpNeq(IRInstruction
* i
) { cgJcc(i
); }
764 void CodeGenerator::cgJmpSame(IRInstruction
* i
) { cgJcc(i
); }
765 void CodeGenerator::cgJmpNSame(IRInstruction
* i
) { cgJcc(i
); }
767 void CodeGenerator::cgReqBindJmpGt(IRInstruction
* i
) { cgReqBindJcc(i
); }
768 void CodeGenerator::cgReqBindJmpGte(IRInstruction
* i
) { cgReqBindJcc(i
); }
769 void CodeGenerator::cgReqBindJmpLt(IRInstruction
* i
) { cgReqBindJcc(i
); }
770 void CodeGenerator::cgReqBindJmpLte(IRInstruction
* i
) { cgReqBindJcc(i
); }
771 void CodeGenerator::cgReqBindJmpEq(IRInstruction
* i
) { cgReqBindJcc(i
); }
772 void CodeGenerator::cgReqBindJmpNeq(IRInstruction
* i
) { cgReqBindJcc(i
); }
773 void CodeGenerator::cgReqBindJmpSame(IRInstruction
* i
) { cgReqBindJcc(i
); }
774 void CodeGenerator::cgReqBindJmpNSame(IRInstruction
* i
) { cgReqBindJcc(i
); }
776 void CodeGenerator::cgSideExitJmpGt(IRInstruction
* i
) { cgExitJcc(i
); }
777 void CodeGenerator::cgSideExitJmpGte(IRInstruction
* i
) { cgExitJcc(i
); }
778 void CodeGenerator::cgSideExitJmpLt(IRInstruction
* i
) { cgExitJcc(i
); }
779 void CodeGenerator::cgSideExitJmpLte(IRInstruction
* i
) { cgExitJcc(i
); }
780 void CodeGenerator::cgSideExitJmpEq(IRInstruction
* i
) { cgExitJcc(i
); }
781 void CodeGenerator::cgSideExitJmpNeq(IRInstruction
* i
) { cgExitJcc(i
); }
782 void CodeGenerator::cgSideExitJmpSame(IRInstruction
* i
) { cgExitJcc(i
); }
783 void CodeGenerator::cgSideExitJmpNSame(IRInstruction
* i
) { cgExitJcc(i
); }
785 void CodeGenerator::cgJmpGtInt(IRInstruction
* i
) { cgJccInt(i
); }
786 void CodeGenerator::cgJmpGteInt(IRInstruction
* i
) { cgJccInt(i
); }
787 void CodeGenerator::cgJmpLtInt(IRInstruction
* i
) { cgJccInt(i
); }
788 void CodeGenerator::cgJmpLteInt(IRInstruction
* i
) { cgJccInt(i
); }
789 void CodeGenerator::cgJmpEqInt(IRInstruction
* i
) { cgJccInt(i
); }
790 void CodeGenerator::cgJmpNeqInt(IRInstruction
* i
) { cgJccInt(i
); }
792 void CodeGenerator::cgReqBindJmpGtInt(IRInstruction
* i
) { cgReqBindJccInt(i
); }
793 void CodeGenerator::cgReqBindJmpGteInt(IRInstruction
* i
) { cgReqBindJccInt(i
); }
794 void CodeGenerator::cgReqBindJmpLtInt(IRInstruction
* i
) { cgReqBindJccInt(i
); }
795 void CodeGenerator::cgReqBindJmpLteInt(IRInstruction
* i
) { cgReqBindJccInt(i
); }
796 void CodeGenerator::cgReqBindJmpEqInt(IRInstruction
* i
) { cgReqBindJccInt(i
); }
797 void CodeGenerator::cgReqBindJmpNeqInt(IRInstruction
* i
) { cgReqBindJccInt(i
); }
799 void CodeGenerator::cgSideExitJmpGtInt(IRInstruction
* i
) { cgExitJccInt(i
); }
800 void CodeGenerator::cgSideExitJmpGteInt(IRInstruction
* i
) { cgExitJccInt(i
); }
801 void CodeGenerator::cgSideExitJmpLtInt(IRInstruction
* i
) { cgExitJccInt(i
); }
802 void CodeGenerator::cgSideExitJmpLteInt(IRInstruction
* i
) { cgExitJccInt(i
); }
803 void CodeGenerator::cgSideExitJmpEqInt(IRInstruction
* i
) { cgExitJccInt(i
); }
804 void CodeGenerator::cgSideExitJmpNeqInt(IRInstruction
* i
) { cgExitJccInt(i
); }
806 //////////////////////////////////////////////////////////////////////
808 void CodeGenerator::cgHalt(IRInstruction
* inst
) {
812 //////////////////////////////////////////////////////////////////////
815 * Once the arg sources and dests are all assigned; emit moves and exchanges to
816 * put all the args in desired registers. Any arguments that don't fit in
817 * registers will be put on the stack. In addition to moves and exchanges,
818 * shuffleArgs also handles adding lea-offsets for dest registers (dest = src +
819 * lea-offset) and zero extending bools (dest = zeroExtend(src)).
821 static bool shuffleArgsPlanningHelper(PhysReg::Map
<PhysReg
>& moves
,
822 PhysReg::Map
<ArgDesc
*>& argDescs
,
824 auto kind
= arg
.kind();
825 if (!(kind
== ArgDesc::Kind::Reg
||
826 kind
== ArgDesc::Kind::Addr
||
827 kind
== ArgDesc::Kind::TypeReg
)) {
830 auto dstReg
= arg
.dstReg();
831 auto srcReg
= arg
.srcReg();
832 if (dstReg
!= srcReg
) {
833 moves
[dstReg
] = srcReg
;
834 argDescs
[dstReg
] = &arg
;
839 static int64_t shuffleArgs(Vout
& v
, ArgGroup
& args
, CppCall
& call
) {
840 // Compute the move/shuffle plan.
841 PhysReg::Map
<PhysReg
> moves
;
842 PhysReg::Map
<ArgDesc
*> argDescs
;
844 for (size_t i
= 0; i
< args
.numGpArgs(); ++i
) {
845 auto& arg
= args
.gpArg(i
);
846 if (shuffleArgsPlanningHelper(moves
, argDescs
, arg
)) {
849 switch (call
.kind()) {
850 case CppCall::Kind::Indirect
:
851 if (arg
.dstReg() == call
.reg()) {
852 // an indirect call uses an argument register for the func ptr.
853 // Use rax instead and update the CppCall
854 moves
[reg::rax
] = call
.reg();
855 call
.updateCallIndirect(reg::rax
);
858 case CppCall::Kind::Direct
:
859 case CppCall::Kind::Virtual
:
860 case CppCall::Kind::ArrayVirt
:
864 for (size_t i
= 0; i
< args
.numSimdArgs(); ++i
) {
865 shuffleArgsPlanningHelper(moves
, argDescs
, args
.simdArg(i
));
868 // Store any arguments past the initial 6 to the stack. This has to happen
869 // before the shuffles below in case the shuffles would clobber any of the
871 for (int i
= args
.numStackArgs() - 1; i
>= 0; --i
) {
872 auto& arg
= args
.stkArg(i
);
873 auto srcReg
= arg
.srcReg();
874 assert(arg
.dstReg() == InvalidReg
);
875 switch (arg
.kind()) {
876 case ArgDesc::Kind::Reg
:
877 if (arg
.isZeroExtend()) {
878 auto tmp
= v
.makeReg();
879 v
<< movzbl
{srcReg
, tmp
};
882 if (srcReg
.isSIMD()) {
883 auto tmp
= v
.makeReg();
884 v
<< copy
{srcReg
, tmp
};
892 case ArgDesc::Kind::TypeReg
:
893 static_assert(kTypeWordOffset
== 0 || kTypeWordOffset
== 1,
894 "kTypeWordOffset value not supported");
895 assert(srcReg
.isGP());
896 // x86 stacks grow down, so push higher offset items first
897 if (kTypeWordOffset
== 0) {
900 // 4 bytes of garbage:
902 // get the type in the right place in rTmp before pushing it
903 auto tmp1
= v
.makeReg();
904 v
<< movb
{srcReg
, tmp1
};
905 auto tmp2
= v
.makeReg();
906 v
<< shlli
{CHAR_BIT
, tmp1
, tmp2
};
911 case ArgDesc::Kind::Imm
: {
912 auto tmp
= v
.makeReg();
913 v
<< ldimm
{arg
.imm(), tmp
};
918 case ArgDesc::Kind::Addr
: {
919 auto tmp
= v
.makeReg();
920 v
<< lea
{arg
.srcReg()[arg
.disp().l()], tmp
};
925 case ArgDesc::Kind::IpRel
: {
926 auto tmp
= v
.makeReg();
927 v
<< leap
{rip
[arg
.imm().q()], tmp
};
932 case ArgDesc::Kind::None
:
934 if (RuntimeOption::EvalHHIRGenerateAsserts
) {
935 emitImmStoreq(v
, 0xbadbadbadbadbad, *rsp
);
944 auto const howTo
= doRegMoves(moves
, rTmp
);
945 for (auto& how
: howTo
) {
946 switch (how
.m_kind
) {
947 case MoveInfo::Kind::Move
: {
949 if (how
.m_dst
== rTmp
) {
950 v
<< copy
{how
.m_src
, how
.m_dst
};
952 ArgDesc
* argDesc
= argDescs
[how
.m_dst
];
953 if (argDesc
== nullptr) {
954 // when no ArgDesc is available is a straight reg to reg copy
955 v
<< copy
{how
.m_src
, how
.m_dst
};
957 ArgDesc::Kind kind
= argDesc
->kind();
958 if (kind
== ArgDesc::Kind::Reg
|| kind
== ArgDesc::Kind::TypeReg
) {
959 if (argDesc
->isZeroExtend()) {
960 assert(how
.m_src
.isGP());
961 assert(how
.m_dst
.isGP());
962 v
<< movzbl
{how
.m_src
, how
.m_dst
};
964 v
<< copy
{how
.m_src
, how
.m_dst
};
967 assert(kind
== ArgDesc::Kind::Addr
);
968 assert(how
.m_src
.isGP());
969 assert(how
.m_dst
.isGP());
970 v
<< lea
{how
.m_src
[argDesc
->disp().l()], how
.m_dst
};
972 if (kind
!= ArgDesc::Kind::TypeReg
) {
979 case MoveInfo::Kind::Xchg
:
981 assert(how
.m_src
.isGP());
982 assert(how
.m_dst
.isGP());
983 v
<< copy2
{how
.m_src
, how
.m_dst
, how
.m_dst
, how
.m_src
};
988 TRACE(2, "arg-moves %d\n", num_moves
);
991 // Handle const-to-register moves, type shifting,
992 // load-effective address and zero extending for bools.
993 // Ignore args that have been handled by the
995 for (size_t i
= 0; i
< args
.numGpArgs(); ++i
) {
996 auto& arg
= args
.gpArg(i
);
997 if (arg
.done()) continue;
998 ArgDesc::Kind kind
= arg
.kind();
999 PhysReg dst
= arg
.dstReg();
1001 if (kind
== ArgDesc::Kind::Imm
) {
1002 v
<< ldimm
{arg
.imm().q(), dst
};
1003 } else if (kind
== ArgDesc::Kind::TypeReg
) {
1004 if (kTypeShiftBits
> 0) {
1005 v
<< shlqi
{kTypeShiftBits
, dst
, dst
};
1007 } else if (kind
== ArgDesc::Kind::Addr
) {
1008 v
<< addqi
{arg
.disp(), dst
, dst
};
1009 } else if (kind
== ArgDesc::Kind::IpRel
) {
1010 v
<< leap
{rip
[arg
.imm().q()], dst
};
1011 } else if (arg
.isZeroExtend()) {
1012 v
<< movzbl
{dst
, dst
};
1013 } else if (RuntimeOption::EvalHHIRGenerateAsserts
&&
1014 kind
== ArgDesc::Kind::None
) {
1015 v
<< ldimm
{0xbadbadbadbadbad, dst
};
1019 for (size_t i
= 0; i
< args
.numSimdArgs(); ++i
) {
1020 auto &arg
= args
.simdArg(i
);
1021 if (arg
.done()) continue;
1022 auto kind
= arg
.kind();
1023 auto dst
= arg
.dstReg();
1024 assert(dst
.isSIMD());
1025 if (kind
== ArgDesc::Kind::Imm
) {
1026 v
<< ldimm
{arg
.imm().q(), dst
};
1027 } else if (RuntimeOption::EvalHHIRGenerateAsserts
&&
1028 kind
== ArgDesc::Kind::None
) {
1029 v
<< ldimm
{0xbadbadbadbadbad, dst
};
1033 return args
.numStackArgs() * sizeof(int64_t);
1036 void CodeGenerator::cgCallNative(Vout
& v
, IRInstruction
* inst
) {
1037 using namespace NativeCalls
;
1039 Opcode opc
= inst
->op();
1040 always_assert(CallMap::hasInfo(opc
));
1042 auto const& info
= CallMap::info(opc
);
1043 ArgGroup argGroup
= info
.toArgGroup(m_state
.regs
, inst
);
1045 auto call
= [&]() -> CppCall
{
1046 switch (info
.func
.type
) {
1047 case FuncType::Call
:
1048 return CppCall(info
.func
.call
);
1050 return CppCall::direct(
1051 reinterpret_cast<void (*)()>(inst
->src(info
.func
.srcIdx
)->tcaVal()));
1056 auto const dest
= [&]() -> CallDest
{
1057 switch (info
.dest
) {
1058 case DestType::None
: return kVoidDest
;
1059 case DestType::TV
: return callDestTV(inst
);
1060 case DestType::SSA
: return callDest(inst
);
1061 case DestType::Dbl
: return callDestDbl(inst
);
1066 cgCallHelper(v
, call
, dest
, info
.sync
, argGroup
);
1069 CallDest
CodeGenerator::callDest(PhysReg reg0
,
1070 PhysReg reg1
/* = InvalidReg */) const {
1071 return { DestType::SSA
, reg0
, reg1
};
1074 CallDest
CodeGenerator::callDest(const IRInstruction
* inst
) const {
1075 if (!inst
->numDsts()) return kVoidDest
;
1076 auto loc
= dstLoc(0);
1077 return { DestType::SSA
, loc
.reg(0), loc
.reg(1) };
1080 CallDest
CodeGenerator::callDestTV(const IRInstruction
* inst
) const {
1081 if (!inst
->numDsts()) return kVoidDest
;
1082 auto loc
= dstLoc(0);
1083 return { DestType::TV
, loc
.reg(0), loc
.reg(1) };
1086 CallDest
CodeGenerator::callDestDbl(const IRInstruction
* inst
) const {
1087 if (!inst
->numDsts()) return kVoidDest
;
1088 auto loc
= dstLoc(0);
1089 return { DestType::Dbl
, loc
.reg(0), loc
.reg(1) };
1093 CodeGenerator::cgCallHelper(Asm
& a
, CppCall call
, const CallDest
& dstInfo
,
1094 SyncOptions sync
, ArgGroup
& args
) {
1095 cgCallHelper(a
, call
, dstInfo
, sync
, args
, m_state
.liveRegs
[m_curInst
]);
1099 CodeGenerator::cgCallHelper(Vout
& v
, CppCall call
, const CallDest
& dstInfo
,
1100 SyncOptions sync
, ArgGroup
& args
) {
1101 return cgCallHelper(v
, call
, dstInfo
, sync
, args
,
1102 m_state
.liveRegs
[m_curInst
]);
1106 CodeGenerator::cgCallHelper(Asm
& a
, CppCall call
, const CallDest
& dstInfo
,
1107 SyncOptions sync
, ArgGroup
& args
, RegSet toSave
) {
1108 cgCallHelper(Vauto(&m_state
.meta
).main(a
), call
, dstInfo
, sync
, args
,
1113 CodeGenerator::cgCallHelper(Vout
& v
, CppCall call
, const CallDest
& dstInfo
,
1114 SyncOptions sync
, ArgGroup
& args
, RegSet toSave
) {
1115 assert(m_curInst
->isNative());
1117 auto const destType
= dstInfo
.type
;
1118 auto const dstReg0
= dstInfo
.reg0
;
1119 auto const dstReg1
= dstInfo
.reg1
;
1121 // Save the caller-saved registers that are live across this
1122 // instruction. The number of regs to save and the number of args
1123 // being passed on the stack affect the parity of the PhysRegSaver,
1124 // so we use the generic version here.
1125 toSave
= toSave
& kCallerSaved
;
1126 assert((toSave
& RegSet().add(dstReg0
).add(dstReg1
)).empty());
1127 assert(IMPLIES(m_curInst
->is(Call
, CallArray
), toSave
.empty()));
1128 PhysRegSaverParity
regSaver(1 + args
.numStackArgs(), v
, toSave
);
1130 // Assign registers to the arguments then prepare them for the call.
1131 for (size_t i
= 0; i
< args
.numGpArgs(); i
++) {
1132 args
.gpArg(i
).setDstReg(argNumToRegName
[i
]);
1134 for (size_t i
= 0; i
< args
.numSimdArgs(); i
++) {
1135 args
.simdArg(i
).setDstReg(argNumToSIMDRegName
[i
]);
1137 regSaver
.bytesPushed(shuffleArgs(v
, args
, call
));
1139 // do the call; may use a trampoline
1140 if (sync
== SyncOptions::kSmashableAndSyncPoint
) {
1141 assert(call
.kind() == CppCall::Kind::Direct
);
1142 v
<< mccall
{(TCA
)call
.address()};
1146 auto afterCall
= v
.makePoint();
1147 v
<< point
{afterCall
};
1148 if (RuntimeOption::HHProfServerEnabled
|| sync
!= SyncOptions::kNoSyncPoint
) {
1149 // if we are profiling the heap, we always need to sync because
1150 // regs need to be correct during smart allocations no matter
1152 recordSyncPoint(v
, sync
);
1155 auto* taken
= m_curInst
->taken();
1156 if (taken
&& taken
->isCatch()) {
1158 sync
!= SyncOptions::kNoSyncPoint
,
1159 "cgCallHelper called with kNoSyncPoint but inst has a catch block: {}\n",
1163 taken
->catchMarker() == m_curInst
->marker(),
1164 "Catch trace doesn't match fixup:\n"
1168 m_curInst
->toString(),
1169 taken
->catchMarker().show(),
1170 m_curInst
->marker().show()
1173 auto& info
= m_state
.catches
[taken
];
1174 info
.afterCall
= (TCA
)size_t(afterCall
);
1175 info
.savedRegs
= toSave
;
1176 info
.rspOffset
= regSaver
.rspAdjustment();
1177 } else if (!m_curInst
->is(Call
, CallArray
, ContEnter
)) {
1178 // The current instruction doesn't have a catch block so it'd better not
1179 // throw. Register a null catch trace to indicate this to the
1180 // unwinder. Call and CallArray don't have catch blocks because they smash
1181 // all live values and optimizations are aware of this.
1182 v
<< fixupcatch
{afterCall
, v
.makePoint()};
1185 // copy the call result to the destination register(s)
1189 // rax contains m_type and m_aux but we're expecting just the
1190 // type in the lower bits, so shift the type result register.
1191 auto rval
= packed_tv
? reg::rdx
: reg::rax
;
1192 auto rtyp
= packed_tv
? reg::rax
: reg::rdx
;
1193 if (kTypeShiftBits
> 0) v
<< shrqi
{kTypeShiftBits
, rtyp
, rtyp
};
1194 shuffle2(v
, rval
, rtyp
, dstReg0
, dstReg1
);
1198 // copy the single-register result to dstReg0
1199 assert(dstReg1
== InvalidReg
);
1200 if (dstReg0
!= InvalidReg
) v
<< copy
{reg::rax
, dstReg0
};
1202 case DestType::None
:
1203 // void return type, no registers have values
1204 assert(dstReg0
== InvalidReg
&& dstReg1
== InvalidReg
);
1207 // copy the single-register result to dstReg0
1208 assert(dstReg1
== InvalidReg
);
1209 if (dstReg0
!= InvalidReg
) v
<< copy
{reg::xmm0
, dstReg0
};
1214 void CodeGenerator::cgMov(IRInstruction
* inst
) {
1215 always_assert(inst
->src(0)->numWords() == inst
->dst(0)->numWords());
1217 auto& v
= vasm
.main(m_as
);
1218 if (srcLoc(0).hasReg(1)) {
1219 assert(srcLoc(0).reg() != InvalidReg
);
1220 shuffle2(v
, srcLoc(0).reg(), srcLoc(0).reg(1),
1221 dstLoc(0).reg(), dstLoc(0).reg(1));
1224 auto const src
= inst
->src(0);
1226 auto sreg
= srcLoc(0).reg();
1227 auto dreg
= dstLoc(0).reg();
1228 if (sreg
!= InvalidReg
&& dreg
!= InvalidReg
) {
1229 v
<< copy
{sreg
, dreg
};
1230 } else if (sreg
== InvalidReg
&& dreg
!= InvalidReg
) {
1231 // It won't have a raw value if it's null.
1232 auto const stype
= src
->type();
1233 assert(stype
.hasRawVal() || stype
<= Type::Null
);
1234 auto const raw
= stype
.hasRawVal() ? src
->rawVal() : 0;
1235 v
<< ldimm
{raw
, dreg
};
1237 assert(sreg
== InvalidReg
&& dreg
== InvalidReg
);
1241 template<class OpInstr
>
1242 void CodeGenerator::cgUnaryIntOp(PhysLoc dst_loc
,
1243 SSATmp
* src
, PhysLoc src_loc
) {
1244 assert(src
->isA(Type::Int
));
1245 assert(dst_loc
.reg() != InvalidReg
);
1246 assert(src_loc
.reg() != InvalidReg
);
1247 auto dstReg
= dst_loc
.reg();
1248 auto srcReg
= src_loc
.reg();
1251 // Integer operations require 64-bit representations
1252 zeroExtendIfBool(v
, src
, srcReg
);
1253 v
<< copy
{srcReg
, dstReg
};
1254 v
<< OpInstr
{dstReg
, dstReg
};
1257 template<class OpInstr
>
1258 void cgUnaryIntOp(Asm
& a
, PhysLoc dst_loc
, SSATmp
* src
, PhysLoc src_loc
,
1260 assert(src
->isA(Type::Int
));
1261 assert(dst_loc
.reg() != InvalidReg
);
1262 assert(src_loc
.reg() != InvalidReg
);
1263 auto dstReg
= dst_loc
.reg();
1264 auto srcReg
= src_loc
.reg();
1266 // Integer operations require 64-bit representations
1267 zeroExtendIfBool(a
, src
, srcReg
);
1269 emitMovRegReg(a
, srcReg
, dstReg
);
1270 (a
.*instr
) (dstReg
);
1273 void CodeGenerator::cgAbsDbl(IRInstruction
* inst
) {
1274 auto srcReg
= srcLoc(0).reg();
1275 auto dstReg
= dstLoc(0).reg();
1278 // clear the high bit
1279 auto resReg
= dstReg
.isSIMD() ? Vreg(dstReg
) : v
.makeReg();
1281 if (srcReg
.isSIMD()) {
1284 tmp_src
= v
.makeReg();
1285 v
<< copy
{srcReg
, tmp_src
};
1287 auto tmp1
= v
.makeReg();
1288 v
<< psllq
{1, tmp_src
, tmp1
};
1289 v
<< psrlq
{1, tmp1
, resReg
};
1290 if (resReg
!= Vreg(dstReg
)) v
<< copy
{resReg
, dstReg
};
1293 typedef void (Asm::*AsmInstrIR
)(Immed
, Reg64
);
1295 void CodeGenerator::cgBinaryIntOp(IRInstruction
* inst
,
1296 void (Asm::*instrIR
)(Immed
, Reg64
),
1297 void (Asm::*instrRR
)(Reg64
, Reg64
),
1298 Commutativity commuteFlag
) {
1299 assert(m_curInst
== inst
); // could remove the inst param
1300 const SSATmp
* src0
= inst
->src(0);
1301 const SSATmp
* src1
= inst
->src(1);
1303 // inputs must be ints, or a (bool,bool) operation that ends up behaving
1304 // like an int anyway (e.g. XorBool)
1305 assert((src0
->isA(Type::Int
) && src1
->isA(Type::Int
)));
1307 auto const dstReg
= dstLoc(0).reg();
1308 auto const src0Reg
= srcLoc(0).reg();
1309 auto const src1Reg
= srcLoc(1).reg();
1312 auto opWithScratch
= [&]() {
1313 a
.movq(src0Reg
, m_rScratch
);
1314 (a
.*instrRR
) (src1Reg
, m_rScratch
);
1315 a
.movq(m_rScratch
, dstReg
);
1319 if (src0Reg
!= InvalidReg
&& src1Reg
!= InvalidReg
) {
1320 if (dstReg
== src0Reg
) {
1321 (a
.*instrRR
) (src1Reg
, dstReg
);
1322 } else if (dstReg
== src1Reg
) {
1323 if (commuteFlag
== Commutative
) {
1324 (a
.*instrRR
) (src0Reg
, dstReg
);
1329 emitMovRegReg(a
, src0Reg
, dstReg
);
1330 (a
.*instrRR
) (src1Reg
, dstReg
);
1335 auto intVal
= [] (const SSATmp
* s
) {
1336 return s
->isA(Type::Int
) ? safe_cast
<int32_t>(s
->intVal()) :
1337 int32_t(s
->boolVal());
1340 // One register, and one immediate.
1341 if (commuteFlag
== Commutative
) {
1343 auto imm
= intVal(src1Reg
== InvalidReg
? src1
: src0
);
1344 auto srcReg
= srcLoc(src1Reg
== InvalidReg
? 0 : 1).reg();
1345 if (srcReg
== dstReg
) {
1346 (a
.*instrIR
) (imm
, dstReg
);
1348 a
. emitImmReg(imm
, dstReg
);
1349 (a
.*instrRR
) (srcReg
, dstReg
);
1355 if (src0Reg
== InvalidReg
) {
1356 if (dstReg
== src1Reg
) {
1357 emitLoadImm(a
, intVal(src0
), m_rScratch
);
1358 (a
.*instrRR
) (src1Reg
, m_rScratch
);
1359 a
.movq(m_rScratch
, dstReg
);
1361 emitLoadImm(a
, intVal(src0
), dstReg
);
1362 (a
.*instrRR
) (src1Reg
, dstReg
);
1367 assert(src1Reg
== InvalidReg
);
1368 emitMovRegReg(a
, src0Reg
, dstReg
);
1369 (a
.*instrIR
) (intVal(src1
), dstReg
);
1372 template<class Op
, class Opi
>
1373 void CodeGenerator::cgBinaryIntOp(IRInstruction
* inst
) {
1374 assert(m_curInst
== inst
); // could remove the inst param
1375 UNUSED
const SSATmp
* src0
= inst
->src(0);
1376 UNUSED
const SSATmp
* src1
= inst
->src(1);
1378 // inputs must be ints, or a (bool,bool) operation that ends up behaving
1379 // like an int anyway (e.g. XorBool)
1380 assert((src0
->isA(Type::Int
) && src1
->isA(Type::Int
)) ||
1381 (src0
->isA(Type::Bool
) && src1
->isA(Type::Bool
)));
1383 auto const dstReg
= dstLoc(0).reg();
1384 auto const src0Reg
= srcLoc(0).reg();
1385 auto const src1Reg
= srcLoc(1).reg();
1388 // LHS must always be assigned a register.
1389 assert(src0Reg
!= InvalidReg
);
1391 if (src1Reg
!= InvalidReg
) {
1393 v
<< Op
{src1Reg
, src0Reg
, dstReg
};
1395 // One register, one immediate
1396 auto imm
= src1
->isA(Type::Int
) ? safe_cast
<int32_t>(src1
->intVal()) :
1397 int32_t(src1
->boolVal());
1398 v
<< Opi
{imm
, src0Reg
, dstReg
};
1402 template<class Emit
>
1403 void CodeGenerator::cgBinaryDblOp(IRInstruction
* inst
, Emit emit
) {
1404 assert(inst
== m_curInst
);
1405 const SSATmp
* src0
= inst
->src(0);
1406 const SSATmp
* src1
= inst
->src(1);
1407 auto loc0
= srcLoc(0);
1408 auto loc1
= srcLoc(1);
1409 assert(src0
->isA(Type::Dbl
) && src1
->isA(Type::Dbl
));
1412 auto dstReg
= dstLoc(0).reg();
1413 auto resReg
= dstReg
.isSIMD() && dstReg
!= loc1
.reg() ? Vreg(dstReg
) :
1415 assert(resReg
.isVirt() || resReg
.isSIMD());
1417 auto srcReg0
= prepXMM(v
, src0
, loc0
);
1418 auto srcReg1
= prepXMM(v
, src1
, loc1
);
1420 emit(v
, srcReg1
, srcReg0
, resReg
);
1421 if (resReg
!= Vreg(dstReg
)) v
<< copy
{resReg
, dstReg
};
1425 * If src2 is 1, this generates dst = src1 - 1 or src1 + 1 using the inc
1426 * or dec x86 instructions. The return value is whether or not the
1427 * instruction could be generated.
1429 bool emitIncDecHelper(Asm
& as
, PhysLoc dst
, SSATmp
* src1
, PhysLoc loc1
,
1430 SSATmp
* src2
, PhysLoc loc2
,
1431 void(Asm::*emitFunc
)(Reg64
)) {
1432 if (loc1
.reg() != InvalidReg
&& loc2
.reg() != InvalidReg
&&
1434 emitMovRegReg(as
, loc1
.reg(), dst
.reg());
1435 (as
.*emitFunc
)(dst
.reg());
1441 void CodeGenerator::cgAddIntO(IRInstruction
* inst
) {
1442 assert(inst
->taken() != nullptr);
1443 SSATmp
* src1
= inst
->src(0);
1444 SSATmp
* src2
= inst
->src(1);
1445 auto loc1
= srcLoc(0);
1446 auto loc2
= srcLoc(1);
1447 auto dst
= dstLoc(0);
1449 // Special cases: x = y + 1, x = 1 + y
1450 if (!emitIncDecHelper(m_as
, dst
, src1
, loc1
, src2
, loc2
, &Asm::incq
) &&
1451 !emitIncDecHelper(m_as
, dst
, src2
, loc2
, src1
, loc1
, &Asm::incq
)) {
1452 cgBinaryIntOp(inst
, &Asm::addq
, &Asm::addq
, Commutative
);
1454 emitFwdJcc(m_as
, CC_O
, inst
->taken());
1460 void CodeGenerator::cgSubIntO(IRInstruction
* inst
) {
1461 assert(inst
->taken() != nullptr);
1462 auto src1
= inst
->src(0);
1463 auto src2
= inst
->src(1);
1464 auto loc1
= srcLoc(0);
1465 auto loc2
= srcLoc(1);
1466 auto dst
= dstLoc(0);
1468 if (src1
->isConst(0)) {
1469 // There is no unary negate HHIR instruction, so handle that here.
1470 X64::cgUnaryIntOp(m_as
, dst
, src2
, loc2
, &Asm::neg
);
1471 } else if (!emitIncDecHelper(m_as
, dst
, src1
, loc1
, src2
, loc2
, &Asm::decq
)) {
1472 cgBinaryIntOp(inst
, &Asm::subq
, &Asm::subq
, NonCommutative
);
1474 emitFwdJcc(m_as
, CC_O
, inst
->taken());
1477 void CodeGenerator::cgMulIntO(IRInstruction
* inst
) {
1478 assert(inst
->taken() != nullptr);
1479 auto src0Reg
= srcLoc(0).reg();
1480 auto src1Reg
= srcLoc(1).reg();
1481 auto dstReg
= dstLoc(0).reg();
1482 if (dstReg
!= src0Reg
&& dstReg
== src1Reg
) {
1483 m_as
.imul(src0Reg
, dstReg
);
1485 emitMovRegReg(m_as
, src0Reg
, dstReg
);
1486 m_as
.imul(src1Reg
, dstReg
);
1488 emitFwdJcc(m_as
, CC_O
, inst
->taken());
1492 * If src2 is 1, this generates dst = src1 - 1 or src1 + 1 using the inc
1493 * or dec x86 instructions. The return value is whether or not the
1494 * instruction could be generated.
1496 template<class Inst
>
1497 bool CodeGenerator::emitIncDec(PhysLoc dst
, SSATmp
* src1
, PhysLoc loc1
,
1498 SSATmp
* src2
, PhysLoc loc2
) {
1500 if (loc1
.reg() != InvalidReg
&& loc2
.reg() != InvalidReg
&&
1502 v
<< copy
{loc1
.reg(), dst
.reg()};
1503 v
<< Inst
{dst
.reg(), dst
.reg()};
1509 void CodeGenerator::cgRoundCommon(IRInstruction
* inst
, RoundDirection dir
) {
1510 auto src
= inst
->src(0);
1511 auto dstReg
= dstLoc(0).reg();
1513 auto inReg
= prepXMM(v
, src
, srcLoc(0));
1514 auto outReg
= dstReg
.isSIMD() ? Vreg(dstReg
) : v
.makeReg();
1515 v
<< roundsd
{dir
, inReg
, outReg
};
1516 v
<< copy
{outReg
, dstReg
};
1519 void CodeGenerator::cgFloor(IRInstruction
* inst
) {
1520 cgRoundCommon(inst
, RoundDirection::floor
);
1523 void CodeGenerator::cgCeil(IRInstruction
* inst
) {
1524 cgRoundCommon(inst
, RoundDirection::ceil
);
1527 void CodeGenerator::cgAddInt(IRInstruction
* inst
) {
1528 SSATmp
* src0
= inst
->src(0);
1529 SSATmp
* src1
= inst
->src(1);
1530 auto loc0
= srcLoc(0);
1531 auto loc1
= srcLoc(1);
1532 auto dst
= dstLoc(0);
1534 // Special cases: x = y + 1, x = 1 + y
1535 if (emitIncDec
<incq
>(dst
, src0
, loc0
, src1
, loc1
) ||
1536 emitIncDec
<incq
>(dst
, src1
, loc1
, src0
, loc1
)) {
1540 cgBinaryIntOp
<addq
,addqi
>(inst
);
1543 void CodeGenerator::cgSubInt(IRInstruction
* inst
) {
1544 auto src0
= inst
->src(0);
1545 auto src1
= inst
->src(1);
1546 auto loc0
= srcLoc(0);
1547 auto loc1
= srcLoc(1);
1548 auto dst
= dstLoc(0);
1550 if (emitIncDec
<decq
>(dst
, src0
, loc0
, src1
, loc1
)) return;
1552 if (src0
->isConst(0)) {
1553 // There is no unary negate HHIR instruction, so handle that here.
1554 cgUnaryIntOp
<neg
>(dst
, src1
, loc1
);
1558 // not using cgBinaryIntOp because sub is not commutative, and we can do
1559 // r1=r0-r1 by doing neg r1; addq r1+=r0 without a scratch register.
1560 auto s0
= loc0
.reg();
1561 auto s1
= loc1
.reg();
1563 assert(s0
!= InvalidReg
&& d
!= InvalidReg
);
1567 v
<< addq
{s0
, s1
, d
};
1568 } else if (s1
== InvalidReg
) {
1569 auto imm
= src1
->isA(Type::Int
) ? safe_cast
<int32_t>(src1
->intVal()) :
1570 int32_t(src1
->boolVal());
1571 v
<< subqi
{imm
, s0
, d
};
1573 v
<< subq
{s1
, s0
, d
};
1577 void CodeGenerator::cgMulInt(IRInstruction
* inst
) {
1578 // not using cgBinaryIntOp() here because x64 imul does not have
1579 // an immediate form. This means we can't provide a well-formed
1580 // Op class, and most of the complicated logic in cgBinaryIntOp
1581 // isn't necessary anyway.
1582 auto src0
= srcLoc(0).reg();
1583 auto src1
= srcLoc(1).reg();
1584 auto dst
= dstLoc(0).reg();
1586 assert(src0
!= InvalidReg
&& src1
!= InvalidReg
&& dst
!= InvalidReg
);
1587 v
<< imul
{src1
, src0
, dst
};
1590 void CodeGenerator::cgAddDbl(IRInstruction
* inst
) {
1591 cgBinaryDblOp(inst
, [&](Vout
& v
, VregXMM s0
, VregXMM s1
, VregXMM res
) {
1592 v
<< addsd
{s0
, s1
, res
};
1596 void CodeGenerator::cgSubDbl(IRInstruction
* inst
) {
1597 cgBinaryDblOp(inst
, [&](Vout
& v
, VregXMM s0
, VregXMM s1
, VregXMM res
) {
1598 v
<< subsd
{s0
, s1
, res
};
1602 void CodeGenerator::cgMulDbl(IRInstruction
* inst
) {
1603 cgBinaryDblOp(inst
, [&](Vout
& v
, VregXMM s0
, VregXMM s1
, VregXMM res
) {
1604 v
<< mulsd
{s0
, s1
, res
};
1608 void CodeGenerator::cgDivDbl(IRInstruction
* inst
) {
1609 const SSATmp
* src1
= inst
->src(0);
1610 const SSATmp
* src2
= inst
->src(1);
1611 auto loc1
= srcLoc(0);
1612 auto loc2
= srcLoc(1);
1613 auto exit
= inst
->taken();
1615 auto dstReg
= dstLoc(0).reg();
1616 auto resReg
= dstReg
.isSIMD() && dstReg
!= loc2
.reg() ? dstReg
:
1618 assert(resReg
.isSIMD());
1620 // only load divisor
1621 PhysReg srcReg2
= prepXMMReg(m_as
, src2
, loc2
, rCgXMM1
);
1622 assert(srcReg2
!= rCgXMM0
);
1624 // divide by zero check
1625 m_as
.pxor(rCgXMM0
, rCgXMM0
);
1626 m_as
.ucomisd(rCgXMM0
, srcReg2
);
1627 unlikelyIfBlock(CC_NP
, [&] (Asm
& a
) {
1628 emitFwdJcc(a
, CC_E
, exit
);
1631 // now load dividend
1632 PhysReg srcReg1
= prepXMMReg(m_as
, src1
, loc1
, resReg
);
1633 assert(srcReg1
!= rCgXMM1
);
1635 emitMovRegReg(m_as
, srcReg1
, resReg
);
1636 m_as
.divsd(srcReg2
, resReg
);
1637 emitMovRegReg(m_as
, resReg
, dstReg
);
1640 void CodeGenerator::cgAndInt(IRInstruction
* inst
) {
1641 cgBinaryIntOp
<andq
,andqi
>(inst
);
1644 void CodeGenerator::cgOrInt(IRInstruction
* inst
) {
1645 cgBinaryIntOp
<orq
,orqi
>(inst
);
1648 void CodeGenerator::cgXorInt(IRInstruction
* inst
) {
1649 if (inst
->src(1)->isConst(-1)) {
1650 return cgUnaryIntOp
<not>(dstLoc(0), inst
->src(0), srcLoc(0));
1652 cgBinaryIntOp
<xorq
,xorqi
>(inst
);
1655 void CodeGenerator::cgXorBool(IRInstruction
* inst
) {
1656 cgBinaryIntOp
<xorb
,xorbi
>(inst
);
1659 void CodeGenerator::cgMod(IRInstruction
* inst
) {
1660 static_assert(rCgGP
!= rax
&& rCgGP
!= rdx
, "");
1661 auto const dstReg
= dstLoc(0).reg();
1662 auto const reg0
= srcLoc(0).reg();
1663 auto const reg1
= srcLoc(1).reg();
1666 // spill rax and/or rdx
1667 bool spillRax
= dstReg
!= rax
;
1668 bool spillRdx
= dstReg
!= rdx
;
1669 if (spillRax
) v
<< push
{rax
};
1670 if (spillRdx
) v
<< push
{rdx
};
1671 // put divisor in rCgGP if it would get clobbered
1672 auto divisor
= reg1
!= rax
&& reg1
!= rdx
? reg1
: PhysReg(rCgGP
);
1673 v
<< copy
{reg1
, divisor
};
1674 // put dividend in rax
1675 v
<< copy
{reg0
, rax
};
1676 v
<< cqo
{}; // sign-extend rax => rdx:rax
1677 v
<< idiv
{divisor
}; // rdx:rax/divisor => quot:rax, rem:rdx
1678 v
<< copy
{rdx
, dstReg
};
1679 // restore rax and/or rdx
1680 if (spillRdx
) v
<< pop
{rdx
};
1681 if (spillRax
) v
<< pop
{rax
};
1684 void CodeGenerator::cgSqrt(IRInstruction
* inst
) {
1685 auto srcReg
= srcLoc(0).reg();
1686 auto dstReg
= dstLoc(0).reg();
1688 auto tmp1
= srcReg
.isSIMD() ? Vreg(srcReg
) : v
.makeReg();
1689 auto tmp2
= dstReg
.isSIMD() ? Vreg(dstReg
) : v
.makeReg();
1690 v
<< copy
{srcReg
, tmp1
};
1691 v
<< sqrtsd
{tmp1
, tmp2
};
1692 v
<< copy
{tmp2
, dstReg
};
1695 template<class Op
, class Opi
>
1696 void CodeGenerator::cgShiftCommon(IRInstruction
* inst
) {
1697 auto const srcReg0
= srcLoc(0).reg();
1698 auto const srcReg1
= srcLoc(1).reg();
1699 auto const dstReg
= dstLoc(0).reg();
1700 assert(srcReg0
!= InvalidReg
);
1703 // one immediate (right hand side)
1704 if (srcReg1
== InvalidReg
) {
1705 v
<< copy
{srcReg0
, dstReg
};
1706 v
<< Opi
{safe_cast
<int32_t>(inst
->src(1)->intVal()), dstReg
, dstReg
};
1710 // in order to shift by a variable amount src2 must be in rcx :(
1711 bool swapRCX
= srcReg1
!= reg::rcx
;
1713 // will we be using dstReg as scratch storage?
1714 bool dstIsRHS
= dstReg
== srcReg1
;
1715 bool tmpIsRCX
= m_rScratch
== reg::rcx
;
1716 bool dstIsRCX
= dstReg
== reg::rcx
;
1718 // we need rcx for srcReg1 so we use srcReg1 as a temp for rcx, we also need
1719 // to handle the cases where the destination is rcx or src2 or both...
1720 auto resReg
= dstIsRCX
? (dstIsRHS
? PhysReg(m_rScratch
) : srcReg1
)
1721 : (dstIsRHS
? (tmpIsRCX
? dstReg
: PhysReg(m_rScratch
))
1724 // if srcReg0 was in rcx it will be swapped with srcReg1 below
1725 auto regLeft
= srcReg0
== reg::rcx
? srcReg1
: srcReg0
;
1727 // we use srcReg1 as a scratch for whatever is in rcx
1729 v
<< copy2
{reg::rcx
, srcReg1
, srcReg1
, reg::rcx
}; // emits xchgq
1732 v
<< copy
{regLeft
, resReg
};
1733 v
<< Op
{resReg
, resReg
};
1735 if (resReg
== dstReg
&& srcReg1
== dstReg
) {
1736 // If we get here it means that m_rScratch was rcx and we shouldn't do any
1737 // more swapping because we stored the result in the right place
1742 v
<< copy2
{reg::rcx
, srcReg1
, srcReg1
, reg::rcx
};
1745 // if resReg == srcReg1 then dstReg must have been rcx and the above swap
1746 // already repaired the situation
1747 if (resReg
!= srcReg1
) {
1748 v
<< copy
{resReg
, dstReg
};
1752 void CodeGenerator::cgShl(IRInstruction
* inst
) {
1753 cgShiftCommon
<shlq
,shlqi
>(inst
);
1756 void CodeGenerator::cgShr(IRInstruction
* inst
) {
1757 cgShiftCommon
<sarq
,sarqi
>(inst
);
1760 ///////////////////////////////////////////////////////////////////////////////
1761 // Comparison Operators
1762 ///////////////////////////////////////////////////////////////////////////////
1764 #define DISPATCHER(name)\
1765 int64_t ccmp_ ## name (StringData* a1, StringData* a2)\
1766 { return name(a1, a2); }\
1767 int64_t ccmp_ ## name (StringData* a1, int64_t a2)\
1768 { return name(a1, a2); }\
1769 int64_t ccmp_ ## name (StringData* a1, ObjectData* a2)\
1770 { return name(a1, Object(a2)); }\
1771 int64_t ccmp_ ## name (ObjectData* a1, ObjectData* a2)\
1772 { return name(Object(a1), Object(a2)); }\
1773 int64_t ccmp_ ## name (ObjectData* a1, int64_t a2)\
1774 { return name(Object(a1), a2); }\
1775 int64_t ccmp_ ## name (ArrayData* a1, ArrayData* a2)\
1776 { return name(Array(a1), Array(a2)); }
1785 template <typename A
, typename B
>
1786 inline int64_t ccmp_nsame(A a
, B b
) { return !ccmp_same(a
, b
); }
1788 template <typename A
, typename B
>
1789 inline int64_t ccmp_nequal(A a
, B b
) { return !ccmp_equal(a
, b
); }
1791 // TODO Task #2661083: We cannot assume that "(a <= b) === !(a > b)" for
1792 // all types. In particular, this assumption does not hold when comparing
1793 // two arrays or comparing two objects. We should fix this.
1794 template <typename A
, typename B
>
1795 inline int64_t ccmp_lte(A a
, B b
) { return !ccmp_more(a
, b
); }
1797 template <typename A
, typename B
>
1798 inline int64_t ccmp_gte(A a
, B b
) { return !ccmp_less(a
, b
); }
1800 #define CG_OP_CMP(inst, cc, name) \
1801 cgCmpHelper(inst, cc, ccmp_ ## name, ccmp_ ## name, \
1802 ccmp_ ## name, ccmp_ ## name, ccmp_ ## name, ccmp_ ## name)
1804 // SON - string, object, or number
1805 static bool typeIsSON(Type t
) {
1806 return t
.subtypeOfAny(Type::Str
, Type::Obj
, Type::Int
, Type::Dbl
);
1809 void CodeGenerator::cgCmpHelper(IRInstruction
* inst
, ConditionCode cc
,
1810 int64_t (*str_cmp_str
)(StringData
*, StringData
*),
1811 int64_t (*str_cmp_int
)(StringData
*, int64_t),
1812 int64_t (*str_cmp_obj
)(StringData
*, ObjectData
*),
1813 int64_t (*obj_cmp_obj
)(ObjectData
*, ObjectData
*),
1814 int64_t (*obj_cmp_int
)(ObjectData
*, int64_t),
1815 int64_t (*arr_cmp_arr
)(ArrayData
*, ArrayData
*)
1817 SSATmp
* src1
= inst
->src(0);
1818 SSATmp
* src2
= inst
->src(1);
1820 Type type1
= src1
->type();
1821 Type type2
= src2
->type();
1823 auto loc1
= srcLoc(0);
1824 auto loc2
= srcLoc(1);
1826 auto src1Reg
= loc1
.reg();
1827 auto src2Reg
= loc2
.reg();
1828 auto dstReg
= dstLoc(0).reg();
1831 // It is possible that some pass has been done after simplification; if such
1832 // a pass invalidates our invariants, then just punt.
1834 // simplifyCmp has done const-const optimization
1836 // If the types are the same and there is only one constant,
1837 // simplifyCmp has moved it to the right.
1838 if (src1
->isConst()) {
1839 // TODO: #3626251 will let us eliminate this punt.
1840 CG_PUNT(cgOpCmpHelper_const
);
1843 /////////////////////////////////////////////////////////////////////////////
1844 // case 1: null/string cmp string
1845 // simplifyCmp has converted the null to ""
1846 if (type1
<= Type::Str
&& type2
<= Type::Str
) {
1847 cgCallHelper(v
, CppCall::direct(str_cmp_str
), callDest(inst
),
1848 SyncOptions::kSyncPoint
, argGroup().ssa(0).ssa(1));
1851 /////////////////////////////////////////////////////////////////////////////
1852 // case 2: bool/null cmp anything
1853 // simplifyCmp has converted all args to bool
1854 else if (type1
<= Type::Bool
&& type2
<= Type::Bool
) {
1855 if (src2
->isConst()) {
1856 v
<< cmpbi
{src2
->boolVal(), src1Reg
};
1858 v
<< cmpb
{src2Reg
, src1Reg
};
1860 v
<< setcc
{cc
, dstReg
};
1863 /////////////////////////////////////////////////////////////////////////////
1864 // case 3, 4, and 7: string/resource/object/number (sron) cmp sron
1865 // These cases must be amalgamated because Type::Obj can refer to an object
1866 // or to a resource.
1867 // strings are canonicalized to the left, ints to the right
1868 else if (typeIsSON(type1
) && typeIsSON(type2
)) {
1869 if (type1
<= Type::Str
) {
1870 // string cmp string is dealt with in case 1
1871 // string cmp double is punted above
1873 if (type2
<= Type::Int
) {
1874 cgCallHelper(v
, CppCall::direct(str_cmp_int
), callDest(inst
),
1875 SyncOptions::kSyncPoint
, argGroup().ssa(0).ssa(1));
1876 } else if (type2
<= Type::Obj
) {
1877 cgCallHelper(v
, CppCall::direct(str_cmp_obj
), callDest(inst
),
1878 SyncOptions::kSyncPoint
, argGroup().ssa(0).ssa(1));
1880 CG_PUNT(cgOpCmpHelper_sx
);
1884 else if (type1
<= Type::Obj
) {
1885 // string cmp object is dealt with above
1886 // object cmp double is punted above
1888 if (type2
<= Type::Obj
) {
1889 cgCallHelper(v
, CppCall::direct(obj_cmp_obj
), callDest(inst
),
1890 SyncOptions::kSyncPoint
, argGroup().ssa(0).ssa(1));
1891 } else if (type2
<= Type::Int
) {
1892 cgCallHelper(v
, CppCall::direct(obj_cmp_int
), callDest(inst
),
1893 SyncOptions::kSyncPoint
, argGroup().ssa(0).ssa(1));
1895 CG_PUNT(cgOpCmpHelper_ox
);
1899 CG_PUNT(cgOpCmpHelper_SON
);
1903 /////////////////////////////////////////////////////////////////////////////
1904 // case 5: array cmp array
1905 else if (type1
<= Type::Arr
&& type2
<= Type::Arr
) {
1906 cgCallHelper(v
, CppCall::direct(arr_cmp_arr
),
1907 callDest(inst
), SyncOptions::kSyncPoint
, argGroup().ssa(0).ssa(1));
1910 /////////////////////////////////////////////////////////////////////////////
1911 // case 6: array cmp anything
1912 // simplifyCmp has already dealt with this case.
1914 /////////////////////////////////////////////////////////////////////////////
1916 // We have a type which is not a common type. It might be a cell or a box.
1917 CG_PUNT(cgOpCmpHelper_unimplemented
);
1921 void CodeGenerator::cgEq(IRInstruction
* inst
) {
1922 CG_OP_CMP(inst
, CC_E
, equal
);
1925 void CodeGenerator::cgEqX(IRInstruction
* inst
) {
1926 CG_OP_CMP(inst
, CC_E
, equal
);
1929 void CodeGenerator::cgNeq(IRInstruction
* inst
) {
1930 CG_OP_CMP(inst
, CC_NE
, nequal
);
1933 void CodeGenerator::cgNeqX(IRInstruction
* inst
) {
1934 CG_OP_CMP(inst
, CC_NE
, nequal
);
1937 void CodeGenerator::cgSame(IRInstruction
* inst
) {
1938 CG_OP_CMP(inst
, CC_E
, same
);
1941 void CodeGenerator::cgNSame(IRInstruction
* inst
) {
1942 CG_OP_CMP(inst
, CC_NE
, nsame
);
1945 void CodeGenerator::cgLt(IRInstruction
* inst
) {
1946 CG_OP_CMP(inst
, CC_L
, less
);
1949 void CodeGenerator::cgLtX(IRInstruction
* inst
) {
1950 CG_OP_CMP(inst
, CC_L
, less
);
1953 void CodeGenerator::cgGt(IRInstruction
* inst
) {
1954 CG_OP_CMP(inst
, CC_G
, more
);
1957 void CodeGenerator::cgGtX(IRInstruction
* inst
) {
1958 CG_OP_CMP(inst
, CC_G
, more
);
1961 void CodeGenerator::cgLte(IRInstruction
* inst
) {
1962 CG_OP_CMP(inst
, CC_LE
, lte
);
1965 void CodeGenerator::cgLteX(IRInstruction
* inst
) {
1966 CG_OP_CMP(inst
, CC_LE
, lte
);
1969 void CodeGenerator::cgGte(IRInstruction
* inst
) {
1970 CG_OP_CMP(inst
, CC_GE
, gte
);
1973 void CodeGenerator::cgGteX(IRInstruction
* inst
) {
1974 CG_OP_CMP(inst
, CC_GE
, gte
);
1977 void CodeGenerator::emitCmpInt(IRInstruction
* inst
, ConditionCode cc
) {
1978 auto dstReg
= dstLoc(0).reg();
1979 emitCompareInt(inst
);
1980 m_as
.setcc(cc
, rbyte(dstReg
));
1983 void CodeGenerator::cgEqInt(IRInstruction
* inst
) { emitCmpInt(inst
, CC_E
); }
1984 void CodeGenerator::cgNeqInt(IRInstruction
* inst
) { emitCmpInt(inst
, CC_NE
); }
1985 void CodeGenerator::cgLtInt(IRInstruction
* inst
) { emitCmpInt(inst
, CC_L
); }
1986 void CodeGenerator::cgGtInt(IRInstruction
* inst
) { emitCmpInt(inst
, CC_G
); }
1987 void CodeGenerator::cgLteInt(IRInstruction
* inst
) { emitCmpInt(inst
, CC_LE
); }
1988 void CodeGenerator::cgGteInt(IRInstruction
* inst
) { emitCmpInt(inst
, CC_GE
); }
1990 void CodeGenerator::emitCmpEqDbl(IRInstruction
* inst
, ComparisonPred pred
) {
1991 auto dstReg
= dstLoc(0).reg();
1992 auto srcReg0
= prepXMMReg(m_as
, inst
->src(0), srcLoc(0), rCgXMM0
);
1993 auto srcReg1
= prepXMMReg(m_as
, inst
->src(1), srcLoc(1), rCgXMM1
);
1995 m_as
. movsd (srcReg1
, rCgXMM2
);
1996 m_as
. cmpsd (srcReg0
, rCgXMM2
, pred
);
1997 m_as
. movq_xr (rCgXMM2
, dstReg
);
1998 m_as
. andb (1, rbyte(dstReg
));
2001 void CodeGenerator::emitCmpRelDbl(IRInstruction
* inst
, ConditionCode cc
,
2002 bool flipOperands
) {
2003 auto dstReg
= dstLoc(0).reg();
2004 auto srcReg0
= prepXMMReg(m_as
, inst
->src(0), srcLoc(0), rCgXMM0
);
2005 auto srcReg1
= prepXMMReg(m_as
, inst
->src(1), srcLoc(1), rCgXMM1
);
2008 std::swap(srcReg0
, srcReg1
);
2011 m_as
. ucomisd (srcReg0
, srcReg1
);
2012 m_as
. setcc (cc
, rbyte(dstReg
));
2015 void CodeGenerator::cgEqDbl(IRInstruction
* inst
) {
2016 emitCmpEqDbl(inst
, ComparisonPred::eq_ord
);
2018 void CodeGenerator::cgNeqDbl(IRInstruction
* inst
) {
2019 emitCmpEqDbl(inst
, ComparisonPred::ne_unord
);
2021 void CodeGenerator::cgLtDbl(IRInstruction
* inst
) {
2022 // This is a little tricky, because "unordered" is a thing.
2030 // This trick lets us avoid needing to handle the unordered case specially.
2031 // The condition codes B and BE are true if CF == 1, which it is in the
2032 // unordered case, and that'll give incorrect results. So we just invert the
2033 // condition code (A and AE don't get set if CF == 1) and flip the operands.
2034 emitCmpRelDbl(inst
, CC_A
, true);
2036 void CodeGenerator::cgGtDbl(IRInstruction
* inst
) {
2037 emitCmpRelDbl(inst
, CC_A
, false);
2039 void CodeGenerator::cgLteDbl(IRInstruction
* inst
) {
2040 emitCmpRelDbl(inst
, CC_AE
, true);
2042 void CodeGenerator::cgGteDbl(IRInstruction
* inst
) {
2043 emitCmpRelDbl(inst
, CC_AE
, false);
2046 ///////////////////////////////////////////////////////////////////////////////
2047 // Type check operators
2048 ///////////////////////////////////////////////////////////////////////////////
2050 // Overloads to put the {Object,Array}Data* into a register so
2051 // emitTypeTest can cmp to the Class*/ArrayKind expected by the
2054 // Nothing to do, return the register that contain the ObjectData already
2055 Reg64
getDataPtrEnregistered(Asm
& as
, PhysReg dataSrc
, Reg64 scratch
) {
2059 // Enregister the memoryRef so it can be used with an offset by the
2061 Reg64
getDataPtrEnregistered(Asm
& as
, MemoryRef dataSrc
, Reg64 scratch
) {
2062 as
.loadq(dataSrc
, scratch
);
2066 template<class Loc1
, class Loc2
, class JmpFn
>
2067 void CodeGenerator::emitTypeTest(Type type
, Loc1 typeSrc
, Loc2 dataSrc
,
2069 assert(!(type
<= Type::Cls
));
2071 if (type
<= Type::StaticStr
) {
2072 emitCmpTVType(m_as
, KindOfStaticString
, typeSrc
);
2074 } else if (type
<= Type::Str
) {
2075 assert(type
!= Type::CountedStr
&&
2076 "We don't support guarding on CountedStr");
2077 emitTestTVType(m_as
, KindOfStringBit
, typeSrc
);
2079 } else if (type
== Type::Null
) {
2080 emitCmpTVType(m_as
, KindOfNull
, typeSrc
);
2082 } else if (type
== Type::UncountedInit
) {
2083 emitTestTVType(m_as
, KindOfUncountedInitBit
, typeSrc
);
2085 } else if (type
== Type::Uncounted
) {
2086 emitCmpTVType(m_as
, KindOfRefCountThreshold
, typeSrc
);
2088 } else if (type
== Type::Cell
) {
2089 assert(!m_curInst
->is(LdRef
));
2090 emitCmpTVType(m_as
, KindOfRef
, typeSrc
);
2092 } else if (type
== Type::Gen
) {
2095 } else if (type
== Type::InitCell
) {
2096 assert(m_curInst
->is(LdRef
));
2097 // nothing to check: Refs cannot contain Uninit or another Ref.
2100 always_assert(type
.isKnownDataType());
2101 DataType dataType
= type
.toDataType();
2102 assert(dataType
== KindOfRef
||
2103 (dataType
>= KindOfUninit
&& dataType
<= KindOfResource
));
2104 emitCmpTVType(m_as
, dataType
, typeSrc
);
2109 if (type
.isSpecialized()) {
2110 emitSpecializedTypeTest(type
, dataSrc
, doJcc
);
2114 template<class DataLoc
, class JmpFn
>
2115 void CodeGenerator::emitSpecializedTypeTest(Type type
, DataLoc dataSrc
,
2117 assert(type
.isSpecialized());
2118 if (type
< Type::Res
) {
2119 // No cls field in Resource
2120 always_assert(0 && "unexpected guard on specialized Resource");
2123 if (type
< Type::Obj
) {
2124 // emit the specific class test
2125 assert(type
.getClass()->attrs() & AttrNoOverride
);
2126 auto reg
= getDataPtrEnregistered(m_as
, dataSrc
, m_rScratch
);
2127 emitCmpClass(m_as
, type
.getClass(), reg
[ObjectData::getVMClassOffset()]);
2130 assert(type
< Type::Arr
);
2131 auto reg
= getDataPtrEnregistered(m_as
, dataSrc
, m_rScratch
);
2132 m_as
.cmpb(type
.getArrayKind(), reg
[ArrayData::offsetofKind()]);
2137 template<class JmpFn
>
2138 void CodeGenerator::emitIsTypeTest(IRInstruction
* inst
, JmpFn doJcc
) {
2139 auto const src
= inst
->src(0);
2140 auto const loc
= srcLoc(0);
2142 // punt if specialized object for now
2143 if (inst
->typeParam() < Type::Obj
|| inst
->typeParam() < Type::Res
) {
2144 CG_PUNT(IsType
-SpecializedUnsupported
);
2147 if (src
->isA(Type::PtrToGen
)) {
2148 PhysReg base
= loc
.reg();
2149 emitTypeTest(inst
->typeParam(), base
[TVOFF(m_type
)],
2150 base
[TVOFF(m_data
)], doJcc
);
2153 assert(src
->isA(Type::Gen
));
2155 PhysReg typeSrcReg
= loc
.reg(1); // type register
2156 if (typeSrcReg
== InvalidReg
) {
2157 // Should only get here if the simplifier didn't run
2158 // TODO: #3626251 will handle this case.
2159 CG_PUNT(IsType
-KnownType
);
2161 PhysReg dataSrcReg
= loc
.reg(0); // data register
2162 emitTypeTest(inst
->typeParam(), typeSrcReg
, dataSrcReg
, doJcc
);
2166 void CodeGenerator::emitTypeCheck(Type type
,
2171 type
, typeSrc
, dataSrc
,
2172 [&](ConditionCode cc
) {
2173 emitFwdJcc(ccNegate(cc
), taken
);
2178 void CodeGenerator::emitTypeGuard(Type type
, Loc typeSrc
, Loc dataSrc
) {
2179 emitTypeTest(type
, typeSrc
, dataSrc
,
2180 [&](ConditionCode cc
) {
2181 auto const destSK
= SrcKey(curFunc(), m_unit
.bcOff(), resumed());
2182 auto const destSR
= mcg
->tx().getSrcRec(destSK
);
2183 destSR
->emitFallbackJump(m_mainCode
, ccNegate(cc
));
2187 void CodeGenerator::emitSetCc(IRInstruction
* inst
, ConditionCode cc
) {
2188 m_as
.setcc(cc
, rbyte(dstLoc(0).reg()));
2191 void CodeGenerator::cgIsTypeMemCommon(IRInstruction
* inst
, bool negate
) {
2192 bool called
= false; // check emitSetCc is called only once
2193 emitIsTypeTest(inst
,
2194 [&](ConditionCode cc
) {
2196 emitSetCc(inst
, negate
? ccNegate(cc
) : cc
);
2201 void CodeGenerator::cgIsTypeCommon(IRInstruction
* inst
, bool negate
) {
2202 bool called
= false; // check emitSetCc is called only once
2203 emitIsTypeTest(inst
,
2204 [&](ConditionCode cc
) {
2206 emitSetCc(inst
, negate
? ccNegate(cc
) : cc
);
2211 void CodeGenerator::cgJmpIsTypeCommon(IRInstruction
* inst
, bool negate
) {
2212 emitIsTypeTest(inst
,
2213 [&](ConditionCode cc
) {
2214 emitFwdJcc(negate
? ccNegate(cc
) : cc
, inst
->taken());
2218 void CodeGenerator::cgIsType(IRInstruction
* inst
) {
2219 cgIsTypeCommon(inst
, false);
2222 void CodeGenerator::cgIsScalarType(IRInstruction
* inst
) {
2223 PhysReg typeReg
= srcLoc(0).reg(1);
2224 PhysReg dstReg
= dstLoc(0).reg(0);
2226 /* static asserts for KindOfBoolean <= scalar type <= KindOfString */
2227 static_assert(KindOfUninit
< KindOfBoolean
, "fix checks for IsScalar");
2228 static_assert(KindOfNull
< KindOfBoolean
, "fix checks for IsScalar");
2229 static_assert(KindOfInt64
> KindOfBoolean
, "fix checks for IsScalar");
2230 static_assert(KindOfDouble
> KindOfBoolean
, "fix checks for IsScalar");
2231 static_assert(KindOfStaticString
> KindOfBoolean
, "fix checks for IsScalar");
2232 static_assert(KindOfString
> KindOfBoolean
, "fix checks for IsScalar");
2234 static_assert(KindOfInt64
< KindOfString
, "fix checks for IsScalar");
2235 static_assert(KindOfDouble
< KindOfString
, "fix checks for IsScalar");
2236 static_assert(KindOfStaticString
< KindOfString
, "fix checks for IsScalar");
2237 static_assert(KindOfArray
> KindOfString
, "fix checks for IsScalar");
2238 static_assert(KindOfObject
> KindOfString
, "fix checks for IsScalar");
2239 static_assert(KindOfResource
> KindOfString
, "fix checks for IsScalar");
2241 static_assert(sizeof(DataType
) == 1, "");
2243 if (typeReg
== InvalidReg
) {
2244 auto const type
= inst
->src(0)->type();
2245 auto const imm
= type
<= (Type::Bool
| Type::Int
| Type::Dbl
| Type::Str
);
2246 m_as
. movl(Immed(imm
), r32(dstReg
));
2250 m_as
. movzbl(rbyte(typeReg
), r32(dstReg
));
2251 m_as
. subl(KindOfBoolean
, r32(dstReg
));
2252 m_as
. subl(KindOfString
- KindOfBoolean
+ 1, r32(dstReg
));
2253 m_as
. sbbl(r32(dstReg
), r32(dstReg
));
2257 void CodeGenerator::cgIsNType(IRInstruction
* inst
) {
2258 cgIsTypeCommon(inst
, true);
2261 void CodeGenerator::cgIsTypeMem(IRInstruction
* inst
) {
2262 cgIsTypeMemCommon(inst
, false);
2265 void CodeGenerator::cgIsNTypeMem(IRInstruction
* inst
) {
2266 cgIsTypeMemCommon(inst
, true);
2269 ///////////////////////////////////////////////////////////////////////////////
2272 * Check instanceof using instance bitmasks.
2274 * Note it's not necessary to check whether the test class is defined:
2275 * if it doesn't exist than the candidate can't be an instance of it
2276 * and will fail this check.
2278 void CodeGenerator::emitInstanceBitmaskCheck(Vout
& v
, IRInstruction
* inst
) {
2279 auto const rObjClass
= srcLoc(0).reg(0);
2280 auto const testClassName
= inst
->src(1)->strVal();
2283 if (!InstanceBits::getMask(testClassName
, offset
, mask
)) {
2284 always_assert(!"cgInstanceOfBitmask had no bitmask");
2286 v
<< testbim
{int8_t(mask
), rObjClass
[offset
]};
2289 void CodeGenerator::emitInstanceBitmaskCheck(IRInstruction
* inst
) {
2290 emitInstanceBitmaskCheck(Vauto().main(m_as
), inst
);
2293 void CodeGenerator::cgInstanceOfBitmask(IRInstruction
* inst
) {
2295 emitInstanceBitmaskCheck(v
, inst
);
2296 v
<< setcc
{CC_NZ
, dstLoc(0).reg()};
2299 void CodeGenerator::cgNInstanceOfBitmask(IRInstruction
* inst
) {
2301 emitInstanceBitmaskCheck(v
, inst
);
2302 v
<< setcc
{CC_Z
, dstLoc(0).reg()};
2305 void CodeGenerator::cgJmpInstanceOfBitmask(IRInstruction
* inst
) {
2306 emitInstanceBitmaskCheck(inst
);
2307 emitFwdJcc(CC_NZ
, inst
->taken());
2310 void CodeGenerator::cgJmpNInstanceOfBitmask(IRInstruction
* inst
) {
2311 emitInstanceBitmaskCheck(inst
);
2312 emitFwdJcc(CC_Z
, inst
->taken());
2315 void CodeGenerator::cgReqBindJmpInstanceOfBitmask(IRInstruction
* inst
) {
2317 emitInstanceBitmaskCheck(v
, inst
);
2318 emitReqBindJcc(v
, opToConditionCode(inst
->op()),
2319 inst
->extra
<ReqBindJccData
>());
2322 void CodeGenerator::cgReqBindJmpNInstanceOfBitmask(IRInstruction
* inst
) {
2324 emitInstanceBitmaskCheck(v
, inst
);
2325 emitReqBindJcc(v
, opToConditionCode(inst
->op()),
2326 inst
->extra
<ReqBindJccData
>());
2329 void CodeGenerator::cgSideExitJmpInstanceOfBitmask(IRInstruction
* inst
) {
2330 auto const extra
= inst
->extra
<SideExitJccData
>();
2331 auto const sk
= SrcKey(curFunc(), extra
->taken
, resumed());
2333 emitInstanceBitmaskCheck(v
, inst
);
2334 v
<< bindexit
{opToConditionCode(inst
->op()), sk
, extra
->trflags
};
2337 void CodeGenerator::cgSideExitJmpNInstanceOfBitmask(IRInstruction
* inst
) {
2338 auto const extra
= inst
->extra
<SideExitJccData
>();
2339 auto const sk
= SrcKey(curFunc(), extra
->taken
, resumed());
2341 emitInstanceBitmaskCheck(v
, inst
);
2342 v
<< bindexit
{opToConditionCode(inst
->op()), sk
, extra
->trflags
};
2345 void CodeGenerator::cgInstanceOf(IRInstruction
* inst
) {
2346 auto testReg
= srcLoc(1).reg();
2347 auto destReg
= dstLoc(0).reg();
2350 if (testReg
== InvalidReg
) {
2351 // Don't need to do the null check when the class is const.
2352 assert(inst
->src(1)->clsVal() != nullptr);
2353 cgCallNative(v
, inst
);
2357 v
<< testq
{testReg
, testReg
};
2358 ifThenElse(v
, CC_NZ
,
2360 cgCallNative(v
, inst
);
2363 // testReg == 0, set dest to false (0)
2364 v
<< copy
{testReg
, destReg
};
2370 * Check instanceof using the superclass vector on the end of the
2373 void CodeGenerator::cgExtendsClass(IRInstruction
* inst
) {
2374 auto const rObjClass
= srcLoc(0).reg();
2375 auto const testClass
= inst
->src(1)->clsVal();
2376 auto rTestClass
= srcLoc(1).reg();
2377 auto const rdst
= rbyte(dstLoc(0).reg());
2384 if (rTestClass
== InvalidReg
) { // TODO(#2031606)
2385 rTestClass
= m_rScratch
; // careful below about asm-x64 smashing this
2386 emitLoadImm(a
, (int64_t)testClass
, rTestClass
);
2389 // Test if it is the exact same class. TODO(#2044801): we should be
2390 // doing this control flow at the IR level.
2391 if (!(testClass
->attrs() & AttrAbstract
)) {
2392 emitCmpClass(a
, rTestClass
, rObjClass
);
2393 // If the test class cannot be extended, we only need to do the
2395 if (testClass
->attrs() & AttrNoOverride
) {
2404 auto const vecOffset
= Class::classVecOff() +
2405 sizeof(LowClassPtr
) * (testClass
->classVecLen() - 1);
2407 // Check the length of the class vectors---if the candidate's is at
2408 // least as long as the potential base (testClass) it might be a
2410 asm_label(a
, notExact
);
2411 a
. cmpl (safe_cast
<int32_t>(testClass
->classVecLen()),
2412 rObjClass
[Class::classVecLenOff()]);
2413 a
. jb8 (falseLabel
);
2415 // If it's a subclass, rTestClass must be at the appropriate index.
2416 emitCmpClass(a
, rTestClass
, rObjClass
[vecOffset
]);
2420 asm_label(a
, falseLabel
);
2421 a
. xorl (r32(rdst
), r32(rdst
));
2426 void CodeGenerator::cgConvDblToInt(IRInstruction
* inst
) {
2427 Vout
& vmain
= this->vmain();
2428 Vout
& vcold
= this->vcold();
2430 auto src
= inst
->src(0);
2431 auto srcReg
= prepXMM(vmain
, src
, srcLoc(0));
2432 auto dstReg
= dstLoc(0).reg();
2434 constexpr uint64_t maxULongAsDouble
= 0x43F0000000000000LL
;
2435 constexpr uint64_t maxLongAsDouble
= 0x43E0000000000000LL
;
2437 auto rIndef
= vmain
.makeReg();
2438 vmain
<< ldimm
{0x8000000000000000LL
, rIndef
};
2439 vmain
<< cvttsd2siq
{srcReg
, dstReg
};
2440 vmain
<< cmpq
{rIndef
, dstReg
};
2442 unlikelyIfBlock(vmain
, vcold
, CC_E
, [&] (Vout
& v
) {
2443 // result > max signed int or unordered
2444 auto tmp
= v
.makeReg();
2446 v
<< ucomisd
{tmp
, srcReg
};
2447 ifThen(v
, CC_B
, [&](Vout
& v
) {
2448 // src0 > 0 (CF = 1 -> less than 0 or unordered)
2449 ifThen(v
, CC_NP
, [&](Vout
& v
) {
2450 auto max_ulong
= v
.makeReg();
2451 v
<< ldimm
{maxULongAsDouble
, max_ulong
};
2452 v
<< ucomisd
{max_ulong
, srcReg
};
2453 ifThenElse(v
, CC_B
, [&](Vout
& v
) {
2455 v
<< ldimm
{0, dstReg
};
2457 // 0 < src0 <= ULONG_MAX
2458 auto max_long
= v
.makeReg();
2459 v
<< ldimm
{maxLongAsDouble
, max_long
};
2461 // we know that LONG_MAX < src0 <= UINT_MAX, therefore,
2462 // 0 < src0 - ULONG_MAX <= LONG_MAX
2463 auto tmp_sub
= v
.makeReg();
2464 v
<< subsd
{max_long
, srcReg
, tmp_sub
};
2465 v
<< cvttsd2siq
{tmp_sub
, dstReg
};
2467 // We want to simulate integer overflow so we take the resulting
2468 // integer and flip its sign bit (NB: we don't use orq here
2469 // because it's possible that src0 == LONG_MAX in which case
2470 // cvttsd2siq will yeild an indefiniteInteger, which we would
2471 // like to make zero)
2472 v
<< xorq
{rIndef
, dstReg
, dstReg
};
2479 void CodeGenerator::cgConvDblToBool(IRInstruction
* inst
) {
2480 auto dstReg
= dstLoc(0).reg();
2481 auto srcReg
= srcLoc(0).reg();
2482 emitMovRegReg(m_as
, srcReg
, dstReg
);
2483 m_as
.shlq(1, dstReg
); // 0.0 stays zero and -0.0 is now 0.0
2484 m_as
.setne(rbyte(dstReg
)); // lower byte becomes 1 if dstReg != 0
2485 m_as
.movzbl(rbyte(dstReg
), r32(dstReg
));
2488 void CodeGenerator::cgConvIntToBool(IRInstruction
* inst
) {
2489 auto dstReg
= dstLoc(0).reg();
2490 auto srcReg
= srcLoc(0).reg();
2491 m_as
.testq(srcReg
, srcReg
);
2492 m_as
.setne(rbyte(dstReg
));
2493 m_as
.movzbl(rbyte(dstReg
), r32(dstReg
));
2496 void CodeGenerator::cgConvArrToBool(IRInstruction
* inst
) {
2497 auto dstReg
= dstLoc(0).reg();
2498 auto srcReg
= srcLoc(0).reg();
2500 // This will incorrectly result in "true" for a NameValueTableWrapper that is
2501 // empty. You can only get such a thing through very contrived PHP, so the
2502 // savings of a branch and a block of cold code outweights the edge-case bug.
2503 m_as
. cmpl (0, srcReg
[ArrayData::offsetofSize()]);
2504 m_as
. setcc (CC_NZ
, rbyte(dstReg
));
2508 * emit something equivalent to testl(val, mr),
2509 * but with a shorter encoding (eg testb(val, mr))
2512 void testimm(Asm
& as
, uint32_t val
, MemoryRef mr
) {
2515 while (v
> 0xff && !(v
& 0xff)) {
2520 as
.testl((int32_t)val
, mr
);
2522 as
.testb((int8_t)v
, *(mr
.r
+ off
));
2526 void CodeGenerator::cgColIsEmpty(IRInstruction
* inst
) {
2527 DEBUG_ONLY
auto const ty
= inst
->src(0)->type();
2528 assert(ty
< Type::Obj
&&
2530 ty
.getClass()->isCollectionClass());
2532 a
. cmpl (0, srcLoc(0).reg()[FAST_COLLECTION_SIZE_OFFSET
]);
2533 a
. sete (rbyte(dstLoc(0).reg()));
2536 void CodeGenerator::cgColIsNEmpty(IRInstruction
* inst
) {
2537 DEBUG_ONLY
auto const ty
= inst
->src(0)->type();
2538 assert(ty
< Type::Obj
&&
2540 ty
.getClass()->isCollectionClass());
2542 a
. cmpl (0, srcLoc(0).reg()[FAST_COLLECTION_SIZE_OFFSET
]);
2543 a
. setne (rbyte(dstLoc(0).reg()));
2546 void CodeGenerator::cgConvObjToBool(IRInstruction
* inst
) {
2547 auto const rdst
= dstLoc(0).reg();
2548 auto const rsrc
= srcLoc(0).reg();
2551 testimm(a
, ObjectData::CallToImpl
, rsrc
[ObjectData::attributeOff()]);
2556 ObjectData::IsCollection
,
2557 rsrc
[ObjectData::attributeOff()]);
2561 [&] (Asm
& a
) { // rsrc points to native collection
2562 a
.cmpl(0, rsrc
[FAST_COLLECTION_SIZE_OFFSET
]);
2563 a
.setne(rbyte(rdst
)); // true iff size not zero
2565 [&] (Asm
& a
) { // rsrc is not a native collection
2568 CppCall::method(&ObjectData::o_toBoolean
),
2570 SyncOptions::kSyncPoint
,
2576 a
. movb (1, rbyte(rdst
));
2581 void CodeGenerator::emitConvBoolOrIntToDbl(IRInstruction
* inst
) {
2582 SSATmp
* src
= inst
->src(0);
2583 assert(src
->isA(Type::Bool
) || src
->isA(Type::Int
));
2584 auto dstReg
= dstLoc(0).reg();
2585 auto srcReg
= srcLoc(0).reg();
2586 // cvtsi2sd doesn't modify the high bits of its target, which can
2587 // cause false dependencies to prevent register renaming from kicking
2588 // in. Break the dependency chain by zeroing out the XMM reg.
2590 zeroExtendIfBool(v
, src
, srcReg
);
2591 auto tmp_dbl
= v
.makeReg();
2592 v
<< cvtsi2sd
{srcReg
, tmp_dbl
};
2593 v
<< copy
{tmp_dbl
, dstReg
};
2596 void CodeGenerator::cgConvBoolToDbl(IRInstruction
* inst
) {
2597 emitConvBoolOrIntToDbl(inst
);
2600 void CodeGenerator::cgConvIntToDbl(IRInstruction
* inst
) {
2601 emitConvBoolOrIntToDbl(inst
);
2604 void CodeGenerator::cgConvBoolToInt(IRInstruction
* inst
) {
2605 auto dstReg
= dstLoc(0).reg();
2606 auto srcReg
= srcLoc(0).reg();
2607 m_as
.movzbl(rbyte(srcReg
), r32(dstReg
));
2610 void CodeGenerator::cgConvBoolToStr(IRInstruction
* inst
) {
2611 auto dstReg
= dstLoc(0).reg();
2612 auto srcReg
= srcLoc(0).reg();
2613 m_as
.testb(rbyte(srcReg
), rbyte(srcReg
));
2614 m_as
.movq(makeStaticString(""), dstReg
);
2615 m_as
.movq(makeStaticString("1"), m_rScratch
);
2616 m_as
.cmov_reg64_reg64(CC_NZ
, m_rScratch
, dstReg
);
2619 void CodeGenerator::cgConvClsToCctx(IRInstruction
* inst
) {
2620 auto const sreg
= srcLoc(0).reg();
2621 auto const dreg
= dstLoc(0).reg();
2623 emitMovRegReg(a
, sreg
, dreg
);
2627 void CodeGenerator::cgUnboxPtr(IRInstruction
* inst
) {
2628 auto srcReg
= srcLoc(0).reg();
2629 auto dstReg
= dstLoc(0).reg();
2630 assert(srcReg
!= InvalidReg
);
2631 emitMovRegReg(m_as
, srcReg
, dstReg
);
2632 emitDerefIfVariant(m_as
, PhysReg(dstReg
));
2635 void CodeGenerator::cgLdFuncCachedCommon(IRInstruction
* inst
) {
2636 auto const dst
= dstLoc(0).reg();
2637 auto const name
= inst
->extra
<LdFuncCachedData
>()->name
;
2638 auto const ch
= NamedEntity::get(name
)->getFuncHandle();
2641 if (dst
== InvalidReg
) {
2642 a
. cmpq (0, rVmTl
[ch
]);
2644 a
. loadq (rVmTl
[ch
], dst
);
2645 a
. testq (dst
, dst
);
2649 void CodeGenerator::cgLdFuncCached(IRInstruction
* inst
) {
2650 cgLdFuncCachedCommon(inst
);
2651 unlikelyIfBlock(CC_Z
, [&] (Asm
& a
) {
2652 const Func
* (*const func
)(const StringData
*) = lookupUnknownFunc
;
2655 CppCall::direct(func
),
2657 SyncOptions::kSyncPoint
,
2659 .immPtr(inst
->extra
<LdFuncCached
>()->name
)
2664 void CodeGenerator::cgLdFuncCachedSafe(IRInstruction
* inst
) {
2665 cgLdFuncCachedCommon(inst
);
2666 if (auto const taken
= inst
->taken()) {
2667 emitFwdJcc(m_as
, CC_Z
, taken
);
2671 void CodeGenerator::cgLdFuncCachedU(IRInstruction
* inst
) {
2672 auto const dstReg
= dstLoc(0).reg();
2673 auto const extra
= inst
->extra
<LdFuncCachedU
>();
2674 auto const hFunc
= NamedEntity::get(extra
->name
)->getFuncHandle();
2678 // Check the first function handle, otherwise try to autoload.
2679 if (dstReg
== InvalidReg
) {
2680 a
. cmpq (0, rVmTl
[hFunc
]);
2682 a
. loadq (rVmTl
[hFunc
], dstReg
);
2683 a
. testq (dstReg
, dstReg
);
2686 unlikelyIfBlock(CC_Z
, [&] (Asm
& a
) {
2687 // If we get here, things are going to be slow anyway, so do all the
2688 // autoloading logic in lookupFallbackFunc instead of ASM
2689 const Func
* (*const func
)(const StringData
*, const StringData
*) =
2693 CppCall::direct(func
),
2695 SyncOptions::kSyncPoint
,
2697 .immPtr(extra
->name
)
2698 .immPtr(extra
->fallback
)
2703 void CodeGenerator::cgLdFunc(IRInstruction
* inst
) {
2704 auto const ch
= FuncCache::alloc();
2705 RDS::recordRds(ch
, sizeof(FuncCache
),
2706 "FuncCache", curFunc()->fullName()->data());
2708 // raises an error if function not found
2710 CppCall::direct(FuncCache::lookup
),
2711 callDest(dstLoc(0).reg()),
2712 SyncOptions::kSyncPoint
,
2713 argGroup().imm(ch
).ssa(0/*methodName*/));
2716 void CodeGenerator::cgLdObjClass(IRInstruction
* inst
) {
2717 auto dstReg
= dstLoc(0).reg();
2718 auto objReg
= srcLoc(0).reg();
2719 emitLdObjClass(m_as
, objReg
, dstReg
);
2722 void CodeGenerator::cgLdObjMethod(IRInstruction
* inst
) {
2723 assert(inst
->taken() && inst
->taken()->isCatch()); // must have catch block
2724 using namespace MethodCache
;
2726 auto const clsReg
= srcLoc(0).reg();
2727 auto const actRecReg
= srcLoc(1).reg();
2728 auto const extra
= inst
->extra
<LdObjMethodData
>();
2731 auto const handle
= RDS::alloc
<Entry
, sizeof(Entry
)>().handle();
2732 if (RuntimeOption::EvalPerfDataMap
) {
2733 auto const caddr_hand
= reinterpret_cast<char*>(
2734 static_cast<intptr_t>(handle
)
2736 Debug::DebugInfo::recordDataMap(
2738 caddr_hand
+ sizeof(TypedValue
),
2739 folly::format("rds+MethodCache-{}",
2740 curFunc()->fullName()->data()).str());
2743 auto const mcHandler
= extra
->fatal
? handlePrimeCacheInit
<true>
2744 : handlePrimeCacheInit
<false>;
2746 auto fast_path
= v
.makeBlock();
2747 auto slow_path
= v
.makeBlock();
2748 auto done
= v
.makeBlock();
2751 * Inline cache: we "prime" the cache across requests by smashing
2752 * this immediate to hold a Func* in the upper 32 bits, and a Class*
2753 * in the lower 32 bits. (If both are low-malloced pointers can
2754 * fit.) See pmethodCacheMissPath.
2757 v
<< movq
{rAsm
, m_rScratch
};
2758 v
<< movl
{Vreg
{rAsm
}, Vreg
{rAsm
}}; // zeros the top 32 bits
2759 v
<< cmpq
{rAsm
, clsReg
};
2760 v
<< jcc
{CC_NE
, {fast_path
, slow_path
}};
2763 v
<< shrqi
{32, m_rScratch
, m_rScratch
};
2764 v
<< storeq
{m_rScratch
, actRecReg
[AROFF(m_func
)]};
2769 CppCall::direct(mcHandler
),
2771 SyncOptions::kSmashableAndSyncPoint
,
2773 .addr(rVmTl
, safe_cast
<int32_t>(handle
))
2775 .immPtr(extra
->method
)
2778 // The scratch reg contains the prime data before we've smashed the call
2779 // to handleSlowPath. After, it contains the primed Class/Func pair.
2786 void CodeGenerator::cgLdObjInvoke(IRInstruction
* inst
) {
2787 auto const rsrc
= srcLoc(0).reg();
2788 auto const rdst
= dstLoc(0).reg();
2791 a
. loadq (rsrc
[Class::invokeOff()], rdst
);
2792 a
. testq (rdst
, rdst
);
2793 emitFwdJcc (a
, CC_Z
, inst
->taken());
2796 void CodeGenerator::cgStRetVal(IRInstruction
* inst
) {
2797 auto const rFp
= srcLoc(0).reg();
2798 auto* const val
= inst
->src(1);
2799 cgStore(rFp
[AROFF(m_r
)], val
, srcLoc(1), Width::Full
);
2802 void CodeGenerator::cgRetAdjustStack(IRInstruction
* inst
) {
2803 auto const rFp
= srcLoc(0).reg();
2804 auto const dstSp
= dstLoc(0).reg();
2806 a
. lea (rFp
[AROFF(m_r
)], dstSp
);
2809 void CodeGenerator::cgLdRetAddr(IRInstruction
* inst
) {
2810 auto fpReg
= srcLoc(0).reg(0);
2811 assert(fpReg
!= InvalidReg
);
2812 m_as
.push(fpReg
[AROFF(m_savedRip
)]);
2815 void traceRet(ActRec
* fp
, Cell
* sp
, void* rip
) {
2816 if (rip
== mcg
->tx().uniqueStubs
.callToExit
) {
2819 checkFrame(fp
, sp
, /*fullCheck*/ false, 0);
2820 assert(sp
<= (Cell
*)fp
|| fp
->resumed());
2821 // check return value if stack not empty
2822 if (sp
< (Cell
*)fp
) assertTv(sp
);
2825 void CodeGenerator::emitTraceRet(Asm
& a
) {
2826 // call to a trace function
2827 a
. movq (rVmFp
, rdi
);
2828 a
. movq (rVmSp
, rsi
);
2829 a
. loadq (*rsp
, rdx
); // return ip from native stack
2830 // do the call; may use a trampoline
2831 emitCall(a
, TCA(traceRet
));
2834 void CodeGenerator::cgRetCtrl(IRInstruction
* inst
) {
2835 // Make sure rVmFp and rVmSp are set appropriately
2836 emitMovRegReg(m_as
, srcLoc(0/*sp*/).reg(), rVmSp
);
2837 emitMovRegReg(m_as
, srcLoc(1/*fp*/).reg(), rVmFp
);
2839 // Return control to caller
2840 if (RuntimeOption::EvalHHIRGenerateAsserts
) {
2845 if (RuntimeOption::EvalHHIRGenerateAsserts
) {
2850 void CodeGenerator::emitReqBindAddr(TCA
& dest
,
2852 mcg
->setJmpTransID((TCA
)&dest
);
2854 dest
= emitEphemeralServiceReq(m_frozenCode
,
2855 mcg
->getFreeStub(m_frozenCode
,
2860 TransFlags
{}.packed
);
2861 mcg
->cgFixups().m_codePointers
.insert(&dest
);
2864 void CodeGenerator::cgLdBindAddr(IRInstruction
* inst
) {
2865 auto data
= inst
->extra
<LdBindAddr
>();
2866 auto dstReg
= dstLoc(0).reg();
2868 // Emit service request to smash address of SrcKey into 'addr'.
2869 TCA
* addrPtr
= mcg
->allocData
<TCA
>(sizeof(TCA
), 1);
2870 emitReqBindAddr(*addrPtr
, data
->sk
);
2872 // Load the maybe bound address.
2873 auto addr
= intptr_t(addrPtr
);
2874 // the tc/global data is intentionally layed out to guarantee
2875 // rip-relative addressing will work.
2876 // Also, a rip-relative load, is 1 byte smaller than the corresponding
2878 m_as
.loadq(rip
[addr
], dstReg
);
2881 void CodeGenerator::cgJmpSwitchDest(IRInstruction
* inst
) {
2882 JmpSwitchData
* data
= inst
->extra
<JmpSwitchDest
>();
2883 SSATmp
* index
= inst
->src(0);
2884 auto indexReg
= srcLoc(0).reg();
2885 auto rTmp
= m_rScratch
;
2887 if (!index
->isConst()) {
2888 if (data
->bounded
) {
2890 if (deltaFits(data
->base
, sz::dword
)) {
2891 m_as
. subq(safe_cast
<int32_t>(data
->base
), indexReg
);
2893 m_as
. emitImmReg(data
->base
, rTmp
);
2894 m_as
. subq(rTmp
, indexReg
);
2897 m_as
. cmpq(data
->cases
- 2, indexReg
);
2898 mcg
->backEnd().prepareForSmash(m_mainCode
, kJmpccLen
);
2899 TCA def
= emitEphemeralServiceReq(
2901 mcg
->getFreeStub(m_frozenCode
, &mcg
->cgFixups()),
2902 REQ_BIND_JMPCC_SECOND
,
2903 RipRelative(m_as
.frontier()),
2906 mcg
->setJmpTransID(m_as
.frontier());
2911 TCA
* table
= mcg
->allocData
<TCA
>(sizeof(TCA
), data
->cases
);
2912 m_as
. lea(rip
[(intptr_t)table
], m_rScratch
);
2913 assert(((int32_t*)m_as
.frontier())[-1] + m_as
.frontier() == (TCA
)table
);
2914 m_as
. jmp(m_rScratch
[indexReg
*8]);
2916 for (int i
= 0; i
< data
->cases
; i
++) {
2917 emitReqBindAddr(table
[i
], SrcKey(curFunc(), data
->targets
[i
], resumed()));
2920 int64_t indexVal
= index
->intVal();
2922 if (data
->bounded
) {
2923 indexVal
-= data
->base
;
2924 if (indexVal
>= data
->cases
- 2 || indexVal
< 0) {
2925 emitBindJmp(m_mainCode
, m_frozenCode
,
2926 SrcKey(curFunc(), data
->defaultOff
, resumed()));
2930 emitBindJmp(m_mainCode
, m_frozenCode
,
2931 SrcKey(curFunc(), data
->targets
[indexVal
], resumed()));
2935 void CodeGenerator::cgLdSSwitchDestFast(IRInstruction
* inst
) {
2936 auto data
= inst
->extra
<LdSSwitchDestFast
>();
2938 auto table
= mcg
->allocData
<SSwitchMap
>(64);
2939 new (table
) SSwitchMap(data
->numCases
);
2940 for (int64_t i
= 0; i
< data
->numCases
; ++i
) {
2941 table
->add(data
->cases
[i
].str
, nullptr);
2942 TCA
* addr
= table
->find(data
->cases
[i
].str
);
2943 emitReqBindAddr(*addr
, SrcKey(curFunc(), data
->cases
[i
].dest
, resumed()));
2945 TCA
* def
= mcg
->allocData
<TCA
>(sizeof(TCA
), 1);
2946 emitReqBindAddr(*def
, SrcKey(curFunc(), data
->defaultOff
, resumed()));
2949 CppCall::direct(sswitchHelperFast
),
2951 SyncOptions::kNoSyncPoint
,
2958 static TCA
sswitchHelperSlow(TypedValue typedVal
,
2959 const StringData
** strs
,
2962 Cell
* cell
= tvToCell(&typedVal
);
2963 for (int i
= 0; i
< numStrs
; ++i
) {
2964 if (cellEqual(*cell
, strs
[i
])) return jmptab
[i
];
2966 return jmptab
[numStrs
]; // default case
2969 void CodeGenerator::cgLdSSwitchDestSlow(IRInstruction
* inst
) {
2970 auto data
= inst
->extra
<LdSSwitchDestSlow
>();
2972 auto strtab
= mcg
->allocData
<const StringData
*>(
2973 sizeof(const StringData
*), data
->numCases
);
2974 auto jmptab
= mcg
->allocData
<TCA
>(sizeof(TCA
), data
->numCases
+ 1);
2975 for (int i
= 0; i
< data
->numCases
; ++i
) {
2976 strtab
[i
] = data
->cases
[i
].str
;
2977 emitReqBindAddr(jmptab
[i
],
2978 SrcKey(curFunc(), data
->cases
[i
].dest
, resumed()));
2980 emitReqBindAddr(jmptab
[data
->numCases
],
2981 SrcKey(curFunc(), data
->defaultOff
, resumed()));
2984 CppCall::direct(sswitchHelperSlow
),
2986 SyncOptions::kSyncPoint
,
2990 .imm(data
->numCases
)
2995 * It'd be nice not to have the cgMov here (and just copy propagate
2996 * the source or something), but for now we're keeping it allocated to
2997 * rVmFp so inlined calls to C++ helpers that use the rbp chain to
2998 * find the caller's ActRec will work correctly.
3000 * This instruction primarily exists to assist in optimizing away
3001 * unused activation records, so it's usually not going to happen
3004 void CodeGenerator::cgDefInlineFP(IRInstruction
* inst
) {
3005 auto const calleeFP
= srcLoc(0).reg();
3006 auto const callerFP
= srcLoc(2).reg();
3007 auto const fakeRet
= mcg
->tx().uniqueStubs
.retInlHelper
;
3008 auto const retBCOff
= inst
->extra
<DefInlineFP
>()->retBCOff
;
3010 m_as
. storeq (callerFP
, calleeFP
[AROFF(m_sfp
)]);
3011 emitImmStoreq(m_as
, intptr_t(fakeRet
), calleeFP
[AROFF(m_savedRip
)]);
3012 m_as
. storel (retBCOff
, calleeFP
[AROFF(m_soff
)]);
3016 void CodeGenerator::cgInlineReturn(IRInstruction
* inst
) {
3017 auto fpReg
= srcLoc(0).reg();
3018 assert(fpReg
== rVmFp
);
3019 m_as
. loadq (fpReg
[AROFF(m_sfp
)], rVmFp
);
3022 void CodeGenerator::cgReDefSP(IRInstruction
* inst
) {
3023 // TODO(#2288359): this instruction won't be necessary (for
3024 // non-generator frames) when we don't track rVmSp independently
3025 // from rVmFp. In generator frames we'll have to track offsets from
3026 // a DefResumableSP or something similar.
3027 auto fp
= srcLoc(1).reg();
3028 auto dst
= dstLoc(0).reg();
3029 auto off
= -inst
->extra
<ReDefSP
>()->spOffset
* sizeof(Cell
);
3030 emitLea(m_as
, fp
[off
], dst
);
3033 void CodeGenerator::cgFreeActRec(IRInstruction
* inst
) {
3034 auto ptr
= srcLoc(0).reg();
3035 auto off
= AROFF(m_sfp
);
3036 auto dst
= dstLoc(0).reg();
3037 m_as
.loadq(ptr
[off
], dst
);
3040 void emitSpill(Asm
& as
, const PhysLoc
& s
, const PhysLoc
& d
, Type t
) {
3041 assert(s
.numWords() == d
.numWords());
3042 assert(!s
.spilled() && d
.spilled());
3043 if (s
.isFullSIMD()) {
3044 as
.movdqu(s
.reg(0), reg::rsp
[d
.offset(0)]);
3046 for (int i
= 0, n
= s
.numAllocated(); i
< n
; ++i
) {
3047 // store the whole register even if it holds a bool or DataType
3048 emitStoreReg(as
, s
.reg(i
), reg::rsp
[d
.offset(i
)]);
3053 void emitReload(Asm
& as
, const PhysLoc
& s
, const PhysLoc
& d
, Type t
) {
3054 assert(s
.numWords() == d
.numWords());
3055 assert(s
.spilled() && !d
.spilled());
3056 if (d
.isFullSIMD()) {
3057 as
.movdqu(reg::rsp
[s
.offset(0)], d
.reg(0));
3059 for (int i
= 0, n
= d
.numAllocated(); i
< n
; ++i
) {
3060 // load the whole register even if it holds a bool or DataType
3061 emitLoadReg(as
, reg::rsp
[s
.offset(i
)], d
.reg(i
));
3066 void CodeGenerator::cgShuffle(IRInstruction
* inst
) {
3067 // Each destination is unique, there are no mem-mem copies, and
3068 // there are no cycles involving spill slots. So do the shuffling
3070 // 1. reg->mem (stores)
3071 // 2. reg->reg (parallel copies)
3072 // 3. mem->reg (loads) & imm->reg (constants)
3073 PhysReg::Map
<PhysReg
> moves
; // moves[dst] = src
3074 for (uint32_t i
= 0, n
= inst
->numSrcs(); i
< n
; ++i
) {
3075 auto& rd
= inst
->extra
<Shuffle
>()->dests
[i
];
3076 if (rd
.numAllocated() == 0) continue; // ignore unused dests.
3077 auto src
= inst
->src(i
);
3078 auto rs
= srcLoc(i
);
3080 emitSpill(m_as
, rs
, rd
, src
->type());
3081 } else if (!rs
.spilled()) {
3082 auto s0
= rs
.reg(0);
3083 auto d0
= rd
.reg(0);
3084 if (s0
!= InvalidReg
) moves
[d0
] = s0
;
3085 auto s1
= rs
.reg(1);
3086 auto d1
= rd
.reg(1);
3087 if (s1
!= InvalidReg
) moves
[d1
] = s1
;
3090 // Compute a serial order of moves and swaps. We can use m_rScratch
3091 // here since cgShuffle is a standalone HHIR instruction, and sometimes
3092 // its a low-numbered register.
3093 auto rTmp
= m_rScratch
;
3094 auto howTo
= doRegMoves(moves
, rTmp
);
3095 for (auto& how
: howTo
) {
3096 if (how
.m_kind
== MoveInfo::Kind::Move
) {
3097 emitMovRegReg(m_as
, how
.m_src
, how
.m_dst
);
3099 // do swap - only support GPRs
3100 assert(how
.m_src
.isGP() && how
.m_dst
.isGP());
3101 m_as
.xchgq(how
.m_src
, how
.m_dst
);
3104 // now do reg<-mem loads and reg<-imm moves. We have already
3105 // dealt with stores, moves, and swaps, so all rTmp is available
3106 // even if it originally contained a value.
3107 for (uint32_t i
= 0, n
= inst
->numSrcs(); i
< n
; ++i
) {
3108 auto src
= inst
->src(i
);
3109 auto rs
= srcLoc(i
);
3110 auto& rd
= inst
->extra
<Shuffle
>()->dests
[i
];
3111 if (rd
.numAllocated() == 0) continue; // ignore unused dests.
3112 if (rd
.spilled()) continue;
3114 emitReload(m_as
, rs
, rd
, src
->type());
3117 if (rs
.numAllocated() == 0) {
3118 assert(src
->isConst());
3120 auto imm
= src
->type().needsValueReg() ? src
->rawVal() :
3122 if (src
->type().needsValueReg() ||
3123 RuntimeOption::EvalHHIRGenerateAsserts
) {
3124 emitLoadImm(m_as
, imm
, r
);
3127 if (rd
.numAllocated() == 2 && rs
.numAllocated() < 2) {
3128 // move a src known type to a dest register
3129 // a.emitImmReg(arg.imm().q(), dst);
3130 assert(src
->type().isKnownDataType());
3131 m_as
.emitImmReg(src
->type().toDataType(), rd
.reg(1));
3136 void CodeGenerator::cgStProp(IRInstruction
* inst
) {
3137 auto objReg
= srcLoc(0).reg();
3138 auto propOff
= inst
->src(1)->intVal();
3139 cgStore(objReg
[propOff
], inst
->src(2), srcLoc(2), Width::Full
);
3142 void CodeGenerator::cgStMem(IRInstruction
* inst
) {
3143 auto ptr
= srcLoc(0).reg();
3144 auto offset
= inst
->src(1)->intVal();
3145 cgStore(ptr
[offset
], inst
->src(2), srcLoc(2), Width::Full
);
3148 void CodeGenerator::cgStRef(IRInstruction
* inst
) {
3149 always_assert(!srcLoc(1).isFullSIMD());
3150 auto destReg
= dstLoc(0).reg();
3151 auto ptr
= srcLoc(0).reg();
3152 auto off
= RefData::tvOffset();
3153 cgStore(ptr
[off
], inst
->src(1), srcLoc(1), Width::Full
);
3154 if (destReg
!= InvalidReg
) emitMovRegReg(m_as
, ptr
, destReg
);
3157 int CodeGenerator::iterOffset(uint32_t id
) {
3158 const Func
* func
= curFunc();
3159 return -cellsToBytes(((id
+ 1) * kNumIterCells
+ func
->numLocals()));
3162 void CodeGenerator::cgStLoc(IRInstruction
* inst
) {
3163 auto ptr
= srcLoc(0).reg();
3164 auto off
= localOffset(inst
->extra
<StLoc
>()->locId
);
3165 cgStore(ptr
[off
], inst
->src(1), srcLoc(1), Width::Full
);
3168 void CodeGenerator::cgStLocNT(IRInstruction
* inst
) {
3169 auto ptr
= srcLoc(0).reg();
3170 auto off
= localOffset(inst
->extra
<StLocNT
>()->locId
);
3171 cgStore(ptr
[off
], inst
->src(1), srcLoc(1), Width::Value
);
3174 void CodeGenerator::cgSyncABIRegs(IRInstruction
* inst
) {
3175 emitMovRegReg(m_as
, srcLoc(0).reg(), rVmFp
);
3176 emitMovRegReg(m_as
, srcLoc(1).reg(), rVmSp
);
3179 void CodeGenerator::cgEagerSyncVMRegs(IRInstruction
* inst
) {
3181 srcLoc(0).reg() == rVmFp
&&
3182 srcLoc(1).reg() == rVmSp
3186 reinterpret_cast<const Op
*>(inst
->marker().sk().pc())
3190 void CodeGenerator::cgReqBindJmp(IRInstruction
* inst
) {
3191 auto offset
= inst
->extra
<ReqBindJmp
>()->offset
;
3192 auto trflags
= inst
->extra
<ReqBindJmp
>()->trflags
;
3197 SrcKey(curFunc(), offset
, resumed()),
3202 void CodeGenerator::cgReqRetranslateOpt(IRInstruction
* inst
) {
3203 auto extra
= inst
->extra
<ReqRetranslateOpt
>();
3205 TCA sr
= emitServiceReq(
3206 m_coldCode
, REQ_RETRANSLATE_OPT
,
3207 SrcKey(curFunc(), extra
->offset
, resumed()).toAtomicInt(),
3209 if (m_mainCode
.frontier() != m_coldCode
.frontier()) {
3214 void CodeGenerator::cgReqRetranslate(IRInstruction
* inst
) {
3215 assert(m_unit
.bcOff() == inst
->marker().bcOff());
3216 auto const destSK
= SrcKey(curFunc(), m_unit
.bcOff(), resumed());
3217 auto const destSR
= mcg
->tx().getSrcRec(destSK
);
3219 auto trflags
= inst
->extra
<ReqRetranslate
>()->trflags
;
3221 if (trflags
.packed
== 0) {
3222 destSR
->emitFallbackJump(m_mainCode
);
3224 destSR
->emitFallbackJumpCustom(m_mainCode
, m_frozenCode
, destSK
, trflags
);
3228 void CodeGenerator::cgIncRefWork(Type type
, SSATmp
* src
, PhysLoc srcLoc
) {
3229 assert(type
.maybeCounted());
3230 auto increfMaybeStatic
= [&](Asm
& a
) {
3231 auto base
= srcLoc
.reg(0);
3232 if (!type
.needsStaticBitCheck()) {
3233 emitIncRef(a
, base
);
3235 a
.cmpl(0, base
[FAST_REFCOUNT_OFFSET
]);
3236 static_assert(UncountedValue
< 0 && StaticValue
< 0, "");
3237 ifThen(a
, CC_NS
, [&](Asm
& a
) { emitIncRef(a
, base
); });
3241 if (type
.isKnownDataType()) {
3242 assert(IS_REFCOUNTED_TYPE(type
.toDataType()));
3243 increfMaybeStatic(m_as
);
3245 emitCmpTVType(m_as
, KindOfRefCountThreshold
, srcLoc
.reg(1));
3246 ifThen(m_as
, CC_NLE
, [&](Asm
& a
) { increfMaybeStatic(a
); });
3250 void CodeGenerator::cgIncRef(IRInstruction
* inst
) {
3251 SSATmp
* src
= inst
->src(0);
3252 Type type
= src
->type();
3254 if (type
.notCounted()) return;
3256 cgIncRefWork(type
, src
, srcLoc(0));
3259 void CodeGenerator::cgIncRefCtx(IRInstruction
* inst
) {
3260 if (inst
->src(0)->isA(Type::Obj
)) return cgIncRef(inst
);
3262 auto const src
= srcLoc(0).reg();
3265 a
. testb (0x1, rbyte(src
));
3266 ifThen(a
, CC_Z
, [&](Asm
& a
) {
3271 void CodeGenerator::cgDecRefStack(IRInstruction
* inst
) {
3272 cgDecRefMem(inst
->typeParam(),
3274 cellsToBytes(inst
->extra
<DecRefStack
>()->offset
));
3277 void CodeGenerator::cgDecRefThis(IRInstruction
* inst
) {
3278 auto fpReg
= srcLoc(0).reg();
3279 auto scratchReg
= m_rScratch
;
3281 // Load AR->m_this into m_rScratch
3283 v
<< loadq
{fpReg
[AROFF(m_this
)], scratchReg
};
3285 auto decrefIfAvailable
= [&](Vout
& v
) {
3286 // Check if this is available and we're not in a static context instead
3287 v
<< testbi
{1, Vreg
{scratchReg
}};
3288 ifThen(v
, CC_Z
, [&](Vout
& v
) {
3289 cgDecRefStaticType(v
,
3292 true /* genZeroCheck */
3297 if (curFunc()->isPseudoMain()) {
3298 // In pseudo-mains, emit check for presence of m_this
3299 v
<< testq
{scratchReg
, scratchReg
};
3300 ifThen(v
, CC_NZ
, [&](Vout
& v
) { decrefIfAvailable(v
); });
3302 decrefIfAvailable(v
);
3306 void CodeGenerator::cgDecRefLoc(IRInstruction
* inst
) {
3307 cgDecRefMem(inst
->typeParam(),
3309 localOffset(inst
->extra
<DecRefLoc
>()->locId
));
3312 void CodeGenerator::cgGenericRetDecRefs(IRInstruction
* inst
) {
3313 auto const rFp
= srcLoc(0).reg();
3314 auto const numLocals
= curFunc()->numLocals();
3317 assert(rFp
== rVmFp
&&
3318 "free locals helper assumes the frame pointer is rVmFp");
3320 if (numLocals
== 0) return;
3322 // The helpers called below use a special ABI, in which r14 and r15 is
3323 // not saved, and the stub expects the stack to be imbalanced (RSP%16==0)
3324 // on entry. So save r14 and r15 in addition to the caller-save registers,
3325 // and use PhysRegSaverStub which assumes the odd stack parity.
3326 auto toSave
= m_state
.liveRegs
[inst
] &
3327 (kCallerSaved
| RegSet(r14
) | RegSet(r15
));
3328 PhysRegSaverStub
saver(v
, toSave
);
3330 auto const target
= numLocals
> kNumFreeLocalsHelpers
3331 ? mcg
->tx().uniqueStubs
.freeManyLocalsHelper
3332 : mcg
->tx().uniqueStubs
.freeLocalsHelpers
[numLocals
- 1];
3334 v
<< lea
{rFp
[-numLocals
* sizeof(TypedValue
)], r14
};
3340 * Depending on the current translation kind, do nothing, profile, or collect
3341 * profiling data for the current DecRef* instruction
3343 * Returns true iff the release path for this DecRef should be put in cold
3346 bool CodeGenerator::decRefDestroyIsUnlikely(OptDecRefProfile
& profile
,
3348 auto const kind
= mcg
->tx().mode();
3349 if (kind
!= TransKind::Profile
&& kind
!= TransKind::Optimize
) return true;
3351 // For a profiling key, we use:
3352 // "DecRefProfile-{opcode name}-{stack/local id if present}-{type}"
3353 // This gives good uniqueness within a bytecode without requiring us to track
3354 // more complex things like "this is the 3rd DecRef in this bytecode".
3355 const int32_t profileId
=
3356 m_curInst
->is(DecRefLoc
) ? m_curInst
->extra
<DecRefLoc
>()->locId
3357 : m_curInst
->is(DecRefStack
) ? m_curInst
->extra
<DecRefStack
>()->offset
3359 auto const profileKey
=
3360 makeStaticString(folly::to
<std::string
>("DecRefProfile-",
3361 opcodeName(m_curInst
->op()),
3366 profile
.emplace(m_unit
.context(), m_curInst
->marker(), profileKey
);
3369 if (profile
->profiling()) {
3370 v
<< incwm
{rVmTl
[profile
->handle() + offsetof(DecRefProfile
, decrement
)]};
3371 } else if (profile
->optimizing()) {
3372 auto const data
= profile
->data(DecRefProfile::reduce
);
3373 if (data
.hitRate() != 0 && data
.hitRate() != 100) {
3374 // These are the only interesting cases where we could be doing better.
3375 FTRACE(5, "DecRefProfile: {}: {} {}\n",
3376 data
, m_curInst
->marker().show(), profileKey
->data());
3378 if (data
.hitRate() == 0) {
3379 v
<< incstat
{Stats::TC_DecRef_Profiled_0
};
3380 } else if (data
.hitRate() == 100) {
3381 v
<< incstat
{Stats::TC_DecRef_Profiled_100
};
3383 return data
.hitRate() < RuntimeOption::EvalJitUnlikelyDecRefPercent
;
3390 template <typename T
>
3392 static bool valid(const T
& f
) { return true; }
3396 struct CheckValid
<void(*)(Asm
&)> {
3397 static bool valid(void (*f
)(Asm
&)) { return f
!= nullptr; }
3400 struct CheckValid
<void(*)(Vout
&)> {
3401 static bool valid(void (*f
)(Vout
&)) { return f
!= nullptr; }
3406 // Using the given dataReg, this method generates code that checks the static
3407 // bit out of dataReg, and emits a DecRef if needed.
3408 // NOTE: the flags are left with the result of the DecRef's subtraction,
3409 // which can then be tested immediately after this.
3411 // We've tried a variety of tweaks to this and found the current state of
3412 // things optimal, at least when the measurements were made:
3413 // - whether to load the count into a register (if one is available)
3414 // - whether to use if (!--count) release(); if we don't need a static check
3415 // - whether to skip using the register and just emit --count if we know
3416 // its not static, and can't hit zero.
3418 // Return value: the address to be patched if a RefCountedStaticValue check is
3419 // emitted; NULL otherwise.
3421 template <typename F
>
3422 void CodeGenerator::cgCheckStaticBitAndDecRef(Vout
& v
, Vlabel done
,
3423 Type type
, PhysReg dataReg
,
3425 always_assert(type
.maybeCounted());
3426 bool hasDestroy
= CheckValid
<F
>::valid(destroyImpl
);
3428 OptDecRefProfile profile
;
3429 auto const unlikelyDestroy
=
3430 hasDestroy
? decRefDestroyIsUnlikely(profile
, type
) : false;
3433 v
<< incstat
{unlikelyDestroy
? Stats::TC_DecRef_Normal_Decl
:
3434 Stats::TC_DecRef_Likely_Decl
};
3436 v
<< incstat
{Stats::TC_DecRef_NZ
};
3439 auto destroy
= [&](Vout
& v
) {
3440 v
<< incstat
{unlikelyDestroy
? Stats::TC_DecRef_Normal_Destroy
:
3441 Stats::TC_DecRef_Likely_Destroy
};
3442 if (profile
&& profile
->profiling()) {
3443 v
<< incwm
{rVmTl
[profile
->handle() + offsetof(DecRefProfile
, destroy
)]};
3448 if (!type
.needsStaticBitCheck()) {
3449 v
<< declm
{dataReg
[FAST_REFCOUNT_OFFSET
]};
3450 if (RuntimeOption::EvalHHIRGenerateAsserts
) {
3451 // Assert that the ref count is not less than zero
3452 emitAssertFlagsNonNegative(v
);
3456 ifBlock(v
, vcold(), CC_E
, destroy
, unlikelyDestroy
);
3461 auto static_check_and_decl
= [&](Vout
& v
) {
3462 static_assert(UncountedValue
== UNCOUNTED
, "");
3463 static_assert(StaticValue
== STATIC
, "");
3465 if (type
.needsStaticBitCheck()) {
3466 auto next
= v
.makeBlock();
3467 v
<< jcc
{CC_L
, {next
, done
}};
3472 v
<< declm
{dataReg
[FAST_REFCOUNT_OFFSET
]};
3473 if (RuntimeOption::EvalHHIRGenerateAsserts
) {
3474 // Assert that the ref count is not less than zero
3475 emitAssertFlagsNonNegative(v
);
3480 v
<< cmplim
{1, dataReg
[FAST_REFCOUNT_OFFSET
]};
3481 ifThenElse(v
, vcold(), CC_E
, destroy
, static_check_and_decl
,
3485 if (type
.needsStaticBitCheck()) {
3486 v
<< cmplim
{0, dataReg
[FAST_REFCOUNT_OFFSET
]};
3489 static_check_and_decl(v
);
3492 void CodeGenerator::cgCheckStaticBitAndDecRef(Vout
& v
, Vlabel done
,
3493 Type type
, PhysReg dataReg
) {
3494 return cgCheckStaticBitAndDecRef(v
, done
, type
, dataReg
,
3495 (void (*)(Vout
&))nullptr);
3499 // Returns the address to be patched with the address to jump to in case
3500 // the type is not ref-counted.
3502 void CodeGenerator::cgCheckRefCountedType(PhysReg typeReg
, Vlabel done
) {
3504 auto next
= v
.makeBlock();
3505 emitCmpTVType(v
, KindOfRefCountThreshold
, typeReg
);
3506 v
<< jcc
{CC_LE
, {next
, done
}};
3510 void CodeGenerator::cgCheckRefCountedType(PhysReg baseReg
, int64_t offset
,
3513 auto next
= v
.makeBlock();
3514 emitCmpTVType(v
, KindOfRefCountThreshold
, baseReg
[offset
+ TVOFF(m_type
)]);
3515 v
<< jcc
{CC_LE
, {next
, done
}};
3520 // Generates dec-ref of a typed value with statically known type.
3522 void CodeGenerator::cgDecRefStaticType(Vout
& v
, Type type
, PhysReg dataReg
,
3523 bool genZeroCheck
) {
3524 assert(type
!= Type::Cell
&& type
!= Type::Gen
);
3525 assert(type
.isKnownDataType());
3527 if (type
.notCounted()) return;
3529 // Check for UncountedValue or StaticValue if needed,
3530 // do the actual DecRef, and leave flags set based on the subtract result,
3531 // which is tested below
3532 auto done
= v
.makeBlock();
3534 cgCheckStaticBitAndDecRef(v
, done
, type
, dataReg
, [&] (Vout
& v
) {
3535 // Emit the call to release in m_acold
3537 mcg
->getDtorCall(type
.toDataType()),
3539 SyncOptions::kSyncPoint
,
3544 cgCheckStaticBitAndDecRef(v
, done
, type
, dataReg
);
3546 if (!v
.closed()) v
<< jmp
{done
};
3551 // Generates dec-ref of a typed value with dynamic (statically unknown) type,
3552 // when the type is stored in typeReg.
3554 void CodeGenerator::cgDecRefDynamicType(PhysReg typeReg
,
3556 bool genZeroCheck
) {
3557 // Emit check for ref-counted type
3559 auto done
= v
.makeBlock();
3560 cgCheckRefCountedType(typeReg
, done
);
3562 // Emit check for UncountedValue or StaticValue and the actual DecRef
3564 cgCheckStaticBitAndDecRef(v
, done
, Type::Cell
, dataReg
, [&] (Vout
& v
) {
3565 // load destructor fptr
3566 loadDestructorFunc(v
, typeReg
, m_rScratch
);
3567 // Emit call to release in m_acold
3569 CppCall::indirect(m_rScratch
),
3571 SyncOptions::kSyncPoint
,
3576 cgCheckStaticBitAndDecRef(v
, done
, Type::Cell
, dataReg
);
3578 if (!v
.closed()) v
<< jmp
{done
};
3583 // Generates dec-ref of a typed value with dynamic (statically
3584 // unknown) type, when all we have is the baseReg and offset of
3585 // the typed value. This method assumes that baseReg is not the
3586 // scratch register.
3588 void CodeGenerator::cgDecRefDynamicTypeMem(PhysReg baseReg
,
3590 auto scratchReg
= m_rScratch
;
3591 assert(baseReg
!= scratchReg
);
3593 auto done
= v
.makeBlock();
3595 // Emit check for ref-counted type
3596 cgCheckRefCountedType(baseReg
, offset
, done
);
3598 v
<< loadq
{baseReg
[offset
+ TVOFF(m_data
)], scratchReg
};
3600 // Emit check for UncountedValue or StaticValue and the actual DecRef
3601 cgCheckStaticBitAndDecRef(v
, done
, Type::Cell
, scratchReg
, [&](Vout
& v
) {
3602 // Emit call to release in m_acold
3603 v
<< lea
{baseReg
[offset
], scratchReg
};
3604 cgCallHelper(v
, CppCall::direct(tv_release_generic
),
3606 SyncOptions::kSyncPoint
,
3607 argGroup().reg(scratchReg
));
3610 if (!v
.closed()) v
<< jmp
{done
};
3615 // Generates the dec-ref of a typed value in memory address [baseReg + offset].
3616 // This handles cases where type is either static or dynamic.
3618 void CodeGenerator::cgDecRefMem(Type type
,
3621 if (type
.notCounted()) return;
3623 auto scratchReg
= m_rScratch
;
3624 assert(baseReg
!= scratchReg
);
3626 if (type
.needsReg()) {
3627 // The type is dynamic, but we don't have two registers available
3628 // to load the type and the data.
3629 cgDecRefDynamicTypeMem(baseReg
, offset
);
3630 } else if (type
.maybeCounted()) {
3631 v
<< loadq
{baseReg
[offset
+ TVOFF(m_data
)], scratchReg
};
3632 cgDecRefStaticType(v
, type
, scratchReg
, true);
3636 void CodeGenerator::cgDecRefMem(IRInstruction
* inst
) {
3637 assert(inst
->src(0)->type().isPtr());
3638 cgDecRefMem(inst
->typeParam(),
3640 inst
->src(1)->intVal());
3643 void CodeGenerator::cgDecRefWork(IRInstruction
* inst
, bool genZeroCheck
) {
3644 SSATmp
* src
= inst
->src(0);
3645 if (!isRefCounted(src
)) return;
3646 Type type
= src
->type();
3647 if (type
.isKnownDataType()) {
3648 cgDecRefStaticType(vmain(), type
, srcLoc(0).reg(), genZeroCheck
);
3650 cgDecRefDynamicType(srcLoc(0).reg(1), srcLoc(0).reg(0), genZeroCheck
);
3654 void CodeGenerator::cgDecRef(IRInstruction
*inst
) {
3655 // DecRef may bring the count to zero, and run the destructor.
3656 // Generate code for this.
3657 cgDecRefWork(inst
, true);
3660 void CodeGenerator::cgDecRefNZ(IRInstruction
* inst
) {
3661 // DecRefNZ cannot bring the count to zero.
3662 // Therefore, we don't generate zero-checking code.
3663 cgDecRefWork(inst
, false);
3666 void CodeGenerator::cgCufIterSpillFrame(IRInstruction
* inst
) {
3667 auto const nArgs
= inst
->extra
<CufIterSpillFrame
>()->args
;
3668 auto const iterId
= inst
->extra
<CufIterSpillFrame
>()->iterId
;
3669 auto const itOff
= iterOffset(iterId
);
3671 const auto spOffset
= -safe_cast
<int32_t>(kNumActRecCells
* sizeof(Cell
));
3672 auto spReg
= srcLoc(0).reg();
3673 auto fpReg
= srcLoc(1).reg();
3675 m_as
.loadq (fpReg
[itOff
+ CufIter::funcOff()], m_rScratch
);
3676 m_as
.storeq (m_rScratch
, spReg
[spOffset
+ int(AROFF(m_func
))]);
3678 m_as
.loadq (fpReg
[itOff
+ CufIter::ctxOff()], m_rScratch
);
3679 m_as
.storeq (m_rScratch
, spReg
[spOffset
+ int(AROFF(m_this
))]);
3681 m_as
.shrq (1, m_rScratch
);
3682 ifThen(m_as
, CC_NBE
, [this](Asm
& a
) {
3683 a
.shlq(1, m_rScratch
);
3684 emitIncRef(a
, m_rScratch
);
3686 m_as
.loadq (fpReg
[itOff
+ CufIter::nameOff()], m_rScratch
);
3687 m_as
.testq (m_rScratch
, m_rScratch
);
3688 ifThen(m_as
, CC_NZ
, [this](Asm
& a
) {
3689 a
.cmpl(0, m_rScratch
[FAST_REFCOUNT_OFFSET
]);
3690 static_assert(UncountedValue
< 0 && StaticValue
< 0, "");
3691 ifThen(a
, CC_NS
, [&](Asm
& a
) { emitIncRef(a
, m_rScratch
); });
3692 a
.orq (ActRec::kInvNameBit
, m_rScratch
);
3694 m_as
.storeq (m_rScratch
, spReg
[spOffset
+ int(AROFF(m_invName
))]);
3695 m_as
.storel (safe_cast
<int32_t>(nArgs
),
3696 spReg
[spOffset
+ int(AROFF(m_numArgsAndFlags
))]);
3698 emitAdjustSp(spReg
, dstLoc(0).reg(), spOffset
);
3701 void CodeGenerator::cgSpillFrame(IRInstruction
* inst
) {
3702 auto const func
= inst
->src(1);
3703 auto const objOrCls
= inst
->src(2);
3704 auto const magicName
= inst
->extra
<SpillFrame
>()->invName
;
3705 auto const nArgs
= inst
->extra
<SpillFrame
>()->numArgs
;
3708 const auto spOffset
= -safe_cast
<int32_t>(kNumActRecCells
* sizeof(Cell
));
3710 auto spReg
= srcLoc(0).reg();
3712 if (objOrCls
->isA(Type::Cls
)) {
3714 if (objOrCls
->isConst()) {
3715 emitImmStoreq(a
, uintptr_t(objOrCls
->clsVal()) | 1,
3716 spReg
[spOffset
+ int(AROFF(m_this
))]);
3718 Reg64 clsPtrReg
= srcLoc(2/*objOrCls*/).reg();
3719 a
. movq (clsPtrReg
, m_rScratch
);
3720 a
. orq (1, m_rScratch
);
3721 a
. storeq (m_rScratch
, spReg
[spOffset
+ int(AROFF(m_this
))]);
3723 } else if (objOrCls
->isA(Type::Obj
)) {
3724 // store this pointer
3725 a
. storeq (srcLoc(2/*objOrCls*/).reg(),
3726 spReg
[spOffset
+ int(AROFF(m_this
))]);
3727 } else if (objOrCls
->isA(Type::Ctx
)) {
3728 // Stores either a this pointer or a Cctx -- statically unknown.
3729 Reg64 objOrClsPtrReg
= srcLoc(2/*objOrCls*/).reg();
3730 a
. storeq (objOrClsPtrReg
, spReg
[spOffset
+ int(AROFF(m_this
))]);
3732 assert(objOrCls
->isA(Type::Nullptr
));
3733 // no obj or class; this happens in FPushFunc
3734 int offset_m_this
= spOffset
+ int(AROFF(m_this
));
3735 a
. storeq (0, spReg
[offset_m_this
]);
3737 // actRec->m_invName
3738 // ActRec::m_invName is encoded as a pointer with bit kInvNameBit
3739 // set to distinguish it from m_varEnv and m_extrArgs
3740 uintptr_t invName
= !magicName
3742 : reinterpret_cast<uintptr_t>(magicName
) | ActRec::kInvNameBit
;
3743 emitImmStoreq(a
, invName
, spReg
[spOffset
+ int(AROFF(m_invName
))]);
3744 // actRec->m_func and possibly actRec->m_cls
3745 // Note m_cls is unioned with m_this and may overwrite previous value
3746 if (func
->isA(Type::Nullptr
)) {
3747 // No need to store the null---we're always about to run another
3748 // instruction that will populate the Func.
3749 } else if (func
->isConst()) {
3750 const Func
* f
= func
->funcVal();
3751 emitImmStoreq(a
, intptr_t(f
), spReg
[spOffset
+ int(AROFF(m_func
))]);
3753 int offset_m_func
= spOffset
+ int(AROFF(m_func
));
3754 auto funcLoc
= srcLoc(1);
3755 a
. storeq (funcLoc
.reg(0), spReg
[offset_m_func
]);
3758 a
. storel (nArgs
, spReg
[spOffset
+ int(AROFF(m_numArgsAndFlags
))]);
3760 emitAdjustSp(spReg
, dstLoc(0).reg(), spOffset
);
3763 void CodeGenerator::cgStClosureFunc(IRInstruction
* inst
) {
3764 auto const obj
= srcLoc(0).reg();
3765 auto const func
= inst
->extra
<StClosureFunc
>()->func
;
3766 emitImmStoreq(m_as
, intptr_t(func
), obj
[c_Closure::funcOffset()]);
3769 void CodeGenerator::cgStClosureArg(IRInstruction
* inst
) {
3771 srcLoc(0).reg()[inst
->extra
<StClosureArg
>()->offsetBytes
],
3772 inst
->src(1), srcLoc(1),
3777 void CodeGenerator::cgStClosureCtx(IRInstruction
* inst
) {
3778 auto const obj
= srcLoc(0).reg();
3780 if (inst
->src(1)->isA(Type::Nullptr
)) {
3781 m_as
. storeq (0, obj
[c_Closure::ctxOffset()]);
3783 auto const ctx
= srcLoc(1).reg();
3784 always_assert(ctx
!= InvalidReg
);
3785 m_as
. storeq (ctx
, obj
[c_Closure::ctxOffset()]);
3789 void CodeGenerator::emitInitObjProps(PhysReg dstReg
,
3792 // If the object has a small number of properties, just emit stores
3795 for (int i
= 0; i
< nProps
; ++i
) {
3797 sizeof(ObjectData
) + cls
->builtinODTailSize() + sizeof(TypedValue
) * i
;
3798 auto propDataOffset
= propOffset
+ TVOFF(m_data
);
3799 auto propTypeOffset
= propOffset
+ TVOFF(m_type
);
3800 if (!IS_NULL_TYPE(cls
->declPropInit()[i
].m_type
)) {
3801 emitImmStoreq(m_as
, cls
->declPropInit()[i
].m_data
.num
,
3802 dstReg
[propDataOffset
]);
3804 m_as
.storeb(cls
->declPropInit()[i
].m_type
, dstReg
[propTypeOffset
]);
3809 // Use memcpy for large numbers of properties.
3810 auto args
= argGroup()
3812 safe_cast
<int32_t>(sizeof(ObjectData
) + cls
->builtinODTailSize()))
3813 .imm(int64_t(&cls
->declPropInit()[0]))
3814 .imm(cellsToBytes(nProps
));
3816 CppCall::direct(memcpy
),
3818 SyncOptions::kNoSyncPoint
,
3822 void CodeGenerator::cgConstructInstance(IRInstruction
* inst
) {
3823 auto const cls
= inst
->extra
<ConstructInstance
>()->cls
;
3824 auto const dstReg
= dstLoc(0).reg();
3826 CppCall::direct(cls
->instanceCtor()),
3828 SyncOptions::kSyncPoint
,
3829 argGroup().immPtr(cls
));
3832 void CodeGenerator::cgCheckInitProps(IRInstruction
* inst
) {
3833 auto const cls
= inst
->extra
<CheckInitProps
>()->cls
;
3834 auto const branch
= inst
->taken();
3835 m_as
.cmpq(0, rVmTl
[cls
->propHandle()]);
3836 emitFwdJcc(m_as
, CC_Z
, branch
);
3839 void CodeGenerator::cgCheckInitSProps(IRInstruction
* inst
) {
3840 auto const cls
= inst
->extra
<CheckInitSProps
>()->cls
;
3841 auto const branch
= inst
->taken();
3842 m_as
.cmpb(0, rVmTl
[cls
->sPropInitHandle()]);
3843 emitFwdJcc(m_as
, CC_Z
, branch
);
3846 void CodeGenerator::cgNewInstanceRaw(IRInstruction
* inst
) {
3847 auto const cls
= inst
->extra
<NewInstanceRaw
>()->cls
;
3848 auto const dstReg
= dstLoc(0).reg();
3849 size_t size
= ObjectData::sizeForNProps(cls
->numDeclProperties());
3851 size
<= kMaxSmartSize
3852 ? CppCall::direct(ObjectData::newInstanceRaw
)
3853 : CppCall::direct(ObjectData::newInstanceRawBig
),
3855 SyncOptions::kSyncPoint
,
3856 argGroup().imm((uint64_t)cls
).imm(size
));
3859 void CodeGenerator::cgInitObjProps(IRInstruction
* inst
) {
3860 auto const cls
= inst
->extra
<InitObjProps
>()->cls
;
3861 auto const srcReg
= srcLoc(0).reg();
3863 // Set the attributes, if any
3864 int odAttrs
= cls
->getODAttrs();
3866 // o_attribute is 16 bits but the fact that we're or-ing a mask makes it ok
3867 assert(!(odAttrs
& 0xffff0000));
3868 m_as
.orq(odAttrs
, srcReg
[ObjectData::attributeOff()]);
3871 // Initialize the properties
3872 size_t nProps
= cls
->numDeclProperties();
3874 if (cls
->pinitVec().size() == 0) {
3875 // Fast case: copy from a known address in the Class
3876 emitInitObjProps(srcReg
, cls
, nProps
);
3878 // Slower case: we have to load the src address from the targetcache
3879 auto rPropData
= m_rScratch
;
3880 // Load the Class's propInitVec from the targetcache
3881 m_as
.loadq(rVmTl
[cls
->propHandle()], rPropData
);
3882 // propData holds the PropInitVec. We want &(*propData)[0]
3883 m_as
.loadq(rPropData
[Class::PropInitVec::dataOff()], rPropData
);
3884 if (!cls
->hasDeepInitProps()) {
3885 auto args
= argGroup()
3887 safe_cast
<int32_t>(sizeof(ObjectData
) + cls
->builtinODTailSize()))
3889 .imm(cellsToBytes(nProps
));
3891 CppCall::direct(memcpy
),
3893 SyncOptions::kNoSyncPoint
,
3896 auto args
= argGroup()
3898 safe_cast
<int32_t>(sizeof(ObjectData
) + cls
->builtinODTailSize()))
3902 CppCall::direct(deepInitHelper
),
3904 SyncOptions::kNoSyncPoint
,
3911 void CodeGenerator::cgCallArray(IRInstruction
* inst
) {
3912 Offset pc
= inst
->extra
<CallArray
>()->pc
;
3913 Offset after
= inst
->extra
<CallArray
>()->after
;
3917 reinterpret_cast<void (*)()>(mcg
->tx().uniqueStubs
.fcallArrayHelper
)),
3919 SyncOptions::kSyncPoint
,
3926 void CodeGenerator::cgCall(IRInstruction
* inst
) {
3927 auto const extra
= inst
->extra
<Call
>();
3928 auto const rSP
= srcLoc(0).reg();
3929 auto const rFP
= srcLoc(1).reg();
3932 auto const ar
= extra
->numParams
* sizeof(TypedValue
);
3933 a
. storeq (rFP
, rSP
[ar
+ AROFF(m_sfp
)]);
3934 a
. storel (safe_cast
<int32_t>(extra
->after
), rSP
[ar
+ AROFF(m_soff
)]);
3936 if (extra
->knownPrologue
) {
3937 assert(extra
->callee
);
3938 if (RuntimeOption::EvalHHIRGenerateAsserts
) {
3939 auto const off
= cellsToBytes(extra
->numParams
) + AROFF(m_savedRip
);
3940 emitImmStoreq(a
, 0xff00ff00b00b00d0, rSP
[off
]);
3942 emitLea(a
, rSP
[cellsToBytes(extra
->numParams
)], rStashedAR
);
3944 * Normally there's no need to prepare for smash if this is a live
3945 * or optimized translation, since we know where we are going.
3947 * However, if we're going to a profiling prologue, we want it to
3948 * be smashable later, so we need to tell the profiling module
3949 * about this and prepare for smashing the call.
3951 if (mcg
->code
.prof().contains(extra
->knownPrologue
)) {
3952 auto const calleeNumParams
= extra
->callee
->numNonVariadicParams();
3953 auto const prologIndex
=
3954 extra
->numParams
<= calleeNumParams
? extra
->numParams
3955 : calleeNumParams
+ 1;
3956 mcg
->backEnd().prepareForSmash(a
.code(), kCallLen
);
3957 mcg
->tx().profData()->addPrologueMainCaller(
3962 always_assert(mcg
->backEnd().isSmashable(a
.code().frontier(), kCallLen
));
3964 a
. call (extra
->knownPrologue
);
3968 assert(dstLoc(0).reg() == rVmSp
);
3969 auto const srcKey
= m_curInst
->marker().sk();
3970 emitBindCall(m_mainCode
, m_coldCode
, m_frozenCode
, srcKey
, extra
->callee
,
3974 void CodeGenerator::cgCastStk(IRInstruction
*inst
) {
3975 Type type
= inst
->typeParam();
3976 uint32_t offset
= inst
->extra
<CastStk
>()->offset
;
3977 PhysReg spReg
= srcLoc(0).reg();
3978 auto args
= argGroup();
3979 args
.addr(spReg
, cellsToBytes(offset
));
3982 if (type
<= Type::Bool
) {
3983 tvCastHelper
= (TCA
)tvCastToBooleanInPlace
;
3984 } else if (type
<= Type::Int
) {
3985 tvCastHelper
= (TCA
)tvCastToInt64InPlace
;
3986 } else if (type
<= Type::Dbl
) {
3987 tvCastHelper
= (TCA
)tvCastToDoubleInPlace
;
3988 } else if (type
<= Type::Arr
) {
3989 tvCastHelper
= (TCA
)tvCastToArrayInPlace
;
3990 } else if (type
<= Type::Str
) {
3991 tvCastHelper
= (TCA
)tvCastToStringInPlace
;
3992 } else if (type
<= Type::Obj
) {
3993 tvCastHelper
= (TCA
)tvCastToObjectInPlace
;
3994 } else if (type
<= Type::NullableObj
) {
3995 tvCastHelper
= (TCA
)tvCastToNullableObjectInPlace
;
3996 } else if (type
<= Type::Res
) {
3997 tvCastHelper
= (TCA
)tvCastToResourceInPlace
;
4002 CppCall::direct(reinterpret_cast<void (*)()>(tvCastHelper
)),
4004 SyncOptions::kSyncPoint
,
4008 void CodeGenerator::cgCastStkIntToDbl(IRInstruction
* inst
) {
4009 auto spReg
= srcLoc(0).reg();
4010 auto offset
= cellsToBytes(inst
->extra
<CastStkIntToDbl
>()->offset
);
4011 m_as
.cvtsi2sd(refTVData(spReg
[offset
]), rCgXMM0
);
4012 emitStoreReg(m_as
, rCgXMM0
, refTVData(spReg
[offset
]));
4013 emitStoreTVType(m_as
, KindOfDouble
, refTVType(spReg
[offset
]));
4016 void CodeGenerator::cgCoerceStk(IRInstruction
*inst
) {
4017 Type type
= inst
->typeParam();
4018 uint32_t offset
= inst
->extra
<CoerceStk
>()->offset
;
4019 Block
* exit
= inst
->taken();
4020 PhysReg spReg
= srcLoc(0).reg();
4022 auto args
= argGroup();
4023 args
.addr(spReg
, cellsToBytes(offset
));
4026 if (type
<= Type::Bool
) {
4027 tvCoerceHelper
= (TCA
)tvCoerceParamToBooleanInPlace
;
4028 } else if (type
<= Type::Int
) {
4029 // if casting to integer, pass 10 as the base for the conversion
4031 tvCoerceHelper
= (TCA
)tvCoerceParamToInt64InPlace
;
4032 } else if (type
<= Type::Dbl
) {
4033 tvCoerceHelper
= (TCA
)tvCoerceParamToDoubleInPlace
;
4034 } else if (type
<= Type::Arr
) {
4035 tvCoerceHelper
= (TCA
)tvCoerceParamToArrayInPlace
;
4036 } else if (type
<= Type::Str
) {
4037 tvCoerceHelper
= (TCA
)tvCoerceParamToStringInPlace
;
4038 } else if (type
<= Type::Obj
) {
4039 tvCoerceHelper
= (TCA
)tvCoerceParamToObjectInPlace
;
4040 } else if (type
<= Type::Res
) {
4041 tvCoerceHelper
= (TCA
)tvCoerceParamToResourceInPlace
;
4046 auto tmpReg
= PhysReg(m_rScratch
);
4048 CppCall::direct(reinterpret_cast<void (*)()>(tvCoerceHelper
)),
4050 SyncOptions::kSyncPoint
,
4053 m_as
.testb(1, rbyte(tmpReg
));
4054 emitFwdJcc(m_as
, CC_E
, exit
);
4057 void CodeGenerator::cgCallBuiltin(IRInstruction
* inst
) {
4058 auto const dst
= dstLoc(0);
4059 auto const dstReg
= dst
.reg(0);
4060 auto const dstType
= dst
.reg(1);
4061 auto const callee
= inst
->extra
<CallBuiltin
>()->callee
;
4062 auto const numArgs
= callee
->numParams();
4063 auto const returnType
= inst
->typeParam();
4064 auto const funcReturnType
= callee
->returnType();
4066 int returnOffset
= MISOFF(tvBuiltinReturn
);
4068 if (FixupMap::eagerRecord(callee
)) {
4069 auto const pc
= curUnit()->entry() + m_curInst
->marker().bcOff();
4070 // we have spilled all args to stack, so spDiff is 0
4071 emitEagerSyncPoint(m_as
, reinterpret_cast<const Op
*>(pc
));
4073 // RSP points to the MInstrState we need to use. Workaround the
4074 // fact that rsp moves when we spill registers around call
4075 PhysReg misReg
= m_rScratch
;
4076 emitMovRegReg(m_as
, reg::rsp
, misReg
);
4078 auto callArgs
= argGroup();
4079 if (isCppByRef(funcReturnType
)) {
4080 // First arg is pointer to storage for that return value
4081 if (isSmartPtrRef(funcReturnType
)) {
4082 returnOffset
+= TVOFF(m_data
);
4084 // misReg is pointing to an MInstrState struct on the C stack. Pass
4085 // the address of tvBuiltinReturn to the native function as the location
4086 // it can construct the return Array, String, Object, or Variant.
4087 callArgs
.addr(misReg
, returnOffset
); // &misReg[returnOffset]
4090 // Non-pointer args are plain values passed by value. String, Array,
4091 // Object, and Variant are passed by const&, ie a pointer to stack memory
4092 // holding the value, so expect PtrToT types for these.
4093 // Pointers to smartptr types (String, Array, Object) need adjusting to
4094 // point to &ptr->m_data.
4095 auto srcNum
= uint32_t{0};
4096 if (callee
->isMethod() && !(callee
->attrs() & AttrStatic
)) {
4097 // Note, we don't support objects with vtables here (if they may
4098 // need a this pointer adjustment). This should be filtered out
4099 // earlier right now.
4100 callArgs
.ssa(srcNum
);
4103 for (uint32_t i
= 0; i
< numArgs
; ++i
, ++srcNum
) {
4104 auto const& pi
= callee
->params()[i
];
4105 if (TVOFF(m_data
) && isSmartPtrRef(pi
.builtinType
)) {
4106 assert(inst
->src(srcNum
)->type().isPtr() &&
4107 srcLoc(srcNum
).reg() != InvalidReg
);
4108 callArgs
.addr(srcLoc(srcNum
).reg(), TVOFF(m_data
));
4110 callArgs
.ssa(srcNum
, pi
.builtinType
== KindOfDouble
);
4114 // If the return value is returned by reference, we don't need the
4115 // return value from this call since we know where the value is.
4116 auto dest
= isCppByRef(funcReturnType
) ? kVoidDest
:
4117 funcReturnType
== KindOfDouble
? callDestDbl(inst
) :
4119 cgCallHelper(m_as
, CppCall::direct(callee
->nativeFuncPtr()),
4120 dest
, SyncOptions::kSyncPoint
, callArgs
);
4122 // For primitive return types (int, bool, double), the return value
4123 // is already in dstReg (the builtin call returns in rax or xmm0).
4124 // For void return types, there is no return value, so just exit.
4125 if (dstReg
== InvalidReg
|| returnType
.isSimpleType()) {
4129 // after the call, RSP is back pointing to MInstrState and rSratch
4130 // has been clobberred.
4133 // For return by reference (String, Object, Array, Variant),
4134 // the builtin writes the return value into MInstrState::tvBuiltinReturn
4135 // TV, from where it has to be tested and copied.
4136 if (returnType
.isReferenceType()) {
4137 assert(isCppByRef(funcReturnType
) && isSmartPtrRef(funcReturnType
));
4138 // return type is String, Array, or Object; fold nullptr to KindOfNull
4139 m_as
. loadq (misReg
[returnOffset
], dstReg
);
4140 emitLoadImm(m_as
, returnType
.toDataType(), dstType
);
4141 emitLoadImm(m_as
, KindOfNull
, m_rScratch
);
4142 m_as
. testq (dstReg
, dstReg
);
4143 m_as
. cmov_reg64_reg64 (CC_Z
, m_rScratch
, dstType
);
4146 if (returnType
<= Type::Cell
|| returnType
<= Type::BoxedCell
) {
4147 // return type is Variant; fold KindOfUninit to KindOfNull
4148 assert(isCppByRef(funcReturnType
) && !isSmartPtrRef(funcReturnType
));
4149 assert(misReg
!= dstType
);
4150 emitLoadTVType(m_as
, misReg
[returnOffset
+ TVOFF(m_type
)], dstType
);
4151 m_as
. loadq (misReg
[returnOffset
+ TVOFF(m_data
)], dstReg
);
4152 emitLoadImm(m_as
, KindOfNull
, m_rScratch
);
4153 static_assert(KindOfUninit
== 0, "KindOfUninit must be 0 for test");
4154 m_as
. testb (rbyte(dstType
), rbyte(dstType
));
4155 m_as
. cmov_reg64_reg64 (CC_Z
, m_rScratch
, dstType
);
4161 void CodeGenerator::cgSpillStack(IRInstruction
* inst
) {
4162 auto const spDeficit
= inst
->src(1)->intVal();
4163 auto const spillVals
= inst
->srcs().subpiece(2);
4164 auto const numSpillSrcs
= spillVals
.size();
4165 auto const dstReg
= dstLoc(0).reg();
4166 auto const spReg
= srcLoc(0).reg();
4167 auto const spillCells
= spillValueCells(inst
);
4169 int adjustment
= safe_cast
<int32_t>(
4170 (spDeficit
- spillCells
) * ssize_t(sizeof(Cell
))
4172 for (uint32_t i
= 0; i
< numSpillSrcs
; ++i
) {
4173 int offset
= safe_cast
<int32_t>(i
* ssize_t(sizeof(Cell
)) + adjustment
);
4174 cgStore(spReg
[offset
], spillVals
[i
], srcLoc(i
+ 2), Width::Full
);
4176 emitAdjustSp(spReg
, dstReg
, adjustment
);
4179 void CodeGenerator::emitAdjustSp(PhysReg spReg
, PhysReg dstReg
,
4180 int adjustment
/* bytes */) {
4181 if (adjustment
!= 0) {
4182 if (dstReg
!= spReg
) {
4183 m_as
. lea (spReg
[adjustment
], dstReg
);
4185 m_as
. addq (adjustment
, dstReg
);
4188 emitMovRegReg(m_as
, spReg
, dstReg
);
4192 void CodeGenerator::cgNativeImpl(IRInstruction
* inst
) {
4193 auto const func
= curFunc();
4194 auto const builtinFuncPtr
= func
->builtinFuncPtr();
4197 v
<< copy
{srcLoc(0).reg(), argNumToRegName
[0]};
4198 if (FixupMap::eagerRecord(func
)) {
4199 emitEagerSyncPoint(v
, reinterpret_cast<const Op
*>(func
->getEntry()));
4201 v
<< call
{(TCA
)builtinFuncPtr
};
4205 void CodeGenerator::cgLdThis(IRInstruction
* inst
) {
4206 Block
* label
= inst
->taken();
4207 auto dstReg
= dstLoc(0).reg();
4209 m_as
.loadq(srcLoc(0).reg()[AROFF(m_this
)], dstReg
);
4210 if (label
== NULL
) return; // no need to perform its checks
4212 if (curFunc()->isPseudoMain() || !curFunc()->mayHaveThis()) {
4213 // Check for a null $this pointer first.
4214 m_as
.testq(dstReg
, dstReg
);
4215 emitFwdJcc(CC_Z
, label
);
4218 m_as
.testb(1, rbyte(dstReg
));
4219 emitFwdJcc(CC_NZ
, label
);
4222 void CodeGenerator::cgLdClsCtx(IRInstruction
* inst
) {
4223 PhysReg srcReg
= srcLoc(0).reg();
4224 PhysReg dstReg
= dstLoc(0).reg();
4225 // Context could be either a this object or a class ptr
4226 m_as
. testb(1, rbyte(srcReg
));
4228 [&](Asm
& a
) { emitLdClsCctx(a
, srcReg
, dstReg
); }, // ctx is class
4229 [&](Asm
& a
) { emitLdObjClass(a
, srcReg
, dstReg
); } // ctx is $this
4233 void CodeGenerator::cgLdClsCctx(IRInstruction
* inst
) {
4234 PhysReg srcReg
= srcLoc(0).reg();
4235 PhysReg dstReg
= dstLoc(0).reg();
4236 emitLdClsCctx(m_as
, srcReg
, dstReg
);
4239 void CodeGenerator::cgLdCtx(IRInstruction
* inst
) {
4240 auto const dstReg
= dstLoc(0).reg();
4241 auto const srcReg
= srcLoc(0).reg();
4242 m_as
.loadq(srcReg
[AROFF(m_this
)], dstReg
);
4245 void CodeGenerator::cgLdCctx(IRInstruction
* inst
) {
4246 return cgLdCtx(inst
);
4249 void CodeGenerator::cgLdClsName(IRInstruction
* inst
) {
4250 auto const dstReg
= dstLoc(0).reg();
4251 auto const srcReg
= srcLoc(0).reg();
4253 m_as
.loadq(srcReg
[Class::preClassOff()], dstReg
);
4254 emitLdLowPtr(m_as
, dstReg
[PreClass::nameOffset()],
4255 dstReg
, sizeof(LowStringPtr
));
4258 void CodeGenerator::cgLdARFuncPtr(IRInstruction
* inst
) {
4259 assert(inst
->src(1)->isConst());
4260 auto const offset
= inst
->src(1);
4261 auto dstReg
= dstLoc(0).reg();
4262 auto baseReg
= srcLoc(0).reg();
4263 m_as
.loadq(baseReg
[offset
->intVal() + AROFF(m_func
)], dstReg
);
4266 void CodeGenerator::cgLdStaticLocCached(IRInstruction
* inst
) {
4267 auto const extra
= inst
->extra
<LdStaticLocCached
>();
4268 auto const link
= RDS::bindStaticLocal(extra
->func
, extra
->name
);
4269 auto const dst
= dstLoc(0).reg();
4271 emitLea(a
, rVmTl
[link
.handle()], dst
);
4274 void CodeGenerator::cgCheckStaticLocInit(IRInstruction
* inst
) {
4275 auto const src
= srcLoc(0).reg();
4277 emitCmpTVType(a
, KindOfUninit
, src
[RefData::tvOffset() + TVOFF(m_type
)]);
4278 emitFwdJcc(a
, CC_E
, inst
->taken());
4281 void CodeGenerator::cgStaticLocInitCached(IRInstruction
* inst
) {
4282 auto const rdSrc
= srcLoc(0).reg();
4285 // If we're here, the target-cache-local RefData is all zeros, so we
4286 // can initialize it by storing the new value into it's TypedValue
4287 // and incrementing the RefData reference count (which will set it
4290 // We are storing the rdSrc value into the static, but we don't need
4291 // to inc ref it because it's a bytecode invariant that it's not a
4292 // reference counted type.
4293 cgStore(rdSrc
[RefData::tvOffset()], inst
->src(1), srcLoc(1), Width::Full
);
4294 a
. incl (rdSrc
[FAST_REFCOUNT_OFFSET
]);
4296 static_assert(sizeof(RefData::Magic::kMagic
) == sizeof(uint64_t), "");
4297 emitImmStoreq(a
, static_cast<int64_t>(RefData::Magic::kMagic
),
4298 rdSrc
[RefData::magicOffset()]);
4302 void CodeGenerator::cgStoreTypedValue(MemoryRef dst
, SSATmp
* src
, PhysLoc loc
) {
4303 assert(src
->type().needsReg());
4304 auto srcReg0
= loc
.reg(0);
4305 auto srcReg1
= loc
.reg(1);
4306 if (srcReg0
.isSIMD()) {
4307 // Whole typed value is stored in single SIMD reg srcReg0
4308 assert(RuntimeOption::EvalHHIRAllocSIMDRegs
);
4309 assert(srcReg1
== InvalidReg
);
4310 m_as
.movdqu(srcReg0
, refTVData(dst
));
4314 if (src
->type().needsValueReg()) {
4315 assert(srcReg0
!= InvalidReg
);
4316 m_as
.storeq(srcReg0
, refTVData(dst
));
4319 assert(srcReg1
!= InvalidReg
);
4320 emitStoreTVType(m_as
, srcReg1
, refTVType(dst
));
4323 void CodeGenerator::cgStore(MemoryRef dst
, SSATmp
* src
, PhysLoc srcLoc
,
4325 Type type
= src
->type();
4326 if (type
.needsReg()) {
4327 always_assert(width
== Width::Full
);
4328 cgStoreTypedValue(dst
, src
, srcLoc
);
4331 if (width
== Width::Full
) {
4332 emitStoreTVType(m_as
, type
.toDataType(), refTVType(dst
));
4334 if (!src
->type().needsValueReg()) return; // no value to store
4336 auto memRef
= refTVData(dst
);
4337 auto srcReg
= srcLoc
.reg();
4338 if (srcReg
== InvalidReg
) {
4339 always_assert(type
<= (Type::Bool
| Type::Int
| Type::Dbl
|
4340 Type::Arr
| Type::StaticStr
| Type::Cls
));
4341 emitImmStoreq(m_as
, src
->rawVal(), memRef
);
4343 zeroExtendIfBool(m_as
, src
, srcReg
);
4344 emitStoreReg(m_as
, srcReg
, memRef
);
4348 void CodeGenerator::cgLoad(SSATmp
* dst
, PhysLoc dstLoc
, MemoryRef base
,
4350 Type type
= dst
->type();
4351 if (type
.needsReg()) {
4352 return cgLoadTypedValue(dst
, dstLoc
, base
, label
);
4354 if (label
!= NULL
) {
4355 emitTypeCheck(type
, refTVType(base
), refTVData(base
), label
);
4357 auto dstReg
= dstLoc
.reg();
4358 // if dstReg == InvalidReg then there's nothing to load.
4359 if (dstReg
== InvalidReg
) return;
4360 if (type
<= Type::Bool
) {
4361 m_as
.loadl(refTVData(base
), toReg32(dstReg
));
4363 emitLoadReg(m_as
, refTVData(base
), dstReg
);
4367 static MemoryRef
makeMemoryRef(Asm
& as
, Reg64 scratch
,
4369 always_assert(int(value
.r
.index
) != -1);
4370 always_assert(value
.r
.scale
== 1); //TASK(2731486): fix this... use imul?
4371 if (value
.r
.base
!= scratch
&& value
.r
.index
!= scratch
) {
4372 as
.movq(value
.r
.base
, scratch
);
4373 value
.r
.base
= scratch
;
4375 if (value
.r
.base
== scratch
) {
4376 as
.addq(value
.r
.index
, value
.r
.base
);
4377 return value
.r
.base
[value
.r
.disp
];
4379 as
.addq(value
.r
.base
, value
.r
.index
);
4380 return value
.r
.index
[value
.r
.disp
];
4383 // If label is not null and type is not Gen, this method generates a check
4384 // that bails to the label if the loaded typed value doesn't match dst type.
4385 void CodeGenerator::cgLoadTypedValue(SSATmp
* dst
, PhysLoc dstLoc
,
4386 MemoryRef ref
, Block
* label
) {
4387 Type type
= dst
->type();
4388 auto valueDstReg
= dstLoc
.reg(0);
4389 auto typeDstReg
= dstLoc
.reg(1);
4391 if (valueDstReg
.isSIMD()) {
4393 // Whole typed value is stored in single SIMD reg valueDstReg
4394 assert(RuntimeOption::EvalHHIRAllocSIMDRegs
);
4395 assert(typeDstReg
== InvalidReg
);
4396 m_as
.movdqu(refTVData(ref
), valueDstReg
);
4400 if (valueDstReg
== InvalidReg
&& typeDstReg
== InvalidReg
&&
4401 (label
== nullptr || type
== Type::Gen
)) {
4406 bool isResolved
= true;
4408 ref
= resolveRegCollision(typeDstReg
, ref
, isResolved
);
4410 // there was a collision with the registers that could not be resolved.
4411 // Re-enter with a non-indexed MemoryRef (slow path).
4412 cgLoadTypedValue(dst
, dstLoc
, makeMemoryRef(m_as
, m_rScratch
, origRef
),
4416 // Load type if it's not dead
4417 if (typeDstReg
!= InvalidReg
) {
4418 emitLoadTVType(m_as
, refTVType(ref
), typeDstReg
);
4420 emitTypeCheck(type
, typeDstReg
,
4421 valueDstReg
, label
);
4424 emitTypeCheck(type
, refTVType(ref
), refTVData(ref
), label
);
4427 // Load value if it's not dead
4428 if (valueDstReg
== InvalidReg
) return;
4430 m_as
.loadq(refTVData(ref
), valueDstReg
);
4433 // May set isResolved = false to signal that there is no register conflict
4434 // resolution. Callers should take proper action to solve the issue (e.g.
4435 // change the indexed MemoryRef into a base+disp MemoryRef). In this case,
4436 // the returned MemoryRef should be ignored.
4437 MemoryRef
CodeGenerator::resolveRegCollision(PhysReg dst
, MemoryRef memRef
,
4440 Reg64 base
= memRef
.r
.base
;
4441 Reg64 index
= memRef
.r
.index
;
4442 if (int(index
) == -1) {
4444 assert(base
!= m_rScratch
);
4445 // use the scratch register instead
4446 m_as
.movq(base
, m_rScratch
);
4447 return m_rScratch
[memRef
.r
.disp
];
4451 bool scratchTaken
= (base
== m_rScratch
|| index
== m_rScratch
);
4454 // bail, the caller will manage somehow
4458 // use the scratch register instead
4459 m_as
.movq(base
, m_rScratch
);
4460 return m_rScratch
[index
+ memRef
.r
.disp
];
4461 } else if (index
== dst
) {
4463 // bail, the caller will manage somehow
4467 // use the scratch register instead
4468 m_as
.movq(index
, m_rScratch
);
4469 return base
[m_rScratch
+ memRef
.r
.disp
];
4474 void CodeGenerator::cgLdProp(IRInstruction
* inst
) {
4475 cgLoad(inst
->dst(), dstLoc(0),
4476 srcLoc(0).reg()[inst
->src(1)->intVal()],
4480 void CodeGenerator::cgLdMem(IRInstruction
* inst
) {
4481 cgLoad(inst
->dst(), dstLoc(0),
4482 srcLoc(0).reg()[inst
->src(1)->intVal()],
4486 void CodeGenerator::cgLdRef(IRInstruction
* inst
) {
4487 cgLoad(inst
->dst(), dstLoc(0),
4488 srcLoc(0).reg()[RefData::tvOffset()],
4492 void CodeGenerator::cgStringIsset(IRInstruction
* inst
) {
4493 auto strReg
= srcLoc(0).reg();
4494 auto idxReg
= srcLoc(1).reg();
4495 auto dstReg
= dstLoc(0).reg();
4496 if (idxReg
== InvalidReg
) {
4497 m_as
.cmpl(safe_cast
<int32_t>(inst
->src(1)->intVal()),
4498 strReg
[StringData::sizeOff()]);
4500 m_as
.cmpl(r32(idxReg
), strReg
[StringData::sizeOff()]);
4502 m_as
.setnbe(rbyte(dstReg
));
4505 void CodeGenerator::cgProfileArray(IRInstruction
* inst
) {
4506 auto baseReg
= srcLoc(0).reg();
4507 auto handle
= inst
->extra
<ProfileArray
>()->handle
;
4509 // If kPackedKind changes to a value that is not 0, change
4510 // this to a conditional add.
4511 static_assert(ArrayData::ArrayKind::kPackedKind
== 0, "kPackedKind changed");
4512 m_as
. loadzbl(baseReg
[ArrayData::offsetofKind()], r32(m_rScratch
));
4513 m_as
. addl(r32(m_rScratch
),
4514 rVmTl
[handle
+ offsetof(NonPackedArrayProfile
, count
)]);
4517 void CodeGenerator::cgCheckPackedArrayBounds(IRInstruction
* inst
) {
4518 static_assert(ArrayData::sizeofSize() == 4, "");
4519 // We may check packed array bounds on profiled arrays for which
4520 // we do not statically know that they are of kPackedKind.
4521 assert(inst
->taken());
4522 auto arrReg
= srcLoc(0).reg();
4523 auto idxReg
= srcLoc(1).reg();
4524 if (idxReg
== InvalidReg
) {
4525 m_as
.cmpl (safe_cast
<int32_t>(inst
->src(1)->intVal()),
4526 arrReg
[ArrayData::offsetofSize()]);
4528 // ArrayData::m_size is a uint32_t but we need to do a 64-bit comparison
4529 // since idx is KindOfInt64.
4530 m_as
.loadl (arrReg
[ArrayData::offsetofSize()], r32(m_rScratch
));
4531 m_as
.cmpq (idxReg
, m_rScratch
);
4533 emitFwdJcc(CC_BE
, inst
->taken());
4536 void CodeGenerator::cgLdPackedArrayElem(IRInstruction
* inst
) {
4537 if (inst
->src(0)->isConst()) {
4538 // This would require two scratch registers and should be very
4540 CG_PUNT(LdPackedArrayElem
-ConstArray
);
4543 auto const rArr
= srcLoc(0).reg();
4544 auto const rIdx
= srcLoc(1).reg();
4547 // We don't know if we have the last use of rIdx, so we can't
4549 if (rIdx
!= InvalidReg
) {
4551 * gcc 4.8 did something more like:
4553 * lea 1(%base), %scratch ; sizeof(ArrayData) == sizeof(TypedValue)
4555 * movq (%base,%scratch,1), %r1
4556 * movzxb 8(%base,%scratch,1), %r2
4558 * Using this way for now (which is more like what clang produced)
4559 * just because it was 2 bytes smaller.
4561 static_assert(sizeof(TypedValue
) == 16, "");
4562 a
. movq (rIdx
, m_rScratch
);
4563 a
. shlq (0x4, m_rScratch
);
4564 cgLoad(inst
->dst(), dstLoc(0), rArr
[m_rScratch
+ sizeof(ArrayData
)]);
4568 auto const idx
= inst
->src(1)->intVal();
4569 auto const offset
= sizeof(ArrayData
) + idx
* sizeof(TypedValue
);
4570 cgLoad(inst
->dst(), dstLoc(0), rArr
[offset
]);
4573 void CodeGenerator::cgCheckPackedArrayElemNull(IRInstruction
* inst
) {
4574 if (inst
->src(0)->isConst()) {
4575 // This would require two scratch registers and should be very
4577 CG_PUNT(LdPackedArrayElemAddr
-ConstArray
);
4580 auto const rArr
= srcLoc(0).reg();
4581 auto const rIdx
= srcLoc(1).reg();
4584 if (rIdx
!= InvalidReg
) {
4585 static_assert(sizeof(TypedValue
) == 16, "");
4586 a
. movq (rIdx
, m_rScratch
);
4587 a
. shlq (0x4, m_rScratch
);
4588 emitCmpTVType(a
, KindOfNull
,
4589 rArr
[m_rScratch
+ sizeof(ArrayData
) + TVOFF(m_type
)]);
4591 auto const idx
= inst
->src(1)->intVal();
4592 auto const offset
= sizeof(ArrayData
) + idx
* sizeof(TypedValue
);
4593 emitCmpTVType(a
, KindOfNull
, rArr
[offset
+ TVOFF(m_type
)]);
4596 auto const dst
= dstLoc(0).reg();
4597 a
. setne (rbyte(dst
));
4598 a
. movzbl (rbyte(dst
), r32(dst
));
4601 void CodeGenerator::cgCheckBounds(IRInstruction
* inst
) {
4602 auto idx
= inst
->src(0);
4603 auto idxReg
= srcLoc(0).reg();
4604 auto size
= inst
->src(1);
4605 auto sizeReg
= srcLoc(1).reg();
4606 // caller made the check if both sources are constant and never
4607 // generate this opcode
4608 assert(!(idx
->isConst() && size
->isConst()));
4611 auto args
= argGroup();
4613 cgCallHelper(as
, CppCall::direct(throwOOB
),
4614 kVoidDest
, SyncOptions::kSyncPoint
, args
);
4618 if (idxReg
== InvalidReg
) {
4619 assert(idx
->intVal() >= 0); // we would have punted otherwise
4620 auto idxVal
= safe_cast
<int32_t>(idx
->intVal());
4621 m_as
.cmpq(idxVal
, sizeReg
);
4622 unlikelyIfBlock(CC_LE
, throwHelper
);
4626 if (sizeReg
== InvalidReg
) {
4627 assert(size
->intVal() >= 0);
4628 auto sizeVal
= safe_cast
<int32_t>(size
->intVal());
4629 m_as
.cmpq(sizeVal
, idxReg
);
4631 m_as
.cmpq(sizeReg
, idxReg
);
4633 unlikelyIfBlock(CC_AE
, throwHelper
);
4636 void CodeGenerator::cgLdVectorSize(IRInstruction
* inst
) {
4637 DEBUG_ONLY
auto vec
= inst
->src(0);
4638 auto vecReg
= srcLoc(0).reg();
4639 auto dstReg
= dstLoc(0).reg();
4640 assert(vec
->type().strictSubtypeOf(Type::Obj
) &&
4641 vec
->type().getClass() == c_Vector::classof());
4642 m_as
.loadl(vecReg
[c_Vector::sizeOffset()], toReg32(dstReg
));
4645 void CodeGenerator::cgLdVectorBase(IRInstruction
* inst
) {
4646 DEBUG_ONLY
auto vec
= inst
->src(0);
4647 auto vecReg
= srcLoc(0).reg();
4648 auto dstReg
= dstLoc(0).reg();
4649 assert(vec
->type().strictSubtypeOf(Type::Obj
) &&
4650 vec
->type().getClass() == c_Vector::classof());
4651 m_as
.loadq(vecReg
[c_Vector::dataOffset()], dstReg
);
4655 * Given a vector, check if it has a immutable copy and jump to the taken
4658 void CodeGenerator::cgVectorHasImmCopy(IRInstruction
* inst
) {
4659 DEBUG_ONLY
auto vec
= inst
->src(0);
4660 auto vecReg
= srcLoc(0).reg();
4662 assert(vec
->type().strictSubtypeOf(Type::Obj
) &&
4663 vec
->type().getClass() == c_Vector::classof());
4665 // Vector::m_data field holds an address of an ArrayData plus
4666 // sizeof(ArrayData) bytes. We need to check this ArrayData's
4667 // m_count field to see if we need to call Vector::triggerCow().
4668 uint rawPtrOffset
= c_Vector::dataOffset() + kExpectedMPxOffset
;
4670 m_as
.loadq(vecReg
[rawPtrOffset
], m_rScratch
);
4673 m_rScratch
[(int64_t)FAST_REFCOUNT_OFFSET
- (int64_t)sizeof(ArrayData
)]
4675 emitFwdJcc(CC_NE
, inst
->taken());
4679 * Given the base of a vector object, pass it to a helper
4680 * which is responsible for triggering COW.
4682 void CodeGenerator::cgVectorDoCow(IRInstruction
* inst
) {
4683 DEBUG_ONLY
auto vec
= inst
->src(0);
4684 assert(vec
->type().strictSubtypeOf(Type::Obj
) &&
4685 vec
->type().getClass() == c_Vector::classof());
4686 auto args
= argGroup();
4688 cgCallHelper(m_as
, CppCall::direct(triggerCow
),
4689 kVoidDest
, SyncOptions::kSyncPoint
, args
);
4692 void CodeGenerator::cgLdPairBase(IRInstruction
* inst
) {
4693 DEBUG_ONLY
auto pair
= inst
->src(0);
4694 auto pairReg
= srcLoc(0).reg();
4695 assert(pair
->type().strictSubtypeOf(Type::Obj
) &&
4696 pair
->type().getClass() == c_Pair::classof());
4697 m_as
.lea(pairReg
[c_Pair::dataOffset()], dstLoc(0).reg());
4700 void CodeGenerator::cgLdElem(IRInstruction
* inst
) {
4701 auto baseReg
= srcLoc(0).reg();
4702 auto idx
= inst
->src(1);
4703 auto idxReg
= srcLoc(1).reg();
4704 if (idx
->isConst()) {
4705 cgLoad(inst
->dst(), dstLoc(0), baseReg
[idx
->intVal()]);
4707 cgLoad(inst
->dst(), dstLoc(0), baseReg
[idxReg
]);
4711 void CodeGenerator::cgStElem(IRInstruction
* inst
) {
4712 auto baseReg
= srcLoc(0).reg();
4713 auto srcValue
= inst
->src(2);
4714 auto idx
= inst
->src(1);
4715 auto idxReg
= srcLoc(1).reg();
4716 if (idxReg
== InvalidReg
) {
4717 cgStore(baseReg
[idx
->intVal()], srcValue
, srcLoc(2), Width::Full
);
4719 cgStore(baseReg
[idxReg
], srcValue
, srcLoc(2), Width::Full
);
4723 void CodeGenerator::recordSyncPoint(Vout
& v
,
4724 SyncOptions sync
/* = kSyncPoint */) {
4725 auto const marker
= m_curInst
->marker();
4726 assert(m_curInst
->marker().valid());
4728 Offset stackOff
= marker
.spOff();
4730 case SyncOptions::kSyncPointAdjustOne
:
4733 case SyncOptions::kSyncPoint
:
4734 case SyncOptions::kSmashableAndSyncPoint
:
4736 case SyncOptions::kNoSyncPoint
:
4737 // we can get here if we are memory profiling, since we override the
4738 // normal sync settings and sync anyway
4739 assert(RuntimeOption::HHProfServerEnabled
);
4742 Offset pcOff
= marker
.bcOff() - marker
.func()->base();
4743 v
<< syncpoint
{Fixup
{pcOff
, stackOff
}};
4746 void CodeGenerator::recordSyncPoint(Asm
& as
, SyncOptions sync
) {
4747 recordSyncPoint(Vauto().main(as
), sync
);
4750 void CodeGenerator::cgLdMIStateAddr(IRInstruction
* inst
) {
4751 auto base
= srcLoc(0).reg();
4752 int64_t offset
= inst
->src(1)->intVal();
4753 m_as
.lea (base
[offset
], dstLoc(0).reg());
4756 void CodeGenerator::cgLdLoc(IRInstruction
* inst
) {
4757 cgLoad(inst
->dst(), dstLoc(0),
4758 srcLoc(0).reg()[localOffset(inst
->extra
<LdLoc
>()->locId
)]);
4761 void CodeGenerator::cgLdLocAddr(IRInstruction
* inst
) {
4762 auto const fpReg
= srcLoc(0).reg();
4763 auto const offset
= localOffset(inst
->extra
<LdLocAddr
>()->locId
);
4764 if (dstLoc(0).hasReg()) {
4765 m_as
.lea(fpReg
[offset
], dstLoc(0).reg());
4769 void CodeGenerator::cgLdGbl(IRInstruction
* inst
) {
4773 srcLoc(0).reg()[localOffset(inst
->extra
<LdGbl
>()->locId
)],
4778 void CodeGenerator::cgStGbl(IRInstruction
* inst
) {
4779 auto ptr
= srcLoc(0).reg();
4780 auto off
= localOffset(inst
->extra
<StGbl
>()->locId
);
4781 cgStore(ptr
[off
], inst
->src(1), srcLoc(1), Width::Full
);
4784 void CodeGenerator::cgLdStackAddr(IRInstruction
* inst
) {
4785 auto const base
= srcLoc(0).reg();
4786 auto const offset
= cellsToBytes(inst
->extra
<LdStackAddr
>()->offset
);
4787 auto const dst
= dstLoc(0).reg();
4788 m_as
.lea (base
[offset
], dst
);
4791 void CodeGenerator::cgLdStack(IRInstruction
* inst
) {
4792 assert(inst
->taken() == nullptr);
4793 cgLoad(inst
->dst(), dstLoc(0),
4794 srcLoc(0).reg()[cellsToBytes(inst
->extra
<LdStack
>()->offset
)]);
4797 void CodeGenerator::cgGuardStk(IRInstruction
* inst
) {
4798 auto const rSP
= srcLoc(0).reg();
4799 auto const baseOff
= cellsToBytes(inst
->extra
<GuardStk
>()->offset
);
4800 emitTypeGuard(inst
->typeParam(),
4801 rSP
[baseOff
+ TVOFF(m_type
)],
4802 rSP
[baseOff
+ TVOFF(m_data
)]);
4805 void CodeGenerator::cgCheckStk(IRInstruction
* inst
) {
4806 auto const rbase
= srcLoc(0).reg();
4807 auto const baseOff
= cellsToBytes(inst
->extra
<CheckStk
>()->offset
);
4809 emitTypeCheck(inst
->typeParam(), rbase
[baseOff
+ TVOFF(m_type
)],
4810 rbase
[baseOff
+ TVOFF(m_data
)], inst
->taken());
4813 void CodeGenerator::cgGuardLoc(IRInstruction
* inst
) {
4814 auto const rFP
= srcLoc(0).reg();
4815 auto const baseOff
= localOffset(inst
->extra
<GuardLoc
>()->locId
);
4816 emitTypeGuard(inst
->typeParam(),
4817 rFP
[baseOff
+ TVOFF(m_type
)],
4818 rFP
[baseOff
+ TVOFF(m_data
)]);
4821 void CodeGenerator::cgCheckLoc(IRInstruction
* inst
) {
4822 auto const rbase
= srcLoc(0).reg();
4823 auto const baseOff
= localOffset(inst
->extra
<CheckLoc
>()->locId
);
4824 emitTypeCheck(inst
->typeParam(), rbase
[baseOff
+ TVOFF(m_type
)],
4825 rbase
[baseOff
+ TVOFF(m_data
)], inst
->taken());
4829 void CodeGenerator::emitSideExitGuard(Type type
,
4834 type
, typeSrc
, dataSrc
,
4835 [&](ConditionCode cc
) {
4836 auto const sk
= SrcKey(curFunc(), taken
, resumed());
4837 emitBindSideExit(m_mainCode
, m_frozenCode
, ccNegate(cc
), sk
);
4841 void CodeGenerator::cgSideExitGuardLoc(IRInstruction
* inst
) {
4842 auto const fp
= srcLoc(0).reg();
4843 auto const extra
= inst
->extra
<SideExitGuardLoc
>();
4844 emitSideExitGuard(inst
->typeParam(),
4845 fp
[localOffset(extra
->checkedSlot
) + TVOFF(m_type
)],
4846 fp
[localOffset(extra
->checkedSlot
) + TVOFF(m_data
)],
4850 void CodeGenerator::cgSideExitGuardStk(IRInstruction
* inst
) {
4851 auto const sp
= srcLoc(0).reg();
4852 auto const extra
= inst
->extra
<SideExitGuardStk
>();
4854 emitSideExitGuard(inst
->typeParam(),
4855 sp
[cellsToBytes(extra
->checkedSlot
) + TVOFF(m_type
)],
4856 sp
[cellsToBytes(extra
->checkedSlot
) + TVOFF(m_data
)],
4860 void CodeGenerator::cgExitJcc(IRInstruction
* inst
) {
4861 auto const extra
= inst
->extra
<SideExitJccData
>();
4862 auto const sk
= SrcKey(curFunc(), extra
->taken
, resumed());
4864 emitCompare(v
, inst
);
4865 v
<< bindexit
{opToConditionCode(inst
->op()), sk
, extra
->trflags
};
4868 void CodeGenerator::cgExitJccInt(IRInstruction
* inst
) {
4869 auto const extra
= inst
->extra
<SideExitJccData
>();
4870 auto const sk
= SrcKey(curFunc(), extra
->taken
, resumed());
4872 emitCompareInt(v
, inst
);
4873 v
<< bindexit
{opToConditionCode(inst
->op()), sk
, extra
->trflags
};
4876 void CodeGenerator::cgDefMIStateBase(IRInstruction
* inst
) {
4877 assert(dstLoc(0).reg() == rsp
);
4880 void CodeGenerator::cgCheckType(IRInstruction
* inst
) {
4881 auto const src
= inst
->src(0);
4882 auto const rData
= srcLoc(0).reg(0);
4883 auto const rType
= srcLoc(0).reg(1);
4884 assert(rData
!= InvalidReg
);
4886 auto doJcc
= [&](ConditionCode cc
) {
4887 emitFwdJcc(ccNegate(cc
), inst
->taken());
4889 auto doMov
= [&]() {
4890 auto const valDst
= dstLoc(0).reg(0);
4891 auto const typeDst
= dstLoc(0).reg(1);
4892 // TODO: #3626251: XLS: Let Uses say whether a constant is
4893 // allowed, and if not, assign a register.
4894 if (valDst
!= InvalidReg
) {
4895 emitMovRegReg(m_as
, rData
, valDst
);
4897 if (typeDst
!= InvalidReg
) {
4898 if (rType
!= InvalidReg
) emitMovRegReg(m_as
, rType
, typeDst
);
4899 else m_as
.emitImmReg(src
->type().toDataType(), typeDst
);
4903 Type typeParam
= inst
->typeParam();
4904 // CheckTypes that are known to succeed or fail may be kept around
4905 // by the simplifier in case the guard can be relaxed.
4906 if (src
->isA(typeParam
)) {
4907 // src is the target type or better. do nothing.
4910 } else if (src
->type().not(typeParam
)) {
4911 // src is definitely not the target type. always jump.
4912 emitFwdJmp(m_as
, inst
->taken(), m_state
);
4916 if (rType
!= InvalidReg
) {
4917 emitTypeTest(typeParam
, rType
, rData
, doJcc
);
4919 Type srcType
= src
->type();
4920 if (srcType
.isBoxed() && typeParam
.isBoxed()) {
4921 // Nothing to do here, since we check the inner type at the uses
4922 } else if (typeParam
.isSpecialized()) {
4923 // We're just checking the array kind or object class of a value with a
4924 // mostly-known type.
4925 emitSpecializedTypeTest(typeParam
, rData
, doJcc
);
4926 } else if (typeParam
<= Type::Uncounted
&&
4927 ((srcType
<= Type::Str
&& typeParam
.maybe(Type::StaticStr
)) ||
4928 (srcType
<= Type::Arr
&& typeParam
.maybe(Type::StaticArr
)))) {
4929 // We carry Str and Arr operands around without a type register,
4930 // even though they're union types. The static and non-static
4931 // subtypes are distinguised by the refcount field.
4932 m_as
.cmpl(0, rData
[FAST_REFCOUNT_OFFSET
]);
4935 // We should only get here if this CheckType should've been simplified
4936 // away but wasn't for some reason, so do a simple version of what it
4937 // would've. Widen inner types first since CheckType ignores them.
4938 if (srcType
.maybeBoxed()) srcType
|= Type::BoxedCell
;
4939 if (typeParam
.maybeBoxed()) typeParam
|= Type::BoxedCell
;
4941 if (srcType
<= typeParam
) {
4942 // This will always succeed. Do nothing.
4943 } else if (srcType
.not(typeParam
)) {
4944 // This will always fail. Emit an unconditional jmp.
4945 emitFwdJmp(m_as
, inst
->taken(), m_state
);
4951 return folly::format("Bad src: {} and dst: {} types in '{}'",
4952 srcType
, typeParam
, *inst
).str();
4961 void CodeGenerator::cgCheckTypeMem(IRInstruction
* inst
) {
4962 auto const reg
= srcLoc(0).reg();
4963 emitTypeCheck(inst
->typeParam(), reg
[TVOFF(m_type
)],
4964 reg
[TVOFF(m_data
)], inst
->taken());
4967 void CodeGenerator::cgCheckDefinedClsEq(IRInstruction
* inst
) {
4968 auto const clsName
= inst
->extra
<CheckDefinedClsEq
>()->clsName
;
4969 auto const cls
= inst
->extra
<CheckDefinedClsEq
>()->cls
;
4970 auto const ch
= NamedEntity::get(clsName
)->getClassHandle();
4971 auto clsImm
= Immed64(cls
);
4972 if (clsImm
.fits(sz::dword
)) {
4973 m_as
.cmpq(clsImm
.l(), rVmTl
[ch
]);
4975 m_as
.emitImmReg(cls
, m_rScratch
);
4976 m_as
.cmpq(m_rScratch
, rVmTl
[ch
]);
4978 emitFwdJcc(CC_NZ
, inst
->taken());
4981 void CodeGenerator::cgGuardRefs(IRInstruction
* inst
) {
4982 assert(inst
->numSrcs() == 5);
4984 DEBUG_ONLY SSATmp
* nParamsTmp
= inst
->src(1);
4985 SSATmp
* firstBitNumTmp
= inst
->src(2);
4986 SSATmp
* mask64Tmp
= inst
->src(3);
4987 SSATmp
* vals64Tmp
= inst
->src(4);
4989 auto funcPtrReg
= srcLoc(0).reg();
4990 auto nParamsReg
= srcLoc(1).reg();
4991 auto mask64Reg
= srcLoc(3).reg();
4992 auto vals64Reg
= srcLoc(4).reg();
4994 // Get values in place
4995 assert(funcPtrReg
!= InvalidReg
);
4997 assert(nParamsReg
!= InvalidReg
|| nParamsTmp
->isConst());
4999 assert(firstBitNumTmp
->isConst(Type::Int
));
5000 auto firstBitNum
= safe_cast
<int32_t>(firstBitNumTmp
->intVal());
5002 uint64_t mask64
= mask64Tmp
->intVal();
5003 assert(mask64Reg
!= InvalidReg
|| mask64
== uint32_t(mask64
));
5006 uint64_t vals64
= vals64Tmp
->intVal();
5007 assert(vals64Reg
!= InvalidReg
|| vals64
== uint32_t(vals64
));
5008 assert((vals64
& mask64
) == vals64
);
5010 auto const destSK
= SrcKey(curFunc(), m_unit
.bcOff(), resumed());
5011 auto const destSR
= mcg
->tx().getSrcRec(destSK
);
5013 auto thenBody
= [&](Asm
& a
) {
5014 auto bitsOff
= sizeof(uint64_t) * (firstBitNum
/ 64);
5016 auto bitsPtrReg
= m_rScratch
;
5018 if (firstBitNum
== 0) {
5019 bitsOff
= Func::refBitValOff();
5020 bitsPtrReg
= funcPtrReg
;
5022 a
.loadq(funcPtrReg
[Func::sharedOff()], bitsPtrReg
);
5023 bitsOff
-= sizeof(uint64_t);
5026 if (vals64
== 0 || (mask64
& (mask64
- 1)) == 0) {
5027 // If vals64 is zero, or we're testing a single
5028 // bit, we can get away with a single test,
5029 // rather than mask-and-compare
5030 if (mask64Reg
!= InvalidReg
) {
5031 a
.testq (mask64Reg
, bitsPtrReg
[bitsOff
]);
5034 a
.testb((int8_t)mask64
, bitsPtrReg
[bitsOff
]);
5036 a
.testl((int32_t)mask64
, bitsPtrReg
[bitsOff
]);
5039 if (vals64
) cond
= CC_E
;
5041 auto bitsValReg
= m_rScratch
;
5042 a
. loadq (bitsPtrReg
[bitsOff
], bitsValReg
);
5043 if (debug
) bitsPtrReg
= InvalidReg
;
5045 // bitsValReg <- bitsValReg & mask64
5046 if (mask64Reg
!= InvalidReg
) {
5047 a
. andq (mask64Reg
, bitsValReg
);
5048 } else if (mask64
< 256) {
5049 a
. andb ((int8_t)mask64
, rbyte(bitsValReg
));
5051 a
. andl ((int32_t)mask64
, r32(bitsValReg
));
5054 // If bitsValReg != vals64, then goto Exit
5055 if (vals64Reg
!= InvalidReg
) {
5056 a
. cmpq (vals64Reg
, bitsValReg
);
5057 } else if (mask64
< 256) {
5058 assert(vals64
< 256);
5059 a
. cmpb ((int8_t)vals64
, rbyte(bitsValReg
));
5061 a
. cmpl ((int32_t)vals64
, r32(bitsValReg
));
5064 destSR
->emitFallbackJump(m_mainCode
, cond
);
5067 if (firstBitNum
== 0) {
5068 assert(nParamsReg
== InvalidReg
);
5069 // This is the first 64 bits. No need to check
5073 assert(nParamsReg
!= InvalidReg
);
5074 // Check number of args...
5075 m_as
. cmpq (firstBitNum
, nParamsReg
);
5077 if (vals64
!= 0 && vals64
!= mask64
) {
5078 // If we're beyond nParams, then either all params
5079 // are refs, or all params are non-refs, so if vals64
5080 // isn't 0 and isnt mask64, there's no possibility of
5082 destSR
->emitFallbackJump(m_mainCode
, CC_LE
);
5085 ifThenElse(CC_NLE
, thenBody
, /* else */ [&](Asm
& a
) {
5086 // If not special builtin...
5087 a
.testl(AttrVariadicByRef
, funcPtrReg
[Func::attrsOff()]);
5088 destSR
->emitFallbackJump(m_mainCode
, vals64
? CC_Z
: CC_NZ
);
5094 void CodeGenerator::cgLdPropAddr(IRInstruction
* inst
) {
5095 auto const dstReg
= dstLoc(0).reg();
5096 auto const objReg
= srcLoc(0).reg();
5097 auto const prop
= inst
->src(1);
5099 always_assert(objReg
!= InvalidReg
);
5100 always_assert(dstReg
!= InvalidReg
);
5101 a
. lea (objReg
[prop
->intVal()], dstReg
);
5104 void CodeGenerator::cgLdClsMethod(IRInstruction
* inst
) {
5105 auto dstReg
= dstLoc(0).reg();
5106 auto clsReg
= srcLoc(0).reg();
5107 int32_t mSlotVal
= inst
->src(1)->rawVal();
5109 assert(dstReg
!= InvalidReg
);
5111 auto methOff
= int32_t(mSlotVal
* sizeof(Func
*));
5112 m_as
.loadq(clsReg
[methOff
], dstReg
);
5115 void CodeGenerator::cgLookupClsMethodCache(IRInstruction
* inst
) {
5116 auto funcDestReg
= dstLoc(0).reg(0);
5118 auto const& extra
= *inst
->extra
<ClsMethodData
>();
5119 auto const cls
= extra
.clsName
;
5120 auto const method
= extra
.methodName
;
5121 auto const ne
= extra
.namedEntity
;
5122 auto const ch
= StaticMethodCache::alloc(cls
,
5124 getContextName(curClass()));
5126 if (false) { // typecheck
5127 UNUSED TypedValue
* fake_fp
= nullptr;
5128 UNUSED TypedValue
* fake_sp
= nullptr;
5129 const UNUSED Func
* f
= StaticMethodCache::lookup(
5130 ch
, ne
, cls
, method
, fake_fp
);
5132 if (inst
->src(0)->isConst()) {
5133 PUNT(LookupClsMethodCache_const_fp
);
5136 // can raise an error if class is undefined
5138 CppCall::direct(StaticMethodCache::lookup
),
5139 callDest(funcDestReg
),
5140 SyncOptions::kSyncPoint
,
5142 .imm(ch
) // Handle ch
5143 .immPtr(ne
) // NamedEntity* np.second
5144 .immPtr(cls
) // className
5145 .immPtr(method
) // methodName
5146 .reg(srcLoc(0).reg()) // frame pointer
5150 void CodeGenerator::cgLdClsMethodCacheCommon(IRInstruction
* inst
, Offset off
) {
5151 auto dstReg
= dstLoc(0).reg();
5152 if (dstReg
== InvalidReg
) return;
5154 auto const& extra
= *inst
->extra
<ClsMethodData
>();
5155 auto const clsName
= extra
.clsName
;
5156 auto const methodName
= extra
.methodName
;
5157 auto const ch
= StaticMethodCache::alloc(clsName
, methodName
,
5158 getContextName(curClass()));
5159 if (dstReg
!= InvalidReg
) {
5160 m_as
.loadq(rVmTl
[ch
+ off
], dstReg
);
5164 void CodeGenerator::cgLdClsMethodCacheFunc(IRInstruction
* inst
) {
5165 cgLdClsMethodCacheCommon(inst
, offsetof(StaticMethodCache
, m_func
));
5169 void CodeGenerator::cgLdClsMethodCacheCls(IRInstruction
* inst
) {
5170 cgLdClsMethodCacheCommon(inst
, offsetof(StaticMethodCache
, m_cls
));
5174 * Helper to emit getting the value for ActRec's m_this/m_cls slot
5175 * from a This pointer depending on whether the callee method is
5178 void CodeGenerator::emitGetCtxFwdCallWithThis(PhysReg ctxReg
,
5179 bool staticCallee
) {
5181 // Load (this->m_cls | 0x1) into ctxReg.
5182 emitLdLowPtr(m_as
, ctxReg
[ObjectData::getVMClassOffset()],
5183 ctxReg
, sizeof(LowClassPtr
));
5184 m_as
.orq(1, ctxReg
);
5186 // Just incref $this.
5187 emitIncRef(m_as
, ctxReg
);
5192 * This method is similar to emitGetCtxFwdCallWithThis above, but
5193 * whether or not the callee is a static method is unknown at JIT
5194 * time, and that is determined dynamically by looking up into the
5195 * StaticMethodFCache.
5197 void CodeGenerator::cgGetCtxFwdCall(IRInstruction
* inst
) {
5198 PhysReg destCtxReg
= dstLoc(0).reg(0);
5199 auto srcCtxTmp
= inst
->src(0);
5200 auto srcCtxReg
= srcLoc(0).reg(0);
5201 const Func
* callee
= inst
->src(1)->funcVal();
5202 bool withThis
= srcCtxTmp
->isA(Type::Obj
);
5204 // Eagerly move src into the dest reg
5205 emitMovRegReg(m_as
, srcCtxReg
, destCtxReg
);
5208 // If we don't know whether we have a This, we need to check dynamically
5210 m_as
.testb(1, rbyte(destCtxReg
));
5211 m_as
.jcc8(CC_NZ
, End
);
5214 // If we have a This pointer in destCtxReg, then select either This
5215 // or its Class based on whether callee is static or not
5216 emitGetCtxFwdCallWithThis(destCtxReg
, (callee
->attrs() & AttrStatic
));
5218 asm_label(m_as
, End
);
5221 void CodeGenerator::cgLdClsMethodFCacheFunc(IRInstruction
* inst
) {
5222 auto const& extra
= *inst
->extra
<ClsMethodData
>();
5223 auto const clsName
= extra
.clsName
;
5224 auto const methodName
= extra
.methodName
;
5225 auto const dstReg
= dstLoc(0).reg();
5227 auto const ch
= StaticMethodFCache::alloc(
5228 clsName
, methodName
, getContextName(curClass())
5230 if (dstReg
!= InvalidReg
) {
5231 m_as
.loadq(rVmTl
[ch
], dstReg
);
5235 void CodeGenerator::cgLookupClsMethodFCache(IRInstruction
* inst
) {
5236 auto const funcDestReg
= dstLoc(0).reg(0);
5237 auto const cls
= inst
->src(0)->clsVal();
5238 auto const& extra
= *inst
->extra
<ClsMethodData
>();
5239 auto const methName
= extra
.methodName
;
5240 auto const fpReg
= srcLoc(1).reg();
5241 auto const clsName
= cls
->name();
5243 auto ch
= StaticMethodFCache::alloc(
5244 clsName
, methName
, getContextName(curClass())
5247 const Func
* (*lookup
)(
5248 RDS::Handle
, const Class
*, const StringData
*, TypedValue
*) =
5249 StaticMethodFCache::lookup
;
5251 CppCall::direct(lookup
),
5252 callDest(funcDestReg
),
5253 SyncOptions::kSyncPoint
,
5261 void CodeGenerator::emitGetCtxFwdCallWithThisDyn(PhysReg destCtxReg
,
5264 Label NonStaticCall
, End
;
5266 // thisReg is holding $this. Should we pass it to the callee?
5268 rVmTl
[ch
+ offsetof(StaticMethodFCache
, m_static
)]);
5269 m_as
.jcc8(CC_NE
, NonStaticCall
);
5270 // If calling a static method...
5272 // Load (this->m_cls | 0x1) into destCtxReg
5273 emitLdLowPtr(m_as
, thisReg
[ObjectData::getVMClassOffset()],
5274 destCtxReg
, sizeof(LowClassPtr
));
5275 m_as
.orq(1, destCtxReg
);
5278 // Else: calling non-static method
5280 asm_label(m_as
, NonStaticCall
);
5281 emitMovRegReg(m_as
, thisReg
, destCtxReg
);
5282 emitIncRef(m_as
, destCtxReg
);
5284 asm_label(m_as
, End
);
5287 void CodeGenerator::cgGetCtxFwdCallDyn(IRInstruction
* inst
) {
5288 auto srcCtxTmp
= inst
->src(0);
5289 auto srcCtxReg
= srcLoc(0).reg();
5290 auto destCtxReg
= dstLoc(0).reg();
5294 emitMovRegReg(m_as
, srcCtxReg
, destCtxReg
);
5295 auto const t
= srcCtxTmp
->type();
5296 if (t
<= Type::Cctx
) {
5297 // Nothing to do. Forward the context as is.
5299 } else if (t
<= Type::Obj
) {
5300 // We definitely have $this, so always run code emitted by
5301 // emitGetCtxFwdCallWithThisDyn
5303 assert(t
<= Type::Ctx
);
5304 // dynamically check if we have a This pointer and call
5305 // emitGetCtxFwdCallWithThisDyn below
5306 m_as
.testb(1, rbyte(destCtxReg
));
5307 m_as
.jcc8(CC_NZ
, End
);
5310 // If we have a 'this' pointer ...
5311 auto const& extra
= *inst
->extra
<ClsMethodData
>();
5312 auto const ch
= StaticMethodFCache::alloc(
5313 extra
.clsName
, extra
.methodName
, getContextName(curClass())
5315 emitGetCtxFwdCallWithThisDyn(destCtxReg
, destCtxReg
, ch
);
5317 asm_label(m_as
, End
);
5320 void CodeGenerator::cgLdClsPropAddrKnown(IRInstruction
* inst
) {
5321 auto dstReg
= dstLoc(0).reg();
5323 auto cls
= inst
->src(0)->clsVal();
5324 auto name
= inst
->src(1)->strVal();
5326 auto ch
= cls
->sPropHandle(cls
->lookupSProp(name
));
5327 m_as
.lea(rVmTl
[ch
], dstReg
);
5330 RDS::Handle
CodeGenerator::cgLdClsCachedCommon(
5331 IRInstruction
* inst
) {
5332 const StringData
* className
= inst
->src(0)->strVal();
5333 auto ch
= NamedEntity::get(className
)->getClassHandle();
5334 auto dstReg
= dstLoc(0).reg();
5335 if (dstReg
== InvalidReg
) {
5336 m_as
. cmpq (0, rVmTl
[ch
]);
5338 m_as
. loadq (rVmTl
[ch
], dstReg
);
5339 m_as
. testq (dstReg
, dstReg
);
5345 void CodeGenerator::cgLdClsCached(IRInstruction
* inst
) {
5346 auto ch
= cgLdClsCachedCommon(inst
);
5347 unlikelyIfBlock(CC_E
, [&] (Asm
& a
) {
5348 Class
* (*const func
)(Class
**, const StringData
*) =
5349 JIT::lookupKnownClass
;
5351 CppCall::direct(func
),
5353 SyncOptions::kSyncPoint
,
5354 argGroup().addr(rVmTl
, safe_cast
<int32_t>(ch
))
5359 void CodeGenerator::cgLdClsCachedSafe(IRInstruction
* inst
) {
5360 cgLdClsCachedCommon(inst
);
5361 if (Block
* taken
= inst
->taken()) {
5362 emitFwdJcc(CC_Z
, taken
);
5366 void CodeGenerator::cgDerefClsRDSHandle(IRInstruction
* inst
) {
5367 auto const dreg
= dstLoc(0).reg();
5368 auto const ch
= inst
->src(0);
5371 if (dreg
== InvalidReg
) return;
5372 if (ch
->isConst()) {
5373 a
. loadq (rVmTl
[ch
->rdsHandleVal()], dreg
);
5375 a
. loadq (rVmTl
[srcLoc(0).reg()], dreg
);
5379 void CodeGenerator::cgLdCls(IRInstruction
* inst
) {
5380 auto const ch
= ClassCache::alloc();
5381 RDS::recordRds(ch
, sizeof(ClassCache
),
5382 "ClassCache", curFunc()->fullName()->data());
5385 CppCall::direct(ClassCache::lookup
),
5387 SyncOptions::kSyncPoint
,
5388 argGroup().imm(ch
).ssa(0/*className*/));
5391 void CodeGenerator::cgLdClsCns(IRInstruction
* inst
) {
5392 auto const extra
= inst
->extra
<LdClsCns
>();
5393 auto const link
= RDS::bindClassConstant(extra
->clsName
, extra
->cnsName
);
5394 cgLoad(inst
->dst(), dstLoc(0), rVmTl
[link
.handle()], inst
->taken());
5397 void CodeGenerator::cgLookupClsCns(IRInstruction
* inst
) {
5398 auto const extra
= inst
->extra
<LookupClsCns
>();
5399 auto const link
= RDS::bindClassConstant(extra
->clsName
, extra
->cnsName
);
5402 CppCall::direct(JIT::lookupClassConstantTv
),
5404 SyncOptions::kSyncPoint
,
5406 .addr(rVmTl
, safe_cast
<int32_t>(link
.handle()))
5407 .immPtr(NamedEntity::get(extra
->clsName
))
5408 .immPtr(extra
->clsName
)
5409 .immPtr(extra
->cnsName
)
5413 void CodeGenerator::cgLdCns(IRInstruction
* inst
) {
5414 const StringData
* cnsName
= inst
->src(0)->strVal();
5416 auto const ch
= makeCnsHandle(cnsName
, false);
5417 // Has an unlikely branch to a LookupCns
5418 cgLoad(inst
->dst(), dstLoc(0), rVmTl
[ch
], inst
->taken());
5421 void CodeGenerator::cgLookupCnsCommon(IRInstruction
* inst
) {
5422 SSATmp
* cnsNameTmp
= inst
->src(0);
5424 assert(cnsNameTmp
->isConst(Type::StaticStr
));
5426 auto const cnsName
= cnsNameTmp
->strVal();
5427 auto const ch
= makeCnsHandle(cnsName
, false);
5429 auto args
= argGroup();
5430 args
.addr(rVmTl
, safe_cast
<int32_t>(ch
))
5432 .imm(inst
->op() == LookupCnsE
);
5435 CppCall::direct(lookupCnsHelper
),
5437 SyncOptions::kSyncPoint
,
5441 void CodeGenerator::cgLookupCns(IRInstruction
* inst
) {
5442 cgLookupCnsCommon(inst
);
5445 void CodeGenerator::cgLookupCnsE(IRInstruction
* inst
) {
5446 cgLookupCnsCommon(inst
);
5449 void CodeGenerator::cgLookupCnsU(IRInstruction
* inst
) {
5450 SSATmp
* cnsNameTmp
= inst
->src(0);
5451 SSATmp
* fallbackNameTmp
= inst
->src(1);
5453 const StringData
* cnsName
= cnsNameTmp
->strVal();
5455 const StringData
* fallbackName
= fallbackNameTmp
->strVal();
5456 auto const fallbackCh
= makeCnsHandle(fallbackName
, false);
5458 auto args
= argGroup();
5459 args
.addr(rVmTl
, safe_cast
<int32_t>(fallbackCh
))
5461 .immPtr(fallbackName
);
5464 CppCall::direct(lookupCnsUHelper
),
5466 SyncOptions::kSyncPoint
,
5470 void CodeGenerator::cgAKExists(IRInstruction
* inst
) {
5471 SSATmp
* arr
= inst
->src(0);
5472 SSATmp
* key
= inst
->src(1);
5474 bool (*obj_int_helper
)(ObjectData
*, int64_t) = &ak_exist_int_obj
;
5475 bool (*obj_str_helper
)(ObjectData
*, StringData
*) = &ak_exist_string_obj
;
5476 bool (*arr_str_helper
)(ArrayData
*, StringData
*) = &ak_exist_string
;
5478 if (key
->type() <= Type::Null
) {
5479 if (arr
->isA(Type::Arr
)) {
5481 CppCall::direct(arr_str_helper
),
5483 SyncOptions::kNoSyncPoint
,
5484 argGroup().ssa(0/*arr*/).immPtr(staticEmptyString()));
5486 m_as
.movq(0, dstLoc(0).reg());
5491 auto helper_func
= arr
->isA(Type::Obj
)
5492 ? (key
->isA(Type::Int
)
5493 ? CppCall::direct(obj_int_helper
)
5494 : CppCall::direct(obj_str_helper
))
5495 : (key
->isA(Type::Int
)
5496 ? CppCall::array(&g_array_funcs
.existsInt
)
5497 : CppCall::direct(arr_str_helper
));
5502 SyncOptions::kNoSyncPoint
,
5503 argGroup().ssa(0/*arr*/).ssa(1/*key*/));
5506 void CodeGenerator::cgLdGblAddr(IRInstruction
* inst
) {
5507 auto dstReg
= dstLoc(0).reg();
5509 CppCall::direct(ldGblAddrHelper
),
5511 SyncOptions::kNoSyncPoint
,
5513 m_as
.testq(dstReg
, dstReg
);
5514 emitFwdJcc(CC_Z
, inst
->taken());
5517 void CodeGenerator::emitTestZero(SSATmp
* src
, PhysLoc srcLoc
) {
5518 emitTestZero(Vauto().main(m_as
), src
, srcLoc
);
5521 void CodeGenerator::emitTestZero(Vout
& v
, SSATmp
* src
, PhysLoc srcLoc
) {
5522 auto reg
= srcLoc
.reg();
5525 * If src is const, normally a earlier optimization pass should have
5526 * converted the thing testing this condition into something
5527 * unconditional. So rather than supporting constants efficiently
5528 * here, we just materialize the value into a register.
5530 if (reg
== InvalidReg
) {
5532 v
<< ldimm
{src
->rawVal(), reg
};
5535 if (src
->isA(Type::Bool
)) {
5536 v
<< testb
{reg
, reg
};
5538 v
<< testq
{reg
, reg
};
5542 void CodeGenerator::cgJmpZero(IRInstruction
* inst
) {
5543 emitTestZero(inst
->src(0), srcLoc(0));
5544 emitFwdJcc(CC_Z
, inst
->taken());
5547 void CodeGenerator::cgJmpNZero(IRInstruction
* inst
) {
5548 emitTestZero(inst
->src(0), srcLoc(0));
5549 emitFwdJcc(CC_NZ
, inst
->taken());
5552 void CodeGenerator::cgReqBindJmpZero(IRInstruction
* inst
) {
5553 // TODO(#2404427): prepareForTestAndSmash?
5555 emitTestZero(v
, inst
->src(0), srcLoc(0));
5556 emitReqBindJcc(v
, CC_Z
, inst
->extra
<ReqBindJmpZero
>());
5559 void CodeGenerator::cgReqBindJmpNZero(IRInstruction
* inst
) {
5560 // TODO(#2404427): prepareForTestAndSmash?
5562 emitTestZero(v
, inst
->src(0), srcLoc(0));
5563 emitReqBindJcc(v
, CC_NZ
, inst
->extra
<ReqBindJmpNZero
>());
5566 void CodeGenerator::cgSideExitJmpZero(IRInstruction
* inst
) {
5567 auto const extra
= inst
->extra
<SideExitJccData
>();
5568 auto const sk
= SrcKey(curFunc(), extra
->taken
, resumed());
5570 emitTestZero(v
, inst
->src(0), srcLoc(0));
5571 v
<< bindexit
{opToConditionCode(inst
->op()), sk
, extra
->trflags
};
5574 void CodeGenerator::cgSideExitJmpNZero(IRInstruction
* inst
) {
5575 auto const extra
= inst
->extra
<SideExitJccData
>();
5576 auto const sk
= SrcKey(curFunc(), extra
->taken
, resumed());
5578 emitTestZero(v
, inst
->src(0), srcLoc(0));
5579 v
<< bindexit
{opToConditionCode(inst
->op()), sk
, extra
->trflags
};
5582 void CodeGenerator::cgJmp(IRInstruction
* inst
) {
5583 if (!m_state
.noTerminalJmp
) {
5584 emitFwdJmp(m_as
, inst
->taken(), m_state
);
5588 void CodeGenerator::cgJmpIndirect(IRInstruction
* inst
) {
5590 v
<< jmpr
{srcLoc(0).reg()};
5593 void CodeGenerator::cgCheckInit(IRInstruction
* inst
) {
5594 Block
* label
= inst
->taken();
5596 SSATmp
* src
= inst
->src(0);
5598 if (src
->type().not(Type::Uninit
)) return;
5600 auto typeReg
= srcLoc(0).reg(1);
5601 assert(typeReg
!= InvalidReg
);
5603 static_assert(KindOfUninit
== 0, "cgCheckInit assumes KindOfUninit == 0");
5604 m_as
.testb (rbyte(typeReg
), rbyte(typeReg
));
5605 emitFwdJcc(CC_Z
, label
);
5608 void CodeGenerator::cgCheckInitMem(IRInstruction
* inst
) {
5609 Block
* label
= inst
->taken();
5611 SSATmp
* base
= inst
->src(0);
5612 int64_t offset
= inst
->src(1)->intVal();
5613 Type t
= base
->type().deref();
5614 if (t
.not(Type::Uninit
)) return;
5615 auto basereg
= srcLoc(0).reg();
5616 emitCmpTVType(m_as
, KindOfUninit
, basereg
[offset
+ TVOFF(m_type
)]);
5617 emitFwdJcc(CC_Z
, label
);
5620 void CodeGenerator::cgCheckSurpriseFlags(IRInstruction
* inst
) {
5621 emitTestSurpriseFlags(m_as
);
5622 emitFwdJcc(CC_NZ
, inst
->taken());
5625 void CodeGenerator::cgCheckCold(IRInstruction
* inst
) {
5626 Block
* label
= inst
->taken();
5627 TransID transId
= inst
->extra
<CheckCold
>()->transId
;
5628 auto counterAddr
= mcg
->tx().profData()->transCounterAddr(transId
);
5630 emitLoadImm(m_as
, uint64_t(counterAddr
), m_rScratch
);
5631 m_as
.decq(m_rScratch
[0]);
5632 emitFwdJcc(CC_LE
, label
);
5635 static const StringData
* s_ReleaseVV
= makeStaticString("ReleaseVV");
5637 void CodeGenerator::cgReleaseVVOrExit(IRInstruction
* inst
) {
5638 auto* const label
= inst
->taken();
5639 auto const rFp
= srcLoc(0).reg();
5641 TargetProfile
<ReleaseVVProfile
> profile(m_unit
.context(), m_curInst
->marker(),
5643 if (profile
.profiling()) {
5644 m_as
.incw(rVmTl
[profile
.handle() + offsetof(ReleaseVVProfile
, executed
)]);
5647 m_as
.cmpq(0, rFp
[AROFF(m_varEnv
)]);
5649 bool releaseUnlikely
= true;
5650 if (profile
.optimizing()) {
5651 auto const data
= profile
.data(ReleaseVVProfile::reduce
);
5652 FTRACE(3, "cgReleaseVVOrExit({}): percentReleased = {}\n",
5653 inst
->toString(), data
.percentReleased());
5654 if (data
.percentReleased() >= RuntimeOption::EvalJitPGOReleaseVVMinPercent
)
5656 releaseUnlikely
= false;
5659 ifBlock(CC_NZ
, [&] (Asm
& a
) {
5660 if (profile
.profiling()) {
5661 a
.incw(rVmTl
[profile
.handle() + offsetof(ReleaseVVProfile
, released
)]);
5663 a
.testl(ActRec::kExtraArgsBit
, rFp
[AROFF(m_varEnv
)]);
5664 emitFwdJcc(a
, CC_Z
, label
);
5667 CppCall::direct(static_cast<void (*)(ActRec
*)>(ExtraArgs::deallocate
)),
5669 SyncOptions::kSyncPoint
,
5676 void CodeGenerator::cgBoxPtr(IRInstruction
* inst
) {
5677 auto base
= srcLoc(0).reg();
5678 auto dstReg
= dstLoc(0).reg();
5679 emitMovRegReg(m_as
, base
, dstReg
);
5680 emitTypeTest(Type::BoxedCell
, base
[TVOFF(m_type
)],
5681 base
[TVOFF(m_data
)],
5682 [&](ConditionCode cc
) {
5683 ifThen(m_as
, ccNegate(cc
), [&](Asm
& a
) {
5685 CppCall::direct(tvBox
),
5687 SyncOptions::kNoSyncPoint
,
5688 argGroup().ssa(0/*addr*/));
5693 void CodeGenerator::cgConcatCellCell(IRInstruction
* inst
) {
5694 // Supported cases are all simplified into other instructions
5695 CG_PUNT(cgConcatCellCell
);
5698 void CodeGenerator::cgInterpOneCommon(IRInstruction
* inst
) {
5699 int64_t pcOff
= inst
->extra
<InterpOneData
>()->bcOff
;
5701 auto opc
= *(curFunc()->unit()->at(pcOff
));
5702 void* interpOneHelper
= interpOneEntryPoints
[opc
];
5704 auto dstReg
= InvalidReg
;
5705 if (inst
->src(1)->isConst()) {
5706 PUNT(InterpOneCommon_const_fp
);
5709 CppCall::direct(reinterpret_cast<void (*)()>(interpOneHelper
)),
5711 SyncOptions::kSyncPoint
,
5712 argGroup().ssa(1/*fp*/).ssa(0/*sp*/).imm(pcOff
));
5715 void CodeGenerator::cgInterpOne(IRInstruction
* inst
) {
5716 cgInterpOneCommon(inst
);
5718 auto const& extra
= *inst
->extra
<InterpOne
>();
5719 auto newSpReg
= dstLoc(0).reg();
5720 assert(newSpReg
== srcLoc(0).reg());
5722 auto spAdjustBytes
= cellsToBytes(extra
.cellsPopped
- extra
.cellsPushed
);
5723 if (spAdjustBytes
!= 0) {
5724 m_as
.addq(spAdjustBytes
, newSpReg
);
5728 void CodeGenerator::cgInterpOneCF(IRInstruction
* inst
) {
5729 cgInterpOneCommon(inst
);
5731 m_as
.loadq(rVmTl
[RDS::kVmfpOff
], rVmFp
);
5732 m_as
.loadq(rVmTl
[RDS::kVmspOff
], rVmSp
);
5734 emitServiceReq(m_mainCode
, REQ_RESUME
);
5737 void CodeGenerator::cgContEnter(IRInstruction
* inst
) {
5738 // ContEnter does not directly use SP, but the generator body we are jumping
5739 // to obviously does. We depend on SP via srcLoc(0) to avoid last SpillStack
5740 // be optimized away.
5741 auto curFpReg
= srcLoc(1).reg();
5742 auto genFpReg
= srcLoc(2).reg();
5743 auto addrReg
= srcLoc(3).reg();
5744 auto returnOff
= safe_cast
<int32_t>(inst
->src(4)->intVal());
5745 assert(srcLoc(0).reg() == rVmSp
);
5746 assert(curFpReg
== rVmFp
);
5748 Label Prologue
, End
;
5750 m_as
. storeq (curFpReg
, genFpReg
[AROFF(m_sfp
)]);
5751 m_as
. storel (returnOff
, genFpReg
[AROFF(m_soff
)]);
5752 m_as
. movq (genFpReg
, curFpReg
);
5755 asm_label(m_as
, Prologue
);
5756 m_as
. pop (curFpReg
[AROFF(m_savedRip
)]);
5757 m_as
. jmp (addrReg
);
5759 asm_label(m_as
, End
);
5760 m_as
. call (Prologue
);
5763 void CodeGenerator::cgContPreNext(IRInstruction
* inst
) {
5764 auto contReg
= srcLoc(0).reg();
5765 auto checkStarted
= inst
->src(1)->boolVal();
5766 auto stateOff
= BaseGenerator::stateOff();
5768 static_assert(uint8_t(BaseGenerator::State::Created
) == 0, "used below");
5769 static_assert(uint8_t(BaseGenerator::State::Started
) == 1, "used below");
5771 // Take exit if state != 1 (checkStarted) or state > 1 (!checkStarted).
5772 m_as
.cmpb(1, contReg
[stateOff
]);
5773 emitFwdJcc(checkStarted
? CC_NE
: CC_A
, inst
->taken());
5775 // Set generator state as Running.
5776 m_as
.storeb(int8_t(BaseGenerator::State::Running
), contReg
[stateOff
]);
5779 void CodeGenerator::cgContStartedCheck(IRInstruction
* inst
) {
5780 auto contReg
= srcLoc(0).reg();
5781 auto stateOff
= BaseGenerator::stateOff();
5783 static_assert(uint8_t(BaseGenerator::State::Created
) == 0, "used below");
5785 // Take exit if state == 0.
5786 m_as
.testb(int8_t(0xff), contReg
[stateOff
]);
5787 emitFwdJcc(CC_Z
, inst
->taken());
5790 void CodeGenerator::cgContValid(IRInstruction
* inst
) {
5791 auto contReg
= srcLoc(0).reg();
5792 auto dstReg
= dstLoc(0).reg();
5793 auto stateOff
= BaseGenerator::stateOff();
5795 // Return 1 if generator state is not Done.
5796 m_as
.cmpb(int8_t(BaseGenerator::State::Done
), contReg
[stateOff
]);
5797 m_as
.setne(rbyte(dstReg
));
5798 m_as
.movzbl(rbyte(dstReg
), r32(dstReg
));
5801 void CodeGenerator::cgContArIncKey(IRInstruction
* inst
) {
5802 auto contArReg
= srcLoc(0).reg();
5803 m_as
.incq(contArReg
[CONTOFF(m_key
) + TVOFF(m_data
) -
5804 c_Generator::arOff()]);
5807 void CodeGenerator::cgContArUpdateIdx(IRInstruction
* inst
) {
5808 auto contArReg
= srcLoc(0).reg();
5809 int64_t off
= CONTOFF(m_index
) - c_Generator::arOff();
5810 auto newIdx
= inst
->src(1);
5811 auto newIdxReg
= srcLoc(1).reg();
5813 // this is hacky and awful oh god
5814 if (newIdx
->isConst()) {
5815 auto const val
= newIdx
->rawVal();
5816 m_as
.emitImmReg(val
, m_rScratch
);
5817 m_as
.cmpq (m_rScratch
, contArReg
[off
]);
5818 m_as
.cload_reg64_disp_reg64(CC_G
, contArReg
, off
, m_rScratch
);
5820 m_as
.loadq (contArReg
[off
], m_rScratch
);
5821 m_as
.cmpq (m_rScratch
, newIdxReg
);
5822 m_as
.cmov_reg64_reg64(CC_G
, newIdxReg
, m_rScratch
);
5824 m_as
.storeq (m_rScratch
, contArReg
[off
]);
5827 void CodeGenerator::cgLdContActRec(IRInstruction
* inst
) {
5828 auto dest
= dstLoc(0).reg();
5829 auto base
= srcLoc(0).reg();
5830 ptrdiff_t offset
= BaseGenerator::arOff();
5832 m_as
.lea (base
[offset
], dest
) ;
5835 void CodeGenerator::emitLdRaw(IRInstruction
* inst
, size_t extraOff
) {
5836 auto destReg
= dstLoc(0).reg();
5837 auto offset
= inst
->extra
<RawMemData
>()->info().offset
;
5838 auto src
= srcLoc(0).reg()[offset
+ extraOff
];
5840 switch (inst
->extra
<RawMemData
>()->info().size
) {
5841 case sz::byte
: m_as
.loadzbl(src
, r32(destReg
)); break;
5843 m_as
.loadl(src
, r32(destReg
));
5844 if (inst
->extra
<RawMemData
>()->type
== RawMemData::FuncNumParams
) {
5845 // See Func::finishedEmittingParams and Func::numParams for rationale
5846 m_as
.shrl(0x1, r32(destReg
));
5849 case sz::qword
: m_as
.loadq(src
, destReg
); break;
5850 default: not_implemented();
5854 void CodeGenerator::cgLdRaw(IRInstruction
* inst
) {
5858 void CodeGenerator::cgLdContArRaw(IRInstruction
* inst
) {
5859 emitLdRaw(inst
, -BaseGenerator::arOff());
5862 void CodeGenerator::emitStRaw(IRInstruction
* inst
, size_t offset
, int size
) {
5863 auto dst
= srcLoc(0).reg()[offset
];
5864 auto src
= inst
->src(1);
5865 auto srcReg
= srcLoc(1).reg();
5867 if (srcReg
== InvalidReg
) {
5868 auto val
= Immed64(src
->type().hasRawVal() ? src
->rawVal() : 0);
5870 case sz::byte
: m_as
.storeb(val
.b(), dst
); break;
5871 case sz::dword
: m_as
.storel(val
.l(), dst
); break;
5872 case sz::qword
: emitImmStoreq(m_as
, val
.q(), dst
); break;
5873 default: not_implemented();
5877 case sz::byte
: m_as
.storeb(rbyte(srcReg
), dst
); break;
5878 case sz::dword
: m_as
.storel(r32(srcReg
), dst
); break;
5879 case sz::qword
: m_as
.storeq(srcReg
, dst
); break;
5880 default: not_implemented();
5885 void CodeGenerator::cgStRaw(IRInstruction
* inst
) {
5886 auto const info
= inst
->extra
<RawMemData
>()->info();
5887 emitStRaw(inst
, info
.offset
, info
.size
);
5890 void CodeGenerator::cgStContArRaw(IRInstruction
* inst
) {
5891 auto const info
= inst
->extra
<RawMemData
>()->info();
5892 emitStRaw(inst
, -BaseGenerator::arOff() + info
.offset
, info
.size
);
5895 void CodeGenerator::cgLdContArValue(IRInstruction
* inst
) {
5896 auto contArReg
= srcLoc(0).reg();
5897 const int64_t valueOff
= CONTOFF(m_value
);
5898 int64_t off
= valueOff
- c_Generator::arOff();
5899 cgLoad(inst
->dst(), dstLoc(0), contArReg
[off
], inst
->taken());
5902 void CodeGenerator::cgStContArValue(IRInstruction
* inst
) {
5903 auto contArReg
= srcLoc(0).reg();
5904 auto value
= inst
->src(1);
5905 auto valueLoc
= srcLoc(1);
5906 const int64_t valueOff
= CONTOFF(m_value
);
5907 int64_t off
= valueOff
- c_Generator::arOff();
5908 cgStore(contArReg
[off
], value
, valueLoc
, Width::Full
);
5911 void CodeGenerator::cgLdContArKey(IRInstruction
* inst
) {
5912 auto contArReg
= srcLoc(0).reg();
5913 const int64_t keyOff
= CONTOFF(m_key
);
5914 int64_t off
= keyOff
- c_Generator::arOff();
5915 cgLoad(inst
->dst(), dstLoc(0), contArReg
[off
], inst
->taken());
5918 void CodeGenerator::cgStContArKey(IRInstruction
* inst
) {
5919 auto contArReg
= srcLoc(0).reg();
5920 auto value
= inst
->src(1);
5921 auto valueLoc
= srcLoc(1);
5923 const int64_t keyOff
= CONTOFF(m_key
);
5924 int64_t off
= keyOff
- c_Generator::arOff();
5925 cgStore(contArReg
[off
], value
, valueLoc
, Width::Full
);
5928 void CodeGenerator::cgStAsyncArRaw(IRInstruction
* inst
) {
5929 auto const info
= inst
->extra
<RawMemData
>()->info();
5930 emitStRaw(inst
, -c_AsyncFunctionWaitHandle::arOff() + info
.offset
,
5934 void CodeGenerator::cgStAsyncArResult(IRInstruction
* inst
) {
5935 auto asyncArReg
= srcLoc(0).reg();
5936 auto value
= inst
->src(1);
5937 auto valueLoc
= srcLoc(1);
5938 const int64_t off
= c_AsyncFunctionWaitHandle::resultOff()
5939 - c_AsyncFunctionWaitHandle::arOff();
5940 cgStore(asyncArReg
[off
], value
, valueLoc
, Width::Full
);
5943 void CodeGenerator::cgLdAsyncArParentChain(IRInstruction
* inst
) {
5944 auto asyncArReg
= srcLoc(0).reg();
5945 auto dstReg
= dstLoc(0).reg();
5946 const int64_t off
= c_AsyncFunctionWaitHandle::parentChainOff()
5947 - c_AsyncFunctionWaitHandle::arOff();
5948 m_as
.loadq(asyncArReg
[off
], dstReg
);
5951 void CodeGenerator::cgAFWHBlockOn(IRInstruction
* inst
) {
5952 auto parentArReg
= srcLoc(0).reg();
5953 auto childReg
= srcLoc(1).reg();
5954 const int8_t blocked
= c_WaitHandle::toKindState(
5955 c_WaitHandle::Kind::AsyncFunction
, c_BlockableWaitHandle::STATE_BLOCKED
);
5956 const int64_t firstParentOff
= c_WaitableWaitHandle::parentChainOff()
5957 + AsioBlockableChain::firstParentOff();
5958 const int64_t stateToArOff
= c_AsyncFunctionWaitHandle::stateOff()
5959 - c_AsyncFunctionWaitHandle::arOff();
5960 const int64_t nextParentToArOff
= c_AsyncFunctionWaitHandle::blockableOff()
5961 + AsioBlockable::bitsOff()
5962 - c_AsyncFunctionWaitHandle::arOff();
5963 const int64_t childToArOff
= c_AsyncFunctionWaitHandle::childOff()
5964 - c_AsyncFunctionWaitHandle::arOff();
5965 const int64_t blockableToArOff
= c_AsyncFunctionWaitHandle::blockableOff()
5966 - c_AsyncFunctionWaitHandle::arOff();
5968 // parent->setState(STATE_BLOCKED);
5969 m_as
.storeb(blocked
, parentArReg
[stateToArOff
]);
5971 // parent->m_blockable.m_bits = child->m_parentChain.m_firstParent|Kind::BWH;
5972 assert(uint8_t(AsioBlockable::Kind::BlockableWaitHandle
) == 0);
5973 m_as
.loadq (childReg
[firstParentOff
], m_rScratch
);
5974 m_as
.storeq(m_rScratch
, parentArReg
[nextParentToArOff
]);
5976 // child->m_parentChain.m_firstParent = &parent->m_blockable;
5977 m_as
.lea (parentArReg
[blockableToArOff
], m_rScratch
);
5978 m_as
.storeq(m_rScratch
, childReg
[firstParentOff
]);
5980 // parent->m_child = child;
5981 m_as
.storeq(childReg
, parentArReg
[childToArOff
]);
5984 void CodeGenerator::cgIsWaitHandle(IRInstruction
* inst
) {
5985 auto const robj
= srcLoc(0).reg();
5986 auto const rdst
= dstLoc(0).reg();
5990 ObjectData::IsWaitHandle
< 0xff,
5991 "we use byte instructions for IsWaitHandle"
5993 a
. testb (ObjectData::IsWaitHandle
, robj
[ObjectData::attributeOff()]);
5994 a
. setnz (rbyte(rdst
));
5997 void CodeGenerator::cgLdWHState(IRInstruction
* inst
) {
5998 auto const robj
= srcLoc(0).reg();
5999 auto const rdst
= dstLoc(0).reg();
6001 a
. loadzbl (robj
[ObjectData::whStateOffset()], r32(rdst
));
6002 a
. andb ((int8_t)0x0F, rbyte(rdst
));
6005 void CodeGenerator::cgLdWHResult(IRInstruction
* inst
) {
6006 auto const robj
= srcLoc(0).reg();
6007 cgLoad(inst
->dst(), dstLoc(0), robj
[c_WaitHandle::resultOff()]);
6010 void CodeGenerator::cgLdAFWHActRec(IRInstruction
* inst
) {
6011 auto const dest
= dstLoc(0).reg();
6012 auto const base
= srcLoc(0).reg();
6013 auto asyncArOffset
= c_AsyncFunctionWaitHandle::arOff();
6014 m_as
.lea (base
[asyncArOffset
], dest
);
6017 void CodeGenerator::cgLdResumableArObj(IRInstruction
* inst
) {
6018 auto const dstReg
= dstLoc(0).reg();
6019 auto const resumableArReg
= srcLoc(0).reg();
6020 auto const objectOff
= Resumable::objectOff() - Resumable::arOff();
6021 m_as
.lea (resumableArReg
[objectOff
], dstReg
);
6024 void CodeGenerator::cgIterInit(IRInstruction
* inst
) {
6025 cgIterInitCommon(inst
);
6028 void CodeGenerator::cgIterInitK(IRInstruction
* inst
) {
6029 cgIterInitCommon(inst
);
6032 void CodeGenerator::cgWIterInit(IRInstruction
* inst
) {
6033 cgIterInitCommon(inst
);
6036 void CodeGenerator::cgWIterInitK(IRInstruction
* inst
) {
6037 cgIterInitCommon(inst
);
6040 void CodeGenerator::cgIterInitCommon(IRInstruction
* inst
) {
6041 bool isInitK
= inst
->op() == IterInitK
|| inst
->op() == WIterInitK
;
6042 bool isWInit
= inst
->op() == WIterInit
|| inst
->op() == WIterInitK
;
6044 PhysReg fpReg
= srcLoc(1).reg();
6045 int iterOffset
= this->iterOffset(inst
->extra
<IterData
>()->iterId
);
6046 int valLocalOffset
= localOffset(inst
->extra
<IterData
>()->valId
);
6047 SSATmp
* src
= inst
->src(0);
6048 auto args
= argGroup();
6049 args
.addr(fpReg
, iterOffset
).ssa(0/*src*/);
6050 if (src
->isA(Type::Arr
)) {
6051 args
.addr(fpReg
, valLocalOffset
);
6053 args
.addr(fpReg
, localOffset(inst
->extra
<IterData
>()->keyId
));
6054 } else if (isWInit
) {
6057 TCA helperAddr
= isWInit
? (TCA
)new_iter_array_key
<true> :
6058 isInitK
? (TCA
)new_iter_array_key
<false> : (TCA
)new_iter_array
;
6061 CppCall::direct(reinterpret_cast<void (*)()>(helperAddr
)),
6063 SyncOptions::kSyncPoint
,
6066 assert(src
->type() <= Type::Obj
);
6067 args
.imm(uintptr_t(curClass())).addr(fpReg
, valLocalOffset
);
6069 args
.addr(fpReg
, localOffset(inst
->extra
<IterData
>()->keyId
));
6073 // new_iter_object decrefs its src object if it propagates an
6074 // exception out, so we use kSyncPointAdjustOne, which adjusts the
6075 // stack pointer by 1 stack element on an unwind, skipping over
6077 cgCallHelper(m_as
, CppCall::direct(new_iter_object
), callDest(inst
),
6078 SyncOptions::kSyncPointAdjustOne
, args
);
6082 void CodeGenerator::cgMIterInit(IRInstruction
* inst
) {
6083 cgMIterInitCommon(inst
);
6086 void CodeGenerator::cgMIterInitK(IRInstruction
* inst
) {
6087 cgMIterInitCommon(inst
);
6090 void CodeGenerator::cgMIterInitCommon(IRInstruction
* inst
) {
6091 PhysReg fpReg
= srcLoc(1).reg();
6092 int iterOffset
= this->iterOffset(inst
->extra
<IterData
>()->iterId
);
6093 int valLocalOffset
= localOffset(inst
->extra
<IterData
>()->valId
);
6094 SSATmp
* src
= inst
->src(0);
6096 auto args
= argGroup();
6097 args
.addr(fpReg
, iterOffset
).ssa(0/*src*/);
6099 assert(src
->type().isBoxed());
6100 auto innerType
= src
->type().innerType();
6101 assert(innerType
.isKnownDataType());
6103 if (innerType
<= Type::Arr
) {
6104 args
.addr(fpReg
, valLocalOffset
);
6105 if (inst
->op() == MIterInitK
) {
6106 args
.addr(fpReg
, localOffset(inst
->extra
<IterData
>()->keyId
));
6111 CppCall::direct(new_miter_array_key
),
6113 SyncOptions::kSyncPoint
,
6115 } else if (innerType
<= Type::Obj
) {
6116 args
.immPtr(curClass()).addr(fpReg
, valLocalOffset
);
6117 if (inst
->op() == MIterInitK
) {
6118 args
.addr(fpReg
, localOffset(inst
->extra
<IterData
>()->keyId
));
6122 // new_miter_object decrefs its src object if it propagates an
6123 // exception out, so we use kSyncPointAdjustOne, which adjusts the
6124 // stack pointer by 1 stack element on an unwind, skipping over
6127 CppCall::direct(new_miter_object
),
6129 SyncOptions::kSyncPointAdjustOne
,
6132 CG_PUNT(MArrayIter
-Unknown
);
6136 void CodeGenerator::cgIterNext(IRInstruction
* inst
) {
6137 cgIterNextCommon(inst
);
6140 void CodeGenerator::cgIterNextK(IRInstruction
* inst
) {
6141 cgIterNextCommon(inst
);
6144 void CodeGenerator::cgWIterNext(IRInstruction
* inst
) {
6145 cgIterNextCommon(inst
);
6148 void CodeGenerator::cgWIterNextK(IRInstruction
* inst
) {
6149 cgIterNextCommon(inst
);
6152 void CodeGenerator::cgIterNextCommon(IRInstruction
* inst
) {
6153 bool isNextK
= inst
->op() == IterNextK
|| inst
->op() == WIterNextK
;
6154 bool isWNext
= inst
->op() == WIterNext
|| inst
->op() == WIterNextK
;
6155 PhysReg fpReg
= srcLoc(0).reg();
6156 auto args
= argGroup();
6157 args
.addr(fpReg
, iterOffset(inst
->extra
<IterData
>()->iterId
))
6158 .addr(fpReg
, localOffset(inst
->extra
<IterData
>()->valId
));
6160 args
.addr(fpReg
, localOffset(inst
->extra
<IterData
>()->keyId
));
6161 } else if (isWNext
) {
6162 // We punt this case because nothing is using WIterNext opcodes
6163 // right now, and we don't want the witer_next_key helper to need
6164 // to check for null.
6165 CG_PUNT(WIterNext
-nonKey
);
6167 TCA helperAddr
= isWNext
? (TCA
)witer_next_key
:
6168 isNextK
? (TCA
)iter_next_key_ind
: (TCA
)iter_next_ind
;
6170 CppCall::direct(reinterpret_cast<void (*)()>(helperAddr
)),
6172 SyncOptions::kSyncPoint
,
6176 void CodeGenerator::cgMIterNext(IRInstruction
* inst
) {
6177 cgMIterNextCommon(inst
);
6180 void CodeGenerator::cgMIterNextK(IRInstruction
* inst
) {
6181 cgMIterNextCommon(inst
);
6184 void CodeGenerator::cgMIterNextCommon(IRInstruction
* inst
) {
6185 PhysReg fpReg
= srcLoc(0).reg();
6186 auto args
= argGroup();
6187 args
.addr(fpReg
, iterOffset(inst
->extra
<IterData
>()->iterId
))
6188 .addr(fpReg
, localOffset(inst
->extra
<IterData
>()->valId
));
6189 if (inst
->op() == MIterNextK
) {
6190 args
.addr(fpReg
, localOffset(inst
->extra
<IterData
>()->keyId
));
6194 cgCallHelper(m_as
, CppCall::direct(miter_next_key
), callDest(inst
),
6195 SyncOptions::kSyncPoint
, args
);
6198 void CodeGenerator::cgIterFree(IRInstruction
* inst
) {
6199 PhysReg fpReg
= srcLoc(0).reg();
6200 int offset
= iterOffset(inst
->extra
<IterFree
>()->iterId
);
6202 CppCall::method(&Iter::free
),
6204 SyncOptions::kSyncPoint
,
6205 argGroup().addr(fpReg
, offset
));
6208 void CodeGenerator::cgMIterFree(IRInstruction
* inst
) {
6209 PhysReg fpReg
= srcLoc(0).reg();
6210 int offset
= iterOffset(inst
->extra
<MIterFree
>()->iterId
);
6212 CppCall::method(&Iter::mfree
),
6214 SyncOptions::kSyncPoint
,
6215 argGroup().addr(fpReg
, offset
));
6218 void CodeGenerator::cgDecodeCufIter(IRInstruction
* inst
) {
6219 PhysReg fpReg
= srcLoc(1).reg();
6220 int offset
= iterOffset(inst
->extra
<DecodeCufIter
>()->iterId
);
6222 CppCall::direct(decodeCufIterHelper
),
6224 SyncOptions::kSyncPoint
,
6225 argGroup().addr(fpReg
, offset
)
6229 void CodeGenerator::cgCIterFree(IRInstruction
* inst
) {
6230 PhysReg fpReg
= srcLoc(0).reg();
6231 int offset
= iterOffset(inst
->extra
<CIterFree
>()->iterId
);
6233 CppCall::method(&Iter::cfree
),
6235 SyncOptions::kSyncPoint
,
6236 argGroup().addr(fpReg
, offset
));
6239 void CodeGenerator::cgNewStructArray(IRInstruction
* inst
) {
6240 auto data
= inst
->extra
<NewStructData
>();
6241 StringData
** table
= mcg
->allocData
<StringData
*>(sizeof(StringData
*),
6243 memcpy(table
, data
->keys
, data
->numKeys
* sizeof(*data
->keys
));
6244 MixedArray
* (*f
)(uint32_t, StringData
**, const TypedValue
*) =
6245 &MixedArray::MakeStruct
;
6249 SyncOptions::kNoSyncPoint
,
6250 argGroup().imm(data
->numKeys
)
6251 .imm(uintptr_t(table
))
6256 void CodeGenerator::cgIncStat(IRInstruction
*inst
) {
6257 emitIncStat(m_mainCode
,
6258 Stats::StatCounter(inst
->src(0)->intVal()),
6259 inst
->src(1)->intVal(),
6260 inst
->src(2)->boolVal());
6263 void CodeGenerator::cgIncTransCounter(IRInstruction
* inst
) {
6264 emitTransCounterInc(m_as
);
6267 void CodeGenerator::cgIncProfCounter(IRInstruction
* inst
) {
6268 TransID transId
= inst
->extra
<TransIDData
>()->transId
;
6269 auto counterAddr
= mcg
->tx().profData()->transCounterAddr(transId
);
6270 emitLoadImm(m_as
, uint64_t(counterAddr
), m_rScratch
);
6271 m_as
.decq(m_rScratch
[0]);
6274 void CodeGenerator::cgDbgAssertRefCount(IRInstruction
* inst
) {
6275 emitAssertRefCount(m_as
, srcLoc(0).reg());
6278 void CodeGenerator::cgDbgAssertType(IRInstruction
* inst
) {
6279 emitTypeTest(inst
->typeParam(),
6282 [&](ConditionCode cc
) {
6283 ifThen(m_as
, ccNegate(cc
), [&](Asm
& a
) { a
.ud2(); });
6288 * Defined in translator-asm-helpers.S. Used for an assert in DbgAssertRetAddr.
6290 extern "C" void enterTCServiceReq();
6292 void CodeGenerator::cgDbgAssertRetAddr(IRInstruction
* inst
) {
6293 // With the exception of FreeActRec and RetCtrl, the native return address
6294 // should always be the part of enterTCHelper that handles service
6295 // requests. To keep things reasonable we only emit this at the beginning of
6296 // a bytecode's translation, which should never begin with FreeActRec or
6298 always_assert(!inst
->is(FreeActRec
, RetCtrl
));
6300 Immed64 imm
= (uintptr_t)enterTCServiceReq
;
6301 if (imm
.fits(sz::dword
)) {
6302 v
<< cmpqim
{imm
.l(), *rsp
};
6304 v
<< ldimm
{imm
, m_rScratch
};
6305 v
<< cmpqm
{m_rScratch
, *rsp
};
6307 ifThen(v
, CC_NE
, [&](Vout
& v
) {
6312 void CodeGenerator::emitVerifyCls(IRInstruction
* inst
) {
6313 auto const objClass
= inst
->src(0);
6314 auto const objClassReg
= srcLoc(0).reg();
6315 auto const constraint
= inst
->src(1);
6316 auto const constraintReg
= srcLoc(1).reg();
6319 if (constraintReg
== InvalidReg
) {
6320 if (objClassReg
!= InvalidReg
) {
6321 auto constraintCls
= constraint
->clsVal();
6322 auto constraintImm
= Immed64(constraintCls
);
6323 if (constraintImm
.fits(sz::dword
)) {
6324 v
<< cmpqi
{constraintImm
.l(), objClassReg
};
6326 auto constraintTmp
= v
.makeReg();
6327 v
<< ldimm
{constraintCls
, constraintTmp
};
6328 v
<< cmpq
{constraintTmp
, objClassReg
};
6332 if (objClass
->clsVal() == constraint
->clsVal()) return;
6333 return cgCallNative(v
, inst
);
6335 } else if (objClassReg
!= InvalidReg
) {
6336 v
<< cmpq
{constraintReg
, objClassReg
};
6338 // Reverse the args because cmpq can only have a constant in the LHS.
6339 auto objCls
= objClass
->clsVal();
6340 auto objImm
= Immed64(objCls
);
6341 if (objImm
.fits(sz::dword
)) {
6342 v
<< cmpqi
{objImm
.l(), constraintReg
};
6344 auto objTmp
= v
.makeReg();
6345 v
<< ldimm
{objCls
, objTmp
};
6346 v
<< cmpq
{objTmp
, constraintReg
};
6350 // The native call for this instruction is the slow path that does
6351 // proper subtype checking. The comparison above is just to
6352 // short-circuit the overhead when the Classes are an exact match.
6353 ifThen(v
, CC_NE
, [&](Vout
& v
){ cgCallNative(v
, inst
); });
6356 void CodeGenerator::cgVerifyParamCls(IRInstruction
* inst
) {
6357 emitVerifyCls(inst
);
6360 void CodeGenerator::cgVerifyRetCls(IRInstruction
* inst
) {
6361 emitVerifyCls(inst
);
6364 void CodeGenerator::cgRBTrace(IRInstruction
* inst
) {
6365 auto const& extra
= *inst
->extra
<RBTrace
>();
6368 auto args
= argGroup();
6370 auto const msg
= extra
.msg
;
6371 assert(msg
->isStatic());
6372 args
.immPtr(msg
->data());
6373 args
.imm(msg
->size());
6374 args
.imm(extra
.type
);
6375 helper
= (TCA
)Trace::ringbufferMsg
;
6377 auto const sk
= extra
.sk
;
6378 args
.imm(extra
.type
);
6379 args
.imm(sk
.toAtomicInt());
6380 args
.ipRel(m_as
.frontier());
6381 helper
= (TCA
)Trace::ringbufferEntry
;
6385 CppCall::direct(reinterpret_cast<void (*)()>(helper
)),
6387 SyncOptions::kNoSyncPoint
,
6391 void CodeGenerator::cgLdClsInitData(IRInstruction
* inst
) {
6392 auto clsReg
= srcLoc(0).reg();
6393 auto dstReg
= dstLoc(0).reg();
6394 auto offset
= Class::propDataCacheOff() +
6395 RDS::Link
<Class::PropInitVec
*>::handleOff();
6396 m_as
. loadl(clsReg
[offset
], r32(dstReg
));
6397 m_as
. loadq(rVmTl
[dstReg
], dstReg
);
6398 m_as
. loadq(dstReg
[Class::PropInitVec::dataOff()], dstReg
);
6401 void CodeGenerator::cgConjure(IRInstruction
* inst
) {
6405 void CodeGenerator::cgProfileStr(IRInstruction
* inst
) {
6406 TargetProfile
<StrProfile
> profile(m_unit
.context(), inst
->marker(),
6407 inst
->extra
<ProfileStrData
>()->key
);
6408 assert(profile
.profiling());
6409 auto const ch
= profile
.handle();
6411 auto ptrReg
= srcLoc(0).reg();
6412 emitCmpTVType(m_as
, KindOfStaticString
, ptrReg
[TVOFF(m_type
)]);
6415 [&](Asm
& a
) { // m_type == KindOfStaticString
6416 a
.incl(rVmTl
[ch
+ offsetof(StrProfile
, staticStr
)]);
6418 [&](Asm
& a
) { // m_type == KindOfString
6419 a
.loadq(ptrReg
[TVOFF(m_data
)], m_rScratch
);
6420 a
.cmpl(StaticValue
, m_rScratch
[FAST_REFCOUNT_OFFSET
]);
6424 [&](Asm
& a
) { // _count == StaticValue
6425 a
.incl(rVmTl
[ch
+ offsetof(StrProfile
, strStatic
)]);
6428 a
.incl(rVmTl
[ch
+ offsetof(StrProfile
, str
)]);
6435 void CodeGenerator::cgCountArray(IRInstruction
* inst
) {
6436 auto const baseReg
= srcLoc(0).reg();
6437 auto const dstReg
= dstLoc(0).reg();
6440 v
<< cmpbim
{ArrayData::kNvtwKind
, baseReg
[ArrayData::offsetofKind()]};
6441 unlikelyIfThenElse(v
, vcold(), CC_Z
,
6443 cgCallNative(v
, inst
);
6446 v
<< loadl
{baseReg
[ArrayData::offsetofSize()], dstReg
};
6451 void CodeGenerator::cgCountArrayFast(IRInstruction
* inst
) {
6452 auto const baseReg
= srcLoc(0).reg();
6453 auto const dstReg
= dstLoc(0).reg();
6455 m_as
. loadl(baseReg
[ArrayData::offsetofSize()], r32(dstReg
));
6458 void CodeGenerator::cgCountCollection(IRInstruction
* inst
) {
6459 auto const baseReg
= srcLoc(0).reg();
6460 auto const dstReg
= dstLoc(0).reg();
6462 m_as
. loadl(baseReg
[FAST_COLLECTION_SIZE_OFFSET
], r32(dstReg
));
6465 void CodeGenerator::print() const {
6466 JIT::print(std::cout
, m_unit
, &m_state
.regs
, m_state
.asmInfo
);