Deshim VirtualExecutor in folly
[hiphop-php.git] / hphp / hhbbc / parse.cpp
blob213db8e838a79ed959a0a8a0cf763b9904a76da1
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
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"
18 #include <thread>
19 #include <unordered_map>
20 #include <map>
22 #include <boost/variant.hpp>
23 #include <algorithm>
24 #include <iterator>
25 #include <memory>
26 #include <set>
27 #include <unordered_set>
28 #include <utility>
29 #include <vector>
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);
62 namespace {
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
82 * to line numbers.
84 boost::variant< SourceLocTable
85 , LineTable
86 > srcLocInfo;
89 * Map from Closure name to the function containing the Closure's
90 * associated CreateCl opcode.
92 hphp_fast_map<
93 SString,
94 php::Func*,
95 string_data_hash,
96 string_data_tsame
97 > createClMap;
99 struct SrcLocHash {
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
116 // block.
117 for (auto& param : fe.params) {
118 if (param.hasDefaultValue()) markBlock(param.funcletOff);
121 // The main entry point is also a basic block start.
122 markBlock(0);
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:
128 * - A jump target
130 * - Immediatelly following a control flow instruction, other than
131 * a call.
133 auto offset = 0;
134 for (;;) {
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) {
146 markBlock(nextOff);
149 auto const targets = instrJumpTargets(bc, offset);
150 for (auto const& target : targets) markBlock(target);
152 offset = nextOff;
153 if (atLast) break;
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) {
174 markBlock(eh.m_end);
178 // Now, each interval in blockStarts delinates a basic block.
179 blockStarts.emplace_back(fe.bcPos());
181 std::sort(blockStarts.begin(), blockStarts.end());
182 blockStarts.erase(
183 std::unique(blockStarts.begin(), blockStarts.end()),
184 blockStarts.end()
186 return blockStarts;
189 struct ExnTreeInfo {
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,
199 php::Func& func,
200 FindBlock findBlock) {
201 ExnTreeInfo ret;
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));
224 return ret;
227 template<class T> T decode(PC& pc) {
228 auto const ret = *reinterpret_cast<const T*>(pc);
229 pc += sizeof ret;
230 return ret;
233 template<class T> void decode(PC& pc, T& val) {
234 val = decode<T>(pc);
237 MKey make_mkey(const php::Func& /*func*/, MemberKey mk) {
238 switch (mk.mcode) {
239 case MEL: case MPL:
240 return MKey{mk.mcode, mk.local, mk.rop};
241 case MEC: case MPC:
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};
245 case MEI:
246 return MKey{mk.mcode, mk.int64, mk.rop};
247 case MW:
248 return MKey{};
250 not_reached();
253 template<class FindBlock>
254 void populate_block(ParseUnitState& puState,
255 const FuncEmitter& fe,
256 php::Func& func,
257 php::Block& blk,
258 PC pc,
259 PC const past,
260 bool& sawCreateCl,
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)));
271 return keys;
274 auto decode_switch = [&] (PC opPC) {
275 SwitchTab ret;
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()
282 return ret;
285 auto decode_sswitch = [&] (PC opPC) {
286 SSwitchTab ret;
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()));
301 return ret;
304 auto createcl = [&] (const Bytecode& b) {
305 sawCreateCl = true;
306 auto const [existing, emplaced] =
307 puState.createClMap.emplace(b.CreateCl.str2, &func);
308 always_assert_flog(
309 emplaced || existing->second == &func,
310 "Closure {} used in CreateCl by two different functions '{}' and '{}'",
311 b.CreateCl.str2,
312 func_fullname(*existing->second),
313 func_fullname(func)
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()); \
324 return id; \
325 }();
326 #define IMM_NLA(n) auto nloc##n = [&] { \
327 NamedLocal loc = decode_named_local(pc); \
328 always_assert(loc.id < func.locals.size());\
329 return loc; \
330 }();
331 #define IMM_ILA(n) auto loc##n = [&] { \
332 LocalId id = decode_iva(pc); \
333 always_assert(id < func.locals.size()); \
334 return id; \
335 }();
336 #define IMM_IA(n) auto iter##n = [&] { \
337 IterId id = decode_iva(pc); \
338 always_assert(id < func.numIters); \
339 return id; \
340 }();
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 }; \
357 }();
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) \
364 : nullptr; \
365 if (inoutArgs) { \
366 memcpy(inoutArgs.get(), fca.inoutArgs, numBytes); \
368 auto readonlyArgs = fca.enforceReadonly() \
369 ? std::make_unique<uint8_t[]>(numBytes) \
370 : nullptr; \
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()) \
377 : NoBlockId; \
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); \
383 }();
385 #define IMM_NA
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)
394 #define IMM_ARG_NA
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), \
398 IMM_ARG(z, 3)
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), \
403 IMM_ARG(m, 5)
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) \
409 case Op::opcode: \
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 }; \
415 }(); \
416 b.srcLoc = srcLocIx; \
417 if (Op::opcode == Op::CreateCl) createcl(b); \
418 blk.hhbcs.push_back(std::move(b)); \
419 assertx(pc == next); \
421 break;
423 assertx(pc != past);
424 do {
425 auto const opPC = pc;
426 auto const next = pc + instrLen(opPC);
427 assertx(next <= past);
429 auto const srcLoc = match<php::SrcLoc>(
430 puState.srcLocInfo,
431 [&] (const SourceLocTable& tab) {
432 SourceLoc sloc;
433 if (SourceLocation::getLoc(tab, opPC - fe.bc(), sloc)) {
434 return php::SrcLoc {
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());
445 if (line != -1) {
446 return php::SrcLoc {
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 }
461 if (next == past) {
462 if (instrAllowsFallThru(op)) {
463 blk.fallthrough = findBlock(next - fe.bc());
467 pc = next;
468 } while (pc != past);
470 #undef O
472 #undef IMM_BLA
473 #undef IMM_SLA
474 #undef IMM_IVA
475 #undef IMM_I64A
476 #undef IMM_LA
477 #undef IMM_NLA
478 #undef IMM_ILA
479 #undef IMM_IA
480 #undef IMM_DA
481 #undef IMM_SA
482 #undef IMM_RATA
483 #undef IMM_AA
484 #undef IMM_BA
485 #undef IMM_OA_IMPL
486 #undef IMM_OA
487 #undef IMM_VSA
488 #undef IMM_LAR
489 #undef IMM_ITA
490 #undef IMM_FCA
492 #undef IMM_NA
493 #undef IMM_ONE
494 #undef IMM_TWO
495 #undef IMM_THREE
496 #undef IMM_FOUR
497 #undef IMM_FIVE
499 #undef IMM_ARG
500 #undef IMM_ARG_NA
501 #undef IMM_ARG_ONE
502 #undef IMM_ARG_TWO
503 #undef IMM_ARG_THREE
504 #undef IMM_ARG_FOUR
505 #undef IMM_ARG_FIVE
506 #undef IMM_ARG_SIX
509 * If a block ends with an unconditional jump, change it to a
510 * fallthrough edge.
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
514 * empty).
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{});
521 } else {
522 blk.hhbcs.pop_back();
526 switch (blk.hhbcs.back().op) {
527 case Op::Jmp: make_fallthrough(); break;
528 default: break;
532 template<class FindBlk>
533 void link_entry_points(php::Func& func,
534 const FuncEmitter& fe,
535 FindBlk findBlock) {
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,
548 php::Func& func,
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];
566 if (!ent.second) {
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;
575 return ent.first;
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);
585 ++it) {
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(
624 php::Param {
625 param.defaultValue,
626 NoBlockId,
627 param.typeConstraint,
628 param.userType,
629 param.upperBounds,
630 param.userAttributes,
631 param.phpCode,
632 param.isInOut(),
633 param.isReadonly(),
634 param.isVariadic()
639 func.locals.reserve(fe.numLocals());
640 for (LocalId id = 0; id < fe.numLocals(); ++id) {
641 func.locals.push_back({
642 .name = nullptr,
643 .id = id,
644 .killed = false,
645 .nameId = id,
646 .unusedName = false
649 for (auto& kv : fe.localNameMap()) {
650 func.locals[kv.second].name = kv.first;
653 func.numIters = fe.numIterators();
656 const StaticString
657 s_construct("__construct"),
658 s_DynamicallyCallable("__DynamicallyCallable"),
659 s_ModuleLevelTrait("__ModuleLevelTrait");
661 std::unique_ptr<php::Func> parse_func(ParseUnitState& puState,
662 php::Unit* unit,
663 php::Class* cls,
664 const FuncEmitter& fe) {
665 if (fe.hasSourceLocInfo()) {
666 puState.srcLocInfo = fe.createSourceLocTable();
667 } else {
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>();
675 ret->name = fe.name;
676 ret->srcInfo = php::SrcInfo { fe.getLocation(),
677 fe.docComment };
678 ret->unit = unit->filename;
679 ret->cls = cls;
681 ret->attrs = static_cast<Attr>((fe.attrs & ~AttrNoOverride & ~AttrInterceptable) |
682 AttrPersistent);
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;
711 return false;
712 }();
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);
736 return true;
737 }();
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);
745 }();
746 auto const it = RuntimeOption::ConstantFunctions.find(func_fullname(*ret));
747 if (it != RuntimeOption::ConstantFunctions.end()) {
748 ret->locals.resize(fe.params.size());
749 ret->numIters = 0;
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;
770 return ret;
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.
779 if (fe.isNative) {
780 // We shouldn't be processing native functions in an extern-worker
781 // job right now.
782 assertx(!extern_worker::g_in_job);
784 auto const f = [&] () -> HPHP::Func* {
785 if (ret->cls) {
786 auto const cls = Class::lookup(ret->cls->name);
787 return cls ? cls->lookupMethod(ret->name) : nullptr;
788 } else {
789 return Func::lookupBuiltin(ret->name);
791 }();
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));
801 val.setEvalScalar();
802 return *val.asTypedValue();
804 if (!res) {
805 FTRACE(4, "Argument {} to {}: Failed to evaluate {}\n",
806 i, f->fullName(), pi.phpCode);
807 continue;
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);
822 return ret;
825 void parse_methods(ParseUnitState& puState,
826 php::Class* ret,
827 php::Unit* unit,
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);
834 } else {
835 f->clsIdx = ret->methods.size();
836 ret->methods.emplace_back(std::move(f));
839 if (cinit) {
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())) {
851 return;
854 bool hasXHP = false;
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(
861 begin(cls->methods),
862 end(cls->methods),
863 [](const auto& func) { return func->name == s_toString.get(); });
864 if (has_toString) {
865 FTRACE(2, "Adding Stringish, StringishObject and XHPChild to {}\n",
866 cls->name->data());
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,
877 php::Unit* unit,
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(),
884 pce.docComment() };
885 ret->unit = unit->filename;
886 ret->closureContextCls = nullptr;
887 ret->parentName = pce.parentName()->empty() ? nullptr
888 : pce.parentName();
889 ret->attrs = static_cast<Attr>(
890 (pce.attrs() & ~(AttrNoOverride | AttrNoOverrideRegular)) |
891 AttrPersistent);
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);
909 return true;
910 }();
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(
931 php::Prop {
932 prop.name(),
933 static_cast<Attr>(
934 prop.attrs() & ~(AttrNoBadRedeclare |
935 AttrNoImplicitNullable |
936 AttrInitialSatisfiesTC)
938 prop.userAttributes(),
939 prop.docComment(),
940 prop.userType(),
941 prop.typeConstraint(),
942 prop.upperBounds(),
943 prop.val()
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 ||
954 !val.has_value() ||
955 !isArrayLikeType(val->type())) {
956 return val;
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(
972 php::Const {
973 cconst.name(),
974 ret->name,
975 cconstValue,
976 cconst.coeffects(),
977 nullptr,
978 cconst.kind(),
979 php::Const::Invariance::None,
980 cconst.isAbstract(),
981 cconst.isFromTrait(),
982 false
987 if (ret->attrs & AttrBuiltin) {
988 // We shouldn't be processing any builtins in an extern-worker job
989 // right now.
990 assertx(!extern_worker::g_in_job);
992 if (auto nativeConsts = Native::getClassConstants(ret->name)) {
993 for (auto const& cnsMap : *nativeConsts) {
994 TypedValueAux tvaux;
995 tvCopy(cnsMap.second, tvaux);
996 tvaux.constModifiers() = {};
997 ret->constants.push_back(
998 php::Const {
999 cnsMap.first,
1000 ret->name,
1001 tvaux,
1003 nullptr,
1004 ConstModifiers::Kind::Value,
1005 php::Const::Invariance::None,
1006 false,
1007 false,
1008 false
1015 ret->enumBaseTy = pce.enumBaseTy();
1017 return ret;
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,
1047 php::Class* clo) {
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.
1055 return;
1058 auto const [ctx, isClass] = find_closure_context(puState, clIt->second);
1059 if (isClass) {
1060 clo->closureContextCls = ctx;
1061 clo->closureDeclFunc = nullptr;
1062 } else {
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{
1074 c.name,
1075 c.val,
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{
1082 m.name,
1083 php::SrcInfo {
1084 {m.line0, m.line1},
1085 m.docComment
1087 m.attrs | AttrPersistent,
1088 m.userAttributes,
1089 m.exports,
1090 m.imports
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);
1104 }();
1106 return std::unique_ptr<php::TypeAlias>(new php::TypeAlias {
1107 php::SrcInfo { te.getLocation() },
1108 te.name(),
1109 te.attrs() | AttrPersistent,
1110 te.value(),
1111 te.kind(),
1112 te.userAttributes(),
1114 nullptr
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());
1122 ParsedUnit ret;
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();
1136 }();
1138 if (RO::EvalAbortBuildOnVerifyError && !ue.check(false)) {
1139 // Record a FatalInfo without a location. This represents a
1140 // verifier failure.
1141 php::FatalInfo fi{
1142 std::nullopt,
1143 FatalOp::Parse,
1144 folly::sformat(
1145 "The unoptimized unit for {} did not pass verification, "
1146 "bailing because Eval.AbortBuildOnVerifyError is set\n",
1147 ue.m_filepath
1150 ret.unit->fatalInfo = std::make_unique<php::FatalInfo>(std::move(fi));
1151 return ret;
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.
1199 ret.classes.erase(
1200 std::remove_if(
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);
1207 always_assert(ctx);
1208 ctx->closures.emplace_back(std::move(c));
1209 return true;
1212 end(ret.classes)
1215 if (debug) {
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);
1233 return ret;
1236 //////////////////////////////////////////////////////////////////////