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/parse.h"
19 #include <unordered_map>
22 #include <boost/variant.hpp>
27 #include <unordered_set>
31 #include <folly/gen/Base.h>
32 #include <folly/gen/String.h>
33 #include <folly/Memory.h>
34 #include <folly/ScopeGuard.h>
35 #include <folly/sorted_vector_types.h>
37 #include "hphp/runtime/base/repo-auth-type-codec.h"
38 #include "hphp/runtime/base/repo-auth-type.h"
39 #include "hphp/runtime/ext/std/ext_std_misc.h"
40 #include "hphp/runtime/vm/func-emitter.h"
41 #include "hphp/runtime/vm/hhbc-codec.h"
42 #include "hphp/runtime/vm/preclass-emitter.h"
43 #include "hphp/runtime/vm/unit-emitter.h"
45 #include "hphp/hhbbc/cfg.h"
46 #include "hphp/hhbbc/eval-cell.h"
47 #include "hphp/hhbbc/func-util.h"
48 #include "hphp/hhbbc/optimize.h"
49 #include "hphp/hhbbc/representation.h"
50 #include "hphp/hhbbc/unit-util.h"
52 namespace HPHP
{ namespace HHBBC
{
54 TRACE_SET_MOD(hhbbc_parse
);
58 //////////////////////////////////////////////////////////////////////
60 const StaticString
s_Closure("Closure");
61 const StaticString
s_toString("__toString");
62 const StaticString
s_Stringish("Stringish");
63 const StaticString
s_XHPChild("XHPChild");
64 const StaticString
s_attr_Deprecated("__Deprecated");
65 const StaticString
s_class_alias("class_alias");
66 const StaticString
s___NoContextSensitiveAnalysis(
67 "__NoContextSensitiveAnalysis");
69 //////////////////////////////////////////////////////////////////////
71 void record_const_init(php::Program
& prog
, php::Func
* func
) {
72 auto id
= prog
.nextConstInit
.fetch_add(1, std::memory_order_relaxed
);
73 prog
.constInits
.ensureSize(id
+ 1);
75 DEBUG_ONLY
auto const oldVal
= prog
.constInits
.exchange(id
, func
);
79 //////////////////////////////////////////////////////////////////////
81 struct ParseUnitState
{
82 std::atomic
<uint32_t>& nextFuncId
;
85 * This is computed once for each unit and stashed here. We support
86 * having either a SourceLocTable or a LineTable. If we're
87 * optimizing a repo that was already created by hphpc, it won't
88 * have the full SourceLocTable information in it, so we're limited
91 boost::variant
< SourceLocTable
96 * Map from class id to the function containing its DefCls
97 * instruction. We use this to compute whether classes are defined
100 * TODO_4: if we don't end up with a use for this, remove it.
102 std::vector
<php::Func
*> defClsMap
;
105 * Map from Closure index to the function(s) containing their
106 * associated CreateCl opcode(s).
110 hphp_fast_set
<php::Func
*>
114 size_t operator()(const php::SrcLoc
& sl
) const {
115 auto const h1
= ((size_t)sl
.start
.col
<< 32) | sl
.start
.line
;
116 auto const h2
= ((size_t)sl
.past
.col
<< 32) | sl
.past
.line
;
117 return hash_int64_pair(h1
, h2
);
120 hphp_fast_map
<php::SrcLoc
, int32_t, SrcLocHash
> srcLocs
;
123 * Set of functions that should be processed in the constant
126 * Must include every function with a DefCns for correctness; cinit,
127 * pinit and sinit functions are added to improve overall
130 hphp_fast_set
<php::Func
*> constPassFuncs
;
133 * List of class aliases defined by this unit
135 CompactVector
<std::pair
<SString
,SString
>> classAliases
;
138 //////////////////////////////////////////////////////////////////////
140 std::set
<Offset
> findBasicBlocks(const FuncEmitter
& fe
) {
141 std::set
<Offset
> blockStarts
;
142 auto markBlock
= [&] (Offset off
) { blockStarts
.insert(off
); };
144 // Each entry point for a DV funclet is the start of a basic
146 for (auto& param
: fe
.params
) {
147 if (param
.hasDefaultValue()) markBlock(param
.funcletOff
);
150 // The main entry point is also a basic block start.
153 bool traceBc
= false;
156 * For each instruction, add it to the set if it must be the start
157 * of a block. It is the start of a block if it is:
161 * - Immediatelly following a control flow instruction, other than
164 auto offset
= fe
.base
;
166 auto const bc
= fe
.ue().bc();
167 auto const pc
= bc
+ offset
;
168 auto const nextOff
= offset
+ instrLen(pc
);
169 auto const atLast
= nextOff
== fe
.past
;
170 auto const op
= peek_op(pc
);
171 auto const breaksBB
=
172 instrIsNonCallControlFlow(op
) ||
173 instrFlags(op
) & TF
||
174 (isFCall(op
) && !instrJumpOffsets(pc
).empty());
176 if (options
.TraceBytecodes
.count(op
)) traceBc
= true;
178 if (breaksBB
&& !atLast
) {
182 auto const targets
= instrJumpTargets(bc
, offset
);
183 for (auto const& target
: targets
) markBlock(target
);
190 * Find blocks associated with exception handlers.
192 * - The start of each EH protected region begins a block.
194 * - The instruction immediately after the end of any
195 * EH protected region begins a block.
197 * - Each catch entry point begins a block.
199 * - The instruction immediately after the end of any
200 * catch region begins a block.
202 for (auto& eh
: fe
.ehtab
) {
203 markBlock(eh
.m_base
);
204 markBlock(eh
.m_past
);
205 markBlock(eh
.m_handler
);
206 if (eh
.m_end
!= kInvalidOffset
) {
211 // Now, each interval in blockStarts delinates a basic block.
212 blockStarts
.insert(fe
.past
);
215 FTRACE(0, "TraceBytecode (parse): {}::{} in {}\n",
216 fe
.pce() ? fe
.pce()->name()->data() : "",
217 fe
.name
, fe
.ue().m_filepath
);
225 * Map from EHEnt to the ExnNode that will represent exception
226 * behavior in that region.
228 hphp_fast_map
<const EHEnt
*,ExnNodeId
> ehMap
;
231 template<class FindBlock
>
232 ExnTreeInfo
build_exn_tree(const FuncEmitter
& fe
,
234 FindBlock findBlock
) {
236 func
.exnNodes
.reserve(fe
.ehtab
.size());
237 for (auto& eh
: fe
.ehtab
) {
238 auto const catchBlk
= findBlock(eh
.m_handler
, true);
239 auto node
= php::ExnNode
{};
240 node
.idx
= func
.exnNodes
.size();
241 node
.parent
= NoExnNodeId
;
242 node
.depth
= 1; // 0 depth means no ExnNode
243 node
.region
= php::CatchRegion
{ catchBlk
, eh
.m_iterId
};
244 ret
.ehMap
[&eh
] = node
.idx
;
246 if (eh
.m_parentIndex
!= -1) {
247 auto it
= ret
.ehMap
.find(&fe
.ehtab
[eh
.m_parentIndex
]);
248 assert(it
!= end(ret
.ehMap
));
249 assert(it
->second
< node
.idx
);
250 node
.parent
= it
->second
;
251 auto& parent
= func
.exnNodes
[node
.parent
];
252 node
.depth
= parent
.depth
+ 1;
253 parent
.children
.emplace_back(node
.idx
);
255 func
.exnNodes
.emplace_back(std::move(node
));
261 template<class T
> T
decode(PC
& pc
) {
262 auto const ret
= *reinterpret_cast<const T
*>(pc
);
267 template<class T
> void decode(PC
& pc
, T
& val
) {
271 MKey
make_mkey(const php::Func
& /*func*/, MemberKey mk
) {
274 return MKey
{mk
.mcode
, static_cast<LocalId
>(mk
.iva
)};
276 return MKey
{mk
.mcode
, mk
.iva
};
277 case MET
: case MPT
: case MQT
:
278 return MKey
{mk
.mcode
, mk
.litstr
};
280 return MKey
{mk
.mcode
, mk
.int64
};
287 template<class FindBlock
>
288 void populate_block(ParseUnitState
& puState
,
289 const FuncEmitter
& fe
,
294 FindBlock findBlock
) {
295 auto const& ue
= fe
.ue();
297 auto decode_stringvec
= [&] {
298 auto const vecLen
= decode_iva(pc
);
299 CompactVector
<LSString
> keys
;
300 for (auto i
= size_t{0}; i
< vecLen
; ++i
) {
301 keys
.push_back(ue
.lookupLitstr(decode
<int32_t>(pc
)));
306 auto decode_switch
= [&] (PC opPC
) {
308 auto const vecLen
= decode_iva(pc
);
309 for (int32_t i
= 0; i
< vecLen
; ++i
) {
310 ret
.push_back(findBlock(
311 opPC
+ decode
<Offset
>(pc
) - ue
.bc()
317 auto decode_sswitch
= [&] (PC opPC
) {
320 auto const vecLen
= decode_iva(pc
);
321 for (int32_t i
= 0; i
< vecLen
- 1; ++i
) {
322 auto const id
= decode
<Id
>(pc
);
323 auto const offset
= decode
<Offset
>(pc
);
324 ret
.emplace_back(ue
.lookupLitstr(id
),
325 findBlock(opPC
+ offset
- ue
.bc()));
328 // Final case is the default, and must have a litstr id of -1.
329 DEBUG_ONLY
auto const defId
= decode
<Id
>(pc
);
330 auto const defOff
= decode
<Offset
>(pc
);
332 ret
.emplace_back(nullptr, findBlock(opPC
+ defOff
- ue
.bc()));
336 auto decode_itertab
= [&] {
338 auto const vecLen
= decode_iva(pc
);
339 for (int32_t i
= 0; i
< vecLen
; ++i
) {
340 auto const kind
= static_cast<IterKind
>(decode_iva(pc
));
341 auto const id
= decode_iva(pc
);
342 auto const local
= [&]{
343 if (kind
!= KindOfLIter
) return NoLocalId
;
344 auto const loc
= decode_iva(pc
);
345 always_assert(loc
< func
.locals
.size());
348 ret
.push_back(IterTabEnt
{kind
, static_cast<IterId
>(id
), local
});
353 auto decode_argv32
= [&] {
354 CompactVector
<uint32_t> ret
;
355 auto const vecLen
= decode_iva(pc
);
356 for (uint32_t i
= 0; i
< vecLen
; ++i
) {
357 ret
.emplace_back(decode
<uint32_t>(pc
));
362 auto defcns
= [&] () {
363 puState
.constPassFuncs
.insert(&func
);
365 auto defcls
= [&] (const Bytecode
& b
) {
366 puState
.defClsMap
[b
.DefCls
.arg1
] = &func
;
368 auto defclsnop
= [&] (const Bytecode
& b
) {
369 puState
.defClsMap
[b
.DefClsNop
.arg1
] = &func
;
371 auto aliascls
= [&] (const Bytecode
& b
) {
372 puState
.classAliases
.emplace_back(b
.AliasCls
.str1
, b
.AliasCls
.str2
);
374 auto createcl
= [&] (const Bytecode
& b
) {
375 puState
.createClMap
[b
.CreateCl
.arg2
].insert(&func
);
377 auto fcallfuncd
= [&] (const Bytecode
& b
) {
378 if (b
.FCallFuncD
.str2
== s_class_alias
.get()) {
379 puState
.constPassFuncs
.insert(&func
);
383 #define IMM_BLA(n) auto targets = decode_switch(opPC);
384 #define IMM_SLA(n) auto targets = decode_sswitch(opPC);
385 #define IMM_ILA(n) auto iterTab = decode_itertab();
386 #define IMM_I32LA(n) auto argv = decode_argv32();
387 #define IMM_IVA(n) auto arg##n = decode_iva(pc);
388 #define IMM_I64A(n) auto arg##n = decode<int64_t>(pc);
389 #define IMM_LA(n) auto loc##n = [&] { \
390 LocalId id = decode_iva(pc); \
391 always_assert(id < func.locals.size()); \
394 #define IMM_IA(n) auto iter##n = [&] { \
395 IterId id = decode_iva(pc); \
396 always_assert(id < func.numIters); \
399 #define IMM_DA(n) auto dbl##n = decode<double>(pc);
400 #define IMM_SA(n) auto str##n = ue.lookupLitstr(decode<Id>(pc));
401 #define IMM_RATA(n) auto rat = decodeRAT(ue, pc);
402 #define IMM_AA(n) auto arr##n = ue.lookupArray(decode<Id>(pc));
403 #define IMM_BA(n) assert(next == past); \
404 auto target##n = findBlock( \
405 opPC + decode<Offset>(pc) - ue.bc());
406 #define IMM_OA_IMPL(n) subop##n; decode(pc, subop##n);
407 #define IMM_OA(type) type IMM_OA_IMPL
408 #define IMM_VSA(n) auto keys = decode_stringvec();
409 #define IMM_KA(n) auto mkey = make_mkey(func, decode_member_key(pc, &ue));
410 #define IMM_LAR(n) auto locrange = [&] { \
411 auto const range = decodeLocalRange(pc); \
412 always_assert(range.first + range.count \
413 <= func.locals.size()); \
414 return LocalRange { range.first, range.count }; \
416 #define IMM_FCA(n) auto fca = [&] { \
417 auto const fca = decodeFCallArgs(op, pc); \
418 auto const numBytes = (fca.numArgs + 7) / 8; \
419 auto byRefs = fca.enforceReffiness() \
420 ? std::make_unique<uint8_t[]>(numBytes) \
423 memcpy(byRefs.get(), fca.byRefs, numBytes); \
425 auto const aeOffset = fca.asyncEagerOffset; \
426 auto const aeTarget = aeOffset != kInvalidOffset \
427 ? findBlock(opPC + aeOffset - ue.bc()) \
429 assertx(aeTarget == NoBlockId || next == past); \
430 return FCallArgs(fca.flags, fca.numArgs, \
431 fca.numRets, std::move(byRefs), \
432 aeTarget, fca.lockWhileUnwinding); \
436 #define IMM_ONE(x) IMM_##x(1)
437 #define IMM_TWO(x, y) IMM_##x(1) IMM_##y(2)
438 #define IMM_THREE(x, y, z) IMM_TWO(x, y) IMM_##z(3)
439 #define IMM_FOUR(x, y, z, n) IMM_THREE(x, y, z) IMM_##n(4)
440 #define IMM_FIVE(x, y, z, n, m) IMM_FOUR(x, y, z, n) IMM_##m(5)
442 #define IMM_ARG(which, n) IMM_NAME_##which(n)
444 #define IMM_ARG_ONE(x) IMM_ARG(x, 1)
445 #define IMM_ARG_TWO(x, y) IMM_ARG(x, 1), IMM_ARG(y, 2)
446 #define IMM_ARG_THREE(x, y, z) IMM_ARG(x, 1), IMM_ARG(y, 2), \
448 #define IMM_ARG_FOUR(x, y, z, l) IMM_ARG(x, 1), IMM_ARG(y, 2), \
449 IMM_ARG(z, 3), IMM_ARG(l, 4)
450 #define IMM_ARG_FIVE(x, y, z, l, m) IMM_ARG(x, 1), IMM_ARG(y, 2), \
451 IMM_ARG(z, 3), IMM_ARG(l, 4), \
465 #define FLAGS_ARG_CF_TF
466 #define FLAGS_ARG_CF_FF
468 #define O(opcode, imms, inputs, outputs, flags) \
471 auto b = [&] () -> Bytecode { \
472 IMM_##imms /*these two macros advance the pc as required*/ \
474 if (isTypeAssert(op)) return bc::Nop {}; \
475 return bc::opcode { IMM_ARG_##imms FLAGS_ARG_##flags }; \
477 b.srcLoc = srcLocIx; \
478 if (Op::opcode == Op::DefCns) defcns(); \
479 if (Op::opcode == Op::DefCls) defcls(b); \
480 if (Op::opcode == Op::DefClsNop) defclsnop(b); \
481 if (Op::opcode == Op::AliasCls) aliascls(b); \
482 if (Op::opcode == Op::CreateCl) createcl(b); \
483 if (Op::opcode == Op::FCallFuncD) fcallfuncd(b); \
484 blk.hhbcs.push_back(std::move(b)); \
485 assert(pc == next); \
491 auto const opPC
= pc
;
492 auto const next
= pc
+ instrLen(opPC
);
493 assert(next
<= past
);
495 auto const srcLoc
= match
<php::SrcLoc
>(
497 [&] (const SourceLocTable
& tab
) {
499 if (getSourceLoc(tab
, opPC
- ue
.bc(), sloc
)) {
501 { static_cast<uint32_t>(sloc
.line0
),
502 static_cast<uint32_t>(sloc
.char0
) },
503 { static_cast<uint32_t>(sloc
.line1
),
504 static_cast<uint32_t>(sloc
.char1
) }
507 return php::SrcLoc
{};
509 [&] (const LineTable
& tab
) {
510 auto const line
= getLineNumber(tab
, opPC
- ue
.bc());
513 { static_cast<uint32_t>(line
), 0 },
514 { static_cast<uint32_t>(line
), 0 },
517 return php::SrcLoc
{};
521 auto const srcLocIx
= puState
.srcLocs
.emplace(
522 srcLoc
, puState
.srcLocs
.size()).first
->second
;
524 auto op
= decode_op(pc
);
525 switch (op
) { OPCODES
}
528 if (instrAllowsFallThru(op
)) {
529 blk
.fallthrough
= findBlock(next
- ue
.bc());
534 } while (pc
!= past
);
549 #undef FLAGS_ARG_CF_TF
550 #undef FLAGS_ARG_CF_FF
587 * If a block ends with an unconditional jump, change it to a
590 * If the jmp is the only instruction, convert it to a Nop, to avoid
591 * creating an empty block (we have an invariant that no blocks are
595 auto make_fallthrough
= [&] {
596 blk
.fallthrough
= blk
.hhbcs
.back().Jmp
.target1
;
597 if (blk
.hhbcs
.size() == 1) {
598 blk
.hhbcs
.back() = bc_with_loc(blk
.hhbcs
.back().srcLoc
, bc::Nop
{});
600 blk
.hhbcs
.pop_back();
604 switch (blk
.hhbcs
.back().op
) {
605 case Op::Jmp
: make_fallthrough(); break;
606 case Op::JmpNS
: make_fallthrough(); blk
.fallthroughNS
= true; break;
611 template<class FindBlk
>
612 void link_entry_points(php::Func
& func
,
613 const FuncEmitter
& fe
,
615 func
.dvEntries
.resize(fe
.params
.size(), NoBlockId
);
616 for (size_t i
= 0, sz
= fe
.params
.size(); i
< sz
; ++i
) {
617 if (fe
.params
[i
].hasDefaultValue()) {
618 auto const dv
= findBlock(fe
.params
[i
].funcletOff
);
619 func
.params
[i
].dvEntryPoint
= dv
;
620 func
.dvEntries
[i
] = dv
;
623 func
.mainEntry
= findBlock(fe
.base
);
626 void build_cfg(ParseUnitState
& puState
,
628 const FuncEmitter
& fe
) {
629 auto const blockStarts
= findBasicBlocks(fe
);
631 FTRACE(3, " blocks are at: {}\n",
632 [&]() -> std::string
{
633 using namespace folly::gen
;
634 return from(blockStarts
)
635 | eachTo
<std::string
>()
636 | unsplit
<std::string
>(" ");
640 std::map
<Offset
,std::pair
<BlockId
, copy_ptr
<php::Block
>>> blockMap
;
641 auto const bc
= fe
.ue().bc();
643 auto findBlock
= [&] (Offset off
, bool catchEntry
= false) {
644 auto& ent
= blockMap
[off
];
646 auto blk
= php::Block
{};
647 ent
.first
= blockMap
.size() - 1;
648 blk
.exnNodeId
= NoExnNodeId
;
649 blk
.catchEntry
= catchEntry
;
650 ent
.second
.emplace(std::move(blk
));
651 } else if (catchEntry
) {
652 ent
.second
.mutate()->catchEntry
= true;
657 auto exnTreeInfo
= build_exn_tree(fe
, func
, findBlock
);
659 hphp_fast_map
<BlockId
, std::pair
<int, int>> predSuccCounts
;
661 for (auto it
= begin(blockStarts
);
662 std::next(it
) != end(blockStarts
);
664 auto const bid
= findBlock(*it
);
665 auto const block
= blockMap
[*it
].second
.mutate();
666 auto const bcStart
= bc
+ *it
;
667 auto const bcStop
= bc
+ *std::next(it
);
669 if (auto const eh
= Func::findEH(fe
.ehtab
, *it
)) {
670 auto it
= exnTreeInfo
.ehMap
.find(eh
);
671 assert(it
!= end(exnTreeInfo
.ehMap
));
672 block
->exnNodeId
= it
->second
;
673 block
->throwExit
= func
.exnNodes
[it
->second
].region
.catchEntry
;
676 populate_block(puState
, fe
, func
, *block
, bcStart
, bcStop
, findBlock
);
677 forEachNonThrowSuccessor(*block
, [&] (BlockId blkId
) {
678 predSuccCounts
[blkId
].first
++;
679 predSuccCounts
[bid
].second
++;
683 link_entry_points(func
, fe
, findBlock
);
685 func
.blocks
.resize(blockMap
.size());
686 for (auto& kv
: blockMap
) {
687 auto const blk
= kv
.second
.second
.mutate();
688 auto const id
= kv
.second
.first
;
689 blk
->multiSucc
= predSuccCounts
[id
].second
> 1;
690 blk
->multiPred
= predSuccCounts
[id
].first
> 1;
691 func
.blocks
[id
] = std::move(kv
.second
.second
);
695 void add_frame_variables(php::Func
& func
, const FuncEmitter
& fe
) {
696 for (auto& param
: fe
.params
) {
697 func
.params
.push_back(
701 param
.typeConstraint
,
704 param
.userAttributes
,
713 func
.locals
.reserve(fe
.numLocals());
714 for (LocalId id
= 0; id
< fe
.numLocals(); ++id
) {
715 func
.locals
.push_back({nullptr, id
, false});
717 for (auto& kv
: fe
.localNameMap()) {
718 func
.locals
[kv
.second
].name
= kv
.first
;
721 func
.numIters
= fe
.numIterators();
724 std::unique_ptr
<php::Func
> parse_func(ParseUnitState
& puState
,
727 const FuncEmitter
& fe
) {
728 FTRACE(2, " func: {}\n",
729 fe
.name
->data() && *fe
.name
->data() ? fe
.name
->data() : "pseudomain");
731 auto ret
= std::make_unique
<php::Func
>();
732 ret
->idx
= puState
.nextFuncId
.fetch_add(1, std::memory_order_relaxed
);
734 ret
->srcInfo
= php::SrcInfo
{ fe
.getLocation(),
739 ret
->attrs
= static_cast<Attr
>(fe
.attrs
& ~AttrNoOverride
);
740 ret
->userAttributes
= fe
.userAttributes
;
741 ret
->returnUserType
= fe
.retUserType
;
742 ret
->retTypeConstraint
= fe
.retTypeConstraint
;
743 ret
->originalFilename
= fe
.originalFilename
;
746 ret
->isClosureBody
= fe
.isClosureBody
;
747 ret
->isAsync
= fe
.isAsync
;
748 ret
->isGenerator
= fe
.isGenerator
;
749 ret
->isPairGenerator
= fe
.isPairGenerator
;
750 ret
->isMemoizeWrapper
= fe
.isMemoizeWrapper
;
751 ret
->isMemoizeWrapperLSB
= fe
.isMemoizeWrapperLSB
;
752 ret
->isMemoizeImpl
= Func::isMemoizeImplName(fe
.name
);
753 ret
->isReified
= fe
.userAttributes
.find(s___Reified
.get()) !=
754 fe
.userAttributes
.end();
755 ret
->isRxDisabled
= fe
.isRxDisabled
;
756 ret
->noContextSensitiveAnalysis
= fe
.userAttributes
.find(
757 s___NoContextSensitiveAnalysis
.get()) != fe
.userAttributes
.end();
759 add_frame_variables(*ret
, fe
);
761 if (!RuntimeOption::ConstantFunctions
.empty()) {
762 auto const name
= [&] {
763 if (!cls
) return fe
.name
->toCppString();
764 return folly::sformat("{}::{}", cls
->name
, ret
->name
);
766 auto const it
= RuntimeOption::ConstantFunctions
.find(name
);
767 if (it
!= RuntimeOption::ConstantFunctions
.end()) {
768 ret
->locals
.resize(fe
.params
.size());
770 ret
->attrs
|= AttrIsFoldable
;
772 auto const mainEntry
= BlockId
{0};
774 auto blk
= php::Block
{};
775 blk
.exnNodeId
= NoExnNodeId
;
777 blk
.hhbcs
.push_back(gen_constant(it
->second
));
778 blk
.hhbcs
.push_back(bc::RetC
{});
779 ret
->blocks
.emplace_back(std::move(blk
));
781 ret
->dvEntries
.resize(fe
.params
.size(), NoBlockId
);
782 ret
->mainEntry
= mainEntry
;
784 for (size_t i
= 0, sz
= fe
.params
.size(); i
< sz
; ++i
) {
785 if (fe
.params
[i
].hasDefaultValue()) {
786 ret
->params
[i
].dvEntryPoint
= mainEntry
;
787 ret
->dvEntries
[i
] = mainEntry
;
795 * Builtin functions get some extra information. The returnType flag is only
796 * non-folly::none for these, but note that something may be a builtin and
797 * still have a folly::none return type.
800 auto const f
= [&] () -> HPHP::Func
* {
802 auto const cls
= Unit::lookupClass(ret
->cls
->name
);
803 return cls
? cls
->lookupMethod(ret
->name
) : nullptr;
805 return Unit::lookupBuiltin(ret
->name
);
809 ret
->nativeInfo
= std::make_unique
<php::NativeInfo
>();
810 ret
->nativeInfo
->returnType
= fe
.hniReturnType
;
811 if (f
&& ret
->params
.size()) {
812 for (auto i
= 0; i
< ret
->params
.size(); i
++) {
813 auto& pi
= ret
->params
[i
];
814 if (pi
.isVariadic
|| !f
->params()[i
].hasDefaultValue()) continue;
815 if (pi
.defaultValue
.m_type
== KindOfUninit
&&
816 pi
.phpCode
!= nullptr) {
817 auto res
= eval_cell_value([&] {
818 auto val
= f_constant(StrNR(pi
.phpCode
));
820 return *val
.asTypedValue();
823 FTRACE(4, "Argument {} to {}: Failed to evaluate {}\n",
824 i
, f
->fullName(), pi
.phpCode
);
827 pi
.defaultValue
= *res
;
831 if (!f
|| !f
->nativeFuncPtr() ||
832 (f
->userAttributes().count(
833 LowStringPtr(s_attr_Deprecated
.get())))) {
834 ret
->attrs
|= AttrNoFCallBuiltin
;
838 build_cfg(puState
, *ret
, fe
);
843 void parse_methods(ParseUnitState
& puState
,
846 const PreClassEmitter
& pce
) {
847 std::unique_ptr
<php::Func
> cinit
;
848 for (auto& me
: pce
.methods()) {
849 auto f
= parse_func(puState
, unit
, ret
, *me
);
850 if (f
->name
== s_86cinit
.get()) {
851 puState
.constPassFuncs
.insert(f
.get());
852 cinit
= std::move(f
);
854 if (f
->name
== s_86pinit
.get() ||
855 f
->name
== s_86sinit
.get() ||
856 f
->name
== s_86linit
.get()) {
857 puState
.constPassFuncs
.insert(f
.get());
859 ret
->methods
.push_back(std::move(f
));
862 if (cinit
) ret
->methods
.push_back(std::move(cinit
));
865 void add_stringish(php::Class
* cls
) {
866 // The runtime adds Stringish to any class providing a __toString() function,
867 // so we mirror that here to make sure analysis of interfaces is correct.
868 // All Stringish are also XHPChild, so handle it here as well.
869 if (cls
->attrs
& AttrInterface
&& cls
->name
->isame(s_Stringish
.get())) {
874 for (auto& iface
: cls
->interfaceNames
) {
875 if (iface
->isame(s_Stringish
.get())) return;
876 if (iface
->isame(s_XHPChild
.get())) { hasXHP
= true; }
879 for (auto& func
: cls
->methods
) {
880 if (func
->name
->isame(s_toString
.get())) {
881 FTRACE(2, "Adding Stringish and XHPChild to {}\n", cls
->name
->data());
882 cls
->interfaceNames
.push_back(s_Stringish
.get());
883 if (!hasXHP
&& !cls
->name
->isame(s_XHPChild
.get())) {
884 cls
->interfaceNames
.push_back(s_XHPChild
.get());
891 std::unique_ptr
<php::Record
> parse_record(php::Unit
* unit
,
892 const RecordEmitter
& re
) {
893 FTRACE(2, " record: {}\n", re
.name()->data());
895 auto ret
= std::make_unique
<php::Record
>();
897 ret
->srcInfo
= php::SrcInfo
{re
.getLocation(), re
.docComment()};
898 ret
->name
= re
.name();
899 ret
->attrs
= static_cast<Attr
>(re
.attrs() & ~AttrNoOverride
);
900 ret
->parentName
= re
.parentName();
902 ret
->userAttributes
= re
.userAttributes();
904 auto& fieldMap
= re
.fieldMap();
905 for (size_t idx
= 0; idx
< fieldMap
.size(); ++idx
) {
906 auto& field
= fieldMap
[idx
];
907 ret
->fields
.push_back(
914 field
.typeConstraint(),
915 field
.userAttributes()
922 std::unique_ptr
<php::Class
> parse_class(ParseUnitState
& puState
,
924 const PreClassEmitter
& pce
) {
925 FTRACE(2, " class: {}\n", pce
.name()->data());
927 auto ret
= std::make_unique
<php::Class
>();
928 ret
->name
= pce
.name();
929 ret
->srcInfo
= php::SrcInfo
{ pce
.getLocation(),
932 ret
->closureContextCls
= nullptr;
933 ret
->parentName
= pce
.parentName()->empty() ? nullptr
935 ret
->attrs
= static_cast<Attr
>(pce
.attrs() & ~AttrNoOverride
);
936 ret
->hoistability
= pce
.hoistability();
937 ret
->userAttributes
= pce
.userAttributes();
939 ret
->hasReifiedGenerics
= ret
->userAttributes
.find(s___Reified
.get()) !=
940 ret
->userAttributes
.end();
941 ret
->hasConstProp
= false;
943 for (auto& iface
: pce
.interfaces()) {
944 ret
->interfaceNames
.push_back(iface
);
947 copy(ret
->usedTraitNames
, pce
.usedTraits());
948 copy(ret
->traitPrecRules
, pce
.traitPrecRules());
949 copy(ret
->traitAliasRules
, pce
.traitAliasRules());
950 copy(ret
->requirements
, pce
.requirements());
952 parse_methods(puState
, ret
.get(), unit
, pce
);
953 add_stringish(ret
.get());
955 auto& propMap
= pce
.propMap();
956 for (size_t idx
= 0; idx
< propMap
.size(); ++idx
) {
957 auto& prop
= propMap
[idx
];
958 ret
->properties
.push_back(
962 prop
.userAttributes(),
965 prop
.typeConstraint(),
969 if ((prop
.attrs() & (AttrStatic
| AttrIsConst
)) == AttrIsConst
) {
970 ret
->hasConstProp
= true;
974 auto& constMap
= pce
.constMap();
975 for (size_t idx
= 0; idx
< constMap
.size(); ++idx
) {
976 auto& cconst
= constMap
[idx
];
977 // Set all constants as NoOverride, we'll clear this while building
979 ret
->constants
.push_back(
985 cconst
.typeConstraint(),
986 cconst
.isTypeconst(),
992 if (ret
->attrs
& AttrBuiltin
) {
993 if (auto nativeConsts
= Native::getClassConstants(ret
->name
)) {
994 for (auto const& cnsMap
: *nativeConsts
) {
996 tvCopy(cnsMap
.second
, tvaux
);
997 tvaux
.constModifiers() = {};
998 ret
->constants
.push_back(
1003 staticEmptyString(),
1004 staticEmptyString(),
1012 ret
->enumBaseTy
= pce
.enumBaseTy();
1017 //////////////////////////////////////////////////////////////////////
1019 void assign_closure_context(const ParseUnitState
&, php::Class
*);
1022 find_closure_context(const ParseUnitState
& puState
,
1023 php::Func
* createClFunc
) {
1024 if (auto const cls
= createClFunc
->cls
) {
1025 if (cls
->parentName
&&
1026 cls
->parentName
->isame(s_Closure
.get())) {
1027 // We have a closure created by a closure's invoke method, which
1028 // means it should inherit the outer closure's context, so we
1029 // have to know that first.
1030 assign_closure_context(puState
, cls
);
1031 return cls
->closureContextCls
;
1038 void assign_closure_context(const ParseUnitState
& puState
,
1040 if (clo
->closureContextCls
) return;
1042 auto clIt
= puState
.createClMap
.find(clo
->id
);
1043 if (clIt
== end(puState
.createClMap
)) {
1044 // Unused closure class. Technically not prohibited by the spec.
1049 * Any route to the closure context must yield the same class, or
1050 * things downstream won't understand. We try every route and
1051 * assert they are all the same here.
1053 * See bytecode.specification for CreateCl for the relevant
1056 always_assert(!clIt
->second
.empty());
1057 auto it
= begin(clIt
->second
);
1058 auto const representative
= find_closure_context(puState
, *it
);
1060 for (++it
; it
!= end(clIt
->second
); ++it
) {
1061 assert(find_closure_context(puState
, *it
) == representative
);
1064 clo
->closureContextCls
= representative
;
1067 void find_additional_metadata(const ParseUnitState
& puState
,
1069 for (auto& c
: unit
->classes
) {
1070 if (!c
->parentName
|| !c
->parentName
->isame(s_Closure
.get())) {
1073 assign_closure_context(puState
, c
.get());
1077 //////////////////////////////////////////////////////////////////////
1081 std::unique_ptr
<php::Unit
> parse_unit(php::Program
& prog
,
1082 std::unique_ptr
<UnitEmitter
> uep
) {
1083 Trace::Bump bumper
{Trace::hhbbc_parse
, kSystemLibBump
, uep
->isASystemLib()};
1084 FTRACE(2, "parse_unit {}\n", uep
->m_filepath
->data());
1086 if (RuntimeOption::EvalAbortBuildOnVerifyError
&& !uep
->check(false)) {
1089 "The unoptimized unit for %s did not pass verification, "
1090 "bailing because Eval.AbortBuildOnVerifyError is set\n",
1091 uep
->m_filepath
->data()
1096 auto const& ue
= *uep
;
1098 auto ret
= std::make_unique
<php::Unit
>();
1099 ret
->sha1
= ue
.sha1();
1100 ret
->filename
= ue
.m_filepath
;
1101 ret
->isHHFile
= ue
.m_isHHFile
;
1102 ret
->metaData
= ue
.m_metaData
;
1103 ret
->fileAttributes
= ue
.m_fileAttributes
;
1105 ParseUnitState puState
{ prog
.nextFuncId
};
1106 if (ue
.hasSourceLocInfo()) {
1107 puState
.srcLocInfo
= ue
.createSourceLocTable();
1109 puState
.srcLocInfo
= ue
.lineTable();
1111 puState
.defClsMap
.resize(ue
.numPreClasses(), nullptr);
1113 for (size_t i
= 0; i
< ue
.numPreClasses(); ++i
) {
1114 auto cls
= parse_class(puState
, ret
.get(), *ue
.pce(i
));
1115 ret
->classes
.push_back(std::move(cls
));
1118 for (size_t i
= 0; i
< ue
.numRecords(); ++i
) {
1119 auto rec
= parse_record(ret
.get(), *ue
.re(i
));
1120 ret
->records
.push_back(std::move(rec
));
1123 for (auto& fe
: ue
.fevec()) {
1124 auto func
= parse_func(puState
, ret
.get(), nullptr, *fe
);
1126 if (fe
->isPseudoMain()) {
1127 ret
->pseudomain
= std::move(func
);
1129 ret
->funcs
.push_back(std::move(func
));
1133 ret
->srcLocs
.resize(puState
.srcLocs
.size());
1134 for (auto& srcInfo
: puState
.srcLocs
) {
1135 ret
->srcLocs
[srcInfo
.second
] = srcInfo
.first
;
1138 for (auto& ta
: ue
.typeAliases()) {
1139 ret
->typeAliases
.push_back(
1140 std::make_unique
<php::TypeAlias
>(ta
)
1144 ret
->classAliases
= std::move(puState
.classAliases
);
1146 find_additional_metadata(puState
, ret
.get());
1148 for (auto const item
: puState
.constPassFuncs
) {
1149 record_const_init(prog
, item
);
1152 assert(check(*ret
));
1156 //////////////////////////////////////////////////////////////////////