use AtomicLowPtr<Class> in NamedEntity>
[hiphop-php.git] / hphp / hhbbc / stats.cpp
blob2cd058e055d1c306b2a3c4d745f294f24d4ca014
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
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"
18 #include <atomic>
19 #include <array>
20 #include <string>
21 #include <cinttypes>
22 #include <cstdlib>
23 #include <iostream>
24 #include <memory>
25 #include <tuple>
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 //////////////////////////////////////////////////////////////////////
52 namespace {
54 //////////////////////////////////////////////////////////////////////
56 #define STAT_TYPES \
57 X(Gen) \
58 X(InitGen) \
59 X(Ref) \
60 X(Cell) \
61 X(InitCell) \
62 X(Unc) \
63 X(InitUnc) \
64 X(Obj) \
65 X(OptObj) \
66 X(Arr) \
67 X(SArr) \
68 X(CArr) \
69 X(ArrE) \
70 X(ArrN) \
71 X(SArrN) \
72 X(SArrE) \
73 X(CArrN) \
74 X(CArrE) \
75 X(Null) \
76 X(Bottom)
78 struct TypeStat {
79 #define X(x) std::atomic<uint64_t> sub_##x; \
80 std::atomic<uint64_t> eq_##x;
81 STAT_TYPES
82 #undef X
85 struct ISameCmp {
86 bool equal(SString s1, SString s2) const {
87 return s1->isame(s2);
90 size_t hash(SString s) const {
91 return s->hash();
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<
105 SString,
106 std::tuple<Type,uint64_t,uint64_t>,
107 ISameCmp
110 struct Builtins {
111 std::atomic<uint64_t> totalBuiltins;
112 std::atomic<uint64_t> reducibleBuiltins;
113 BuiltinInfo builtinsInfo;
116 #define TAG(x) 1 +
117 constexpr uint32_t kNumRATTags = REPO_AUTH_TYPE_TAGS 0 ;
118 #undef TAG
120 struct Stats {
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;
137 TypeStat returns;
138 TypeStat privateProps;
139 TypeStat privateStatics;
140 TypeStat cgetmBase;
141 TypeStat iterInitBase;
142 TypeStat iterInitKBase;
143 Builtins builtins;
146 void type_stat_string(std::string& ret,
147 const std::string& prefix,
148 const TypeStat& st) {
149 #define X(x) \
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());
154 STAT_TYPES
155 #undef X
156 ret += "\n";
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",
173 it->first,
174 std::get<1>(it->second),
175 std::get<2>(it->second),
176 show(std::get<0>(it->second))
177 ).str();
179 ret += "\n";
181 return ret;
184 std::string show(const Stats& stats) {
185 auto ret = std::string{};
187 for (auto i = uint32_t{}; i < stats.op_counts.size(); ++i) {
188 folly::format(
189 &ret,
190 " {: >20}: {: >15}\n",
191 opcodeToName(static_cast<Op>(i)),
192 stats.op_counts[i].load()
195 ret += "\n";
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);
204 folly::format(
205 &ret,
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"
213 "\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()
231 ret += "\n";
232 ret += show(stats.builtins);
234 ret += "\n";
235 using T = RepoAuthType::Tag;
236 using U = std::underlying_type<T>::type;
237 #define TAG(x) \
238 folly::format(&ret, " {: >24}: {: >8}\n" \
239 " {: >24}: {: >8}\n", \
240 "RATL_" #x, \
241 stats.ratL_tags[static_cast<U>(T::x)].load(), \
242 "RATStk_" #x, \
243 stats.ratStk_tags[static_cast<U>(T::x)].load());
244 REPO_AUTH_TYPE_TAGS
245 #undef TAG
247 folly::format(&ret, " {: >24}: {: >8}\n"
248 " {: >24}: {: >8}\n",
249 "RATL_Arr_Special",
250 stats.ratL_specialized_array.load(),
251 "RATStk_Arr_Special",
252 stats.ratStk_specialized_array.load());
254 return ret;
257 //////////////////////////////////////////////////////////////////////
259 void add_type(TypeStat& stat, const Type& t) {
260 #define X(x) \
261 if (t.strictSubtypeOf(T##x)) ++stat.sub_##x; \
262 if (t == T##x) ++stat.eq_##x;
263 STAT_TYPES
264 #undef X
267 //////////////////////////////////////////////////////////////////////
269 struct StatsSS : ISS {
270 explicit StatsSS(ISS& env, Stats& stats)
271 : ISS(env)
272 , stats(stats)
275 Stats& stats;
278 //////////////////////////////////////////////////////////////////////
280 template<class OpCode>
281 bool in(StatsSS& env, const OpCode&) {
282 return false;
285 bool in(StatsSS& env, const bc::IterInit& op) {
286 add_type(env.stats.iterInitBase, topC(env));
287 return false;
290 bool in(StatsSS& env, const bc::IterInitK& op) {
291 add_type(env.stats.iterInitKBase, topC(env));
292 return false;
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) {
299 case LL:
300 return env.state.locals[op.mvec.locBase->id];
301 case LC:
302 case LR:
303 return topT(env, pops - 1);
304 case LH:
305 case LGL:
306 case LGC:
307 case LNL:
308 case LNC:
309 case LSL:
310 case LSC:
311 return TTop;
312 case InvalidLocationCode:
313 break;
315 not_reached();
316 }();
317 add_type(env.stats.cgetmBase, ty);
318 return false;
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) {
329 reducible = false;
330 break;
334 default_dispatch(env, op);
336 auto builtin = op.str3;
338 BuiltinInfo::accessor acc;
339 auto inserted = env.stats.builtins.builtinsInfo.insert(acc, builtin);
340 if (inserted) {
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);
344 } else {
345 ++std::get<1>(acc->second);
346 if (reducible) ++std::get<2>(acc->second);
350 return true;
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, ...) \
362 case Op::opcode: \
363 if (!in(env, op.opcode)) default_dispatch(env, op); \
364 return;
366 switch (op.op) { OPCODES }
367 #undef O
368 not_reached();
371 //////////////////////////////////////////////////////////////////////
373 // Simple stats about opcodes (that don't require full type
374 // information---those cases are only enabled when extendedStats is
375 // on).
376 void collect_simple(Stats& stats, const Bytecode& bc) {
377 ++stats.op_counts[static_cast<uint64_t>(bc.op)];
379 RepoAuthType rat;
380 switch (bc.op) {
381 case Op::AssertRATL:
382 rat = bc.AssertRATL.rat;
383 break;
384 case Op::AssertRATStk:
385 rat = bc.AssertRATStk.rat;
386 break;
387 default:
388 return;
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];
396 } else {
397 ++stats.ratStk_tags[tagInt];
400 if (rat.mayHaveArrData()) {
401 if (rat.array()) {
402 if (bc.op == Op::AssertRATL) {
403 ++stats.ratL_specialized_array;
404 } else {
405 ++stats.ratStk_specialized_array;
411 void collect_func(Stats& stats, const Index& index, php::Func& func) {
412 if (!func.cls) {
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 };
449 dispatch(sss, bc);
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) {
474 ++stats.totalSProps;
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,
485 const Index& index,
486 const php::Program& program) {
487 parallel::for_each(
488 program.units,
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");
515 Stats stats{};
516 collect_stats(stats, index, program);
518 auto const str = show(stats);
519 if (Trace::moduleEnabledRelease(Trace::hhbbc_time, 2)) {
520 std::cout << str;
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;
536 close(fd);
537 return nullptr;
538 }();
540 if (file == nullptr) {
541 std::cerr << "couldn't open file for stats: "
542 << folly::errnoStr(errno) << '\n';
543 return;
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);
549 std::fflush(file);
552 //////////////////////////////////////////////////////////////////////