Teach hhbbc to optimize away LockObj.
[hiphop-php.git] / hphp / hhbbc / stats.cpp
blobfdc19736ad8dfe3c40a6715f169a48474dd33e79
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
125 struct Stats {
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;
143 TypeStat returns;
144 TypeStat privateProps;
145 TypeStat privateStatics;
146 TypeStat cgetmBase;
147 TypeStat iterInitBase;
148 TypeStat iterInitKBase;
149 Builtins builtins;
152 namespace {
154 void type_stat_string(std::string& ret,
155 const std::string& prefix,
156 const TypeStat& st) {
157 #define X(x) \
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());
162 STAT_TYPES
163 #undef X
164 ret += "\n";
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) {
179 folly::format(
180 &ret,
181 " {: >30} [tot:{: >8}, red:{: >8}]\t\ttype: {}\n",
182 it->first,
183 std::get<1>(it->second),
184 std::get<2>(it->second),
185 show(std::get<0>(it->second))
188 ret += "\n";
190 return ret;
193 std::string show(const Stats& stats) {
194 auto ret = std::string{};
196 for (auto i = uint32_t{}; i < stats.op_counts.size(); ++i) {
197 folly::format(
198 &ret,
199 " {: >20}: {: >15}\n",
200 opcodeToName(static_cast<Op>(i)),
201 stats.op_counts[i].load()
204 ret += "\n";
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);
213 folly::format(
214 &ret,
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"
223 "\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()
242 ret += "\n";
243 ret += show(stats.builtins);
245 ret += "\n";
246 using T = RepoAuthType::Tag;
247 using U = std::underlying_type<T>::type;
248 #define TAG(x) \
249 folly::format(&ret, " {: >24}: {: >8}\n" \
250 " {: >24}: {: >8}\n", \
251 "RATL_" #x, \
252 stats.ratL_tags[static_cast<U>(T::x)].load(), \
253 "RATStk_" #x, \
254 stats.ratStk_tags[static_cast<U>(T::x)].load());
255 REPO_AUTH_TYPE_TAGS
256 #undef TAG
258 folly::format(&ret, " {: >24}: {: >8}\n"
259 " {: >24}: {: >8}\n",
260 "RATL_Arr_Special",
261 stats.ratL_specialized_array.load(),
262 "RATStk_Arr_Special",
263 stats.ratStk_specialized_array.load());
265 return ret;
268 //////////////////////////////////////////////////////////////////////
270 void add_type(TypeStat& stat, const Type& t) {
271 #define X(x) \
272 if (t.strictSubtypeOf(T##x)) ++stat.sub_##x; \
273 if (t == T##x) ++stat.eq_##x;
274 STAT_TYPES
275 #undef X
278 //////////////////////////////////////////////////////////////////////
280 struct StatsSS : ISS {
281 explicit StatsSS(ISS& env, Stats& stats)
282 : ISS(env)
283 , stats(stats)
286 Stats& stats;
289 //////////////////////////////////////////////////////////////////////
291 template <class OpCode>
292 bool in(StatsSS& /*env*/, const OpCode&) {
293 return false;
296 bool in(StatsSS& env, const bc::IterInit& /*op*/) {
297 add_type(env.stats.iterInitBase, topC(env));
298 return false;
301 bool in(StatsSS& env, const bc::IterInitK& /*op*/) {
302 add_type(env.stats.iterInitKBase, topC(env));
303 return false;
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) {
314 reducible = false;
315 break;
319 default_dispatch(env, op);
321 auto builtin = op.str3;
323 BuiltinInfo::accessor acc;
324 auto inserted = env.stats.builtins.builtinsInfo.insert(acc, builtin);
325 if (inserted) {
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);
329 } else {
330 ++std::get<1>(acc->second);
331 if (reducible) ++std::get<2>(acc->second);
335 return true;
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, ...) \
347 case Op::opcode: \
348 if (!in(env, op.opcode)) default_dispatch(env, op); \
349 return;
351 switch (op.op) { OPCODES }
352 #undef O
353 not_reached();
356 //////////////////////////////////////////////////////////////////////
358 // Simple stats about opcodes (that don't require full type
359 // information---those cases are only enabled when extendedStats is
360 // on).
361 void collect_simple(Stats& stats, const Bytecode& bc) {
362 ++stats.op_counts[static_cast<uint64_t>(bc.op)];
364 RepoAuthType rat;
365 switch (bc.op) {
366 case Op::AssertRATL:
367 rat = bc.AssertRATL.rat;
368 break;
369 case Op::AssertRATStk:
370 rat = bc.AssertRATStk.rat;
371 break;
372 default:
373 return;
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];
381 } else {
382 ++stats.ratStk_tags[tagInt];
385 if (rat.mayHaveArrData()) {
386 if (rat.hasArrData()) {
387 if (bc.op == Op::AssertRATL) {
388 ++stats.ratL_specialized_array;
389 } else {
390 ++stats.ratStk_specialized_array;
396 void collect_func(Stats& stats, const Index& index, php::Func& func) {
397 if (!func.cls) {
398 if (is_pseudomain(&func)) {
399 ++stats.totalPseudoMains;
400 } else {
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;
411 } else {
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 };
449 dispatch(sss, bc);
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;
467 } else {
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) {
482 ++stats.totalSProps;
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,
493 const Index& index,
494 const php::Program& program) {
495 parallel::for_each(
496 program.units,
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;
520 stats = new Stats{};
523 StatsHolder::~StatsHolder() {
524 delete stats;
527 StatsHolder allocate_stats() {
528 return StatsHolder();
531 void collect_stats(const StatsHolder& stats,
532 const Index& index,
533 const php::Unit* unit) {
534 if (!stats) return;
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) {
548 if (!stats) return;
550 auto const str = show(*stats.stats);
551 if (Trace::moduleEnabledRelease(Trace::hhbbc_time, 2)) {
552 std::cout << str;
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;
568 close(fd);
569 return nullptr;
570 }();
572 if (file == nullptr) {
573 std::cerr << "couldn't open file for stats: "
574 << folly::errnoStr(errno) << '\n';
575 return;
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);
581 std::fflush(file);
584 //////////////////////////////////////////////////////////////////////