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/stats.h"
26 #include <type_traits>
28 #include <boost/variant.hpp>
30 #include <tbb/concurrent_hash_map.h>
32 #include <folly/Conv.h>
33 #include <folly/String.h>
34 #include <folly/Format.h>
35 #include <folly/ScopeGuard.h>
36 #include <folly/portability/Stdlib.h>
38 #include "hphp/runtime/vm/hhbc.h"
40 #include "hphp/util/trace.h"
42 #include "hphp/hhbbc/analyze.h"
43 #include "hphp/hhbbc/func-util.h"
44 #include "hphp/hhbbc/index.h"
45 #include "hphp/hhbbc/interp-internal.h"
46 #include "hphp/hhbbc/interp.h"
47 #include "hphp/hhbbc/context.h"
48 #include "hphp/hhbbc/misc.h"
49 #include "hphp/hhbbc/parallel.h"
50 #include "hphp/hhbbc/representation.h"
52 namespace HPHP
{ namespace HHBBC
{
54 //////////////////////////////////////////////////////////////////////
58 TRACE_SET_MOD(hhbbc_stats
);
60 //////////////////////////////////////////////////////////////////////
82 #define X(x) std::atomic<uint64_t> sub_##x; \
83 std::atomic<uint64_t> eq_##x;
89 bool equal(SString s1
, SString s2
) const {
93 size_t hash(SString s
) const {
99 * Information about builtins usage.
101 * The tuple contains the known return type for the builtin, the total
102 * number of calls seen and the total number of calls that could be
103 * reduced. A builtin call is considered reducible if its output is a
104 * constant and all its inputs are constants. That is not a guaranteed
105 * condition but it gives us an idea of what's possible.
107 using BuiltinInfo
= tbb::concurrent_hash_map
<
109 std::tuple
<Type
,uint64_t,uint64_t>,
114 std::atomic
<uint64_t> totalBuiltins
;
115 std::atomic
<uint64_t> reducibleBuiltins
;
116 BuiltinInfo builtinsInfo
{};
120 constexpr uint32_t kNumRATTags
= REPO_AUTH_TYPE_TAGS
0 ;
126 std::array
<std::atomic
<uint64_t>,Op_count
> op_counts
;
127 std::array
<std::atomic
<uint64_t>,kNumRATTags
> ratL_tags
;
128 std::array
<std::atomic
<uint64_t>,kNumRATTags
> ratStk_tags
;
129 std::atomic
<uint64_t> ratL_specialized_array
;
130 std::atomic
<uint64_t> ratStk_specialized_array
;
131 std::atomic
<uint64_t> persistentClasses
;
132 std::atomic
<uint64_t> persistentFunctions
;
133 std::atomic
<uint64_t> uniqueClasses
;
134 std::atomic
<uint64_t> uniqueFunctions
;
135 std::atomic
<uint64_t> totalClasses
;
136 std::atomic
<uint64_t> totalFunctions
;
137 std::atomic
<uint64_t> totalPseudoMains
;
138 std::atomic
<uint64_t> totalMethods
;
139 std::atomic
<uint64_t> persistentSPropsPub
;
140 std::atomic
<uint64_t> persistentSPropsProt
;
141 std::atomic
<uint64_t> persistentSPropsPriv
;
142 std::atomic
<uint64_t> totalSProps
;
144 TypeStat privateProps
;
145 TypeStat privateStatics
;
147 TypeStat iterInitBase
;
148 TypeStat iterInitKBase
;
154 void type_stat_string(std::string
& ret
,
155 const std::string
& prefix
,
156 const TypeStat
& st
) {
158 folly::format(&ret, " {}_=_{: <9} {: >8}\n", \
159 prefix, #x ":", st.eq_##x.load()); \
160 folly::format(&ret, " {}_<_{: <9} {: >8}\n", \
161 prefix, #x ":", st.sub_##x.load());
167 std::string
show(const Builtins
& builtins
) {
168 auto ret
= std::string
{};
170 if (builtins
.builtinsInfo
.begin() != builtins
.builtinsInfo
.end()) {
171 folly::format(&ret
, "Total number of builtin calls: {: >15}\n",
172 builtins
.totalBuiltins
.load());
173 folly::format(&ret
, "Possible reducible builtins: {: >15}\n",
174 builtins
.reducibleBuiltins
.load());
176 ret
+= "Builtins Info:\n";
177 for (auto it
= builtins
.builtinsInfo
.begin();
178 it
!= builtins
.builtinsInfo
.end(); ++it
) {
181 " {: >30} [tot:{: >8}, red:{: >8}]\t\ttype: {}\n",
183 std::get
<1>(it
->second
),
184 std::get
<2>(it
->second
),
185 show(std::get
<0>(it
->second
))
193 std::string
show(const Stats
& stats
) {
194 auto ret
= std::string
{};
196 for (auto i
= uint32_t{}; i
< stats
.op_counts
.size(); ++i
) {
199 " {: >20}: {: >15}\n",
200 opcodeToName(static_cast<Op
>(i
)),
201 stats
.op_counts
[i
].load()
206 type_stat_string(ret
, "ret", stats
.returns
);
207 type_stat_string(ret
, "priv_prop", stats
.privateProps
);
208 type_stat_string(ret
, "priv_static", stats
.privateStatics
);
209 type_stat_string(ret
, "cgetm_base", stats
.cgetmBase
);
210 type_stat_string(ret
, "iterInit_base", stats
.iterInitBase
);
211 type_stat_string(ret
, "iterInitK_base", stats
.iterInitKBase
);
215 " total_methods: {: >8}\n"
216 " total_pseudomains: {: >8}\n"
217 " total_funcs: {: >8}\n"
218 " unique_funcs: {: >8}\n"
219 " persistent_funcs: {: >8}\n"
220 " total_classes: {: >8}\n"
221 " unique_classes: {: >8}\n"
222 " persistent_classes: {: >8}\n"
224 " total_sprops: {: >8}\n"
225 " persistent_sprops_pub: {: >8}\n"
226 " persistent_sprops_prot: {: >8}\n"
227 " persistent_sprops_priv: {: >8}\n",
228 stats
.totalMethods
.load(),
229 stats
.totalPseudoMains
.load(),
230 stats
.totalFunctions
.load(),
231 stats
.uniqueFunctions
.load(),
232 stats
.persistentFunctions
.load(),
233 stats
.totalClasses
.load(),
234 stats
.uniqueClasses
.load(),
235 stats
.persistentClasses
.load(),
236 stats
.totalSProps
.load(),
237 stats
.persistentSPropsPub
.load(),
238 stats
.persistentSPropsProt
.load(),
239 stats
.persistentSPropsPriv
.load()
243 ret
+= show(stats
.builtins
);
246 using T
= RepoAuthType::Tag
;
247 using U
= std::underlying_type
<T
>::type
;
249 folly::format(&ret, " {: >24}: {: >8}\n" \
250 " {: >24}: {: >8}\n", \
252 stats.ratL_tags[static_cast<U>(T::x)].load(), \
254 stats.ratStk_tags[static_cast<U>(T::x)].load());
258 folly::format(&ret
, " {: >24}: {: >8}\n"
259 " {: >24}: {: >8}\n",
261 stats
.ratL_specialized_array
.load(),
262 "RATStk_Arr_Special",
263 stats
.ratStk_specialized_array
.load());
268 //////////////////////////////////////////////////////////////////////
270 void add_type(TypeStat
& stat
, const Type
& t
) {
272 if (t.strictSubtypeOf(T##x)) ++stat.sub_##x; \
273 if (t == T##x) ++stat.eq_##x;
278 //////////////////////////////////////////////////////////////////////
280 struct StatsSS
: ISS
{
281 explicit StatsSS(ISS
& env
, Stats
& stats
)
289 //////////////////////////////////////////////////////////////////////
291 template <class OpCode
>
292 bool in(StatsSS
& /*env*/, const OpCode
&) {
296 bool in(StatsSS
& env
, const bc::IterInit
& /*op*/) {
297 add_type(env
.stats
.iterInitBase
, topC(env
));
301 bool in(StatsSS
& env
, const bc::IterInitK
& /*op*/) {
302 add_type(env
.stats
.iterInitKBase
, topC(env
));
306 bool in(StatsSS
& env
, const bc::FCallBuiltin
& op
) {
307 ++env
.stats
.builtins
.totalBuiltins
;
309 bool reducible
= op
.arg1
> 0;
310 for (auto i
= uint32_t{0}; i
< op
.arg1
; ++i
) {
311 auto t
= topT(env
, i
);
312 auto const v
= tv(t
);
313 if (!v
|| v
->m_type
== KindOfUninit
) {
319 default_dispatch(env
, op
);
321 auto builtin
= op
.str3
;
323 BuiltinInfo::accessor acc
;
324 auto inserted
= env
.stats
.builtins
.builtinsInfo
.insert(acc
, builtin
);
326 auto f
= env
.index
.resolve_func(env
.ctx
, builtin
);
327 auto t
= env
.index
.lookup_return_type(env
.ctx
, f
);
328 acc
->second
= std::make_tuple(t
, 1, 0);
330 ++std::get
<1>(acc
->second
);
331 if (reducible
) ++std::get
<2>(acc
->second
);
338 //////////////////////////////////////////////////////////////////////
340 // Run the interpreter. The "bool in(StatsSS&, const bc::XX)" functions
341 // can call the default dispatch on their own or return false to let
342 // the main loop call the default dispatch. Returning true stops
343 // the call to the default interpreter which implies the handler for
344 // that opcode must perform all the right steps wrt the state.
345 void dispatch(StatsSS
& env
, const Bytecode
& op
) {
346 #define O(opcode, ...) \
348 if (!in(env, op.opcode)) default_dispatch(env, op); \
351 switch (op
.op
) { OPCODES
}
356 //////////////////////////////////////////////////////////////////////
358 // Simple stats about opcodes (that don't require full type
359 // information---those cases are only enabled when extendedStats is
361 void collect_simple(Stats
& stats
, const Bytecode
& bc
) {
362 ++stats
.op_counts
[static_cast<uint64_t>(bc
.op
)];
367 rat
= bc
.AssertRATL
.rat
;
369 case Op::AssertRATStk
:
370 rat
= bc
.AssertRATStk
.rat
;
376 using U
= std::underlying_type
<RepoAuthType::Tag
>::type
;
377 auto const tagInt
= static_cast<U
>(rat
.tag());
378 assert(tagInt
< stats
.ratL_tags
.size());
379 if (bc
.op
== Op::AssertRATL
) {
380 ++stats
.ratL_tags
[tagInt
];
382 ++stats
.ratStk_tags
[tagInt
];
385 if (rat
.mayHaveArrData()) {
386 if (rat
.hasArrData()) {
387 if (bc
.op
== Op::AssertRATL
) {
388 ++stats
.ratL_specialized_array
;
390 ++stats
.ratStk_specialized_array
;
396 void collect_func(Stats
& stats
, const Index
& index
, php::Func
& func
) {
398 if (is_pseudomain(&func
)) {
399 ++stats
.totalPseudoMains
;
401 ++stats
.totalFunctions
;
402 if (func
.attrs
& AttrPersistent
) {
403 ++stats
.persistentFunctions
;
405 if (func
.attrs
& AttrUnique
) {
406 if (!(func
.attrs
& AttrPersistent
)) {
407 FTRACE(1, "Func unique but not persistent: {} : {}\n",
408 func
.name
, func
.unit
->filename
);
410 ++stats
.uniqueFunctions
;
412 FTRACE(1, "Func not unique: {} : {}\n",
413 func
.name
, func
.unit
->filename
);
418 auto const ty
= index
.lookup_return_type_raw(&func
);
420 add_type(stats
.returns
, ty
);
422 for (auto const bid
: func
.blockRange()) {
423 auto const blk
= func
.blocks
[bid
].get();
424 if (blk
->dead
) continue;
425 for (auto& bc
: blk
->hhbcs
) {
426 collect_simple(stats
, bc
);
430 if (!options
.extendedStats
) return;
432 auto const ctx
= Context
{ func
.unit
, &func
, func
.cls
};
433 auto const fa
= analyze_func(index
, ctx
, CollectionOpts
{});
435 Trace::Bump bumper
{Trace::hhbbc
, kStatsBump
};
436 for (auto const bid
: func
.blockRange()) {
437 auto const blk
= func
.blocks
[bid
].get();
438 auto state
= fa
.bdata
[bid
].stateIn
;
439 if (!state
.initialized
) continue;
441 CollectedInfo collect
{
442 index
, ctx
, nullptr, CollectionOpts
{}, &fa
444 Interp interp
{ index
, ctx
, collect
, bid
, blk
, state
};
445 for (auto& bc
: blk
->hhbcs
) {
446 auto noop
= [] (BlockId
, const State
*) {};
447 ISS env
{ interp
, noop
};
448 StatsSS sss
{ env
, stats
};
450 if (state
.unreachable
) break;
456 void collect_class(Stats
& stats
, const Index
& index
, const php::Class
& cls
) {
457 ++stats
.totalClasses
;
458 if (cls
.attrs
& AttrPersistent
) {
459 ++stats
.persistentClasses
;
461 if (cls
.attrs
& AttrUnique
) {
462 if (!(cls
.attrs
& AttrPersistent
)) {
463 FTRACE(1, "Class unique but not persistent: {} : {}\n",
464 cls
.name
, cls
.unit
->filename
);
466 ++stats
.uniqueClasses
;
468 FTRACE(1, "Class not unique: {} : {}\n",
469 cls
.name
, cls
.unit
->filename
);
471 stats
.totalMethods
+= cls
.methods
.size();
473 for (auto& kv
: index
.lookup_private_props(&cls
)) {
474 add_type(stats
.privateProps
, kv
.second
.ty
);
476 for (auto& kv
: index
.lookup_private_statics(&cls
)) {
477 add_type(stats
.privateStatics
, kv
.second
.ty
);
480 for (auto& prop
: cls
.properties
) {
481 if (prop
.attrs
& AttrStatic
) {
483 if (prop
.attrs
& AttrPersistent
) {
484 if (prop
.attrs
& AttrPublic
) ++stats
.persistentSPropsPub
;
485 if (prop
.attrs
& AttrProtected
) ++stats
.persistentSPropsProt
;
486 if (prop
.attrs
& AttrPrivate
) ++stats
.persistentSPropsPriv
;
492 void collect_stats(Stats
& stats
,
494 const php::Program
& program
) {
497 [&] (const std::unique_ptr
<php::Unit
>& unit
) {
498 for (auto& c
: unit
->classes
) {
499 collect_class(stats
, index
, *c
);
500 for (auto& m
: c
->methods
) {
501 collect_func(stats
, index
, *m
);
504 for (auto& x
: unit
->funcs
) {
505 collect_func(stats
, index
, *x
);
507 collect_func(stats
, index
, *unit
->pseudomain
);
512 //////////////////////////////////////////////////////////////////////
516 //////////////////////////////////////////////////////////////////////
518 StatsHolder::StatsHolder() {
519 if (!Trace::moduleEnabledRelease(Trace::hhbbc_time
, 1)) return;
523 StatsHolder::~StatsHolder() {
527 StatsHolder
allocate_stats() {
528 return StatsHolder();
531 void collect_stats(const StatsHolder
& stats
,
533 const php::Unit
* unit
) {
535 for (auto& c
: unit
->classes
) {
536 collect_class(*stats
.stats
, index
, *c
);
537 for (auto& m
: c
->methods
) {
538 collect_func(*stats
.stats
, index
, *m
);
541 for (auto& x
: unit
->funcs
) {
542 collect_func(*stats
.stats
, index
, *x
);
544 collect_func(*stats
.stats
, index
, *unit
->pseudomain
);
547 void print_stats(const StatsHolder
& stats
) {
550 auto const str
= show(*stats
.stats
);
551 if (Trace::moduleEnabledRelease(Trace::hhbbc_time
, 2)) {
555 auto stats_file
= options
.stats_file
;
557 auto const file
= [&] () -> std::FILE* {
558 if (!stats_file
.empty()) {
559 return fopen(stats_file
.c_str(), "w");
562 char fileBuf
[] = "/tmp/hhbbcXXXXXX";
563 auto const fd
= mkstemp(fileBuf
);
564 stats_file
= fileBuf
;
565 if (fd
== -1) return nullptr;
567 if (auto const fp
= fdopen(fd
, "w")) return fp
;
572 if (file
== nullptr) {
573 std::cerr
<< "couldn't open file for stats: "
574 << folly::errnoStr(errno
) << '\n';
578 SCOPE_EXIT
{ fclose(file
); };
579 std::cout
<< "stats saved to " << stats_file
<< '\n';
580 std::fwrite(str
.c_str(), sizeof(char), str
.size(), file
);
584 //////////////////////////////////////////////////////////////////////