Teach hhbbc to optimize away LockObj.
[hiphop-php.git] / hphp / hhbbc / parse.cpp
blob6b2ae8deaa26d2dc63637b6dec99f74cc4d4e995
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 (hasFCallEffects(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 fpushfuncd = [&] (const Bytecode& b) {
378 if (b.FPushFuncD.str2 == s_class_alias.get()) {
379 puState.constPassFuncs.insert(&func);
382 auto has_call_unpack = [&] {
383 auto const fpi = Func::findFPI(&*fe.fpitab.begin(),
384 &*fe.fpitab.end(), pc - ue.bc());
385 auto pc = ue.bc() + fpi->m_fpiEndOff;
386 auto const op = decode_op(pc);
387 if (!isLegacyFCall(op)) return false;
388 return decodeFCallArgs(pc).hasUnpack();
391 #define IMM_BLA(n) auto targets = decode_switch(opPC);
392 #define IMM_SLA(n) auto targets = decode_sswitch(opPC);
393 #define IMM_ILA(n) auto iterTab = decode_itertab();
394 #define IMM_I32LA(n) auto argv = decode_argv32();
395 #define IMM_IVA(n) auto arg##n = decode_iva(pc);
396 #define IMM_I64A(n) auto arg##n = decode<int64_t>(pc);
397 #define IMM_LA(n) auto loc##n = [&] { \
398 LocalId id = decode_iva(pc); \
399 always_assert(id < func.locals.size()); \
400 return id; \
401 }();
402 #define IMM_IA(n) auto iter##n = [&] { \
403 IterId id = decode_iva(pc); \
404 always_assert(id < func.numIters); \
405 return id; \
406 }();
407 #define IMM_CAR(n) auto slot = [&] { \
408 ClsRefSlotId id = decode_iva(pc); \
409 always_assert(id >= 0 && id < func.numClsRefSlots); \
410 return id; \
411 }();
412 #define IMM_CAW(n) auto slot = [&] { \
413 ClsRefSlotId id = decode_iva(pc); \
414 always_assert(id >= 0 && id < func.numClsRefSlots); \
415 return id; \
416 }();
417 #define IMM_DA(n) auto dbl##n = decode<double>(pc);
418 #define IMM_SA(n) auto str##n = ue.lookupLitstr(decode<Id>(pc));
419 #define IMM_RATA(n) auto rat = decodeRAT(ue, pc);
420 #define IMM_AA(n) auto arr##n = ue.lookupArray(decode<Id>(pc));
421 #define IMM_BA(n) assert(next == past); \
422 auto target##n = findBlock( \
423 opPC + decode<Offset>(pc) - ue.bc());
424 #define IMM_OA_IMPL(n) subop##n; decode(pc, subop##n);
425 #define IMM_OA(type) type IMM_OA_IMPL
426 #define IMM_VSA(n) auto keys = decode_stringvec();
427 #define IMM_KA(n) auto mkey = make_mkey(func, decode_member_key(pc, &ue));
428 #define IMM_LAR(n) auto locrange = [&] { \
429 auto const range = decodeLocalRange(pc); \
430 always_assert(range.first + range.count \
431 <= func.locals.size()); \
432 return LocalRange { range.first, range.count }; \
433 }();
434 #define IMM_FCA(n) auto fca = [&] { \
435 auto const fca = decodeFCallArgs(pc); \
436 auto const numBytes = (fca.numArgs + 7) / 8; \
437 auto byRefs = fca.enforceReffiness() \
438 ? std::make_unique<uint8_t[]>(numBytes) \
439 : nullptr; \
440 if (byRefs) { \
441 memcpy(byRefs.get(), fca.byRefs, numBytes); \
443 auto const aeOffset = fca.asyncEagerOffset; \
444 auto const aeTarget = aeOffset != kInvalidOffset \
445 ? findBlock(opPC + aeOffset - ue.bc()) \
446 : NoBlockId; \
447 assertx(aeTarget == NoBlockId || next == past); \
448 return FCallArgs(fca.flags, fca.numArgs, \
449 fca.numRets, std::move(byRefs), \
450 aeTarget); \
451 }();
453 #define IMM_NA
454 #define IMM_ONE(x) IMM_##x(1)
455 #define IMM_TWO(x, y) IMM_##x(1) IMM_##y(2)
456 #define IMM_THREE(x, y, z) IMM_TWO(x, y) IMM_##z(3)
457 #define IMM_FOUR(x, y, z, n) IMM_THREE(x, y, z) IMM_##n(4)
458 #define IMM_FIVE(x, y, z, n, m) IMM_FOUR(x, y, z, n) IMM_##m(5)
460 #define IMM_ARG(which, n) IMM_NAME_##which(n)
461 #define IMM_ARG_NA
462 #define IMM_ARG_ONE(x) IMM_ARG(x, 1)
463 #define IMM_ARG_TWO(x, y) IMM_ARG(x, 1), IMM_ARG(y, 2)
464 #define IMM_ARG_THREE(x, y, z) IMM_ARG(x, 1), IMM_ARG(y, 2), \
465 IMM_ARG(z, 3)
466 #define IMM_ARG_FOUR(x, y, z, l) IMM_ARG(x, 1), IMM_ARG(y, 2), \
467 IMM_ARG(z, 3), IMM_ARG(l, 4)
468 #define IMM_ARG_FIVE(x, y, z, l, m) IMM_ARG(x, 1), IMM_ARG(y, 2), \
469 IMM_ARG(z, 3), IMM_ARG(l, 4), \
470 IMM_ARG(m, 5)
472 #define FLAGS_NF
473 #define FLAGS_TF
474 #define FLAGS_CF
475 #define FLAGS_FF
476 #define FLAGS_PF auto hu = has_call_unpack();
477 #define FLAGS_CF_TF
478 #define FLAGS_CF_FF
480 #define FLAGS_ARG_NF
481 #define FLAGS_ARG_TF
482 #define FLAGS_ARG_CF
483 #define FLAGS_ARG_FF
484 #define FLAGS_ARG_PF ,hu
485 #define FLAGS_ARG_CF_TF
486 #define FLAGS_ARG_CF_FF
488 #define O(opcode, imms, inputs, outputs, flags) \
489 case Op::opcode: \
491 auto b = [&] () -> Bytecode { \
492 IMM_##imms /*these two macros advance the pc as required*/ \
493 FLAGS_##flags \
494 if (isTypeAssert(op)) return bc::Nop {}; \
495 return bc::opcode { IMM_ARG_##imms FLAGS_ARG_##flags }; \
496 }(); \
497 b.srcLoc = srcLocIx; \
498 if (Op::opcode == Op::DefCns) defcns(); \
499 if (Op::opcode == Op::DefCls) defcls(b); \
500 if (Op::opcode == Op::DefClsNop) defclsnop(b); \
501 if (Op::opcode == Op::AliasCls) aliascls(b); \
502 if (Op::opcode == Op::CreateCl) createcl(b); \
503 if (Op::opcode == Op::FPushFuncD) fpushfuncd(b); \
504 blk.hhbcs.push_back(std::move(b)); \
505 assert(pc == next); \
507 break;
509 assert(pc != past);
510 do {
511 auto const opPC = pc;
512 auto const next = pc + instrLen(opPC);
513 assert(next <= past);
515 auto const srcLoc = match<php::SrcLoc>(
516 puState.srcLocInfo,
517 [&] (const SourceLocTable& tab) {
518 SourceLoc sloc;
519 if (getSourceLoc(tab, opPC - ue.bc(), sloc)) {
520 return php::SrcLoc {
521 { static_cast<uint32_t>(sloc.line0),
522 static_cast<uint32_t>(sloc.char0) },
523 { static_cast<uint32_t>(sloc.line1),
524 static_cast<uint32_t>(sloc.char1) }
527 return php::SrcLoc{};
529 [&] (const LineTable& tab) {
530 auto const line = getLineNumber(tab, opPC - ue.bc());
531 if (line != -1) {
532 return php::SrcLoc {
533 { static_cast<uint32_t>(line), 0 },
534 { static_cast<uint32_t>(line), 0 },
537 return php::SrcLoc{};
541 auto const srcLocIx = puState.srcLocs.emplace(
542 srcLoc, puState.srcLocs.size()).first->second;
544 auto const op = decode_op(pc);
545 switch (op) { OPCODES }
547 if (next == past) {
548 if (instrAllowsFallThru(op)) {
549 blk.fallthrough = findBlock(next - ue.bc());
553 pc = next;
554 } while (pc != past);
556 #undef O
558 #undef FLAGS_NF
559 #undef FLAGS_TF
560 #undef FLAGS_CF
561 #undef FLAGS_FF
562 #undef FLAGS_PF
563 #undef FLAGS_CF_TF
564 #undef FLAGS_CF_FF
566 #undef FLAGS_ARG_NF
567 #undef FLAGS_ARG_TF
568 #undef FLAGS_ARG_CF
569 #undef FLAGS_ARG_FF
570 #undef FLAGS_ARG_PF
571 #undef FLAGS_ARG_CF_TF
572 #undef FLAGS_ARG_CF_FF
574 #undef IMM_BLA
575 #undef IMM_SLA
576 #undef IMM_ILA
577 #undef IMM_I32LA
578 #undef IMM_IVA
579 #undef IMM_I64A
580 #undef IMM_LA
581 #undef IMM_IA
582 #undef IMM_CAR
583 #undef IMM_CAW
584 #undef IMM_DA
585 #undef IMM_SA
586 #undef IMM_RATA
587 #undef IMM_AA
588 #undef IMM_BA
589 #undef IMM_OA_IMPL
590 #undef IMM_OA
591 #undef IMM_VSA
592 #undef IMM_LAR
593 #undef IMM_FCA
595 #undef IMM_NA
596 #undef IMM_ONE
597 #undef IMM_TWO
598 #undef IMM_THREE
599 #undef IMM_FOUR
600 #undef IMM_FIVE
602 #undef IMM_ARG
603 #undef IMM_ARG_NA
604 #undef IMM_ARG_ONE
605 #undef IMM_ARG_TWO
606 #undef IMM_ARG_THREE
607 #undef IMM_ARG_FOUR
608 #undef IMM_ARG_FIVE
611 * If a block ends with an unconditional jump, change it to a
612 * fallthrough edge.
614 * If the jmp is the only instruction, convert it to a Nop, to avoid
615 * creating an empty block (we have an invariant that no blocks are
616 * empty).
619 auto make_fallthrough = [&] {
620 blk.fallthrough = blk.hhbcs.back().Jmp.target1;
621 if (blk.hhbcs.size() == 1) {
622 blk.hhbcs.back() = bc_with_loc(blk.hhbcs.back().srcLoc, bc::Nop{});
623 } else {
624 blk.hhbcs.pop_back();
628 switch (blk.hhbcs.back().op) {
629 case Op::Jmp: make_fallthrough(); break;
630 case Op::JmpNS: make_fallthrough(); blk.fallthroughNS = true; break;
631 default: break;
635 template<class FindBlk>
636 void link_entry_points(php::Func& func,
637 const FuncEmitter& fe,
638 FindBlk findBlock) {
639 func.dvEntries.resize(fe.params.size(), NoBlockId);
640 for (size_t i = 0, sz = fe.params.size(); i < sz; ++i) {
641 if (fe.params[i].hasDefaultValue()) {
642 auto const dv = findBlock(fe.params[i].funcletOff);
643 func.params[i].dvEntryPoint = dv;
644 func.dvEntries[i] = dv;
647 func.mainEntry = findBlock(fe.base);
650 void build_cfg(ParseUnitState& puState,
651 php::Func& func,
652 const FuncEmitter& fe) {
653 auto const blockStarts = findBasicBlocks(fe);
655 FTRACE(3, " blocks are at: {}\n",
656 [&]() -> std::string {
657 using namespace folly::gen;
658 return from(blockStarts)
659 | eachTo<std::string>()
660 | unsplit<std::string>(" ");
664 std::map<Offset,std::pair<BlockId, copy_ptr<php::Block>>> blockMap;
665 auto const bc = fe.ue().bc();
667 auto findBlock = [&] (Offset off, bool catchEntry = false) {
668 auto& ent = blockMap[off];
669 if (!ent.second) {
670 auto blk = php::Block{};
671 ent.first = blockMap.size() - 1;
672 blk.exnNodeId = NoExnNodeId;
673 blk.catchEntry = catchEntry;
674 ent.second.emplace(std::move(blk));
675 } else if (catchEntry) {
676 ent.second.mutate()->catchEntry = true;
678 return ent.first;
681 auto exnTreeInfo = build_exn_tree(fe, func, findBlock);
683 hphp_fast_map<BlockId, std::pair<int, int>> predSuccCounts;
685 for (auto it = begin(blockStarts);
686 std::next(it) != end(blockStarts);
687 ++it) {
688 auto const bid = findBlock(*it);
689 auto const block = blockMap[*it].second.mutate();
690 auto const bcStart = bc + *it;
691 auto const bcStop = bc + *std::next(it);
693 if (auto const eh = Func::findEH(fe.ehtab, *it)) {
694 auto it = exnTreeInfo.ehMap.find(eh);
695 assert(it != end(exnTreeInfo.ehMap));
696 block->exnNodeId = it->second;
697 block->throwExit = func.exnNodes[it->second].region.catchEntry;
700 populate_block(puState, fe, func, *block, bcStart, bcStop, findBlock);
701 forEachNonThrowSuccessor(*block, [&] (BlockId blkId) {
702 predSuccCounts[blkId].first++;
703 predSuccCounts[bid].second++;
707 link_entry_points(func, fe, findBlock);
709 func.blocks.resize(blockMap.size());
710 for (auto& kv : blockMap) {
711 auto const blk = kv.second.second.mutate();
712 auto const id = kv.second.first;
713 blk->multiSucc = predSuccCounts[id].second > 1;
714 blk->multiPred = predSuccCounts[id].first > 1;
715 func.blocks[id] = std::move(kv.second.second);
719 void add_frame_variables(php::Func& func, const FuncEmitter& fe) {
720 for (auto& param : fe.params) {
721 func.params.push_back(
722 php::Param {
723 param.defaultValue,
724 NoBlockId,
725 param.typeConstraint,
726 param.userType,
727 param.phpCode,
728 param.userAttributes,
729 param.builtinType,
730 param.inout,
731 param.byRef,
732 param.variadic
737 func.locals.reserve(fe.numLocals());
738 for (LocalId id = 0; id < fe.numLocals(); ++id) {
739 func.locals.push_back({nullptr, id, false});
741 for (auto& kv : fe.localNameMap()) {
742 func.locals[kv.second].name = kv.first;
745 func.numIters = fe.numIterators();
746 func.numClsRefSlots = fe.numClsRefSlots();
749 std::unique_ptr<php::Func> parse_func(ParseUnitState& puState,
750 php::Unit* unit,
751 php::Class* cls,
752 const FuncEmitter& fe) {
753 FTRACE(2, " func: {}\n",
754 fe.name->data() && *fe.name->data() ? fe.name->data() : "pseudomain");
756 auto ret = std::make_unique<php::Func>();
757 ret->idx = puState.nextFuncId.fetch_add(1, std::memory_order_relaxed);
758 ret->name = fe.name;
759 ret->srcInfo = php::SrcInfo { fe.getLocation(),
760 fe.docComment };
761 ret->unit = unit;
762 ret->cls = cls;
764 ret->attrs = static_cast<Attr>(fe.attrs & ~AttrNoOverride);
765 ret->userAttributes = fe.userAttributes;
766 ret->returnUserType = fe.retUserType;
767 ret->retTypeConstraint = fe.retTypeConstraint;
768 ret->originalFilename = fe.originalFilename;
770 ret->top = fe.top;
771 ret->isClosureBody = fe.isClosureBody;
772 ret->isAsync = fe.isAsync;
773 ret->isGenerator = fe.isGenerator;
774 ret->isPairGenerator = fe.isPairGenerator;
775 ret->isMemoizeWrapper = fe.isMemoizeWrapper;
776 ret->isMemoizeWrapperLSB = fe.isMemoizeWrapperLSB;
777 ret->isMemoizeImpl = Func::isMemoizeImplName(fe.name);
778 ret->isReified = fe.userAttributes.find(s___Reified.get()) !=
779 fe.userAttributes.end();
780 ret->isRxDisabled = fe.isRxDisabled;
781 ret->noContextSensitiveAnalysis = fe.userAttributes.find(
782 s___NoContextSensitiveAnalysis.get()) != fe.userAttributes.end();
784 add_frame_variables(*ret, fe);
786 if (!RuntimeOption::ConstantFunctions.empty()) {
787 auto const name = [&] {
788 if (!cls) return fe.name->toCppString();
789 return folly::sformat("{}::{}", cls->name, ret->name);
790 }();
791 auto const it = RuntimeOption::ConstantFunctions.find(name);
792 if (it != RuntimeOption::ConstantFunctions.end()) {
793 ret->locals.resize(fe.params.size());
794 ret->numIters = 0;
795 ret->numClsRefSlots = 0;
796 ret->attrs |= AttrIsFoldable;
798 auto const mainEntry = BlockId{0};
800 auto blk = php::Block{};
801 blk.exnNodeId = NoExnNodeId;
803 blk.hhbcs.push_back(gen_constant(it->second));
804 blk.hhbcs.push_back(bc::RetC {});
805 ret->blocks.emplace_back(std::move(blk));
807 ret->dvEntries.resize(fe.params.size(), NoBlockId);
808 ret->mainEntry = mainEntry;
810 for (size_t i = 0, sz = fe.params.size(); i < sz; ++i) {
811 if (fe.params[i].hasDefaultValue()) {
812 ret->params[i].dvEntryPoint = mainEntry;
813 ret->dvEntries[i] = mainEntry;
816 return ret;
821 * Builtin functions get some extra information. The returnType flag is only
822 * non-folly::none for these, but note that something may be a builtin and
823 * still have a folly::none return type.
825 if (fe.isNative) {
826 auto const f = [&] () -> HPHP::Func* {
827 if (ret->cls) {
828 auto const cls = Unit::lookupClass(ret->cls->name);
829 return cls ? cls->lookupMethod(ret->name) : nullptr;
830 } else {
831 return Unit::lookupBuiltin(ret->name);
833 }();
835 ret->nativeInfo = std::make_unique<php::NativeInfo>();
836 ret->nativeInfo->returnType = fe.hniReturnType;
837 if (f && ret->params.size()) {
838 for (auto i = 0; i < ret->params.size(); i++) {
839 auto& pi = ret->params[i];
840 if (pi.isVariadic || !f->params()[i].hasDefaultValue()) continue;
841 if (pi.defaultValue.m_type == KindOfUninit &&
842 pi.phpCode != nullptr) {
843 auto res = eval_cell_value([&] {
844 auto val = f_constant(StrNR(pi.phpCode));
845 val.setEvalScalar();
846 return *val.asTypedValue();
848 if (!res) {
849 FTRACE(4, "Argument {} to {}: Failed to evaluate {}\n",
850 i, f->fullName(), pi.phpCode);
851 continue;
853 pi.defaultValue = *res;
857 if (!f || !f->nativeFuncPtr() ||
858 (f->userAttributes().count(
859 LowStringPtr(s_attr_Deprecated.get())))) {
860 ret->attrs |= AttrNoFCallBuiltin;
864 build_cfg(puState, *ret, fe);
866 return ret;
869 void parse_methods(ParseUnitState& puState,
870 php::Class* ret,
871 php::Unit* unit,
872 const PreClassEmitter& pce) {
873 std::unique_ptr<php::Func> cinit;
874 for (auto& me : pce.methods()) {
875 auto f = parse_func(puState, unit, ret, *me);
876 if (f->name == s_86cinit.get()) {
877 puState.constPassFuncs.insert(f.get());
878 cinit = std::move(f);
879 } else {
880 if (f->name == s_86pinit.get() ||
881 f->name == s_86sinit.get() ||
882 f->name == s_86linit.get()) {
883 puState.constPassFuncs.insert(f.get());
885 ret->methods.push_back(std::move(f));
888 if (cinit) ret->methods.push_back(std::move(cinit));
891 void add_stringish(php::Class* cls) {
892 // The runtime adds Stringish to any class providing a __toString() function,
893 // so we mirror that here to make sure analysis of interfaces is correct.
894 // All Stringish are also XHPChild, so handle it here as well.
895 if (cls->attrs & AttrInterface && cls->name->isame(s_Stringish.get())) {
896 return;
899 bool hasXHP = false;
900 for (auto& iface : cls->interfaceNames) {
901 if (iface->isame(s_Stringish.get())) return;
902 if (iface->isame(s_XHPChild.get())) { hasXHP = true; }
905 for (auto& func : cls->methods) {
906 if (func->name->isame(s_toString.get())) {
907 FTRACE(2, "Adding Stringish and XHPChild to {}\n", cls->name->data());
908 cls->interfaceNames.push_back(s_Stringish.get());
909 if (!hasXHP && !cls->name->isame(s_XHPChild.get())) {
910 cls->interfaceNames.push_back(s_XHPChild.get());
912 return;
917 std::unique_ptr<php::Record> parse_record(php::Unit* unit,
918 const RecordEmitter& re) {
919 FTRACE(2, " record: {}\n", re.name()->data());
921 auto ret = std::make_unique<php::Record>();
922 ret->unit = unit;
923 ret->srcInfo = php::SrcInfo {re.getLocation(), re.docComment()};
924 ret->name = re.name();
925 ret->attrs = static_cast<Attr>(re.attrs() & ~AttrNoOverride);
926 ret->parentName = re.parentName();
927 ret->id = re.id();
928 ret->userAttributes = re.userAttributes();
930 auto& fieldMap = re.fieldMap();
931 for (size_t idx = 0; idx < fieldMap.size(); ++idx) {
932 auto& field = fieldMap[idx];
933 ret->fields.push_back(
934 php::RecordField {
935 field.name(),
936 field.attrs(),
937 field.userType(),
938 field.docComment(),
939 field.val(),
940 field.typeConstraint(),
941 field.userAttributes()
945 return ret;
948 std::unique_ptr<php::Class> parse_class(ParseUnitState& puState,
949 php::Unit* unit,
950 const PreClassEmitter& pce) {
951 FTRACE(2, " class: {}\n", pce.name()->data());
953 auto ret = std::make_unique<php::Class>();
954 ret->name = pce.name();
955 ret->srcInfo = php::SrcInfo { pce.getLocation(),
956 pce.docComment() };
957 ret->unit = unit;
958 ret->closureContextCls = nullptr;
959 ret->parentName = pce.parentName()->empty() ? nullptr
960 : pce.parentName();
961 ret->attrs = static_cast<Attr>(pce.attrs() & ~AttrNoOverride);
962 ret->hoistability = pce.hoistability();
963 ret->userAttributes = pce.userAttributes();
964 ret->id = pce.id();
965 ret->hasReifiedGenerics = ret->userAttributes.find(s___Reified.get()) !=
966 ret->userAttributes.end();
968 for (auto& iface : pce.interfaces()) {
969 ret->interfaceNames.push_back(iface);
972 copy(ret->usedTraitNames, pce.usedTraits());
973 copy(ret->traitPrecRules, pce.traitPrecRules());
974 copy(ret->traitAliasRules, pce.traitAliasRules());
975 copy(ret->requirements, pce.requirements());
977 parse_methods(puState, ret.get(), unit, pce);
978 add_stringish(ret.get());
980 auto& propMap = pce.propMap();
981 for (size_t idx = 0; idx < propMap.size(); ++idx) {
982 auto& prop = propMap[idx];
983 ret->properties.push_back(
984 php::Prop {
985 prop.name(),
986 prop.attrs(),
987 prop.userAttributes(),
988 prop.docComment(),
989 prop.userType(),
990 prop.typeConstraint(),
991 prop.val()
996 auto& constMap = pce.constMap();
997 for (size_t idx = 0; idx < constMap.size(); ++idx) {
998 auto& cconst = constMap[idx];
999 // Set all constants as NoOverride, we'll clear this while building
1000 // the index
1001 ret->constants.push_back(
1002 php::Const {
1003 cconst.name(),
1004 ret.get(),
1005 cconst.valOption(),
1006 cconst.phpCode(),
1007 cconst.typeConstraint(),
1008 cconst.isTypeconst(),
1009 true // NoOverride
1014 if (ret->attrs & AttrBuiltin) {
1015 if (auto nativeConsts = Native::getClassConstants(ret->name)) {
1016 for (auto const& cnsMap : *nativeConsts) {
1017 TypedValueAux tvaux;
1018 tvCopy(cnsMap.second, tvaux);
1019 tvaux.constModifiers() = {};
1020 ret->constants.push_back(
1021 php::Const {
1022 cnsMap.first,
1023 ret.get(),
1024 tvaux,
1025 staticEmptyString(),
1026 staticEmptyString(),
1027 false
1034 ret->enumBaseTy = pce.enumBaseTy();
1036 return ret;
1039 //////////////////////////////////////////////////////////////////////
1041 void assign_closure_context(const ParseUnitState&, php::Class*);
1043 php::Class*
1044 find_closure_context(const ParseUnitState& puState,
1045 php::Func* createClFunc) {
1046 if (auto const cls = createClFunc->cls) {
1047 if (cls->parentName &&
1048 cls->parentName->isame(s_Closure.get())) {
1049 // We have a closure created by a closure's invoke method, which
1050 // means it should inherit the outer closure's context, so we
1051 // have to know that first.
1052 assign_closure_context(puState, cls);
1053 return cls->closureContextCls;
1055 return cls;
1057 return nullptr;
1060 void assign_closure_context(const ParseUnitState& puState,
1061 php::Class* clo) {
1062 if (clo->closureContextCls) return;
1064 auto clIt = puState.createClMap.find(clo->id);
1065 if (clIt == end(puState.createClMap)) {
1066 // Unused closure class. Technically not prohibited by the spec.
1067 return;
1071 * Any route to the closure context must yield the same class, or
1072 * things downstream won't understand. We try every route and
1073 * assert they are all the same here.
1075 * See bytecode.specification for CreateCl for the relevant
1076 * invariants.
1078 always_assert(!clIt->second.empty());
1079 auto it = begin(clIt->second);
1080 auto const representative = find_closure_context(puState, *it);
1081 if (debug) {
1082 for (++it; it != end(clIt->second); ++it) {
1083 assert(find_closure_context(puState, *it) == representative);
1086 clo->closureContextCls = representative;
1089 void find_additional_metadata(const ParseUnitState& puState,
1090 php::Unit* unit) {
1091 for (auto& c : unit->classes) {
1092 if (!c->parentName || !c->parentName->isame(s_Closure.get())) {
1093 continue;
1095 assign_closure_context(puState, c.get());
1099 //////////////////////////////////////////////////////////////////////
1103 std::unique_ptr<php::Unit> parse_unit(php::Program& prog,
1104 std::unique_ptr<UnitEmitter> uep) {
1105 Trace::Bump bumper{Trace::hhbbc_parse, kSystemLibBump, uep->isASystemLib()};
1106 FTRACE(2, "parse_unit {}\n", uep->m_filepath->data());
1108 if (RuntimeOption::EvalAbortBuildOnVerifyError && !uep->check(false)) {
1109 fprintf(
1110 stderr,
1111 "The unoptimized unit for %s did not pass verification, "
1112 "bailing because Eval.AbortBuildOnVerifyError is set\n",
1113 uep->m_filepath->data()
1115 _Exit(1);
1118 auto const& ue = *uep;
1120 auto ret = std::make_unique<php::Unit>();
1121 ret->sha1 = ue.sha1();
1122 ret->filename = ue.m_filepath;
1123 ret->isHHFile = ue.m_isHHFile;
1124 ret->metaData = ue.m_metaData;
1125 ret->fileAttributes = ue.m_fileAttributes;
1127 ParseUnitState puState{ prog.nextFuncId };
1128 if (ue.hasSourceLocInfo()) {
1129 puState.srcLocInfo = ue.createSourceLocTable();
1130 } else {
1131 puState.srcLocInfo = ue.lineTable();
1133 puState.defClsMap.resize(ue.numPreClasses(), nullptr);
1135 for (size_t i = 0; i < ue.numPreClasses(); ++i) {
1136 auto cls = parse_class(puState, ret.get(), *ue.pce(i));
1137 ret->classes.push_back(std::move(cls));
1140 for (size_t i = 0; i < ue.numRecords(); ++i) {
1141 auto rec = parse_record(ret.get(), *ue.re(i));
1142 ret->records.push_back(std::move(rec));
1145 for (auto& fe : ue.fevec()) {
1146 auto func = parse_func(puState, ret.get(), nullptr, *fe);
1147 assert(!fe->pce());
1148 if (fe->isPseudoMain()) {
1149 ret->pseudomain = std::move(func);
1150 } else {
1151 ret->funcs.push_back(std::move(func));
1155 ret->srcLocs.resize(puState.srcLocs.size());
1156 for (auto& srcInfo : puState.srcLocs) {
1157 ret->srcLocs[srcInfo.second] = srcInfo.first;
1160 for (auto& ta : ue.typeAliases()) {
1161 ret->typeAliases.push_back(
1162 std::make_unique<php::TypeAlias>(ta)
1166 ret->classAliases = std::move(puState.classAliases);
1168 find_additional_metadata(puState, ret.get());
1170 for (auto const item : puState.constPassFuncs) {
1171 record_const_init(prog, item);
1174 assert(check(*ret));
1175 return ret;
1178 //////////////////////////////////////////////////////////////////////