Improve refineLocation a little
[hiphop-php.git] / hphp / hhbbc / stats.cpp
blob6592f25467bee84ec5e6d90d92d0ea96d0c60de4
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
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"
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>
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 //////////////////////////////////////////////////////////////////////
56 namespace {
58 TRACE_SET_MOD(hhbbc_stats);
60 //////////////////////////////////////////////////////////////////////
62 #define STAT_TYPES \
63 X(Gen) \
64 X(InitGen) \
65 X(Ref) \
66 X(Cell) \
67 X(InitCell) \
68 X(Unc) \
69 X(InitUnc) \
70 X(Obj) \
71 X(OptObj) \
72 X(Arr) \
73 X(SArr) \
74 X(ArrE) \
75 X(ArrN) \
76 X(SArrN) \
77 X(SArrE) \
78 X(Null) \
79 X(Bottom)
81 struct TypeStat {
82 #define X(x) std::atomic<uint64_t> sub_##x; \
83 std::atomic<uint64_t> eq_##x;
84 STAT_TYPES
85 #undef X
88 struct ISameCmp {
89 bool equal(SString s1, SString s2) const {
90 return s1->isame(s2);
93 size_t hash(SString s) const {
94 return s->hash();
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<
108 SString,
109 std::tuple<Type,uint64_t,uint64_t>,
110 ISameCmp
113 struct Builtins {
114 std::atomic<uint64_t> totalBuiltins;
115 std::atomic<uint64_t> reducibleBuiltins;
116 BuiltinInfo builtinsInfo{};
119 #define TAG(x) 1 +
120 constexpr uint32_t kNumRATTags = REPO_AUTH_TYPE_TAGS 0 ;
121 #undef TAG
123 struct Stats {
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;
141 TypeStat returns;
142 TypeStat privateProps;
143 TypeStat privateStatics;
144 TypeStat cgetmBase;
145 TypeStat iterInitBase;
146 TypeStat iterInitKBase;
147 Builtins builtins;
150 void type_stat_string(std::string& ret,
151 const std::string& prefix,
152 const TypeStat& st) {
153 #define X(x) \
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());
158 STAT_TYPES
159 #undef X
160 ret += "\n";
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) {
175 folly::format(
176 &ret,
177 " {: >30} [tot:{: >8}, red:{: >8}]\t\ttype: {}\n",
178 it->first,
179 std::get<1>(it->second),
180 std::get<2>(it->second),
181 show(std::get<0>(it->second))
184 ret += "\n";
186 return ret;
189 std::string show(const Stats& stats) {
190 auto ret = std::string{};
192 for (auto i = uint32_t{}; i < stats.op_counts.size(); ++i) {
193 folly::format(
194 &ret,
195 " {: >20}: {: >15}\n",
196 opcodeToName(static_cast<Op>(i)),
197 stats.op_counts[i].load()
200 ret += "\n";
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);
209 folly::format(
210 &ret,
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"
219 "\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()
238 ret += "\n";
239 ret += show(stats.builtins);
241 ret += "\n";
242 using T = RepoAuthType::Tag;
243 using U = std::underlying_type<T>::type;
244 #define TAG(x) \
245 folly::format(&ret, " {: >24}: {: >8}\n" \
246 " {: >24}: {: >8}\n", \
247 "RATL_" #x, \
248 stats.ratL_tags[static_cast<U>(T::x)].load(), \
249 "RATStk_" #x, \
250 stats.ratStk_tags[static_cast<U>(T::x)].load());
251 REPO_AUTH_TYPE_TAGS
252 #undef TAG
254 folly::format(&ret, " {: >24}: {: >8}\n"
255 " {: >24}: {: >8}\n",
256 "RATL_Arr_Special",
257 stats.ratL_specialized_array.load(),
258 "RATStk_Arr_Special",
259 stats.ratStk_specialized_array.load());
261 return ret;
264 //////////////////////////////////////////////////////////////////////
266 void add_type(TypeStat& stat, const Type& t) {
267 #define X(x) \
268 if (t.strictSubtypeOf(T##x)) ++stat.sub_##x; \
269 if (t == T##x) ++stat.eq_##x;
270 STAT_TYPES
271 #undef X
274 //////////////////////////////////////////////////////////////////////
276 struct StatsSS : ISS {
277 explicit StatsSS(ISS& env, Stats& stats)
278 : ISS(env)
279 , stats(stats)
282 Stats& stats;
285 //////////////////////////////////////////////////////////////////////
287 template <class OpCode>
288 bool in(StatsSS& /*env*/, const OpCode&) {
289 return false;
292 bool in(StatsSS& env, const bc::IterInit& /*op*/) {
293 add_type(env.stats.iterInitBase, topC(env));
294 return false;
297 bool in(StatsSS& env, const bc::IterInitK& /*op*/) {
298 add_type(env.stats.iterInitKBase, topC(env));
299 return false;
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) {
310 reducible = false;
311 break;
315 default_dispatch(env, op);
317 auto builtin = op.str3;
319 BuiltinInfo::accessor acc;
320 auto inserted = env.stats.builtins.builtinsInfo.insert(acc, builtin);
321 if (inserted) {
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);
325 } else {
326 ++std::get<1>(acc->second);
327 if (reducible) ++std::get<2>(acc->second);
331 return true;
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, ...) \
343 case Op::opcode: \
344 if (!in(env, op.opcode)) default_dispatch(env, op); \
345 return;
347 switch (op.op) { OPCODES }
348 #undef O
349 not_reached();
352 //////////////////////////////////////////////////////////////////////
354 // Simple stats about opcodes (that don't require full type
355 // information---those cases are only enabled when extendedStats is
356 // on).
357 void collect_simple(Stats& stats, const Bytecode& bc) {
358 ++stats.op_counts[static_cast<uint64_t>(bc.op)];
360 RepoAuthType rat;
361 switch (bc.op) {
362 case Op::AssertRATL:
363 rat = bc.AssertRATL.rat;
364 break;
365 case Op::AssertRATStk:
366 rat = bc.AssertRATStk.rat;
367 break;
368 default:
369 return;
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];
377 } else {
378 ++stats.ratStk_tags[tagInt];
381 if (rat.mayHaveArrData()) {
382 if (rat.hasArrData()) {
383 if (bc.op == Op::AssertRATL) {
384 ++stats.ratL_specialized_array;
385 } else {
386 ++stats.ratStk_specialized_array;
392 void collect_func(Stats& stats, const Index& index, php::Func& func) {
393 if (!func.cls) {
394 if (is_pseudomain(&func)) {
395 ++stats.totalPseudoMains;
396 } else {
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;
407 } else {
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& blk : func.blocks) {
419 if (blk->id == NoBlockId) continue;
420 for (auto& bc : blk->hhbcs) {
421 collect_simple(stats, bc);
425 if (!options.extendedStats) return;
427 auto const ctx = Context { func.unit, &func, func.cls };
428 auto const fa = analyze_func(index, ctx,
429 CollectionOpts::TrackConstantArrays);
431 Trace::Bump bumper{Trace::hhbbc, kStatsBump};
432 for (auto& blk : func.blocks) {
433 if (blk->id == NoBlockId) continue;
434 auto state = fa.bdata[blk->id].stateIn;
435 if (!state.initialized) continue;
437 CollectedInfo collect {
438 index, ctx, nullptr, nullptr, CollectionOpts {}, &fa
440 Interp interp { index, ctx, collect, borrow(blk), state };
441 for (auto& bc : blk->hhbcs) {
442 auto noop = [] (BlockId, const State*) {};
443 auto flags = StepFlags {};
444 ISS env { interp, flags, noop };
445 StatsSS sss { env, stats };
446 dispatch(sss, bc);
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;
463 } else {
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);
472 for (auto& kv : index.lookup_private_statics(&cls)) {
473 add_type(stats.privateStatics, kv.second);
476 for (auto& prop : cls.properties) {
477 if (prop.attrs & AttrStatic) {
478 ++stats.totalSProps;
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,
489 const Index& index,
490 const php::Program& program) {
491 parallel::for_each(
492 program.units,
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");
519 Stats stats{};
520 collect_stats(stats, index, program);
522 auto const str = show(stats);
523 if (Trace::moduleEnabledRelease(Trace::hhbbc_time, 2)) {
524 std::cout << str;
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;
540 close(fd);
541 return nullptr;
542 }();
544 if (file == nullptr) {
545 std::cerr << "couldn't open file for stats: "
546 << folly::errnoStr(errno) << '\n';
547 return;
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);
553 std::fflush(file);
556 //////////////////////////////////////////////////////////////////////