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