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