2 +----------------------------------------------------------------------+
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"
24 #include <folly/AtomicLinkedList.h>
25 #include <folly/Memory.h>
26 #include <folly/ScopeGuard.h>
29 #include <strobelight/strobemeta/strobemeta_frames.h>
31 #define SET_FRAME_METADATA(...)
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"
58 using namespace extern_worker
;
59 namespace coro
= folly::coro
;
65 //////////////////////////////////////////////////////////////////////
69 //////////////////////////////////////////////////////////////////////
71 const StaticString
s_invoke("__invoke");
73 //////////////////////////////////////////////////////////////////////
75 enum class WorkType
{ Class
, Func
};
78 explicit WorkItem(WorkType type
, Context ctx
)
88 explicit WorkResult(ClassAnalysis cls
)
89 : type(WorkType::Class
)
93 explicit WorkResult(FuncAnalysisResult func
)
94 : type(WorkType::Func
)
95 , func(std::move(func
))
98 WorkResult(WorkResult
&& wr
) noexcept
102 case WorkType::Class
:
103 new (&cls
) ClassAnalysis(std::move(wr
.cls
));
106 new (&func
) FuncAnalysisResult(std::move(wr
.func
));
111 WorkResult
& operator=(WorkResult
&& o
) noexcept
{
114 case WorkType::Class
: new (this) WorkResult(std::move(o
.cls
)); break;
115 case WorkType::Func
: new (this) WorkResult(std::move(o
.func
)); break;
122 case WorkType::Class
:
123 cls
.~ClassAnalysis();
126 func
.~FuncAnalysisResult();
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
}); }
158 // Return all the WorkItems we'll need to start analyzing this
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
) {
170 Context
{ c
->unit
, f
.get(), f
->cls
}
176 Context
{ c
->unit
, nullptr, c
.get() }
180 for (auto const& f
: program
.funcs
) {
183 Context
{ f
->unit
, f
.get() }
189 WorkItem
work_item_for(const DependencyContext
& d
,
190 const Index
& index
) {
192 case DependencyContextType::Class
: {
193 auto const cls
= (const php::Class
*)d
.ptr();
194 assertx(!is_used_trait(*cls
));
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
)
205 assertx(!cls
|| is_used_trait(*cls
));
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.
217 always_assert(false);
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
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};
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()) {
265 folly::sformat("round {} -- {} work items", round
, work
.size())
267 return parallel::map(
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());
274 case WorkType::Func
: {
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
: {
283 IndexAdaptor adaptor
{ index
};
284 return WorkResult
{ analyze_class(adaptor
, wi
.ctx
) };
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(
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());
323 "Failed to find __invoke on {} during index update\n",
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
,
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
);
351 [&] (auto& result
, size_t worker
) {
352 assertx(worker
< deps_vec
.size());
353 switch (result
->type
) {
355 update_func(result
->func
, deps_vec
[worker
]);
357 case WorkType::Class
:
358 update_class(result
->cls
, deps_vec
[worker
]);
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
);
374 auto& deps
= deps_vec
[0];
376 index
.refine_public_statics(deps
);
379 work
.reserve(deps
.size());
380 for (auto& d
: deps
) work
.emplace_back(work_item_for(d
, index
));
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
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.
400 void final_pass(Index
& index
,
401 const StatsHolder
& stats
,
403 trace_time
final_pass("final pass", index
.sample());
405 auto const dump_dir
= debug_dump_to();
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
);
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
,
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
;
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
),
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.
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
495 return index
.finish();
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
);
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
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.
628 auto outputs
= co_await index
.client().exec(
629 s_analyzeConstantsJob
,
631 singleton_vec(std::move(tuple
)),
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
));
655 [&] (SString n
) { return meta
.removedFuncs
.count(n
); }
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
674 std::move(classNames
),
675 std::move(cinfoNames
),
676 std::move(minfoNames
),
678 std::move(clsBCRefs
),
679 std::move(cinfoRefs
),
680 std::move(funcNames
),
682 std::move(funcBCRefs
),
683 std::move(finfoRefs
),
684 std::move(minfoRefs
),
685 std::move(unitNames
),
694 while (auto const workItems
= scheduler
.workItems()) {
696 "analyze constants round",
697 folly::sformat("round {} -- {} work items", round
, workItems
)
699 // Get the work buckets from the scheduler.
702 "analyze constants schedule",
703 folly::sformat("round {}", round
)
705 trace2
.ignore_client_stats();
706 return scheduler
.schedule(kBucketSize
, kMaxBucketSize
);
708 // Work shouldn't be empty because we add non-zero work items this
710 assertx(!work
.empty());
713 // Process the work buckets in individual analyze constants
714 // jobs. These will record their results as each one finishes.
716 "analyze constants run",
717 folly::sformat("round {}", round
)
719 trace2
.ignore_client_stats();
721 CoroLatch latch
{work
.size()};
722 coro::blockingWait(coro::collectAllRange(
725 | map([&] (AnalysisInput
&& input
) {
726 return run(std::move(input
), latch
)
727 .scheduleOn(index
.executor().sticky());
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.
738 "analyze constants deps",
739 folly::sformat("round {}", round
)
741 trace2
.ignore_client_stats();
742 scheduler
.recordingDone();
748 //////////////////////////////////////////////////////////////////////
752 //////////////////////////////////////////////////////////////////////
754 struct WholeProgramInput::Key::Impl
{
765 using UnresolvedTypes
=
766 hphp_fast_set
<SString
, string_data_hash
, string_data_tsame
>;
770 template <typename SerDe
> void serde(SerDe
& sd
) { sd(message
); }
774 std::vector
<TypeMapping
> typeMappings
;
775 std::vector
<std::pair
<SString
, bool>> constants
;
776 template <typename SerDe
> void serde(SerDe
& sd
) {
787 UnresolvedTypes unresolvedTypes
;
788 template <typename SerDe
> void serde(SerDe
& sd
) {
792 (unresolvedTypes
, string_data_lt_type
{})
796 struct FuncBytecodeInfo
{
800 template <typename SerDe
> void serde(SerDe
& sd
) {
801 sd(name
)(unit
)(methCaller
);
808 std::vector
<SString
> closures
;
809 std::vector
<SString
> dependencies
;
811 Optional
<TypeMapping
> typeMapping
;
812 UnresolvedTypes unresolvedTypes
;
813 template <typename SerDe
> void serde(SerDe
& sd
) {
821 (unresolvedTypes
, string_data_lt_type
{})
825 struct ClassBytecodeInfo
{
827 template <typename SerDe
> void serde(SerDe
& sd
) {
837 FuncBytecodeInfo funcBC
;
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;
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();
868 case Type::ClassBytecode
:
869 clsBC
.~ClassBytecodeInfo();
874 ~Impl() { destroyInfo(); }
876 template <typename SerDe
> void serde(SerDe
& sd
) {
877 if constexpr (SerDe::deserializing
) {
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();
889 case Type::ClassBytecode
:
890 new (&clsBC
) ClassBytecodeInfo();
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
:
906 case Type::ClassBytecode
:
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
) {
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
) {
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
) {
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
));
959 [&] (KeyI::UnresolvedTypes
& u
,
960 const folly::Range
<const TypeConstraint
*>& tcs
,
961 const php::Class
* cls
= nullptr) {
962 // Skip names which match the current class name. We don't need to
963 // report these as it's implicit.
964 for (auto const& tc
: tcs
) {
965 if (tc
.isUnresolved() && (!cls
|| !cls
->name
->tsame(tc
.typeName()))) {
966 u
.emplace(tc
.typeName());
971 auto const addFuncTypes
= [&] (KeyI::UnresolvedTypes
& u
,
973 const php::Class
* cls
= nullptr) {
974 for (auto const& p
: f
.params
) {
975 addType(u
, p
.typeConstraints
.range(), cls
);
977 addType(u
, f
.retTypeConstraints
.range(), cls
);
981 if (auto const& fi
= parsed
.unit
->fatalInfo
) {
982 auto const msg
= makeStaticString(fi
->fatalMsg
);
984 add(KeyI::FailInfo
{msg
}, nullptr);
989 KeyI::UnitInfo info
{parsed
.unit
->filename
};
990 for (auto const& typeAlias
: parsed
.unit
->typeAliases
) {
991 info
.typeMappings
.emplace_back(
1000 for (auto const& cns
: parsed
.unit
->constants
) {
1001 info
.constants
.emplace_back(
1003 type(cns
->val
) == KindOfUninit
1006 add(std::move(info
), std::move(parsed
.unit
));
1009 auto const onCls
= [&] (std::unique_ptr
<php::Class
>& c
,
1010 php::ClassBytecode
& bc
,
1011 KeyI::UnresolvedTypes
& types
,
1012 std::vector
<SString
>& deps
,
1014 assertx(IMPLIES(is_closure(*c
), c
->methods
.size() == 1));
1016 for (auto& m
: c
->methods
) {
1017 addFuncTypes(types
, *m
, c
.get());
1018 bc
.methodBCs
.emplace_back(m
->name
, std::move(m
->rawBlocks
));
1019 assertx(IMPLIES(is_closure(*c
), !is_86init_func(*m
)));
1020 has86init
|= is_86init_func(*m
);
1022 for (auto const& p
: c
->properties
) {
1023 addType(types
, p
.typeConstraints
.range(), c
.get());
1026 auto const d
= Index::Input::makeDeps(*c
);
1027 deps
.insert(end(deps
), begin(d
), end(d
));
1029 assertx(IMPLIES(is_closure(*c
), !(c
->attrs
& AttrEnum
)));
1032 for (auto& c
: parsed
.classes
) {
1033 auto const name
= c
->name
;
1034 auto const declFunc
= c
->closureDeclFunc
;
1035 auto const unit
= c
->unit
;
1037 assertx(IMPLIES(is_closure(*c
), !c
->closureContextCls
));
1038 assertx(IMPLIES(is_closure(*c
), declFunc
));
1040 auto has86init
= false;
1041 php::ClassBytecode bc
{name
};
1042 KeyI::UnresolvedTypes types
;
1043 std::vector
<SString
> deps
;
1044 std::vector
<SString
> closures
;
1046 onCls(c
, bc
, types
, deps
, has86init
);
1047 for (auto& clo
: c
->closures
) {
1048 assertx(is_closure(*clo
));
1049 onCls(clo
, bc
, types
, deps
, has86init
);
1050 closures
.emplace_back(clo
->name
);
1053 Optional
<TypeMapping
> typeMapping
;
1054 if (c
->attrs
& AttrEnum
) {
1055 assertx(!is_closure(*c
));
1056 auto tc
= c
->enumBaseTy
;
1057 assertx(!tc
.isNullable());
1058 addType(types
, folly::Range
<const TypeConstraint
*>(&tc
, &tc
+ 1), nullptr);
1059 if (tc
.isMixed()) tc
.setType(AnnotType::ArrayKey
);
1060 typeMapping
.emplace(TypeMapping
{c
->name
, tc
, false, true});
1063 std::sort(begin(deps
), end(deps
), string_data_lt_type
{});
1065 std::unique(begin(deps
), end(deps
), string_data_tsame
{}),
1069 std::sort(begin(closures
), end(closures
), string_data_lt_type
{});
1072 KeyI::ClassBytecodeInfo
{name
},
1073 std::make_unique
<php::ClassBytecode
>(std::move(bc
))
1080 std::move(closures
),
1083 std::move(typeMapping
),
1090 for (auto& f
: parsed
.funcs
) {
1091 auto const name
= f
->name
;
1092 auto const unit
= f
->unit
;
1093 auto const methCaller
= bool(f
->attrs
& AttrIsMethCaller
);
1095 KeyI::UnresolvedTypes types
;
1096 addFuncTypes(types
, *f
);
1099 KeyI::FuncBytecodeInfo
{name
, unit
, methCaller
},
1100 std::make_unique
<php::FuncBytecode
>(name
, std::move(f
->rawBlocks
))
1103 KeyI::FuncInfo
{name
, unit
, methCaller
, std::move(types
)},
1110 void WholeProgramInput::Key::serde(BlobEncoder
& sd
) const {
1115 void WholeProgramInput::Key::serde(BlobDecoder
& sd
) {
1116 m_impl
.reset(new Impl());
1120 void WholeProgramInput::Value::serde(BlobEncoder
& sd
) const {
1123 (bool)m_impl
->func
+ (bool)m_impl
->cls
+ (bool)m_impl
->unit
+
1124 (bool)m_impl
->funcBC
+ (bool)m_impl
->clsBC
<= 1
1127 sd(m_impl
->func
, nullptr);
1128 } else if (m_impl
->cls
) {
1130 } else if (m_impl
->unit
) {
1132 } else if (m_impl
->funcBC
) {
1134 } else if (m_impl
->clsBC
) {
1139 void WholeProgramInput::Key::Deleter::operator()(Impl
* i
) const {
1142 void WholeProgramInput::Value::Deleter::operator()(Impl
* i
) const {
1145 void WholeProgramInput::Deleter::operator()(Impl
* i
) const {
1149 //////////////////////////////////////////////////////////////////////
1153 Index::Input
make_index_input(WholeProgramInput input
) {
1156 using WPI
= WholeProgramInput
;
1157 using Key
= WPI::Key::Impl
;
1159 input
.m_impl
->values
.sweep(
1160 [&] (std::pair
<WPI::Key
, Ref
<WPI::Value
>>&& p
) {
1161 switch (p
.first
.m_impl
->type
) {
1162 case Key::Type::None
:
1164 case Key::Type::Fail
:
1165 // An unit which failed the verifier. This causes us
1166 // to exit immediately with an error.
1167 fprintf(stderr
, "%s", p
.first
.m_impl
->fail
.message
->data());
1168 _Exit(HPHP_EXIT_FAILURE
);
1170 case Key::Type::Class
:
1171 out
.classes
.emplace_back(
1172 Index::Input::ClassMeta
{
1173 p
.second
.cast
<std::unique_ptr
<php::Class
>>(),
1174 p
.first
.m_impl
->cls
.name
,
1175 std::move(p
.first
.m_impl
->cls
.dependencies
),
1176 p
.first
.m_impl
->cls
.context
,
1177 std::move(p
.first
.m_impl
->cls
.closures
),
1178 p
.first
.m_impl
->cls
.unit
,
1179 p
.first
.m_impl
->cls
.has86init
,
1180 std::move(p
.first
.m_impl
->cls
.typeMapping
),
1181 std::vector
<SString
>{
1182 begin(p
.first
.m_impl
->cls
.unresolvedTypes
),
1183 end(p
.first
.m_impl
->cls
.unresolvedTypes
)
1188 case Key::Type::Func
:
1189 out
.funcs
.emplace_back(
1190 Index::Input::FuncMeta
{
1191 p
.second
.cast
<std::unique_ptr
<php::Func
>>(),
1192 p
.first
.m_impl
->func
.name
,
1193 p
.first
.m_impl
->func
.unit
,
1194 p
.first
.m_impl
->func
.methCaller
,
1195 std::vector
<SString
>{
1196 begin(p
.first
.m_impl
->func
.unresolvedTypes
),
1197 end(p
.first
.m_impl
->func
.unresolvedTypes
)
1202 case Key::Type::Unit
:
1203 out
.units
.emplace_back(
1204 Index::Input::UnitMeta
{
1205 p
.second
.cast
<std::unique_ptr
<php::Unit
>>(),
1206 p
.first
.m_impl
->unit
.name
,
1207 std::move(p
.first
.m_impl
->unit
.typeMappings
),
1208 std::move(p
.first
.m_impl
->unit
.constants
)
1212 case Key::Type::FuncBytecode
:
1213 out
.funcBC
.emplace_back(
1214 Index::Input::FuncBytecodeMeta
{
1215 p
.second
.cast
<std::unique_ptr
<php::FuncBytecode
>>(),
1216 p
.first
.m_impl
->funcBC
.name
,
1217 p
.first
.m_impl
->funcBC
.unit
,
1218 p
.first
.m_impl
->funcBC
.methCaller
1222 case Key::Type::ClassBytecode
:
1223 out
.classBC
.emplace_back(
1224 Index::Input::ClassBytecodeMeta
{
1225 p
.second
.cast
<std::unique_ptr
<php::ClassBytecode
>>(),
1226 p
.first
.m_impl
->clsBC
.name
1237 //////////////////////////////////////////////////////////////////////
1241 //////////////////////////////////////////////////////////////////////
1243 void whole_program(WholeProgramInput inputs
,
1245 std::unique_ptr
<TicketExecutor
> executor
,
1246 std::unique_ptr
<Client
> client
,
1247 const EmitCallback
& callback
,
1248 DisposeCallback dispose
,
1249 StructuredLogEntry
* sample
,
1251 trace_time
tracer("whole program", sample
);
1254 sample
->setInt("hhbbc_thread_count", executor
->numThreads());
1257 if (num_threads
> 0) {
1258 parallel::num_threads
= num_threads
;
1259 // Leave a thread free for cleanup
1260 parallel::final_threads
= (num_threads
> 1) ? (num_threads
- 1) : 1;
1264 make_index_input(std::move(inputs
)),
1266 std::move(executor
),
1272 analyze_constants(index
);
1275 auto stats
= allocate_stats();
1276 auto const emitUnit
= [&] (php::Unit
& unit
) {
1277 auto ue
= emit_unit(index
, unit
);
1278 if (Cfg::Eval::AbortBuildOnVerifyError
&& !ue
->check(false)) {
1281 "The optimized unit for %s did not pass verification, "
1282 "bailing because Eval.AbortBuildOnVerifyError is set\n",
1283 ue
->m_filepath
->data()
1285 _Exit(HPHP_EXIT_FAILURE
);
1287 callback(std::move(ue
));
1290 assertx(check(index
.program()));
1292 // Defer preresolve type-structures and initializing public static
1293 // property types until after the constant pass, to try to get
1294 // better initial values.
1295 index
.use_class_dependencies(false);
1296 index
.preresolve_type_structures();
1297 index
.use_class_dependencies(true);
1298 analyze_iteratively(index
);
1299 auto cleanup_for_final
= std::thread([&] { index
.cleanup_for_final(); });
1300 parallel::num_threads
= parallel::final_threads
;
1301 final_pass(index
, stats
, emitUnit
);
1302 cleanup_for_final
.join();
1307 sample
->setInt("hhbbc_num_units", index
.program().units
.size());
1308 sample
->setInt("hhbbc_num_classes", index
.program().classes
.size());
1309 sample
->setInt("hhbbc_num_funcs", index
.program().funcs
.size());
1312 index
.cleanup_post_emit();
1313 summarize_memory(sample
);
1316 //////////////////////////////////////////////////////////////////////