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 struct ParseUnitState
{
72 std::atomic
<uint32_t>& nextFuncId
;
75 * This is computed once for each unit and stashed here. We support
76 * having either a SourceLocTable or a LineTable. If we're
77 * optimizing a repo that was already created by hphpc, it won't
78 * have the full SourceLocTable information in it, so we're limited
81 boost::variant
< SourceLocTable
86 * Map from class id to the function containing its DefCls
87 * instruction. We use this to compute whether classes are defined
90 * TODO_4: if we don't end up with a use for this, remove it.
92 std::vector
<php::Func
*> defClsMap
;
95 * Map from Closure index to the function(s) containing their
96 * associated CreateCl opcode(s).
100 hphp_fast_set
<php::Func
*>
104 size_t operator()(const php::SrcLoc
& sl
) const {
105 auto const h1
= ((size_t)sl
.start
.col
<< 32) | sl
.start
.line
;
106 auto const h2
= ((size_t)sl
.past
.col
<< 32) | sl
.past
.line
;
107 return hash_int64_pair(h1
, h2
);
110 hphp_fast_map
<php::SrcLoc
, int32_t, SrcLocHash
> srcLocs
;
113 * Set of functions that should be processed in the constant
116 * Must include every function with a DefCns for correctness; cinit,
117 * pinit and sinit functions are added to improve overall
120 hphp_fast_set
<php::Func
*> constPassFuncs
;
123 * List of class aliases defined by this unit
125 CompactVector
<std::pair
<SString
,SString
>> classAliases
;
128 //////////////////////////////////////////////////////////////////////
130 std::set
<Offset
> findBasicBlocks(const FuncEmitter
& fe
) {
131 std::set
<Offset
> blockStarts
;
132 auto markBlock
= [&] (Offset off
) { blockStarts
.insert(off
); };
134 // Each entry point for a DV funclet is the start of a basic
136 for (auto& param
: fe
.params
) {
137 if (param
.hasDefaultValue()) markBlock(param
.funcletOff
);
140 // The main entry point is also a basic block start.
143 bool traceBc
= false;
146 * For each instruction, add it to the set if it must be the start
147 * of a block. It is the start of a block if it is:
151 * - Immediatelly following a control flow instruction, other than
154 auto offset
= fe
.base
;
156 auto const bc
= fe
.ue().bc();
157 auto const pc
= bc
+ offset
;
158 auto const nextOff
= offset
+ instrLen(pc
);
159 auto const atLast
= nextOff
== fe
.past
;
160 auto const op
= peek_op(pc
);
161 auto const breaksBB
=
162 instrIsNonCallControlFlow(op
) ||
163 instrFlags(op
) & TF
||
164 (isFCall(op
) && !instrJumpOffsets(pc
).empty());
166 if (options
.TraceBytecodes
.count(op
)) traceBc
= true;
168 if (breaksBB
&& !atLast
) {
172 auto const targets
= instrJumpTargets(bc
, offset
);
173 for (auto const& target
: targets
) markBlock(target
);
180 * Find blocks associated with exception handlers.
182 * - The start of each EH protected region begins a block.
184 * - The instruction immediately after the end of any
185 * EH protected region begins a block.
187 * - Each catch entry point begins a block.
189 * - The instruction immediately after the end of any
190 * catch region begins a block.
192 for (auto& eh
: fe
.ehtab
) {
193 markBlock(eh
.m_base
);
194 markBlock(eh
.m_past
);
195 markBlock(eh
.m_handler
);
196 if (eh
.m_end
!= kInvalidOffset
) {
201 // Now, each interval in blockStarts delinates a basic block.
202 blockStarts
.insert(fe
.past
);
205 FTRACE(0, "TraceBytecode (parse): {}::{} in {}\n",
206 fe
.pce() ? fe
.pce()->name()->data() : "",
207 fe
.name
, fe
.ue().m_filepath
);
215 * Map from EHEnt to the ExnNode that will represent exception
216 * behavior in that region.
218 hphp_fast_map
<const EHEnt
*,ExnNodeId
> ehMap
;
221 template<class FindBlock
>
222 ExnTreeInfo
build_exn_tree(const FuncEmitter
& fe
,
224 FindBlock findBlock
) {
226 func
.exnNodes
.reserve(fe
.ehtab
.size());
227 for (auto& eh
: fe
.ehtab
) {
228 auto const catchBlk
= findBlock(eh
.m_handler
, true);
229 auto node
= php::ExnNode
{};
230 node
.idx
= func
.exnNodes
.size();
231 node
.parent
= NoExnNodeId
;
232 node
.depth
= 1; // 0 depth means no ExnNode
233 node
.region
= php::CatchRegion
{ catchBlk
, eh
.m_iterId
};
234 ret
.ehMap
[&eh
] = node
.idx
;
236 if (eh
.m_parentIndex
!= -1) {
237 auto it
= ret
.ehMap
.find(&fe
.ehtab
[eh
.m_parentIndex
]);
238 assert(it
!= end(ret
.ehMap
));
239 assert(it
->second
< node
.idx
);
240 node
.parent
= it
->second
;
241 auto& parent
= func
.exnNodes
[node
.parent
];
242 node
.depth
= parent
.depth
+ 1;
243 parent
.children
.emplace_back(node
.idx
);
245 func
.exnNodes
.emplace_back(std::move(node
));
251 template<class T
> T
decode(PC
& pc
) {
252 auto const ret
= *reinterpret_cast<const T
*>(pc
);
257 template<class T
> void decode(PC
& pc
, T
& val
) {
261 MKey
make_mkey(const php::Func
& /*func*/, MemberKey mk
) {
264 return MKey
{mk
.mcode
, static_cast<LocalId
>(mk
.iva
)};
266 return MKey
{mk
.mcode
, mk
.iva
};
267 case MET
: case MPT
: case MQT
:
268 return MKey
{mk
.mcode
, mk
.litstr
};
270 return MKey
{mk
.mcode
, mk
.int64
};
277 template<class FindBlock
>
278 void populate_block(ParseUnitState
& puState
,
279 const FuncEmitter
& fe
,
284 FindBlock findBlock
) {
285 auto const& ue
= fe
.ue();
287 auto decode_stringvec
= [&] {
288 auto const vecLen
= decode_iva(pc
);
289 CompactVector
<LSString
> keys
;
290 for (auto i
= size_t{0}; i
< vecLen
; ++i
) {
291 keys
.push_back(ue
.lookupLitstr(decode
<int32_t>(pc
)));
296 auto decode_switch
= [&] (PC opPC
) {
298 auto const vecLen
= decode_iva(pc
);
299 for (int32_t i
= 0; i
< vecLen
; ++i
) {
300 ret
.push_back(findBlock(
301 opPC
+ decode
<Offset
>(pc
) - ue
.bc()
307 auto decode_sswitch
= [&] (PC opPC
) {
310 auto const vecLen
= decode_iva(pc
);
311 for (int32_t i
= 0; i
< vecLen
- 1; ++i
) {
312 auto const id
= decode
<Id
>(pc
);
313 auto const offset
= decode
<Offset
>(pc
);
314 ret
.emplace_back(ue
.lookupLitstr(id
),
315 findBlock(opPC
+ offset
- ue
.bc()));
318 // Final case is the default, and must have a litstr id of -1.
319 DEBUG_ONLY
auto const defId
= decode
<Id
>(pc
);
320 auto const defOff
= decode
<Offset
>(pc
);
322 ret
.emplace_back(nullptr, findBlock(opPC
+ defOff
- ue
.bc()));
326 auto decode_itertab
= [&] {
328 auto const vecLen
= decode_iva(pc
);
329 for (int32_t i
= 0; i
< vecLen
; ++i
) {
330 auto const kind
= static_cast<IterKind
>(decode_iva(pc
));
331 auto const id
= decode_iva(pc
);
332 auto const local
= [&]{
333 if (kind
!= KindOfLIter
) return NoLocalId
;
334 auto const loc
= decode_iva(pc
);
335 always_assert(loc
< func
.locals
.size());
338 ret
.push_back(IterTabEnt
{kind
, static_cast<IterId
>(id
), local
});
343 auto defcns
= [&] () {
344 puState
.constPassFuncs
.insert(&func
);
346 auto defcls
= [&] (const Bytecode
& b
) {
347 puState
.defClsMap
[b
.DefCls
.arg1
] = &func
;
349 auto defclsnop
= [&] (const Bytecode
& b
) {
350 puState
.defClsMap
[b
.DefClsNop
.arg1
] = &func
;
352 auto aliascls
= [&] (const Bytecode
& b
) {
353 puState
.classAliases
.emplace_back(b
.AliasCls
.str1
, b
.AliasCls
.str2
);
355 auto createcl
= [&] (const Bytecode
& b
) {
356 puState
.createClMap
[b
.CreateCl
.arg2
].insert(&func
);
358 auto fcallfuncd
= [&] (const Bytecode
& b
) {
359 if (b
.FCallFuncD
.str2
== s_class_alias
.get()) {
360 puState
.constPassFuncs
.insert(&func
);
364 #define IMM_BLA(n) auto targets = decode_switch(opPC);
365 #define IMM_SLA(n) auto targets = decode_sswitch(opPC);
366 #define IMM_ILA(n) auto iterTab = decode_itertab();
367 #define IMM_IVA(n) auto arg##n = decode_iva(pc);
368 #define IMM_I64A(n) auto arg##n = decode<int64_t>(pc);
369 #define IMM_LA(n) auto loc##n = [&] { \
370 LocalId id = decode_iva(pc); \
371 always_assert(id < func.locals.size()); \
374 #define IMM_IA(n) auto iter##n = [&] { \
375 IterId id = decode_iva(pc); \
376 always_assert(id < func.numIters); \
379 #define IMM_DA(n) auto dbl##n = decode<double>(pc);
380 #define IMM_SA(n) auto str##n = ue.lookupLitstr(decode<Id>(pc));
381 #define IMM_RATA(n) auto rat = decodeRAT(ue, pc);
382 #define IMM_AA(n) auto arr##n = ue.lookupArray(decode<Id>(pc));
383 #define IMM_BA(n) assert(next == past); \
384 auto target##n = findBlock( \
385 opPC + decode<Offset>(pc) - ue.bc());
386 #define IMM_OA_IMPL(n) subop##n; decode(pc, subop##n);
387 #define IMM_OA(type) type IMM_OA_IMPL
388 #define IMM_VSA(n) auto keys = decode_stringvec();
389 #define IMM_KA(n) auto mkey = make_mkey(func, decode_member_key(pc, &ue));
390 #define IMM_LAR(n) auto locrange = [&] { \
391 auto const range = decodeLocalRange(pc); \
392 always_assert(range.first + range.count \
393 <= func.locals.size()); \
394 return LocalRange { range.first, range.count }; \
396 #define IMM_FCA(n) auto fca = [&] { \
397 auto const fca = decodeFCallArgs(op, pc); \
398 auto const numBytes = (fca.numArgs + 7) / 8; \
399 auto inoutArgs = fca.enforceInOut() \
400 ? std::make_unique<uint8_t[]>(numBytes) \
403 memcpy(inoutArgs.get(), fca.inoutArgs, numBytes); \
405 auto const aeOffset = fca.asyncEagerOffset; \
406 auto const aeTarget = aeOffset != kInvalidOffset \
407 ? findBlock(opPC + aeOffset - ue.bc()) \
409 assertx(aeTarget == NoBlockId || next == past); \
410 return FCallArgs(fca.flags, fca.numArgs, \
411 fca.numRets, std::move(inoutArgs), \
412 aeTarget, fca.lockWhileUnwinding); \
416 #define IMM_ONE(x) IMM_##x(1)
417 #define IMM_TWO(x, y) IMM_##x(1) IMM_##y(2)
418 #define IMM_THREE(x, y, z) IMM_TWO(x, y) IMM_##z(3)
419 #define IMM_FOUR(x, y, z, n) IMM_THREE(x, y, z) IMM_##n(4)
420 #define IMM_FIVE(x, y, z, n, m) IMM_FOUR(x, y, z, n) IMM_##m(5)
421 #define IMM_SIX(x, y, z, n, m, o) IMM_FIVE(x, y, z, n, m) IMM_##o(6)
423 #define IMM_ARG(which, n) IMM_NAME_##which(n)
425 #define IMM_ARG_ONE(x) IMM_ARG(x, 1)
426 #define IMM_ARG_TWO(x, y) IMM_ARG(x, 1), IMM_ARG(y, 2)
427 #define IMM_ARG_THREE(x, y, z) IMM_ARG(x, 1), IMM_ARG(y, 2), \
429 #define IMM_ARG_FOUR(x, y, z, l) IMM_ARG(x, 1), IMM_ARG(y, 2), \
430 IMM_ARG(z, 3), IMM_ARG(l, 4)
431 #define IMM_ARG_FIVE(x, y, z, l, m) IMM_ARG(x, 1), IMM_ARG(y, 2), \
432 IMM_ARG(z, 3), IMM_ARG(l, 4), \
434 #define IMM_ARG_SIX(x, y, z, l, m, n) IMM_ARG(x, 1), IMM_ARG(y, 2), \
435 IMM_ARG(z, 3), IMM_ARG(l, 4), \
436 IMM_ARG(m, 5), IMM_ARG(n, 6)
449 #define FLAGS_ARG_CF_TF
450 #define FLAGS_ARG_CF_FF
452 #define O(opcode, imms, inputs, outputs, flags) \
455 auto b = [&] () -> Bytecode { \
456 IMM_##imms /*these two macros advance the pc as required*/ \
458 if (isTypeAssert(op)) return bc::Nop {}; \
459 return bc::opcode { IMM_ARG_##imms FLAGS_ARG_##flags }; \
461 b.srcLoc = srcLocIx; \
462 if (Op::opcode == Op::DefCns) defcns(); \
463 if (Op::opcode == Op::DefCls) defcls(b); \
464 if (Op::opcode == Op::DefClsNop) defclsnop(b); \
465 if (Op::opcode == Op::AliasCls) aliascls(b); \
466 if (Op::opcode == Op::CreateCl) createcl(b); \
467 if (Op::opcode == Op::FCallFuncD) fcallfuncd(b); \
468 blk.hhbcs.push_back(std::move(b)); \
469 assert(pc == next); \
475 auto const opPC
= pc
;
476 auto const next
= pc
+ instrLen(opPC
);
477 assert(next
<= past
);
479 auto const srcLoc
= match
<php::SrcLoc
>(
481 [&] (const SourceLocTable
& tab
) {
483 if (getSourceLoc(tab
, opPC
- ue
.bc(), sloc
)) {
485 { static_cast<uint32_t>(sloc
.line0
),
486 static_cast<uint32_t>(sloc
.char0
) },
487 { static_cast<uint32_t>(sloc
.line1
),
488 static_cast<uint32_t>(sloc
.char1
) }
491 return php::SrcLoc
{};
493 [&] (const LineTable
& tab
) {
494 auto const line
= getLineNumber(tab
, opPC
- ue
.bc());
497 { static_cast<uint32_t>(line
), 0 },
498 { static_cast<uint32_t>(line
), 0 },
501 return php::SrcLoc
{};
505 auto const srcLocIx
= puState
.srcLocs
.emplace(
506 srcLoc
, puState
.srcLocs
.size()).first
->second
;
508 auto op
= decode_op(pc
);
509 switch (op
) { OPCODES
}
512 if (instrAllowsFallThru(op
)) {
513 blk
.fallthrough
= findBlock(next
- ue
.bc());
518 } while (pc
!= past
);
533 #undef FLAGS_ARG_CF_TF
534 #undef FLAGS_ARG_CF_FF
571 * If a block ends with an unconditional jump, change it to a
574 * If the jmp is the only instruction, convert it to a Nop, to avoid
575 * creating an empty block (we have an invariant that no blocks are
579 auto make_fallthrough
= [&] {
580 blk
.fallthrough
= blk
.hhbcs
.back().Jmp
.target1
;
581 if (blk
.hhbcs
.size() == 1) {
582 blk
.hhbcs
.back() = bc_with_loc(blk
.hhbcs
.back().srcLoc
, bc::Nop
{});
584 blk
.hhbcs
.pop_back();
588 switch (blk
.hhbcs
.back().op
) {
589 case Op::Jmp
: make_fallthrough(); break;
590 case Op::JmpNS
: make_fallthrough(); blk
.fallthroughNS
= true; break;
595 template<class FindBlk
>
596 void link_entry_points(php::Func
& func
,
597 const FuncEmitter
& fe
,
599 func
.dvEntries
.resize(fe
.params
.size(), NoBlockId
);
600 for (size_t i
= 0, sz
= fe
.params
.size(); i
< sz
; ++i
) {
601 if (fe
.params
[i
].hasDefaultValue()) {
602 auto const dv
= findBlock(fe
.params
[i
].funcletOff
);
603 func
.params
[i
].dvEntryPoint
= dv
;
604 func
.dvEntries
[i
] = dv
;
607 func
.mainEntry
= findBlock(fe
.base
);
610 void build_cfg(ParseUnitState
& puState
,
612 const FuncEmitter
& fe
) {
613 auto const blockStarts
= findBasicBlocks(fe
);
615 FTRACE(3, " blocks are at: {}\n",
616 [&]() -> std::string
{
617 using namespace folly::gen
;
618 return from(blockStarts
)
619 | eachTo
<std::string
>()
620 | unsplit
<std::string
>(" ");
624 std::map
<Offset
,std::pair
<BlockId
, copy_ptr
<php::Block
>>> blockMap
;
625 auto const bc
= fe
.ue().bc();
627 auto findBlock
= [&] (Offset off
, bool catchEntry
= false) {
628 auto& ent
= blockMap
[off
];
630 auto blk
= php::Block
{};
631 ent
.first
= blockMap
.size() - 1;
632 blk
.exnNodeId
= NoExnNodeId
;
633 blk
.catchEntry
= catchEntry
;
634 ent
.second
.emplace(std::move(blk
));
635 } else if (catchEntry
) {
636 ent
.second
.mutate()->catchEntry
= true;
641 auto exnTreeInfo
= build_exn_tree(fe
, func
, findBlock
);
643 hphp_fast_map
<BlockId
, std::pair
<int, int>> predSuccCounts
;
645 for (auto it
= begin(blockStarts
);
646 std::next(it
) != end(blockStarts
);
648 auto const bid
= findBlock(*it
);
649 auto const block
= blockMap
[*it
].second
.mutate();
650 auto const bcStart
= bc
+ *it
;
651 auto const bcStop
= bc
+ *std::next(it
);
653 if (auto const eh
= Func::findEH(fe
.ehtab
, *it
)) {
654 auto it
= exnTreeInfo
.ehMap
.find(eh
);
655 assert(it
!= end(exnTreeInfo
.ehMap
));
656 block
->exnNodeId
= it
->second
;
657 block
->throwExit
= func
.exnNodes
[it
->second
].region
.catchEntry
;
660 populate_block(puState
, fe
, func
, *block
, bcStart
, bcStop
, findBlock
);
661 forEachNonThrowSuccessor(*block
, [&] (BlockId blkId
) {
662 predSuccCounts
[blkId
].first
++;
663 predSuccCounts
[bid
].second
++;
667 link_entry_points(func
, fe
, findBlock
);
669 func
.blocks
.resize(blockMap
.size());
670 for (auto& kv
: blockMap
) {
671 auto const blk
= kv
.second
.second
.mutate();
672 auto const id
= kv
.second
.first
;
673 blk
->multiSucc
= predSuccCounts
[id
].second
> 1;
674 blk
->multiPred
= predSuccCounts
[id
].first
> 1;
675 func
.blocks
[id
] = std::move(kv
.second
.second
);
679 void add_frame_variables(php::Func
& func
, const FuncEmitter
& fe
) {
680 for (auto& param
: fe
.params
) {
681 func
.params
.push_back(
685 param
.typeConstraint
,
688 param
.userAttributes
,
696 func
.locals
.reserve(fe
.numLocals());
697 for (LocalId id
= 0; id
< fe
.numLocals(); ++id
) {
698 func
.locals
.push_back({nullptr, id
, false});
700 for (auto& kv
: fe
.localNameMap()) {
701 func
.locals
[kv
.second
].name
= kv
.first
;
704 func
.numIters
= fe
.numIterators();
707 std::unique_ptr
<php::Func
> parse_func(ParseUnitState
& puState
,
710 const FuncEmitter
& fe
) {
711 FTRACE(2, " func: {}\n",
712 fe
.name
->data() && *fe
.name
->data() ? fe
.name
->data() : "pseudomain");
714 auto ret
= std::make_unique
<php::Func
>();
715 ret
->idx
= puState
.nextFuncId
.fetch_add(1, std::memory_order_relaxed
);
717 ret
->srcInfo
= php::SrcInfo
{ fe
.getLocation(),
722 ret
->attrs
= static_cast<Attr
>(fe
.attrs
& ~AttrNoOverride
);
723 ret
->userAttributes
= fe
.userAttributes
;
724 ret
->returnUserType
= fe
.retUserType
;
725 ret
->retTypeConstraint
= fe
.retTypeConstraint
;
726 ret
->originalFilename
= fe
.originalFilename
;
729 ret
->isClosureBody
= fe
.isClosureBody
;
730 ret
->isAsync
= fe
.isAsync
;
731 ret
->isGenerator
= fe
.isGenerator
;
732 ret
->isPairGenerator
= fe
.isPairGenerator
;
733 ret
->isMemoizeWrapper
= fe
.isMemoizeWrapper
;
734 ret
->isMemoizeWrapperLSB
= fe
.isMemoizeWrapperLSB
;
735 ret
->isMemoizeImpl
= Func::isMemoizeImplName(fe
.name
);
736 ret
->isReified
= fe
.userAttributes
.find(s___Reified
.get()) !=
737 fe
.userAttributes
.end();
738 ret
->isRxDisabled
= fe
.isRxDisabled
;
739 ret
->noContextSensitiveAnalysis
= fe
.userAttributes
.find(
740 s___NoContextSensitiveAnalysis
.get()) != fe
.userAttributes
.end();
741 ret
->hasInOutArgs
= [&] {
742 for (auto& a
: fe
.params
) if (a
.inout
) return true;
746 add_frame_variables(*ret
, fe
);
748 if (!RuntimeOption::ConstantFunctions
.empty()) {
749 auto const name
= [&] {
750 if (!cls
) return fe
.name
->toCppString();
751 return folly::sformat("{}::{}", cls
->name
, ret
->name
);
753 auto const it
= RuntimeOption::ConstantFunctions
.find(name
);
754 if (it
!= RuntimeOption::ConstantFunctions
.end()) {
755 ret
->locals
.resize(fe
.params
.size());
757 ret
->attrs
|= AttrIsFoldable
;
759 auto const mainEntry
= BlockId
{0};
761 auto blk
= php::Block
{};
762 blk
.exnNodeId
= NoExnNodeId
;
764 blk
.hhbcs
.push_back(gen_constant(it
->second
));
765 blk
.hhbcs
.push_back(bc::RetC
{});
766 ret
->blocks
.emplace_back(std::move(blk
));
768 ret
->dvEntries
.resize(fe
.params
.size(), NoBlockId
);
769 ret
->mainEntry
= mainEntry
;
771 for (size_t i
= 0, sz
= fe
.params
.size(); i
< sz
; ++i
) {
772 if (fe
.params
[i
].hasDefaultValue()) {
773 ret
->params
[i
].dvEntryPoint
= mainEntry
;
774 ret
->dvEntries
[i
] = mainEntry
;
782 * Builtin functions get some extra information. The returnType flag is only
783 * non-folly::none for these, but note that something may be a builtin and
784 * still have a folly::none return type.
787 auto const f
= [&] () -> HPHP::Func
* {
789 auto const cls
= Unit::lookupClass(ret
->cls
->name
);
790 return cls
? cls
->lookupMethod(ret
->name
) : nullptr;
792 return Unit::lookupBuiltin(ret
->name
);
796 ret
->nativeInfo
= std::make_unique
<php::NativeInfo
>();
797 ret
->nativeInfo
->returnType
= fe
.hniReturnType
;
798 if (f
&& ret
->params
.size()) {
799 for (auto i
= 0; i
< ret
->params
.size(); i
++) {
800 auto& pi
= ret
->params
[i
];
801 if (pi
.isVariadic
|| !f
->params()[i
].hasDefaultValue()) continue;
802 if (pi
.defaultValue
.m_type
== KindOfUninit
&&
803 pi
.phpCode
!= nullptr) {
804 auto res
= eval_cell_value([&] {
805 auto val
= f_constant(StrNR(pi
.phpCode
));
807 return *val
.asTypedValue();
810 FTRACE(4, "Argument {} to {}: Failed to evaluate {}\n",
811 i
, f
->fullName(), pi
.phpCode
);
814 pi
.defaultValue
= *res
;
818 if (!f
|| !f
->nativeFuncPtr() ||
819 (f
->userAttributes().count(
820 LowStringPtr(s_attr_Deprecated
.get())))) {
821 ret
->attrs
|= AttrNoFCallBuiltin
;
825 build_cfg(puState
, *ret
, fe
);
830 void parse_methods(ParseUnitState
& puState
,
833 const PreClassEmitter
& pce
) {
834 std::unique_ptr
<php::Func
> cinit
;
835 for (auto& me
: pce
.methods()) {
836 auto f
= parse_func(puState
, unit
, ret
, *me
);
837 if (f
->name
== s_86cinit
.get()) {
838 puState
.constPassFuncs
.insert(f
.get());
839 cinit
= std::move(f
);
841 if (f
->name
== s_86pinit
.get() ||
842 f
->name
== s_86sinit
.get() ||
843 f
->name
== s_86linit
.get()) {
844 puState
.constPassFuncs
.insert(f
.get());
846 ret
->methods
.push_back(std::move(f
));
849 if (cinit
) ret
->methods
.push_back(std::move(cinit
));
852 void add_stringish(php::Class
* cls
) {
853 // The runtime adds Stringish to any class providing a __toString() function,
854 // so we mirror that here to make sure analysis of interfaces is correct.
855 // All Stringish are also XHPChild, so handle it here as well.
856 if (cls
->attrs
& AttrInterface
&& cls
->name
->isame(s_Stringish
.get())) {
861 for (auto& iface
: cls
->interfaceNames
) {
862 if (iface
->isame(s_Stringish
.get())) return;
863 if (iface
->isame(s_XHPChild
.get())) { hasXHP
= true; }
866 for (auto& func
: cls
->methods
) {
867 if (func
->name
->isame(s_toString
.get())) {
868 FTRACE(2, "Adding Stringish and XHPChild to {}\n", cls
->name
->data());
869 cls
->interfaceNames
.push_back(s_Stringish
.get());
870 if (!hasXHP
&& !cls
->name
->isame(s_XHPChild
.get())) {
871 cls
->interfaceNames
.push_back(s_XHPChild
.get());
878 std::unique_ptr
<php::Record
> parse_record(php::Unit
* unit
,
879 const RecordEmitter
& re
) {
880 FTRACE(2, " record: {}\n", re
.name()->data());
882 auto ret
= std::make_unique
<php::Record
>();
884 ret
->srcInfo
= php::SrcInfo
{re
.getLocation(), re
.docComment()};
885 ret
->name
= re
.name();
886 ret
->attrs
= static_cast<Attr
>(re
.attrs() & ~AttrNoOverride
);
887 ret
->parentName
= re
.parentName()->empty()? nullptr: re
.parentName();
889 ret
->userAttributes
= re
.userAttributes();
891 auto& fieldMap
= re
.fieldMap();
892 for (size_t idx
= 0; idx
< fieldMap
.size(); ++idx
) {
893 auto& field
= fieldMap
[idx
];
894 ret
->fields
.push_back(
901 field
.typeConstraint(),
902 field
.userAttributes()
909 std::unique_ptr
<php::Class
> parse_class(ParseUnitState
& puState
,
911 const PreClassEmitter
& pce
) {
912 FTRACE(2, " class: {}\n", pce
.name()->data());
914 auto ret
= std::make_unique
<php::Class
>();
915 ret
->name
= pce
.name();
916 ret
->srcInfo
= php::SrcInfo
{ pce
.getLocation(),
919 ret
->closureContextCls
= nullptr;
920 ret
->parentName
= pce
.parentName()->empty() ? nullptr
922 ret
->attrs
= static_cast<Attr
>(pce
.attrs() & ~AttrNoOverride
);
923 ret
->hoistability
= pce
.hoistability();
924 ret
->userAttributes
= pce
.userAttributes();
926 ret
->hasReifiedGenerics
= ret
->userAttributes
.find(s___Reified
.get()) !=
927 ret
->userAttributes
.end();
928 ret
->hasConstProp
= false;
930 for (auto& iface
: pce
.interfaces()) {
931 ret
->interfaceNames
.push_back(iface
);
934 copy(ret
->usedTraitNames
, pce
.usedTraits());
935 copy(ret
->traitPrecRules
, pce
.traitPrecRules());
936 copy(ret
->traitAliasRules
, pce
.traitAliasRules());
937 copy(ret
->requirements
, pce
.requirements());
939 parse_methods(puState
, ret
.get(), unit
, pce
);
940 add_stringish(ret
.get());
942 auto& propMap
= pce
.propMap();
943 for (size_t idx
= 0; idx
< propMap
.size(); ++idx
) {
944 auto& prop
= propMap
[idx
];
945 ret
->properties
.push_back(
949 prop
.userAttributes(),
952 prop
.typeConstraint(),
956 if ((prop
.attrs() & (AttrStatic
| AttrIsConst
)) == AttrIsConst
) {
957 ret
->hasConstProp
= true;
961 auto& constMap
= pce
.constMap();
962 for (size_t idx
= 0; idx
< constMap
.size(); ++idx
) {
963 auto& cconst
= constMap
[idx
];
964 // Set all constants as NoOverride, we'll clear this while building
966 ret
->constants
.push_back(
972 cconst
.typeConstraint(),
973 cconst
.isTypeconst(),
979 if (ret
->attrs
& AttrBuiltin
) {
980 if (auto nativeConsts
= Native::getClassConstants(ret
->name
)) {
981 for (auto const& cnsMap
: *nativeConsts
) {
983 tvCopy(cnsMap
.second
, tvaux
);
984 tvaux
.constModifiers() = {};
985 ret
->constants
.push_back(
999 ret
->enumBaseTy
= pce
.enumBaseTy();
1004 //////////////////////////////////////////////////////////////////////
1006 void assign_closure_context(const ParseUnitState
&, php::Class
*);
1009 find_closure_context(const ParseUnitState
& puState
,
1010 php::Func
* createClFunc
) {
1011 if (auto const cls
= createClFunc
->cls
) {
1012 if (cls
->parentName
&&
1013 cls
->parentName
->isame(s_Closure
.get())) {
1014 // We have a closure created by a closure's invoke method, which
1015 // means it should inherit the outer closure's context, so we
1016 // have to know that first.
1017 assign_closure_context(puState
, cls
);
1018 return cls
->closureContextCls
;
1025 void assign_closure_context(const ParseUnitState
& puState
,
1027 if (clo
->closureContextCls
) return;
1029 auto clIt
= puState
.createClMap
.find(clo
->id
);
1030 if (clIt
== end(puState
.createClMap
)) {
1031 // Unused closure class. Technically not prohibited by the spec.
1036 * Any route to the closure context must yield the same class, or
1037 * things downstream won't understand. We try every route and
1038 * assert they are all the same here.
1040 * See bytecode.specification for CreateCl for the relevant
1043 always_assert(!clIt
->second
.empty());
1044 auto it
= begin(clIt
->second
);
1045 auto const representative
= find_closure_context(puState
, *it
);
1047 for (++it
; it
!= end(clIt
->second
); ++it
) {
1048 assert(find_closure_context(puState
, *it
) == representative
);
1051 clo
->closureContextCls
= representative
;
1054 void find_additional_metadata(const ParseUnitState
& puState
,
1056 for (auto& c
: unit
->classes
) {
1057 if (!c
->parentName
|| !c
->parentName
->isame(s_Closure
.get())) {
1060 assign_closure_context(puState
, c
.get());
1064 //////////////////////////////////////////////////////////////////////
1068 void parse_unit(php::Program
& prog
, const UnitEmitter
* uep
) {
1069 Trace::Bump bumper
{Trace::hhbbc_parse
, kSystemLibBump
, uep
->isASystemLib()};
1070 FTRACE(2, "parse_unit {}\n", uep
->m_filepath
->data());
1072 if (RuntimeOption::EvalAbortBuildOnVerifyError
&& !uep
->check(false)) {
1075 "The unoptimized unit for %s did not pass verification, "
1076 "bailing because Eval.AbortBuildOnVerifyError is set\n",
1077 uep
->m_filepath
->data()
1082 auto const& ue
= *uep
;
1084 auto ret
= std::make_unique
<php::Unit
>();
1085 ret
->filename
= ue
.m_filepath
;
1086 ret
->isHHFile
= ue
.m_isHHFile
;
1087 ret
->metaData
= ue
.m_metaData
;
1088 ret
->fileAttributes
= ue
.m_fileAttributes
;
1090 ParseUnitState puState
{ prog
.nextFuncId
};
1091 if (ue
.hasSourceLocInfo()) {
1092 puState
.srcLocInfo
= ue
.createSourceLocTable();
1094 puState
.srcLocInfo
= ue
.lineTable();
1096 puState
.defClsMap
.resize(ue
.numPreClasses(), nullptr);
1098 for (size_t i
= 0; i
< ue
.numPreClasses(); ++i
) {
1099 auto cls
= parse_class(puState
, ret
.get(), *ue
.pce(i
));
1100 ret
->classes
.push_back(std::move(cls
));
1103 for (size_t i
= 0; i
< ue
.numRecords(); ++i
) {
1104 auto rec
= parse_record(ret
.get(), *ue
.re(i
));
1105 ret
->records
.push_back(std::move(rec
));
1108 for (auto& fe
: ue
.fevec()) {
1109 auto func
= parse_func(puState
, ret
.get(), nullptr, *fe
);
1111 if (fe
->isPseudoMain()) {
1112 ret
->pseudomain
= std::move(func
);
1114 ret
->funcs
.push_back(std::move(func
));
1118 ret
->srcLocs
.resize(puState
.srcLocs
.size());
1119 for (auto& srcInfo
: puState
.srcLocs
) {
1120 ret
->srcLocs
[srcInfo
.second
] = srcInfo
.first
;
1123 for (auto& ta
: ue
.typeAliases()) {
1124 ret
->typeAliases
.push_back(
1125 std::make_unique
<php::TypeAlias
>(ta
)
1129 ret
->classAliases
= std::move(puState
.classAliases
);
1131 find_additional_metadata(puState
, ret
.get());
1133 assert(check(*ret
));
1135 std::lock_guard
<std::mutex
> _
{prog
.lock
};
1136 for (auto const item
: puState
.constPassFuncs
) {
1137 prog
.constInits
.push_back(item
);
1139 ret
->sha1
= SHA1
{ prog
.units
.size() };
1140 prog
.units
.push_back(std::move(ret
));
1143 //////////////////////////////////////////////////////////////////////