2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
16 #include "hphp/hhbbc/emit.h"
23 #include <type_traits>
25 #include <folly/gen/Base.h>
26 #include <folly/Conv.h>
27 #include <folly/Memory.h>
29 #include "hphp/hhbbc/cfg.h"
30 #include "hphp/hhbbc/class-util.h"
31 #include "hphp/hhbbc/context.h"
32 #include "hphp/hhbbc/func-util.h"
33 #include "hphp/hhbbc/index.h"
34 #include "hphp/hhbbc/options.h"
35 #include "hphp/hhbbc/representation.h"
36 #include "hphp/hhbbc/type-structure.h"
37 #include "hphp/hhbbc/unit-util.h"
39 #include "hphp/runtime/base/array-data.h"
40 #include "hphp/runtime/base/repo-auth-type.h"
41 #include "hphp/runtime/base/tv-comparisons.h"
43 #include "hphp/runtime/ext/extension-registry.h"
45 #include "hphp/runtime/vm/bytecode.h"
46 #include "hphp/runtime/vm/func-emitter.h"
47 #include "hphp/runtime/vm/native.h"
48 #include "hphp/runtime/vm/preclass-emitter.h"
49 #include "hphp/runtime/vm/type-alias-emitter.h"
50 #include "hphp/runtime/vm/unit-emitter.h"
52 #include "hphp/util/configs/eval.h"
54 namespace HPHP::HHBBC
{
56 TRACE_SET_MOD(hhbbc_emit
);
60 //////////////////////////////////////////////////////////////////////
62 const StaticString
s_invoke("__invoke");
64 //////////////////////////////////////////////////////////////////////
66 struct EmitUnitState
{
67 explicit EmitUnitState(const Index
& index
, const php::Unit
* unit
) :
68 index(index
), unit(unit
) {}
71 * Access to the Index for this program.
76 * Access to the unit we're emitting
78 const php::Unit
* unit
;
81 * While emitting bytecode, we keep track of the classes and funcs
84 std::vector
<std::pair
<php::Class
*, PreClassEmitter
*>> pces
;
87 * Whether a closure in a CreateCl opcode has been seen before
88 * (CreateCls within the same func are allowed to refer to the same
89 * closure, so we must avoid creating duplicate PreClassEmitters for
92 hphp_fast_set
<const php::Class
*> seenClosures
;
96 * Some bytecodes need to be mutated before being emitted. Pass those
97 * bytecodes by value to their respective emit_op functions.
100 struct OpInfoHelper
{
101 static constexpr bool by_value
= T::op
== Op::CreateCl
;
102 using type
= typename
std::conditional
<by_value
, T
, const T
&>::type
;
106 using OpInfo
= typename OpInfoHelper
<T
>::type
;
109 * Helper to conditionally call fun on data provided data is of the
112 template<Op op
, typename F
, typename Bc
>
113 std::enable_if_t
<std::remove_reference_t
<Bc
>::op
== op
>
114 caller(F
&& fun
, Bc
&& data
) { fun(std::forward
<Bc
>(data
)); }
116 template<Op op
, typename F
, typename Bc
>
117 std::enable_if_t
<std::remove_reference_t
<Bc
>::op
!= op
>
120 void recordClass(EmitUnitState
& euState
,
123 euState
.pces
.emplace_back(
125 ue
.newPreClassEmitter(cls
.name
->toCppString())
129 //////////////////////////////////////////////////////////////////////
131 php::SrcLoc
srcLoc(const php::Unit
& unit
, const php::Func
& func
, int32_t ix
) {
132 if (ix
< 0) return php::SrcLoc
{};
133 assertx(ix
< unit
.srcLocs
.size());
134 return unit
.srcLocs
[ix
];
138 * Order the blocks for bytecode emission.
140 * Rules about block order:
142 * - Each DV funclet must have all of its blocks contiguous, with the
145 * - Main entry point must be the first block.
147 * It is not a requirement, but we attempt to locate all the DV entry
148 * points after the rest of the primary function body. The normal
149 * case for DV initializers is that each one falls through to the
150 * next, with the block jumping back to the main entry point.
152 std::vector
<BlockId
> order_blocks(const php::WideFunc
& f
) {
153 auto sorted
= rpoSortFromMain(f
);
155 // Get the DV blocks, without the rest of the primary function body,
156 // and then add them to the end of sorted.
157 auto const dvBlocks
= [&] {
158 auto withDVs
= rpoSortAddDVs(f
);
160 std::find(begin(withDVs
), end(withDVs
), sorted
.front()),
165 sorted
.insert(end(sorted
), begin(dvBlocks
), end(dvBlocks
));
167 FTRACE(2, " block order:{}\n",
170 for (auto const bid
: sorted
) {
172 ret
+= folly::to
<std::string
>(bid
);
180 // While emitting bytecode, we learn about some metadata that will
181 // need to be registered in the FuncEmitter.
183 struct JmpFixup
{ Offset instrOff
; Offset jmpImmedOff
; };
187 : offset(kInvalidOffset
)
188 , past(kInvalidOffset
)
192 // The offset of the block, if we've already emitted it.
193 // Otherwise kInvalidOffset.
196 // The offset past the end of this block.
199 // How many catch regions the jump at the end of this block is leaving.
200 // 0 if there is no jump or if the jump is to the same catch region or a
204 // When we emit a forward jump to a block we haven't seen yet, we
205 // write down where the jump was so we can fix it up when we get
207 std::vector
<JmpFixup
> forwardJumps
;
209 // When we see a forward jump to a block, we record the stack
210 // depth at the jump site here. This is needed to track
211 // currentStackDepth correctly (and we also assert all the jumps
212 // have the same depth).
213 Optional
<uint32_t> expectedStackDepth
;
216 std::vector
<BlockId
> blockOrder
;
217 uint32_t maxStackDepth
;
219 std::vector
<BlockInfo
> blockInfo
;
222 using ExnNodePtr
= php::ExnNode
*;
224 bool handleEquivalent(const php::Func
& func
, ExnNodeId eh1
, ExnNodeId eh2
) {
225 auto entry
= [&] (ExnNodeId eid
) {
226 return func
.exnNodes
[eid
].region
.catchEntry
;
230 assertx(eh1
!= NoExnNodeId
&&
231 eh2
!= NoExnNodeId
&&
232 func
.exnNodes
[eh1
].depth
== func
.exnNodes
[eh2
].depth
);
233 if (entry(eh1
) != entry(eh2
)) return false;
234 eh1
= func
.exnNodes
[eh1
].parent
;
235 eh2
= func
.exnNodes
[eh2
].parent
;
241 // The common parent P of eh1 and eh2 is the deepest region such that
242 // eh1 and eh2 are both handle-equivalent to P or a child of P
243 ExnNodeId
commonParent(const php::Func
& func
, ExnNodeId eh1
, ExnNodeId eh2
) {
244 if (eh1
== NoExnNodeId
|| eh2
== NoExnNodeId
) return NoExnNodeId
;
245 while (func
.exnNodes
[eh1
].depth
> func
.exnNodes
[eh2
].depth
) {
246 eh1
= func
.exnNodes
[eh1
].parent
;
248 while (func
.exnNodes
[eh2
].depth
> func
.exnNodes
[eh1
].depth
) {
249 eh2
= func
.exnNodes
[eh2
].parent
;
251 while (!handleEquivalent(func
, eh1
, eh2
)) {
252 eh1
= func
.exnNodes
[eh1
].parent
;
253 eh2
= func
.exnNodes
[eh2
].parent
;
259 s_hhbbc_fail_verification("__hhvm_intrinsics\\hhbbc_fail_verification");
261 EmitBcInfo
emit_bytecode(EmitUnitState
& euState
, UnitEmitter
& ue
, FuncEmitter
& fe
,
262 const php::WideFunc
& func
) {
264 auto& blockInfo
= ret
.blockInfo
;
265 blockInfo
.resize(func
.blocks().size());
267 // Track the stack depth while emitting to determine maxStackDepth.
268 int32_t currentStackDepth
{ 0 };
270 // Temporary buffer for vector immediates. (Hoisted so it's not
271 // allocated in the loop.)
272 std::vector
<uint8_t> immVec
;
274 // Offset of the last emitted bytecode.
275 Offset lastOff
{ 0 };
277 auto const unit
= euState
.index
.lookup_func_original_unit(*func
);
279 SCOPE_ASSERT_DETAIL("emit") {
281 for (auto bid
: func
.blockRange()) {
282 auto const block
= show(*func
, *func
.blocks()[bid
]);
283 folly::format(&ret
, "block #{}\n{}", bid
, block
);
289 auto const map_local
= [&] (LocalId id
) {
290 if (id
>= func
->locals
.size()) return id
;
291 auto const loc
= func
->locals
[id
];
292 assertx(!loc
.killed
);
293 assertx(loc
.id
<= id
);
298 auto const map_local_name
= [&] (NamedLocal nl
) {
299 nl
.id
= map_local(nl
.id
);
300 if (nl
.name
== kInvalidLocalName
) return nl
;
301 if (nl
.name
>= func
->locals
.size()) return nl
;
302 auto const loc
= func
->locals
[nl
.name
];
304 nl
.name
= kInvalidLocalName
;
307 assertx(!loc
.unusedName
);
308 nl
.name
= loc
.nameId
;
312 auto const set_expected_depth
= [&] (BlockId block
) {
313 auto& info
= blockInfo
[block
];
315 if (info
.expectedStackDepth
) {
316 assertx(*info
.expectedStackDepth
== currentStackDepth
);
318 info
.expectedStackDepth
= currentStackDepth
;
322 auto const make_member_key
= [&] (MKey mkey
) {
323 switch (mkey
.mcode
) {
325 return MemberKey
{mkey
.mcode
, static_cast<int32_t>(mkey
.idx
), mkey
.rop
};
327 return MemberKey
{mkey
.mcode
, map_local_name(mkey
.local
), mkey
.rop
};
328 case MET
: case MPT
: case MQT
:
329 return MemberKey
{mkey
.mcode
, mkey
.litstr
, mkey
.rop
};
331 return MemberKey
{mkey
.mcode
, mkey
.int64
, mkey
.rop
};
338 auto const emit_inst
= [&] (const Bytecode
& inst
) {
339 auto const startOffset
= fe
.bcPos();
340 lastOff
= startOffset
;
342 FTRACE(4, " emit: {} -- {} @ {}\n", currentStackDepth
, show(*func
, inst
),
343 show(srcLoc(*unit
, *func
, inst
.srcLoc
)));
345 auto const emit_vsa
= [&] (const CompactVector
<LSString
>& keys
) {
346 auto n
= keys
.size();
348 for (size_t i
= 0; i
< n
; ++i
) {
349 fe
.emitInt32(ue
.mergeLitstr(keys
[i
]));
353 auto const emit_branch
= [&] (BlockId id
) {
354 auto& info
= blockInfo
[id
];
355 if (info
.offset
!= kInvalidOffset
) {
356 fe
.emitInt32(info
.offset
- startOffset
);
358 info
.forwardJumps
.push_back({ startOffset
, fe
.bcPos() });
363 auto const emit_switch
= [&] (const SwitchTab
& targets
) {
364 fe
.emitIVA(targets
.size());
365 for (auto t
: targets
) {
366 set_expected_depth(t
);
371 auto const emit_sswitch
= [&] (const SSwitchTab
& targets
) {
372 fe
.emitIVA(targets
.size());
373 for (size_t i
= 0; i
< targets
.size() - 1; ++i
) {
374 set_expected_depth(targets
[i
].second
);
375 fe
.emitInt32(ue
.mergeLitstr(targets
[i
].first
));
376 emit_branch(targets
[i
].second
);
379 set_expected_depth(targets
[targets
.size() - 1].second
);
380 emit_branch(targets
[targets
.size() - 1].second
);
383 auto const emit_srcloc
= [&] {
384 auto const sl
= srcLoc(*unit
, *func
, inst
.srcLoc
);
385 auto const loc
= sl
.isValid() ?
386 Location::Range(sl
.start
.line
, sl
.start
.col
, sl
.past
.line
, sl
.past
.col
)
387 : Location::Range(-1,-1,-1,-1);
388 fe
.recordSourceLocation(loc
, startOffset
);
391 auto const pop
= [&] (int32_t n
) {
392 currentStackDepth
-= n
;
393 assertx(currentStackDepth
>= 0);
395 auto const push
= [&] (int32_t n
) {
396 currentStackDepth
+= n
;
398 std::max
<uint32_t>(ret
.maxStackDepth
, currentStackDepth
);
401 auto const ret_assert
= [&] { assertx(currentStackDepth
== inst
.numPop()); };
403 auto const createcl
= [&] (auto const& data
) {
404 auto const cls
= euState
.index
.lookup_class(data
.str2
);
407 "A closure class ({}) failed to resolve",
410 assertx(cls
->unit
== euState
.unit
->filename
);
411 assertx(is_closure(*cls
));
412 // Skip closures we've already recorded
413 if (!euState
.seenClosures
.emplace(cls
).second
) return;
414 recordClass(euState
, ue
, const_cast<php::Class
&>(*cls
));
417 auto const emit_lar
= [&](const LocalRange
& range
) {
418 encodeLocalRange(fe
, HPHP::LocalRange
{
419 map_local(range
.first
), range
.count
423 #define IMM_BLA(n) emit_switch(data.targets);
424 #define IMM_SLA(n) emit_sswitch(data.targets);
425 #define IMM_IVA(n) fe.emitIVA(data.arg##n);
426 #define IMM_I64A(n) fe.emitInt64(data.arg##n);
427 #define IMM_LA(n) fe.emitIVA(map_local(data.loc##n));
428 #define IMM_NLA(n) fe.emitNamedLocal(map_local_name(data.nloc##n));
429 #define IMM_ILA(n) fe.emitIVA(map_local(data.loc##n));
430 #define IMM_IA(n) fe.emitIVA(data.iter##n);
431 #define IMM_DA(n) fe.emitDouble(data.dbl##n);
432 #define IMM_SA(n) fe.emitInt32(ue.mergeLitstr(data.str##n));
433 #define IMM_RATA(n) encodeRAT(fe, data.rat);
434 #define IMM_AA(n) fe.emitInt32(ue.mergeArray(data.arr##n));
435 #define IMM_OA_IMPL(n) fe.emitByte(static_cast<uint8_t>(data.subop##n));
436 #define IMM_OA(type) IMM_OA_IMPL
437 #define IMM_BA(n) targets[numTargets++] = data.target##n; \
438 emit_branch(data.target##n);
439 #define IMM_VSA(n) emit_vsa(data.keys);
440 #define IMM_KA(n) encode_member_key(make_member_key(data.mkey), fe);
441 #define IMM_LAR(n) emit_lar(data.locrange);
442 #define IMM_ITA(n) encodeIterArgs(fe, data.ita);
443 #define IMM_FCA(n) encodeFCallArgs( \
444 fe, data.fca.base(), \
445 data.fca.enforceInOut(), \
448 [&] (int numBytes, const uint8_t* inOut) { \
449 encodeFCallArgsBoolVec(fe, numBytes, inOut); \
453 data.fca.enforceReadonly(), \
455 data.fca.applyReadonly( \
456 [&] (int numBytes, const uint8_t* readonly) { \
457 encodeFCallArgsBoolVec(fe, numBytes, readonly); \
461 data.fca.asyncEagerTarget() != NoBlockId, \
463 set_expected_depth(data.fca.asyncEagerTarget()); \
464 emit_branch(data.fca.asyncEagerTarget()); \
466 data.fca.context() != nullptr, \
468 fe.emitInt32(ue.mergeLitstr(data.fca.context()));\
470 if (!data.fca.hasUnpack()) ret.containsCalls = true;
473 #define IMM_ONE(x) IMM_##x(1)
474 #define IMM_TWO(x, y) IMM_##x(1); IMM_##y(2);
475 #define IMM_THREE(x, y, z) IMM_TWO(x, y); IMM_##z(3);
476 #define IMM_FOUR(x, y, z, n) IMM_THREE(x, y, z); IMM_##n(4);
477 #define IMM_FIVE(x, y, z, n, m) IMM_FOUR(x, y, z, n); IMM_##m(5);
478 #define IMM_SIX(x, y, z, n, m, o) IMM_FIVE(x, y, z, n, m); IMM_##o(6);
481 #define POP_ONE(x) pop(1);
482 #define POP_TWO(x, y) pop(2);
483 #define POP_THREE(x, y, z) pop(3);
485 #define POP_MFINAL pop(data.arg1);
486 #define POP_C_MFINAL(n) pop(n); pop(data.arg1);
487 #define POP_CMANY pop(data.arg##1);
488 #define POP_SMANY pop(data.keys.size());
489 #define POP_CUMANY pop(data.arg##1);
490 #define POP_FCALL(nin, nobj) \
491 pop(nin + data.fca.numInputs() + 1 + data.fca.numRets());
494 #define PUSH_ONE(x) push(1);
495 #define PUSH_TWO(x, y) push(2);
496 #define PUSH_THREE(x, y, z) push(3);
497 #define PUSH_CMANY push(data.arg1);
498 #define PUSH_FCALL push(data.fca.numRets());
500 #define O(opcode, imms, inputs, outputs, flags) \
502 if (Op::opcode == Op::Nop) break; \
503 OpInfo<bc::opcode> data{inst.opcode}; \
504 if (Cfg::Eval::EnableIntrinsicsExtension) { \
505 if (Op::opcode == Op::FCallFuncD && \
506 inst.FCallFuncD.str2 == s_hhbbc_fail_verification.get()) {\
507 fe.emitOp(Op::CheckProp); \
509 ue.mergeLitstr(inst.FCallFuncD.str2)); \
510 fe.emitOp(Op::PopC); \
511 ret.maxStackDepth++; \
514 caller<Op::CreateCl>(createcl, data); \
516 if (isRet(Op::opcode)) ret_assert(); \
517 fe.emitOp(Op::opcode); \
520 size_t numTargets = 0; \
521 std::array<BlockId, kMaxHhbcImms> targets; \
523 if (Op::opcode == Op::MemoGet) { \
525 assertx(numTargets == 1); \
526 set_expected_depth(targets[0]); \
528 } else if (Op::opcode == Op::MemoGetEager) { \
530 assertx(numTargets == 2); \
531 set_expected_depth(targets[0]); \
533 set_expected_depth(targets[1]); \
537 for (size_t i = 0; i < numTargets; ++i) { \
538 set_expected_depth(targets[i]); \
542 if (flags & TF) currentStackDepth = 0; \
546 switch (inst
.op
) { OPCODES
}
599 ret
.blockOrder
= order_blocks(func
);
600 auto blockIt
= begin(ret
.blockOrder
);
601 auto const endBlockIt
= end(ret
.blockOrder
);
602 for (; blockIt
!= endBlockIt
; ++blockIt
) {
604 auto& info
= blockInfo
[bid
];
605 auto const b
= func
.blocks()[bid
].get();
607 info
.offset
= fe
.bcPos();
608 FTRACE(2, " block {}: {}\n", bid
, info
.offset
);
610 for (auto& fixup
: info
.forwardJumps
) {
611 fe
.emitInt32(info
.offset
- fixup
.instrOff
, fixup
.jmpImmedOff
);
614 if (!info
.expectedStackDepth
) {
615 // unreachable, or entry block
616 info
.expectedStackDepth
= b
->catchEntry
? 1 : 0;
619 currentStackDepth
= *info
.expectedStackDepth
;
621 auto fallthrough
= b
->fallthrough
;
622 auto end
= b
->hhbcs
.end();
625 if (!is_single_nop(*b
)) {
626 // If the block ends with JmpZ or JmpNZ to the next block, flip
627 // the condition to make the fallthrough the next block
628 if (b
->hhbcs
.back().op
== Op::JmpZ
||
629 b
->hhbcs
.back().op
== Op::JmpNZ
) {
630 auto const& bc
= b
->hhbcs
.back();
632 bc
.op
== Op::JmpNZ
? bc
.JmpNZ
.target1
: bc
.JmpZ
.target1
;
633 if (std::next(blockIt
) != endBlockIt
&& blockIt
[1] == target
) {
634 fallthrough
= target
;
640 for (auto iit
= b
->hhbcs
.begin(); iit
!= end
; ++iit
) emit_inst(*iit
);
642 if (end
->op
== Op::JmpNZ
) {
643 emit_inst(bc_with_loc(end
->srcLoc
, bc::JmpZ
{ b
->fallthrough
}));
645 emit_inst(bc_with_loc(end
->srcLoc
, bc::JmpNZ
{ b
->fallthrough
}));
650 info
.past
= fe
.bcPos();
652 if (fallthrough
!= NoBlockId
) {
653 set_expected_depth(fallthrough
);
654 if (std::next(blockIt
) == endBlockIt
||
655 blockIt
[1] != fallthrough
) {
656 emit_inst(bc::Jmp
{ fallthrough
});
658 auto const nextExnId
= func
.blocks()[fallthrough
]->exnNodeId
;
659 auto const parent
= commonParent(*func
, nextExnId
, b
->exnNodeId
);
661 auto depth
= [&] (ExnNodeId eid
) {
662 return eid
== NoExnNodeId
? 0 : func
->exnNodes
[eid
].depth
;
664 // If we are in an exn region we pop from the current region to the
665 // common parent. If the common parent is null, we pop all regions
666 info
.regionsToPop
= depth(b
->exnNodeId
) - depth(parent
);
667 assertx(info
.regionsToPop
>= 0);
668 FTRACE(4, " popped catch regions: {}\n", info
.regionsToPop
);
672 if (b
->throwExit
!= NoBlockId
) {
673 FTRACE(4, " throw: {}\n", b
->throwExit
);
675 if (fallthrough
!= NoBlockId
) {
676 FTRACE(4, " fallthrough: {}\n", fallthrough
);
678 FTRACE(2, " block {} end: {}\n", bid
, info
.past
);
684 void emit_locals_and_params(FuncEmitter
& fe
, const php::Func
& func
,
685 const EmitBcInfo
& info
) {
687 for (auto const& loc
: func
.locals
) {
688 if (loc
.id
< func
.params
.size()) {
690 assertx(!loc
.killed
);
691 assertx(!loc
.unusedName
);
692 auto& param
= func
.params
[id
];
693 Func::ParamInfo pinfo
;
694 pinfo
.defaultValue
= param
.defaultValue
;
695 pinfo
.typeConstraints
= param
.typeConstraints
;
696 pinfo
.userType
= param
.userTypeConstraint
;
697 pinfo
.phpCode
= param
.phpCode
;
698 pinfo
.userAttributes
= param
.userAttributes
;
699 if (param
.inout
) pinfo
.setFlag(Func::ParamInfo::Flags::InOut
);
700 if (param
.readonly
) pinfo
.setFlag(Func::ParamInfo::Flags::Readonly
);
701 if (param
.isVariadic
) pinfo
.setFlag(Func::ParamInfo::Flags::Variadic
);
702 if (param
.isOptional
) pinfo
.setFlag(Func::ParamInfo::Flags::Optional
);
703 fe
.appendParam(func
.locals
[id
].name
, pinfo
);
704 auto const dv
= param
.dvEntryPoint
;
705 if (dv
!= NoBlockId
) {
706 fe
.params
[id
].funcletOff
= info
.blockInfo
[dv
].offset
;
710 if (loc
.killed
) continue;
711 if (loc
.name
&& !loc
.unusedName
&& loc
.name
) {
712 fe
.allocVarId(loc
.name
);
713 assertx(fe
.lookupVarId(loc
.name
) == id
);
714 assertx(loc
.id
== id
);
717 fe
.allocUnnamedLocal();
722 for (auto const& loc
: func
.locals
) {
723 if (loc
.killed
&& !loc
.unusedName
&& loc
.name
) {
724 fe
.allocVarId(loc
.name
, true);
729 for (auto const& loc
: func
.locals
) {
731 assertx(loc
.id
< fe
.numLocals());
733 if (!loc
.unusedName
&& loc
.name
) {
734 assertx(loc
.nameId
< fe
.numNamedLocals());
739 assertx(fe
.numLocals() == id
);
740 fe
.setNumIterators(func
.numIters
);
744 const php::ExnNode
* node
;
750 template<class BlockInfo
, class ParentIndexMap
>
751 void emit_eh_region(FuncEmitter
& fe
,
752 const EHRegion
* region
,
753 const BlockInfo
& blockInfo
,
754 ParentIndexMap
& parentIndexMap
) {
755 FTRACE(2, " func {}: ExnNode {}\n", fe
.name
, region
->node
->idx
);
757 auto const unreachable
= [&] (const php::ExnNode
& node
) {
758 return blockInfo
[node
.region
.catchEntry
].offset
== kInvalidOffset
;
761 // A region on a single empty block.
762 if (region
->start
== region
->past
) {
763 FTRACE(2, " Skipping\n");
765 } else if (unreachable(*region
->node
)) {
766 FTRACE(2, " Unreachable\n");
770 FTRACE(2, " Process @ {}-{}\n", region
->start
, region
->past
);
772 auto& eh
= fe
.addEHEnt();
773 eh
.m_base
= region
->start
;
774 eh
.m_past
= region
->past
;
775 assertx(eh
.m_past
>= eh
.m_base
);
776 assertx(eh
.m_base
!= kInvalidOffset
&& eh
.m_past
!= kInvalidOffset
);
778 // An unreachable parent won't be emitted (and thus its offset won't be set),
779 // so find the closest reachable one.
780 auto parent
= region
->parent
;
781 while (parent
&& unreachable(*parent
->node
)) parent
= parent
->parent
;
783 auto parentIt
= parentIndexMap
.find(parent
);
784 assertx(parentIt
!= end(parentIndexMap
));
785 eh
.m_parentIndex
= parentIt
->second
;
787 eh
.m_parentIndex
= -1;
789 parentIndexMap
[region
] = fe
.ehtab
.size() - 1;
791 auto const& cr
= region
->node
->region
;
792 eh
.m_handler
= blockInfo
[cr
.catchEntry
].offset
;
793 eh
.m_end
= kInvalidOffset
;
794 eh
.m_iterId
= cr
.iterId
;
796 assertx(eh
.m_handler
!= kInvalidOffset
);
799 void exn_path(const php::Func
& func
,
800 std::vector
<const php::ExnNode
*>& ret
,
802 if (id
== NoExnNodeId
) return;
803 auto const& n
= func
.exnNodes
[id
];
804 exn_path(func
, ret
, n
.parent
);
808 // Return the count of shared elements in the front of two forward
810 template<class ForwardRange1
, class ForwardRange2
>
811 size_t shared_prefix(ForwardRange1
& r1
, ForwardRange2
& r2
) {
812 auto r1it
= begin(r1
);
813 auto r2it
= begin(r2
);
814 auto const r1end
= end(r1
);
815 auto const r2end
= end(r2
);
816 auto ret
= size_t{0};
817 while (r1it
!= r1end
&& r2it
!= r2end
&& *r1it
== *r2it
) {
818 ++ret
; ++r1it
; ++r2it
;
824 * Traverse the actual block layout, and find out the intervals for
825 * each exception region in the tree.
827 * The basic idea here is that we haven't constrained block layout
828 * based on the exception tree, but adjacent blocks are still
829 * reasonably likely to have the same ExnNode. Try to coalesce the EH
830 * regions we create for in those cases.
832 void emit_ehent_tree(FuncEmitter
& fe
, const php::WideFunc
& func
,
833 const EmitBcInfo
& info
) {
836 std::vector
<std::unique_ptr
<EHRegion
>>
840 * While walking over the blocks in layout order, we track the set
841 * of "active" exnNodes. This are a list of exnNodes that inherit
842 * from each other. When a new active node is pushed, begin an
843 * EHEnt, and when it's popped, it's done.
845 std::vector
<const php::ExnNode
*> activeList
;
847 auto pop_active
= [&] (Offset past
) {
848 auto p
= activeList
.back();
849 activeList
.pop_back();
850 exnMap
[p
].back()->past
= past
;
853 auto push_active
= [&] (const php::ExnNode
* p
, Offset start
) {
854 auto const parent
= activeList
.empty()
856 : exnMap
[activeList
.back()].back().get();
858 std::make_unique
<EHRegion
>(
859 EHRegion
{ p
, parent
, start
, kInvalidOffset
}
862 activeList
.push_back(p
);
866 * Walk over the blocks, and compare the new block's exnNode path to
867 * the active one. Find the least common ancestor of the two paths,
868 * then modify the active list by popping and then pushing nodes to
869 * set it to the new block's path.
871 for (auto const bid
: info
.blockOrder
) {
872 auto const b
= func
.blocks()[bid
].get();
873 auto const offset
= info
.blockInfo
[bid
].offset
;
875 if (b
->exnNodeId
== NoExnNodeId
) {
876 while (!activeList
.empty()) pop_active(offset
);
880 std::vector
<const php::ExnNode
*> current
;
881 exn_path(*func
, current
, b
->exnNodeId
);
883 auto const prefix
= shared_prefix(current
, activeList
);
884 for (size_t i
= prefix
, sz
= activeList
.size(); i
< sz
; ++i
) {
887 for (size_t i
= prefix
, sz
= current
.size(); i
< sz
; ++i
) {
888 push_active(current
[i
], offset
);
891 for (int i
= 0; i
< info
.blockInfo
[bid
].regionsToPop
; i
++) {
892 // If the block ended in a jump out of the catch region, this effectively
893 // ends all catch regions deeper than the one we are jumping to
894 pop_active(info
.blockInfo
[bid
].past
);
897 if (debug
&& !activeList
.empty()) {
899 exn_path(*func
, current
, activeList
.back()->idx
);
900 assertx(current
== activeList
);
904 while (!activeList
.empty()) {
905 pop_active(info
.blockInfo
[info
.blockOrder
.back()].past
);
909 * We've created all our regions, but we need to sort them instead
910 * of trying to get the UnitEmitter to do it.
912 * The UnitEmitter expects EH regions that look a certain way
913 * (basically the way emitter.cpp likes them). There are some rules
914 * about the order it needs to have at runtime, which we set up
917 * Essentially, an entry a is less than an entry b iff:
919 * - a starts before b
920 * - a starts at the same place, but encloses b entirely
921 * - a has the same extents as b, but is a parent of b
923 std::vector
<EHRegion
*> regions
;
924 for (auto& mapEnt
: exnMap
) {
925 for (auto& region
: mapEnt
.second
) {
926 regions
.push_back(region
.get());
930 begin(regions
), end(regions
),
931 [&] (const EHRegion
* a
, const EHRegion
* b
) {
932 if (a
== b
) return false;
933 if (a
->start
== b
->start
) {
934 if (a
->past
== b
->past
) {
935 // When regions exactly overlap, the parent is less than the
937 for (auto p
= b
->parent
; p
!= nullptr; p
= p
->parent
) {
938 if (p
== a
) return true;
940 // If a is not a parent of b, and they have the same region;
941 // then b better be a parent of a.
944 for (; p
!= b
&& p
!= nullptr; p
= p
->parent
) continue;
949 return a
->past
> b
->past
;
951 return a
->start
< b
->start
;
955 hphp_fast_map
<const EHRegion
*,uint32_t> parentIndexMap
;
956 for (auto& r
: regions
) {
957 emit_eh_region(fe
, r
, info
.blockInfo
, parentIndexMap
);
959 fe
.setEHTabIsSorted();
962 void emit_finish_func(EmitUnitState
& state
, FuncEmitter
& fe
,
963 php::WideFunc
& wf
, const EmitBcInfo
& info
) {
964 auto const& func
= *wf
;
965 if (info
.containsCalls
) fe
.containsCalls
= true;
967 emit_locals_and_params(fe
, func
, info
);
968 emit_ehent_tree(fe
, wf
, info
);
971 fe
.userAttributes
= func
.userAttributes
;
972 fe
.retUserType
= func
.returnUserType
;
973 fe
.retTypeConstraints
= func
.retTypeConstraints
;
974 fe
.originalFilename
=
975 func
.originalFilename
? func
.originalFilename
:
976 func
.originalUnit
? func
.originalUnit
: nullptr;
977 fe
.originalModuleName
= func
.originalModuleName
;
978 fe
.requiresFromOriginalModule
= func
.requiresFromOriginalModule
;
979 fe
.isClosureBody
= func
.isClosureBody
;
980 fe
.isAsync
= func
.isAsync
;
981 fe
.isGenerator
= func
.isGenerator
;
982 fe
.isPairGenerator
= func
.isPairGenerator
;
983 fe
.isNative
= func
.isNative
;
984 fe
.isMemoizeWrapper
= func
.isMemoizeWrapper
;
985 fe
.isMemoizeWrapperLSB
= func
.isMemoizeWrapperLSB
;
987 for (auto& name
: func
.staticCoeffects
) fe
.staticCoeffects
.push_back(name
);
988 for (auto& rule
: func
.coeffectRules
) fe
.coeffectRules
.push_back(rule
);
990 auto const [retTy
, _
] = state
.index
.lookup_return_type_raw(&func
).first
;
991 if (!retTy
.subtypeOf(BBottom
)) {
992 fe
.repoReturnType
= make_repo_type(retTy
);
995 if (is_specialized_wait_handle(retTy
)) {
996 auto const awaitedTy
= wait_handle_inner(retTy
);
997 if (!awaitedTy
.subtypeOf(BBottom
)) {
998 fe
.repoAwaitedReturnType
= make_repo_type(awaitedTy
);
1002 fe
.maxStackCells
= info
.maxStackDepth
+
1004 fe
.numIterators() * kNumIterCells
;
1009 void renumber_locals(php::Func
& func
) {
1013 // We initialise local name ids in two passes, since locals that have not
1014 // been remapped may require their name be at the same offset as the local.
1015 // That's true for params, volatile locals, or locals in funcs with VarEnvs.
1017 // In the first pass, we assume that all local names are used. Only in the
1018 // second pass do we apply the fact that some local names are never used.
1020 for (auto& loc
: func
.locals
) {
1022 // Make sure it's out of range, in case someone tries to read it.
1025 loc
.nameId
= nameId
++;
1029 for (auto& loc
: func
.locals
) {
1030 if (loc
.unusedName
|| !loc
.name
) {
1031 // Make sure it's out of range, in case someone tries to read it.
1032 loc
.nameId
= INT_MAX
;
1033 } else if (loc
.killed
) {
1034 // The local uses a shared local slot, but its name is still used.
1035 loc
.nameId
= nameId
++;
1040 void emit_init_func(FuncEmitter
& fe
, const php::Func
& func
) {
1042 std::get
<0>(func
.srcInfo
.loc
),
1043 std::get
<1>(func
.srcInfo
.loc
),
1044 func
.attrs
| (func
.sampleDynamicCalls
? AttrDynamicallyCallable
: AttrNone
),
1045 func
.srcInfo
.docComment
1049 void emit_func(EmitUnitState
& state
, UnitEmitter
& ue
,
1050 FuncEmitter
& fe
, php::Func
& f
) {
1051 FTRACE(2, " func {}\n", f
.name
->data());
1052 assertx(f
.attrs
& AttrPersistent
);
1054 emit_init_func(fe
, f
);
1055 auto func
= php::WideFunc::mut(&f
);
1056 auto const info
= emit_bytecode(state
, ue
, fe
, func
);
1057 emit_finish_func(state
, fe
, func
, info
);
1060 void emit_class(EmitUnitState
& state
, UnitEmitter
& ue
, PreClassEmitter
* pce
,
1062 FTRACE(2, " class: {}\n", cls
.name
->data());
1063 assertx(cls
.attrs
& AttrPersistent
);
1065 std::get
<0>(cls
.srcInfo
.loc
),
1066 std::get
<1>(cls
.srcInfo
.loc
),
1068 (cls
.sampleDynamicConstruct
? AttrDynamicallyConstructible
: AttrNone
),
1069 cls
.parentName
? cls
.parentName
: staticEmptyString(),
1070 cls
.srcInfo
.docComment
1072 pce
->setUserAttributes(cls
.userAttributes
);
1074 for (auto& x
: cls
.interfaceNames
) pce
->addInterface(x
);
1075 for (auto& x
: cls
.includedEnumNames
) pce
->addEnumInclude(x
);
1076 for (auto& x
: cls
.usedTraitNames
) pce
->addUsedTrait(x
);
1077 for (auto& x
: cls
.requirements
) pce
->addClassRequirement(x
);
1079 pce
->setIfaceVtableSlot(state
.index
.lookup_iface_vtable_slot(&cls
));
1081 bool needs86cinit
= false;
1083 auto const nativeConsts
= cls
.attrs
& AttrBuiltin
?
1084 Native::getClassConstants(cls
.name
) : nullptr;
1086 for (auto& cconst
: cls
.constants
) {
1087 if (nativeConsts
&& nativeConsts
->count(cconst
.name
)) {
1090 if (cconst
.kind
== ConstModifiers::Kind::Context
) {
1091 assertx(cconst
.cls
->tsame(cls
.name
));
1092 assertx(!cconst
.resolvedTypeStructure
);
1093 assertx(cconst
.invariance
== php::Const::Invariance::None
);
1094 pce
->addContextConstant(
1096 std::vector
<LowStringPtr
>(cconst
.coeffects
),
1100 } else if (!cconst
.val
.has_value()) {
1101 assertx(cconst
.cls
->tsame(cls
.name
));
1102 assertx(!cconst
.resolvedTypeStructure
);
1103 assertx(cconst
.invariance
== php::Const::Invariance::None
);
1104 pce
->addAbstractConstant(
1110 needs86cinit
|= cconst
.val
->m_type
== KindOfUninit
;
1113 cconst
.cls
->tsame(cls
.name
) ? nullptr : cconst
.cls
,
1114 &cconst
.val
.value(),
1115 ArrNR
{cconst
.resolvedTypeStructure
},
1124 for (auto& m
: cls
.methods
) {
1125 if (!m
) continue; // Removed
1126 if (!needs86cinit
&& m
->name
== s_86cinit
.get()) continue;
1127 FTRACE(2, " method: {}\n", m
->name
);
1128 auto const fe
= ue
.newMethodEmitter(m
->name
, pce
);
1129 emit_func(state
, ue
, *fe
, *m
);
1133 CompactVector
<Type
> useVars
;
1134 if (is_closure(cls
)) {
1135 auto f
= find_method(&cls
, s_invoke
.get());
1136 useVars
= state
.index
.lookup_closure_use_vars(f
, true);
1138 auto uvIt
= useVars
.begin();
1140 auto const privateProps
= state
.index
.lookup_private_props(&cls
, true);
1141 auto const privateStatics
= state
.index
.lookup_private_statics(&cls
, true);
1142 auto const publicStatics
= state
.index
.lookup_public_statics(&cls
);
1143 for (auto const& prop
: cls
.properties
) {
1144 auto const makeRat
= [&] (const Type
& ty
) -> RepoAuthType
{
1145 if (!ty
.subtypeOf(BCell
)) return RepoAuthType
{};
1146 if (ty
.subtypeOf(BBottom
)) {
1147 // A property can be TBottom if no sets (nor its initial value) is
1148 // compatible with its type-constraint, or if its LateInit and there's
1149 // no sets to it. The repo auth type here doesn't particularly matter,
1150 // since such a prop will be inaccessible.
1151 return RepoAuthType
{};
1153 return make_repo_type(ty
);
1156 auto const privPropTy
= [&] (const PropState
& ps
) -> std::pair
<Type
, bool> {
1157 if (is_closure(cls
)) {
1158 // For closures use variables will be the first properties of the
1159 // closure object, in declaration order
1160 if (uvIt
!= useVars
.end()) return std::make_pair(*uvIt
++, true);
1161 return std::make_pair(TCell
, true);
1164 auto it
= ps
.find(prop
.name
);
1165 if (it
== end(ps
)) return std::make_pair(TCell
, true);
1166 return std::make_pair(it
->second
.ty
, it
->second
.everModified
);
1169 auto propTy
= TCell
;
1170 auto everModified
= true;
1171 auto attrs
= prop
.attrs
;
1172 if (attrs
& AttrPrivate
) {
1173 std::tie(propTy
, everModified
) =
1174 privPropTy((attrs
& AttrStatic
) ? privateStatics
: privateProps
);
1175 } else if ((attrs
& (AttrPublic
|AttrProtected
)) && (attrs
& AttrStatic
)) {
1176 std::tie(propTy
, everModified
) = [&] {
1177 auto const it
= publicStatics
.find(prop
.name
);
1178 if (it
== end(publicStatics
)) return std::make_pair(TCell
, true);
1179 return std::make_pair(it
->second
.ty
, it
->second
.everModified
);
1183 if (!everModified
&& (attrs
& AttrStatic
) && !propTy
.is(BBottom
)) {
1184 attrs
|= AttrPersistent
;
1191 TypeIntersectionConstraint(prop
.typeConstraints
),
1198 assertx(uvIt
== useVars
.end());
1200 pce
->setEnumBaseTy(cls
.enumBaseTy
);
1203 void emit_typealias(UnitEmitter
& ue
, const php::TypeAlias
& alias
) {
1204 assertx(alias
.attrs
& AttrPersistent
);
1205 auto const te
= ue
.newTypeAliasEmitter(alias
.name
->toCppString());
1208 std::get
<0>(alias
.srcInfo
.loc
),
1209 std::get
<1>(alias
.srcInfo
.loc
),
1213 Array::attach(const_cast<ArrayData
*>(alias
.typeStructure
)),
1214 Array::attach(const_cast<ArrayData
*>(alias
.resolvedTypeStructure
))
1216 te
->setUserAttributes(alias
.userAttrs
);
1219 void emit_constant(UnitEmitter
& ue
, const php::Constant
& constant
) {
1220 assertx(constant
.attrs
& AttrPersistent
);
1229 void emit_module(UnitEmitter
& ue
, const php::Module
& module
) {
1232 module
.srcInfo
.docComment
,
1233 (int)std::get
<0>(module
.srcInfo
.loc
),
1234 (int)std::get
<1>(module
.srcInfo
.loc
),
1236 module
.userAttributes
,
1241 //////////////////////////////////////////////////////////////////////
1245 std::unique_ptr
<UnitEmitter
> emit_unit(Index
& index
, php::Unit
& unit
) {
1247 Trace::hhbbc_emit
, kSystemLibBump
, is_systemlib_part(unit
)
1250 assertx(check(unit
, index
));
1252 static std::atomic
<uint64_t> nextUnitId
{0};
1253 auto unitSn
= nextUnitId
++;
1255 auto extension
= [&]() -> Extension
* {
1256 if (!unit
.extName
->empty()) {
1257 return ExtensionRegistry::get(unit
.extName
->data());
1262 auto ue
= std::make_unique
<UnitEmitter
>(SHA1
{ unitSn
},
1265 FTRACE(1, " unit {}\n", unit
.filename
->data());
1267 ue
->m_filepath
= unit
.filename
;
1268 ue
->m_extension
= extension
;
1269 ue
->m_metaData
= unit
.metaData
;
1270 ue
->m_fileAttributes
= unit
.fileAttributes
;
1271 ue
->m_moduleName
= unit
.moduleName
;
1272 ue
->m_softDeployedRepoOnly
= unit
.packageInfo
.isModuleSoftDeployed(unit
.moduleName
);
1274 if (unit
.fatalInfo
) {
1275 // We should have dealt with verifier failures long ago.
1276 assertx(unit
.fatalInfo
->fatalLoc
.has_value());
1277 ue
->m_fatalUnit
= true;
1278 ue
->m_fatalOp
= unit
.fatalInfo
->fatalOp
;
1279 ue
->m_fatalLoc
= *unit
.fatalInfo
->fatalLoc
;
1280 ue
->m_fatalMsg
= unit
.fatalInfo
->fatalMsg
;
1283 EmitUnitState state
{ index
, &unit
};
1285 // Go thought all constants and see if they still need their
1286 // matching 86cinit func. In repo mode we are able to optimize away
1287 // most of them away. And if the const don't need them anymore we
1288 // should not emit them.
1289 hphp_fast_set
<const StringData
*> const_86cinit_funcs
;
1290 for (size_t id
= 0; id
< unit
.constants
.size(); ++id
) {
1291 auto const& c
= unit
.constants
[id
];
1292 if (type(c
->val
) != KindOfUninit
) {
1293 const_86cinit_funcs
.emplace(Constant::funcNameFromName(c
->name
));
1297 index
.for_each_unit_class_mutable(
1299 [&] (php::Class
& c
) {
1300 // No reason to include closures unless there's a reachable
1302 if (is_closure(c
)) return;
1303 recordClass(state
, *ue
, c
);
1307 index
.for_each_unit_func_mutable(
1309 [&] (php::Func
& f
) {
1310 if (const_86cinit_funcs
.count(f
.name
)) return;
1311 auto fe
= ue
->newFuncEmitter(f
.name
);
1312 emit_func(state
, *ue
, *fe
, f
);
1316 // Note that state.pces can grow inside the loop due to discovering
1318 for (size_t idx
= 0; idx
< state
.pces
.size(); ++idx
) {
1319 auto const [cls
, pce
] = state
.pces
[idx
];
1320 emit_class(state
, *ue
, pce
, *cls
);
1323 for (size_t id
= 0; id
< unit
.typeAliases
.size(); ++id
) {
1324 emit_typealias(*ue
, *unit
.typeAliases
[id
]);
1327 for (size_t id
= 0; id
< unit
.constants
.size(); ++id
) {
1328 emit_constant(*ue
, *unit
.constants
[id
]);
1331 for (size_t id
= 0; id
< unit
.modules
.size(); ++id
) {
1332 emit_module(*ue
, *unit
.modules
[id
]);
1339 //////////////////////////////////////////////////////////////////////