Make write_props be callable from policied code
[hiphop-php.git] / hphp / hhbbc / stats.cpp
blob3247efb83806b5fe27ca6b4c9bca04e476bfa9b5
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 SPEC_TYPES(X) \
63 X(wh, "wait handle", is_specialized_wait_handle) \
64 X(obj_sub, "sub obj", is_sub_obj) \
65 X(obj_exact, "exact obj", is_exact_obj) \
66 X(cls_sub, "sub class", is_sub_cls) \
67 X(cls_exact, "exact class", is_exact_cls) \
68 X(arr_val, "array value", is_specialized_array_like_arrval) \
69 X(arr_packedn, "array packedn", is_specialized_array_like_packedn) \
70 X(arr_packed, "array packed", is_specialized_array_like_packed) \
71 X(arr_mapn, "array mapn", is_specialized_array_like_mapn) \
72 X(arr_map, "array map", is_specialized_array_like_map) \
73 X(str, "string", is_specialized_string) \
74 X(lazy_cls, "lazy class", is_specialized_lazycls) \
75 X(int, "int", is_specialized_int) \
76 X(dbl, "double", is_specialized_double) \
77 X(record, "record", is_specialized_record) \
79 struct TypeStat {
80 #define X(y, ...) \
81 std::atomic<uint64_t> sub_##y{}; \
82 std::atomic<uint64_t> eq_##y{};
83 HHBBC_TYPE_PREDEFINED(X)
84 #undef X
86 #define X(y, ...) std::atomic<uint64_t> spec_##y;
87 SPEC_TYPES(X)
88 #undef X
91 struct ISameCmp {
92 bool equal(SString s1, SString s2) const {
93 return s1->isame(s2);
96 size_t hash(SString s) const {
97 return s->hash();
101 #define TAG(x) 1 +
102 constexpr uint32_t kNumRATTags = REPO_AUTH_TYPE_TAGS 0 ;
103 #undef TAG
107 struct Stats {
108 std::array<std::atomic<uint64_t>,Op_count> op_counts{};
109 std::array<std::atomic<uint64_t>,kNumRATTags> ratL_tags{};
110 std::array<std::atomic<uint64_t>,kNumRATTags> ratStk_tags{};
111 std::atomic<uint64_t> ratL_specialized_array{};
112 std::atomic<uint64_t> ratStk_specialized_array{};
113 std::atomic<uint64_t> totalClasses{};
114 std::atomic<uint64_t> totalRecords{};
115 std::atomic<uint64_t> totalFunctions{};
116 std::atomic<uint64_t> totalMethods{};
117 std::atomic<uint64_t> effectfulFuncs{};
118 std::atomic<uint64_t> effectFreeFuncs{};
119 std::atomic<uint64_t> persistentSPropsPub{};
120 std::atomic<uint64_t> persistentSPropsProt{};
121 std::atomic<uint64_t> persistentSPropsPriv{};
122 std::atomic<uint64_t> totalSProps{};
123 TypeStat returns;
124 TypeStat privateProps;
125 TypeStat publicStatics;
126 TypeStat privateStatics;
127 TypeStat iterInitBase;
130 namespace {
132 void type_stat_string(std::string& ret,
133 const std::string& prefix,
134 const TypeStat& st) {
135 #define X(y, ...) \
136 if (st.eq_##y.load() > 0) { \
137 folly::format(&ret, " {: <32} {: >12}\n", \
138 folly::sformat("{}_=_{}", prefix, #y ":"), \
139 st.eq_##y.load()); \
141 if (st.sub_##y.load() > 0) { \
142 folly::format(&ret, " {: <32} {: >12}\n", \
143 folly::sformat("{}_<_{}", prefix, #y ":"), \
144 st.sub_##y.load()); \
146 HHBBC_TYPE_PREDEFINED(X)
147 #undef X
149 ret += "\n";
151 #define X(y, z, ...) \
152 if (st.spec_##y.load() > 0) { \
153 folly::format(&ret, " {: <32} {: >12}\n", \
154 folly::sformat("{}_specialized {}", prefix, z ":"), \
155 st.spec_##y.load()); \
157 SPEC_TYPES(X)
158 #undef X
160 ret += "\n";
163 std::string show(const Stats& stats) {
164 auto ret = std::string{};
166 for (auto i = uint32_t{}; i < stats.op_counts.size(); ++i) {
167 if (stats.op_counts[i].load() == 0) continue;
168 folly::format(
169 &ret,
170 " {: >30}: {: >15}\n",
171 opcodeToName(static_cast<Op>(i)),
172 stats.op_counts[i].load()
175 ret += "\n";
177 type_stat_string(ret, "ret", stats.returns);
178 type_stat_string(ret, "priv_prop", stats.privateProps);
179 type_stat_string(ret, "pub_static", stats.publicStatics);
180 type_stat_string(ret, "priv_static", stats.privateStatics);
181 type_stat_string(ret, "iterInit_base", stats.iterInitBase);
183 folly::format(
184 &ret,
185 " total_methods: {: >12}\n"
186 " total_funcs: {: >12}\n"
187 " total_classes: {: >12}\n"
188 " total_records: {: >12}\n"
189 "\n"
190 " effectful_funcs: {: >12}\n"
191 " effect_free_funcs: {: >12}\n"
192 "\n"
193 " total_sprops: {: >12}\n"
194 " persistent_sprops_pub: {: >12}\n"
195 " persistent_sprops_prot: {: >12}\n"
196 " persistent_sprops_priv: {: >12}\n",
197 stats.totalMethods.load(),
198 stats.totalFunctions.load(),
199 stats.totalClasses.load(),
200 stats.totalRecords.load(),
201 stats.effectfulFuncs.load(),
202 stats.effectFreeFuncs.load(),
203 stats.totalSProps.load(),
204 stats.persistentSPropsPub.load(),
205 stats.persistentSPropsProt.load(),
206 stats.persistentSPropsPriv.load()
209 ret += "\n";
210 using T = RepoAuthType::Tag;
211 using U = std::underlying_type<T>::type;
212 #define TAG(x) \
213 if (stats.ratL_tags[static_cast<U>(T::x)].load() > 0 || \
214 stats.ratStk_tags[static_cast<U>(T::x)].load() > 0) { \
215 folly::format(&ret, \
216 " {: >28}: {: >12}\n" \
217 " {: >28}: {: >12}\n", \
218 "RATL_" #x, \
219 stats.ratL_tags[static_cast<U>(T::x)].load(), \
220 "RATStk_" #x, \
221 stats.ratStk_tags[static_cast<U>(T::x)].load()); \
223 REPO_AUTH_TYPE_TAGS
224 #undef TAG
226 if (stats.ratL_specialized_array.load() > 0 ||
227 stats.ratStk_specialized_array.load() > 0) {
228 folly::format(&ret,
229 " {: >28}: {: >12}\n"
230 " {: >28}: {: >12}\n",
231 "RATL_Arr_Special",
232 stats.ratL_specialized_array.load(),
233 "RATStk_Arr_Special",
234 stats.ratStk_specialized_array.load());
237 return ret;
240 //////////////////////////////////////////////////////////////////////
242 bool is_sub_obj(const Type& t) {
243 return
244 is_specialized_obj(t) &&
245 !is_specialized_wait_handle(t) &&
246 dobj_of(t).type == DObj::Sub;
248 bool is_exact_obj(const Type& t) {
249 return
250 is_specialized_obj(t) &&
251 !is_specialized_wait_handle(t) &&
252 dobj_of(t).type == DObj::Exact;
255 bool is_sub_cls(const Type& t) {
256 return is_specialized_cls(t) && dcls_of(t).type == DCls::Sub;
258 bool is_exact_cls(const Type& t) {
259 return is_specialized_cls(t) && dcls_of(t).type == DCls::Exact;
262 void add_type(TypeStat& stat, const Type& t) {
263 #define X(y, ...) \
264 if (t.subtypeOf(B##y)) { \
265 if (B##y == BBottom || !t.is(BBottom)) { \
266 if (t.strictSubtypeOf(B##y)) ++stat.sub_##y; \
267 else ++stat.eq_##y; \
270 HHBBC_TYPE_PREDEFINED(X)
271 #undef X
273 #define X(a, b, c) if (c(t)) ++stat.spec_##a;
274 SPEC_TYPES(X)
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 //////////////////////////////////////////////////////////////////////
303 // Run the interpreter. The "bool in(StatsSS&, const bc::XX)" functions
304 // can call the default dispatch on their own or return false to let
305 // the main loop call the default dispatch. Returning true stops
306 // the call to the default interpreter which implies the handler for
307 // that opcode must perform all the right steps wrt the state.
308 void dispatch(StatsSS& env, const Bytecode& op) {
309 #define O(opcode, ...) \
310 case Op::opcode: \
311 if (!in(env, op.opcode)) default_dispatch(env, op); \
312 return;
314 switch (op.op) { OPCODES }
315 #undef O
316 not_reached();
319 //////////////////////////////////////////////////////////////////////
321 // Simple stats about opcodes (that don't require full type
322 // information---those cases are only enabled when extendedStats is
323 // on).
324 void collect_simple(Stats& stats, const Bytecode& bc) {
325 ++stats.op_counts[static_cast<uint64_t>(bc.op)];
327 RepoAuthType rat;
328 switch (bc.op) {
329 case Op::AssertRATL:
330 rat = bc.AssertRATL.rat;
331 break;
332 case Op::AssertRATStk:
333 rat = bc.AssertRATStk.rat;
334 break;
335 default:
336 return;
339 using U = std::underlying_type<RepoAuthType::Tag>::type;
340 auto const tagInt = static_cast<U>(rat.tag());
341 assertx(tagInt < stats.ratL_tags.size());
342 if (bc.op == Op::AssertRATL) {
343 ++stats.ratL_tags[tagInt];
344 } else {
345 ++stats.ratStk_tags[tagInt];
348 if (rat.mayHaveArrData()) {
349 if (rat.hasArrData()) {
350 if (bc.op == Op::AssertRATL) {
351 ++stats.ratL_specialized_array;
352 } else {
353 ++stats.ratStk_specialized_array;
359 void collect_func(Stats& stats, const Index& index, const php::Func& func) {
360 if (!func.cls) ++stats.totalFunctions;
362 if (index.is_effect_free(&func)) {
363 ++stats.effectFreeFuncs;
364 } else {
365 ++stats.effectfulFuncs;
368 auto const ty = index.lookup_return_type_raw(&func).first;
369 add_type(stats.returns, ty);
371 auto const cf = php::WideFunc::cns(&func);
372 for (auto const bid : cf.blockRange()) {
373 auto const blk = cf.blocks()[bid].get();
374 if (blk->dead) continue;
375 for (auto& bc : blk->hhbcs) {
376 collect_simple(stats, bc);
380 if (!options.extendedStats) return;
382 auto const ctx = AnalysisContext { func.unit, cf, func.cls };
383 auto const fa = analyze_func(index, ctx, CollectionOpts{});
385 Trace::Bump bumper{Trace::hhbbc, kStatsBump};
386 for (auto const bid : cf.blockRange()) {
387 auto const blk = cf.blocks()[bid].get();
388 auto state = fa.bdata[bid].stateIn;
389 if (!state.initialized) continue;
391 CollectedInfo collect {
392 index, ctx, nullptr, CollectionOpts {}, &fa
394 Interp interp { index, ctx, collect, bid, blk, state };
395 for (auto& bc : blk->hhbcs) {
396 auto noop = [] (BlockId, const State*) {};
397 ISS env { interp, noop };
398 StatsSS sss { env, stats };
399 dispatch(sss, bc);
400 if (state.unreachable) break;
406 void collect_record(Stats& stats, const php::Record& rec) {
407 ++stats.totalRecords;
410 void collect_class(Stats& stats, const Index& index, const php::Class& cls) {
411 ++stats.totalClasses;
412 stats.totalMethods += cls.methods.size();
414 for (auto const& kv : index.lookup_private_props(&cls)) {
415 add_type(stats.privateProps, kv.second.ty);
417 for (auto const& kv : index.lookup_public_statics(&cls)) {
418 add_type(stats.publicStatics, kv.second.ty);
420 for (auto const& kv : index.lookup_private_statics(&cls)) {
421 add_type(stats.privateStatics, kv.second.ty);
424 for (auto& prop : cls.properties) {
425 if (prop.attrs & AttrStatic) {
426 ++stats.totalSProps;
427 if (prop.attrs & AttrPersistent) {
428 if (prop.attrs & AttrPublic) ++stats.persistentSPropsPub;
429 if (prop.attrs & AttrProtected) ++stats.persistentSPropsProt;
430 if (prop.attrs & AttrPrivate) ++stats.persistentSPropsPriv;
436 void collect_stats(Stats& stats,
437 const Index& index,
438 const php::Program& program) {
439 parallel::for_each(
440 program.units,
441 [&] (const std::unique_ptr<php::Unit>& unit) {
442 for (auto const& c : unit->classes) {
443 collect_class(stats, index, *c);
444 for (auto const& m : c->methods) {
445 collect_func(stats, index, *m);
448 for (auto const& r : unit->records) {
449 collect_record(stats, *r);
451 for (auto const& x : unit->funcs) {
452 collect_func(stats, index, *x);
458 //////////////////////////////////////////////////////////////////////
462 //////////////////////////////////////////////////////////////////////
464 StatsHolder::StatsHolder() {
465 if (!Trace::moduleEnabledRelease(Trace::hhbbc_time, 1)) return;
466 stats = new Stats{};
469 StatsHolder::~StatsHolder() {
470 delete stats;
473 StatsHolder allocate_stats() {
474 return StatsHolder();
477 void collect_stats(const StatsHolder& stats,
478 const Index& index,
479 const php::Unit* unit) {
480 if (!stats) return;
481 for (auto& c : unit->classes) {
482 collect_class(*stats.stats, index, *c);
483 for (auto& m : c->methods) {
484 collect_func(*stats.stats, index, *m);
487 for (auto& r : unit->records) {
488 collect_record(*stats.stats, *r);
490 for (auto& x : unit->funcs) {
491 collect_func(*stats.stats, index, *x);
495 void print_stats(const StatsHolder& stats) {
496 if (!stats) return;
498 auto const str = show(*stats.stats);
499 if (Trace::moduleEnabledRelease(Trace::hhbbc_time, 2)) {
500 std::cout << str;
503 auto stats_file = options.stats_file;
505 auto const file = [&] () -> std::FILE* {
506 if (!stats_file.empty()) {
507 return fopen(stats_file.c_str(), "w");
510 char fileBuf[] = "/tmp/hhbbcXXXXXX";
511 auto const fd = mkstemp(fileBuf);
512 stats_file = fileBuf;
513 if (fd == -1) return nullptr;
515 if (auto const fp = fdopen(fd, "w")) return fp;
516 close(fd);
517 return nullptr;
518 }();
520 if (file == nullptr) {
521 std::cerr << "couldn't open file for stats: "
522 << folly::errnoStr(errno) << '\n';
523 return;
526 SCOPE_EXIT { fclose(file); };
527 std::cout << "stats saved to " << stats_file << '\n';
528 std::fwrite(str.c_str(), sizeof(char), str.size(), file);
529 std::fflush(file);
532 //////////////////////////////////////////////////////////////////////