Rename RefcountProfile to IncRefProfile
[hiphop-php.git] / hphp / runtime / vm / jit / prof-data-serialize.cpp
blobad19901cfe6924ff2ee54299a74c328773c29f5d
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 +----------------------------------------------------------------------+
17 #include "hphp/runtime/vm/jit/prof-data-serialize.h"
19 #include "hphp/runtime/base/builtin-functions.h"
20 #include "hphp/runtime/base/program-functions.h"
21 #include "hphp/runtime/base/string-data.h"
22 #include "hphp/runtime/base/timestamp.h"
23 #include "hphp/runtime/base/type-structure-helpers-defs.h"
24 #include "hphp/runtime/base/unit-cache.h"
25 #include "hphp/runtime/base/variable-serializer.h"
27 #include "hphp/runtime/ext/std/ext_std_closure.h"
29 #include "hphp/runtime/vm/class.h"
30 #include "hphp/runtime/vm/func.h"
31 #include "hphp/runtime/vm/instance-bits.h"
32 #include "hphp/runtime/vm/jit/array-kind-profile.h"
33 #include "hphp/runtime/vm/jit/array-offset-profile.h"
34 #include "hphp/runtime/vm/jit/call-target-profile.h"
35 #include "hphp/runtime/vm/jit/cls-cns-profile.h"
36 #include "hphp/runtime/vm/jit/containers.h"
37 #include "hphp/runtime/vm/jit/decref-profile.h"
38 #include "hphp/runtime/vm/jit/incref-profile.h"
39 #include "hphp/runtime/vm/jit/inlining-decider.h"
40 #include "hphp/runtime/vm/jit/meth-profile.h"
41 #include "hphp/runtime/vm/jit/prof-data.h"
42 #include "hphp/runtime/vm/jit/release-vv-profile.h"
43 #include "hphp/runtime/vm/jit/switch-profile.h"
44 #include "hphp/runtime/vm/jit/trans-cfg.h"
45 #include "hphp/runtime/vm/jit/type-profile.h"
46 #include "hphp/runtime/vm/named-entity-defs.h"
47 #include "hphp/runtime/vm/named-entity.h"
48 #include "hphp/runtime/vm/repo-global-data.h"
49 #include "hphp/runtime/vm/repo.h"
50 #include "hphp/runtime/vm/treadmill.h"
51 #include "hphp/runtime/vm/type-profile.h"
52 #include "hphp/runtime/vm/unit.h"
54 #include "hphp/util/boot-stats.h"
55 #include "hphp/util/build-info.h"
56 #include "hphp/util/managed-arena.h"
57 #include "hphp/util/numa.h"
58 #include "hphp/util/process.h"
60 #include <folly/portability/Unistd.h>
61 #include <folly/String.h>
63 namespace HPHP { namespace jit {
64 //////////////////////////////////////////////////////////////////////
66 namespace {
67 //////////////////////////////////////////////////////////////////////
69 TRACE_SET_MOD(hhbc);
71 StaticString s_invoke("__invoke");
72 StaticString s_86ctor("86ctor");
73 StaticString s_86pinit("86pinit");
74 StaticString s_86sinit("86sinit");
75 StaticString s_86linit("86linit");
77 constexpr uint32_t k86pinitSlot = 0x80000000u;
78 constexpr uint32_t k86sinitSlot = 0x80000001u;
79 constexpr uint32_t k86linitSlot = 0x80000002u;
81 template<typename F>
82 auto deserialize(ProfDataDeserializer&ser, F&& f) -> decltype(f()) {
83 using T = decltype(f());
84 auto const ptr = read_raw<uintptr_t>(ser);
85 if (!ptr) return T{};
86 if (ptr & 1) {
87 auto& ent = ser.getEnt(reinterpret_cast<T>(ptr - 1));
88 assertx(!ent);
89 ent = f();
90 ITRACE(3, "0x{:08x} => 0x{:08x}\n",
91 ptr - 1, reinterpret_cast<uintptr_t>(ent));
92 assertx(ent);
93 return ent;
95 auto const ent = ser.getEnt(reinterpret_cast<T>(ptr));
96 assertx(ent);
97 return ent;
100 void write_serialized_ptr(ProfDataSerializer& ser, const void* p) {
101 auto const ptr_as_int = reinterpret_cast<uintptr_t>(p);
102 assertx(!(ptr_as_int & 1));
103 write_raw(ser, ptr_as_int | 1);
106 void write_raw_ptr(ProfDataSerializer& ser, const void* p) {
107 auto const ptr_as_int = reinterpret_cast<uintptr_t>(p);
108 assertx(!(ptr_as_int & 1));
109 write_raw(ser, ptr_as_int);
113 * Helper functions so that the function passed to write_container can
114 * take a serializer as a parameter, or skip it (eg if its a lambda
115 * which is already capturing the serializer).
117 template<typename S, typename T, typename F>
118 auto call(F& f, S& ser, const T& t) -> decltype(f(ser, t)) {
119 return f(ser, t);
122 template<typename S, typename T, typename F>
123 auto call(F& f, S&, const T& t) -> decltype(f(t)) {
124 return f(t);
127 template<typename C, typename F>
128 void write_container(ProfDataSerializer& ser, const C& cont, F f) {
129 write_raw(ser, safe_cast<uint32_t>(cont.size()));
130 for (auto const &elm : cont) {
131 call(f, ser, elm);
135 template<typename F>
136 void read_container(ProfDataDeserializer& ser, F f) {
137 auto sz = read_raw<uint32_t>(ser);
138 while (sz--) { f(); }
141 void write_srckey(ProfDataSerializer& ser, SrcKey sk) {
142 ITRACE(2, "SrcKey>\n");
143 if (ser.serialize(sk.func())) {
144 Trace::Indent _i;
145 write_raw(ser, uintptr_t(-1));
146 write_func(ser, sk.func());
148 write_raw(ser, sk.toAtomicInt());
149 ITRACE(2, "SrcKey: {}\n", show(sk));
152 SrcKey read_srckey(ProfDataDeserializer& ser) {
153 ITRACE(2, "SrcKey>\n");
154 auto orig = read_raw<SrcKey::AtomicInt>(ser);
155 if (orig == uintptr_t(-1)) {
156 Trace::Indent _i;
157 read_func(ser);
158 orig = read_raw<SrcKey::AtomicInt>(ser);
160 auto const id = SrcKey::fromAtomicInt(orig).funcID();
161 assertx(uint32_t(orig) == id);
162 auto const sk = SrcKey::fromAtomicInt(orig - id + ser.getFid(id));
163 ITRACE(2, "SrcKey: {}\n", show(sk));
164 return sk;
167 void write_type(ProfDataSerializer& ser, Type t) {
168 t.serialize(ser);
171 Type read_type(ProfDataDeserializer& ser) {
172 return Type::deserialize(ser);
175 void write_typed_location(ProfDataSerializer& ser,
176 const RegionDesc::TypedLocation& loc) {
177 write_raw(ser, loc.location);
178 write_type(ser, loc.type);
181 void write_guarded_location(ProfDataSerializer& ser,
182 const RegionDesc::GuardedLocation& loc) {
183 write_raw(ser, loc.location);
184 write_type(ser, loc.type);
185 write_raw(ser, loc.category);
188 RegionDesc::TypedLocation read_typed_location(ProfDataDeserializer& ser) {
189 auto const location = read_raw<Location>(ser);
190 auto const type = read_type(ser);
191 return { location, type };
194 RegionDesc::GuardedLocation read_guarded_location(ProfDataDeserializer& ser) {
195 auto const location = read_raw<Location>(ser);
196 auto const type = read_type(ser);
197 auto const cat = read_raw<DataTypeCategory>(ser);
198 return { location, type, cat };
201 void write_global_array_map(ProfDataSerializer& ser) {
202 write_container(ser, globalArrayTypeTable(),
203 write_raw<const RepoAuthType::Array*>);
206 void read_global_array_map(ProfDataDeserializer& ser) {
207 BootStats::Block timer("DES_read_global_array_map",
208 RuntimeOption::ServerExecutionMode());
209 auto sz DEBUG_ONLY = read_raw<uint32_t>(ser);
210 assertx(sz == globalArrayTypeTable().size());
211 for (auto arr : globalArrayTypeTable()) {
212 auto const orig = read_raw<const RepoAuthType::Array*>(ser);
213 ser.recordRat(orig, arr);
217 void write_region_block(ProfDataSerializer& ser,
218 const RegionDesc::BlockPtr& block) {
219 write_raw(ser, block->id());
220 write_srckey(ser, block->start());
221 write_raw(ser, block->length());
222 write_raw(ser, block->initialSpOffset());
223 write_raw(ser, block->profTransID());
224 write_container(ser, block->typePredictions(), write_typed_location);
225 write_container(ser, block->typePreConditions(), write_guarded_location);
226 write_container(ser, block->knownFuncs(),
227 [] (ProfDataSerializer& s,
228 std::pair<SrcKey, const Func*> knownFunc) {
229 write_srckey(s, knownFunc.first);
230 write_func(s, knownFunc.second);
232 write_container(ser, block->postConds().changed, write_typed_location);
233 write_container(ser, block->postConds().refined, write_typed_location);
236 RegionDesc::BlockPtr read_region_block(ProfDataDeserializer& ser) {
237 auto const id = read_raw<RegionDesc::BlockId>(ser);
238 auto const start = read_srckey(ser);
239 auto const length = read_raw<int>(ser);
240 auto const initialSpOffset = read_raw<FPInvOffset>(ser);
242 auto const block = std::make_shared<RegionDesc::Block>(id,
243 start.func(),
244 start.resumeMode(),
245 start.hasThis(),
246 start.offset(),
247 length,
248 initialSpOffset);
250 block->setProfTransID(read_raw<TransID>(ser));
252 read_container(ser,
253 [&] {
254 block->addPredicted(read_typed_location(ser));
257 read_container(ser,
258 [&] {
259 block->addPreCondition(read_guarded_location(ser));
262 read_container(ser,
263 [&] {
264 auto const sk = read_srckey(ser);
265 auto const func = read_func(ser);
266 block->setKnownFunc(sk, func);
269 PostConditions postConds;
270 read_container(ser,
271 [&] {
272 postConds.changed.push_back(read_typed_location(ser));
274 read_container(ser,
275 [&] {
276 postConds.refined.push_back(read_typed_location(ser));
278 block->setPostConds(postConds);
279 return block;
282 void write_region_desc(ProfDataSerializer& ser, const RegionDesc* rd) {
283 write_container(ser, rd->blocks(), write_region_block);
284 write_container(ser, findPredTrans(*rd, profData()),
285 [&] (RegionDesc::BlockId id) {
286 write_raw(ser, id);
288 write_container(ser, rd->blocks(),
289 [&] (const RegionDesc::BlockPtr& b) {
290 auto const bid = b->id();
291 write_raw(ser, bid);
292 assertx(rd->succs(bid).empty());
293 assertx(rd->preds(bid).empty());
294 assertx(rd->merged(bid).empty());
295 auto const pr = rd->prevRetrans(bid);
296 write_raw(ser, pr ? pr.value() : kInvalidTransID);
297 auto const nr = rd->nextRetrans(bid);
298 write_raw(ser, nr ? nr.value() : kInvalidTransID);
300 write_type(ser, rd->inlineCtxType());
301 write_container(ser, rd->inlineInputTypes(), write_type);
304 RegionDescPtr read_region_desc(ProfDataDeserializer& ser) {
305 auto ret = std::make_shared<RegionDesc>();
306 read_container(ser, [&] { ret->addBlock(read_region_block(ser)); });
307 RegionDesc::BlockIdSet incoming;
308 read_container(ser,
309 [&] { incoming.insert(read_raw<RegionDesc::BlockId>(ser)); });
310 ret->incoming(std::move(incoming));
312 read_container(ser,
313 [&] {
314 auto const id = read_raw<RegionDesc::BlockId>(ser);
315 auto const pr = read_raw<RegionDesc::BlockId>(ser);
316 auto const nr = read_raw<RegionDesc::BlockId>(ser);
317 if (pr != kInvalidTransID) {
318 ret->setNextRetrans(pr, id);
320 if (nr != kInvalidTransID) {
321 ret->setNextRetrans(id, nr);
324 auto const ty = read_type(ser);
325 std::vector<Type> args;
326 read_container(ser,
327 [&] {
328 args.push_back(read_type(ser));
330 ret->setInlineContext(ty, args);
331 return ret;
334 void write_prof_trans_rec(ProfDataSerializer& ser,
335 const ProfTransRec* ptr,
336 ProfData* pd) {
337 if (!ptr) return write_raw(ser, TransKind{});
338 write_raw(ser, ptr->kind());
339 write_srckey(ser, ptr->srcKey());
340 if (ptr->kind() == TransKind::Profile) {
341 write_raw(ser, ptr->lastBcOff());
342 write_region_desc(ser, ptr->region().get());
343 } else {
344 write_raw(ser, ptr->prologueArgs());
346 auto lock = ptr->lockCallerList();
347 std::vector<TransID> callers;
348 auto addCaller = [&] (TCA caller) {
349 if (!tc::isProfileCodeAddress(caller)) return;
350 auto const callerTransId = pd->jmpTransID(caller);
351 assertx(callerTransId != kInvalidTransID);
352 callers.push_back(callerTransId);
354 for (auto const caller : ptr->mainCallers()) addCaller(caller);
355 for (auto const caller : ptr->guardCallers()) addCaller(caller);
356 write_container(ser, callers, write_raw<TransID>);
360 std::unique_ptr<ProfTransRec> read_prof_trans_rec(ProfDataDeserializer& ser) {
361 auto const kind = read_raw<TransKind>(ser);
362 static_assert(TransKind::Profile != TransKind{} &&
363 TransKind::ProfPrologue != TransKind{},
364 "Profile and ProfPrologue must not be zero");
365 if (kind == TransKind{}) return nullptr;
366 auto const sk = read_srckey(ser);
367 if (kind == TransKind::Profile) {
368 auto const lastBcOff = read_raw<Offset>(ser);
369 auto const region = read_region_desc(ser);
370 return std::make_unique<ProfTransRec>(lastBcOff, sk, region);
373 auto ret = std::make_unique<ProfTransRec>(sk, read_raw<int>(ser));
375 read_container(ser,
376 [&] {
377 ret->profCallers().push_back(read_raw<TransID>(ser));
380 return ret;
383 bool write_type_alias(ProfDataSerializer& ser, const TypeAliasReq* td);
385 bool write_type_alias_or_class(ProfDataSerializer& ser, const NamedEntity* ne) {
386 if (!ne) return false;
387 if (auto const cls = ne->clsList()) {
388 if (!(cls->attrs() & AttrUnique)) return false;
389 if (!cls->wasSerialized()) write_class(ser, cls);
390 return true;
392 if (!ne->isPersistentTypeAlias()) return false;
393 return write_type_alias(ser, ne->getCachedTypeAlias());
396 bool write_type_alias(ProfDataSerializer& ser, const TypeAliasReq* td) {
397 SCOPE_EXIT {
398 ITRACE(2, "TypeAlias: {}\n", td->name);
400 ITRACE(2, "TypeAlias>\n");
401 // we're writing out Class* and TypeAliasReq* intermingled here. Set
402 // bit 1 for TypeAliasReq's so we can distinguish when reading back
403 // in. We also need to keep track of both whether we already tried
404 // serialize this one, and whether it was successful. Use td2 for
405 // that too.
406 auto const td2 = reinterpret_cast<const char*>(td) + 2;
407 if (!ser.serialize(td)) {
408 return ser.wasSerialized(td2);
410 Trace::Indent _;
412 auto const unit = td->unit;
413 auto const name = td->name;
414 auto const tas = unit->typeAliases();
415 for (auto const& ta : tas) {
416 if (ta.name == name) {
417 switch (get_ts_kind(ta.typeStructure.get())) {
418 case TypeStructure::Kind::T_unresolved: {
419 auto const clsname = get_ts_classname(ta.typeStructure.get());
420 if (!write_type_alias_or_class(ser,
421 NamedEntity::get(clsname, false))) {
422 return false;
424 break;
426 case TypeStructure::Kind::T_void:
427 case TypeStructure::Kind::T_int:
428 case TypeStructure::Kind::T_bool:
429 case TypeStructure::Kind::T_float:
430 case TypeStructure::Kind::T_string:
431 case TypeStructure::Kind::T_resource:
432 case TypeStructure::Kind::T_num:
433 case TypeStructure::Kind::T_arraykey:
434 case TypeStructure::Kind::T_noreturn:
435 case TypeStructure::Kind::T_tuple:
436 case TypeStructure::Kind::T_array:
437 case TypeStructure::Kind::T_darray:
438 case TypeStructure::Kind::T_varray:
439 case TypeStructure::Kind::T_varray_or_darray:
440 case TypeStructure::Kind::T_dict:
441 case TypeStructure::Kind::T_vec:
442 case TypeStructure::Kind::T_keyset:
443 case TypeStructure::Kind::T_vec_or_dict:
444 case TypeStructure::Kind::T_arraylike:
445 case TypeStructure::Kind::T_nonnull:
446 break;
448 case TypeStructure::Kind::T_fun:
449 case TypeStructure::Kind::T_typevar:
450 case TypeStructure::Kind::T_shape:
451 case TypeStructure::Kind::T_typeaccess:
452 case TypeStructure::Kind::T_xhp:
453 case TypeStructure::Kind::T_mixed:
454 case TypeStructure::Kind::T_reifiedtype:
455 return false;
457 case TypeStructure::Kind::T_class:
458 case TypeStructure::Kind::T_interface:
459 case TypeStructure::Kind::T_trait:
460 case TypeStructure::Kind::T_enum:
461 // these types don't occur in unresolved TypeStructures
462 always_assert(false);
465 if (!ser.serialize(td2)) always_assert(false);
466 write_serialized_ptr(ser, td2);
467 write_unit(ser, td->unit);
468 write_string(ser, td->name);
469 return true;
472 return false;
475 Class* read_class_internal(ProfDataDeserializer& ser) {
476 auto const id = read_raw<decltype(std::declval<PreClass*>()->id())>(ser);
477 auto const unit = read_unit(ser);
479 read_container(ser,
480 [&] {
481 auto const dep = read_class(ser);
482 auto const ne = dep->preClass()->namedEntity();
483 // if it's not persistent, make sure that dep
484 // is the active class for this NamedEntity
485 assertx(ne->m_cachedClass.bound());
486 if (ne->m_cachedClass.isNormal()) {
487 ne->setCachedClass(dep);
489 auto const depName = read_string(ser);
490 if (!dep->name()->isame(depName)) {
491 // this dependent was referred to via a
492 // class_alias, so we need to make sure
493 // *that* points to the class too
494 auto const aliasNe = NamedEntity::get(depName);
495 aliasNe->m_cachedClass.bind(rds::Mode::Normal);
496 if (aliasNe->m_cachedClass.isNormal()) {
497 aliasNe->m_cachedClass.markUninit();
499 aliasNe->setCachedClass(dep);
503 auto const preClass = unit->lookupPreClassId(id);
504 folly::Optional<TypeAliasReq> enumBaseReq;
505 SCOPE_EXIT {
506 if (enumBaseReq) {
507 preClass->enumBaseTy().namedEntity()->m_cachedTypeAlias.markUninit();
510 if (preClass->attrs() & AttrEnum &&
511 preClass->enumBaseTy().isObject()) {
512 auto const dt = read_raw<DataType>(ser);
513 auto const ne = preClass->enumBaseTy().namedEntity();
514 if (!ne->m_cachedTypeAlias.bound() ||
515 !ne->m_cachedTypeAlias.isInit()) {
516 enumBaseReq.emplace();
517 enumBaseReq->type = dt == KindOfInt64 ?
518 AnnotType::Int : AnnotType::String;
519 enumBaseReq->name = preClass->enumBaseTy().typeName();
520 ne->m_cachedTypeAlias.bind(rds::Mode::Normal);
521 ne->m_cachedTypeAlias.initWith(*enumBaseReq);
524 auto const ne = preClass->namedEntity();
525 // If it's not persistent, make sure its NamedEntity is
526 // unbound, ready for DefClass
527 if (ne->m_cachedClass.bound() &&
528 ne->m_cachedClass.isNormal()) {
529 ne->m_cachedClass.markUninit();
532 auto const cls = Unit::defClass(preClass, true);
533 if (cls->pinitVec().size()) cls->initPropHandle();
534 if (cls->numStaticProperties()) cls->initSPropHandles();
536 if (cls->parent() == c_Closure::classof()) {
537 auto const ctx = read_class(ser);
538 if (ctx != cls) return cls->rescope(ctx);
540 return cls;
544 * This reads in TypeAliases and Classes that are used for code gen,
545 * but otherwise aren't needed for profiling. We just need them to be
546 * loaded into the NamedEntity table, so this function just returns
547 * whether or not to continue (the end of the list is marked by a
548 * null pointer).
550 bool read_type_alias_or_class(ProfDataDeserializer& ser) {
551 auto const ptr = read_raw<uintptr_t>(ser);
552 if (!ptr) return false;
553 assertx(ptr & 1);
555 if (!(ptr & 2)) {
556 ITRACE(2, "Class>\n");
557 Trace::Indent _;
558 auto& ent = ser.getEnt(reinterpret_cast<Class*>(ptr - 1));
559 assertx(!ent);
560 ent = read_class_internal(ser);
561 assertx(ent);
562 ITRACE(2, "Class: {}\n", ent->name());
563 return true;
566 auto const unit = read_unit(ser);
567 auto const name = read_string(ser);
568 ITRACE(2, "TypeAlias: {}\n", name);
569 auto const tas = unit->typeAliases();
570 Id id = 0;
571 for (auto const& ta : tas) {
572 if (ta.name == name) {
573 unit->defTypeAlias(id);
574 return true;
576 ++id;
578 always_assert(false);
581 void write_profiled_funcs(ProfDataSerializer& ser, ProfData* pd) {
582 auto const maxFuncId = pd->maxProfilingFuncId();
584 for (FuncId fid = 0; fid <= maxFuncId; fid++) {
585 if (!Func::isFuncIdValid(fid) || !pd->profiling(fid)) continue;
586 write_func(ser, Func::fromFuncId(fid));
588 write_raw(ser, uintptr_t{});
591 void read_profiled_funcs(ProfDataDeserializer& ser, ProfData* pd) {
592 while (auto const func = read_func(ser)) {
593 pd->setProfiling(func->getFuncId());
597 void write_classes_and_type_aliases(ProfDataSerializer& ser, ProfData* pd) {
598 auto const maxFuncId = pd->maxProfilingFuncId();
600 // in an attempt to get a sensible order for these, start with the
601 // ones referenced by params and return constraints.
602 for (FuncId fid = 0; fid <= maxFuncId; fid++) {
603 if (!Func::isFuncIdValid(fid) || !pd->profiling(fid)) continue;
604 auto const func = Func::fromFuncId(fid);
605 for (auto const& p : func->params()) {
606 write_type_alias_or_class(ser, p.typeConstraint.namedEntity());
610 // Now just iterate and write anything that remains
611 NamedEntity::foreach_name(
612 [&] (NamedEntity& ne) {
613 write_type_alias_or_class(ser, &ne);
616 write_raw(ser, uintptr_t{});
619 void read_classes_and_type_aliases(ProfDataDeserializer& ser) {
620 BootStats::Block timer("DES_read_classes_and_type_aliases",
621 RuntimeOption::ServerExecutionMode());
622 while (read_type_alias_or_class(ser)) {
623 // nothing to do. this was just to make sure everything is loaded
624 // into the NamedEntity table
628 void write_prof_data(ProfDataSerializer& ser, ProfData* pd) {
629 write_profiled_funcs(ser, pd);
631 write_raw(ser, pd->counterDefault());
632 pd->forEachTransRec(
633 [&] (const ProfTransRec* ptr) {
634 auto const transID = ptr->isProfile() ?
635 ptr->region()->entry()->profTransID() :
636 pd->proflogueTransId(ptr->func(), ptr->prologueArgs());
637 write_raw(ser, transID);
638 write_prof_trans_rec(ser, ptr, pd);
639 // forEachTransRec already grabs a read lock, and we're not
640 // going to add a *new* counter here (so we don't need a write
641 // lock).
642 write_raw(ser, *pd->transCounterAddrNoLock(transID));
645 write_raw(ser, kInvalidTransID);
648 void read_prof_data(ProfDataDeserializer& ser, ProfData* pd) {
649 BootStats::Block timer("DES_read_prof_data",
650 RuntimeOption::ServerExecutionMode());
651 read_profiled_funcs(ser, pd);
653 pd->resetCounters(read_raw<int64_t>(ser));
654 while (true) {
655 auto const transID = read_raw<TransID>(ser);
656 if (transID == kInvalidTransID) break;
657 pd->addProfTrans(transID, read_prof_trans_rec(ser));
658 *pd->transCounterAddr(transID) = read_raw<int64_t>(ser);
662 template<typename T>
663 auto write_impl(ProfDataSerializer& ser, const T& out, bool) ->
664 decltype(std::declval<T&>().serialize(ser),void()) {
665 out.serialize(ser);
668 template<typename T>
669 void write_impl(ProfDataSerializer& ser, const T& out, int) {
670 write_raw(ser, out);
673 template<typename T>
674 void write_maybe_serializable(ProfDataSerializer& ser, const T& out) {
675 write_impl(ser, out, false);
678 struct TargetProfileVisitor : boost::static_visitor<void> {
679 TargetProfileVisitor(ProfDataSerializer& ser,
680 const rds::Symbol& sym,
681 rds::Handle handle,
682 uint32_t size) :
683 ser{ser},
684 sym{sym},
685 handle{handle},
686 size{size} {}
688 template<typename T>
689 void process(T& out, const StringData* name) {
690 write_raw(ser, size);
691 write_string(ser, name);
692 write_raw(ser, sym);
693 TargetProfile<T>::reduce(out, handle, size);
694 if (size == sizeof(T)) {
695 write_maybe_serializable(ser, out);
696 } else {
697 write_raw(ser, &out, size);
701 template<typename T> void operator()(const T&) {}
702 template<typename T>
703 void operator()(const rds::Profile<T>& pt) {
704 if (size == sizeof(T)) {
705 T out{};
706 process(out, pt.name.get());
707 } else {
708 auto const mem = calloc(1, size);
709 SCOPE_EXIT { free(mem); };
710 process(*reinterpret_cast<T*>(mem), pt.name.get());
714 ProfDataSerializer& ser;
715 const rds::Symbol& sym;
716 rds::Handle handle;
717 uint32_t size;
720 void write_target_profiles(ProfDataSerializer& ser) {
721 rds::visitSymbols(
722 [&] (const rds::Symbol& symbol, rds::Handle handle, uint32_t size) {
723 TargetProfileVisitor tv(ser, symbol, handle, size);
724 boost::apply_visitor(tv, symbol);
727 write_raw(ser, uint32_t{});
730 template<typename T>
731 auto read_impl(ProfDataDeserializer& ser, T& out, bool) ->
732 decltype(out.deserialize(ser),void()) {
733 out.deserialize(ser);
736 template<typename T>
737 void read_impl(ProfDataDeserializer& ser, T& out, int) {
738 read_raw(ser, out);
741 template<typename T>
742 void read_maybe_serializable(ProfDataDeserializer& ser, T& out) {
743 read_impl(ser, out, false);
746 struct SymbolFixup : boost::static_visitor<void> {
747 SymbolFixup(ProfDataDeserializer& ser, StringData* name, uint32_t size) :
748 ser{ser}, name{name}, size{size} {}
750 template<typename T> void operator()(T&) { always_assert(false); }
751 template<typename T>
752 void operator()(rds::Profile<T>& pt) {
753 TargetProfile<T> prof(pt.transId,
754 TransKind::Profile,
755 pt.bcOff,
756 name,
757 size - sizeof(T));
759 if (size == sizeof(T)) {
760 read_maybe_serializable(ser, prof.value());
761 } else {
762 read_raw(ser, &prof.value(), size);
766 ProfDataDeserializer& ser;
767 StringData* name;
768 // The size of the original rds allocation.
769 uint32_t size;
772 void read_target_profiles(ProfDataDeserializer& ser) {
773 BootStats::Block timer("DES_read_target_profiles",
774 RuntimeOption::ServerExecutionMode());
775 while (true) {
776 auto const size = read_raw<uint32_t>(ser);
777 if (!size) break;
778 auto const name = read_string(ser);
779 auto sym = read_raw<rds::Symbol>(ser);
780 auto sf = SymbolFixup{ser, name, size};
781 boost::apply_visitor(sf, sym);
785 void merge_loaded_units(int numWorkers) {
786 BootStats::Block timer("DES_merge_loaded_units",
787 RuntimeOption::ServerExecutionMode());
788 auto units = loadedUnitsRepoAuth();
790 std::vector<std::thread> workers;
791 // Compute a batch size that causes each thread to process approximately 16
792 // batches. Even if the batches are somewhat imbalanced in what they contain,
793 // the straggler workers are very unlikey to take more than 10% longer than
794 // the first worker to finish.
795 auto const batchSize{std::max(units.size() / numWorkers / 16, size_t(1))};
796 std::atomic<size_t> index{0};
797 UNUSED std::atomic_int curr_node{0};
798 for (auto worker = 0; worker < numWorkers; ++worker) {
799 workers.push_back(std::thread([&] {
800 ProfileNonVMThread nonVM;
801 #if USE_JEMALLOC_EXTENT_HOOKS
802 auto const numaNode = next_numa_node(curr_node);
803 #ifdef HAVE_NUMA
804 if (use_numa) {
805 s_numaNode = numaNode;
806 numa_sched_setaffinity(0, node_to_cpu_mask[numaNode]);
808 #endif
809 if (auto arena = next_extra_arena(numaNode)) {
810 arena->bindCurrentThread();
812 #endif
814 hphp_thread_init();
815 hphp_session_init(Treadmill::SessionKind::PreloadRepo);
817 while (true) {
818 auto begin = index.fetch_add(batchSize);
819 auto end = std::min(begin + batchSize, units.size());
820 if (begin >= end) break;
821 auto unitCount = end - begin;
822 for (auto i = size_t{0}; i < unitCount; ++i) {
823 auto const unit = units[begin + i];
824 try {
825 unit->merge();
826 } catch (...) {
827 // swallow errors silently. persistent things should raise
828 // errors, and we don't really care about merging
829 // non-persistent things.
834 hphp_context_exit();
835 hphp_session_exit();
836 hphp_thread_exit();
837 }));
839 for (auto& worker : workers) {
840 worker.join();
844 ////////////////////////////////////////////////////////////////////////////////
847 ProfDataSerializer::ProfDataSerializer(const std::string& name)
848 : fileName(name) {
849 // Delete old profile data to avoid confusion. This should've happened from
850 // outside the process, but in case it didn't happen, try to do it here.
851 unlink(name.c_str());
853 std::string partialFile = name + ".part";
854 fd = open(partialFile.c_str(),
855 O_CLOEXEC | O_CREAT | O_TRUNC | O_WRONLY, 0644);
856 if (fd == -1) {
857 auto const msg =
858 folly::sformat("Failed to open file for write {}, {}", name,
859 folly::errnoStr(errno));
860 Logger::Error(msg);
861 throw std::runtime_error(msg);
865 void ProfDataSerializer::finalize() {
866 assertx(fd != -1);
867 if (offset) ::write(fd, buffer, offset);
868 offset = 0;
869 close(fd);
870 fd = -1;
871 std::string partialFile = fileName + ".part";
872 if (rename(partialFile.c_str(), fileName.c_str()) == -1) {
873 auto const msg =
874 folly::sformat("Failed to rename {} to {}, {}",
875 partialFile, fileName, folly::errnoStr(errno));
876 Logger::Error(msg);
877 throw std::runtime_error(msg);
878 } else {
879 FTRACE(1, "Finished serializing profile data to " + fileName);
883 ProfDataSerializer::~ProfDataSerializer() {
884 if (fd != -1) {
885 // We didn't finalize(), maybe because an exception was thrown while writing
886 // the data. The file is likely corrupt or incomplete, so discard it.
887 ftruncate(fd, 0);
888 close(fd);
889 std::string partialFile = fileName + ".part";
890 unlink(partialFile.c_str());
894 std::string ProfDataDeserializer::s_buildHost;
895 int64_t ProfDataDeserializer::s_buildTime{0};
897 ProfDataDeserializer::ProfDataDeserializer(const std::string& name) {
898 fd = open(name.c_str(), O_CLOEXEC | O_RDONLY);
899 if (fd == -1) throw std::runtime_error("Failed to open: " + name);
902 ProfDataDeserializer::~ProfDataDeserializer() {
903 assertx(fd != -1);
904 close(fd);
907 bool ProfDataDeserializer::done() {
908 char byte;
909 return offset == buffer_size && ::read(fd, &byte, 1) == 0;
912 void write_raw(ProfDataSerializer& ser, const void* data, size_t sz) {
913 if (ser.offset + sz <= ProfDataSerializer::buffer_size) {
914 memcpy(ser.buffer + ser.offset, data, sz);
915 ser.offset += sz;
916 return;
918 if (ser.offset == 0) {
919 if (::write(ser.fd, data, sz) != sz) {
920 throw std::runtime_error("Failed to write serialized data");
922 return;
924 if (auto const delta = ProfDataSerializer::buffer_size - ser.offset) {
925 memcpy(ser.buffer + ser.offset, data, delta);
926 data = static_cast<const char*>(data) + delta;
927 sz -= delta;
928 ser.offset = ProfDataSerializer::buffer_size;
930 assertx(ser.offset == ProfDataSerializer::buffer_size);
931 if (::write(ser.fd, ser.buffer, ser.offset) != ser.offset) {
932 throw std::runtime_error("Failed to write serialized data");
934 ser.offset = 0;
935 write_raw(ser, data, sz);
938 void read_raw(ProfDataDeserializer& ser, void* data, size_t sz) {
939 if (ser.offset + sz <= ProfDataDeserializer::buffer_size) {
940 memcpy(data, ser.buffer + ser.offset, sz);
941 ser.offset += sz;
942 return;
944 if (auto const delta = ProfDataDeserializer::buffer_size - ser.offset) {
945 memcpy(data, ser.buffer + ser.offset, delta);
946 data = static_cast<char*>(data) + delta;
947 sz -= delta;
948 ser.offset = ProfDataDeserializer::buffer_size;
950 if (sz >= ProfDataDeserializer::buffer_size) {
951 auto const bytes_read = ::read(ser.fd, data, sz);
952 if (bytes_read < 0 || bytes_read < sz) {
953 throw std::runtime_error("Failed to read serialized data");
955 return;
958 auto const bytes_read = ::read(ser.fd,
959 ser.buffer,
960 ProfDataDeserializer::buffer_size);
961 if (bytes_read < 0 || bytes_read < sz) {
962 throw std::runtime_error("Failed to read serialized data");
964 ser.offset = ProfDataDeserializer::buffer_size - bytes_read;
965 if (ser.offset) {
966 memmove(ser.buffer + ser.offset, ser.buffer, bytes_read);
968 return read_raw(ser, data, sz);
971 StringData*& ProfDataDeserializer::getEnt(const StringData* p) {
972 return stringMap[uintptr_t(p)];
975 ArrayData*& ProfDataDeserializer::getEnt(const ArrayData* p) {
976 return arrayMap[uintptr_t(p)];
979 Unit*& ProfDataDeserializer::getEnt(const Unit* p) {
980 return unitMap[uintptr_t(p)];
983 Func*& ProfDataDeserializer::getEnt(const Func* p) {
984 return funcMap[uintptr_t(p)];
987 Class*& ProfDataDeserializer::getEnt(const Class* p) {
988 return classMap[uintptr_t(p)];
991 const RepoAuthType::Array*&
992 ProfDataDeserializer::getEnt(const RepoAuthType::Array* p) {
993 return ratMap[uintptr_t(p)];
996 bool ProfDataSerializer::serialize(const Unit* unit) {
997 return unit->serialize();
1000 bool ProfDataSerializer::serialize(const Func* func) {
1001 return func->serialize();
1004 bool ProfDataSerializer::serialize(const Class* cls) {
1005 return cls->serialize();
1008 void write_string(ProfDataSerializer& ser, const StringData* str) {
1009 if (!ser.serialize(str)) return write_raw(ser, str);
1010 write_serialized_ptr(ser, str);
1011 uint32_t sz = str->size();
1012 write_raw(ser, sz);
1013 write_raw(ser, str->data(), sz);
1016 StringData* read_string(ProfDataDeserializer& ser) {
1017 return deserialize(
1018 ser,
1019 [&] () -> StringData* {
1020 auto const sz = read_raw<uint32_t>(ser);
1021 String s{sz, ReserveStringMode{}};
1022 auto const range = s.bufferSlice();
1023 read_raw(ser, range.begin(), sz);
1024 s.setSize(sz);
1025 return makeStaticString(s);
1030 void write_array(ProfDataSerializer& ser, const ArrayData* arr) {
1031 if (!ser.serialize(arr)) return write_raw(ser, arr);
1032 write_serialized_ptr(ser, arr);
1033 auto const str = internal_serialize(VarNR(const_cast<ArrayData*>(arr)));
1034 uint32_t sz = str.size();
1035 write_raw(ser, sz);
1036 write_raw(ser, str.data(), sz);
1039 ArrayData* read_array(ProfDataDeserializer& ser) {
1040 return deserialize(
1041 ser,
1042 [&] () -> ArrayData* {
1043 auto const sz = read_raw<uint32_t>(ser);
1044 String s{sz, ReserveStringMode{}};
1045 auto const range = s.bufferSlice();
1046 read_raw(ser, range.begin(), sz);
1047 s.setSize(sz);
1048 auto v = unserialize_from_buffer(
1049 s.data(),
1050 s.size(),
1051 VariableUnserializer::Type::Internal
1053 return ArrayData::GetScalarArray(std::move(v));
1058 void write_unit(ProfDataSerializer& ser, const Unit* unit) {
1059 if (!ser.serialize(unit)) return write_raw(ser, unit);
1060 ITRACE(2, "Unit: {}\n", unit->filepath());
1061 write_serialized_ptr(ser, unit);
1062 write_string(ser, unit->filepath());
1065 Unit* read_unit(ProfDataDeserializer& ser) {
1066 return deserialize(
1067 ser,
1068 [&] () -> Unit* {
1069 auto const filepath = read_string(ser);
1070 ITRACE(2, "Unit: {}\n", filepath);
1071 auto& nativeFuncs = Native::s_noNativeFuncs;
1072 if (filepath->data()[0] == '/' && filepath->data()[1] == ':') {
1073 return lookupSyslibUnit(filepath, nativeFuncs);
1075 return lookupUnit(filepath, "", nullptr, nativeFuncs);
1080 template<typename C1, typename C2, typename F>
1081 void visit_deps(const C1& c1, const C2& c2, F& f) {
1082 auto it = c2.begin();
1083 auto const DEBUG_ONLY end = c2.end();
1084 for (auto const& dep : c1) {
1085 assertx(it != end);
1086 f(dep.get(), *it++);
1090 void write_class(ProfDataSerializer& ser, const Class* cls) {
1091 SCOPE_EXIT {
1092 ITRACE(2, "Class: {}\n", cls ? cls->name() : staticEmptyString());
1094 ITRACE(2, "Class>\n");
1095 Trace::Indent _;
1097 if (!cls || !ser.serialize(cls)) return write_raw(ser, cls);
1099 write_serialized_ptr(ser, cls);
1100 write_raw(ser, cls->preClass()->id());
1101 write_unit(ser, cls->preClass()->unit());
1103 jit::vector<std::pair<const Class*, const StringData*>> dependents;
1104 auto record_dep = [&] (const Class* dep, const StringData* depName) {
1105 if (!dep) return;
1106 if (!dep->wasSerialized() ||
1107 !classHasPersistentRDS(dep) ||
1108 !dep->name()->isame(depName)) {
1109 dependents.emplace_back(dep, depName);
1112 record_dep(cls->parent(), cls->preClass()->parent());
1114 visit_deps(cls->declInterfaces(), cls->preClass()->interfaces(), record_dep);
1116 if (cls->preClass()->attrs() & AttrNoExpandTrait) {
1117 for (auto const tName : cls->preClass()->usedTraits()) {
1118 auto const trait = Unit::lookupUniqueClassInContext(tName, nullptr);
1119 assertx(trait);
1120 record_dep(trait, tName);
1122 } else {
1123 visit_deps(cls->usedTraitClasses(),
1124 cls->preClass()->usedTraits(),
1125 record_dep);
1128 write_container(ser, dependents,
1129 [&] (const std::pair<const Class*, const StringData*> &dep) {
1130 write_class(ser, dep.first);
1131 write_string(ser, dep.second);
1134 if (cls->attrs() & AttrEnum &&
1135 cls->preClass()->enumBaseTy().isObject()) {
1136 if (cls->enumBaseTy()) {
1137 write_raw(ser, *cls->enumBaseTy());
1138 } else {
1139 write_raw(ser, KindOfUninit);
1143 if (cls->parent() == c_Closure::classof()) {
1144 auto const func = cls->lookupMethod(s_invoke.get());
1145 assertx(func);
1146 write_class(ser, func->cls());
1150 Class* read_class(ProfDataDeserializer& ser) {
1151 ITRACE(2, "Class>\n");
1152 auto const ret = deserialize(
1153 ser,
1154 [&] () -> Class* {
1155 Trace::Indent _;
1156 return read_class_internal(ser);
1160 ITRACE(2, "Class: {}\n", ret ? ret->name() : staticEmptyString());
1161 return ret;
1164 void write_func(ProfDataSerializer& ser, const Func* func) {
1165 SCOPE_EXIT {
1166 ITRACE(2, "Func: {}\n", func ? func->fullName() : staticEmptyString());
1168 ITRACE(2, "Func>\n");
1169 Trace::Indent _;
1171 if (!func || !ser.serialize(func)) return write_raw(ser, func);
1173 write_serialized_ptr(ser, func);
1174 uint32_t fid = func->getFuncId();
1175 assertx(!(fid & 0x80000000));
1176 if (func == SystemLib::s_nullCtor ||
1177 (!func->isMethod() && !func->isPseudoMain() && func->isBuiltin())) {
1178 if (func == SystemLib::s_nullCtor) {
1179 assertx(func->name()->isame(s_86ctor.get()));
1181 fid = ~fid;
1182 write_raw(ser, fid);
1183 return write_string(ser, func->name());
1185 write_raw(ser, fid);
1186 if (func->isPseudoMain()) {
1187 const uint32_t zero = 0;
1188 write_raw(ser, zero);
1189 write_unit(ser, func->unit());
1190 return write_class(ser, func->cls());
1193 if (func->isMethod()) {
1194 auto const* cls = func->implCls();
1195 auto const nslot = [&] () -> uint32_t {
1196 const uint32_t slot = func->methodSlot();
1197 if (cls->getMethod(slot) != func) {
1198 if (func->name() == s_86pinit.get()) return k86pinitSlot;
1199 if (func->name() == s_86sinit.get()) return k86sinitSlot;
1200 if (func->name() == s_86linit.get()) return k86linitSlot;
1201 cls = getOwningClassForFunc(func);
1202 assertx(cls->getMethod(slot) == func);
1204 return ~slot;
1205 }();
1206 assertx(nslot & 0x80000000);
1207 write_raw(ser, nslot);
1208 return write_class(ser, cls);
1211 // Ideally we'd write the func's index in its Unit; but we may not
1212 // have that after Unit::initial_merge
1213 const uint32_t off = func->base();
1214 assertx(off && !(off & 0x80000000));
1215 write_raw(ser, off);
1216 write_unit(ser, func->unit());
1219 Func* read_func(ProfDataDeserializer& ser) {
1220 ITRACE(2, "Func>\n");
1221 auto const ret = deserialize(
1222 ser,
1223 [&] () -> Func* {
1224 Trace::Indent _;
1225 auto fid = read_raw<uint32_t>(ser);
1226 auto const func = [&] () -> const Func* {
1227 if (fid & 0x80000000) {
1228 fid = ~fid;
1229 auto const name = read_string(ser);
1230 if (name->isame(s_86ctor.get())) return SystemLib::s_nullCtor;
1231 return Unit::lookupFunc(name);
1233 auto const id = read_raw<uint32_t>(ser);
1234 if (!id) {
1235 auto const unit = read_unit(ser);
1236 return unit->getMain(read_class(ser));
1238 if (id & 0x80000000) {
1239 auto const cls = read_class(ser);
1240 if (id == k86pinitSlot) return cls->get86pinit();
1241 if (id == k86sinitSlot) return cls->get86sinit();
1242 if (id == k86linitSlot) return cls->get86linit();
1243 const Slot slot = ~id;
1244 return cls->getMethod(slot);
1246 auto const unit = read_unit(ser);
1247 for (auto const f : unit->funcs()) {
1248 if (f->base() == id) {
1249 Unit::bindFunc(f);
1250 auto const handle = f->funcHandle();
1251 if (!rds::isPersistentHandle(handle) &&
1252 (!rds::isHandleInit(handle, rds::NormalTag{}) ||
1253 rds::handleToRef<LowPtr<Func>,
1254 rds::Mode::Normal>(handle).get() != f)) {
1255 rds::uninitHandle(handle);
1256 Unit::defFunc(f, false);
1258 return f;
1261 not_reached();
1262 }();
1263 ser.recordFid(fid, func->getFuncId());
1264 return const_cast<Func*>(func);
1267 ITRACE(2, "Func: {}\n", ret ? ret->fullName() : staticEmptyString());
1268 return ret;
1271 std::string serializeProfData(const std::string& filename) {
1272 try {
1273 ProfDataSerializer ser{filename};
1275 write_raw(ser, Repo::get().global().Signature);
1276 auto schema = repoSchemaId();
1277 write_raw(ser, schema.size());
1278 write_raw(ser, schema.begin(), schema.size());
1280 auto host = Process::GetHostName();
1281 write_raw(ser, host.size());
1282 write_raw(ser, &host[0], host.size());
1283 write_raw(ser, TimeStamp::Current());
1285 Func::s_treadmill = true;
1286 hphp_thread_init();
1287 hphp_session_init(Treadmill::SessionKind::ProfData);
1288 requestInitProfData();
1290 SCOPE_EXIT {
1291 requestExitProfData();
1292 hphp_context_exit();
1293 hphp_session_exit();
1294 hphp_thread_exit();
1295 Func::s_treadmill = false;
1298 InstanceBits::init();
1299 InstanceBits::serialize(ser);
1300 write_global_array_map(ser);
1302 auto const pd = profData();
1303 write_prof_data(ser, pd);
1305 write_target_profiles(ser);
1307 // We've written everything directly referenced by the profile
1308 // data, but jitted code might still use Classes and TypeAliasReqs
1309 // that haven't been otherwise mentioned (eg VerifyParamType,
1310 // InstanceOfD etc).
1311 write_classes_and_type_aliases(ser, pd);
1313 ser.finalize();
1315 return "";
1316 } catch (std::runtime_error& err) {
1317 return folly::sformat("Failed to serialize profile data: {}", err.what());
1321 std::string deserializeProfData(const std::string& filename, int numWorkers) {
1322 try {
1323 ProfDataDeserializer ser{filename};
1325 auto signature = read_raw<decltype(Repo::get().global().Signature)>(ser);
1326 if (signature != Repo::get().global().Signature) {
1327 throw std::runtime_error("Mismatched repo-schema");
1329 auto size = read_raw<size_t>(ser);
1330 std::string schema;
1331 schema.resize(size);
1332 read_raw(ser, &schema[0], size);
1333 if (schema != repoSchemaId()) {
1334 throw std::runtime_error("Mismatched repo-schema");
1337 size = read_raw<size_t>(ser);
1338 ProfDataDeserializer::s_buildHost.resize(size);
1339 read_raw(ser, &ProfDataDeserializer::s_buildHost[0], size);
1341 int64_t buildTime;
1342 read_raw(ser, buildTime);
1343 auto const currTime = TimeStamp::Current();
1344 if (buildTime <= currTime - 3600 * RuntimeOption::ProfDataTTLHours) {
1345 throw std::runtime_error(
1346 "Stale profile data (check Eval.ProfDataTTLHours)");
1347 } else if (buildTime >= currTime) {
1348 throw std::runtime_error("profile data dumped in the future?");
1350 ProfDataDeserializer::s_buildTime = buildTime;
1352 InstanceBits::deserialize(ser);
1353 read_global_array_map(ser);
1355 ProfData::Session pds;
1356 auto const pd = profData();
1357 read_prof_data(ser, pd);
1358 pd->setDeserialized();
1360 read_target_profiles(ser);
1362 read_classes_and_type_aliases(ser);
1364 always_assert(ser.done());
1366 // During deserialization we didn't merge the loaded units because
1367 // we wanted to pick and choose the hot Funcs and Classes. But we
1368 // need to merge them before we start serving traffic to ensure we
1369 // don't have inconsistentcies (eg a persistent memoized Func
1370 // wrapper might have been merged, while its implementation was
1371 // not; since the implementation has an internal name, there won't
1372 // be an autoload entry for it, so unless something else causes
1373 // the unit to be loaded the implementation might never get pulled
1374 // in (resulting in fatals when the wrapper tries to call it).
1375 merge_loaded_units(numWorkers);
1377 return "";
1378 } catch (std::runtime_error& err) {
1379 return folly::sformat("Failed to deserialize profile data {}: {}",
1380 filename, err.what());
1384 //////////////////////////////////////////////////////////////////////