993 linhas
31 KiB
C++
993 linhas
31 KiB
C++
/*
|
|
+----------------------------------------------------------------------+
|
|
| HipHop for PHP |
|
|
+----------------------------------------------------------------------+
|
|
| Copyright (c) 2010- 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 <boost/program_options/options_description.hpp>
|
|
#include <boost/program_options/positional_options.hpp>
|
|
#include <boost/program_options/variables_map.hpp>
|
|
#include <boost/program_options/parsers.hpp>
|
|
|
|
#include <compiler/package.h>
|
|
#include <compiler/analysis/analysis_result.h>
|
|
#include <compiler/analysis/alias_manager.h>
|
|
#include <compiler/analysis/code_error.h>
|
|
#include <compiler/analysis/emitter.h>
|
|
#include <compiler/analysis/type.h>
|
|
#include <util/json.h>
|
|
#include <util/logger.h>
|
|
#include <compiler/analysis/symbol_table.h>
|
|
#include <compiler/option.h>
|
|
#include <compiler/parser/parser.h>
|
|
#include <compiler/builtin_symbols.h>
|
|
#include <util/db_conn.h>
|
|
#include <util/exception.h>
|
|
#include <util/process.h>
|
|
#include <util/util.h>
|
|
#include <util/timer.h>
|
|
#include <util/hdf.h>
|
|
#include <util/async_func.h>
|
|
#include <runtime/base/program_functions.h>
|
|
#include <runtime/base/memory/smart_allocator.h>
|
|
#include <runtime/base/externals.h>
|
|
#include <runtime/base/thread_init_fini.h>
|
|
#include <runtime/vm/repo.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
#include <dlfcn.h>
|
|
#include <system/lib/systemlib.h>
|
|
#include <compiler/compiler.h>
|
|
#include "util/repo_schema.h"
|
|
|
|
#include "hhvm/process_init.h"
|
|
|
|
using namespace boost::program_options;
|
|
using std::cout;
|
|
|
|
namespace HPHP {
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
struct CompilerOptions {
|
|
string target;
|
|
string format;
|
|
string outputDir;
|
|
string outputFile;
|
|
string syncDir;
|
|
vector<string> config;
|
|
string configDir;
|
|
vector<string> confStrings;
|
|
string inputDir;
|
|
vector<string> inputs;
|
|
string inputList;
|
|
vector<string> includePaths;
|
|
vector<string> modules;
|
|
vector<string> excludeDirs;
|
|
vector<string> excludeFiles;
|
|
vector<string> excludePatterns;
|
|
vector<string> excludeStaticDirs;
|
|
vector<string> excludeStaticFiles;
|
|
vector<string> excludeStaticPatterns;
|
|
vector<string> fmodules;
|
|
vector<string> ffiles;
|
|
vector<string> cfiles;
|
|
vector<string> cmodules;
|
|
bool parseOnDemand;
|
|
vector<string> parseOnDemandDirs; // parse these directories on-demand
|
|
// when parseOnDemand=false
|
|
string program;
|
|
string programArgs;
|
|
string branch;
|
|
int revision;
|
|
bool genStats;
|
|
bool keepTempDir;
|
|
string dbStats;
|
|
bool noTypeInference;
|
|
bool noMinInclude;
|
|
bool noMetaInfo;
|
|
int logLevel;
|
|
bool force;
|
|
int clusterCount;
|
|
int optimizeLevel;
|
|
string filecache;
|
|
string javaRoot;
|
|
bool dump;
|
|
string docjson;
|
|
bool coredump;
|
|
bool nofork;
|
|
bool fl_annotate;
|
|
string optimizations;
|
|
string ppp;
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
class AsyncFileCacheSaver : public AsyncFunc<AsyncFileCacheSaver> {
|
|
public:
|
|
AsyncFileCacheSaver(Package *package, const char *name)
|
|
: AsyncFunc<AsyncFileCacheSaver>(this, &AsyncFileCacheSaver::saveCache),
|
|
m_package(package), m_name(name) {
|
|
}
|
|
|
|
void saveCache() {
|
|
Timer timer(Timer::WallTime, "saving file cache...");
|
|
m_package->getFileCache()->save(m_name);
|
|
|
|
struct stat sb;
|
|
stat(m_name, &sb);
|
|
Logger::Info("%dMB %s saved", (int64_t)sb.st_size/(1024*1024), m_name);
|
|
}
|
|
|
|
private:
|
|
Package *m_package;
|
|
const char *m_name;
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// forward declarations
|
|
|
|
int prepareOptions(CompilerOptions &po, int argc, char **argv);
|
|
void createOutputDirectory(CompilerOptions &po);
|
|
int process(const CompilerOptions &po);
|
|
int lintTarget(const CompilerOptions &po);
|
|
int analyzeTarget(const CompilerOptions &po, AnalysisResultPtr ar);
|
|
int phpTarget(const CompilerOptions &po, AnalysisResultPtr ar);
|
|
void hhbcTargetInit(const CompilerOptions &po, AnalysisResultPtr ar);
|
|
int hhbcTarget(const CompilerOptions &po, AnalysisResultPtr ar,
|
|
AsyncFileCacheSaver &fcThread);
|
|
int runTargetCheck(const CompilerOptions &po, AnalysisResultPtr ar,
|
|
AsyncFileCacheSaver &fcThread);
|
|
int runTarget(const CompilerOptions &po);
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
extern "C" void compiler_hook_initialize();
|
|
|
|
int compiler_main(int argc, char **argv) {
|
|
try {
|
|
Hdf empty;
|
|
RuntimeOption::Load(empty);
|
|
|
|
CompilerOptions po;
|
|
#ifdef FACEBOOK
|
|
compiler_hook_initialize();
|
|
#endif
|
|
|
|
int ret = prepareOptions(po, argc, argv);
|
|
if (ret == 1) return 0; // --help
|
|
if (ret == -1) return -1; // command line error
|
|
|
|
Timer totalTimer(Timer::WallTime, "running hphp");
|
|
createOutputDirectory(po);
|
|
if (ret == 0) {
|
|
if (!po.nofork && !Process::IsUnderGDB()) {
|
|
int pid = fork();
|
|
if (pid == 0) {
|
|
ret = process(po);
|
|
_exit(ret);
|
|
}
|
|
wait(&ret);
|
|
ret = WIFEXITED(ret) ? WEXITSTATUS(ret) : -1;
|
|
} else {
|
|
ret = process(po);
|
|
}
|
|
}
|
|
if (ret == 0) {
|
|
if (po.target == "run") {
|
|
ret = runTarget(po);
|
|
}
|
|
}
|
|
if (ret) {
|
|
Logger::Error("hphp failed");
|
|
} else {
|
|
Logger::Info("all files saved in %s ...", po.outputDir.c_str());
|
|
}
|
|
return ret;
|
|
} catch (Exception &e) {
|
|
Logger::Error("Exception: %s\n", e.getMessage().c_str());
|
|
} catch (const FailedAssertion& fa) {
|
|
fa.print();
|
|
StackTraceNoHeap::AddExtraLogging("Assertion failure", fa.summary);
|
|
abort();
|
|
} catch (std::exception &e) {
|
|
Logger::Error("std::exception: %s\n", e.what());
|
|
} catch (...) {
|
|
Logger::Error("(unknown exception was thrown)\n");
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
int prepareOptions(CompilerOptions &po, int argc, char **argv) {
|
|
options_description desc("HipHop Compiler for PHP Usage:\n\n"
|
|
"\thphp <options> <inputs>\n\n"
|
|
"Options");
|
|
desc.add_options()
|
|
("help", "display this message")
|
|
("version", "display version number")
|
|
("target,t", value<string>(&po.target)->default_value("run"),
|
|
"lint | "
|
|
"analyze | "
|
|
"php | "
|
|
"hhbc | "
|
|
"filecache | "
|
|
"run (default)")
|
|
("format,f", value<string>(&po.format),
|
|
"lint: (none); \n"
|
|
"analyze: (none); \n"
|
|
"php: trimmed (default) | inlined | pickled | typeinfo |"
|
|
" <any combination of them by any separator>; \n"
|
|
"hhbc: binary (default) | text; \n"
|
|
"run: cluster (default) | file")
|
|
("cluster-count", value<int>(&po.clusterCount)->default_value(0),
|
|
"Cluster by file sizes and output roughly these many number of files. "
|
|
"Use 0 for no clustering.")
|
|
("input-dir", value<string>(&po.inputDir), "input directory")
|
|
("program", value<string>(&po.program)->default_value("program"),
|
|
"final program name to use")
|
|
("args", value<string>(&po.programArgs), "program arguments")
|
|
("inputs,i", value<vector<string> >(&po.inputs), "input file names")
|
|
("input-list", value<string>(&po.inputList),
|
|
"file containing list of file names, one per line")
|
|
("include-path",
|
|
value<vector<string> >(&po.includePaths)->composing(),
|
|
"a list of full paths to search for files being included in includes "
|
|
"or requires but cannot be found assuming relative paths")
|
|
("module", value<vector<string> >(&po.modules)->composing(),
|
|
"directories containing all input files")
|
|
("exclude-dir", value<vector<string> >(&po.excludeDirs)->composing(),
|
|
"directories to exclude from the input")
|
|
("fmodule", value<vector<string> >(&po.fmodules)->composing(),
|
|
"same with module, except no exclusion checking is performed, so these "
|
|
"modules are forced to be included")
|
|
("ffile", value<vector<string> >(&po.ffiles)->composing(),
|
|
"extra PHP files forced to include without exclusion checking")
|
|
("exclude-file", value<vector<string> >(&po.excludeFiles)->composing(),
|
|
"files to exclude from the input, even if parse-on-demand finds it")
|
|
("exclude-pattern",
|
|
value<vector<string> >(&po.excludePatterns)->composing(),
|
|
"regex (in 'find' command's regex command line option format) of files "
|
|
"or directories to exclude from the input, even if parse-on-demand finds "
|
|
"it")
|
|
("exclude-static-pattern",
|
|
value<vector<string> >(&po.excludeStaticPatterns)->composing(),
|
|
"regex (in 'find' command's regex command line option format) of files "
|
|
"or directories to exclude from static content cache")
|
|
("exclude-static-dir",
|
|
value<vector<string> >(&po.excludeStaticDirs)->composing(),
|
|
"directories to exclude from static content cache")
|
|
("exclude-static-file",
|
|
value<vector<string> >(&po.excludeStaticFiles)->composing(),
|
|
"files to exclude from static content cache")
|
|
("cfile", value<vector<string> >(&po.cfiles)->composing(),
|
|
"extra static files forced to include without exclusion checking")
|
|
("cmodule", value<vector<string> >(&po.cmodules)->composing(),
|
|
"extra directories for static files without exclusion checking")
|
|
("parse-on-demand", value<bool>(&po.parseOnDemand)->default_value(true),
|
|
"whether to parse files that are not specified from command line")
|
|
("branch", value<string>(&po.branch), "SVN branch")
|
|
("revision", value<int>(&po.revision), "SVN revision")
|
|
("output-dir,o", value<string>(&po.outputDir), "output directory")
|
|
("output-file", value<string>(&po.outputFile), "output file")
|
|
("sync-dir", value<string>(&po.syncDir),
|
|
"Files will be created in this directory first, then sync with output "
|
|
"directory without overwriting identical files. Great for incremental "
|
|
"compilation and build.")
|
|
("optimize-level", value<int>(&po.optimizeLevel)->default_value(-1),
|
|
"optimization level")
|
|
("gen-stats", value<bool>(&po.genStats)->default_value(false),
|
|
"whether to generate code errors")
|
|
("keep-tempdir,k", value<bool>(&po.keepTempDir)->default_value(false),
|
|
"whether to keep the temporary directory")
|
|
("db-stats", value<string>(&po.dbStats),
|
|
"database connection string to save code errors: "
|
|
"<username>:<password>@<host>:<port>/<db>")
|
|
("no-type-inference",
|
|
value<bool>(&po.noTypeInference)->default_value(false),
|
|
"turn off type inference for C++ code generation")
|
|
("no-min-include",
|
|
value<bool>(&po.noMinInclude)->default_value(false),
|
|
"turn off minimium include analysis when target is \"analyze\"")
|
|
("no-meta-info",
|
|
value<bool>(&po.noMetaInfo)->default_value(false),
|
|
"do not generate class map, function jump table and macros "
|
|
"when generating code; good for demo purposes")
|
|
("config,c", value<vector<string> >(&po.config)->composing(),
|
|
"config file name")
|
|
("config-dir", value<string>(&po.configDir),
|
|
"root directory configuration is based on (for example, "
|
|
"excluded directories may be relative path in configuration.")
|
|
("config-value,v", value<vector<string> >(&po.confStrings)->composing(),
|
|
"individual configuration string in a format of name=value, where "
|
|
"name can be any valid configuration for a config file")
|
|
("log,l",
|
|
value<int>(&po.logLevel)->default_value(-1),
|
|
"-1: (default); 0: no logging; 1: errors only; 2: warnings and errors; "
|
|
"3: informational as well; 4: really verbose.")
|
|
("force",
|
|
value<bool>(&po.force)->default_value(true),
|
|
"force to ignore code generation errors and continue compilations")
|
|
("file-cache",
|
|
value<string>(&po.filecache),
|
|
"if specified, generate a static file cache with this file name")
|
|
("dump",
|
|
value<bool>(&po.dump)->default_value(false),
|
|
"dump the program graph")
|
|
("docjson",
|
|
value<string>(&po.docjson)->default_value(""),
|
|
"Filename to generate a JSON file for PHP docs")
|
|
("coredump",
|
|
value<bool>(&po.coredump)->default_value(false),
|
|
"turn on coredump")
|
|
("nofork",
|
|
value<bool>(&po.nofork)->default_value(false),
|
|
"forking is needed for large compilation to release memory before g++"
|
|
"compilation. turning off forking can help gdb debugging.")
|
|
("fl-annotate",
|
|
value<bool>(&po.fl_annotate)->default_value(false),
|
|
"Annotate emitted source with compiler file-line info")
|
|
("opts",
|
|
value<string>(&po.optimizations)->default_value(""),
|
|
"Set optimizations to enable/disable")
|
|
("ppp",
|
|
value<string>(&po.ppp)->default_value(""),
|
|
"Preprocessed partition configuration. To speed up distcc compilation, "
|
|
"bin/ppp.php can pre-compute better partition between different .cpp "
|
|
"files according to preprocessed file sizes, instead of original file "
|
|
"sizes (default). Run bin/ppp.php to generate an HDF configuration file "
|
|
"to specify here.")
|
|
("compiler-id", "display the git hash for the compiler id")
|
|
("repo-schema", "display the repo schema id used by this app")
|
|
("taint-status", "check if the compiler was built with taint enabled")
|
|
;
|
|
|
|
positional_options_description p;
|
|
p.add("inputs", -1);
|
|
variables_map vm;
|
|
try {
|
|
store(command_line_parser(argc, argv).options(desc).positional(p).run(),
|
|
vm);
|
|
notify(vm);
|
|
} catch (unknown_option e) {
|
|
Logger::Error("Error in command line: %s\n\n", e.what());
|
|
cout << desc << "\n";
|
|
return -1;
|
|
}
|
|
if (argc <= 1 || vm.count("help")) {
|
|
cout << desc << "\n";
|
|
return 1;
|
|
}
|
|
if (vm.count("version")) {
|
|
#ifdef HPHP_VERSION
|
|
#undef HPHP_VERSION
|
|
#endif
|
|
|
|
#ifdef HPHP_COMPILER_STR
|
|
#undef HPHP_COMPILER_STR
|
|
#endif
|
|
|
|
#ifdef DEBUG
|
|
#define HPHP_COMPILER_STR "HipHop Compiler (Debug Build) v"
|
|
#else
|
|
#define HPHP_COMPILER_STR "HipHop Compiler v"
|
|
#endif
|
|
|
|
#define HPHP_VERSION(v) cout << HPHP_COMPILER_STR #v << "\n";
|
|
#include "../version"
|
|
|
|
cout << "Compiler: " << kCompilerId << "\n";
|
|
cout << "Repo schema: " << kRepoSchemaId << "\n";
|
|
return 1;
|
|
}
|
|
|
|
if (vm.count("compiler-id")) {
|
|
cout << kCompilerId << "\n";
|
|
return 1;
|
|
}
|
|
|
|
if (vm.count("repo-schema")) {
|
|
cout << kRepoSchemaId << "\n";
|
|
return 1;
|
|
}
|
|
|
|
if (vm.count("taint-status")) {
|
|
#ifdef TAINTED
|
|
cout << TAINTED << "\n";
|
|
#endif
|
|
return 1;
|
|
}
|
|
|
|
if ((po.target == "hhbc" || po.target == "run") &&
|
|
po.format.find("exe") == string::npos) {
|
|
if (po.program == "program") {
|
|
po.program = "hhvm.hhbc";
|
|
}
|
|
}
|
|
|
|
// log level
|
|
if (po.logLevel != -1) {
|
|
Logger::LogLevel = (Logger::LogLevelType)po.logLevel;
|
|
} else if (po.target == "run") {
|
|
Logger::LogLevel = Logger::LogNone;
|
|
} else {
|
|
Logger::LogLevel = Logger::LogInfo;
|
|
}
|
|
|
|
Option::FlAnnotate = po.fl_annotate;
|
|
|
|
Hdf config;
|
|
for (vector<string>::const_iterator it = po.config.begin();
|
|
it != po.config.end(); ++it) {
|
|
config.append(*it);
|
|
}
|
|
for (unsigned int i = 0; i < po.confStrings.size(); i++) {
|
|
config.fromString(po.confStrings[i].c_str());
|
|
}
|
|
Option::Load(config);
|
|
vector<string> badnodes;
|
|
config.lint(badnodes);
|
|
for (unsigned int i = 0; i < badnodes.size(); i++) {
|
|
Logger::Error("Possible bad config node: %s", badnodes[i].c_str());
|
|
}
|
|
|
|
if (po.dump) Option::DumpAst = true;
|
|
|
|
if (po.inputDir.empty()) {
|
|
po.inputDir = '.';
|
|
}
|
|
po.inputDir = Util::normalizeDir(po.inputDir);
|
|
if (po.configDir.empty()) {
|
|
po.configDir = po.inputDir;
|
|
}
|
|
po.configDir = Util::normalizeDir(po.configDir);
|
|
Option::RootDirectory = po.configDir;
|
|
Option::IncludeSearchPaths = po.includePaths;
|
|
|
|
for (unsigned int i = 0; i < po.excludeDirs.size(); i++) {
|
|
Option::PackageExcludeDirs.insert
|
|
(Util::normalizeDir(po.excludeDirs[i]));
|
|
}
|
|
for (unsigned int i = 0; i < po.excludeFiles.size(); i++) {
|
|
Option::PackageExcludeFiles.insert(po.excludeFiles[i]);
|
|
}
|
|
for (unsigned int i = 0; i < po.excludePatterns.size(); i++) {
|
|
Option::PackageExcludePatterns.insert
|
|
(Util::format_pattern(po.excludePatterns[i], true));
|
|
}
|
|
for (unsigned int i = 0; i < po.excludeStaticDirs.size(); i++) {
|
|
Option::PackageExcludeStaticDirs.insert
|
|
(Util::normalizeDir(po.excludeStaticDirs[i]));
|
|
}
|
|
for (unsigned int i = 0; i < po.excludeStaticFiles.size(); i++) {
|
|
Option::PackageExcludeStaticFiles.insert(po.excludeStaticFiles[i]);
|
|
}
|
|
for (unsigned int i = 0; i < po.excludeStaticPatterns.size(); i++) {
|
|
Option::PackageExcludeStaticPatterns.insert
|
|
(Util::format_pattern(po.excludeStaticPatterns[i], true));
|
|
}
|
|
|
|
if (po.target == "hhbc" || po.target == "run") {
|
|
Option::AnalyzePerfectVirtuals = false;
|
|
}
|
|
|
|
Option::ProgramName = po.program;
|
|
Option::PreprocessedPartitionConfig = po.ppp;
|
|
|
|
if (po.format.empty()) {
|
|
if (po.target == "php") {
|
|
po.format = "trimmed";
|
|
} else if (po.target == "run") {
|
|
po.format = "binary";
|
|
} else if (po.target == "hhbc") {
|
|
po.format = "binary";
|
|
}
|
|
}
|
|
|
|
if (!po.docjson.empty()) {
|
|
if (po.target != "run" &&
|
|
po.target != "hhbc" &&
|
|
po.target != "analyze") {
|
|
Logger::Error(
|
|
"Cannot generate doc JSON file unless target is "
|
|
"'hhbc', 'run', or 'analyze'");
|
|
} else {
|
|
Option::DocJson = po.docjson;
|
|
}
|
|
}
|
|
|
|
if (po.optimizeLevel == -1) {
|
|
po.optimizeLevel = 1;
|
|
}
|
|
|
|
// we always do pre/post opt no matter the opt level
|
|
Option::PreOptimization = true;
|
|
Option::PostOptimization = true;
|
|
if (po.optimizeLevel == 0) {
|
|
// --optimize-level=0 is equivalent to --opts=none
|
|
po.optimizations = "none";
|
|
Option::ParseTimeOpts = false;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
int process(const CompilerOptions &po) {
|
|
if (po.coredump) {
|
|
#if defined(__APPLE__) || defined(__FreeBSD__)
|
|
struct rlimit rl;
|
|
getrlimit(RLIMIT_CORE, &rl);
|
|
rl.rlim_cur = 80000000LL;
|
|
if (rl.rlim_max < rl.rlim_cur) {
|
|
rl.rlim_max = rl.rlim_cur;
|
|
}
|
|
setrlimit(RLIMIT_CORE, &rl);
|
|
#else
|
|
struct rlimit64 rl;
|
|
getrlimit64(RLIMIT_CORE, &rl);
|
|
rl.rlim_cur = 8000000000LL;
|
|
if (rl.rlim_max < rl.rlim_cur) {
|
|
rl.rlim_max = rl.rlim_cur;
|
|
}
|
|
setrlimit64(RLIMIT_CORE, &rl);
|
|
#endif
|
|
}
|
|
|
|
// lint doesn't need analysis
|
|
if (po.target == "lint") {
|
|
return lintTarget(po);
|
|
}
|
|
|
|
register_process_init();
|
|
init_thread_locals();
|
|
|
|
Timer timer(Timer::WallTime);
|
|
AnalysisResultPtr ar;
|
|
|
|
// prepare a package
|
|
Package package(po.inputDir.c_str());
|
|
ar = package.getAnalysisResult();
|
|
|
|
if (po.target == "hhbc" || po.target == "run") {
|
|
hhbcTargetInit(po, ar);
|
|
}
|
|
|
|
std::string errs;
|
|
if (!AliasManager::parseOptimizations(po.optimizations, errs)) {
|
|
Logger::Error("%s\n", errs.c_str());
|
|
return false;
|
|
}
|
|
|
|
// one time initialization
|
|
Type::InitTypeHintMap();
|
|
BuiltinSymbols::LoadSuperGlobals();
|
|
ClassInfo::Load();
|
|
|
|
bool isPickledPHP = (po.target == "php" && po.format == "pickled");
|
|
if (!isPickledPHP) {
|
|
if (!BuiltinSymbols::Load(ar,
|
|
po.target == "hhbc" && !Option::WholeProgram)) {
|
|
return false;
|
|
}
|
|
if (po.target == "hhbc" && !Option::WholeProgram) {
|
|
BuiltinSymbols::NoSuperGlobals = false;
|
|
} else {
|
|
ar->loadBuiltins();
|
|
}
|
|
hphp_process_init();
|
|
}
|
|
|
|
{
|
|
Timer timer(Timer::WallTime, "parsing inputs");
|
|
if (!po.inputs.empty() && isPickledPHP) {
|
|
for (unsigned int i = 0; i < po.inputs.size(); i++) {
|
|
package.addSourceFile(po.inputs[i].c_str());
|
|
}
|
|
} else {
|
|
ar->setPackage(&package);
|
|
ar->setParseOnDemand(po.parseOnDemand);
|
|
if (!po.parseOnDemand) {
|
|
ar->setParseOnDemandDirs(Option::ParseOnDemandDirs);
|
|
}
|
|
if (po.modules.empty() && po.fmodules.empty() &&
|
|
po.ffiles.empty() && po.inputs.empty() && po.inputList.empty()) {
|
|
package.addAllFiles(false);
|
|
} else {
|
|
for (unsigned int i = 0; i < po.modules.size(); i++) {
|
|
package.addDirectory(po.modules[i], false);
|
|
}
|
|
for (unsigned int i = 0; i < po.fmodules.size(); i++) {
|
|
package.addDirectory(po.fmodules[i], true);
|
|
}
|
|
for (unsigned int i = 0; i < po.ffiles.size(); i++) {
|
|
package.addSourceFile(po.ffiles[i].c_str());
|
|
}
|
|
for (unsigned int i = 0; i < po.cmodules.size(); i++) {
|
|
package.addStaticDirectory(po.cmodules[i].c_str());
|
|
}
|
|
for (unsigned int i = 0; i < po.cfiles.size(); i++) {
|
|
package.addStaticFile(po.cfiles[i].c_str());
|
|
}
|
|
for (unsigned int i = 0; i < po.inputs.size(); i++) {
|
|
package.addSourceFile(po.inputs[i].c_str());
|
|
}
|
|
if (!po.inputList.empty()) {
|
|
package.addInputList(po.inputList.c_str());
|
|
}
|
|
}
|
|
}
|
|
if (po.target != "filecache") {
|
|
if (!package.parse(!po.force)) {
|
|
return 1;
|
|
}
|
|
if (Option::WholeProgram || po.target == "analyze") {
|
|
ar->analyzeProgram();
|
|
}
|
|
}
|
|
}
|
|
|
|
// saving file cache
|
|
AsyncFileCacheSaver fileCacheThread(&package, po.filecache.c_str());
|
|
if (po.target != "analyze" && !po.filecache.empty()) {
|
|
fileCacheThread.start();
|
|
}
|
|
|
|
if (Option::DumpAst) {
|
|
ar->dump();
|
|
}
|
|
|
|
int ret = 0;
|
|
if (po.target == "analyze") {
|
|
ret = analyzeTarget(po, ar);
|
|
} else if (po.target == "php") {
|
|
ret = phpTarget(po, ar);
|
|
} else if (po.target == "hhbc") {
|
|
ret = hhbcTarget(po, ar, fileCacheThread);
|
|
} else if (po.target == "run") {
|
|
ret = runTargetCheck(po, ar, fileCacheThread);
|
|
} else if (po.target == "filecache") {
|
|
// do nothing
|
|
} else {
|
|
Logger::Error("Unknown target: %s", po.target.c_str());
|
|
return 1;
|
|
}
|
|
|
|
if (Option::DumpAst) {
|
|
ar->dump();
|
|
}
|
|
|
|
if (!Option::DocJson.empty()) {
|
|
Timer timer(Timer::WallTime, "Saving doc JSON file");
|
|
ar->docJson(Option::DocJson);
|
|
}
|
|
|
|
// saving stats
|
|
if (po.target == "analyze" || po.genStats || !po.dbStats.empty()) {
|
|
int seconds = timer.getMicroSeconds() / 1000000;
|
|
|
|
Logger::Info("saving code errors and stats...");
|
|
Timer timer(Timer::WallTime, "saving stats");
|
|
|
|
if (!po.dbStats.empty()) {
|
|
try {
|
|
ServerDataPtr server = ServerData::Create(po.dbStats);
|
|
int runId = package.saveStatsToDB(server, seconds, po.branch,
|
|
po.revision);
|
|
package.commitStats(server, runId);
|
|
} catch (DatabaseException e) {
|
|
Logger::Error("%s", e.what());
|
|
}
|
|
} else {
|
|
Compiler::SaveErrors(ar, (po.outputDir + "/CodeError.js").c_str());
|
|
package.saveStatsToFile((po.outputDir + "/Stats.js").c_str(), seconds);
|
|
}
|
|
} else if (Compiler::HasError()) {
|
|
Logger::Info("saving code errors...");
|
|
Compiler::SaveErrors(ar, (po.outputDir + "/CodeError.js").c_str());
|
|
}
|
|
|
|
if (!po.filecache.empty()) {
|
|
fileCacheThread.waitForEnd();
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
int lintTarget(const CompilerOptions &po) {
|
|
int ret = 0;
|
|
for (unsigned int i = 0; i < po.inputs.size(); i++) {
|
|
string filename = po.inputDir + "/" + po.inputs[i];
|
|
try {
|
|
Scanner scanner(filename.c_str(), Option::ScannerType);
|
|
Compiler::Parser parser(scanner, filename.c_str(),
|
|
AnalysisResultPtr(new AnalysisResult()));
|
|
if (!parser.parse()) {
|
|
Logger::Error("Unable to parse file %s: %s", filename.c_str(),
|
|
parser.getMessage().c_str());
|
|
ret = 1;
|
|
} else {
|
|
Logger::Info("%s parsed successfully...", filename.c_str());
|
|
}
|
|
} catch (FileOpenException &e) {
|
|
Logger::Error("%s", e.getMessage().c_str());
|
|
ret = 1;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
int analyzeTarget(const CompilerOptions &po, AnalysisResultPtr ar) {
|
|
int ret = 0;
|
|
|
|
if (!po.noTypeInference) {
|
|
Option::GenerateInferredTypes = true;
|
|
}
|
|
if (Option::PreOptimization) {
|
|
Timer timer(Timer::WallTime, "pre-optimizing");
|
|
ar->preOptimize();
|
|
}
|
|
|
|
if (!Option::AllVolatile) {
|
|
Timer timer(Timer::WallTime, "analyze includes");
|
|
ar->analyzeIncludes();
|
|
}
|
|
|
|
if (Option::GenerateInferredTypes) {
|
|
Timer timer(Timer::WallTime, "inferring types");
|
|
ar->inferTypes();
|
|
}
|
|
if (Option::PostOptimization) {
|
|
Timer timer(Timer::WallTime, "post-optimizing");
|
|
ar->postOptimize();
|
|
}
|
|
ar->analyzeProgramFinal();
|
|
|
|
return ret;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
int phpTarget(const CompilerOptions &po, AnalysisResultPtr ar) {
|
|
int ret = 0;
|
|
|
|
// format
|
|
int formatCount = 0;
|
|
if (po.format.find("pickled") != string::npos) {
|
|
Option::GeneratePickledPHP = true;
|
|
formatCount++;
|
|
}
|
|
if (po.format.find("inlined") != string::npos) {
|
|
Option::GenerateInlinedPHP = true;
|
|
formatCount++;
|
|
}
|
|
if (po.format.find("trimmed") != string::npos) {
|
|
Option::GenerateTrimmedPHP = true;
|
|
formatCount++;
|
|
}
|
|
if (po.format.find("typeinfo") != string::npos) {
|
|
Option::GenerateInferredTypes = true;
|
|
}
|
|
if (formatCount == 0) {
|
|
Logger::Error("Unknown format for PHP target: %s", po.format.c_str());
|
|
return 1;
|
|
}
|
|
|
|
// analyze
|
|
if (Option::GenerateInferredTypes || Option::ConvertSuperGlobals) {
|
|
Logger::Info("inferring types...");
|
|
ar->inferTypes();
|
|
}
|
|
|
|
// generate
|
|
ar->setOutputPath(po.outputDir);
|
|
if (Option::GeneratePickledPHP) {
|
|
Logger::Info("creating pickled PHP files...");
|
|
string outputDir = po.outputDir;
|
|
if (formatCount > 1) outputDir += "/pickled";
|
|
mkdir(outputDir.c_str(), 0777);
|
|
ar->outputAllPHP(CodeGenerator::PickledPHP);
|
|
}
|
|
if (Option::GenerateInlinedPHP) {
|
|
Logger::Info("creating inlined PHP files...");
|
|
string outputDir = po.outputDir;
|
|
if (formatCount > 1) outputDir += "/inlined";
|
|
mkdir(outputDir.c_str(), 0777);
|
|
if (!ar->outputAllPHP(CodeGenerator::InlinedPHP)) {
|
|
ret = -1;
|
|
}
|
|
}
|
|
if (Option::GenerateTrimmedPHP) {
|
|
Logger::Info("creating trimmed PHP files...");
|
|
string outputDir = po.outputDir;
|
|
if (formatCount > 1) outputDir += "/trimmed";
|
|
mkdir(outputDir.c_str(), 0777);
|
|
if (!ar->outputAllPHP(CodeGenerator::TrimmedPHP)) {
|
|
ret = -1;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
void hhbcTargetInit(const CompilerOptions &po, AnalysisResultPtr ar) {
|
|
if (po.syncDir.empty()) {
|
|
ar->setOutputPath(po.outputDir);
|
|
} else {
|
|
ar->setOutputPath(po.syncDir);
|
|
}
|
|
// Propagate relevant compiler-specific options to the runtime.
|
|
RuntimeOption::RepoCentralPath = ar->getOutputPath() + '/' + po.program;
|
|
if (po.format.find("exe") != string::npos) {
|
|
RuntimeOption::RepoCentralPath += ".hhbc";
|
|
}
|
|
RuntimeOption::RepoLocalMode = "--";
|
|
RuntimeOption::RepoDebugInfo = Option::RepoDebugInfo;
|
|
RuntimeOption::RepoJournal = "memory";
|
|
RuntimeOption::EnableHipHopSyntax = Option::EnableHipHopSyntax;
|
|
RuntimeOption::EvalJitEnableRenameFunction = Option::JitEnableRenameFunction;
|
|
}
|
|
|
|
int hhbcTarget(const CompilerOptions &po, AnalysisResultPtr ar,
|
|
AsyncFileCacheSaver &fcThread) {
|
|
int ret = 0;
|
|
int formatCount = 0;
|
|
const char *type = 0;
|
|
if (po.format.find("text") != string::npos) {
|
|
Option::GenerateTextHHBC = true;
|
|
type = "creating text HHBC files";
|
|
formatCount++;
|
|
}
|
|
if (po.format.find("binary") != string::npos) {
|
|
Option::GenerateBinaryHHBC = true;
|
|
type = "creating binary HHBC files";
|
|
formatCount++;
|
|
}
|
|
if (po.format.find("exe") != string::npos) {
|
|
Option::GenerateBinaryHHBC = true;
|
|
type = "creating binary HHBC files";
|
|
formatCount++;
|
|
}
|
|
|
|
if (formatCount == 0) {
|
|
Logger::Error("Unknown format for HHBC target: %s", po.format.c_str());
|
|
return 1;
|
|
}
|
|
|
|
/* without this, emitClass allows classes with interfaces to be
|
|
hoistable */
|
|
SystemLib::s_inited = true;
|
|
|
|
Option::AutoInline = -1;
|
|
|
|
if (po.optimizeLevel > 0) {
|
|
ret = analyzeTarget(po, ar);
|
|
}
|
|
|
|
Timer timer(Timer::WallTime, type);
|
|
Compiler::emitAllHHBC(ar);
|
|
|
|
if (!po.syncDir.empty()) {
|
|
if (!po.filecache.empty()) {
|
|
fcThread.waitForEnd();
|
|
}
|
|
Util::syncdir(po.outputDir, po.syncDir);
|
|
boost::filesystem::remove_all(po.syncDir);
|
|
}
|
|
|
|
if (!ret && po.format.find("exe") != string::npos) {
|
|
/*
|
|
* We need to create an executable with the repo
|
|
* embedded in it.
|
|
* Copy ourself, and embed the repo as a section
|
|
* named "repo".
|
|
*/
|
|
string exe = po.outputDir + '/' + po.program;
|
|
string repo = "repo=" + exe + ".hhbc";
|
|
char buf[PATH_MAX];
|
|
if (!realpath("/proc/self/exe", buf)) return -1;
|
|
|
|
const char *argv[] = { "objcopy", "--add-section", repo.c_str(),
|
|
buf, exe.c_str(), 0 };
|
|
string out;
|
|
ret = Process::Exec(argv[0], argv, nullptr, out, nullptr) ? 0 : 1;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
int runTargetCheck(const CompilerOptions &po, AnalysisResultPtr ar,
|
|
AsyncFileCacheSaver &fcThread) {
|
|
// generate code
|
|
if (hhbcTarget(po, ar, fcThread)) {
|
|
return 1;
|
|
}
|
|
|
|
// check error
|
|
if (Compiler::HasError() && !po.force) {
|
|
Compiler::DumpErrors(ar);
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int runTarget(const CompilerOptions &po) {
|
|
int ret = 0;
|
|
|
|
// If there are more than one input files, we need one extra arg to run.
|
|
// If it's missing, we will stop right here, with compiled code.
|
|
if ((po.inputs.size() != 1 && po.programArgs.empty()) ||
|
|
!po.inputList.empty()) {
|
|
return 0;
|
|
}
|
|
|
|
// run the executable
|
|
string cmd;
|
|
if (po.format.find("exe") == string::npos) {
|
|
char buf[PATH_MAX];
|
|
if (!realpath("/proc/self/exe", buf)) return -1;
|
|
|
|
cmd += buf;
|
|
cmd += " -vRepo.Authoritative=true";
|
|
cmd += " -vRepo.Local.Mode=r- -vRepo.Local.Path=";
|
|
}
|
|
cmd += po.outputDir + '/' + po.program;
|
|
cmd += string(" --file ") +
|
|
(po.inputs.size() == 1 ? po.inputs[0] : "") + po.programArgs;
|
|
Logger::Info("running executable %s...", cmd.c_str());
|
|
ret = Util::ssystem(cmd.c_str());
|
|
if (ret && ret != -1) ret = 1;
|
|
|
|
// delete the temporary directory if not needed
|
|
if (!po.keepTempDir) {
|
|
Logger::Info("deleting temporary directory %s...", po.outputDir.c_str());
|
|
boost::filesystem::remove_all(po.outputDir);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void createOutputDirectory(CompilerOptions &po) {
|
|
if (po.outputDir.empty()) {
|
|
const char *t = getenv("TEMP");
|
|
if (!t) {
|
|
t = "/tmp";
|
|
}
|
|
string temp = t;
|
|
temp += "/hphp_XXXXXX";
|
|
char path[PATH_MAX + 1];
|
|
strncpy(path, temp.c_str(), PATH_MAX);
|
|
path[PATH_MAX] = '\0';
|
|
po.outputDir = mkdtemp(path);
|
|
Logger::Info("creating temporary directory %s ...", po.outputDir.c_str());
|
|
}
|
|
mkdir(po.outputDir.c_str(), 0777);
|
|
|
|
if (!po.syncDir.empty()) {
|
|
Logger::Info("re-creating sync directory %s ...", po.syncDir.c_str());
|
|
boost::filesystem::remove_all(po.syncDir);
|
|
mkdir(po.syncDir.c_str(), 0777);
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
}
|