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/Memory.h>
25 #include <folly/ScopeGuard.h>
27 #include "hphp/runtime/vm/unit-emitter.h"
29 #include "hphp/hhbbc/analyze.h"
30 #include "hphp/hhbbc/class-util.h"
31 #include "hphp/hhbbc/debug.h"
32 #include "hphp/hhbbc/emit.h"
33 #include "hphp/hhbbc/func-util.h"
34 #include "hphp/hhbbc/index.h"
35 #include "hphp/hhbbc/optimize.h"
36 #include "hphp/hhbbc/options.h"
37 #include "hphp/hhbbc/parallel.h"
38 #include "hphp/hhbbc/parse.h"
39 #include "hphp/hhbbc/representation.h"
40 #include "hphp/hhbbc/stats.h"
41 #include "hphp/hhbbc/type-system.h"
43 namespace HPHP
{ namespace HHBBC
{
47 //////////////////////////////////////////////////////////////////////
51 //////////////////////////////////////////////////////////////////////
53 const StaticString
s_invoke("__invoke");
55 //////////////////////////////////////////////////////////////////////
57 enum class AnalyzeMode
{ NormalPass
, ConstPass
};
58 enum class WorkType
{ Class
, Func
};
61 explicit WorkItem(WorkType type
, Context ctx
)
66 bool operator<(const WorkItem
& o
) const {
67 return type
< o
.type
? true :
68 o
.type
< type
? false :
72 bool operator==(const WorkItem
& o
) const {
73 return type
== o
.type
&& ctx
== o
.ctx
;
81 explicit WorkResult(ClassAnalysis cls
)
82 : type(WorkType::Class
)
86 explicit WorkResult(FuncAnalysisResult func
)
87 : type(WorkType::Func
)
88 , func(std::move(func
))
91 WorkResult(WorkResult
&& wr
) noexcept
96 new (&cls
) ClassAnalysis(std::move(wr
.cls
));
99 new (&func
) FuncAnalysisResult(std::move(wr
.func
));
104 WorkResult
& operator=(WorkResult
&& o
) noexcept
{
107 case WorkType::Class
: new (this) WorkResult(std::move(o
.cls
)); break;
108 case WorkType::Func
: new (this) WorkResult(std::move(o
.func
)); break;
115 case WorkType::Class
:
116 cls
.~ClassAnalysis();
119 func
.~FuncAnalysisResult();
127 FuncAnalysisResult func
;
131 //////////////////////////////////////////////////////////////////////
134 void all_unit_contexts(const php::Unit
* u
, F
&& fun
) {
135 for (auto& c
: u
->classes
) {
136 for (auto& m
: c
->methods
) {
137 fun(Context
{ u
, m
.get(), c
.get()});
140 for (auto& f
: u
->funcs
) {
141 fun(Context
{ u
, f
.get() });
143 if (options
.AnalyzePseudomains
) {
144 fun(Context
{ u
, u
->pseudomain
.get() });
148 std::vector
<Context
> const_pass_contexts(const php::Program
& program
) {
149 std::vector
<Context
> ret
;
150 ret
.reserve(program
.constInits
.size());
151 for (auto func
: program
.constInits
) {
153 ret
.push_back(Context
{ func
->unit
, func
, func
->cls
});
158 // Return all the WorkItems we'll need to start analyzing this
160 std::vector
<WorkItem
> initial_work(const php::Program
& program
,
162 std::vector
<WorkItem
> ret
;
164 if (mode
== AnalyzeMode::ConstPass
) {
165 auto const ctxs
= const_pass_contexts(program
);
166 std::transform(begin(ctxs
), end(ctxs
), std::back_inserter(ret
),
167 [&] (Context ctx
) { return WorkItem
{ WorkType::Func
, ctx
}; }
172 for (auto& u
: program
.units
) {
174 * If we're not doing private property inference, schedule only
175 * function-at-a-time work items.
177 if (!options
.HardPrivatePropInference
) {
178 all_unit_contexts(u
.get(), [&] (Context
&& c
) {
179 ret
.emplace_back(WorkType::Func
, std::move(c
));
185 for (auto& c
: u
->classes
) {
186 if (c
->closureContextCls
) {
187 // For class-at-a-time analysis, closures that are associated
188 // with a class context are analyzed as part of that context.
191 if (is_used_trait(*c
)) {
192 for (auto& f
: c
->methods
) {
193 ret
.emplace_back(WorkType::Func
,
194 Context
{ u
.get(), f
.get(), f
->cls
});
197 ret
.emplace_back(WorkType::Class
,
198 Context
{ u
.get(), nullptr, c
.get() });
201 for (auto& f
: u
->funcs
) {
202 ret
.emplace_back(WorkType::Func
, Context
{ u
.get(), f
.get() });
204 if (options
.AnalyzePseudomains
) {
207 Context
{ u
.get(), u
->pseudomain
.get() }
214 std::vector
<Context
> opt_prop_type_hints_contexts(const php::Program
& program
) {
215 std::vector
<Context
> ret
;
216 for (auto& u
: program
.units
) {
217 for (auto& c
: u
->classes
) {
218 ret
.emplace_back(Context
{ u
.get(), nullptr, c
.get() });
224 WorkItem
work_item_for(const DependencyContext
& d
, AnalyzeMode mode
) {
226 case DependencyContextType::Class
: {
227 auto const cls
= (const php::Class
*)d
.ptr();
228 assertx(mode
!= AnalyzeMode::ConstPass
&&
229 options
.HardPrivatePropInference
&&
230 !is_used_trait(*cls
));
231 return WorkItem
{ WorkType::Class
, Context
{ cls
->unit
, nullptr, cls
} };
233 case DependencyContextType::Func
: {
234 auto const func
= (const php::Func
*)d
.ptr();
235 auto const cls
= !func
->cls
? nullptr :
236 func
->cls
->closureContextCls
?
237 func
->cls
->closureContextCls
: func
->cls
;
239 mode
== AnalyzeMode::ConstPass
||
240 !options
.HardPrivatePropInference
||
241 is_used_trait(*cls
));
244 Context
{ func
->unit
, const_cast<php::Func
*>(func
), cls
}
247 case DependencyContextType::PropName
:
248 // We only record dependencies on static property names. We don't schedule
249 // any work on their behalf.
252 always_assert(false);
258 * Start by running an analyze pass on every class or free function.
259 * During analysis, information about functions or classes will be
260 * requested from the Index, which initially won't really know much,
261 * but will record a dependency. This part is done in parallel: no
262 * passes are mutating anything, just reading from the Index.
264 * After a pass, we do a single-threaded "update" step to prepare
265 * for the next pass: for each function or class that was analyzed,
266 * note the facts we learned that may aid analyzing other functions
267 * in the program, and register them in the index.
269 * If any of these facts are more useful than they used to be, add
270 * all the Contexts that had a dependency on the new information to
271 * the work list again, in case they can do better based on the new
272 * fact. (This only applies to function analysis information right
275 * Repeat until the work list is empty.
278 void analyze_iteratively(Index
& index
, php::Program
& program
,
280 trace_time
tracer(mode
== AnalyzeMode::ConstPass
?
281 "analyze constants" : "analyze iteratively");
283 // Counters, just for debug printing.
284 std::atomic
<uint32_t> total_funcs
{0};
285 std::atomic
<uint32_t> total_classes
{0};
286 auto round
= uint32_t{0};
289 if (Trace::moduleEnabledRelease(Trace::hhbbc_time
, 1)) {
290 Trace::traceRelease("total class visits %u\n", total_classes
.load());
291 Trace::traceRelease("total function visits %u\n", total_funcs
.load());
295 auto work
= initial_work(program
, mode
);
296 while (!work
.empty()) {
300 folly::format("round {} -- {} work items", round
, work
.size()).str()
302 return parallel::map(
304 // We have a folly::Optional just to keep the result type
305 // DefaultConstructible.
306 [&] (const WorkItem
& wi
) -> folly::Optional
<WorkResult
> {
311 analyze_func(index
, wi
.ctx
, CollectionOpts
{})
313 case WorkType::Class
:
315 return WorkResult
{ analyze_class(index
, wi
.ctx
) };
323 trace_time
update_time("updating");
325 std::vector
<DependencyContextSet
> deps_vec
{parallel::num_threads
};
327 auto update_func
= [&] (FuncAnalysisResult
& fa
,
328 DependencyContextSet
& deps
) {
329 SCOPE_ASSERT_DETAIL("update_func") {
330 return "Updating Func: " + show(fa
.ctx
);
332 index
.refine_return_info(fa
, deps
);
333 index
.refine_constants(fa
, deps
);
334 update_bytecode(fa
.ctx
.func
, std::move(fa
.blockUpdates
));
336 if (options
.AnalyzePublicStatics
&& mode
== AnalyzeMode::NormalPass
) {
337 index
.record_public_static_mutations(
339 std::move(fa
.publicSPropMutations
)
343 if (fa
.resolvedConstants
.size()) {
344 index
.refine_class_constants(fa
.ctx
,
345 fa
.resolvedConstants
,
348 for (auto& kv
: fa
.closureUseTypes
) {
349 assert(is_closure(*kv
.first
));
350 if (index
.refine_closure_use_vars(kv
.first
, kv
.second
)) {
351 auto const func
= find_method(kv
.first
, s_invoke
.get());
354 "Failed to find __invoke on {} during index update\n",
355 kv
.first
->name
->data()
357 auto const ctx
= Context
{ func
->unit
, func
, kv
.first
};
358 deps
.insert(index
.dependency_context(ctx
));
363 auto update_class
= [&] (ClassAnalysis
& ca
,
364 DependencyContextSet
& deps
) {
366 SCOPE_ASSERT_DETAIL("update_class") {
367 return "Updating Class: " + show(ca
.ctx
);
369 index
.refine_private_props(ca
.ctx
.cls
,
370 ca
.privateProperties
);
371 index
.refine_private_statics(ca
.ctx
.cls
,
373 index
.refine_bad_initial_prop_values(ca
.ctx
.cls
,
374 ca
.badPropInitialValues
,
377 for (auto& fa
: ca
.methods
) update_func(fa
, deps
);
378 for (auto& fa
: ca
.closures
) update_func(fa
, deps
);
383 [&] (auto& result
, size_t worker
) {
384 assertx(worker
< deps_vec
.size());
385 switch (result
->type
) {
387 update_func(result
->func
, deps_vec
[worker
]);
389 case WorkType::Class
:
390 update_class(result
->cls
, deps_vec
[worker
]);
397 trace_time
_("merging deps");
398 for (auto& deps
: deps_vec
) {
399 if (&deps
== &deps_vec
[0]) continue;
400 for (auto& d
: deps
) deps_vec
[0].insert(d
);
405 auto& deps
= deps_vec
[0];
407 if (options
.AnalyzePublicStatics
&& mode
== AnalyzeMode::NormalPass
) {
408 index
.refine_public_statics(deps
);
411 index
.update_class_aliases();
413 work
.reserve(deps
.size());
414 for (auto& d
: deps
) work
.push_back(work_item_for(d
, mode
));
418 void constant_pass(Index
& index
, php::Program
& program
) {
419 if (!options
.HardConstProp
) return;
420 index
.use_class_dependencies(false);
421 analyze_iteratively(index
, program
, AnalyzeMode::ConstPass
);
424 void prop_type_hint_pass(Index
& index
, php::Program
& program
) {
425 trace_time
tracer("optimize prop type-hints");
427 auto const contexts
= opt_prop_type_hints_contexts(program
);
430 [&] (Context ctx
) { optimize_class_prop_type_hints(index
, ctx
); }
436 index
.mark_no_bad_redeclare_props(const_cast<php::Class
&>(*ctx
.cls
));
442 * Finally, use the results of all these iterations to perform
443 * optimization. This reanalyzes every function using our
444 * now-very-updated Index, and then runs optimize_func with the
447 * We do this in parallel: all the shared information is queried out
448 * of the index, and each thread is allowed to modify the bytecode
449 * for the function it is looking at.
451 * NOTE: currently they can't modify anything other than the
452 * bytecode/Blocks, because other threads may be doing unlocked
453 * queries to php::Func and php::Class structures.
456 void final_pass(Index
& index
,
457 php::Program
& program
,
458 const StatsHolder
& stats
,
460 trace_time
final_pass("final pass");
463 LitstrTable::get().setWriting();
465 auto const dump_dir
= debug_dump_to();
468 [&] (std::unique_ptr
<php::Unit
>& unit
) {
469 // optimize_func can remove 86*init methods from classes, so we
470 // have to save the contexts for now.
471 std::vector
<Context
> contexts
;
472 all_unit_contexts(unit
.get(), [&] (Context
&& ctx
) {
473 contexts
.push_back(std::move(ctx
));
476 for (auto const& ctx
: contexts
) {
478 analyze_func(index
, ctx
, CollectionOpts
{}),
481 assert(check(*unit
));
482 state_after("optimize", *unit
);
483 if (!dump_dir
.empty()) {
484 if (Trace::moduleEnabledRelease(Trace::hhbbc_dump
, 2)) {
485 dump_representation(dump_dir
, unit
.get());
487 dump_index(dump_dir
, index
, unit
.get());
489 collect_stats(stats
, index
, unit
.get());
495 //////////////////////////////////////////////////////////////////////
499 //////////////////////////////////////////////////////////////////////
501 void UnitEmitterQueue::push(std::unique_ptr
<UnitEmitter
> ue
) {
502 assertx(!m_done
.load(std::memory_order_relaxed
));
505 m_done
.store(true, std::memory_order_relaxed
);
507 m_ues
.push_back(std::move(ue
));
512 std::unique_ptr
<UnitEmitter
> UnitEmitterQueue::pop() {
514 while (m_ues
.empty()) {
515 if (m_done
.load(std::memory_order_relaxed
)) return nullptr;
518 assertx(m_ues
.size() > 0);
519 auto ue
= std::move(m_ues
.front());
520 assertx(ue
!= nullptr);
525 void UnitEmitterQueue::fetch(std::vector
<std::unique_ptr
<UnitEmitter
>>& ues
) {
526 assertx(m_done
.load(std::memory_order_relaxed
));
527 std::move(m_ues
.begin(), m_ues
.end(), std::back_inserter(ues
));
531 void UnitEmitterQueue::reset() {
533 m_done
.store(false, std::memory_order_relaxed
);
536 void hard_constprop(bool f
) {
537 options
.HardConstProp
= f
;
540 //////////////////////////////////////////////////////////////////////
544 void ProgramPtr::clear() { delete m_program
; }
548 php::ProgramPtr
make_program() {
549 return php::ProgramPtr
{ new php::Program
};
552 void add_unit_to_program(const UnitEmitter
* ue
, php::Program
& program
) {
553 parse_unit(program
, ue
);
556 void whole_program(php::ProgramPtr program
,
557 UnitEmitterQueue
& ueq
,
558 std::unique_ptr
<ArrayTypeTable::Builder
>& arrTable
,
560 trace_time
tracer("whole program");
562 RuntimeOption::EvalLowStaticArrays
= false;
564 if (num_threads
> 0) {
565 parallel::num_threads
= num_threads
;
568 state_after("parse", *program
);
570 folly::Optional
<Index
> index
;
571 index
.emplace(program
.get());
572 auto stats
= allocate_stats();
573 auto freeFuncMem
= [&] (php::Func
* fun
) {
576 auto emitUnit
= [&] (php::Unit
& unit
) {
577 auto ue
= emit_unit(*index
, unit
);
578 if (RuntimeOption::EvalAbortBuildOnVerifyError
&& !ue
->check(false)) {
581 "The optimized unit for %s did not pass verification, "
582 "bailing because Eval.AbortBuildOnVerifyError is set\n",
583 ue
->m_filepath
->data()
587 ueq
.push(std::move(ue
));
588 for (auto& c
: unit
.classes
) {
589 for (auto& m
: c
->methods
) {
590 freeFuncMem(m
.get());
593 for (auto& f
: unit
.funcs
) {
594 freeFuncMem(f
.get());
596 freeFuncMem(unit
.pseudomain
.get());
599 std::thread cleanup_pre
;
600 if (!options
.NoOptimizations
) {
603 assert(check(*program
));
604 prop_type_hint_pass(*index
, *program
);
605 index
->rewrite_default_initial_values(*program
);
606 constant_pass(*index
, *program
);
607 // Defer initializing public static property types until after the
608 // constant pass, to try to get better initial values.
609 index
->init_public_static_prop_types();
610 index
->use_class_dependencies(options
.HardPrivatePropInference
);
611 analyze_iteratively(*index
, *program
, AnalyzeMode::NormalPass
);
613 } catch (Index::rebuild
& rebuild
) {
614 FTRACE(1, "whole_program: rebuilding index\n");
615 index
.emplace(program
.get(), &rebuild
);
619 cleanup_pre
= std::thread([&] { index
->cleanup_for_final(); });
620 index
->mark_persistent_types_and_functions(*program
);
621 index
->join_iface_vtable_thread();
622 if (parallel::num_threads
> parallel::final_threads
) {
623 parallel::num_threads
= parallel::final_threads
;
625 final_pass(*index
, *program
, stats
, emitUnit
);
627 debug_dump_program(*index
, *program
);
628 index
->join_iface_vtable_thread();
631 [&] (const std::unique_ptr
<php::Unit
>& unit
) {
632 collect_stats(stats
, *index
, unit
.get());
638 auto const logging
= Trace::moduleEnabledRelease(Trace::hhbbc_time
, 1);
639 // running cleanup_for_emit can take a while... start it as early as
640 // possible, and run in its own thread.
641 auto cleanup_post
= std::thread([&] {
643 logging
&& !Trace::moduleEnabledRelease(Trace::hhbbc_time
, 1);
644 Trace::BumpRelease
bumper(Trace::hhbbc_time
, -1, enable
);
645 index
->cleanup_post_emit();
651 arrTable
= std::move(index
->array_table_builder());
657 //////////////////////////////////////////////////////////////////////