hphp/compiler/analysis/emitter.cpp (356 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/compiler/analysis/emitter.h"
#include "hphp/compiler/analysis/analysis_result.h"
#include "hphp/compiler/option.h"
#include "hphp/hhbbc/hhbbc.h"
#include "hphp/runtime/base/execution-context.h"
#include "hphp/runtime/base/program-functions.h"
#include "hphp/runtime/base/runtime-option.h"
#include "hphp/runtime/base/request-info.h"
#include "hphp/runtime/base/unit-cache.h"
#include "hphp/runtime/base/variable-serializer.h"
#include "hphp/runtime/vm/disas.h"
#include "hphp/runtime/vm/func-emitter.h"
#include "hphp/runtime/vm/preclass-emitter.h"
#include "hphp/runtime/vm/repo-autoload-map-builder.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-alias-emitter.h"
#include "hphp/runtime/vm/unit.h"
#include "hphp/runtime/vm/unit-parser.h"
#include "hphp/util/job-queue.h"
#include "hphp/util/logger.h"
#include <folly/ScopeGuard.h>
#include <exception>
#include <fstream>
#include <future>
#include <iostream>
#include <vector>
namespace HPHP {
namespace Compiler {
///////////////////////////////////////////////////////////////////////////////
namespace {
void genText(UnitEmitter* ue, const std::string& outputPath) {
std::unique_ptr<Unit> unit(ue->create());
auto const basePath = AnalysisResult::prepareFile(
outputPath.c_str(),
"php/" + unit->filepath()->toCppString(),
true, false);
if (Option::GenerateTextHHBC) {
auto const fullPath = basePath + ".hhbc.txt";
std::ofstream f(fullPath.c_str());
if (!f) {
Logger::Error("Unable to open %s for write", fullPath.c_str());
} else {
f << "Hash: " << ue->sha1().toString() << std::endl;
f << unit->toString();
f.close();
}
}
if (Option::GenerateHhasHHBC) {
auto const fullPath = basePath + ".hhas";
std::ofstream f(fullPath.c_str());
if (!f) {
Logger::Error("Unable to open %s for write", fullPath.c_str());
} else {
f << disassemble(unit.get());
f.close();
}
}
}
class GenTextWorker
: public JobQueueWorker<UnitEmitter*, const std::string*, true, true> {
public:
void doJob(JobType job) override {
try {
genText(job, *m_context);
} catch (Exception& e) {
Logger::Error(e.getMessage());
} catch (...) {
Logger::Error("Fatal: An unexpected exception was thrown");
}
}
void onThreadEnter() override {
g_context.getCheck();
}
void onThreadExit() override {
hphp_memory_cleanup();
}
};
void genText(const std::vector<std::unique_ptr<UnitEmitter>>& ues,
const std::string& outputPath) {
if (!ues.size()) return;
Timer timer(Timer::WallTime, "Generating text bytecode");
if (ues.size() > Option::ParserThreadCount && Option::ParserThreadCount > 1) {
JobQueueDispatcher<GenTextWorker> dispatcher {
Option::ParserThreadCount,
Option::ParserThreadCount,
0, false, &outputPath
};
dispatcher.start();
for (auto& ue : ues) {
dispatcher.enqueue(ue.get());
}
dispatcher.waitEmpty();
} else {
for (auto& ue : ues) {
genText(ue.get(), outputPath);
}
}
}
RepoGlobalData getGlobalData() {
auto const now = std::chrono::high_resolution_clock::now();
auto const nanos =
std::chrono::duration_cast<std::chrono::nanoseconds>(
now.time_since_epoch()
);
auto gd = RepoGlobalData{};
gd.Signature = nanos.count();
gd.CheckPropTypeHints = RuntimeOption::EvalCheckPropTypeHints;
gd.HardPrivatePropInference = true;
gd.PHP7_NoHexNumerics = RuntimeOption::PHP7_NoHexNumerics;
gd.PHP7_Substr = RuntimeOption::PHP7_Substr;
gd.PHP7_Builtins = RuntimeOption::PHP7_Builtins;
gd.HardGenericsUB = RuntimeOption::EvalEnforceGenericsUB >= 2;
gd.EnableIntrinsicsExtension = RuntimeOption::EnableIntrinsicsExtension;
gd.ForbidDynamicCallsToFunc = RuntimeOption::EvalForbidDynamicCallsToFunc;
gd.ForbidDynamicCallsWithAttr =
RuntimeOption::EvalForbidDynamicCallsWithAttr;
gd.ForbidDynamicCallsToClsMeth =
RuntimeOption::EvalForbidDynamicCallsToClsMeth;
gd.ForbidDynamicCallsToInstMeth =
RuntimeOption::EvalForbidDynamicCallsToInstMeth;
gd.ForbidDynamicConstructs = RuntimeOption::EvalForbidDynamicConstructs;
gd.LogKnownMethodsAsDynamicCalls =
RuntimeOption::EvalLogKnownMethodsAsDynamicCalls;
gd.EnableArgsInBacktraces = RuntimeOption::EnableArgsInBacktraces;
gd.NoticeOnBuiltinDynamicCalls =
RuntimeOption::EvalNoticeOnBuiltinDynamicCalls;
gd.InitialNamedEntityTableSize =
RuntimeOption::EvalInitialNamedEntityTableSize;
gd.InitialStaticStringTableSize =
RuntimeOption::EvalInitialStaticStringTableSize;
gd.HackArrCompatSerializeNotices =
RuntimeOption::EvalHackArrCompatSerializeNotices;
gd.AbortBuildOnVerifyError = RuntimeOption::EvalAbortBuildOnVerifyError;
gd.EmitClassPointers = RuntimeOption::EvalEmitClassPointers;
gd.EmitClsMethPointers = RuntimeOption::EvalEmitClsMethPointers;
gd.IsVecNotices = RuntimeOption::EvalIsVecNotices;
gd.RaiseClassConversionWarning =
RuntimeOption::EvalRaiseClassConversionWarning;
gd.ClassPassesClassname = RuntimeOption::EvalClassPassesClassname;
gd.ClassnameNotices = RuntimeOption::EvalClassnameNotices;
gd.ClassIsStringNotices = RuntimeOption::EvalClassIsStringNotices;
gd.StrictArrayFillKeys = RuntimeOption::StrictArrayFillKeys;
gd.TraitConstantInterfaceBehavior =
RuntimeOption::EvalTraitConstantInterfaceBehavior;
gd.BuildMayNoticeOnMethCallerHelperIsObject =
RO::EvalBuildMayNoticeOnMethCallerHelperIsObject;
gd.DiamondTraitMethods = RuntimeOption::EvalDiamondTraitMethods;
gd.EvalCoeffectEnforcementLevels = RO::EvalCoeffectEnforcementLevels;
gd.EnableImplicitContext = RO::EvalEnableImplicitContext;
for (auto const& elm : RuntimeOption::ConstantFunctions) {
auto const s = internal_serialize(tvAsCVarRef(elm.second));
gd.ConstantFunctions.emplace_back(elm.first, s.toCppString());
}
return gd;
}
/*
* It's an invariant that symbols in the repo must be Unique and
* Persistent. Normally HHBBC verifies this for us, but if we're not
* using HHBBC and writing directly to the repo, we must do it
* ourself. Verify all relevant symbols are unique and set the
* appropriate Attrs.
*
* We use a common set of verification functions exported from HHBBC
* (to keep error messages identical), so we need store the data in a
* certain way it expects.
*/
struct SymbolSets {
struct Unit {
const StringData* filename;
};
struct Data {
const StringData* name;
std::unique_ptr<Unit> unit;
Attr attrs;
};
using IMap = hphp_fast_map<
const StringData*,
std::unique_ptr<Data>,
string_data_hash,
string_data_isame
>;
using Map = hphp_fast_map<
const StringData*,
std::unique_ptr<Data>,
string_data_hash,
string_data_same
>;
IMap enums;
IMap classes;
IMap funcs;
IMap typeAliases;
Map constants;
static std::unique_ptr<Data> make(const UnitEmitter* ue,
const StringData* name,
Attr attrs) {
assertx(name->isStatic());
assertx(!ue || ue->m_filepath->isStatic());
std::unique_ptr<Unit> unit;
if (ue) {
unit = std::make_unique<SymbolSets::Unit>();
unit->filename = ue->m_filepath;
}
auto data = std::make_unique<SymbolSets::Data>();
data->name = name;
data->unit = std::move(unit);
data->attrs = attrs;
return data;
}
SymbolSets() {
// These aren't stored in the repo, but we still need to check for
// collisions against them, so put them in the maps.
for (auto const& kv : Native::getConstants()) {
assertx(kv.second.m_type != KindOfUninit ||
kv.second.dynamic());
HHBBC::add_symbol(
constants,
make(nullptr, kv.first, AttrUnique | AttrPersistent),
"constant"
);
}
}
};
void writeUnit(UnitEmitter& ue,
RepoFileBuilder& repoBuilder,
RepoAutoloadMapBuilder& autoloadMapBuilder,
SymbolSets& sets) {
// Verify uniqueness of symbols, set Attrs, then write to actual
// repo.
auto const make = [&] (const StringData* name, Attr attrs) {
return SymbolSets::make(&ue, name, attrs);
};
for (size_t n = 0; n < ue.numPreClasses(); ++n) {
auto pce = ue.pce(n);
pce->setAttrs(pce->attrs() | AttrUnique | AttrPersistent);
if (pce->attrs() & AttrEnum) {
HHBBC::add_symbol(sets.enums, make(pce->name(), pce->attrs()), "enum");
}
HHBBC::add_symbol(sets.classes, make(pce->name(), pce->attrs()), "class",
sets.typeAliases);
}
for (auto& fe : ue.fevec()) {
// Dedup meth_caller wrappers
if (fe->attrs & AttrIsMethCaller && sets.funcs.count(fe->name)) continue;
fe->attrs |= AttrUnique | AttrPersistent;
HHBBC::add_symbol(sets.funcs, make(fe->name, fe->attrs), "function");
}
for (auto& te : ue.typeAliases()) {
te->setAttrs(te->attrs() | AttrUnique | AttrPersistent);
HHBBC::add_symbol(sets.typeAliases, make(te->name(), te->attrs()),
"type alias", sets.classes);
}
for (auto& c : ue.constants()) {
c.attrs |= AttrUnique | AttrPersistent;
HHBBC::add_symbol(sets.constants, make(c.name, c.attrs), "constant");
}
autoloadMapBuilder.addUnit(ue);
repoBuilder.add(ue);
}
}
/*
* This is the entry point for offline bytecode generation.
*/
void emitAllHHBC(AnalysisResultPtr&& ar) {
auto ues = ar->getHhasFiles();
decltype(ues) ues_to_print;
auto const outputPath = ar->getOutputPath();
std::thread wp_thread;
std::future<void> fut;
auto unexpectedException = [&] (const char* what) {
if (wp_thread.joinable()) {
Logger::Error("emitAllHHBC exited via an exception "
"before wp_thread was joined: %s", what);
}
throw;
};
try {
{
SCOPE_EXIT {
genText(ues_to_print, outputPath);
};
Optional<RepoAutoloadMapBuilder> autoloadMapBuilder;
Optional<RepoFileBuilder> repoBuilder;
Optional<SymbolSets> symbolSets;
if (Option::GenerateBinaryHHBC) {
autoloadMapBuilder.emplace();
repoBuilder.emplace(RuntimeOption::RepoPath);
symbolSets.emplace();
}
auto program = std::move(ar->program());
if (!program.get()) {
uint32_t id = 0;
for (auto& ue : ues) {
ue->m_symbol_refs.clear();
ue->m_sn = id;
ue->setSha1(SHA1 { id });
if (repoBuilder) {
try {
writeUnit(*ue, *repoBuilder, *autoloadMapBuilder, *symbolSets);
} catch (const HHBBC::NonUniqueSymbolException&) {
ar->setFinish({});
throw;
}
}
if (Option::GenerateTextHHBC || Option::GenerateHhasHHBC) {
ues_to_print.emplace_back(std::move(ue));
}
id++;
}
ar->finish();
ar.reset();
if (repoBuilder) {
Timer finalizeTime(Timer::WallTime, "finalizing repo");
repoBuilder->finish(getGlobalData(), *autoloadMapBuilder);
}
return;
}
assertx(ues.size() == 0);
ar->finish();
ar.reset();
HHBBC::UnitEmitterQueue ueq{
repoBuilder ? &*autoloadMapBuilder : nullptr,
Option::GenerateTextHHBC || Option::GenerateHhasHHBC
};
RuntimeOption::EvalJit = false; // For HHBBC to invoke builtins.
std::unique_ptr<ArrayTypeTable::Builder> arrTable;
std::promise<void> arrTableReady;
fut = arrTableReady.get_future();
wp_thread = std::thread(
[program = std::move(program), &ueq, &arrTable, &arrTableReady]
() mutable {
Timer timer(Timer::WallTime, "running HHBBC");
HphpSessionAndThread _(Treadmill::SessionKind::CompilerEmit);
try {
// We rely on this function to provide a value to arrTable
HHBBC::whole_program(
std::move(program), ueq, arrTable,
Option::ParserThreadCount > 0 ? Option::ParserThreadCount : 0,
&arrTableReady);
} catch (...) {
arrTableReady.set_exception(std::current_exception());
ueq.finish();
}
}
);
Optional<Timer> commitTime;
while (auto encoded = ueq.pop()) {
if (!commitTime) {
commitTime.emplace(Timer::WallTime, "committing units to repo");
}
if (repoBuilder) repoBuilder->add(*encoded);
if (Option::GenerateTextHHBC || Option::GenerateHhasHHBC) {
if (auto ue = ueq.popUnitEmitter()) {
ues_to_print.emplace_back(std::move(ue));
}
}
}
fut.wait();
Timer finalizeTime(Timer::WallTime, "finalizing repo");
if (arrTable) globalArrayTypeTable().repopulate(*arrTable);
if (repoBuilder) {
repoBuilder->finish(getGlobalData(), *autoloadMapBuilder);
}
}
wp_thread.join();
fut.get(); // Exception thrown here if it holds one, otherwise no-op.
} catch (std::exception& ex) {
unexpectedException(ex.what());
} catch (const Object& o) {
unexpectedException("Object");
} catch (...) {
unexpectedException("non-standard-exception");
}
}
///////////////////////////////////////////////////////////////////////////////
}
}