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.h"
38 #include "hphp/runtime/base/bespoke/type-structure.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/type-alias-emitter.h"
44 #include "hphp/runtime/vm/unit-emitter.h"
46 #include "hphp/hhbbc/cfg.h"
47 #include "hphp/hhbbc/class-util.h"
48 #include "hphp/hhbbc/debug.h"
49 #include "hphp/hhbbc/eval-cell.h"
50 #include "hphp/hhbbc/func-util.h"
51 #include "hphp/hhbbc/optimize.h"
52 #include "hphp/hhbbc/representation.h"
53 #include "hphp/hhbbc/unit-util.h"
54 #include "hphp/hhbbc/wide-func.h"
56 #include "hphp/util/configs/eval.h"
58 namespace HPHP::HHBBC
{
60 TRACE_SET_MOD(hhbbc_parse
);
64 //////////////////////////////////////////////////////////////////////
66 const StaticString
s_toString("__toString");
67 const StaticString
s_Stringish("Stringish");
68 const StaticString
s_StringishObject("StringishObject");
69 const StaticString
s_XHPChild("XHPChild");
70 const StaticString
s_attr_Deprecated("__Deprecated");
71 const StaticString
s___NoContextSensitiveAnalysis(
72 "__NoContextSensitiveAnalysis");
74 //////////////////////////////////////////////////////////////////////
76 struct ParseUnitState
{
78 * This is computed once for each unit and stashed here. We support
79 * having either a SourceLocTable or a LineTable. If we're
80 * optimizing a repo that was already created by hphpc, it won't
81 * have the full SourceLocTable information in it, so we're limited
84 boost::variant
< SourceLocTable
89 * Map from Closure name to the function containing the Closure's
90 * associated CreateCl opcode.
100 size_t operator()(const php::SrcLoc
& sl
) const {
101 auto const h1
= ((size_t)sl
.start
.col
<< 32) | sl
.start
.line
;
102 auto const h2
= ((size_t)sl
.past
.col
<< 32) | sl
.past
.line
;
103 return hash_int64_pair(h1
, h2
);
106 hphp_fast_map
<php::SrcLoc
, int32_t, SrcLocHash
> srcLocs
;
109 //////////////////////////////////////////////////////////////////////
111 std::vector
<Offset
> findBasicBlocks(const FuncEmitter
& fe
) {
112 std::vector
<Offset
> blockStarts
;
113 auto markBlock
= [&] (Offset off
) { blockStarts
.emplace_back(off
); };
115 // Each entry point for a DV funclet is the start of a basic
117 for (auto& param
: fe
.params
) {
118 if (param
.hasDefaultValue()) markBlock(param
.funcletOff
);
121 // The main entry point is also a basic block start.
125 * For each instruction, add it to the set if it must be the start
126 * of a block. It is the start of a block if it is:
130 * - Immediatelly following a control flow instruction, other than
135 auto const bc
= fe
.bc();
136 auto const pc
= bc
+ offset
;
137 auto const nextOff
= offset
+ instrLen(pc
);
138 auto const atLast
= nextOff
== fe
.bcPos();
139 auto const op
= peek_op(pc
);
140 auto const breaksBB
=
141 instrIsNonCallControlFlow(op
) ||
142 instrFlags(op
) & TF
||
143 (isFCall(op
) && !instrJumpTargets(bc
, offset
).empty());
145 if (breaksBB
&& !atLast
) {
149 auto const targets
= instrJumpTargets(bc
, offset
);
150 for (auto const& target
: targets
) markBlock(target
);
157 * Find blocks associated with exception handlers.
159 * - The start of each EH protected region begins a block.
161 * - The instruction immediately after the end of any
162 * EH protected region begins a block.
164 * - Each catch entry point begins a block.
166 * - The instruction immediately after the end of any
167 * catch region begins a block.
169 for (auto& eh
: fe
.ehtab
) {
170 markBlock(eh
.m_base
);
171 markBlock(eh
.m_past
);
172 markBlock(eh
.m_handler
);
173 if (eh
.m_end
!= kInvalidOffset
) {
178 // Now, each interval in blockStarts delinates a basic block.
179 blockStarts
.emplace_back(fe
.bcPos());
181 std::sort(blockStarts
.begin(), blockStarts
.end());
183 std::unique(blockStarts
.begin(), blockStarts
.end()),
191 * Map from EHEnt to the ExnNode that will represent exception
192 * behavior in that region.
194 hphp_fast_map
<const EHEnt
*,ExnNodeId
> ehMap
;
197 template<class FindBlock
>
198 ExnTreeInfo
build_exn_tree(const FuncEmitter
& fe
,
200 FindBlock findBlock
) {
202 func
.exnNodes
.reserve(fe
.ehtab
.size());
203 for (auto& eh
: fe
.ehtab
) {
204 auto const catchBlk
= findBlock(eh
.m_handler
, true);
205 auto node
= php::ExnNode
{};
206 node
.idx
= func
.exnNodes
.size();
207 node
.parent
= NoExnNodeId
;
208 node
.depth
= 1; // 0 depth means no ExnNode
209 node
.region
= php::CatchRegion
{ catchBlk
, eh
.m_iterId
};
210 ret
.ehMap
[&eh
] = node
.idx
;
212 if (eh
.m_parentIndex
!= -1) {
213 auto it
= ret
.ehMap
.find(&fe
.ehtab
[eh
.m_parentIndex
]);
214 assertx(it
!= end(ret
.ehMap
));
215 assertx(it
->second
< node
.idx
);
216 node
.parent
= it
->second
;
217 auto& parent
= func
.exnNodes
[node
.parent
];
218 node
.depth
= parent
.depth
+ 1;
219 parent
.children
.emplace_back(node
.idx
);
221 func
.exnNodes
.emplace_back(std::move(node
));
227 template<class T
> T
decode(PC
& pc
) {
228 auto const ret
= *reinterpret_cast<const T
*>(pc
);
233 template<class T
> void decode(PC
& pc
, T
& val
) {
237 MKey
make_mkey(const php::Func
& /*func*/, MemberKey mk
) {
240 return MKey
{mk
.mcode
, mk
.local
, mk
.rop
};
242 return MKey
{mk
.mcode
, mk
.iva
, mk
.rop
};
243 case MET
: case MPT
: case MQT
:
244 return MKey
{mk
.mcode
, mk
.litstr
, mk
.rop
};
246 return MKey
{mk
.mcode
, mk
.int64
, mk
.rop
};
253 template<class FindBlock
>
254 void populate_block(ParseUnitState
& puState
,
255 const FuncEmitter
& fe
,
261 FindBlock findBlock
) {
262 auto const& ue
= fe
.ue();
264 auto decode_stringvec
= [&] {
265 auto const vecLen
= decode_iva(pc
);
266 CompactVector
<LSString
> keys
;
267 keys
.reserve(vecLen
);
268 for (auto i
= size_t{0}; i
< vecLen
; ++i
) {
269 keys
.push_back(ue
.lookupLitstr(decode
<int32_t>(pc
)));
274 auto decode_switch
= [&] (PC opPC
) {
276 auto const vecLen
= decode_iva(pc
);
277 for (int32_t i
= 0; i
< vecLen
; ++i
) {
278 ret
.push_back(findBlock(
279 opPC
+ decode
<Offset
>(pc
) - fe
.bc()
285 auto decode_sswitch
= [&] (PC opPC
) {
288 auto const vecLen
= decode_iva(pc
);
289 for (int32_t i
= 0; i
< vecLen
- 1; ++i
) {
290 auto const id
= decode
<Id
>(pc
);
291 auto const offset
= decode
<Offset
>(pc
);
292 ret
.emplace_back(ue
.lookupLitstr(id
),
293 findBlock(opPC
+ offset
- fe
.bc()));
296 // Final case is the default, and must have a litstr id of -1.
297 DEBUG_ONLY
auto const defId
= decode
<Id
>(pc
);
298 auto const defOff
= decode
<Offset
>(pc
);
299 assertx(defId
== -1);
300 ret
.emplace_back(nullptr, findBlock(opPC
+ defOff
- fe
.bc()));
304 auto createcl
= [&] (const Bytecode
& b
) {
306 auto const [existing
, emplaced
] =
307 puState
.createClMap
.emplace(b
.CreateCl
.str2
, &func
);
309 emplaced
|| existing
->second
== &func
,
310 "Closure {} used in CreateCl by two different functions '{}' and '{}'",
312 func_fullname(*existing
->second
),
317 #define IMM_BLA(n) auto targets = decode_switch(opPC);
318 #define IMM_SLA(n) auto targets = decode_sswitch(opPC);
319 #define IMM_IVA(n) auto arg##n = decode_iva(pc);
320 #define IMM_I64A(n) auto arg##n = decode<int64_t>(pc);
321 #define IMM_LA(n) auto loc##n = [&] { \
322 LocalId id = decode_iva(pc); \
323 always_assert(id < func.locals.size()); \
326 #define IMM_NLA(n) auto nloc##n = [&] { \
327 NamedLocal loc = decode_named_local(pc); \
328 always_assert(loc.id < func.locals.size());\
331 #define IMM_ILA(n) auto loc##n = [&] { \
332 LocalId id = decode_iva(pc); \
333 always_assert(id < func.locals.size()); \
336 #define IMM_IA(n) auto iter##n = [&] { \
337 IterId id = decode_iva(pc); \
338 always_assert(id < func.numIters); \
341 #define IMM_DA(n) auto dbl##n = decode<double>(pc);
342 #define IMM_SA(n) auto str##n = ue.lookupLitstr(decode<Id>(pc));
343 #define IMM_RATA(n) auto rat = decodeRAT(ue, pc);
344 #define IMM_AA(n) auto arr##n = ue.lookupArray(decode<Id>(pc));
345 #define IMM_BA(n) assertx(next == past); \
346 auto target##n = findBlock( \
347 opPC + decode<Offset>(pc) - fe.bc());
348 #define IMM_OA_IMPL(n) subop##n; decode(pc, subop##n);
349 #define IMM_OA(type) type IMM_OA_IMPL
350 #define IMM_VSA(n) auto keys = decode_stringvec();
351 #define IMM_KA(n) auto mkey = make_mkey(func, decode_member_key(pc, &ue));
352 #define IMM_LAR(n) auto locrange = [&] { \
353 auto const range = decodeLocalRange(pc); \
354 always_assert(range.first + range.count \
355 <= func.locals.size()); \
356 return LocalRange { range.first, range.count }; \
358 #define IMM_ITA(n) auto ita = decodeIterArgs(pc);
359 #define IMM_FCA(n) auto fca = [&] { \
360 auto const fca = decodeFCallArgs(op, pc, &ue); \
361 auto const numBytes = (fca.numArgs + 7) / 8; \
362 auto inoutArgs = fca.enforceInOut() \
363 ? std::make_unique<uint8_t[]>(numBytes) \
366 memcpy(inoutArgs.get(), fca.inoutArgs, numBytes); \
368 auto readonlyArgs = fca.enforceReadonly() \
369 ? std::make_unique<uint8_t[]>(numBytes) \
371 if (readonlyArgs) { \
372 memcpy(readonlyArgs.get(), fca.readonlyArgs, numBytes); \
374 auto const aeOffset = fca.asyncEagerOffset; \
375 auto const aeTarget = aeOffset != kInvalidOffset \
376 ? findBlock(opPC + aeOffset - fe.bc()) \
378 assertx(aeTarget == NoBlockId || next == past); \
379 return FCallArgs(fca.flags, fca.numArgs, \
380 fca.numRets, std::move(inoutArgs), \
381 std::move(readonlyArgs), \
382 aeTarget, fca.context); \
386 #define IMM_ONE(x) IMM_##x(1)
387 #define IMM_TWO(x, y) IMM_##x(1) IMM_##y(2)
388 #define IMM_THREE(x, y, z) IMM_TWO(x, y) IMM_##z(3)
389 #define IMM_FOUR(x, y, z, n) IMM_THREE(x, y, z) IMM_##n(4)
390 #define IMM_FIVE(x, y, z, n, m) IMM_FOUR(x, y, z, n) IMM_##m(5)
391 #define IMM_SIX(x, y, z, n, m, o) IMM_FIVE(x, y, z, n, m) IMM_##o(6)
393 #define IMM_ARG(which, n) IMM_NAME_##which(n)
395 #define IMM_ARG_ONE(x) IMM_ARG(x, 1)
396 #define IMM_ARG_TWO(x, y) IMM_ARG(x, 1), IMM_ARG(y, 2)
397 #define IMM_ARG_THREE(x, y, z) IMM_ARG(x, 1), IMM_ARG(y, 2), \
399 #define IMM_ARG_FOUR(x, y, z, l) IMM_ARG(x, 1), IMM_ARG(y, 2), \
400 IMM_ARG(z, 3), IMM_ARG(l, 4)
401 #define IMM_ARG_FIVE(x, y, z, l, m) IMM_ARG(x, 1), IMM_ARG(y, 2), \
402 IMM_ARG(z, 3), IMM_ARG(l, 4), \
404 #define IMM_ARG_SIX(x, y, z, l, m, n) IMM_ARG(x, 1), IMM_ARG(y, 2), \
405 IMM_ARG(z, 3), IMM_ARG(l, 4), \
406 IMM_ARG(m, 5), IMM_ARG(n, 6)
408 #define O(opcode, imms, inputs, outputs, flags) \
411 auto b = [&] () -> Bytecode { \
412 IMM_##imms /*these two macros advance the pc as required*/ \
413 if (isTypeAssert(op)) return bc::Nop {}; \
414 return bc::opcode { IMM_ARG_##imms }; \
416 b.srcLoc = srcLocIx; \
417 if (Op::opcode == Op::CreateCl) createcl(b); \
418 blk.hhbcs.push_back(std::move(b)); \
419 assertx(pc == next); \
425 auto const opPC
= pc
;
426 auto const next
= pc
+ instrLen(opPC
);
427 assertx(next
<= past
);
429 auto const srcLoc
= match
<php::SrcLoc
>(
431 [&] (const SourceLocTable
& tab
) {
433 if (SourceLocation::getLoc(tab
, opPC
- fe
.bc(), sloc
)) {
435 { static_cast<uint32_t>(sloc
.line0
),
436 static_cast<uint32_t>(sloc
.char0
) },
437 { static_cast<uint32_t>(sloc
.line1
),
438 static_cast<uint32_t>(sloc
.char1
) }
441 return php::SrcLoc
{};
443 [&] (const LineTable
& tab
) {
444 auto const line
= SourceLocation::getLineNumber(tab
, opPC
- fe
.bc());
447 { static_cast<uint32_t>(line
), 0 },
448 { static_cast<uint32_t>(line
), 0 },
451 return php::SrcLoc
{};
455 auto const srcLocIx
= puState
.srcLocs
.emplace(
456 srcLoc
, puState
.srcLocs
.size()).first
->second
;
458 auto op
= decode_op(pc
);
459 switch (op
) { OPCODES
}
462 if (instrAllowsFallThru(op
)) {
463 blk
.fallthrough
= findBlock(next
- fe
.bc());
468 } while (pc
!= past
);
509 * If a block ends with an unconditional jump, change it to a
512 * If the jmp is the only instruction, convert it to a Nop, to avoid
513 * creating an empty block (we have an invariant that no blocks are
517 auto make_fallthrough
= [&] {
518 blk
.fallthrough
= blk
.hhbcs
.back().Jmp
.target1
;
519 if (blk
.hhbcs
.size() == 1) {
520 blk
.hhbcs
.back() = bc_with_loc(blk
.hhbcs
.back().srcLoc
, bc::Nop
{});
522 blk
.hhbcs
.pop_back();
526 switch (blk
.hhbcs
.back().op
) {
527 case Op::Jmp
: make_fallthrough(); break;
532 template<class FindBlk
>
533 void link_entry_points(php::Func
& func
,
534 const FuncEmitter
& fe
,
536 func
.dvEntries
.resize(fe
.params
.size(), NoBlockId
);
537 for (size_t i
= 0, sz
= fe
.params
.size(); i
< sz
; ++i
) {
538 if (fe
.params
[i
].hasDefaultValue()) {
539 auto const dv
= findBlock(fe
.params
[i
].funcletOff
);
540 func
.params
[i
].dvEntryPoint
= dv
;
541 func
.dvEntries
[i
] = dv
;
544 func
.mainEntry
= findBlock(0);
547 void build_cfg(ParseUnitState
& puState
,
549 const FuncEmitter
& fe
) {
550 auto const blockStarts
= findBasicBlocks(fe
);
552 FTRACE(3, " blocks are at: {}\n",
553 [&]() -> std::string
{
554 using namespace folly::gen
;
555 return from(blockStarts
)
556 | eachTo
<std::string
>()
557 | unsplit
<std::string
>(" ");
561 hphp_fast_map
<Offset
,std::pair
<BlockId
, copy_ptr
<php::Block
>>> blockMap
;
562 auto const bc
= fe
.bc();
564 auto findBlock
= [&] (Offset off
, bool catchEntry
= false) {
565 auto& ent
= blockMap
[off
];
567 auto blk
= php::Block
{};
568 ent
.first
= blockMap
.size() - 1;
569 blk
.exnNodeId
= NoExnNodeId
;
570 blk
.catchEntry
= catchEntry
;
571 ent
.second
.emplace(std::move(blk
));
572 } else if (catchEntry
) {
573 ent
.second
.mutate()->catchEntry
= true;
578 auto exnTreeInfo
= build_exn_tree(fe
, func
, findBlock
);
580 hphp_fast_map
<BlockId
, std::pair
<int, int>> predSuccCounts
;
582 bool sawCreateCl
= false;
583 for (auto it
= begin(blockStarts
);
584 std::next(it
) != end(blockStarts
);
586 auto const bid
= findBlock(*it
);
587 auto const block
= blockMap
[*it
].second
.mutate();
588 auto const bcStart
= bc
+ *it
;
589 auto const bcStop
= bc
+ *std::next(it
);
591 if (auto const eh
= Func::findEH(fe
.ehtab
, *it
)) {
592 auto it
= exnTreeInfo
.ehMap
.find(eh
);
593 assertx(it
!= end(exnTreeInfo
.ehMap
));
594 block
->exnNodeId
= it
->second
;
595 block
->throwExit
= func
.exnNodes
[it
->second
].region
.catchEntry
;
598 populate_block(puState
, fe
, func
, *block
, bcStart
, bcStop
,
599 sawCreateCl
, findBlock
);
600 forEachNonThrowSuccessor(*block
, [&] (BlockId blkId
) {
601 predSuccCounts
[blkId
].first
++;
602 predSuccCounts
[bid
].second
++;
605 func
.hasCreateCl
= sawCreateCl
;
607 link_entry_points(func
, fe
, findBlock
);
609 auto mf
= php::WideFunc::create(func
);
610 mf
.blocks().resize(blockMap
.size());
611 for (auto& kv
: blockMap
) {
612 auto const blk
= kv
.second
.second
.mutate();
613 auto const id
= kv
.second
.first
;
614 blk
->multiSucc
= predSuccCounts
[id
].second
> 1;
615 blk
->multiPred
= predSuccCounts
[id
].first
> 1;
616 blk
->hhbcs
.shrink_to_fit();
617 mf
.blocks()[id
] = std::move(kv
.second
.second
);
621 void add_frame_variables(php::Func
& func
, const FuncEmitter
& fe
) {
622 for (auto& param
: fe
.params
) {
623 func
.params
.push_back(
627 param
.typeConstraint
,
630 param
.userAttributes
,
639 func
.locals
.reserve(fe
.numLocals());
640 for (LocalId id
= 0; id
< fe
.numLocals(); ++id
) {
641 func
.locals
.push_back({
649 for (auto& kv
: fe
.localNameMap()) {
650 func
.locals
[kv
.second
].name
= kv
.first
;
653 func
.numIters
= fe
.numIterators();
657 s_construct("__construct"),
658 s_DynamicallyCallable("__DynamicallyCallable"),
659 s_ModuleLevelTrait("__ModuleLevelTrait");
661 std::unique_ptr
<php::Func
> parse_func(ParseUnitState
& puState
,
664 const FuncEmitter
& fe
) {
665 if (fe
.hasSourceLocInfo()) {
666 puState
.srcLocInfo
= fe
.createSourceLocTable();
668 puState
.srcLocInfo
= fe
.lineTable();
671 FTRACE(2, " func: {}\n",
672 fe
.name
->data() && *fe
.name
->data() ? fe
.name
->data() : "pseudomain");
674 auto ret
= std::make_unique
<php::Func
>();
676 ret
->srcInfo
= php::SrcInfo
{ fe
.getLocation(),
678 ret
->unit
= unit
->filename
;
681 ret
->attrs
= static_cast<Attr
>((fe
.attrs
& ~AttrNoOverride
& ~AttrInterceptable
) |
684 ret
->userAttributes
= fe
.userAttributes
;
685 ret
->returnUserType
= fe
.retUserType
;
686 ret
->retTypeConstraint
= fe
.retTypeConstraint
;
687 ret
->hasParamsWithMultiUBs
= fe
.hasParamsWithMultiUBs
;
688 ret
->hasReturnWithMultiUBs
= fe
.hasReturnWithMultiUBs
;
689 ret
->returnUBs
= fe
.retUpperBounds
;
690 ret
->originalFilename
= fe
.originalFilename
;
691 ret
->originalModuleName
= unit
->moduleName
;
693 ret
->isClosureBody
= fe
.isClosureBody
;
694 ret
->isAsync
= fe
.isAsync
;
695 ret
->isGenerator
= fe
.isGenerator
;
696 ret
->isPairGenerator
= fe
.isPairGenerator
;
697 ret
->isMemoizeWrapper
= fe
.isMemoizeWrapper
;
698 ret
->isMemoizeWrapperLSB
= fe
.isMemoizeWrapperLSB
;
699 ret
->isMemoizeImpl
= Func::isMemoizeImplName(fe
.name
);
700 ret
->isNative
= fe
.isNative
;
701 ret
->isReified
= fe
.userAttributes
.find(s___Reified
.get()) !=
702 fe
.userAttributes
.end();
703 ret
->isReadonlyReturn
= fe
.attrs
& AttrReadonlyReturn
;
704 ret
->isReadonlyThis
= fe
.attrs
& AttrReadonlyThis
;
705 ret
->noContextSensitiveAnalysis
= fe
.userAttributes
.find(
706 s___NoContextSensitiveAnalysis
.get()) != fe
.userAttributes
.end();
707 ret
->fromModuleLevelTrait
= cls
&& cls
->userAttributes
.find(
708 s_ModuleLevelTrait
.get()) != cls
->userAttributes
.end();
709 ret
->hasInOutArgs
= [&] {
710 for (auto& a
: fe
.params
) if (a
.isInOut()) return true;
714 // Assume true, will be updated in build_cfg().
715 ret
->hasCreateCl
= true;
717 auto const coeffectsInfo
= getCoeffectsInfoFromList(
718 fe
.staticCoeffects
, cls
&& fe
.name
== s_construct
.get());
719 ret
->requiredCoeffects
= coeffectsInfo
.first
.toRequired();
720 ret
->coeffectEscapes
= coeffectsInfo
.second
;
722 for (auto& name
: fe
.staticCoeffects
) ret
->staticCoeffects
.push_back(name
);
723 for (auto& rule
: fe
.coeffectRules
) ret
->coeffectRules
.push_back(rule
);
725 ret
->sampleDynamicCalls
= [&] {
726 if (!(fe
.attrs
& AttrDynamicallyCallable
)) return false;
728 auto const it
= fe
.userAttributes
.find(s_DynamicallyCallable
.get());
729 if (it
== fe
.userAttributes
.end()) return false;
731 assertx(isArrayLikeType(type(it
->second
)));
732 auto const rate
= val(it
->second
).parr
->get(int64_t(0));
733 if (!isIntType(type(rate
)) || val(rate
).num
< 0) return false;
735 ret
->attrs
= Attr(ret
->attrs
& ~AttrDynamicallyCallable
);
739 add_frame_variables(*ret
, fe
);
741 if (!RuntimeOption::ConstantFunctions
.empty()) {
742 auto const name
= [&] {
743 if (!cls
) return fe
.name
->toCppString();
744 return folly::sformat("{}::{}", cls
->name
, ret
->name
);
746 auto const it
= RuntimeOption::ConstantFunctions
.find(func_fullname(*ret
));
747 if (it
!= RuntimeOption::ConstantFunctions
.end()) {
748 ret
->locals
.resize(fe
.params
.size());
750 ret
->attrs
|= AttrIsFoldable
;
752 auto const mainEntry
= BlockId
{0};
754 auto blk
= php::Block
{};
755 blk
.exnNodeId
= NoExnNodeId
;
756 blk
.hhbcs
= {gen_constant(it
->second
), bc::RetC
{}};
758 auto mf
= php::WideFunc::create(*ret
);
759 mf
.blocks().emplace_back(std::move(blk
));
761 ret
->dvEntries
.resize(fe
.params
.size(), NoBlockId
);
762 ret
->mainEntry
= mainEntry
;
764 for (size_t i
= 0, sz
= fe
.params
.size(); i
< sz
; ++i
) {
765 if (fe
.params
[i
].hasDefaultValue()) {
766 ret
->params
[i
].dvEntryPoint
= mainEntry
;
767 ret
->dvEntries
[i
] = mainEntry
;
775 * Builtin functions get some extra information. The returnType flag is only
776 * non-std::nullopt for these, but note that something may be a builtin and
777 * still have a std::nullopt return type.
780 // We shouldn't be processing native functions in an extern-worker
782 assertx(!extern_worker::g_in_job
);
784 auto const f
= [&] () -> HPHP::Func
* {
786 auto const cls
= Class::lookup(ret
->cls
->name
);
787 return cls
? cls
->lookupMethod(ret
->name
) : nullptr;
789 return Func::lookupBuiltin(ret
->name
);
793 if (f
&& ret
->params
.size()) {
794 for (auto i
= 0; i
< ret
->params
.size(); i
++) {
795 auto& pi
= ret
->params
[i
];
796 if (pi
.isVariadic
|| !f
->params()[i
].hasDefaultValue()) continue;
797 if (pi
.defaultValue
.m_type
== KindOfUninit
&&
798 pi
.phpCode
!= nullptr) {
799 auto res
= eval_cell_value([&] {
800 auto val
= HHVM_FN(constant
)(StrNR(pi
.phpCode
));
802 return *val
.asTypedValue();
805 FTRACE(4, "Argument {} to {}: Failed to evaluate {}\n",
806 i
, f
->fullName(), pi
.phpCode
);
809 pi
.defaultValue
= *res
;
813 if (!f
|| !f
->nativeFuncPtr() ||
814 (f
->userAttributes().count(
815 LowStringPtr(s_attr_Deprecated
.get())))) {
816 ret
->attrs
|= AttrNoFCallBuiltin
;
820 build_cfg(puState
, *ret
, fe
);
825 void parse_methods(ParseUnitState
& puState
,
828 const PreClassEmitter
& pce
) {
829 std::unique_ptr
<php::Func
> cinit
;
830 for (auto& me
: pce
.methods()) {
831 auto f
= parse_func(puState
, unit
, ret
, *me
);
832 if (f
->name
== s_86cinit
.get()) {
833 cinit
= std::move(f
);
835 f
->clsIdx
= ret
->methods
.size();
836 ret
->methods
.emplace_back(std::move(f
));
840 cinit
->clsIdx
= ret
->methods
.size();
841 ret
->methods
.emplace_back(std::move(cinit
));
845 void add_stringish(php::Class
* cls
) {
846 // The runtime adds StringishObject to any class providing a
847 // __toString() function, so we mirror that here to make sure
848 // analysis of interfaces is correct. All StringishObjects are also
849 // XHPChild, so handle it here as well.
850 if (cls
->attrs
& AttrInterface
&& cls
->name
->tsame(s_StringishObject
.get())) {
855 for (auto& iface
: cls
->interfaceNames
) {
856 if (iface
->tsame(s_StringishObject
.get())) return;
857 if (iface
->tsame(s_XHPChild
.get())) { hasXHP
= true; }
860 const auto has_toString
= std::any_of(
863 [](const auto& func
) { return func
->name
== s_toString
.get(); });
865 FTRACE(2, "Adding Stringish, StringishObject and XHPChild to {}\n",
867 cls
->interfaceNames
.push_back(s_StringishObject
.get());
868 if (!hasXHP
&& !cls
->name
->tsame(s_XHPChild
.get())) {
869 cls
->interfaceNames
.push_back(s_XHPChild
.get());
874 const StaticString
s_DynamicallyConstructible("__DynamicallyConstructible");
876 std::unique_ptr
<php::Class
> parse_class(ParseUnitState
& puState
,
878 const PreClassEmitter
& pce
) {
879 FTRACE(2, " class: {}\n", pce
.name()->data());
881 auto ret
= std::make_unique
<php::Class
>();
882 ret
->name
= pce
.name();
883 ret
->srcInfo
= php::SrcInfo
{ pce
.getLocation(),
885 ret
->unit
= unit
->filename
;
886 ret
->closureContextCls
= nullptr;
887 ret
->parentName
= pce
.parentName()->empty() ? nullptr
889 ret
->attrs
= static_cast<Attr
>(
890 (pce
.attrs() & ~(AttrNoOverride
| AttrNoOverrideRegular
)) |
892 ret
->userAttributes
= pce
.userAttributes();
893 ret
->hasReifiedGenerics
= ret
->userAttributes
.find(s___Reified
.get()) !=
894 ret
->userAttributes
.end();
895 ret
->hasConstProp
= false;
896 ret
->moduleName
= unit
->moduleName
;
898 ret
->sampleDynamicConstruct
= [&] {
899 if (!(ret
->attrs
& AttrDynamicallyConstructible
)) return false;
901 auto const it
= ret
->userAttributes
.find(s_DynamicallyConstructible
.get());
902 if (it
== ret
->userAttributes
.end()) return false;
904 assertx(isArrayLikeType(type(it
->second
)));
905 auto const rate
= val(it
->second
).parr
->get(int64_t(0));
906 if (!isIntType(type(rate
)) || val(rate
).num
< 0) return false;
908 ret
->attrs
= Attr(ret
->attrs
& ~AttrDynamicallyConstructible
);
913 for (auto& iface
: pce
.interfaces()) {
914 ret
->interfaceNames
.push_back(iface
);
916 for (auto& enumInclude
: pce
.enumIncludes()) {
917 ret
->includedEnumNames
.push_back(enumInclude
);
920 copy(ret
->usedTraitNames
, pce
.usedTraits());
921 copy(ret
->requirements
, pce
.requirements());
923 parse_methods(puState
, ret
.get(), unit
, pce
);
924 add_stringish(ret
.get());
926 auto& propMap
= pce
.propMap();
927 for (size_t idx
= 0; idx
< propMap
.size(); ++idx
) {
928 auto& prop
= propMap
[idx
];
929 assertx(prop
.typeConstraint().validForProp());
930 ret
->properties
.push_back(
934 prop
.attrs() & ~(AttrNoBadRedeclare
|
935 AttrNoImplicitNullable
|
936 AttrInitialSatisfiesTC
)
938 prop
.userAttributes(),
941 prop
.typeConstraint(),
946 if ((prop
.attrs() & (AttrStatic
| AttrIsConst
)) == AttrIsConst
) {
947 ret
->hasConstProp
= true;
951 auto const getTypeStructureConst
= [&] (const PreClassEmitter::Const
& cconst
) {
952 auto const val
= cconst
.valOption();
953 if (!Cfg::Eval::EmitBespokeTypeStructures
||
955 !isArrayLikeType(val
->type())) {
958 auto const ad
= val
->val().parr
;
959 if (!bespoke::TypeStructure::isValidTypeStructure(ad
)) return val
;
960 auto const ts
= bespoke::TypeStructure::MakeFromVanillaStatic(ad
, true);
961 return make_optional(make_tv
<KindOfPersistentDict
>(ts
));
964 auto& constMap
= pce
.constMap();
965 for (size_t idx
= 0; idx
< constMap
.size(); ++idx
) {
966 auto& cconst
= constMap
[idx
];
967 auto const cconstValue
= (cconst
.kind() == ConstModifiers::Kind::Type
)
968 ? getTypeStructureConst(cconst
)
969 : cconst
.valOption();
971 ret
->constants
.push_back(
979 php::Const::Invariance::None
,
981 cconst
.isFromTrait(),
987 if (ret
->attrs
& AttrBuiltin
) {
988 // We shouldn't be processing any builtins in an extern-worker job
990 assertx(!extern_worker::g_in_job
);
992 if (auto nativeConsts
= Native::getClassConstants(ret
->name
)) {
993 for (auto const& cnsMap
: *nativeConsts
) {
995 tvCopy(cnsMap
.second
, tvaux
);
996 tvaux
.constModifiers() = {};
997 ret
->constants
.push_back(
1004 ConstModifiers::Kind::Value
,
1005 php::Const::Invariance::None
,
1015 ret
->enumBaseTy
= pce
.enumBaseTy();
1020 //////////////////////////////////////////////////////////////////////
1022 void assign_closure_context(const ParseUnitState
&, php::Class
*);
1024 std::pair
<LSString
, bool>
1025 find_closure_context(const ParseUnitState
& puState
,
1026 php::Func
* createClFunc
) {
1027 if (auto const cls
= createClFunc
->cls
) {
1028 if (is_closure(*cls
)) {
1029 // We have a closure created by a closure's invoke method, which
1030 // means it should inherit the outer closure's context, so we
1031 // have to know that first.
1032 assign_closure_context(puState
, cls
);
1033 if (cls
->closureContextCls
) {
1034 return std::make_pair(cls
->closureContextCls
, true);
1036 if (cls
->closureDeclFunc
) {
1037 return std::make_pair(cls
->closureDeclFunc
, false);
1039 always_assert(false);
1041 return std::make_pair(cls
->name
, true);
1043 return std::make_pair(createClFunc
->name
, false);
1046 void assign_closure_context(const ParseUnitState
& puState
,
1048 assertx(is_closure(*clo
));
1050 if (clo
->closureContextCls
|| clo
->closureDeclFunc
) return;
1052 auto const clIt
= puState
.createClMap
.find(clo
->name
);
1053 if (clIt
== end(puState
.createClMap
)) {
1054 // Unused closure class. Technically not prohibited by the spec.
1058 auto const [ctx
, isClass
] = find_closure_context(puState
, clIt
->second
);
1060 clo
->closureContextCls
= ctx
;
1061 clo
->closureDeclFunc
= nullptr;
1063 clo
->closureContextCls
= nullptr;
1064 clo
->closureDeclFunc
= ctx
;
1068 //////////////////////////////////////////////////////////////////////
1072 std::unique_ptr
<php::Constant
> parse_constant(const Constant
& c
) {
1073 return std::unique_ptr
<php::Constant
>(new php::Constant
{
1076 c
.attrs
| AttrPersistent
1080 std::unique_ptr
<php::Module
> parse_module(const Module
& m
) {
1081 return std::unique_ptr
<php::Module
>(new php::Module
{
1087 m
.attrs
| AttrPersistent
,
1094 std::unique_ptr
<php::TypeAlias
> parse_type_alias(const TypeAliasEmitter
& te
) {
1095 FTRACE(2, " type alias: {}\n", te
.name()->data());
1097 auto const ts
= [&] () -> SArray
{
1098 auto const a
= te
.typeStructure();
1099 if (a
.isNull()) return nullptr;
1100 assertx(a
->isStatic());
1101 if (!Cfg::Eval::EmitBespokeTypeStructures
) return a
.get();
1102 if (!bespoke::TypeStructure::isValidTypeStructure(a
.get())) return a
.get();
1103 return bespoke::TypeStructure::MakeFromVanillaStatic(a
.get(), true);
1106 return std::unique_ptr
<php::TypeAlias
>(new php::TypeAlias
{
1107 php::SrcInfo
{ te
.getLocation() },
1109 te
.attrs() | AttrPersistent
,
1112 te
.userAttributes(),
1118 ParsedUnit
parse_unit(const UnitEmitter
& ue
) {
1119 Trace::Bump bumper
{Trace::hhbbc_parse
, kSystemLibBump
, ue
.isASystemLib()};
1120 FTRACE(2, "parse_unit {}\n", ue
.m_filepath
->data());
1123 ret
.unit
= std::make_unique
<php::Unit
>();
1125 ret
.unit
->filename
= ue
.m_filepath
;
1126 ret
.unit
->metaData
= ue
.m_metaData
;
1127 ret
.unit
->fileAttributes
= ue
.m_fileAttributes
;
1128 ret
.unit
->moduleName
= ue
.m_moduleName
;
1129 ret
.unit
->packageInfo
= ue
.m_packageInfo
;
1131 ret
.unit
->extName
= [&]{
1132 if (ue
.m_extension
) {
1133 return makeStaticString(ue
.m_extension
->getName());
1135 return staticEmptyString();
1138 if (RO::EvalAbortBuildOnVerifyError
&& !ue
.check(false)) {
1139 // Record a FatalInfo without a location. This represents a
1140 // verifier failure.
1145 "The unoptimized unit for {} did not pass verification, "
1146 "bailing because Eval.AbortBuildOnVerifyError is set\n",
1150 ret
.unit
->fatalInfo
= std::make_unique
<php::FatalInfo
>(std::move(fi
));
1154 if (ue
.m_fatalUnit
) {
1155 php::FatalInfo fi
{ue
.m_fatalLoc
, ue
.m_fatalOp
, ue
.m_fatalMsg
};
1156 ret
.unit
->fatalInfo
= std::make_unique
<php::FatalInfo
>(std::move(fi
));
1159 ParseUnitState puState
;
1161 for (auto const pce
: ue
.preclasses()) {
1162 auto cls
= parse_class(puState
, ret
.unit
.get(), *pce
);
1163 ret
.unit
->classes
.emplace_back(cls
->name
);
1164 ret
.classes
.emplace_back(std::move(cls
));
1167 for (auto const& fe
: ue
.fevec()) {
1168 assertx(!fe
->pce());
1169 auto func
= parse_func(puState
, ret
.unit
.get(), nullptr, *fe
);
1170 ret
.unit
->funcs
.emplace_back(func
->name
);
1171 ret
.funcs
.emplace_back(std::move(func
));
1174 ret
.unit
->srcLocs
.resize(puState
.srcLocs
.size());
1175 for (auto const& srcInfo
: puState
.srcLocs
) {
1176 ret
.unit
->srcLocs
[srcInfo
.second
] = srcInfo
.first
;
1179 for (auto const& te
: ue
.typeAliases()) {
1180 ret
.unit
->typeAliases
.emplace_back(parse_type_alias(*te
));
1183 for (auto const& c
: ue
.constants()) {
1184 ret
.unit
->constants
.emplace_back(parse_constant(c
));
1187 for (auto const& m
: ue
.modules()) {
1188 ret
.unit
->modules
.emplace_back(parse_module(m
));
1191 TSStringToOneT
<php::Class
*> classMap
;
1192 classMap
.reserve(ret
.classes
.size());
1193 for (auto const& c
: ret
.classes
) {
1194 classMap
.try_emplace(c
->name
, c
.get());
1197 // Remove closures declared in other classes from the unit's class
1198 // list. These are owned by their declaring classes, not the unit.
1201 begin(ret
.classes
), end(ret
.classes
),
1202 [&] (std::unique_ptr
<php::Class
>& c
) {
1203 if (!is_closure(*c
)) return false;
1204 assign_closure_context(puState
, c
.get());
1205 if (!c
->closureContextCls
) return false;
1206 auto const ctx
= folly::get_default(classMap
, c
->closureContextCls
);
1208 ctx
->closures
.emplace_back(std::move(c
));
1216 // Make sure all closures in our createClMap (which are just
1217 // strings) actually exist in this unit (CreateCls should not be
1218 // referring to classes outside of their unit).
1219 TSStringSet classes
;
1220 for (auto const& c
: ret
.classes
) {
1221 classes
.emplace(c
->name
);
1222 for (auto const& clo
: c
->closures
) classes
.emplace(clo
->name
);
1224 for (auto const [name
, _
] : puState
.createClMap
) {
1225 always_assert(classes
.count(name
));
1228 for (auto const& f
: ret
.funcs
) always_assert(check(*f
));
1229 for (auto const& c
: ret
.classes
) always_assert(check(*c
));
1231 state_after("parse", ret
);
1236 //////////////////////////////////////////////////////////////////////