Add a capi conversion no-op method to thrift python structs/unions
[hiphop-php.git] / hphp / hhbbc / emit.cpp
blob4476fa55c8672600a085579866be1fe0997b6599
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/emit.h"
18 #include <vector>
19 #include <algorithm>
20 #include <iterator>
21 #include <map>
22 #include <memory>
23 #include <type_traits>
25 #include <folly/gen/Base.h>
26 #include <folly/Conv.h>
27 #include <folly/Memory.h>
29 #include "hphp/hhbbc/cfg.h"
30 #include "hphp/hhbbc/class-util.h"
31 #include "hphp/hhbbc/context.h"
32 #include "hphp/hhbbc/func-util.h"
33 #include "hphp/hhbbc/index.h"
34 #include "hphp/hhbbc/options.h"
35 #include "hphp/hhbbc/representation.h"
36 #include "hphp/hhbbc/type-structure.h"
37 #include "hphp/hhbbc/unit-util.h"
39 #include "hphp/runtime/base/array-data.h"
40 #include "hphp/runtime/base/repo-auth-type.h"
41 #include "hphp/runtime/base/tv-comparisons.h"
43 #include "hphp/runtime/ext/extension-registry.h"
45 #include "hphp/runtime/vm/bytecode.h"
46 #include "hphp/runtime/vm/func-emitter.h"
47 #include "hphp/runtime/vm/native.h"
48 #include "hphp/runtime/vm/preclass-emitter.h"
49 #include "hphp/runtime/vm/type-alias-emitter.h"
50 #include "hphp/runtime/vm/unit-emitter.h"
52 #include "hphp/util/configs/eval.h"
54 namespace HPHP::HHBBC {
56 TRACE_SET_MOD(hhbbc_emit);
58 namespace {
60 //////////////////////////////////////////////////////////////////////
62 const StaticString s_invoke("__invoke");
64 //////////////////////////////////////////////////////////////////////
66 struct EmitUnitState {
67 explicit EmitUnitState(const Index& index, const php::Unit* unit) :
68 index(index), unit(unit) {}
71 * Access to the Index for this program.
73 const Index& index;
76 * Access to the unit we're emitting
78 const php::Unit* unit;
81 * While emitting bytecode, we keep track of the classes and funcs
82 * we emit.
84 std::vector<std::pair<php::Class*, PreClassEmitter*>> pces;
87 * Whether a closure in a CreateCl opcode has been seen before
88 * (CreateCls within the same func are allowed to refer to the same
89 * closure, so we must avoid creating duplicate PreClassEmitters for
90 * them).
92 hphp_fast_set<const php::Class*> seenClosures;
96 * Some bytecodes need to be mutated before being emitted. Pass those
97 * bytecodes by value to their respective emit_op functions.
99 template<typename T>
100 struct OpInfoHelper {
101 static constexpr bool by_value = T::op == Op::CreateCl;
102 using type = typename std::conditional<by_value, T, const T&>::type;
105 template<typename T>
106 using OpInfo = typename OpInfoHelper<T>::type;
109 * Helper to conditionally call fun on data provided data is of the
110 * given type.
112 template<Op op, typename F, typename Bc>
113 std::enable_if_t<std::remove_reference_t<Bc>::op == op>
114 caller(F&& fun, Bc&& data) { fun(std::forward<Bc>(data)); }
116 template<Op op, typename F, typename Bc>
117 std::enable_if_t<std::remove_reference_t<Bc>::op != op>
118 caller(F&&, Bc&&) {}
120 void recordClass(EmitUnitState& euState,
121 UnitEmitter& ue,
122 php::Class& cls) {
123 euState.pces.emplace_back(
124 &cls,
125 ue.newPreClassEmitter(cls.name->toCppString())
129 //////////////////////////////////////////////////////////////////////
131 php::SrcLoc srcLoc(const php::Unit& unit, const php::Func& func, int32_t ix) {
132 if (ix < 0) return php::SrcLoc{};
133 assertx(ix < unit.srcLocs.size());
134 return unit.srcLocs[ix];
138 * Order the blocks for bytecode emission.
140 * Rules about block order:
142 * - Each DV funclet must have all of its blocks contiguous, with the
143 * entry block first.
145 * - Main entry point must be the first block.
147 * It is not a requirement, but we attempt to locate all the DV entry
148 * points after the rest of the primary function body. The normal
149 * case for DV initializers is that each one falls through to the
150 * next, with the block jumping back to the main entry point.
152 std::vector<BlockId> order_blocks(const php::WideFunc& f) {
153 auto sorted = rpoSortFromMain(f);
155 // Get the DV blocks, without the rest of the primary function body,
156 // and then add them to the end of sorted.
157 auto const dvBlocks = [&] {
158 auto withDVs = rpoSortAddDVs(f);
159 withDVs.erase(
160 std::find(begin(withDVs), end(withDVs), sorted.front()),
161 end(withDVs)
163 return withDVs;
164 }();
165 sorted.insert(end(sorted), begin(dvBlocks), end(dvBlocks));
167 FTRACE(2, " block order:{}\n",
168 [&] {
169 std::string ret;
170 for (auto const bid : sorted) {
171 ret += " ";
172 ret += folly::to<std::string>(bid);
174 return ret;
177 return sorted;
180 // While emitting bytecode, we learn about some metadata that will
181 // need to be registered in the FuncEmitter.
182 struct EmitBcInfo {
183 struct JmpFixup { Offset instrOff; Offset jmpImmedOff; };
185 struct BlockInfo {
186 BlockInfo()
187 : offset(kInvalidOffset)
188 , past(kInvalidOffset)
189 , regionsToPop(0)
192 // The offset of the block, if we've already emitted it.
193 // Otherwise kInvalidOffset.
194 Offset offset;
196 // The offset past the end of this block.
197 Offset past;
199 // How many catch regions the jump at the end of this block is leaving.
200 // 0 if there is no jump or if the jump is to the same catch region or a
201 // child
202 int regionsToPop;
204 // When we emit a forward jump to a block we haven't seen yet, we
205 // write down where the jump was so we can fix it up when we get
206 // to that block.
207 std::vector<JmpFixup> forwardJumps;
209 // When we see a forward jump to a block, we record the stack
210 // depth at the jump site here. This is needed to track
211 // currentStackDepth correctly (and we also assert all the jumps
212 // have the same depth).
213 Optional<uint32_t> expectedStackDepth;
216 std::vector<BlockId> blockOrder;
217 uint32_t maxStackDepth;
218 bool containsCalls;
219 std::vector<BlockInfo> blockInfo;
222 using ExnNodePtr = php::ExnNode*;
224 bool handleEquivalent(const php::Func& func, ExnNodeId eh1, ExnNodeId eh2) {
225 auto entry = [&] (ExnNodeId eid) {
226 return func.exnNodes[eid].region.catchEntry;
229 while (eh1 != eh2) {
230 assertx(eh1 != NoExnNodeId &&
231 eh2 != NoExnNodeId &&
232 func.exnNodes[eh1].depth == func.exnNodes[eh2].depth);
233 if (entry(eh1) != entry(eh2)) return false;
234 eh1 = func.exnNodes[eh1].parent;
235 eh2 = func.exnNodes[eh2].parent;
238 return true;
241 // The common parent P of eh1 and eh2 is the deepest region such that
242 // eh1 and eh2 are both handle-equivalent to P or a child of P
243 ExnNodeId commonParent(const php::Func& func, ExnNodeId eh1, ExnNodeId eh2) {
244 if (eh1 == NoExnNodeId || eh2 == NoExnNodeId) return NoExnNodeId;
245 while (func.exnNodes[eh1].depth > func.exnNodes[eh2].depth) {
246 eh1 = func.exnNodes[eh1].parent;
248 while (func.exnNodes[eh2].depth > func.exnNodes[eh1].depth) {
249 eh2 = func.exnNodes[eh2].parent;
251 while (!handleEquivalent(func, eh1, eh2)) {
252 eh1 = func.exnNodes[eh1].parent;
253 eh2 = func.exnNodes[eh2].parent;
255 return eh1;
258 const StaticString
259 s_hhbbc_fail_verification("__hhvm_intrinsics\\hhbbc_fail_verification");
261 EmitBcInfo emit_bytecode(EmitUnitState& euState, UnitEmitter& ue, FuncEmitter& fe,
262 const php::WideFunc& func) {
263 EmitBcInfo ret = {};
264 auto& blockInfo = ret.blockInfo;
265 blockInfo.resize(func.blocks().size());
267 // Track the stack depth while emitting to determine maxStackDepth.
268 int32_t currentStackDepth { 0 };
270 // Temporary buffer for vector immediates. (Hoisted so it's not
271 // allocated in the loop.)
272 std::vector<uint8_t> immVec;
274 // Offset of the last emitted bytecode.
275 Offset lastOff { 0 };
277 auto const unit = euState.index.lookup_func_original_unit(*func);
279 SCOPE_ASSERT_DETAIL("emit") {
280 std::string ret;
281 for (auto bid : func.blockRange()) {
282 auto const block = show(*func, *func.blocks()[bid]);
283 folly::format(&ret, "block #{}\n{}", bid, block);
286 return ret;
289 auto const map_local = [&] (LocalId id) {
290 if (id >= func->locals.size()) return id;
291 auto const loc = func->locals[id];
292 assertx(!loc.killed);
293 assertx(loc.id <= id);
294 id = loc.id;
295 return id;
298 auto const map_local_name = [&] (NamedLocal nl) {
299 nl.id = map_local(nl.id);
300 if (nl.name == kInvalidLocalName) return nl;
301 if (nl.name >= func->locals.size()) return nl;
302 auto const loc = func->locals[nl.name];
303 if (!loc.name) {
304 nl.name = kInvalidLocalName;
305 return nl;
307 assertx(!loc.unusedName);
308 nl.name = loc.nameId;
309 return nl;
312 auto const set_expected_depth = [&] (BlockId block) {
313 auto& info = blockInfo[block];
315 if (info.expectedStackDepth) {
316 assertx(*info.expectedStackDepth == currentStackDepth);
317 } else {
318 info.expectedStackDepth = currentStackDepth;
322 auto const make_member_key = [&] (MKey mkey) {
323 switch (mkey.mcode) {
324 case MEC: case MPC:
325 return MemberKey{mkey.mcode, static_cast<int32_t>(mkey.idx), mkey.rop};
326 case MEL: case MPL:
327 return MemberKey{mkey.mcode, map_local_name(mkey.local), mkey.rop};
328 case MET: case MPT: case MQT:
329 return MemberKey{mkey.mcode, mkey.litstr, mkey.rop};
330 case MEI:
331 return MemberKey{mkey.mcode, mkey.int64, mkey.rop};
332 case MW:
333 return MemberKey{};
335 not_reached();
338 auto const emit_inst = [&] (const Bytecode& inst) {
339 auto const startOffset = fe.bcPos();
340 lastOff = startOffset;
342 FTRACE(4, " emit: {} -- {} @ {}\n", currentStackDepth, show(*func, inst),
343 show(srcLoc(*unit, *func, inst.srcLoc)));
345 auto const emit_vsa = [&] (const CompactVector<LSString>& keys) {
346 auto n = keys.size();
347 fe.emitIVA(n);
348 for (size_t i = 0; i < n; ++i) {
349 fe.emitInt32(ue.mergeLitstr(keys[i]));
353 auto const emit_branch = [&] (BlockId id) {
354 auto& info = blockInfo[id];
355 if (info.offset != kInvalidOffset) {
356 fe.emitInt32(info.offset - startOffset);
357 } else {
358 info.forwardJumps.push_back({ startOffset, fe.bcPos() });
359 fe.emitInt32(0);
363 auto const emit_switch = [&] (const SwitchTab& targets) {
364 fe.emitIVA(targets.size());
365 for (auto t : targets) {
366 set_expected_depth(t);
367 emit_branch(t);
371 auto const emit_sswitch = [&] (const SSwitchTab& targets) {
372 fe.emitIVA(targets.size());
373 for (size_t i = 0; i < targets.size() - 1; ++i) {
374 set_expected_depth(targets[i].second);
375 fe.emitInt32(ue.mergeLitstr(targets[i].first));
376 emit_branch(targets[i].second);
378 fe.emitInt32(-1);
379 set_expected_depth(targets[targets.size() - 1].second);
380 emit_branch(targets[targets.size() - 1].second);
383 auto const emit_srcloc = [&] {
384 auto const sl = srcLoc(*unit, *func, inst.srcLoc);
385 auto const loc = sl.isValid() ?
386 Location::Range(sl.start.line, sl.start.col, sl.past.line, sl.past.col)
387 : Location::Range(-1,-1,-1,-1);
388 fe.recordSourceLocation(loc, startOffset);
391 auto const pop = [&] (int32_t n) {
392 currentStackDepth -= n;
393 assertx(currentStackDepth >= 0);
395 auto const push = [&] (int32_t n) {
396 currentStackDepth += n;
397 ret.maxStackDepth =
398 std::max<uint32_t>(ret.maxStackDepth, currentStackDepth);
401 auto const ret_assert = [&] { assertx(currentStackDepth == inst.numPop()); };
403 auto const createcl = [&] (auto const& data) {
404 auto const cls = euState.index.lookup_class(data.str2);
405 always_assert_flog(
406 cls,
407 "A closure class ({}) failed to resolve",
408 data.str2
410 assertx(cls->unit == euState.unit->filename);
411 assertx(is_closure(*cls));
412 // Skip closures we've already recorded
413 if (!euState.seenClosures.emplace(cls).second) return;
414 recordClass(euState, ue, const_cast<php::Class&>(*cls));
417 auto const emit_lar = [&](const LocalRange& range) {
418 encodeLocalRange(fe, HPHP::LocalRange{
419 map_local(range.first), range.count
423 #define IMM_BLA(n) emit_switch(data.targets);
424 #define IMM_SLA(n) emit_sswitch(data.targets);
425 #define IMM_IVA(n) fe.emitIVA(data.arg##n);
426 #define IMM_I64A(n) fe.emitInt64(data.arg##n);
427 #define IMM_LA(n) fe.emitIVA(map_local(data.loc##n));
428 #define IMM_NLA(n) fe.emitNamedLocal(map_local_name(data.nloc##n));
429 #define IMM_ILA(n) fe.emitIVA(map_local(data.loc##n));
430 #define IMM_IA(n) fe.emitIVA(data.iter##n);
431 #define IMM_DA(n) fe.emitDouble(data.dbl##n);
432 #define IMM_SA(n) fe.emitInt32(ue.mergeLitstr(data.str##n));
433 #define IMM_RATA(n) encodeRAT(fe, data.rat);
434 #define IMM_AA(n) fe.emitInt32(ue.mergeArray(data.arr##n));
435 #define IMM_OA_IMPL(n) fe.emitByte(static_cast<uint8_t>(data.subop##n));
436 #define IMM_OA(type) IMM_OA_IMPL
437 #define IMM_BA(n) targets[numTargets++] = data.target##n; \
438 emit_branch(data.target##n);
439 #define IMM_VSA(n) emit_vsa(data.keys);
440 #define IMM_KA(n) encode_member_key(make_member_key(data.mkey), fe);
441 #define IMM_LAR(n) emit_lar(data.locrange);
442 #define IMM_ITA(n) encodeIterArgs(fe, data.ita);
443 #define IMM_FCA(n) encodeFCallArgs( \
444 fe, data.fca.base(), \
445 data.fca.enforceInOut(), \
446 [&] { \
447 data.fca.applyIO( \
448 [&] (int numBytes, const uint8_t* inOut) { \
449 encodeFCallArgsBoolVec(fe, numBytes, inOut); \
451 ); \
452 }, \
453 data.fca.enforceReadonly(), \
454 [&] { \
455 data.fca.applyReadonly( \
456 [&] (int numBytes, const uint8_t* readonly) { \
457 encodeFCallArgsBoolVec(fe, numBytes, readonly); \
459 ); \
460 }, \
461 data.fca.asyncEagerTarget() != NoBlockId, \
462 [&] { \
463 set_expected_depth(data.fca.asyncEagerTarget()); \
464 emit_branch(data.fca.asyncEagerTarget()); \
465 }, \
466 data.fca.context() != nullptr, \
467 [&] { \
468 fe.emitInt32(ue.mergeLitstr(data.fca.context()));\
469 }); \
470 if (!data.fca.hasUnpack()) ret.containsCalls = true;
472 #define IMM_NA
473 #define IMM_ONE(x) IMM_##x(1)
474 #define IMM_TWO(x, y) IMM_##x(1); IMM_##y(2);
475 #define IMM_THREE(x, y, z) IMM_TWO(x, y); IMM_##z(3);
476 #define IMM_FOUR(x, y, z, n) IMM_THREE(x, y, z); IMM_##n(4);
477 #define IMM_FIVE(x, y, z, n, m) IMM_FOUR(x, y, z, n); IMM_##m(5);
478 #define IMM_SIX(x, y, z, n, m, o) IMM_FIVE(x, y, z, n, m); IMM_##o(6);
480 #define POP_NOV
481 #define POP_ONE(x) pop(1);
482 #define POP_TWO(x, y) pop(2);
483 #define POP_THREE(x, y, z) pop(3);
485 #define POP_MFINAL pop(data.arg1);
486 #define POP_C_MFINAL(n) pop(n); pop(data.arg1);
487 #define POP_CMANY pop(data.arg##1);
488 #define POP_SMANY pop(data.keys.size());
489 #define POP_CUMANY pop(data.arg##1);
490 #define POP_FCALL(nin, nobj) \
491 pop(nin + data.fca.numInputs() + 1 + data.fca.numRets());
493 #define PUSH_NOV
494 #define PUSH_ONE(x) push(1);
495 #define PUSH_TWO(x, y) push(2);
496 #define PUSH_THREE(x, y, z) push(3);
497 #define PUSH_CMANY push(data.arg1);
498 #define PUSH_FCALL push(data.fca.numRets());
500 #define O(opcode, imms, inputs, outputs, flags) \
501 case Op::opcode: { \
502 if (Op::opcode == Op::Nop) break; \
503 OpInfo<bc::opcode> data{inst.opcode}; \
504 if (Cfg::Eval::EnableIntrinsicsExtension) { \
505 if (Op::opcode == Op::FCallFuncD && \
506 inst.FCallFuncD.str2 == s_hhbbc_fail_verification.get()) {\
507 fe.emitOp(Op::CheckProp); \
508 fe.emitInt32( \
509 ue.mergeLitstr(inst.FCallFuncD.str2)); \
510 fe.emitOp(Op::PopC); \
511 ret.maxStackDepth++; \
514 caller<Op::CreateCl>(createcl, data); \
516 if (isRet(Op::opcode)) ret_assert(); \
517 fe.emitOp(Op::opcode); \
518 POP_##inputs \
520 size_t numTargets = 0; \
521 std::array<BlockId, kMaxHhbcImms> targets; \
523 if (Op::opcode == Op::MemoGet) { \
524 IMM_##imms \
525 assertx(numTargets == 1); \
526 set_expected_depth(targets[0]); \
527 PUSH_##outputs \
528 } else if (Op::opcode == Op::MemoGetEager) { \
529 IMM_##imms \
530 assertx(numTargets == 2); \
531 set_expected_depth(targets[0]); \
532 PUSH_##outputs \
533 set_expected_depth(targets[1]); \
534 } else { \
535 PUSH_##outputs \
536 IMM_##imms \
537 for (size_t i = 0; i < numTargets; ++i) { \
538 set_expected_depth(targets[i]); \
542 if (flags & TF) currentStackDepth = 0; \
543 emit_srcloc(); \
544 break; \
546 switch (inst.op) { OPCODES }
547 #undef O
549 #undef IMM_MA
550 #undef IMM_BLA
551 #undef IMM_SLA
552 #undef IMM_IVA
553 #undef IMM_I64A
554 #undef IMM_LA
555 #undef IMM_NLA
556 #undef IMM_ILA
557 #undef IMM_IA
558 #undef IMM_DA
559 #undef IMM_SA
560 #undef IMM_RATA
561 #undef IMM_AA
562 #undef IMM_BA
563 #undef IMM_OA_IMPL
564 #undef IMM_OA
565 #undef IMM_VSA
566 #undef IMM_KA
567 #undef IMM_LAR
568 #undef IMM_FCA
570 #undef IMM_NA
571 #undef IMM_ONE
572 #undef IMM_TWO
573 #undef IMM_THREE
574 #undef IMM_FOUR
575 #undef IMM_FIVE
576 #undef IMM_SIX
578 #undef POP_NOV
579 #undef POP_ONE
580 #undef POP_TWO
581 #undef POP_THREE
583 #undef POP_CMANY
584 #undef POP_SMANY
585 #undef POP_CUMANY
586 #undef POP_FCALL
587 #undef POP_MFINAL
588 #undef POP_C_MFINAL
590 #undef PUSH_NOV
591 #undef PUSH_ONE
592 #undef PUSH_TWO
593 #undef PUSH_THREE
594 #undef PUSH_CMANY
595 #undef PUSH_FCALL
599 ret.blockOrder = order_blocks(func);
600 auto blockIt = begin(ret.blockOrder);
601 auto const endBlockIt = end(ret.blockOrder);
602 for (; blockIt != endBlockIt; ++blockIt) {
603 auto bid = *blockIt;
604 auto& info = blockInfo[bid];
605 auto const b = func.blocks()[bid].get();
607 info.offset = fe.bcPos();
608 FTRACE(2, " block {}: {}\n", bid, info.offset);
610 for (auto& fixup : info.forwardJumps) {
611 fe.emitInt32(info.offset - fixup.instrOff, fixup.jmpImmedOff);
614 if (!info.expectedStackDepth) {
615 // unreachable, or entry block
616 info.expectedStackDepth = b->catchEntry ? 1 : 0;
619 currentStackDepth = *info.expectedStackDepth;
621 auto fallthrough = b->fallthrough;
622 auto end = b->hhbcs.end();
623 auto flip = false;
625 if (!is_single_nop(*b)) {
626 // If the block ends with JmpZ or JmpNZ to the next block, flip
627 // the condition to make the fallthrough the next block
628 if (b->hhbcs.back().op == Op::JmpZ ||
629 b->hhbcs.back().op == Op::JmpNZ) {
630 auto const& bc = b->hhbcs.back();
631 auto const target =
632 bc.op == Op::JmpNZ ? bc.JmpNZ.target1 : bc.JmpZ.target1;
633 if (std::next(blockIt) != endBlockIt && blockIt[1] == target) {
634 fallthrough = target;
635 --end;
636 flip = true;
640 for (auto iit = b->hhbcs.begin(); iit != end; ++iit) emit_inst(*iit);
641 if (flip) {
642 if (end->op == Op::JmpNZ) {
643 emit_inst(bc_with_loc(end->srcLoc, bc::JmpZ { b->fallthrough }));
644 } else {
645 emit_inst(bc_with_loc(end->srcLoc, bc::JmpNZ { b->fallthrough }));
650 info.past = fe.bcPos();
652 if (fallthrough != NoBlockId) {
653 set_expected_depth(fallthrough);
654 if (std::next(blockIt) == endBlockIt ||
655 blockIt[1] != fallthrough) {
656 emit_inst(bc::Jmp { fallthrough });
658 auto const nextExnId = func.blocks()[fallthrough]->exnNodeId;
659 auto const parent = commonParent(*func, nextExnId, b->exnNodeId);
661 auto depth = [&] (ExnNodeId eid) {
662 return eid == NoExnNodeId ? 0 : func->exnNodes[eid].depth;
664 // If we are in an exn region we pop from the current region to the
665 // common parent. If the common parent is null, we pop all regions
666 info.regionsToPop = depth(b->exnNodeId) - depth(parent);
667 assertx(info.regionsToPop >= 0);
668 FTRACE(4, " popped catch regions: {}\n", info.regionsToPop);
672 if (b->throwExit != NoBlockId) {
673 FTRACE(4, " throw: {}\n", b->throwExit);
675 if (fallthrough != NoBlockId) {
676 FTRACE(4, " fallthrough: {}\n", fallthrough);
678 FTRACE(2, " block {} end: {}\n", bid, info.past);
681 return ret;
684 void emit_locals_and_params(FuncEmitter& fe, const php::Func& func,
685 const EmitBcInfo& info) {
686 Id id = 0;
687 for (auto const& loc : func.locals) {
688 if (loc.id < func.params.size()) {
689 assertx(loc.name);
690 assertx(!loc.killed);
691 assertx(!loc.unusedName);
692 auto& param = func.params[id];
693 Func::ParamInfo pinfo;
694 pinfo.defaultValue = param.defaultValue;
695 pinfo.typeConstraints = param.typeConstraints;
696 pinfo.userType = param.userTypeConstraint;
697 pinfo.phpCode = param.phpCode;
698 pinfo.userAttributes = param.userAttributes;
699 if (param.inout) pinfo.setFlag(Func::ParamInfo::Flags::InOut);
700 if (param.readonly) pinfo.setFlag(Func::ParamInfo::Flags::Readonly);
701 if (param.isVariadic) pinfo.setFlag(Func::ParamInfo::Flags::Variadic);
702 if (param.isOptional) pinfo.setFlag(Func::ParamInfo::Flags::Optional);
703 fe.appendParam(func.locals[id].name, pinfo);
704 auto const dv = param.dvEntryPoint;
705 if (dv != NoBlockId) {
706 fe.params[id].funcletOff = info.blockInfo[dv].offset;
708 ++id;
709 } else {
710 if (loc.killed) continue;
711 if (loc.name && !loc.unusedName && loc.name) {
712 fe.allocVarId(loc.name);
713 assertx(fe.lookupVarId(loc.name) == id);
714 assertx(loc.id == id);
715 ++id;
716 } else {
717 fe.allocUnnamedLocal();
718 ++id;
722 for (auto const& loc : func.locals) {
723 if (loc.killed && !loc.unusedName && loc.name) {
724 fe.allocVarId(loc.name, true);
728 if (debug) {
729 for (auto const& loc : func.locals) {
730 if (!loc.killed) {
731 assertx(loc.id < fe.numLocals());
733 if (!loc.unusedName && loc.name) {
734 assertx(loc.nameId < fe.numNamedLocals());
739 assertx(fe.numLocals() == id);
740 fe.setNumIterators(func.numIters);
743 struct EHRegion {
744 const php::ExnNode* node;
745 EHRegion* parent;
746 Offset start;
747 Offset past;
750 template<class BlockInfo, class ParentIndexMap>
751 void emit_eh_region(FuncEmitter& fe,
752 const EHRegion* region,
753 const BlockInfo& blockInfo,
754 ParentIndexMap& parentIndexMap) {
755 FTRACE(2, " func {}: ExnNode {}\n", fe.name, region->node->idx);
757 auto const unreachable = [&] (const php::ExnNode& node) {
758 return blockInfo[node.region.catchEntry].offset == kInvalidOffset;
761 // A region on a single empty block.
762 if (region->start == region->past) {
763 FTRACE(2, " Skipping\n");
764 return;
765 } else if (unreachable(*region->node)) {
766 FTRACE(2, " Unreachable\n");
767 return;
770 FTRACE(2, " Process @ {}-{}\n", region->start, region->past);
772 auto& eh = fe.addEHEnt();
773 eh.m_base = region->start;
774 eh.m_past = region->past;
775 assertx(eh.m_past >= eh.m_base);
776 assertx(eh.m_base != kInvalidOffset && eh.m_past != kInvalidOffset);
778 // An unreachable parent won't be emitted (and thus its offset won't be set),
779 // so find the closest reachable one.
780 auto parent = region->parent;
781 while (parent && unreachable(*parent->node)) parent = parent->parent;
782 if (parent) {
783 auto parentIt = parentIndexMap.find(parent);
784 assertx(parentIt != end(parentIndexMap));
785 eh.m_parentIndex = parentIt->second;
786 } else {
787 eh.m_parentIndex = -1;
789 parentIndexMap[region] = fe.ehtab.size() - 1;
791 auto const& cr = region->node->region;
792 eh.m_handler = blockInfo[cr.catchEntry].offset;
793 eh.m_end = kInvalidOffset;
794 eh.m_iterId = cr.iterId;
796 assertx(eh.m_handler != kInvalidOffset);
799 void exn_path(const php::Func& func,
800 std::vector<const php::ExnNode*>& ret,
801 ExnNodeId id) {
802 if (id == NoExnNodeId) return;
803 auto const& n = func.exnNodes[id];
804 exn_path(func, ret, n.parent);
805 ret.push_back(&n);
808 // Return the count of shared elements in the front of two forward
809 // ranges.
810 template<class ForwardRange1, class ForwardRange2>
811 size_t shared_prefix(ForwardRange1& r1, ForwardRange2& r2) {
812 auto r1it = begin(r1);
813 auto r2it = begin(r2);
814 auto const r1end = end(r1);
815 auto const r2end = end(r2);
816 auto ret = size_t{0};
817 while (r1it != r1end && r2it != r2end && *r1it == *r2it) {
818 ++ret; ++r1it; ++r2it;
820 return ret;
824 * Traverse the actual block layout, and find out the intervals for
825 * each exception region in the tree.
827 * The basic idea here is that we haven't constrained block layout
828 * based on the exception tree, but adjacent blocks are still
829 * reasonably likely to have the same ExnNode. Try to coalesce the EH
830 * regions we create for in those cases.
832 void emit_ehent_tree(FuncEmitter& fe, const php::WideFunc& func,
833 const EmitBcInfo& info) {
834 hphp_fast_map<
835 const php::ExnNode*,
836 std::vector<std::unique_ptr<EHRegion>>
837 > exnMap;
840 * While walking over the blocks in layout order, we track the set
841 * of "active" exnNodes. This are a list of exnNodes that inherit
842 * from each other. When a new active node is pushed, begin an
843 * EHEnt, and when it's popped, it's done.
845 std::vector<const php::ExnNode*> activeList;
847 auto pop_active = [&] (Offset past) {
848 auto p = activeList.back();
849 activeList.pop_back();
850 exnMap[p].back()->past = past;
853 auto push_active = [&] (const php::ExnNode* p, Offset start) {
854 auto const parent = activeList.empty()
855 ? nullptr
856 : exnMap[activeList.back()].back().get();
857 exnMap[p].push_back(
858 std::make_unique<EHRegion>(
859 EHRegion { p, parent, start, kInvalidOffset }
862 activeList.push_back(p);
866 * Walk over the blocks, and compare the new block's exnNode path to
867 * the active one. Find the least common ancestor of the two paths,
868 * then modify the active list by popping and then pushing nodes to
869 * set it to the new block's path.
871 for (auto const bid : info.blockOrder) {
872 auto const b = func.blocks()[bid].get();
873 auto const offset = info.blockInfo[bid].offset;
875 if (b->exnNodeId == NoExnNodeId) {
876 while (!activeList.empty()) pop_active(offset);
877 continue;
880 std::vector<const php::ExnNode*> current;
881 exn_path(*func, current, b->exnNodeId);
883 auto const prefix = shared_prefix(current, activeList);
884 for (size_t i = prefix, sz = activeList.size(); i < sz; ++i) {
885 pop_active(offset);
887 for (size_t i = prefix, sz = current.size(); i < sz; ++i) {
888 push_active(current[i], offset);
891 for (int i = 0; i < info.blockInfo[bid].regionsToPop; i++) {
892 // If the block ended in a jump out of the catch region, this effectively
893 // ends all catch regions deeper than the one we are jumping to
894 pop_active(info.blockInfo[bid].past);
897 if (debug && !activeList.empty()) {
898 current.clear();
899 exn_path(*func, current, activeList.back()->idx);
900 assertx(current == activeList);
904 while (!activeList.empty()) {
905 pop_active(info.blockInfo[info.blockOrder.back()].past);
909 * We've created all our regions, but we need to sort them instead
910 * of trying to get the UnitEmitter to do it.
912 * The UnitEmitter expects EH regions that look a certain way
913 * (basically the way emitter.cpp likes them). There are some rules
914 * about the order it needs to have at runtime, which we set up
915 * here.
917 * Essentially, an entry a is less than an entry b iff:
919 * - a starts before b
920 * - a starts at the same place, but encloses b entirely
921 * - a has the same extents as b, but is a parent of b
923 std::vector<EHRegion*> regions;
924 for (auto& mapEnt : exnMap) {
925 for (auto& region : mapEnt.second) {
926 regions.push_back(region.get());
929 std::sort(
930 begin(regions), end(regions),
931 [&] (const EHRegion* a, const EHRegion* b) {
932 if (a == b) return false;
933 if (a->start == b->start) {
934 if (a->past == b->past) {
935 // When regions exactly overlap, the parent is less than the
936 // child.
937 for (auto p = b->parent; p != nullptr; p = p->parent) {
938 if (p == a) return true;
940 // If a is not a parent of b, and they have the same region;
941 // then b better be a parent of a.
942 if (debug) {
943 auto p = a->parent;
944 for (; p != b && p != nullptr; p = p->parent) continue;
945 assertx(p == b);
947 return false;
949 return a->past > b->past;
951 return a->start < b->start;
955 hphp_fast_map<const EHRegion*,uint32_t> parentIndexMap;
956 for (auto& r : regions) {
957 emit_eh_region(fe, r, info.blockInfo, parentIndexMap);
959 fe.setEHTabIsSorted();
962 void emit_finish_func(EmitUnitState& state, FuncEmitter& fe,
963 php::WideFunc& wf, const EmitBcInfo& info) {
964 auto const& func = *wf;
965 if (info.containsCalls) fe.containsCalls = true;
967 emit_locals_and_params(fe, func, info);
968 emit_ehent_tree(fe, wf, info);
969 wf.blocks().clear();
971 fe.userAttributes = func.userAttributes;
972 fe.retUserType = func.returnUserType;
973 fe.retTypeConstraints = func.retTypeConstraints;
974 fe.originalFilename =
975 func.originalFilename ? func.originalFilename :
976 func.originalUnit ? func.originalUnit : nullptr;
977 fe.originalModuleName = func.originalModuleName;
978 fe.requiresFromOriginalModule = func.requiresFromOriginalModule;
979 fe.isClosureBody = func.isClosureBody;
980 fe.isAsync = func.isAsync;
981 fe.isGenerator = func.isGenerator;
982 fe.isPairGenerator = func.isPairGenerator;
983 fe.isNative = func.isNative;
984 fe.isMemoizeWrapper = func.isMemoizeWrapper;
985 fe.isMemoizeWrapperLSB = func.isMemoizeWrapperLSB;
987 for (auto& name : func.staticCoeffects) fe.staticCoeffects.push_back(name);
988 for (auto& rule : func.coeffectRules) fe.coeffectRules.push_back(rule);
990 auto const [retTy, _] = state.index.lookup_return_type_raw(&func).first;
991 if (!retTy.subtypeOf(BBottom)) {
992 fe.repoReturnType = make_repo_type(retTy);
995 if (is_specialized_wait_handle(retTy)) {
996 auto const awaitedTy = wait_handle_inner(retTy);
997 if (!awaitedTy.subtypeOf(BBottom)) {
998 fe.repoAwaitedReturnType = make_repo_type(awaitedTy);
1002 fe.maxStackCells = info.maxStackDepth +
1003 fe.numLocals() +
1004 fe.numIterators() * kNumIterCells;
1006 fe.finish();
1009 void renumber_locals(php::Func& func) {
1010 Id id = 0;
1011 Id nameId = 0;
1013 // We initialise local name ids in two passes, since locals that have not
1014 // been remapped may require their name be at the same offset as the local.
1015 // That's true for params, volatile locals, or locals in funcs with VarEnvs.
1017 // In the first pass, we assume that all local names are used. Only in the
1018 // second pass do we apply the fact that some local names are never used.
1020 for (auto& loc : func.locals) {
1021 if (loc.killed) {
1022 // Make sure it's out of range, in case someone tries to read it.
1023 loc.id = INT_MAX;
1024 } else {
1025 loc.nameId = nameId++;
1026 loc.id = id++;
1029 for (auto& loc : func.locals) {
1030 if (loc.unusedName || !loc.name) {
1031 // Make sure it's out of range, in case someone tries to read it.
1032 loc.nameId = INT_MAX;
1033 } else if (loc.killed) {
1034 // The local uses a shared local slot, but its name is still used.
1035 loc.nameId = nameId++;
1040 void emit_init_func(FuncEmitter& fe, const php::Func& func) {
1041 fe.init(
1042 std::get<0>(func.srcInfo.loc),
1043 std::get<1>(func.srcInfo.loc),
1044 func.attrs | (func.sampleDynamicCalls ? AttrDynamicallyCallable : AttrNone),
1045 func.srcInfo.docComment
1049 void emit_func(EmitUnitState& state, UnitEmitter& ue,
1050 FuncEmitter& fe, php::Func& f) {
1051 FTRACE(2, " func {}\n", f.name->data());
1052 assertx(f.attrs & AttrPersistent);
1053 renumber_locals(f);
1054 emit_init_func(fe, f);
1055 auto func = php::WideFunc::mut(&f);
1056 auto const info = emit_bytecode(state, ue, fe, func);
1057 emit_finish_func(state, fe, func, info);
1060 void emit_class(EmitUnitState& state, UnitEmitter& ue, PreClassEmitter* pce,
1061 php::Class& cls) {
1062 FTRACE(2, " class: {}\n", cls.name->data());
1063 assertx(cls.attrs & AttrPersistent);
1064 pce->init(
1065 std::get<0>(cls.srcInfo.loc),
1066 std::get<1>(cls.srcInfo.loc),
1067 cls.attrs |
1068 (cls.sampleDynamicConstruct ? AttrDynamicallyConstructible : AttrNone),
1069 cls.parentName ? cls.parentName : staticEmptyString(),
1070 cls.srcInfo.docComment
1072 pce->setUserAttributes(cls.userAttributes);
1074 for (auto& x : cls.interfaceNames) pce->addInterface(x);
1075 for (auto& x : cls.includedEnumNames) pce->addEnumInclude(x);
1076 for (auto& x : cls.usedTraitNames) pce->addUsedTrait(x);
1077 for (auto& x : cls.requirements) pce->addClassRequirement(x);
1079 pce->setIfaceVtableSlot(state.index.lookup_iface_vtable_slot(&cls));
1081 bool needs86cinit = false;
1083 auto const nativeConsts = cls.attrs & AttrBuiltin ?
1084 Native::getClassConstants(cls.name) : nullptr;
1086 for (auto& cconst : cls.constants) {
1087 if (nativeConsts && nativeConsts->count(cconst.name)) {
1088 continue;
1090 if (cconst.kind == ConstModifiers::Kind::Context) {
1091 assertx(cconst.cls->tsame(cls.name));
1092 assertx(!cconst.resolvedTypeStructure);
1093 assertx(cconst.invariance == php::Const::Invariance::None);
1094 pce->addContextConstant(
1095 cconst.name,
1096 std::vector<LowStringPtr>(cconst.coeffects),
1097 cconst.isAbstract,
1098 cconst.isFromTrait
1100 } else if (!cconst.val.has_value()) {
1101 assertx(cconst.cls->tsame(cls.name));
1102 assertx(!cconst.resolvedTypeStructure);
1103 assertx(cconst.invariance == php::Const::Invariance::None);
1104 pce->addAbstractConstant(
1105 cconst.name,
1106 cconst.kind,
1107 cconst.isFromTrait
1109 } else {
1110 needs86cinit |= cconst.val->m_type == KindOfUninit;
1111 pce->addConstant(
1112 cconst.name,
1113 cconst.cls->tsame(cls.name) ? nullptr : cconst.cls,
1114 &cconst.val.value(),
1115 ArrNR{cconst.resolvedTypeStructure},
1116 cconst.kind,
1117 cconst.invariance,
1118 cconst.isFromTrait,
1119 cconst.isAbstract
1124 for (auto& m : cls.methods) {
1125 if (!m) continue; // Removed
1126 if (!needs86cinit && m->name == s_86cinit.get()) continue;
1127 FTRACE(2, " method: {}\n", m->name);
1128 auto const fe = ue.newMethodEmitter(m->name, pce);
1129 emit_func(state, ue, *fe, *m);
1130 pce->addMethod(fe);
1133 CompactVector<Type> useVars;
1134 if (is_closure(cls)) {
1135 auto f = find_method(&cls, s_invoke.get());
1136 useVars = state.index.lookup_closure_use_vars(f, true);
1138 auto uvIt = useVars.begin();
1140 auto const privateProps = state.index.lookup_private_props(&cls, true);
1141 auto const privateStatics = state.index.lookup_private_statics(&cls, true);
1142 auto const publicStatics = state.index.lookup_public_statics(&cls);
1143 for (auto const& prop : cls.properties) {
1144 auto const makeRat = [&] (const Type& ty) -> RepoAuthType {
1145 if (!ty.subtypeOf(BCell)) return RepoAuthType{};
1146 if (ty.subtypeOf(BBottom)) {
1147 // A property can be TBottom if no sets (nor its initial value) is
1148 // compatible with its type-constraint, or if its LateInit and there's
1149 // no sets to it. The repo auth type here doesn't particularly matter,
1150 // since such a prop will be inaccessible.
1151 return RepoAuthType{};
1153 return make_repo_type(ty);
1156 auto const privPropTy = [&] (const PropState& ps) -> std::pair<Type, bool> {
1157 if (is_closure(cls)) {
1158 // For closures use variables will be the first properties of the
1159 // closure object, in declaration order
1160 if (uvIt != useVars.end()) return std::make_pair(*uvIt++, true);
1161 return std::make_pair(TCell, true);
1164 auto it = ps.find(prop.name);
1165 if (it == end(ps)) return std::make_pair(TCell, true);
1166 return std::make_pair(it->second.ty, it->second.everModified);
1169 auto propTy = TCell;
1170 auto everModified = true;
1171 auto attrs = prop.attrs;
1172 if (attrs & AttrPrivate) {
1173 std::tie(propTy, everModified) =
1174 privPropTy((attrs & AttrStatic) ? privateStatics : privateProps);
1175 } else if ((attrs & (AttrPublic|AttrProtected)) && (attrs & AttrStatic)) {
1176 std::tie(propTy, everModified) = [&] {
1177 auto const it = publicStatics.find(prop.name);
1178 if (it == end(publicStatics)) return std::make_pair(TCell, true);
1179 return std::make_pair(it->second.ty, it->second.everModified);
1180 }();
1183 if (!everModified && (attrs & AttrStatic) && !propTy.is(BBottom)) {
1184 attrs |= AttrPersistent;
1187 pce->addProperty(
1188 prop.name,
1189 attrs,
1190 prop.userType,
1191 TypeIntersectionConstraint(prop.typeConstraints),
1192 prop.docComment,
1193 &prop.val,
1194 makeRat(propTy),
1195 prop.userAttributes
1198 assertx(uvIt == useVars.end());
1200 pce->setEnumBaseTy(cls.enumBaseTy);
1203 void emit_typealias(UnitEmitter& ue, const php::TypeAlias& alias) {
1204 assertx(alias.attrs & AttrPersistent);
1205 auto const te = ue.newTypeAliasEmitter(alias.name->toCppString());
1207 te->init(
1208 std::get<0>(alias.srcInfo.loc),
1209 std::get<1>(alias.srcInfo.loc),
1210 alias.attrs,
1211 alias.value,
1212 alias.kind,
1213 Array::attach(const_cast<ArrayData*>(alias.typeStructure)),
1214 Array::attach(const_cast<ArrayData*>(alias.resolvedTypeStructure))
1216 te->setUserAttributes(alias.userAttrs);
1219 void emit_constant(UnitEmitter& ue, const php::Constant& constant) {
1220 assertx(constant.attrs & AttrPersistent);
1221 Constant c {
1222 constant.name,
1223 constant.val,
1224 constant.attrs,
1226 ue.addConstant(c);
1229 void emit_module(UnitEmitter& ue, const php::Module& module) {
1230 Module m {
1231 module.name,
1232 module.srcInfo.docComment,
1233 (int)std::get<0>(module.srcInfo.loc),
1234 (int)std::get<1>(module.srcInfo.loc),
1235 module.attrs,
1236 module.userAttributes,
1238 ue.addModule(m);
1241 //////////////////////////////////////////////////////////////////////
1245 std::unique_ptr<UnitEmitter> emit_unit(Index& index, php::Unit& unit) {
1246 Trace::Bump bumper{
1247 Trace::hhbbc_emit, kSystemLibBump, is_systemlib_part(unit)
1250 assertx(check(unit, index));
1252 static std::atomic<uint64_t> nextUnitId{0};
1253 auto unitSn = nextUnitId++;
1255 auto extension = [&]() -> Extension* {
1256 if (!unit.extName->empty()) {
1257 return ExtensionRegistry::get(unit.extName->data());
1259 return nullptr;
1260 }();
1262 auto ue = std::make_unique<UnitEmitter>(SHA1 { unitSn },
1263 SHA1{},
1264 unit.packageInfo);
1265 FTRACE(1, " unit {}\n", unit.filename->data());
1266 ue->m_sn = unitSn;
1267 ue->m_filepath = unit.filename;
1268 ue->m_extension = extension;
1269 ue->m_metaData = unit.metaData;
1270 ue->m_fileAttributes = unit.fileAttributes;
1271 ue->m_moduleName = unit.moduleName;
1272 ue->m_softDeployedRepoOnly = unit.packageInfo.isModuleSoftDeployed(unit.moduleName);
1274 if (unit.fatalInfo) {
1275 // We should have dealt with verifier failures long ago.
1276 assertx(unit.fatalInfo->fatalLoc.has_value());
1277 ue->m_fatalUnit = true;
1278 ue->m_fatalOp = unit.fatalInfo->fatalOp;
1279 ue->m_fatalLoc = *unit.fatalInfo->fatalLoc;
1280 ue->m_fatalMsg = unit.fatalInfo->fatalMsg;
1283 EmitUnitState state { index, &unit };
1285 // Go thought all constants and see if they still need their
1286 // matching 86cinit func. In repo mode we are able to optimize away
1287 // most of them away. And if the const don't need them anymore we
1288 // should not emit them.
1289 hphp_fast_set<const StringData*> const_86cinit_funcs;
1290 for (size_t id = 0; id < unit.constants.size(); ++id) {
1291 auto const& c = unit.constants[id];
1292 if (type(c->val) != KindOfUninit) {
1293 const_86cinit_funcs.emplace(Constant::funcNameFromName(c->name));
1297 index.for_each_unit_class_mutable(
1298 unit,
1299 [&] (php::Class& c) {
1300 // No reason to include closures unless there's a reachable
1301 // CreateCl.
1302 if (is_closure(c)) return;
1303 recordClass(state, *ue, c);
1307 index.for_each_unit_func_mutable(
1308 unit,
1309 [&] (php::Func& f) {
1310 if (const_86cinit_funcs.count(f.name)) return;
1311 auto fe = ue->newFuncEmitter(f.name);
1312 emit_func(state, *ue, *fe, f);
1316 // Note that state.pces can grow inside the loop due to discovering
1317 // more closures.
1318 for (size_t idx = 0; idx < state.pces.size(); ++idx) {
1319 auto const [cls, pce] = state.pces[idx];
1320 emit_class(state, *ue, pce, *cls);
1323 for (size_t id = 0; id < unit.typeAliases.size(); ++id) {
1324 emit_typealias(*ue, *unit.typeAliases[id]);
1327 for (size_t id = 0; id < unit.constants.size(); ++id) {
1328 emit_constant(*ue, *unit.constants[id]);
1331 for (size_t id = 0; id < unit.modules.size(); ++id) {
1332 emit_module(*ue, *unit.modules[id]);
1335 ue->finish();
1336 return ue;
1339 //////////////////////////////////////////////////////////////////////