hphp/runtime/vm/jit/prof-data-serialize.cpp (1,656 lines of code) (raw):

/* +----------------------------------------------------------------------+ | HipHop for PHP | +----------------------------------------------------------------------+ | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) | +----------------------------------------------------------------------+ | This source file is subject to version 3.01 of the PHP license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.php.net/license/3_01.txt | | If you did not receive a copy of the PHP license and are unable to | | obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ */ #include "hphp/runtime/vm/jit/prof-data-serialize.h" #include "hphp/runtime/base/builtin-functions.h" #include "hphp/runtime/base/program-functions.h" #include "hphp/runtime/base/string-data.h" #include "hphp/runtime/base/timestamp.h" #include "hphp/runtime/base/types.h" #include "hphp/runtime/base/type-structure-helpers-defs.h" #include "hphp/runtime/base/unit-cache.h" #include "hphp/runtime/base/variable-serializer.h" #include "hphp/runtime/base/vm-worker.h" #include "hphp/runtime/ext/extension-registry.h" #include "hphp/runtime/ext/std/ext_std_closure.h" #include "hphp/runtime/vm/class.h" #include "hphp/runtime/vm/func.h" #include "hphp/runtime/vm/instance-bits.h" #include "hphp/runtime/vm/jit/array-access-profile.h" #include "hphp/runtime/vm/jit/array-iter-profile.h" #include "hphp/runtime/vm/jit/array-layout.h" #include "hphp/runtime/vm/jit/call-target-profile.h" #include "hphp/runtime/vm/jit/cls-cns-profile.h" #include "hphp/runtime/vm/jit/containers.h" #include "hphp/runtime/vm/jit/cow-profile.h" #include "hphp/runtime/vm/jit/decref-profile.h" #include "hphp/runtime/vm/jit/func-order.h" #include "hphp/runtime/vm/jit/incref-profile.h" #include "hphp/runtime/vm/jit/inlining-decider.h" #include "hphp/runtime/vm/jit/meth-profile.h" #include "hphp/runtime/vm/jit/prof-data.h" #include "hphp/runtime/vm/jit/region-selection.h" #include "hphp/runtime/vm/jit/switch-profile.h" #include "hphp/runtime/vm/jit/tc-internal.h" #include "hphp/runtime/vm/jit/tc-record.h" #include "hphp/runtime/vm/jit/trans-cfg.h" #include "hphp/runtime/vm/jit/type-profile.h" #include "hphp/runtime/vm/jit/vasm-block-counters.h" #include "hphp/runtime/vm/named-entity-defs.h" #include "hphp/runtime/vm/named-entity.h" #include "hphp/runtime/vm/property-profile.h" #include "hphp/runtime/vm/repo-file.h" #include "hphp/runtime/vm/repo-global-data.h" #include "hphp/runtime/vm/treadmill.h" #include "hphp/runtime/vm/type-profile.h" #include "hphp/runtime/vm/unit.h" #include "hphp/util/boot-stats.h" #include "hphp/util/build-info.h" #include "hphp/util/job-queue.h" #include "hphp/util/logger.h" #include "hphp/util/managed-arena.h" #include "hphp/util/match.h" #include "hphp/util/numa.h" #include "hphp/util/process.h" #include "hphp/util/service-data.h" #include <folly/portability/Unistd.h> #include <folly/String.h> namespace HPHP { namespace jit { ////////////////////////////////////////////////////////////////////// namespace { ////////////////////////////////////////////////////////////////////// TRACE_SET_MOD(hhbc); StaticString s_invoke("__invoke"); constexpr uint32_t kMagic = 0x4d564848; constexpr uint32_t k86pinitSlot = 0x80000000u; constexpr uint32_t k86sinitSlot = 0x80000001u; constexpr uint32_t k86linitSlot = 0x80000002u; enum class SeenType : uint8_t { Class, TypeAlias, End }; constexpr ProfDataSerializer::Id kSerializedIdBit = (ProfDataSerializer::Id{1} << (std::numeric_limits<ProfDataSerializer::Id>::digits - 1)); ProfDataSerializer::Id read_id(ProfDataDeserializer& ser) { auto const id = read_raw<ProfDataSerializer::Id>(ser); always_assert(!(id & kSerializedIdBit)); return id; } Unit* deserializeImpl(ProfDataDeserializer& ser, ProfDataSerializer::Id id, Unit*) { return ser.getUnit(id); } Func* deserializeImpl(ProfDataDeserializer& ser, ProfDataSerializer::Id id, Func*) { return ser.getFunc(id); } Class* deserializeImpl(ProfDataDeserializer& ser, ProfDataSerializer::Id id, Class*) { return ser.getClass(id); } const TypeAlias* deserializeImpl(ProfDataDeserializer& ser, ProfDataSerializer::Id id, const TypeAlias*) { return ser.getTypeAlias(id); } ArrayData* deserializeImpl(ProfDataDeserializer& ser, ProfDataSerializer::Id id, ArrayData*) { return ser.getArray(id); } StringData* deserializeImpl(ProfDataDeserializer& ser, ProfDataSerializer::Id id, StringData*) { return ser.getString(id); } const RepoAuthType::Array* deserializeImpl(ProfDataDeserializer& ser, ProfDataSerializer::Id id, const RepoAuthType::Array*) { return ser.getArrayRAT(id); } FuncId deserializeImpl(ProfDataDeserializer& ser, ProfDataSerializer::Id id, FuncId) { return ser.getFuncId(id); } template<typename F> auto deserialize(ProfDataDeserializer& ser, F&& f) -> decltype(f()) { using T = decltype(f()); auto const id = read_raw<ProfDataSerializer::Id>(ser); if (id & kSerializedIdBit) { auto const ent = f(); ser.record(id - kSerializedIdBit, ent); return ent; } return deserializeImpl(ser, id, T{}); } void write_serialized_id(ProfDataSerializer& ser, ProfDataSerializer::Id id) { assertx(!(id & kSerializedIdBit)); write_raw(ser, id | kSerializedIdBit); } void write_id(ProfDataSerializer& ser, ProfDataSerializer::Id id) { assertx(!(id & kSerializedIdBit)); write_raw(ser, id); } /* * Helper functions so that the function passed to write_container can * take a serializer as a parameter, or skip it (eg if its a lambda * which is already capturing the serializer). */ template<typename S, typename T, typename F> auto call(F& f, S& ser, const T& t) -> decltype(f(ser, t)) { return f(ser, t); } template<typename S, typename T, typename F> auto call(F& f, S&, const T& t) -> decltype(f(t)) { return f(t); } template<typename C, typename F> void write_container(ProfDataSerializer& ser, const C& cont, F f) { write_raw(ser, safe_cast<uint32_t>(cont.size())); for (auto const &elm : cont) { call(f, ser, elm); } } template<typename F> void read_container(ProfDataDeserializer& ser, F f) { auto sz = read_raw<uint32_t>(ser); while (sz--) { f(); } } void write_unit_preload(ProfDataSerializer& ser, Unit* unit) { write_raw_string(ser, unit->origFilepath()); } struct UnitPreloader : JobQueueWorker<StringData*, void*> { virtual void onThreadEnter() override { rl_typeProfileLocals->nonVMThread = true; hphp_session_init(Treadmill::SessionKind::PreloadRepo); } virtual void onThreadExit() override { hphp_context_exit(); hphp_session_exit(); } virtual void doJob(StringData* path) override { auto& nativeFuncs = Native::s_noNativeFuncs; DEBUG_ONLY auto unit = lookupUnit(path, "", nullptr, nativeFuncs, false); FTRACE(2, "Preloaded unit with path {}\n", path->data()); assertx(unit->origFilepath() == path); // both static } }; using UnitPreloadDispatcher = JobQueueDispatcher<UnitPreloader>; UnitPreloadDispatcher* s_preload_dispatcher; void read_unit_preload(ProfDataDeserializer& ser) { auto const path = read_raw_string(ser, /* skip = */ !RuntimeOption::EvalJitDesUnitPreload); // path may be nullptr when JitDesUnitPreload isn't set. if (RuntimeOption::EvalJitDesUnitPreload) { assertx(path); s_preload_dispatcher->enqueue(path); } } void write_units_preload(ProfDataSerializer& ser) { std::vector<Unit*> units; hphp_fast_set<Unit*, pointer_hash<Unit>> seen; auto const check_unit = [] (Unit* unit) -> bool { if (!unit) return false; auto const filepath = unit->origFilepath(); if (filepath->empty()) return false; // skip systemlib if (filepath->size() >= 2 && filepath->data()[1] == ':') return false; return true; }; auto const pd = profData(); assertx(pd); pd->forEachProfilingFunc([&](auto const& func) { always_assert(func); auto const u = func->unit(); if (!check_unit(u)) return; if (seen.insert(u).second) units.push_back(u); }); auto all_loaded = loadedUnitsRepoAuth(); for (auto u : all_loaded) { if (!check_unit(u)) continue; if (seen.insert(u).second) units.push_back(u); } write_container(ser, units, write_unit_preload); } void read_units_preload(ProfDataDeserializer& ser) { BootStats::Block timer("DES_read_units_preload", RuntimeOption::ServerExecutionMode()); if (RuntimeOption::EvalJitDesUnitPreload) { auto const threads = std::max(RuntimeOption::EvalJitWorkerThreadsForSerdes, 1); s_preload_dispatcher = new UnitPreloadDispatcher( threads, threads, 0, false, nullptr ); s_preload_dispatcher->start(); } read_container(ser, [&] { read_unit_preload(ser); }); } void write_type(ProfDataSerializer& ser, Type t) { t.serialize(ser); } Type read_type(ProfDataDeserializer& ser) { return Type::deserialize(ser); } void write_typed_location(ProfDataSerializer& ser, const RegionDesc::TypedLocation& loc) { write_raw(ser, loc.location); write_type(ser, loc.type); } void write_guarded_location(ProfDataSerializer& ser, const RegionDesc::GuardedLocation& loc) { write_raw(ser, loc.location); write_type(ser, loc.type); write_raw(ser, loc.category); } RegionDesc::TypedLocation read_typed_location(ProfDataDeserializer& ser) { auto const location = read_raw<Location>(ser); auto const type = read_type(ser); return { location, type }; } RegionDesc::GuardedLocation read_guarded_location(ProfDataDeserializer& ser) { auto const location = read_raw<Location>(ser); auto const type = read_type(ser); auto const cat = read_raw<DataTypeCategory>(ser); return { location, type, cat }; } void write_global_array_map(ProfDataSerializer& ser) { write_container(ser, globalArrayTypeTable(), write_array_rat); } void read_global_array_map(ProfDataDeserializer& ser) { BootStats::Block timer("DES_read_global_array_map", RuntimeOption::ServerExecutionMode()); auto const sz = read_raw<uint32_t>(ser); always_assert(sz == globalArrayTypeTable().size()); for (auto const arr : globalArrayTypeTable()) { ser.record(read_id(ser), arr); } } void write_region_block(ProfDataSerializer& ser, const RegionDesc::BlockPtr& block) { write_raw(ser, block->id()); write_srckey(ser, block->start()); write_raw(ser, block->length()); write_raw(ser, block->initialSpOffset()); write_raw(ser, block->profTransID()); write_container(ser, block->typePreConditions(), write_guarded_location); write_container(ser, block->postConds().changed, write_typed_location); write_container(ser, block->postConds().refined, write_typed_location); } RegionDesc::BlockPtr read_region_block(ProfDataDeserializer& ser) { auto const id = read_raw<RegionDesc::BlockId>(ser); auto const start = read_srckey(ser); auto const length = read_raw<int>(ser); auto const initialSpOffset = read_raw<SBInvOffset>(ser); auto const block = std::make_shared<RegionDesc::Block>( id, start, length, initialSpOffset); block->setProfTransID(read_raw<TransID>(ser)); read_container(ser, [&] { block->addPreCondition(read_guarded_location(ser)); }); PostConditions postConds; read_container(ser, [&] { postConds.changed.push_back(read_typed_location(ser)); }); read_container(ser, [&] { postConds.refined.push_back(read_typed_location(ser)); }); block->setPostConds(postConds); return block; } void write_region_desc(ProfDataSerializer& ser, const RegionDesc* rd) { write_container(ser, rd->blocks(), write_region_block); write_container(ser, findPredTrans(*rd, profData()), [&] (RegionDesc::BlockId id) { write_raw(ser, id); }); write_container(ser, rd->blocks(), [&] (const RegionDesc::BlockPtr& b) { auto const bid = b->id(); write_raw(ser, bid); assertx(rd->succs(bid).empty()); assertx(rd->preds(bid).empty()); assertx(rd->merged(bid).empty()); auto const pr = rd->prevRetrans(bid); write_raw(ser, pr ? pr.value() : kInvalidTransID); auto const nr = rd->nextRetrans(bid); write_raw(ser, nr ? nr.value() : kInvalidTransID); }); write_type(ser, rd->inlineCtxType()); write_container(ser, rd->inlineInputTypes(), write_type); } RegionDescPtr read_region_desc(ProfDataDeserializer& ser) { auto ret = std::make_shared<RegionDesc>(); read_container(ser, [&] { ret->addBlock(read_region_block(ser)); }); RegionDesc::BlockIdSet incoming; read_container(ser, [&] { incoming.insert(read_raw<RegionDesc::BlockId>(ser)); }); ret->incoming(std::move(incoming)); read_container(ser, [&] { auto const id = read_raw<RegionDesc::BlockId>(ser); auto const pr = read_raw<RegionDesc::BlockId>(ser); auto const nr = read_raw<RegionDesc::BlockId>(ser); if (pr != kInvalidTransID) { ret->setNextRetrans(pr, id); } if (nr != kInvalidTransID) { ret->setNextRetrans(id, nr); } }); auto const ty = read_type(ser); std::vector<Type> args; read_container(ser, [&] { args.push_back(read_type(ser)); }); ret->setInlineContext(ty, args); return ret; } void write_prof_trans_rec(ProfDataSerializer& ser, const ProfTransRec* ptr, ProfData* pd) { if (!ptr) return write_raw(ser, TransKind{}); write_raw(ser, ptr->kind()); write_srckey(ser, ptr->srcKey()); if (ptr->kind() == TransKind::Profile) { write_srckey(ser, ptr->lastSrcKey()); write_region_desc(ser, ptr->region().get()); write_raw(ser, ptr->asmSize()); } else { write_raw(ser, ptr->prologueArgs()); auto lock = ptr->lockCallerList(); std::vector<TransID> callers; auto addCaller = [&] (TCA caller) { auto const callerTransId = pd->jmpTransID(caller); if (callerTransId != kInvalidTransID) { callers.push_back(callerTransId); } }; for (auto const caller : ptr->mainCallers()) addCaller(caller); write_container(ser, callers, write_raw<TransID>); write_raw(ser, ptr->asmSize()); } } std::unique_ptr<ProfTransRec> read_prof_trans_rec(ProfDataDeserializer& ser) { auto const kind = read_raw<TransKind>(ser); static_assert(TransKind::Profile != TransKind{} && TransKind::ProfPrologue != TransKind{}, "Profile and ProfPrologue must not be zero"); if (kind == TransKind{}) return nullptr; auto const sk = read_srckey(ser); if (kind == TransKind::Profile) { auto const lastSk = read_srckey(ser); auto const region = read_region_desc(ser); auto const asmSize = read_raw<uint32_t>(ser); return std::make_unique<ProfTransRec>(lastSk, sk, region, asmSize); } auto ret = std::make_unique<ProfTransRec>(sk, read_raw<int>(ser), 0); read_container(ser, [&] { ret->profCallers().push_back(read_raw<TransID>(ser)); }); auto const asmSize = read_raw<uint32_t>(ser); ret->setAsmSize(asmSize); return ret; } bool write_seen_type(ProfDataSerializer& ser, const NamedEntity* ne) { if (!ne) return false; if (auto const cls = ne->clsList()) { if (!(cls->attrs() & AttrUnique)) return false; auto const filepath = cls->preClass()->unit()->origFilepath(); if (!filepath || filepath->empty()) return false; if (!ser.present(cls)) { write_raw(ser, SeenType::Class); write_class(ser, cls); } return true; } else if (auto const td = ne->getCachedTypeAlias()) { if (!ne->isPersistentTypeAlias()) return false; auto const filepath = td->unit()->origFilepath(); if (!filepath || filepath->empty()) return false; if (!ser.present(td)) { write_raw(ser, SeenType::TypeAlias); write_typealias(ser, td); } return true; } return false; } void write_named_type(ProfDataSerializer& ser, const NamedEntity* ne) { always_assert(ne); if (auto const cls = ne->clsList()) { write_raw(ser, SeenType::Class); write_class(ser, cls); } else if (auto const td = ne->getCachedTypeAlias()) { write_raw(ser, SeenType::TypeAlias); write_typealias(ser, td); } else { always_assert(false); } } void read_named_type(ProfDataDeserializer& ser); Class* read_class_internal(ProfDataDeserializer& ser) { auto const id = read_raw<decltype(std::declval<PreClass*>()->id())>(ser); auto const unit = read_unit(ser); read_container(ser, [&] { auto const dep = read_class(ser); auto const ne = dep->preClass()->namedEntity(); // if it's not persistent, make sure that dep // is the active class for this NamedEntity assertx(ne->m_cachedClass.bound()); if (ne->m_cachedClass.isNormal()) { ne->setCachedClass(dep); } }); auto const preClass = unit->lookupPreClassId(id); if (preClass->attrs() & AttrEnum && preClass->enumBaseTy().isObject()) { read_named_type(ser); } auto const ne = preClass->namedEntity(); // If it's not persistent, make sure its NamedEntity is // unbound, ready for DefClass if (ne->m_cachedClass.bound() && ne->m_cachedClass.isNormal()) { ne->m_cachedClass.markUninit(); } auto const cls = Class::def(preClass, true); if (cls->pinitVec().size()) cls->initPropHandle(); if (cls->numStaticProperties()) cls->initSPropHandles(); if (cls->parent() == c_Closure::classof()) { auto const ctx = read_class(ser); if (ctx != cls) return cls->rescope(ctx); } return cls; } const TypeAlias* read_typealias_internal(ProfDataDeserializer& ser) { const Id id = read_raw<Id>(ser); auto const unit = read_unit(ser); auto const has_class = read_raw<bool>(ser); if (has_class) { read_class(ser); } auto const td = unit->lookupTypeAliasId(id); return TypeAlias::def(td); } /* * This reads in TypeAliases and Classes that are used for code gen, * but otherwise aren't needed for profiling. We just need them to be * loaded into the NamedEntity table, so this function just returns * whether or not to continue. */ bool read_seen_type(ProfDataDeserializer& ser) { auto const type = read_raw<SeenType>(ser); switch (type) { case SeenType::Class: { read_class(ser); return true; } case SeenType::TypeAlias: { read_typealias(ser); return true; } case SeenType::End: return false; default: always_assert(false); } } void read_named_type(ProfDataDeserializer& ser) { auto const type = read_raw<SeenType>(ser); switch (type) { case SeenType::Class: read_class(ser); return; case SeenType::TypeAlias: read_typealias(ser); return; default: always_assert(false); } } void write_profiled_funcs(ProfDataSerializer& ser, ProfData* pd) { pd->forEachProfilingFunc([&](auto const& func) { always_assert(func); write_func(ser, func); }); write_func(ser, nullptr); } void read_profiled_funcs(ProfDataDeserializer& ser, ProfData* pd) { while (auto const func = read_func(ser)) { pd->setProfiling(func); } } void write_seen_types(ProfDataSerializer& ser, ProfData* pd) { // in an attempt to get a sensible order for these, start with the // ones referenced by params and return constraints. pd->forEachProfilingFunc([&](auto const& func) { always_assert(func); for (auto const& p : func->params()) { write_seen_type(ser, p.typeConstraint.namedEntity()); } }); // Now just iterate and write anything that remains NamedEntity::foreach_name( [&] (NamedEntity& ne) { write_seen_type(ser, &ne); } ); write_raw(ser, SeenType::End); } void read_seen_types(ProfDataDeserializer& ser) { BootStats::Block timer("DES_read_classes_and_type_aliases", RuntimeOption::ServerExecutionMode()); while (read_seen_type(ser)) { // nothing to do. this was just to make sure everything is loaded // into the NamedEntity table } } void write_prof_data(ProfDataSerializer& ser, ProfData* pd) { // Write the profiled metadata to output std::string metaFile = ser.filename() + ".meta"; if (auto out = fopen(metaFile.c_str(), "w")) { fprintf(out, "profFuncCnt=%ld\n", pd->profilingFuncs()); fprintf(out, "profBCSize=%ld\n", pd->profilingBCSize()); fclose(out); } write_profiled_funcs(ser, pd); write_raw(ser, pd->counterDefault()); pd->forEachTransRec( [&] (const ProfTransRec* ptr) { auto const transID = ptr->isProfile() ? ptr->region()->entry()->profTransID() : pd->proflogueTransId(ptr->func(), ptr->prologueArgs()); write_raw(ser, transID); write_prof_trans_rec(ser, ptr, pd); // forEachTransRec already grabs a read lock, and we're not // going to add a *new* counter here (so we don't need a write // lock). write_raw(ser, *pd->transCounterAddrNoLock(transID)); } ); write_raw(ser, kInvalidTransID); write_raw<uint64_t>(ser, pd->baseProfCount()); } void maybe_output_prof_trans_rec_trace( TransID transId, const ProfTransRec* profTransRec, uint64_t translationWeight) { if (profTransRec->kind() != TransKind::Profile) { return; } if (HPHP::Trace::moduleEnabledRelease(HPHP::Trace::print_profiles)) { auto const sk = profTransRec->srcKey(); auto const unit = sk.unit(); auto const func = sk.func(); const char *filePath = ""; if (func->originalFilename() && func->originalFilename()->size()) { filePath = func->originalFilename()->data(); } else if (unit->origFilepath()->data() && unit->origFilepath()->size()) { filePath = unit->origFilepath()->data(); } folly::dynamic blocks = folly::dynamic::array; for (const auto& block : profTransRec->region()->blocks()) { const auto typePreconditionsStr = show(block->typePreConditions()); if (!typePreconditionsStr.empty()) { blocks.push_back(folly::dynamic::object("type_preconditions_raw", typePreconditionsStr)); } } folly::dynamic profTransRecProfile = folly::dynamic::object; profTransRecProfile["end_bytecode_offset"] = profTransRec->lastSrcKey().printableOffset(); profTransRecProfile["end_line_number"] = profTransRec->lastSrcKey().lineNumber(); profTransRecProfile["file_path"] = filePath; profTransRecProfile["function_name"] = sk.func()->fullName()->data(); profTransRecProfile["profile"] = folly::dynamic::object("profileType", "ProfTransRec"); profTransRecProfile["region"] = folly::dynamic::object("blocks", blocks); profTransRecProfile["start_bytecode_offset"] = profTransRec->srcKey().printableOffset(); profTransRecProfile["start_line_number"] = profTransRec->region()->start().lineNumber(); profTransRecProfile["translation_weight"] = translationWeight; HPHP::Trace::traceRelease("json:%s\n", folly::toJson(profTransRecProfile).c_str()); } } void read_prof_data(ProfDataDeserializer& ser, ProfData* pd) { BootStats::Block timer("DES_read_prof_data", RuntimeOption::ServerExecutionMode()); read_profiled_funcs(ser, pd); pd->resetCounters(read_raw<int64_t>(ser)); while (true) { auto const transID = read_raw<TransID>(ser); if (transID == kInvalidTransID) break; pd->addProfTrans(transID, read_prof_trans_rec(ser)); *pd->transCounterAddr(transID) = read_raw<int64_t>(ser); auto const profTransRec = pd->transRec(transID); maybe_output_prof_trans_rec_trace(transID, profTransRec, pd->transCounter(transID)); } pd->setBaseProfCount(read_raw<uint64_t>(ser)); } template<typename T> auto write_impl(ProfDataSerializer& ser, const T& out, bool) -> decltype(std::declval<T&>().serialize(ser),void()) { out.serialize(ser); } template<typename T> void write_impl(ProfDataSerializer& ser, const T& out, int) { write_raw(ser, out); } template<typename T> void write_maybe_serializable(ProfDataSerializer& ser, const T& out) { write_impl(ser, out, false); } struct TargetProfileVisitor : boost::static_visitor<void> { TargetProfileVisitor(ProfDataSerializer& ser, const rds::Symbol& sym, rds::Handle handle, uint32_t size) : ser{ser} , sym{sym} , handle{handle} , size{size} {} template<typename T> void process(T& out, const StringData* name) { write_raw(ser, size); write_string(ser, name); write_raw(ser, sym); TargetProfile<T>::reduce(out, handle, size); if (size == sizeof(T)) { write_maybe_serializable(ser, out); } else { write_raw(ser, &out, size); } } template<typename T> void operator()(const T&) {} template<typename T> void go(const rds::Profile& pt) { if (size == sizeof(T)) { T out{}; process(out, pt.name.get()); } else { auto const mem = calloc(1, size); SCOPE_EXIT { free(mem); }; process(*reinterpret_cast<T*>(mem), pt.name.get()); } } void operator()(const rds::Profile& pt) { switch (pt.kind) { case rds::ProfileKind::None: always_assert(false); #define PR(T) \ case rds::ProfileKind::T: \ return go<T>(pt); RDS_PROFILE_SYMBOLS #undef PR } } ProfDataSerializer& ser; const rds::Symbol& sym; rds::Handle handle; uint32_t size; }; void write_target_profiles(ProfDataSerializer& ser) { rds::visitSymbols( [&] (const rds::Symbol& symbol, rds::Handle handle, uint32_t size) { TargetProfileVisitor tv(ser, symbol, handle, size); boost::apply_visitor(tv, symbol); } ); write_raw(ser, uint32_t{}); } void write_rds_ordering_item(ProfDataSerializer& ser, const rds::Ordering::Item& item) { write_string(ser, item.key); write_raw(ser, item.size); write_raw(ser, item.alignment); } rds::Ordering::Item read_rds_ordering_item(ProfDataDeserializer& des) { auto const key = read_cpp_string(des); auto const size = read_raw<decltype(rds::Ordering::Item::size)>(des); auto const alignment = read_raw<decltype(rds::Ordering::Item::alignment)>(des); return rds::Ordering::Item{key, size, alignment}; } void write_rds_ordering(ProfDataSerializer& ser) { rds::Ordering order; if (RO::EvalReorderRDS) order = rds::profiledOrdering(); write_container(ser, order.persistent, write_rds_ordering_item); write_container(ser, order.local, write_rds_ordering_item); write_container(ser, order.normal, write_rds_ordering_item); } rds::Ordering read_rds_ordering(ProfDataDeserializer& des) { rds::Ordering order; read_container( des, [&] { order.persistent.emplace_back(read_rds_ordering_item(des)); } ); read_container( des, [&] { order.local.emplace_back(read_rds_ordering_item(des)); } ); read_container( des, [&] { order.normal.emplace_back(read_rds_ordering_item(des)); } ); return order; } template<typename T> auto read_impl(ProfDataDeserializer& ser, T& out, bool) -> decltype(out.deserialize(ser),void()) { out.deserialize(ser); } template<typename T> void read_impl(ProfDataDeserializer& ser, T& out, int) { read_raw(ser, out); } template<typename T> void read_maybe_serializable(ProfDataDeserializer& ser, T& out) { read_impl(ser, out, false); } template<typename T> void maybe_output_target_profile_trace( const StringData* name, const TargetProfile<T>& prof, const rds::Profile &pt) { if (HPHP::Trace::moduleEnabledRelease(HPHP::Trace::print_profiles, 1)) { auto const pd = profData(); assertx(pd != nullptr); if (isValidTransID(pt.transId)) { auto const ptr = pd->transRec(pt.transId); if (ptr) { auto const srcKey = ptr->srcKey(); auto const func = srcKey.func(); auto const unit = srcKey.unit(); const char *filePath = ""; if (func->originalFilename() && func->originalFilename()->size()) { filePath = func->originalFilename()->data(); } else if (unit->origFilepath()->data() && unit->origFilepath()->size()) { filePath = unit->origFilepath()->data(); } folly::dynamic targetProfileInfo = folly::dynamic::object; targetProfileInfo["trans_id"] = pt.transId; targetProfileInfo["profile_raw_name"] = name->toCppString(); targetProfileInfo["profile"] = prof.value().toDynamic(); targetProfileInfo["file_path"] = filePath; targetProfileInfo["line_number"] = func->getLineNumber(pt.bcOff); targetProfileInfo["function_name"] = func->fullName()->data(); targetProfileInfo["start_bytecode_offset"] = pt.bcOff; HPHP::Trace::traceRelease("json:%s\n", folly::toJson(targetProfileInfo).c_str()); } } } } struct SymbolFixup : boost::static_visitor<void> { SymbolFixup(ProfDataDeserializer& ser, StringData* name, uint32_t size) : ser{ser}, name{name}, size{size} {} template<typename T> void operator()(T&) { always_assert(false); } template<typename T> void go(rds::Profile& pt) { auto prof = TargetProfile<T>::deserialize( {pt.transId}, TransKind::Profile, pt.bcOff, name, size - sizeof(T)); if (size == sizeof(T)) { read_maybe_serializable(ser, prof.value()); } else { read_raw(ser, &prof.value(), size); } maybe_output_target_profile_trace(name, prof, pt); } void operator()(rds::Profile& pt) { switch (pt.kind) { case rds::ProfileKind::None: always_assert(false); #define PR(T) \ case rds::ProfileKind::T: \ return go<T>(pt); RDS_PROFILE_SYMBOLS #undef PR } not_reached(); } ProfDataDeserializer& ser; StringData* name; // The size of the original rds allocation. uint32_t size; }; void read_target_profiles(ProfDataDeserializer& ser) { BootStats::Block timer("DES_read_target_profiles", RuntimeOption::ServerExecutionMode()); while (true) { auto const size = read_raw<uint32_t>(ser); if (!size) break; auto const name = read_string(ser); auto sym = read_raw<rds::Symbol>(ser); auto sf = SymbolFixup{ser, name, size}; boost::apply_visitor(sf, sym); } } void merge_loaded_units(int numWorkers) { BootStats::Block timer("DES_merge_loaded_units", RuntimeOption::ServerExecutionMode()); auto units = loadedUnitsRepoAuth(); std::vector<VMWorker> workers; // Compute a batch size that causes each thread to process approximately 16 // batches. Even if the batches are somewhat imbalanced in what they contain, // the straggler workers are very unlikey to take more than 10% longer than // the first worker to finish. auto const batchSize{std::max(units.size() / numWorkers / 16, size_t(1))}; std::atomic<size_t> index{0}; std::atomic<uint32_t> curr_node{0}; for (auto worker = 0; worker < numWorkers; ++worker) { WorkerSpec spec; spec.numaNode = next_numa_node(curr_node); workers.emplace_back( VMWorker( spec, [&] { ProfileNonVMThread nonVM; #if USE_JEMALLOC_EXTENT_HOOKS if (auto arena = next_extra_arena(spec.numaNode)) { arena->bindCurrentThread(); } #endif hphp_session_init(Treadmill::SessionKind::PreloadRepo); while (true) { auto begin = index.fetch_add(batchSize); auto end = std::min(begin + batchSize, units.size()); if (begin >= end) break; auto unitCount = end - begin; for (auto i = size_t{0}; i < unitCount; ++i) { auto const unit = units[begin + i]; try { unit->merge(); } catch (...) { // swallow errors silently. persistent things should raise // errors, and we don't really care about merging // non-persistent things. } } } hphp_context_exit(); hphp_session_exit(); } ) ); } for (auto& worker : workers) { worker.start(); } for (auto& worker : workers) { worker.waitForEnd(); } } //////////////////////////////////////////////////////////////////////////////// void renameFile(const std::string& src, const std::string& dst) { if (::rename(src.c_str(), dst.c_str()) != -1) return; auto const msg = folly::sformat("Failed to rename {} to {}, {}", src, dst, folly::errnoStr(errno)); Logger::Error(msg); throw std::runtime_error(msg); }; std::string mangleFilenameForCreate(const std::string& name) { return name + ".part1"; } std::string mangleFilenameForAppendStart(const std::string& name) { return name + ".part2"; } std::string mangleFilenameForAppendInProgress(const std::string& name) { return name + ".part3"; } //////////////////////////////////////////////////////////////////////////////// } ProfDataSerializer::ProfDataSerializer(const std::string& name, FileMode mode) : baseFileName(name) , fileMode(mode) { auto const check = [] (int ret, const std::string& file) { if (ret != -1) return; auto const msg = folly::sformat("Failed to open file for write {}, {}", file, folly::errnoStr(errno)); Logger::Error(msg); throw std::runtime_error(msg); }; if (fileMode == FileMode::Append) { fileName = mangleFilenameForAppendInProgress(name); auto const mangled = mangleFilenameForAppendStart(name); fd = open(mangled.c_str(), O_CLOEXEC | O_APPEND | O_WRONLY, 0644); check(fd, mangled); renameFile(mangled, fileName); } else { // Delete old profile data to avoid confusion. This should've happened from // outside the process, but in case it didn't happen, try to do it here. unlink(name.c_str()); fileName = mangleFilenameForCreate(name); fd = open(fileName.c_str(), O_CLOEXEC | O_CREAT | O_TRUNC | O_WRONLY, 0644); check(fd, fileName); } } void ProfDataSerializer::finalize() { assertx(fd != -1); if (offset) ::write(fd, buffer, offset); offset = 0; close(fd); fd = -1; // Rename file to its appropriate new name. If we're going to append // additional profile data, rename it to another (different) // temporary name (to mark the base data has been written). If not, // rename it to its file name (signifying completion). if (fileMode == FileMode::Create && serializeOptProfEnabled()) { // Don't rename the file to it's final name yet as we're still going to // append the profile data collected for the optimized code to it. FTRACE(1, "Finished serializing base profile data to {}\n", fileName); renameFile(fileName, mangleFilenameForAppendStart(baseFileName)); } else { FTRACE(1, "Finished serializing all profile data to {}\n", fileName); renameFile(fileName, baseFileName); } } ProfDataSerializer::~ProfDataSerializer() { if (fd != -1) { // We didn't finalize(), maybe because an exception was thrown while writing // the data. The file is likely corrupt or incomplete, so discard it. ftruncate(fd, 0); close(fd); unlink(fileName.c_str()); } } ProfDataDeserializer::ProfDataDeserializer(const std::string& name) { fd = open(name.c_str(), O_CLOEXEC | O_RDONLY); if (fd == -1) throw std::runtime_error("Failed to open: " + name); } ProfDataDeserializer::~ProfDataDeserializer() { assertx(fd != -1); close(fd); } bool ProfDataDeserializer::done() { auto const pos = lseek(fd, 0, SEEK_CUR); auto const end = lseek(fd, 0, SEEK_END); lseek(fd, pos, SEEK_SET); // go back to original position return offset == buffer_size && pos == end; } ProfDataSerializer::Mappers ProfDataDeserializer::getMappers() const { ProfDataSerializer::Mappers mappers; auto const rev = [] (auto const& i, auto& o) { for (ProfDataSerializer::Id id = 1; id < i.map.size(); ++id) { if (!i.map[id]) continue; o.assign(id, i.map[id]); } }; rev(unitMap, mappers.unitMap); rev(funcMap, mappers.funcMap); rev(classMap, mappers.classMap); rev(typeAliasMap, mappers.typeAliasMap); rev(stringMap, mappers.stringMap); rev(arrayMap, mappers.arrayMap); rev(ratMap, mappers.ratMap); rev(funcIdMap, mappers.funcIdMap); return mappers; } void write_raw(ProfDataSerializer& ser, const void* data, size_t sz) { if (ser.offset + sz <= ProfDataSerializer::buffer_size) { memcpy(ser.buffer + ser.offset, data, sz); ser.offset += sz; return; } if (ser.offset == 0) { if (::write(ser.fd, data, sz) != sz) { throw std::runtime_error("Failed to write serialized data"); } return; } if (auto const delta = ProfDataSerializer::buffer_size - ser.offset) { memcpy(ser.buffer + ser.offset, data, delta); data = static_cast<const char*>(data) + delta; sz -= delta; ser.offset = ProfDataSerializer::buffer_size; } assertx(ser.offset == ProfDataSerializer::buffer_size); if (::write(ser.fd, ser.buffer, ser.offset) != ser.offset) { throw std::runtime_error("Failed to write serialized data"); } ser.offset = 0; write_raw(ser, data, sz); } void read_raw(ProfDataDeserializer& ser, void* data, size_t sz) { if (ser.offset + sz <= ProfDataDeserializer::buffer_size) { memcpy(data, ser.buffer + ser.offset, sz); ser.offset += sz; return; } if (auto const delta = ProfDataDeserializer::buffer_size - ser.offset) { memcpy(data, ser.buffer + ser.offset, delta); data = static_cast<char*>(data) + delta; sz -= delta; ser.offset = ProfDataDeserializer::buffer_size; } if (sz >= ProfDataDeserializer::buffer_size) { auto const bytes_read = ::read(ser.fd, data, sz); if (bytes_read < 0 || bytes_read < sz) { throw std::runtime_error("Failed to read serialized data"); } return; } auto const bytes_read = ::read(ser.fd, ser.buffer, ProfDataDeserializer::buffer_size); if (bytes_read < 0 || bytes_read < sz) { throw std::runtime_error("Failed to read serialized data"); } ser.offset = ProfDataDeserializer::buffer_size - bytes_read; if (ser.offset) { memmove(ser.buffer + ser.offset, ser.buffer, bytes_read); } return read_raw(ser, data, sz); } Unit* ProfDataDeserializer::getUnit(ProfDataSerializer::Id id) const { return unitMap.get(id); } Func* ProfDataDeserializer::getFunc(ProfDataSerializer::Id id) const { return funcMap.get(id); } Class* ProfDataDeserializer::getClass(ProfDataSerializer::Id id) const { return classMap.get(id); } const TypeAlias* ProfDataDeserializer::getTypeAlias(ProfDataSerializer::Id id) const { return typeAliasMap.get(id); } ArrayData* ProfDataDeserializer::getArray(ProfDataSerializer::Id id) const { return arrayMap.get(id); } StringData* ProfDataDeserializer::getString(ProfDataSerializer::Id id) const { return stringMap.get(id); } const RepoAuthType::Array* ProfDataDeserializer::getArrayRAT(ProfDataSerializer::Id id) const { return ratMap.get(id); } FuncId ProfDataDeserializer::getFuncId(ProfDataSerializer::Id id) const { return FuncId::fromInt(funcIdMap.get(id)); } void ProfDataDeserializer::record(ProfDataSerializer::Id id, Unit* unit) { unitMap.record(id, unit); } void ProfDataDeserializer::record(ProfDataSerializer::Id id, Func* func) { funcMap.record(id, func); } void ProfDataDeserializer::record(ProfDataSerializer::Id id, Class* cls) { classMap.record(id, cls); } void ProfDataDeserializer::record(ProfDataSerializer::Id id, const TypeAlias* ta) { typeAliasMap.record(id, ta); } void ProfDataDeserializer::record(ProfDataSerializer::Id id, ArrayData* arr) { arrayMap.record(id, arr); } void ProfDataDeserializer::record(ProfDataSerializer::Id id, StringData* str) { stringMap.record(id, str); } void ProfDataDeserializer::record(ProfDataSerializer::Id id, const RepoAuthType::Array* arr) { ratMap.record(id, arr); } void ProfDataDeserializer::record(ProfDataSerializer::Id id, FuncId funcId) { funcIdMap.record(id, funcId.toInt()); } std::pair<ProfDataSerializer::Id, bool> ProfDataSerializer::serialize(const Unit* unit) { return mappers.unitMap.get(unit); } std::pair<ProfDataSerializer::Id, bool> ProfDataSerializer::serialize(const Func* func) { return mappers.funcMap.get(func); } std::pair<ProfDataSerializer::Id, bool> ProfDataSerializer::serialize(const Class* cls) { return mappers.classMap.get(cls); } std::pair<ProfDataSerializer::Id, bool> ProfDataSerializer::serialize(const TypeAlias* td) { return mappers.typeAliasMap.get(td); } std::pair<ProfDataSerializer::Id, bool> ProfDataSerializer::serialize(const ArrayData* ad) { return mappers.arrayMap.get(ad); } std::pair<ProfDataSerializer::Id, bool> ProfDataSerializer::serialize(const StringData* sd) { return mappers.stringMap.get(sd); } std::pair<ProfDataSerializer::Id, bool> ProfDataSerializer::serialize(const RepoAuthType::Array* arr) { return mappers.ratMap.get(arr); } std::pair<ProfDataSerializer::Id, bool> ProfDataSerializer::serialize(FuncId fid) { return mappers.funcIdMap.get(fid.toInt()); } bool ProfDataSerializer::present(const Class* cls) const { return mappers.classMap.present(cls); } bool ProfDataSerializer::present(const TypeAlias* ta) const { return mappers.typeAliasMap.present(ta); } void write_raw_string(ProfDataSerializer& ser, const StringData* str) { uint32_t sz = str->size(); write_raw(ser, sz); write_raw(ser, str->data(), sz); if (allowBespokeArrayLikes()) { write_raw(ser, str->color()); } } StringData* read_raw_string(ProfDataDeserializer& ser, bool skip /* = false */) { auto const sz = read_raw<uint32_t>(ser); constexpr uint32_t kMaxStringLen = StringData::MaxSize; if (sz > kMaxStringLen) { throw std::runtime_error("string too long, likely corrupt"); } constexpr uint32_t kBufLen = 8192; char buffer[kBufLen]; char* ptr = buffer; if (sz > kBufLen) ptr = (char*)malloc(sz); SCOPE_EXIT { if (ptr != buffer) free(ptr); }; read_raw(ser, ptr, sz); auto const color = [&] { if (allowBespokeArrayLikes()) return read_raw<uint16_t>(ser); return uint16_t{0}; }(); if (!skip) { auto const str = makeStaticString(ptr, sz); str->setColor(color); return str; } return nullptr; } void write_string(ProfDataSerializer& ser, const StringData* str) { auto const [id, old] = ser.serialize(str); if (old) return write_id(ser, id); write_serialized_id(ser, id); write_raw_string(ser, str); } void write_string(ProfDataSerializer& ser, const std::string& str) { uint64_t size = str.size(); write_raw(ser, size); write_raw(ser, str.data(), size); } StringData* read_string(ProfDataDeserializer& ser) { return deserialize( ser, [&] { return read_raw_string(ser); } ); } std::string read_cpp_string(ProfDataDeserializer& ser) { auto const size = read_raw<uint64_t>(ser); constexpr uint32_t kMaxStringLen = 2 << 20; if (size > kMaxStringLen) { throw std::runtime_error("string too long, likely corrupt"); } auto const buf = std::make_unique<char[]>(size); read_raw(ser, buf.get(), size); return std::string{buf.get(), size}; } void write_array(ProfDataSerializer& ser, const ArrayData* arr) { auto const [id, old] = ser.serialize(arr); if (old) return write_id(ser, id); write_serialized_id(ser, id); auto const str = internal_serialize(VarNR(const_cast<ArrayData*>(arr))); uint32_t sz = str.size(); write_raw(ser, sz); write_raw(ser, str.data(), sz); if (!allowBespokeArrayLikes()) return; write_layout(ser, ArrayLayout::FromArray(arr)); } ArrayData* read_array(ProfDataDeserializer& ser) { return deserialize( ser, [&] () -> ArrayData* { auto const sz = read_raw<uint32_t>(ser); String s{sz, ReserveStringMode{}}; auto const range = s.bufferSlice(); read_raw(ser, range.begin(), sz); s.setSize(sz); auto v = unserialize_from_buffer( s.data(), s.size(), VariableUnserializer::Type::Internal ); auto const result = ArrayData::GetScalarArray(std::move(v)); if (!allowBespokeArrayLikes()) return result; return read_layout(ser).apply(result); } ); } void write_array_rat(ProfDataSerializer& ser, const RepoAuthType::Array* arr) { write_id(ser, ser.serialize(arr).first); } const RepoAuthType::Array* read_array_rat(ProfDataDeserializer& ser) { auto const id = read_id(ser); return ser.getArrayRAT(id); } void write_unit(ProfDataSerializer& ser, const Unit* unit) { auto const [id, old] = ser.serialize(unit); if (old) return write_id(ser, id); ITRACE(2, "Unit: {}\n", unit->origFilepath()); write_serialized_id(ser, id); write_string(ser, unit->origFilepath()); } Unit* read_unit(ProfDataDeserializer& ser) { return deserialize( ser, [&] () -> Unit* { auto const filepath = read_string(ser); ITRACE(2, "Unit: {}\n", filepath); auto& nativeFuncs = Native::s_noNativeFuncs; if (filepath->data()[0] == '/' && filepath->data()[1] == ':') { return lookupSyslibUnit(filepath, nativeFuncs); } return lookupUnit(filepath, "", nullptr, nativeFuncs, false); } ); } void write_class(ProfDataSerializer& ser, const Class* cls) { SCOPE_EXIT { ITRACE(2, "Class: {}\n", cls ? cls->name() : staticEmptyString()); }; ITRACE(2, "Class>\n"); Trace::Indent _; auto const [id, old] = ser.serialize(cls); if (old) return write_id(ser, id); write_serialized_id(ser, id); write_raw(ser, cls->preClass()->id()); write_unit(ser, cls->preClass()->unit()); jit::vector<const Class*> dependents; auto record_dep = [&] (const Class* dep) { if (!dep) return; if (!ser.present(dep) || !classHasPersistentRDS(dep)) { dependents.emplace_back(dep); } }; record_dep(cls->parent()); for (auto const& iface : cls->declInterfaces()) { record_dep(iface.get()); } if (cls->preClass()->attrs() & AttrNoExpandTrait) { for (auto const tName : cls->preClass()->usedTraits()) { auto const trait = Class::lookupUniqueInContext(tName, nullptr, nullptr); assertx(trait); record_dep(trait); } } else { for (auto const& trait : cls->usedTraitClasses()) { record_dep(trait.get()); } } if (cls->hasIncludedEnums()) { for (auto const& e : cls->allIncludedEnums().range()) { record_dep(e.get()); } } write_container(ser, dependents, write_class); if (cls->attrs() & AttrEnum && cls->preClass()->enumBaseTy().isObject()) { write_named_type(ser, cls->preClass()->enumBaseTy().namedEntity()); } if (cls->parent() == c_Closure::classof()) { auto const func = cls->lookupMethod(s_invoke.get()); assertx(func); write_class(ser, func->cls()); } } Class* read_class(ProfDataDeserializer& ser) { ITRACE(2, "Class>\n"); auto const ret = deserialize( ser, [&] () -> Class* { Trace::Indent _; return read_class_internal(ser); } ); ITRACE(2, "Class: {}\n", ret ? ret->name() : staticEmptyString()); return ret; } void write_lclass(ProfDataSerializer& ser, LazyClassData lcls) { ITRACE(2, "LClass>\n"); Trace::Indent _i; write_raw_string(ser, lcls.name()); ITRACE(2, "LCLass: {}\n", lcls.name()); } LazyClassData read_lclass(ProfDataDeserializer& ser) { ITRACE(2, "LClass>\n"); auto const name = read_raw_string(ser); ITRACE(2, "LClass: {}\n", name ? name : staticEmptyString()); return LazyClassData::create(name); } void write_typealias(ProfDataSerializer& ser, const TypeAlias* td) { SCOPE_EXIT { ITRACE(2, "TypeAlias: {}\n", td->name()); }; ITRACE(2, "TypeAlias>\n"); Trace::Indent _; auto const [id, old] = ser.serialize(td); if (old) return write_id(ser, id); write_serialized_id(ser, id); auto const tdId = [td] { Id tdId = 0; auto const name = td->name(); for (auto const& ta : td->unit()->typeAliases()) { if (ta.name == name) return tdId; tdId++; } always_assert(false); }(); write_raw(ser, tdId); write_unit(ser, td->unit()); if (td->klass) { write_raw(ser, true); write_class(ser, td->klass); } else { write_raw(ser, false); } } const TypeAlias* read_typealias(ProfDataDeserializer& ser) { ITRACE(2, "TypeAlias>\n"); auto const ret = deserialize( ser, [&] () -> const TypeAlias* { Trace::Indent _; return read_typealias_internal(ser); } ); ITRACE(2, "TypeAlias: {}\n", ret ? ret->name() : staticEmptyString()); return ret; } void write_func(ProfDataSerializer& ser, const Func* func) { SCOPE_EXIT { ITRACE(2, "Func: {}\n", func ? func->fullName() : staticEmptyString()); }; ITRACE(2, "Func>\n"); Trace::Indent _; auto const [id, old] = ser.serialize(func); if (old) return write_id(ser, id); write_serialized_id(ser, id); write_func_id(ser, func->getFuncId()); if (func == SystemLib::s_nullCtor || (!func->isMethod() && func->isBuiltin() && !func->isMethCaller())) { if (func == SystemLib::s_nullCtor) { assertx(func->name()->isame(s_86ctor.get())); } write_raw(ser, true); return write_string(ser, func->name()); } write_raw(ser, false); if (func->isMethod()) { auto const* cls = func->implCls(); auto const nslot = [&] () -> uint32_t { const uint32_t slot = func->methodSlot(); if (cls->getMethod(slot) != func) { if (func->name() == s_86pinit.get()) return k86pinitSlot; if (func->name() == s_86sinit.get()) return k86sinitSlot; if (func->name() == s_86linit.get()) return k86linitSlot; cls = func->cls(); assertx(cls->getMethod(slot) == func); } return ~slot; }(); assertx(nslot & 0x80000000); write_raw(ser, nslot); write_class(ser, cls); return; } const uint32_t sn = func->sn(); assertx(!(sn & 0x80000000)); write_raw(ser, sn); write_unit(ser, func->unit()); } Func* read_func(ProfDataDeserializer& ser) { ITRACE(2, "Func>\n"); auto const ret = deserialize( ser, [&] () -> Func* { Trace::Indent _; auto const fid = read_id(ser); auto const builtin_func = read_raw<bool>(ser); auto const func = [&] () -> const Func* { if (builtin_func) { auto const name = read_string(ser); if (name->isame(s_86ctor.get())) return SystemLib::s_nullCtor; return Func::lookup(name); } auto const id = read_raw<uint32_t>(ser); if (id & 0x80000000) { auto const cls = read_class(ser); if (id == k86pinitSlot) return cls->get86pinit(); if (id == k86sinitSlot) return cls->get86sinit(); if (id == k86linitSlot) return cls->get86linit(); const Slot slot = ~id; return cls->getMethod(slot); } auto const unit = read_unit(ser); for (auto const f : unit->funcs()) { if (f->sn() == id) { auto const ne = f->getNamedEntity(); // If it's not persistent, make sure its NamedEntity is // unbound, ready for DefFunc if (ne->m_cachedFunc.bound() && ne->m_cachedFunc.isNormal()) { ne->m_cachedFunc.markUninit(); } Func::def(f); return f; } } not_reached(); }(); ser.record(fid, func->getFuncId()); return const_cast<Func*>(func); } ); ITRACE(2, "Func: {}\n", ret ? ret->fullName() : staticEmptyString()); return ret; } void write_clsmeth(ProfDataSerializer& ser, ClsMethDataRef clsMeth) { SCOPE_EXIT { ITRACE(2, "ClsMeth: {}, {}\n", clsMeth->getCls() ? clsMeth->getCls()->name() : staticEmptyString(), clsMeth->getFunc() ? clsMeth->getFunc()->fullName() : staticEmptyString() ); }; ITRACE(2, "ClsMeth>\n"); write_class(ser, clsMeth->getCls()); write_func(ser, clsMeth->getFunc()); } ClsMethDataRef read_clsmeth(ProfDataDeserializer& ser) { ITRACE(2, "ClsMeth>\n"); auto const cls = read_class(ser); auto const func = read_func(ser); ITRACE(2, "ClsMeth: {}, {}\n", cls ? cls->name() : staticEmptyString(), func ? func->fullName() : staticEmptyString()); return ClsMethDataRef::create(cls, func); } void write_regionkey(ProfDataSerializer& ser, const RegionEntryKey& regionkey) { write_srckey(ser, regionkey.srcKey()); write_container(ser, regionkey.guards(), write_guarded_location); } RegionEntryKey read_regionkey(ProfDataDeserializer& des) { auto srcKey = read_srckey(des); GuardedLocations guards; read_container(des, [&] { guards.push_back(read_guarded_location(des)); }); return RegionEntryKey(srcKey, guards); } void write_prologueid(ProfDataSerializer& ser, const PrologueID& pid) { ITRACE(2, "PrologueID>\n"); write_func(ser, pid.func()); write_raw(ser, safe_cast<uint32_t>(pid.nargs())); ITRACE(2, "PrologueID: {}\n", show(pid)); } PrologueID read_prologueid(ProfDataDeserializer& des) { ITRACE(2, "PrologueID>\n"); auto const func = read_func(des); auto const nargs = read_raw<uint32_t>(des); auto const pid = PrologueID(func, nargs); ITRACE(2, "PrologueID: {}\n", show(pid)); return pid; } void write_srckey(ProfDataSerializer& ser, SrcKey sk) { ITRACE(2, "SrcKey>\n"); write_func(ser, sk.func()); write_raw(ser, sk.toAtomicInt()); ITRACE(2, "SrcKey: {}\n", show(sk)); } SrcKey read_srckey(ProfDataDeserializer& ser) { ITRACE(2, "SrcKey>\n"); auto const func = read_func(ser); auto const orig = SrcKey::fromAtomicInt(read_raw<SrcKey::AtomicInt>(ser)); auto const sk = orig.withFuncID(func->getFuncId()); ITRACE(2, "SrcKey: {}\n", show(sk)); return sk; } void write_layout(ProfDataSerializer& ser, ArrayLayout layout) { write_raw(ser, layout.toUint16()); } ArrayLayout read_layout(ProfDataDeserializer& ser) { return ArrayLayout::FromUint16(read_raw<uint16_t>(ser)); } void write_func_id(ProfDataSerializer& ser, FuncId fid) { write_id(ser, ser.serialize(fid).first); } FuncId read_func_id(ProfDataDeserializer& ser) { auto const id = read_id(ser); return ser.getFuncId(id); } namespace { ProfDataSerializer::Mappers s_lastMappers; } std::string serializeProfData(const std::string& filename) { try { ProfDataSerializer ser{filename, ProfDataSerializer::FileMode::Create}; write_raw(ser, kMagic); write_raw(ser, RepoFile::globalData().Signature); auto schema = repoSchemaId(); write_raw(ser, schema.size()); write_raw(ser, schema.begin(), schema.size()); auto host = Process::GetHostName(); write_raw(ser, host.size()); write_raw(ser, &host[0], host.size()); auto& tag = RuntimeOption::ProfDataTag; write_raw(ser, tag.size()); write_raw(ser, &tag[0], tag.size()); write_raw(ser, TimeStamp::Current()); Func::s_treadmill = true; hphp_session_init(Treadmill::SessionKind::ProfData); requestInitProfData(); SCOPE_EXIT { requestExitProfData(); hphp_context_exit(); hphp_session_exit(); Func::s_treadmill = false; }; write_rds_ordering(ser); write_units_preload(ser); ExtensionRegistry::serialize(ser); PropertyProfile::serialize(ser); InstanceBits::init(); InstanceBits::serialize(ser); write_global_array_map(ser); auto const pd = profData(); write_prof_data(ser, pd); if (RO::EnableIntrinsicsExtension) { write_container(ser, prioritySerializeClasses(), write_class); } serializeSharedProfiles(ser); write_target_profiles(ser); // We've written everything directly referenced by the profile // data, but jitted code might still use Classes and TypeAliases // that haven't been otherwise mentioned (eg VerifyParamType, // InstanceOfD etc). write_seen_types(ser, pd); if (allowBespokeArrayLikes()) { serializeBespokeLayouts(ser); } writeGlobalProfiles(ser); // Record the size of main profile code so we can use it to fake // jit.maturity if we're going to restart before running RTA. write_raw(ser, tc::getProfMainUsage()); ser.finalize(); if (serializeOptProfEnabled()) { s_lastMappers = std::move(ser.getMappers()); } return ""; } catch (std::runtime_error& err) { return folly::sformat("Failed to serialize profile data: {}", err.what()); } } std::string serializeOptProfData(const std::string& filename) { try { ProfDataSerializer ser(filename, ProfDataSerializer::FileMode::Append); ser.setMappers(std::move(s_lastMappers)); // If enabled, recompute the function order using the call graph obtained // via instrumentation of the optimized code, then serialize it. if (RuntimeOption::EvalJitPGOOptCodeCallGraph) { FuncOrder::compute(); } FuncOrder::serialize(ser); // Serialize the vasm block counters. VasmBlockCounters::serialize(ser); serializeCachedInliningCost(ser); ser.finalize(); return ""; } catch (std::runtime_error& err) { return folly::sformat("Failed serializeOptProfData: {}", err.what()); } } std::string deserializeProfData(const std::string& filename, int numWorkers, bool rds) { try { if (!rds) ProfData::setTriedDeserialization(); ProfDataDeserializer ser{filename}; if (read_raw<decltype(kMagic)>(ser) != kMagic) { throw std::runtime_error("Not a profile-data dump"); } auto signature = read_raw<decltype(RepoFile::globalData().Signature)>(ser); if (signature != RepoFile::globalData().Signature) { auto const msg = folly::sformat("Mismatched repo-schema (expected signature '{}')", RepoFile::globalData().Signature); throw std::runtime_error(msg); } auto size = read_raw<size_t>(ser); std::string schema; schema.resize(size); read_raw(ser, &schema[0], size); if (schema != repoSchemaId()) { auto const msg = folly::sformat("Mismatched repo-schema (expected schema_id '{}')", repoSchemaId()); throw std::runtime_error(msg); } size = read_raw<size_t>(ser); std::string buildHost; buildHost.resize(size); read_raw(ser, &buildHost[0], size); size = read_raw<size_t>(ser); std::string tag; tag.resize(size); read_raw(ser, &tag[0], size); int64_t buildTime; read_raw(ser, buildTime); auto const currTime = TimeStamp::Current(); if (buildTime <= currTime - 3600 * RuntimeOption::ProfDataTTLHours) { throw std::runtime_error( "Stale profile data (check Eval.ProfDataTTLHours)"); } else if (buildTime > currTime) { throw std::runtime_error( folly::sformat("profile data build timestame: {}, currTime: {}", buildTime, currTime).c_str()); } // If we're loading RDS ordering, we can stop here. auto const ordering = read_rds_ordering(ser); if (rds) { rds::setPreAssignments(ordering); return ""; } read_units_preload(ser); ExtensionRegistry::deserialize(ser); PropertyProfile::deserialize(ser); InstanceBits::deserialize(ser); read_global_array_map(ser); ProfData::Session pds; auto const pd = profData(); read_prof_data(ser, pd); pd->setDeserialized(buildHost, tag, buildTime); if (RO::EnableIntrinsicsExtension) { read_container(ser, [&] { read_class(ser); }); } deserializeSharedProfiles(ser); read_target_profiles(ser); read_seen_types(ser); if (allowBespokeArrayLikes()) { deserializeBespokeLayouts(ser); } readGlobalProfiles(ser); // If isJitSerializing() is true, we've restarted to reload the // profiling data. Record the size of prof before we restarted so // we can fake jit.maturity. auto const prevProfSize = read_raw<size_t>(ser); if (isJitSerializing()) ProfData::setPrevProfSize(prevProfSize); if (!ser.done()) { // We have profile data for the optimized code, so deserialize it too. FuncOrder::deserialize(ser); VasmBlockCounters::deserialize(ser); deserializeCachedInliningCost(ser); } if (s_preload_dispatcher) { BootStats::Block timer("DES_wait_for_units_preload", RuntimeOption::ServerExecutionMode()); s_preload_dispatcher->waitEmpty(true); delete s_preload_dispatcher; s_preload_dispatcher = nullptr; } always_assert(ser.done()); // During deserialization we didn't merge the loaded units because // we wanted to pick and choose the hot Funcs and Classes. But we // need to merge them before we start serving traffic to ensure we // don't have inconsistentcies (eg a persistent memoized Func // wrapper might have been merged, while its implementation was // not; since the implementation has an internal name, there won't // be an autoload entry for it, so unless something else causes // the unit to be loaded the implementation might never get pulled // in (resulting in fatals when the wrapper tries to call it). merge_loaded_units(numWorkers); if (isJitSerializing() && serializeOptProfEnabled()) { s_lastMappers = ser.getMappers(); } return ""; } catch (std::runtime_error& err) { return folly::sformat("Failed to deserialize profile data {}: {}", filename, err.what()); } } bool tryDeserializePartialProfData(const std::string& filename, int numWorkers, bool rds) { auto const mangled = mangleFilenameForAppendStart(filename); return deserializeProfData(mangled, numWorkers, rds).empty(); } bool serializeOptProfEnabled() { return RuntimeOption::EvalJitSerializeOptProfSeconds > 0 || RuntimeOption::EvalJitSerializeOptProfRequests > 0; } ////////////////////////////////////////////////////////////////////// } }