hphp/runtime/vm/unit.cpp (407 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/unit.h"
#include <algorithm>
#include <atomic>
#include <cstdlib>
#include <cstring>
#include <iomanip>
#include <map>
#include <ostream>
#include <sstream>
#include <vector>
#include <boost/container/flat_map.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <folly/Format.h>
#include <tbb/concurrent_hash_map.h>
#include "hphp/system/systemlib.h"
#include "hphp/util/alloc.h"
#include "hphp/util/assertions.h"
#include "hphp/util/compilation-flags.h"
#include "hphp/util/functional.h"
#include "hphp/util/lock.h"
#include "hphp/util/mutex.h"
#include "hphp/util/struct-log.h"
#include "hphp/runtime/base/attr.h"
#include "hphp/runtime/base/array-iterator.h"
#include "hphp/runtime/base/autoload-handler.h"
#include "hphp/runtime/base/execution-context.h"
#include "hphp/runtime/base/file-util.h"
#include "hphp/runtime/base/rds.h"
#include "hphp/runtime/base/runtime-error.h"
#include "hphp/runtime/base/runtime-option.h"
#include "hphp/runtime/base/stats.h"
#include "hphp/runtime/base/string-data.h"
#include "hphp/runtime/base/strings.h"
#include "hphp/runtime/base/tv-mutate.h"
#include "hphp/runtime/base/tv-variant.h"
#include "hphp/runtime/base/tv-refcount.h"
#include "hphp/runtime/base/type-array.h"
#include "hphp/runtime/base/type-string.h"
#include "hphp/runtime/base/type-variant.h"
#include "hphp/runtime/base/typed-value.h"
#include "hphp/runtime/base/unit-cache.h"
#include "hphp/runtime/base/vanilla-vec.h"
#include "hphp/runtime/debugger/debugger.h"
#include "hphp/runtime/vm/bytecode.h"
#include "hphp/runtime/vm/class.h"
#include "hphp/runtime/vm/debug/debug.h"
#include "hphp/runtime/vm/frame-restore.h"
#include "hphp/runtime/vm/func.h"
#include "hphp/runtime/vm/hh-utils.h"
#include "hphp/runtime/vm/hhbc-codec.h"
#include "hphp/runtime/vm/hhbc.h"
#include "hphp/runtime/vm/instance-bits.h"
#include "hphp/runtime/vm/named-entity.h"
#include "hphp/runtime/vm/named-entity-defs.h"
#include "hphp/runtime/vm/preclass.h"
#include "hphp/runtime/vm/preclass-emitter.h"
#include "hphp/runtime/vm/reverse-data-map.h"
#include "hphp/runtime/vm/treadmill.h"
#include "hphp/runtime/vm/type-alias.h"
#include "hphp/runtime/vm/unit-emitter.h"
#include "hphp/runtime/vm/unit-util.h"
#include "hphp/runtime/vm/vm-regs.h"
#include "hphp/runtime/server/memory-stats.h"
#include "hphp/runtime/server/source-root-info.h"
#include "hphp/runtime/ext/std/ext_std_closure.h"
#include "hphp/runtime/ext/server/ext_server.h"
#include "hphp/runtime/ext/string/ext_string.h"
#include "hphp/runtime/ext/vsdebug/debugger.h"
#include "hphp/runtime/ext/vsdebug/ext_vsdebug.h"
#include "hphp/system/systemlib.h"
#include "hphp/runtime/base/program-functions.h"
namespace HPHP {
//////////////////////////////////////////////////////////////////////
TRACE_SET_MOD(hhbc);
//////////////////////////////////////////////////////////////////////
std::atomic<size_t> Unit::s_createdUnits{0};
std::atomic<size_t> Unit::s_liveUnits{0};
///////////////////////////////////////////////////////////////////////////////
// Construction and destruction.
Unit::Unit()
: m_interpretOnly(false)
, m_extended(false)
, m_ICE(false)
{
++s_createdUnits;
++s_liveUnits;
}
Unit::~Unit() {
if (RuntimeOption::EvalEnableReverseDataMap &&
m_mergeState.load(std::memory_order_relaxed) != MergeState::Unmerged) {
// Units are registered to data_map in Unit::initialMerge().
data_map::deregister(this);
}
for (auto const func : funcs()) Func::destroy(func);
// ExecutionContext and the TC may retain references to Class'es, so
// it is possible for Class'es to outlive their Unit.
for (auto const& pcls : m_preClasses) {
Class* cls = pcls->namedEntity()->clsList();
while (cls) {
Class* cur = cls;
cls = cls->m_next;
if (cur->preClass() == pcls.get()) {
cur->destroy();
}
}
}
--s_liveUnits;
}
void* Unit::operator new(size_t sz) {
MemoryStats::LogAlloc(AllocKind::Unit, sz);
return low_malloc(sz);
}
void Unit::operator delete(void* p, size_t /*sz*/) {
low_free(p);
}
///////////////////////////////////////////////////////////////////////////////
// Code locations.
bool Unit::getOffsetRanges(int line, OffsetFuncRangeVec& offsets) const {
assertx(offsets.size() == 0);
forEachFunc([&](const Func* func) {
// Quickly ignore functions that can't have that line in them
if (line < func->line1() || line > func->line2()) return false;
auto map = func->getLineToOffsetRangeVecMap();
auto it = map.find(line);
if (it != map.end()) {
offsets.push_back(std::pair(func, it->second));
}
return false;
});
return offsets.size() != 0;
}
bool Unit::isCoverageEnabled() const {
return m_coverage.bound() && m_coverage.isInit();
}
void Unit::enableCoverage() {
if (!m_coverage.bound()) {
assertx(!RO::RepoAuthoritative && RO::EvalEnablePerFileCoverage);
m_coverage.bind(
rds::Mode::Normal,
rds::LinkName{"UnitCoverage", origFilepath()}
);
}
if (m_coverage.isInit()) return;
new (m_coverage.get()) req::dynamic_bitset{};
m_coverage.markInit();
}
void Unit::disableCoverage() {
if (!isCoverageEnabled()) return;
m_coverage.markUninit();
m_coverage->req::dynamic_bitset::~dynamic_bitset();
}
void Unit::clearCoverage() {
assertx(isCoverageEnabled());
m_coverage->reset();
}
void Unit::recordCoverage(int line) {
assertx(isCoverageEnabled());
if (line == -1) return;
if (m_coverage->size() <= line) m_coverage->resize(line + 1);
m_coverage->set(line);
}
Array Unit::reportCoverage() const {
assertx(isCoverageEnabled());
auto const& c = *m_coverage;
auto const end = req::dynamic_bitset::npos;
VecInit init{m_coverage->count()};
for (auto i = c.find_first(); i != end; i = c.find_next(i)) {
init.append(i);
}
return init.toArray();
}
rds::Handle Unit::coverageDataHandle() const {
assertx(m_coverage.bound());
return m_coverage.handle();
}
///////////////////////////////////////////////////////////////////////////////
// Funcs and PreClasses.
Func* Unit::getEntryPoint() const {
if (m_entryPointId == kInvalidId) {
return nullptr;
}
return lookupFuncId(m_entryPointId);
}
///////////////////////////////////////////////////////////////////////////////
// Merge.
namespace {
///////////////////////////////////////////////////////////////////////////////
SimpleMutex unitInitLock(false /* reentrant */, RankUnitInit);
std::atomic<uint64_t> s_loadedUnits{0};
bool isEvalName(const StringData* name) {
return name->empty() || boost::ends_with(name->slice(), EVAL_FILENAME_SUFFIX);
}
///////////////////////////////////////////////////////////////////////////////
}
void Unit::initialMerge() {
unitInitLock.assertOwnedBySelf();
if (m_mergeState.load(std::memory_order_relaxed) != MergeState::Unmerged) {
return;
}
auto const nrecord = RuntimeOption::EvalRecordFirstUnits;
if (s_loadedUnits.load(std::memory_order_relaxed) < nrecord) {
auto const index = s_loadedUnits.fetch_add(1, std::memory_order_relaxed);
if (index < nrecord) {
StructuredLogEntry ent;
ent.setStr("path", m_origFilepath->data());
ent.setInt("index", index);
if (RuntimeOption::ServerExecutionMode()) {
ent.setInt("uptime", f_server_uptime());
}
StructuredLog::log("hhvm_first_units", ent);
}
}
if (RuntimeOption::EvalEnableReverseDataMap) {
data_map::register_start(this);
}
if (!RO::RepoAuthoritative && RO::EvalEnablePerFileCoverage) {
m_coverage.bind(
rds::Mode::Normal,
rds::LinkName{"UnitCoverage", origFilepath()}
);
}
m_mergeState.store(MergeState::InitialMerged, std::memory_order_relaxed);
}
void Unit::merge() {
if (LIKELY(m_mergeState.load(std::memory_order_relaxed) == MergeState::Merged)) {
return;
}
if (m_fatalInfo) {
raise_parse_error(filepath(),
m_fatalInfo->m_fatalMsg.c_str(),
m_fatalInfo->m_fatalLoc);
}
auto mergeTypes = MergeTypes::Everything;
if (UNLIKELY((m_mergeState.load(std::memory_order_relaxed) == MergeState::Unmerged))) {
SimpleLock lock(unitInitLock);
initialMerge();
} else if (!RuntimeOption::RepoAuthoritative && !SystemLib::s_inited && RuntimeOption::EvalJitEnableRenameFunction) {
mergeTypes = MergeTypes::Function;
}
mergeImpl(mergeTypes);
if (RuntimeOption::RepoAuthoritative || (!SystemLib::s_inited && !RuntimeOption::EvalJitEnableRenameFunction)) {
m_mergeState.store(MergeState::Merged, std::memory_order_relaxed);
}
}
void Unit::logTearing(int64_t nsecs) {
assertx(!RO::RepoAuthoritative);
assertx(RO::EvalSampleRequestTearing);
auto const repoOptions = g_context->getRepoOptionsForRequest();
auto repoRoot = folly::fs::path(repoOptions->path()).parent_path();
assertx(!isSystemLib());
StructuredLogEntry ent;
auto const tpath = [&] () -> std::string {
auto const orig = folly::fs::path(origFilepath()->data());
if (repoRoot.size() > orig.size() || repoRoot.empty()) return orig.native();
if (!std::equal(repoRoot.begin(), repoRoot.end(), orig.begin())) {
return orig.native();
}
return orig.lexically_relative(repoRoot).native();
}();
auto const debuggerCount = [&] {
if (RuntimeOption::EnableHphpdDebugger) {
return Eval::Debugger::CountConnectedProxy();
}
HPHP::VSDEBUG::Debugger* vspDebugger =
HPHP::VSDEBUG::VSDebugExtension::getDebugger();
if (vspDebugger != nullptr && vspDebugger->clientConnected()) {
return 1;
}
return 0;
}();
auto const hash = sha1().toString();
auto const bchash = bcSha1().toString();
ent.setStr("filepath", tpath);
ent.setStr("sha1", hash);
ent.setStr("bc_sha1", bchash);
ent.setInt("time_diff_ns", nsecs);
ent.setInt("debuggers", debuggerCount);
// always generate logs
ent.force_init = RO::EvalSampleRequestTearingForce;
FTRACE(2, "Tearing in {} ({} ns)\n", tpath.data(), nsecs);
StructuredLog::log("hhvm_sandbox_file_tearing", ent);
}
template <typename T, typename I>
static bool defineSymbols(const T& symbols, boost::dynamic_bitset<>& define, bool failIsFatal, Stats::StatCounter counter, I lambda) {
auto i = define.find_first();
if (failIsFatal) {
if (i == define.npos) i = define.find_first();
}
bool madeProgress = false;
for (; i != define.npos; i = define.find_next(i)) {
auto& symbol = symbols[i];
Stats::inc(Stats::UnitMerge_mergeable);
Stats::inc(counter);
auto res = lambda(symbol);
if (res) {
define.reset(i);
madeProgress = true;
}
}
return madeProgress;
}
void Unit::mergeImpl(MergeTypes mergeTypes) {
assertx(m_mergeState.load(std::memory_order_relaxed) >= MergeState::InitialMerged);
autoTypecheck(this);
FTRACE(1, "Merging unit {} ({} funcs, {} constants, {} typealiases, {} classes)\n",
this->m_origFilepath->data(), m_funcs.size(), m_constants.size(), m_typeAliases.size(),
m_preClasses.size());
if (mergeTypes & MergeTypes::Function) {
for (auto func : funcs()) {
Func::def(func);
}
}
if (mergeTypes & MergeTypes::NotFunction) {
for (auto& constant : m_constants) {
Stats::inc(Stats::UnitMerge_mergeable);
Stats::inc(Stats::UnitMerge_mergeable_define);
assertx((!!(constant.attrs & AttrPersistent)) == (this->isSystemLib() || RuntimeOption::RepoAuthoritative));
Constant::def(&constant);
}
boost::dynamic_bitset<> preClasses(m_preClasses.size());
preClasses.set();
boost::dynamic_bitset<> typeAliases(m_typeAliases.size());
typeAliases.set();
bool failIsFatal = false;
do {
bool madeProgress = false;
madeProgress = defineSymbols(m_preClasses, preClasses, failIsFatal,
Stats::UnitMerge_mergeable_class,
[&](const PreClassPtr& preClass) {
// Anonymous classes doesn't need to be defined because they will be defined when used
if (PreClassEmitter::IsAnonymousClassName(preClass->name()->toCppString())) {
return true;
}
assertx(preClass->isPersistent() == (this->isSystemLib() || RuntimeOption::RepoAuthoritative));
return Class::def(preClass.get(), failIsFatal) != nullptr;
}) || madeProgress;
// We do type alias last because they may depend on classes that needs to be define first
madeProgress = defineSymbols(m_typeAliases, typeAliases, failIsFatal,
Stats::UnitMerge_mergeable_typealias,
[&](const PreTypeAlias& typeAlias) {
assertx(typeAlias.isPersistent() == (this->isSystemLib() || RuntimeOption::RepoAuthoritative));
return TypeAlias::def(&typeAlias, failIsFatal);
}) || madeProgress;
if (!madeProgress) failIsFatal = true;
} while (typeAliases.any() || preClasses.any());
}
}
bool Unit::isSystemLib() const {
return FileUtil::isSystemName(m_origFilepath->slice());
}
///////////////////////////////////////////////////////////////////////////////
// Info arrays.
namespace {
Array getClassesWithAttrInfo(Attr attrs, bool inverse = false) {
auto builtins = Array::CreateVec();
auto non_builtins = Array::CreateVec();
NamedEntity::foreach_cached_class([&](Class* c) {
if ((c->attrs() & attrs) ? !inverse : inverse) {
if (c->isBuiltin()) {
builtins.append(make_tv<KindOfPersistentString>(c->name()));
} else {
non_builtins.append(make_tv<KindOfPersistentString>(c->name()));
}
}
});
if (builtins.empty()) return non_builtins;
auto all = VecInit(builtins.size() + non_builtins.size());
for (auto i = builtins.size(); i > 0; i--) {
all.append(builtins.lookup(safe_cast<int64_t>(i - 1)));
}
IterateV(non_builtins.get(), [&](auto name) { all.append(name); });
return all.toArray();
}
template<bool system>
Array getFunctions() {
// Return an array of all defined functions. This method is used
// to support get_defined_functions().
Array a = Array::CreateVec();
NamedEntity::foreach_cached_func([&](Func* func) {
if ((system ^ func->isBuiltin()) || func->isGenerated()) return; //continue
a.append(HHVM_FN(strtolower)(func->nameStr()));
});
return a;
}
///////////////////////////////////////////////////////////////////////////////
}
Array Unit::getClassesInfo() {
return getClassesWithAttrInfo(AttrInterface | AttrTrait,
/* inverse = */ true);
}
Array Unit::getInterfacesInfo() {
return getClassesWithAttrInfo(AttrInterface);
}
Array Unit::getTraitsInfo() {
return getClassesWithAttrInfo(AttrTrait);
}
Array Unit::getUserFunctions() {
return getFunctions<false>();
}
Array Unit::getSystemFunctions() {
return getFunctions<true>();
}
///////////////////////////////////////////////////////////////////////////////
// Pretty printer.
std::string Unit::toString() const {
std::ostringstream ss;
for (auto& pc : m_preClasses) {
pc->prettyPrint(ss);
}
for (auto& func : funcs()) {
func->prettyPrint(ss);
}
for (auto& cns : constants()) {
cns.prettyPrint(ss);
}
return ss.str();
}
///////////////////////////////////////////////////////////////////////////////
// Other methods.
std::string mangleReifiedGenericsName(const ArrayData* tsList) {
std::vector<std::string> l;
IterateV(
tsList,
[&](TypedValue v) {
assertx(tvIsDict(v));
auto str =
TypeStructure::toString(ArrNR(v.m_data.parr),
TypeStructure::TSDisplayType::TSDisplayTypeInternal).toCppString();
str.erase(remove_if(str.begin(), str.end(), isspace), str.end());
l.emplace_back(str);
}
);
return folly::sformat("<{}>", folly::join(",", l));
}
}