Consolidate generator code
[hiphop-php.git] / hphp / hhbbc / whole-program.cpp
blobbe887a5f6242fe6d036ea560544e0fb9a10ec6a9
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/hhbbc.h"
18 #include <vector>
19 #include <algorithm>
20 #include <atomic>
21 #include <memory>
22 #include <set>
24 #include <folly/AtomicLinkedList.h>
25 #include <folly/Memory.h>
26 #include <folly/ScopeGuard.h>
28 #ifdef HHVM_FACEBOOK
29 #include <strobelight/strobemeta/strobemeta_frames.h>
30 #else
31 #define SET_FRAME_METADATA(...)
32 #endif
34 #include "hphp/runtime/vm/repo-global-data.h"
35 #include "hphp/runtime/vm/unit-emitter.h"
37 #include "hphp/hhbbc/analyze.h"
38 #include "hphp/hhbbc/class-util.h"
39 #include "hphp/hhbbc/debug.h"
40 #include "hphp/hhbbc/emit.h"
41 #include "hphp/hhbbc/func-util.h"
42 #include "hphp/hhbbc/index.h"
43 #include "hphp/hhbbc/optimize.h"
44 #include "hphp/hhbbc/options.h"
45 #include "hphp/hhbbc/options-util.h"
46 #include "hphp/hhbbc/parallel.h"
47 #include "hphp/hhbbc/parse.h"
48 #include "hphp/hhbbc/representation.h"
49 #include "hphp/hhbbc/stats.h"
50 #include "hphp/hhbbc/type-system.h"
51 #include "hphp/hhbbc/wide-func.h"
53 #include "hphp/util/extern-worker.h"
54 #include "hphp/util/struct-log.h"
56 namespace HPHP {
58 using namespace extern_worker;
59 namespace coro = folly::coro;
61 namespace HHBBC {
63 TRACE_SET_MOD(hhbbc);
65 //////////////////////////////////////////////////////////////////////
67 namespace {
69 //////////////////////////////////////////////////////////////////////
71 const StaticString s_invoke("__invoke");
73 //////////////////////////////////////////////////////////////////////
75 enum class WorkType { Class, Func };
77 struct WorkItem {
78 explicit WorkItem(WorkType type, Context ctx)
79 : type(type)
80 , ctx(ctx)
83 WorkType type;
84 Context ctx;
87 struct WorkResult {
88 explicit WorkResult(ClassAnalysis cls)
89 : type(WorkType::Class)
90 , cls(std::move(cls))
93 explicit WorkResult(FuncAnalysisResult func)
94 : type(WorkType::Func)
95 , func(std::move(func))
98 WorkResult(WorkResult&& wr) noexcept
99 : type(wr.type)
101 switch (type) {
102 case WorkType::Class:
103 new (&cls) ClassAnalysis(std::move(wr.cls));
104 break;
105 case WorkType::Func:
106 new (&func) FuncAnalysisResult(std::move(wr.func));
107 break;
111 WorkResult& operator=(WorkResult&& o) noexcept {
112 this->~WorkResult();
113 switch (o.type) {
114 case WorkType::Class: new (this) WorkResult(std::move(o.cls)); break;
115 case WorkType::Func: new (this) WorkResult(std::move(o.func)); break;
117 return *this;
120 ~WorkResult() {
121 switch (type) {
122 case WorkType::Class:
123 cls.~ClassAnalysis();
124 break;
125 case WorkType::Func:
126 func.~FuncAnalysisResult();
127 break;
131 WorkType type;
132 union {
133 ClassAnalysis cls;
134 FuncAnalysisResult func;
138 //////////////////////////////////////////////////////////////////////
140 std::vector<Context> all_unit_contexts(const Index& index,
141 const php::Unit& u) {
142 std::vector<Context> ret;
143 index.for_each_unit_class(
145 [&] (const php::Class& c) {
146 for (auto const& m : c.methods) {
147 ret.emplace_back(Context { u.filename, m.get(), &c });
151 index.for_each_unit_func(
153 [&] (const php::Func& f) { ret.emplace_back(Context { u.filename, &f }); }
155 return ret;
158 // Return all the WorkItems we'll need to start analyzing this
159 // program.
160 std::vector<WorkItem> initial_work(const Index& index) {
161 std::vector<WorkItem> ret;
163 auto const& program = index.program();
164 for (auto const& c : program.classes) {
165 assertx(!c->closureContextCls);
166 if (is_used_trait(*c)) {
167 for (auto const& f : c->methods) {
168 ret.emplace_back(
169 WorkType::Func,
170 Context { c->unit, f.get(), f->cls }
173 } else {
174 ret.emplace_back(
175 WorkType::Class,
176 Context { c->unit, nullptr, c.get() }
180 for (auto const& f : program.funcs) {
181 ret.emplace_back(
182 WorkType::Func,
183 Context { f->unit, f.get() }
186 return ret;
189 WorkItem work_item_for(const DependencyContext& d,
190 const Index& index) {
191 switch (d.tag()) {
192 case DependencyContextType::Class: {
193 auto const cls = (const php::Class*)d.ptr();
194 assertx(!is_used_trait(*cls));
195 return WorkItem {
196 WorkType::Class,
197 Context { cls->unit, nullptr, cls }
200 case DependencyContextType::Func: {
201 auto const func = (const php::Func*)d.ptr();
202 auto const cls = func->cls
203 ? index.lookup_closure_context(*func->cls)
204 : nullptr;
205 assertx(!cls || is_used_trait(*cls));
206 return WorkItem {
207 WorkType::Func,
208 Context { func->unit, func, cls }
211 case DependencyContextType::Prop:
212 case DependencyContextType::FuncFamily:
213 // We only record dependencies on these. We don't schedule any
214 // work on their behalf.
215 break;
217 always_assert(false);
221 * Algorithm:
223 * Start by running an analyze pass on every class or free function.
224 * During analysis, information about functions or classes will be
225 * requested from the Index, which initially won't really know much,
226 * but will record a dependency. This part is done in parallel: no
227 * passes are mutating anything, just reading from the Index.
229 * After a pass, we do a single-threaded "update" step to prepare
230 * for the next pass: for each function or class that was analyzed,
231 * note the facts we learned that may aid analyzing other functions
232 * in the program, and register them in the index.
234 * If any of these facts are more useful than they used to be, add
235 * all the Contexts that had a dependency on the new information to
236 * the work list again, in case they can do better based on the new
237 * fact. (This only applies to function analysis information right
238 * now.)
240 * Repeat until the work list is empty.
243 void analyze_iteratively(Index& index) {
244 trace_time tracer("analyze iteratively", index.sample());
246 // Counters, just for debug printing.
247 std::atomic<uint32_t> total_funcs{0};
248 std::atomic<uint32_t> total_classes{0};
249 auto round = uint32_t{0};
251 SCOPE_EXIT {
252 if (Trace::moduleEnabledRelease(Trace::hhbbc_time, 1)) {
253 Trace::traceRelease("total class visits %u\n", total_classes.load());
254 Trace::traceRelease("total function visits %u\n", total_funcs.load());
258 std::vector<DependencyContextSet> deps_vec{parallel::num_threads};
260 auto work = initial_work(index);
261 while (!work.empty()) {
262 auto results = [&] {
263 trace_time trace(
264 "analyzing",
265 folly::sformat("round {} -- {} work items", round, work.size())
267 return parallel::map(
268 work,
269 // We have a Optional just to keep the result type
270 // DefaultConstructible.
271 [&] (const WorkItem& wi) -> Optional<WorkResult> {
272 SET_FRAME_METADATA(wi.ctx.unit->toCppString());
273 switch (wi.type) {
274 case WorkType::Func: {
275 ++total_funcs;
276 auto const wf = php::WideFunc::cns(wi.ctx.func);
277 auto const ctx = AnalysisContext { wi.ctx.unit, wf, wi.ctx.cls };
278 IndexAdaptor adaptor{ index };
279 return WorkResult { analyze_func(adaptor, ctx, CollectionOpts{}) };
281 case WorkType::Class: {
282 ++total_classes;
283 IndexAdaptor adaptor{ index };
284 return WorkResult { analyze_class(adaptor, wi.ctx) };
287 not_reached();
290 }();
292 ++round;
293 trace_time update_time("updating");
295 auto const update_func = [&] (FuncAnalysisResult& fa,
296 DependencyContextSet& deps) {
297 SCOPE_ASSERT_DETAIL("update_func") {
298 return "Updating Func: " + show(fa.ctx);
300 // This const_cast is safe since no two threads update the same Func.
301 auto func = php::WideFunc::mut(const_cast<php::Func*>(fa.ctx.func));
302 index.refine_return_info(fa, deps);
303 index.refine_constants(fa, deps);
304 update_bytecode(func, std::move(fa.blockUpdates));
306 index.record_public_static_mutations(
307 *func,
308 std::move(fa.publicSPropMutations)
311 if (auto const l = fa.resolvedInitializers.left()) {
312 index.refine_class_constants(fa.ctx, *l, deps);
313 } else if (auto const r = fa.resolvedInitializers.right()) {
314 index.update_prop_initial_values(fa.ctx, *r, deps);
317 for (auto const& [cls, vars] : fa.closureUseTypes) {
318 assertx(is_closure(*cls));
319 if (index.refine_closure_use_vars(cls, vars)) {
320 auto const func = find_method(cls, s_invoke.get());
321 always_assert_flog(
322 func != nullptr,
323 "Failed to find __invoke on {} during index update\n",
324 cls->name
326 auto const ctx =
327 Context { func->unit, func, cls };
328 deps.insert(index.dependency_context(ctx));
333 auto const update_class = [&] (ClassAnalysis& ca,
334 DependencyContextSet& deps) {
336 SCOPE_ASSERT_DETAIL("update_class") {
337 return "Updating Class: " + show(ca.ctx);
339 index.refine_private_props(ca.ctx.cls,
340 ca.privateProperties);
341 index.refine_private_statics(ca.ctx.cls,
342 ca.privateStatics);
343 index.update_prop_initial_values(ca.ctx, ca.resolvedProps, deps);
345 for (auto& fa : ca.methods) update_func(fa, deps);
346 for (auto& fa : ca.closures) update_func(fa, deps);
349 parallel::for_each(
350 results,
351 [&] (auto& result, size_t worker) {
352 assertx(worker < deps_vec.size());
353 switch (result->type) {
354 case WorkType::Func:
355 update_func(result->func, deps_vec[worker]);
356 break;
357 case WorkType::Class:
358 update_class(result->cls, deps_vec[worker]);
359 break;
361 result.reset();
366 trace_time _("merging deps");
367 for (auto& deps : deps_vec) {
368 if (&deps == &deps_vec[0]) continue;
369 for (auto& d : deps) deps_vec[0].insert(d);
370 deps.clear();
374 auto& deps = deps_vec[0];
376 index.refine_public_statics(deps);
378 work.clear();
379 work.reserve(deps.size());
380 for (auto& d : deps) work.emplace_back(work_item_for(d, index));
381 deps.clear();
386 * Finally, use the results of all these iterations to perform
387 * optimization. This reanalyzes every function using our
388 * now-very-updated Index, and then runs optimize_func with the
389 * results.
391 * We do this in parallel: all the shared information is queried out
392 * of the index, and each thread is allowed to modify the bytecode
393 * for the function it is looking at.
395 * NOTE: currently they can't modify anything other than the
396 * bytecode/Blocks, because other threads may be doing unlocked
397 * queries to php::Func and php::Class structures.
399 template<typename F>
400 void final_pass(Index& index,
401 const StatsHolder& stats,
402 F emitUnit) {
403 trace_time final_pass("final pass", index.sample());
404 index.freeze();
405 auto const dump_dir = debug_dump_to();
406 parallel::for_each(
407 index.program().units,
408 [&] (const std::unique_ptr<php::Unit>& unit) {
409 SET_FRAME_METADATA(unit->filename->toCppString());
410 // optimize_func can remove 86*init methods from classes, so we
411 // have to save the contexts for now.
412 for (auto const& context : all_unit_contexts(index, *unit)) {
413 // This const_cast is safe since no two threads update the same Func.
414 auto func = php::WideFunc::mut(const_cast<php::Func*>(context.func));
415 auto const ctx = AnalysisContext { context.unit, func, context.cls };
416 IndexAdaptor adaptor{ index };
417 optimize_func(index, analyze_func(adaptor, ctx, CollectionOpts{}), func);
419 state_after("optimize", *unit, index);
420 if (!dump_dir.empty()) {
421 if (Trace::moduleEnabledRelease(Trace::hhbbc_dump, 2)) {
422 dump_representation(dump_dir, index, *unit);
424 dump_index(dump_dir, index, *unit);
426 collect_stats(stats, index, *unit);
427 emitUnit(*unit);
432 //////////////////////////////////////////////////////////////////////
434 // Extern-worker job to analyze constants
436 struct AnalyzeConstantsJob {
437 static std::string name() { return "hhbbc-analyze-constants"; }
438 static void init(const Config& config) {
439 process_init(config.o, config.gd, false);
440 AnalysisIndex::start();
442 static void fini() { AnalysisIndex::stop(); }
444 template<typename T> using V = Variadic<T>;
445 template<typename T> using VU = V<std::unique_ptr<T>>;
447 using Output = AnalysisIndex::Output;
449 static Output run(VU<php::Class> classes,
450 VU<php::Func> funcs,
451 VU<php::Unit> units,
452 VU<php::ClassBytecode> clsBC,
453 VU<php::FuncBytecode> funcBC,
454 V<AnalysisIndexCInfo> cinfos,
455 V<AnalysisIndexFInfo> finfos,
456 V<AnalysisIndexMInfo> minfos,
457 VU<php::Class> depClasses,
458 VU<php::Func> depFuncs,
459 VU<php::Unit> depUnits,
460 AnalysisInput::Meta meta) {
461 // AnalysisIndex ctor will initialize the worklist appropriately.
462 AnalysisWorklist worklist;
463 AnalysisIndex index{
464 worklist,
465 std::move(classes.vals),
466 std::move(funcs.vals),
467 std::move(units.vals),
468 std::move(clsBC.vals),
469 std::move(funcBC.vals),
470 std::move(cinfos.vals),
471 std::move(finfos.vals),
472 std::move(minfos.vals),
473 std::move(depClasses.vals),
474 std::move(depFuncs.vals),
475 std::move(depUnits.vals),
476 std::move(meta),
477 AnalysisIndex::Mode::Constants
480 // Keep processing work until we reach a fixed-point (nothing new
481 // gets put on the worklist).
482 while (process(index, worklist)) {}
483 // Freeze the index. Nothing is allowed to update the index after
484 // this. This will also re-load the worklist with all of the
485 // classes which will end up in the output.
486 index.freeze();
487 // Now do a pass through the original work items again. Now that
488 // the index is frozen, we'll gather up any dependencies from the
489 // analysis. Since we already reached a fixed point, this should
490 // not cause any updates (and if it does, we'll assert).
491 while (process(index, worklist)) {}
492 // Everything is analyzed and dependencies are recorded. Turn the
493 // index data into AnalysisIndex::Output and return it from this
494 // job.
495 return index.finish();
498 private:
499 // Analyze the work item at the front of the worklist (returning
500 // false if the list is empty).
501 static bool process(AnalysisIndex& index,
502 AnalysisWorklist& worklist) {
503 auto const w = worklist.next();
504 if (auto const c = w.cls()) {
505 update(analyze(*c, index), index);
506 } else if (auto const f = w.func()) {
507 update(analyze(*f, index), index);
508 } else if (auto const u = w.unit()) {
509 update(analyze(*u, index), index);
510 } else {
511 return false;
513 return true;
516 static FuncAnalysisResult analyze(const php::Func& f,
517 const AnalysisIndex& index) {
518 auto const wf = php::WideFunc::cns(&f);
519 AnalysisContext ctx{ f.unit, wf, f.cls };
520 return analyze_func(AnalysisIndexAdaptor{ index }, ctx, CollectionOpts{});
523 static ClassAnalysis analyze(const php::Class& c,
524 const AnalysisIndex& index) {
525 return analyze_class_constants(
526 AnalysisIndexAdaptor { index },
527 Context { c.unit, nullptr, &c }
531 static UnitAnalysis analyze(const php::Unit& u, const AnalysisIndex& index) {
532 return analyze_unit(index, Context { u.filename, nullptr, nullptr });
535 static void update(FuncAnalysisResult fa, AnalysisIndex& index) {
536 SCOPE_ASSERT_DETAIL("update func") {
537 return "Updating Func: " + show(fa.ctx);
540 auto const UNUSED bump =
541 trace_bump(fa.ctx, Trace::hhbbc, Trace::hhbbc_cfg, Trace::hhbbc_index);
542 AnalysisIndexAdaptor adaptor{index};
543 ContextPusher _{adaptor, fa.ctx};
544 index.refine_return_info(fa);
545 index.refine_constants(fa);
546 index.refine_class_constants(fa);
547 index.update_prop_initial_values(fa);
548 index.update_bytecode(fa);
551 static void update(ClassAnalysis ca, AnalysisIndex& index) {
552 SCOPE_ASSERT_DETAIL("update class") {
553 return "Updating Class: " + show(ca.ctx);
557 auto const UNUSED bump =
558 trace_bump(ca.ctx, Trace::hhbbc, Trace::hhbbc_cfg, Trace::hhbbc_index);
559 AnalysisIndexAdaptor adaptor{index};
560 ContextPusher _{adaptor, ca.ctx};
561 index.update_type_consts(ca);
563 for (auto& fa : ca.methods) update(std::move(fa), index);
564 for (auto& fa : ca.closures) update(std::move(fa), index);
567 static void update(UnitAnalysis ua, AnalysisIndex& index) {
568 SCOPE_ASSERT_DETAIL("update unit") {
569 return "Updating Unit: " + show(ua.ctx);
571 AnalysisIndexAdaptor adaptor{index};
572 ContextPusher _{adaptor, ua.ctx};
573 index.update_type_aliases(ua);
577 Job<AnalyzeConstantsJob> s_analyzeConstantsJob;
579 void analyze_constants(Index& index) {
580 trace_time tracer{"analyze constants", index.sample()};
582 constexpr size_t kBucketSize = 2000;
583 constexpr size_t kMaxBucketSize = 30000;
585 using namespace folly::gen;
587 // We'll only process classes with 86*init functions or top-level
588 // 86cinits.
589 AnalysisScheduler scheduler{index};
590 for (auto const cls : index.classes_with_86inits()) {
591 scheduler.registerClass(cls);
593 for (auto const func : index.constant_init_funcs()) {
594 scheduler.registerFunc(func);
596 for (auto const unit : index.units_with_type_aliases()) {
597 scheduler.registerUnit(unit);
600 auto const run = [&] (AnalysisInput input,
601 CoroLatch& latch) -> coro::Task<void> {
602 auto guard = folly::makeGuard([&] { latch.count_down(); });
604 co_await coro::co_reschedule_on_current_executor;
606 if (input.empty()) co_return;
608 Client::ExecMetadata metadata{
609 .job_key = folly::sformat("analyze constants {}", input.key())
612 auto [inputMeta, config] = co_await coro::collectAll(
613 index.client().store(input.takeMeta()),
614 index.configRef().getCopy()
617 auto classNames = input.classNames();
618 auto cinfoNames = input.cinfoNames();
619 auto minfoNames = input.minfoNames();
620 auto funcNames = input.funcNames();
621 auto unitNames = input.unitNames();
622 auto tuple = input.toTuple(std::move(inputMeta));
624 // Signal we're done touching the Index.
625 guard.dismiss();
626 latch.count_down();
628 auto outputs = co_await index.client().exec(
629 s_analyzeConstantsJob,
630 std::move(config),
631 singleton_vec(std::move(tuple)),
632 std::move(metadata)
635 // Run the job
636 always_assert(outputs.size() == 1);
637 auto& [clsRefs, funcRefs, unitRefs,
638 clsBCRefs, funcBCRefs,
639 cinfoRefs, finfoRefs,
640 minfoRefs, metaRef] = outputs[0];
642 // We cannot call scheduler.record below until all co-routines have called
643 // input.toTuple (record modifies the Index and toTuple reads from it), so
644 // block here until the latch is cleared. Technically we only need to wait
645 // on this before scheduler.record, but by doing this before the load, we
646 // avoid blocking while holding onto a lot of memory.
647 co_await latch.wait();
649 auto meta = co_await index.client().load(std::move(metaRef));
651 funcNames.erase(
652 std::remove_if(
653 begin(funcNames),
654 end(funcNames),
655 [&] (SString n) { return meta.removedFuncs.count(n); }
657 end(funcNames)
660 always_assert(clsRefs.size() == classNames.size());
661 always_assert(clsBCRefs.size() == classNames.size());
662 always_assert(cinfoRefs.size() == cinfoNames.size());
663 always_assert(minfoRefs.size() == minfoNames.size());
664 always_assert(meta.classDeps.size() == classNames.size());
665 always_assert(funcRefs.size() == funcNames.size());
666 always_assert(funcBCRefs.size() == funcNames.size());
667 always_assert(finfoRefs.size() == funcNames.size());
668 always_assert(meta.funcDeps.size() == funcNames.size());
669 always_assert(unitRefs.size() == unitNames.size());
671 // Inform the scheduler
672 scheduler.record(
673 AnalysisOutput{
674 std::move(classNames),
675 std::move(cinfoNames),
676 std::move(minfoNames),
677 std::move(clsRefs),
678 std::move(clsBCRefs),
679 std::move(cinfoRefs),
680 std::move(funcNames),
681 std::move(funcRefs),
682 std::move(funcBCRefs),
683 std::move(finfoRefs),
684 std::move(minfoRefs),
685 std::move(unitNames),
686 std::move(unitRefs),
687 std::move(meta)
690 co_return;
693 size_t round{0};
694 while (auto const workItems = scheduler.workItems()) {
695 trace_time trace{
696 "analyze constants round",
697 folly::sformat("round {} -- {} work items", round, workItems)
699 // Get the work buckets from the scheduler.
700 auto work = [&] {
701 trace_time trace2{
702 "analyze constants schedule",
703 folly::sformat("round {}", round)
705 trace2.ignore_client_stats();
706 return scheduler.schedule(kBucketSize, kMaxBucketSize);
707 }();
708 // Work shouldn't be empty because we add non-zero work items this
709 // round.
710 assertx(!work.empty());
713 // Process the work buckets in individual analyze constants
714 // jobs. These will record their results as each one finishes.
715 trace_time trace2{
716 "analyze constants run",
717 folly::sformat("round {}", round)
719 trace2.ignore_client_stats();
721 CoroLatch latch{work.size()};
722 coro::blockingWait(coro::collectAllRange(
723 from(work)
724 | move
725 | map([&] (AnalysisInput&& input) {
726 return run(std::move(input), latch)
727 .scheduleOn(index.executor().sticky());
729 | as<std::vector>()
734 // All the jobs recorded their results in the scheduler. Now let
735 // the scheduler know that all jobs are done, so it can
736 // determine what needs to be run in the next round.
737 trace_time trace2{
738 "analyze constants deps",
739 folly::sformat("round {}", round)
741 trace2.ignore_client_stats();
742 scheduler.recordingDone();
743 ++round;
748 //////////////////////////////////////////////////////////////////////
752 //////////////////////////////////////////////////////////////////////
754 struct WholeProgramInput::Key::Impl {
755 enum class Type {
756 None,
757 Fail,
758 Unit,
759 Func,
760 FuncBytecode,
761 Class,
762 ClassBytecode
765 using UnresolvedTypes =
766 hphp_fast_set<SString, string_data_hash, string_data_tsame>;
768 struct FailInfo {
769 LSString message;
770 template <typename SerDe> void serde(SerDe& sd) { sd(message); }
772 struct UnitInfo {
773 LSString name;
774 std::vector<TypeMapping> typeMappings;
775 std::vector<std::pair<SString, bool>> constants;
776 template <typename SerDe> void serde(SerDe& sd) {
777 sd(name)
778 (typeMappings)
779 (constants)
783 struct FuncInfo {
784 LSString name;
785 LSString unit;
786 bool methCaller;
787 UnresolvedTypes unresolvedTypes;
788 template <typename SerDe> void serde(SerDe& sd) {
789 sd(name)
790 (unit)
791 (methCaller)
792 (unresolvedTypes, string_data_lt_type{})
796 struct FuncBytecodeInfo {
797 LSString name;
798 LSString unit;
799 bool methCaller;
800 template <typename SerDe> void serde(SerDe& sd) {
801 sd(name)(unit)(methCaller);
804 struct ClassInfo {
805 LSString name;
806 LSString context;
807 LSString unit;
808 std::vector<SString> closures;
809 std::vector<SString> dependencies;
810 bool has86init;
811 Optional<TypeMapping> typeMapping;
812 UnresolvedTypes unresolvedTypes;
813 template <typename SerDe> void serde(SerDe& sd) {
814 sd(name)
815 (context)
816 (unit)
817 (closures)
818 (dependencies)
819 (has86init)
820 (typeMapping)
821 (unresolvedTypes, string_data_lt_type{})
825 struct ClassBytecodeInfo {
826 LSString name;
827 template <typename SerDe> void serde(SerDe& sd) {
828 sd(name);
832 Type type;
833 union {
834 FailInfo fail;
835 UnitInfo unit;
836 FuncInfo func;
837 FuncBytecodeInfo funcBC;
838 ClassInfo cls;
839 ClassBytecodeInfo clsBC;
842 Impl() : type{Type::None} {}
844 explicit Impl(FailInfo i) : type{Type::Fail}, fail{std::move(i)} {}
845 explicit Impl(UnitInfo i) : type{Type::Unit}, unit{std::move(i)} {}
846 explicit Impl(FuncInfo i) : type{Type::Func}, func{std::move(i)} {}
847 explicit Impl(ClassInfo i) : type{Type::Class}, cls{std::move(i)} {}
848 explicit Impl(FuncBytecodeInfo i)
849 : type{Type::FuncBytecode}, funcBC{std::move(i)} {}
850 explicit Impl(ClassBytecodeInfo i)
851 : type{Type::ClassBytecode}, clsBC{std::move(i)} {}
853 Impl(const Impl&) = delete;
854 Impl(Impl&&) = delete;
855 Impl& operator=(const Impl&) = delete;
856 Impl& operator=(Impl&&) = delete;
858 void destroyInfo() {
859 switch (type) {
860 case Type::None: break;
861 case Type::Fail: fail.~FailInfo(); break;
862 case Type::Unit: unit.~UnitInfo(); break;
863 case Type::Func: func.~FuncInfo(); break;
864 case Type::Class: cls.~ClassInfo(); break;
865 case Type::FuncBytecode:
866 funcBC.~FuncBytecodeInfo();
867 break;
868 case Type::ClassBytecode:
869 clsBC.~ClassBytecodeInfo();
870 break;
874 ~Impl() { destroyInfo(); }
876 template <typename SerDe> void serde(SerDe& sd) {
877 if constexpr (SerDe::deserializing) {
878 destroyInfo();
879 sd(type);
880 switch (type) {
881 case Type::None: break;
882 case Type::Fail: new (&fail) FailInfo(); break;
883 case Type::Unit: new (&unit) UnitInfo(); break;
884 case Type::Func: new (&func) FuncInfo(); break;
885 case Type::Class: new (&cls) ClassInfo(); break;
886 case Type::FuncBytecode:
887 new (&funcBC) FuncBytecodeInfo();
888 break;
889 case Type::ClassBytecode:
890 new (&clsBC) ClassBytecodeInfo();
891 break;
893 } else {
894 sd(type);
897 switch (type) {
898 case Type::None: break;
899 case Type::Fail: sd(fail); break;
900 case Type::Unit: sd(unit); break;
901 case Type::Func: sd(func); break;
902 case Type::Class: sd(cls); break;
903 case Type::FuncBytecode:
904 sd(funcBC);
905 break;
906 case Type::ClassBytecode:
907 sd(clsBC);
908 break;
913 struct WholeProgramInput::Value::Impl {
914 std::unique_ptr<php::Func> func;
915 std::unique_ptr<php::Class> cls;
916 std::unique_ptr<php::Unit> unit;
917 std::unique_ptr<php::FuncBytecode> funcBC;
918 std::unique_ptr<php::ClassBytecode> clsBC;
920 explicit Impl(std::nullptr_t) {}
921 explicit Impl(std::unique_ptr<php::Func> func) : func{std::move(func)} {}
922 explicit Impl(std::unique_ptr<php::Class> cls) : cls{std::move(cls)} {}
923 explicit Impl(std::unique_ptr<php::Unit> unit) : unit{std::move(unit)} {}
924 explicit Impl(std::unique_ptr<php::FuncBytecode> b) : funcBC{std::move(b)} {}
925 explicit Impl(std::unique_ptr<php::ClassBytecode> b) : clsBC{std::move(b)} {}
928 struct WholeProgramInput::Impl {
929 folly::AtomicLinkedList<std::pair<Key, Ref<Value>>> values;
932 WholeProgramInput::WholeProgramInput() : m_impl{new Impl} {}
934 void WholeProgramInput::add(Key k, extern_worker::Ref<Value> v) {
935 assertx(m_impl);
936 m_impl->values.insertHead(std::make_pair(std::move(k), std::move(v)));
939 std::vector<std::pair<WholeProgramInput::Key, WholeProgramInput::Value>>
940 WholeProgramInput::make(std::unique_ptr<UnitEmitter> ue) {
941 assertx(ue);
943 auto parsed = parse_unit(*ue);
945 std::vector<std::pair<Key, Value>> out;
947 using KeyI = Key::Impl;
948 using ValueI = Value::Impl;
950 auto const add = [&] (auto k, auto v) {
951 Key key;
952 Value value;
953 key.m_impl.reset(new KeyI{std::move(k)});
954 value.m_impl.reset(new ValueI{std::move(v)});
955 out.emplace_back(std::move(key), std::move(value));
958 auto const addType =
959 [&] (KeyI::UnresolvedTypes& u,
960 const TypeConstraint& tc,
961 const php::Class* cls = nullptr,
962 const TypeIntersectionConstraint* ubs = nullptr) {
963 // Skip names which match the current class name. We don't need to
964 // report these as it's implicit.
965 if (tc.isUnresolved() && (!cls || !cls->name->tsame(tc.typeName()))) {
966 u.emplace(tc.typeName());
968 if (!ubs) return;
969 for (auto const& ub : ubs->m_constraints) {
970 if (ub.isUnresolved() && (!cls || !cls->name->tsame(ub.typeName()))) {
971 u.emplace(ub.typeName());
976 auto const addFuncTypes = [&] (KeyI::UnresolvedTypes& u,
977 const php::Func& f,
978 const php::Class* cls = nullptr) {
979 for (auto const& p : f.params) {
980 addType(u, p.typeConstraint, cls, &p.upperBounds);
982 addType(u, f.retTypeConstraint, cls, &f.returnUBs);
985 if (parsed.unit) {
986 if (auto const& fi = parsed.unit->fatalInfo) {
987 auto const msg = makeStaticString(fi->fatalMsg);
988 if (!fi->fatalLoc) {
989 add(KeyI::FailInfo{msg}, nullptr);
990 return out;
994 KeyI::UnitInfo info{parsed.unit->filename};
995 for (auto const& typeAlias : parsed.unit->typeAliases) {
996 info.typeMappings.emplace_back(
997 TypeMapping{
998 typeAlias->name,
999 typeAlias->value,
1000 true,
1001 false
1005 for (auto const& cns : parsed.unit->constants) {
1006 info.constants.emplace_back(
1007 cns->name,
1008 type(cns->val) == KindOfUninit
1011 add(std::move(info), std::move(parsed.unit));
1014 auto const onCls = [&] (std::unique_ptr<php::Class>& c,
1015 php::ClassBytecode& bc,
1016 KeyI::UnresolvedTypes& types,
1017 std::vector<SString>& deps,
1018 bool& has86init) {
1019 assertx(IMPLIES(is_closure(*c), c->methods.size() == 1));
1021 for (auto& m : c->methods) {
1022 addFuncTypes(types, *m, c.get());
1023 bc.methodBCs.emplace_back(m->name, std::move(m->rawBlocks));
1024 assertx(IMPLIES(is_closure(*c), !is_86init_func(*m)));
1025 has86init |= is_86init_func(*m);
1027 for (auto const& p : c->properties) {
1028 addType(types, p.typeConstraint, c.get(), &p.ubs);
1031 auto const d = Index::Input::makeDeps(*c);
1032 deps.insert(end(deps), begin(d), end(d));
1034 assertx(IMPLIES(is_closure(*c), !(c->attrs & AttrEnum)));
1037 for (auto& c : parsed.classes) {
1038 auto const name = c->name;
1039 auto const declFunc = c->closureDeclFunc;
1040 auto const unit = c->unit;
1042 assertx(IMPLIES(is_closure(*c), !c->closureContextCls));
1043 assertx(IMPLIES(is_closure(*c), declFunc));
1045 auto has86init = false;
1046 php::ClassBytecode bc{name};
1047 KeyI::UnresolvedTypes types;
1048 std::vector<SString> deps;
1049 std::vector<SString> closures;
1051 onCls(c, bc, types, deps, has86init);
1052 for (auto& clo : c->closures) {
1053 assertx(is_closure(*clo));
1054 onCls(clo, bc, types, deps, has86init);
1055 closures.emplace_back(clo->name);
1058 Optional<TypeMapping> typeMapping;
1059 if (c->attrs & AttrEnum) {
1060 assertx(!is_closure(*c));
1061 auto tc = c->enumBaseTy;
1062 assertx(!tc.isNullable());
1063 addType(types, tc, nullptr);
1064 if (tc.isMixed()) tc.setType(AnnotType::ArrayKey);
1065 typeMapping.emplace(TypeMapping{c->name, tc, false, true});
1068 std::sort(begin(deps), end(deps), string_data_lt_type{});
1069 deps.erase(
1070 std::unique(begin(deps), end(deps), string_data_tsame{}),
1071 end(deps)
1074 std::sort(begin(closures), end(closures), string_data_lt_type{});
1076 add(
1077 KeyI::ClassBytecodeInfo{name},
1078 std::make_unique<php::ClassBytecode>(std::move(bc))
1080 add(
1081 KeyI::ClassInfo{
1082 name,
1083 declFunc,
1084 unit,
1085 std::move(closures),
1086 std::move(deps),
1087 has86init,
1088 std::move(typeMapping),
1089 std::move(types)
1091 std::move(c)
1095 for (auto& f : parsed.funcs) {
1096 auto const name = f->name;
1097 auto const unit = f->unit;
1098 auto const methCaller = bool(f->attrs & AttrIsMethCaller);
1100 KeyI::UnresolvedTypes types;
1101 addFuncTypes(types, *f);
1103 add(
1104 KeyI::FuncBytecodeInfo{name, unit, methCaller},
1105 std::make_unique<php::FuncBytecode>(name, std::move(f->rawBlocks))
1107 add(
1108 KeyI::FuncInfo{name, unit, methCaller, std::move(types)},
1109 std::move(f)
1112 return out;
1115 void WholeProgramInput::Key::serde(BlobEncoder& sd) const {
1116 assertx(m_impl);
1117 sd(*m_impl);
1120 void WholeProgramInput::Key::serde(BlobDecoder& sd) {
1121 m_impl.reset(new Impl());
1122 sd(*m_impl);
1125 void WholeProgramInput::Value::serde(BlobEncoder& sd) const {
1126 assertx(m_impl);
1127 assertx(
1128 (bool)m_impl->func + (bool)m_impl->cls + (bool)m_impl->unit +
1129 (bool)m_impl->funcBC + (bool)m_impl->clsBC <= 1
1131 if (m_impl->func) {
1132 sd(m_impl->func, nullptr);
1133 } else if (m_impl->cls) {
1134 sd(m_impl->cls);
1135 } else if (m_impl->unit) {
1136 sd(m_impl->unit);
1137 } else if (m_impl->funcBC) {
1138 sd(m_impl->funcBC);
1139 } else if (m_impl->clsBC) {
1140 sd(m_impl->clsBC);
1144 void WholeProgramInput::Key::Deleter::operator()(Impl* i) const {
1145 delete i;
1147 void WholeProgramInput::Value::Deleter::operator()(Impl* i) const {
1148 delete i;
1150 void WholeProgramInput::Deleter::operator()(Impl* i) const {
1151 delete i;
1154 //////////////////////////////////////////////////////////////////////
1156 namespace {
1158 Index::Input make_index_input(WholeProgramInput input) {
1159 Index::Input out;
1161 using WPI = WholeProgramInput;
1162 using Key = WPI::Key::Impl;
1164 input.m_impl->values.sweep(
1165 [&] (std::pair<WPI::Key, Ref<WPI::Value>>&& p) {
1166 switch (p.first.m_impl->type) {
1167 case Key::Type::None:
1168 break;
1169 case Key::Type::Fail:
1170 // An unit which failed the verifier. This causes us
1171 // to exit immediately with an error.
1172 fprintf(stderr, "%s", p.first.m_impl->fail.message->data());
1173 _Exit(HPHP_EXIT_FAILURE);
1174 break;
1175 case Key::Type::Class:
1176 out.classes.emplace_back(
1177 Index::Input::ClassMeta{
1178 p.second.cast<std::unique_ptr<php::Class>>(),
1179 p.first.m_impl->cls.name,
1180 std::move(p.first.m_impl->cls.dependencies),
1181 p.first.m_impl->cls.context,
1182 std::move(p.first.m_impl->cls.closures),
1183 p.first.m_impl->cls.unit,
1184 p.first.m_impl->cls.has86init,
1185 std::move(p.first.m_impl->cls.typeMapping),
1186 std::vector<SString>{
1187 begin(p.first.m_impl->cls.unresolvedTypes),
1188 end(p.first.m_impl->cls.unresolvedTypes)
1192 break;
1193 case Key::Type::Func:
1194 out.funcs.emplace_back(
1195 Index::Input::FuncMeta{
1196 p.second.cast<std::unique_ptr<php::Func>>(),
1197 p.first.m_impl->func.name,
1198 p.first.m_impl->func.unit,
1199 p.first.m_impl->func.methCaller,
1200 std::vector<SString>{
1201 begin(p.first.m_impl->func.unresolvedTypes),
1202 end(p.first.m_impl->func.unresolvedTypes)
1206 break;
1207 case Key::Type::Unit:
1208 out.units.emplace_back(
1209 Index::Input::UnitMeta{
1210 p.second.cast<std::unique_ptr<php::Unit>>(),
1211 p.first.m_impl->unit.name,
1212 std::move(p.first.m_impl->unit.typeMappings),
1213 std::move(p.first.m_impl->unit.constants)
1216 break;
1217 case Key::Type::FuncBytecode:
1218 out.funcBC.emplace_back(
1219 Index::Input::FuncBytecodeMeta{
1220 p.second.cast<std::unique_ptr<php::FuncBytecode>>(),
1221 p.first.m_impl->funcBC.name,
1222 p.first.m_impl->funcBC.unit,
1223 p.first.m_impl->funcBC.methCaller
1226 break;
1227 case Key::Type::ClassBytecode:
1228 out.classBC.emplace_back(
1229 Index::Input::ClassBytecodeMeta{
1230 p.second.cast<std::unique_ptr<php::ClassBytecode>>(),
1231 p.first.m_impl->clsBC.name
1234 break;
1239 return out;
1242 //////////////////////////////////////////////////////////////////////
1246 //////////////////////////////////////////////////////////////////////
1248 void whole_program(WholeProgramInput inputs,
1249 Config config,
1250 std::unique_ptr<TicketExecutor> executor,
1251 std::unique_ptr<Client> client,
1252 const EmitCallback& callback,
1253 DisposeCallback dispose,
1254 StructuredLogEntry* sample,
1255 int num_threads) {
1256 trace_time tracer("whole program", sample);
1258 if (sample) {
1259 sample->setInt("hhbbc_thread_count", executor->numThreads());
1262 if (num_threads > 0) {
1263 parallel::num_threads = num_threads;
1264 // Leave a thread free for cleanup
1265 parallel::final_threads = (num_threads > 1) ? (num_threads - 1) : 1;
1268 Index index{
1269 make_index_input(std::move(inputs)),
1270 std::move(config),
1271 std::move(executor),
1272 std::move(client),
1273 std::move(dispose),
1274 sample
1277 analyze_constants(index);
1278 index.make_local();
1280 auto stats = allocate_stats();
1281 auto const emitUnit = [&] (php::Unit& unit) {
1282 auto ue = emit_unit(index, unit);
1283 if (RO::EvalAbortBuildOnVerifyError && !ue->check(false)) {
1284 fprintf(
1285 stderr,
1286 "The optimized unit for %s did not pass verification, "
1287 "bailing because Eval.AbortBuildOnVerifyError is set\n",
1288 ue->m_filepath->data()
1290 _Exit(HPHP_EXIT_FAILURE);
1292 callback(std::move(ue));
1295 assertx(check(index.program()));
1297 // Defer preresolve type-structures and initializing public static
1298 // property types until after the constant pass, to try to get
1299 // better initial values.
1300 index.use_class_dependencies(false);
1301 index.preresolve_type_structures();
1302 index.use_class_dependencies(true);
1303 analyze_iteratively(index);
1304 auto cleanup_for_final = std::thread([&] { index.cleanup_for_final(); });
1305 parallel::num_threads = parallel::final_threads;
1306 final_pass(index, stats, emitUnit);
1307 cleanup_for_final.join();
1309 print_stats(stats);
1311 if (sample) {
1312 sample->setInt("hhbbc_num_units", index.program().units.size());
1313 sample->setInt("hhbbc_num_classes", index.program().classes.size());
1314 sample->setInt("hhbbc_num_funcs", index.program().funcs.size());
1317 index.cleanup_post_emit();
1318 summarize_memory(sample);
1321 //////////////////////////////////////////////////////////////////////