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 ;
124 std::array
<std::atomic
<uint64_t>,Op_count
> op_counts
;
125 std::array
<std::atomic
<uint64_t>,kNumRATTags
> ratL_tags
;
126 std::array
<std::atomic
<uint64_t>,kNumRATTags
> ratStk_tags
;
127 std::atomic
<uint64_t> ratL_specialized_array
;
128 std::atomic
<uint64_t> ratStk_specialized_array
;
129 std::atomic
<uint64_t> persistentClasses
;
130 std::atomic
<uint64_t> persistentFunctions
;
131 std::atomic
<uint64_t> uniqueClasses
;
132 std::atomic
<uint64_t> uniqueFunctions
;
133 std::atomic
<uint64_t> totalClasses
;
134 std::atomic
<uint64_t> totalFunctions
;
135 std::atomic
<uint64_t> totalPseudoMains
;
136 std::atomic
<uint64_t> totalMethods
;
137 std::atomic
<uint64_t> persistentSPropsPub
;
138 std::atomic
<uint64_t> persistentSPropsProt
;
139 std::atomic
<uint64_t> persistentSPropsPriv
;
140 std::atomic
<uint64_t> totalSProps
;
142 TypeStat privateProps
;
143 TypeStat privateStatics
;
145 TypeStat iterInitBase
;
146 TypeStat iterInitKBase
;
150 void type_stat_string(std::string
& ret
,
151 const std::string
& prefix
,
152 const TypeStat
& st
) {
154 folly::format(&ret, " {}_=_{: <9} {: >8}\n", \
155 prefix, #x ":", st.eq_##x.load()); \
156 folly::format(&ret, " {}_<_{: <9} {: >8}\n", \
157 prefix, #x ":", st.sub_##x.load());
163 std::string
show(const Builtins
& builtins
) {
164 auto ret
= std::string
{};
166 if (builtins
.builtinsInfo
.begin() != builtins
.builtinsInfo
.end()) {
167 folly::format(&ret
, "Total number of builtin calls: {: >15}\n",
168 builtins
.totalBuiltins
.load());
169 folly::format(&ret
, "Possible reducible builtins: {: >15}\n",
170 builtins
.reducibleBuiltins
.load());
172 ret
+= "Builtins Info:\n";
173 for (auto it
= builtins
.builtinsInfo
.begin();
174 it
!= builtins
.builtinsInfo
.end(); ++it
) {
177 " {: >30} [tot:{: >8}, red:{: >8}]\t\ttype: {}\n",
179 std::get
<1>(it
->second
),
180 std::get
<2>(it
->second
),
181 show(std::get
<0>(it
->second
))
189 std::string
show(const Stats
& stats
) {
190 auto ret
= std::string
{};
192 for (auto i
= uint32_t{}; i
< stats
.op_counts
.size(); ++i
) {
195 " {: >20}: {: >15}\n",
196 opcodeToName(static_cast<Op
>(i
)),
197 stats
.op_counts
[i
].load()
202 type_stat_string(ret
, "ret", stats
.returns
);
203 type_stat_string(ret
, "priv_prop", stats
.privateProps
);
204 type_stat_string(ret
, "priv_static", stats
.privateStatics
);
205 type_stat_string(ret
, "cgetm_base", stats
.cgetmBase
);
206 type_stat_string(ret
, "iterInit_base", stats
.iterInitBase
);
207 type_stat_string(ret
, "iterInitK_base", stats
.iterInitKBase
);
211 " total_methods: {: >8}\n"
212 " total_pseudomains: {: >8}\n"
213 " total_funcs: {: >8}\n"
214 " unique_funcs: {: >8}\n"
215 " persistent_funcs: {: >8}\n"
216 " total_classes: {: >8}\n"
217 " unique_classes: {: >8}\n"
218 " persistent_classes: {: >8}\n"
220 " total_sprops: {: >8}\n"
221 " persistent_sprops_pub: {: >8}\n"
222 " persistent_sprops_prot: {: >8}\n"
223 " persistent_sprops_priv: {: >8}\n",
224 stats
.totalMethods
.load(),
225 stats
.totalPseudoMains
.load(),
226 stats
.totalFunctions
.load(),
227 stats
.uniqueFunctions
.load(),
228 stats
.persistentFunctions
.load(),
229 stats
.totalClasses
.load(),
230 stats
.uniqueClasses
.load(),
231 stats
.persistentClasses
.load(),
232 stats
.totalSProps
.load(),
233 stats
.persistentSPropsPub
.load(),
234 stats
.persistentSPropsProt
.load(),
235 stats
.persistentSPropsPriv
.load()
239 ret
+= show(stats
.builtins
);
242 using T
= RepoAuthType::Tag
;
243 using U
= std::underlying_type
<T
>::type
;
245 folly::format(&ret, " {: >24}: {: >8}\n" \
246 " {: >24}: {: >8}\n", \
248 stats.ratL_tags[static_cast<U>(T::x)].load(), \
250 stats.ratStk_tags[static_cast<U>(T::x)].load());
254 folly::format(&ret
, " {: >24}: {: >8}\n"
255 " {: >24}: {: >8}\n",
257 stats
.ratL_specialized_array
.load(),
258 "RATStk_Arr_Special",
259 stats
.ratStk_specialized_array
.load());
264 //////////////////////////////////////////////////////////////////////
266 void add_type(TypeStat
& stat
, const Type
& t
) {
268 if (t.strictSubtypeOf(T##x)) ++stat.sub_##x; \
269 if (t == T##x) ++stat.eq_##x;
274 //////////////////////////////////////////////////////////////////////
276 struct StatsSS
: ISS
{
277 explicit StatsSS(ISS
& env
, Stats
& stats
)
285 //////////////////////////////////////////////////////////////////////
287 template <class OpCode
>
288 bool in(StatsSS
& /*env*/, const OpCode
&) {
292 bool in(StatsSS
& env
, const bc::IterInit
& /*op*/) {
293 add_type(env
.stats
.iterInitBase
, topC(env
));
297 bool in(StatsSS
& env
, const bc::IterInitK
& /*op*/) {
298 add_type(env
.stats
.iterInitKBase
, topC(env
));
302 bool in(StatsSS
& env
, const bc::FCallBuiltin
& op
) {
303 ++env
.stats
.builtins
.totalBuiltins
;
305 bool reducible
= op
.arg1
> 0;
306 for (auto i
= uint32_t{0}; i
< op
.arg1
; ++i
) {
307 auto t
= topT(env
, i
);
308 auto const v
= tv(t
);
309 if (!v
|| v
->m_type
== KindOfUninit
) {
315 default_dispatch(env
, op
);
317 auto builtin
= op
.str3
;
319 BuiltinInfo::accessor acc
;
320 auto inserted
= env
.stats
.builtins
.builtinsInfo
.insert(acc
, builtin
);
322 auto f
= env
.index
.resolve_func(env
.ctx
, builtin
);
323 auto t
= env
.index
.lookup_return_type(env
.ctx
, f
);
324 acc
->second
= std::make_tuple(t
, 1, 0);
326 ++std::get
<1>(acc
->second
);
327 if (reducible
) ++std::get
<2>(acc
->second
);
334 //////////////////////////////////////////////////////////////////////
336 // Run the interpreter. The "bool in(StatsSS&, const bc::XX)" functions
337 // can call the default dispatch on their own or return false to let
338 // the main loop call the default dispatch. Returning true stops
339 // the call to the default interpreter which implies the handler for
340 // that opcode must perform all the right steps wrt the state.
341 void dispatch(StatsSS
& env
, const Bytecode
& op
) {
342 #define O(opcode, ...) \
344 if (!in(env, op.opcode)) default_dispatch(env, op); \
347 switch (op
.op
) { OPCODES
}
352 //////////////////////////////////////////////////////////////////////
354 // Simple stats about opcodes (that don't require full type
355 // information---those cases are only enabled when extendedStats is
357 void collect_simple(Stats
& stats
, const Bytecode
& bc
) {
358 ++stats
.op_counts
[static_cast<uint64_t>(bc
.op
)];
363 rat
= bc
.AssertRATL
.rat
;
365 case Op::AssertRATStk
:
366 rat
= bc
.AssertRATStk
.rat
;
372 using U
= std::underlying_type
<RepoAuthType::Tag
>::type
;
373 auto const tagInt
= static_cast<U
>(rat
.tag());
374 assert(tagInt
< stats
.ratL_tags
.size());
375 if (bc
.op
== Op::AssertRATL
) {
376 ++stats
.ratL_tags
[tagInt
];
378 ++stats
.ratStk_tags
[tagInt
];
381 if (rat
.mayHaveArrData()) {
382 if (rat
.hasArrData()) {
383 if (bc
.op
== Op::AssertRATL
) {
384 ++stats
.ratL_specialized_array
;
386 ++stats
.ratStk_specialized_array
;
392 void collect_func(Stats
& stats
, const Index
& index
, php::Func
& func
) {
394 if (is_pseudomain(&func
)) {
395 ++stats
.totalPseudoMains
;
397 ++stats
.totalFunctions
;
398 if (func
.attrs
& AttrPersistent
) {
399 ++stats
.persistentFunctions
;
401 if (func
.attrs
& AttrUnique
) {
402 if (!(func
.attrs
& AttrPersistent
)) {
403 FTRACE(1, "Func unique but not persistent: {} : {}\n",
404 func
.name
, func
.unit
->filename
);
406 ++stats
.uniqueFunctions
;
408 FTRACE(1, "Func not unique: {} : {}\n",
409 func
.name
, func
.unit
->filename
);
414 auto const ty
= index
.lookup_return_type_raw(&func
);
416 add_type(stats
.returns
, ty
);
418 for (auto const bid
: func
.blockRange()) {
419 auto const blk
= func
.blocks
[bid
].get();
420 if (blk
->dead
) continue;
421 for (auto& bc
: blk
->hhbcs
) {
422 collect_simple(stats
, bc
);
426 if (!options
.extendedStats
) return;
428 auto const ctx
= Context
{ func
.unit
, &func
, func
.cls
};
429 auto const fa
= analyze_func(index
, ctx
, CollectionOpts
{});
431 Trace::Bump bumper
{Trace::hhbbc
, kStatsBump
};
432 for (auto const bid
: func
.blockRange()) {
433 auto const blk
= func
.blocks
[bid
].get();
434 auto state
= fa
.bdata
[bid
].stateIn
;
435 if (!state
.initialized
) continue;
437 CollectedInfo collect
{
438 index
, ctx
, nullptr, CollectionOpts
{}, &fa
440 Interp interp
{ index
, ctx
, collect
, bid
, blk
, state
};
441 for (auto& bc
: blk
->hhbcs
) {
442 auto noop
= [] (BlockId
, const State
*) {};
443 ISS env
{ interp
, noop
};
444 StatsSS sss
{ env
, stats
};
446 if (state
.unreachable
) break;
452 void collect_class(Stats
& stats
, const Index
& index
, const php::Class
& cls
) {
453 ++stats
.totalClasses
;
454 if (cls
.attrs
& AttrPersistent
) {
455 ++stats
.persistentClasses
;
457 if (cls
.attrs
& AttrUnique
) {
458 if (!(cls
.attrs
& AttrPersistent
)) {
459 FTRACE(1, "Class unique but not persistent: {} : {}\n",
460 cls
.name
, cls
.unit
->filename
);
462 ++stats
.uniqueClasses
;
464 FTRACE(1, "Class not unique: {} : {}\n",
465 cls
.name
, cls
.unit
->filename
);
467 stats
.totalMethods
+= cls
.methods
.size();
469 for (auto& kv
: index
.lookup_private_props(&cls
)) {
470 add_type(stats
.privateProps
, kv
.second
.ty
);
472 for (auto& kv
: index
.lookup_private_statics(&cls
)) {
473 add_type(stats
.privateStatics
, kv
.second
.ty
);
476 for (auto& prop
: cls
.properties
) {
477 if (prop
.attrs
& AttrStatic
) {
479 if (prop
.attrs
& AttrPersistent
) {
480 if (prop
.attrs
& AttrPublic
) ++stats
.persistentSPropsPub
;
481 if (prop
.attrs
& AttrProtected
) ++stats
.persistentSPropsProt
;
482 if (prop
.attrs
& AttrPrivate
) ++stats
.persistentSPropsPriv
;
488 void collect_stats(Stats
& stats
,
490 const php::Program
& program
) {
493 [&] (const std::unique_ptr
<php::Unit
>& unit
) {
494 for (auto& c
: unit
->classes
) {
495 collect_class(stats
, index
, *c
);
496 for (auto& m
: c
->methods
) {
497 collect_func(stats
, index
, *m
);
500 for (auto& x
: unit
->funcs
) {
501 collect_func(stats
, index
, *x
);
503 collect_func(stats
, index
, *unit
->pseudomain
);
508 //////////////////////////////////////////////////////////////////////
512 //////////////////////////////////////////////////////////////////////
514 void print_stats(const Index
& index
, const php::Program
& program
) {
515 if (!Trace::moduleEnabledRelease(Trace::hhbbc_time
, 1)) return;
517 trace_time
timer("stats");
520 collect_stats(stats
, index
, program
);
522 auto const str
= show(stats
);
523 if (Trace::moduleEnabledRelease(Trace::hhbbc_time
, 2)) {
527 auto stats_file
= options
.stats_file
;
529 auto const file
= [&] () -> std::FILE* {
530 if (!stats_file
.empty()) {
531 return fopen(stats_file
.c_str(), "w");
534 char fileBuf
[] = "/tmp/hhbbcXXXXXX";
535 auto const fd
= mkstemp(fileBuf
);
536 stats_file
= fileBuf
;
537 if (fd
== -1) return nullptr;
539 if (auto const fp
= fdopen(fd
, "w")) return fp
;
544 if (file
== nullptr) {
545 std::cerr
<< "couldn't open file for stats: "
546 << folly::errnoStr(errno
) << '\n';
550 SCOPE_EXIT
{ fclose(file
); };
551 std::cout
<< "stats saved to " << stats_file
<< '\n';
552 std::fwrite(str
.c_str(), sizeof(char), str
.size(), file
);
556 //////////////////////////////////////////////////////////////////////