2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-2015 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>
37 #include "hphp/util/trace.h"
38 #include "hphp/runtime/vm/hhbc.h"
40 #include "hphp/hhbbc/misc.h"
41 #include "hphp/hhbbc/representation.h"
42 #include "hphp/hhbbc/parallel.h"
43 #include "hphp/hhbbc/index.h"
44 #include "hphp/hhbbc/interp-internal.h"
45 #include "hphp/hhbbc/interp.h"
46 #include "hphp/hhbbc/analyze.h"
48 namespace HPHP
{ namespace HHBBC
{
50 //////////////////////////////////////////////////////////////////////
54 //////////////////////////////////////////////////////////////////////
79 #define X(x) std::atomic<uint64_t> sub_##x; \
80 std::atomic<uint64_t> eq_##x;
86 bool equal(SString s1
, SString s2
) const {
90 size_t hash(SString s
) const {
96 * Information about builtins usage.
98 * The tuple contains the known return type for the builtin, the total
99 * number of calls seen and the total number of calls that could be
100 * reduced. A builtin call is considered reducible if its output is a
101 * constant and all its inputs are constants. That is not a guaranteed
102 * condition but it gives us an idea of what's possible.
104 using BuiltinInfo
= tbb::concurrent_hash_map
<
106 std::tuple
<Type
,uint64_t,uint64_t>,
111 std::atomic
<uint64_t> totalBuiltins
;
112 std::atomic
<uint64_t> reducibleBuiltins
;
113 BuiltinInfo builtinsInfo
;
117 constexpr uint32_t kNumRATTags
= REPO_AUTH_TYPE_TAGS
0 ;
121 std::array
<std::atomic
<uint64_t>,Op_count
> op_counts
;
122 std::array
<std::atomic
<uint64_t>,kNumRATTags
> ratL_tags
;
123 std::array
<std::atomic
<uint64_t>,kNumRATTags
> ratStk_tags
;
124 std::atomic
<uint64_t> ratL_specialized_array
;
125 std::atomic
<uint64_t> ratStk_specialized_array
;
126 std::atomic
<uint64_t> persistentClasses
;
127 std::atomic
<uint64_t> persistentFunctions
;
128 std::atomic
<uint64_t> uniqueClasses
;
129 std::atomic
<uint64_t> uniqueFunctions
;
130 std::atomic
<uint64_t> totalClasses
;
131 std::atomic
<uint64_t> totalFunctions
;
132 std::atomic
<uint64_t> totalMethods
;
133 std::atomic
<uint64_t> persistentSPropsPub
;
134 std::atomic
<uint64_t> persistentSPropsProt
;
135 std::atomic
<uint64_t> persistentSPropsPriv
;
136 std::atomic
<uint64_t> totalSProps
;
138 TypeStat privateProps
;
139 TypeStat privateStatics
;
141 TypeStat iterInitBase
;
142 TypeStat iterInitKBase
;
146 void type_stat_string(std::string
& ret
,
147 const std::string
& prefix
,
148 const TypeStat
& st
) {
150 folly::format(&ret, " {}_=_{: <9} {: >8}\n", \
151 prefix, #x ":", st.eq_##x.load()); \
152 folly::format(&ret, " {}_<_{: <9} {: >8}\n", \
153 prefix, #x ":", st.sub_##x.load());
159 std::string
show(const Builtins
& builtins
) {
160 auto ret
= std::string
{};
162 if (builtins
.builtinsInfo
.begin() != builtins
.builtinsInfo
.end()) {
163 ret
+= folly::format("Total number of builtin calls: {: >15}\n",
164 builtins
.totalBuiltins
.load()).str();
165 ret
+= folly::format("Possible reducible builtins: {: >15}\n",
166 builtins
.reducibleBuiltins
.load()).str();
168 ret
+= "Builtins Info:\n";
169 for (auto it
= builtins
.builtinsInfo
.begin();
170 it
!= builtins
.builtinsInfo
.end(); ++it
) {
171 ret
+= folly::format(
172 " {: >30} [tot:{: >8}, red:{: >8}]\t\ttype: {}\n",
174 std::get
<1>(it
->second
),
175 std::get
<2>(it
->second
),
176 show(std::get
<0>(it
->second
))
184 std::string
show(const Stats
& stats
) {
185 auto ret
= std::string
{};
187 for (auto i
= uint32_t{}; i
< stats
.op_counts
.size(); ++i
) {
190 " {: >20}: {: >15}\n",
191 opcodeToName(static_cast<Op
>(i
)),
192 stats
.op_counts
[i
].load()
197 type_stat_string(ret
, "ret", stats
.returns
);
198 type_stat_string(ret
, "priv_prop", stats
.privateProps
);
199 type_stat_string(ret
, "priv_static", stats
.privateStatics
);
200 type_stat_string(ret
, "cgetm_base", stats
.cgetmBase
);
201 type_stat_string(ret
, "iterInit_base", stats
.iterInitBase
);
202 type_stat_string(ret
, "iterInitK_base", stats
.iterInitKBase
);
206 " total_methods: {: >8}\n"
207 " total_funcs: {: >8}\n"
208 " unique_funcs: {: >8}\n"
209 " persistent_funcs: {: >8}\n"
210 " total_classes: {: >8}\n"
211 " unique_classes: {: >8}\n"
212 " persistent_classes: {: >8}\n"
214 " total_sprops: {: >8}\n"
215 " persistent_sprops_pub: {: >8}\n"
216 " persistent_sprops_prot: {: >8}\n"
217 " persistent_sprops_priv: {: >8}\n",
218 stats
.totalMethods
.load(),
219 stats
.totalFunctions
.load(),
220 stats
.uniqueFunctions
.load(),
221 stats
.persistentFunctions
.load(),
222 stats
.totalClasses
.load(),
223 stats
.uniqueClasses
.load(),
224 stats
.persistentClasses
.load(),
225 stats
.totalSProps
.load(),
226 stats
.persistentSPropsPub
.load(),
227 stats
.persistentSPropsProt
.load(),
228 stats
.persistentSPropsPriv
.load()
232 ret
+= show(stats
.builtins
);
235 using T
= RepoAuthType::Tag
;
236 using U
= std::underlying_type
<T
>::type
;
238 folly::format(&ret, " {: >24}: {: >8}\n" \
239 " {: >24}: {: >8}\n", \
241 stats.ratL_tags[static_cast<U>(T::x)].load(), \
243 stats.ratStk_tags[static_cast<U>(T::x)].load());
247 folly::format(&ret
, " {: >24}: {: >8}\n"
248 " {: >24}: {: >8}\n",
250 stats
.ratL_specialized_array
.load(),
251 "RATStk_Arr_Special",
252 stats
.ratStk_specialized_array
.load());
257 //////////////////////////////////////////////////////////////////////
259 void add_type(TypeStat
& stat
, const Type
& t
) {
261 if (t.strictSubtypeOf(T##x)) ++stat.sub_##x; \
262 if (t == T##x) ++stat.eq_##x;
267 //////////////////////////////////////////////////////////////////////
269 struct StatsSS
: ISS
{
270 explicit StatsSS(ISS
& env
, Stats
& stats
)
278 //////////////////////////////////////////////////////////////////////
280 template<class OpCode
>
281 bool in(StatsSS
& env
, const OpCode
&) {
285 bool in(StatsSS
& env
, const bc::IterInit
& op
) {
286 add_type(env
.stats
.iterInitBase
, topC(env
));
290 bool in(StatsSS
& env
, const bc::IterInitK
& op
) {
291 add_type(env
.stats
.iterInitKBase
, topC(env
));
295 bool in(StatsSS
& env
, const bc::CGetM
& op
) {
296 auto const pops
= op
.numPop();
297 auto const ty
= [&]() -> Type
{
298 switch (op
.mvec
.lcode
) {
300 return env
.state
.locals
[op
.mvec
.locBase
->id
];
303 return topT(env
, pops
- 1);
312 case InvalidLocationCode
:
317 add_type(env
.stats
.cgetmBase
, ty
);
321 bool in(StatsSS
& env
, const bc::FCallBuiltin
& op
) {
322 ++env
.stats
.builtins
.totalBuiltins
;
324 bool reducible
= op
.arg1
> 0;
325 for (auto i
= uint32_t{0}; i
< op
.arg1
; ++i
) {
326 auto t
= topT(env
, i
);
327 auto const v
= tv(t
);
328 if (!v
|| v
->m_type
== KindOfUninit
) {
334 default_dispatch(env
, op
);
336 auto builtin
= op
.str3
;
338 BuiltinInfo::accessor acc
;
339 auto inserted
= env
.stats
.builtins
.builtinsInfo
.insert(acc
, builtin
);
341 auto f
= env
.index
.resolve_func(env
.ctx
, builtin
);
342 auto t
= env
.index
.lookup_return_type(env
.ctx
, f
);
343 acc
->second
= std::make_tuple(t
, 1, 0);
345 ++std::get
<1>(acc
->second
);
346 if (reducible
) ++std::get
<2>(acc
->second
);
353 //////////////////////////////////////////////////////////////////////
355 // Run the interpreter. The "bool in(StatsSS&, const bc::XX)" functions
356 // can call the default dispatch on their own or return false to let
357 // the main loop call the default dispatch. Returning true stops
358 // the call to the default interpreter which implies the handler for
359 // that opcode must perform all the right steps wrt the state.
360 void dispatch(StatsSS
& env
, const Bytecode
& op
) {
361 #define O(opcode, ...) \
363 if (!in(env, op.opcode)) default_dispatch(env, op); \
366 switch (op
.op
) { OPCODES
}
371 //////////////////////////////////////////////////////////////////////
373 // Simple stats about opcodes (that don't require full type
374 // information---those cases are only enabled when extendedStats is
376 void collect_simple(Stats
& stats
, const Bytecode
& bc
) {
377 ++stats
.op_counts
[static_cast<uint64_t>(bc
.op
)];
382 rat
= bc
.AssertRATL
.rat
;
384 case Op::AssertRATStk
:
385 rat
= bc
.AssertRATStk
.rat
;
391 using U
= std::underlying_type
<RepoAuthType::Tag
>::type
;
392 auto const tagInt
= static_cast<U
>(rat
.tag());
393 assert(tagInt
< stats
.ratL_tags
.size());
394 if (bc
.op
== Op::AssertRATL
) {
395 ++stats
.ratL_tags
[tagInt
];
397 ++stats
.ratStk_tags
[tagInt
];
400 if (rat
.mayHaveArrData()) {
402 if (bc
.op
== Op::AssertRATL
) {
403 ++stats
.ratL_specialized_array
;
405 ++stats
.ratStk_specialized_array
;
411 void collect_func(Stats
& stats
, const Index
& index
, php::Func
& func
) {
413 ++stats
.totalFunctions
;
414 if (func
.attrs
& AttrPersistent
) {
415 ++stats
.persistentFunctions
;
417 if (func
.attrs
& AttrUnique
) {
418 ++stats
.uniqueFunctions
;
422 auto const ty
= index
.lookup_return_type_raw(&func
);
424 add_type(stats
.returns
, ty
);
426 for (auto& blk
: func
.blocks
) {
427 for (auto& bc
: blk
->hhbcs
) {
428 collect_simple(stats
, bc
);
432 if (!options
.extendedStats
) return;
434 auto const ctx
= Context
{ func
.unit
, &func
, func
.cls
};
435 auto const fa
= analyze_func(index
, ctx
);
437 Trace::Bump bumper
{Trace::hhbbc
, kStatsBump
};
438 for (auto& blk
: func
.blocks
) {
439 auto state
= fa
.bdata
[blk
->id
].stateIn
;
440 if (!state
.initialized
) continue;
442 CollectedInfo collect
{ index
, ctx
, nullptr, nullptr };
443 Interp interp
{ index
, ctx
, collect
, borrow(blk
), state
};
444 for (auto& bc
: blk
->hhbcs
) {
445 auto noop
= [] (php::Block
&, const State
&) {};
446 auto flags
= StepFlags
{};
447 ISS env
{ interp
, flags
, noop
};
448 StatsSS sss
{ env
, stats
};
455 void collect_class(Stats
& stats
, const Index
& index
, const php::Class
& cls
) {
456 ++stats
.totalClasses
;
457 if (cls
.attrs
& AttrPersistent
) {
458 ++stats
.persistentClasses
;
460 if (cls
.attrs
& AttrUnique
) {
461 ++stats
.uniqueClasses
;
463 stats
.totalMethods
+= cls
.methods
.size();
465 for (auto& kv
: index
.lookup_private_props(&cls
)) {
466 add_type(stats
.privateProps
, kv
.second
);
468 for (auto& kv
: index
.lookup_private_statics(&cls
)) {
469 add_type(stats
.privateStatics
, kv
.second
);
472 for (auto& prop
: cls
.properties
) {
473 if (prop
.attrs
& AttrStatic
) {
475 if (prop
.attrs
& AttrPersistent
) {
476 if (prop
.attrs
& AttrPublic
) ++stats
.persistentSPropsPub
;
477 if (prop
.attrs
& AttrProtected
) ++stats
.persistentSPropsProt
;
478 if (prop
.attrs
& AttrPrivate
) ++stats
.persistentSPropsPriv
;
484 void collect_stats(Stats
& stats
,
486 const php::Program
& program
) {
489 [&] (const std::unique_ptr
<php::Unit
>& unit
) {
490 for (auto& c
: unit
->classes
) {
491 collect_class(stats
, index
, *c
);
492 for (auto& m
: c
->methods
) {
493 collect_func(stats
, index
, *m
);
496 for (auto& x
: unit
->funcs
) {
497 collect_func(stats
, index
, *x
);
499 collect_func(stats
, index
, *unit
->pseudomain
);
504 //////////////////////////////////////////////////////////////////////
508 //////////////////////////////////////////////////////////////////////
510 void print_stats(const Index
& index
, const php::Program
& program
) {
511 if (!Trace::moduleEnabledRelease(Trace::hhbbc_time
, 1)) return;
513 trace_time
timer("stats");
516 collect_stats(stats
, index
, program
);
518 auto const str
= show(stats
);
519 if (Trace::moduleEnabledRelease(Trace::hhbbc_time
, 2)) {
523 auto stats_file
= options
.stats_file
;
525 auto const file
= [&] () -> std::FILE* {
526 if (!stats_file
.empty()) {
527 return fopen(stats_file
.c_str(), "w");
530 char fileBuf
[] = "/tmp/hhbbcXXXXXX";
531 auto const fd
= mkstemp(fileBuf
);
532 stats_file
= fileBuf
;
533 if (fd
== -1) return nullptr;
535 if (auto const fp
= fdopen(fd
, "w")) return fp
;
540 if (file
== nullptr) {
541 std::cerr
<< "couldn't open file for stats: "
542 << folly::errnoStr(errno
) << '\n';
546 SCOPE_EXIT
{ fclose(file
); };
547 std::cout
<< "stats saved to " << stats_file
<< '\n';
548 std::fwrite(str
.c_str(), sizeof(char), str
.size(), file
);
552 //////////////////////////////////////////////////////////////////////