Deshim VirtualExecutor in folly
[hiphop-php.git] / hphp / hhbbc / main.cpp
blob65ec058e938f2b077c0030b7dbbee2297c591365
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
16 #include "hphp/hhbbc/hhbbc.h"
18 #include <stdexcept>
19 #include <iostream>
20 #include <cstdlib>
21 #include <string>
22 #include <memory>
23 #include <cstdint>
24 #include <algorithm>
25 #include <exception>
26 #include <utility>
27 #include <vector>
28 #include <filesystem>
30 #include <boost/program_options.hpp>
32 #include <folly/ScopeGuard.h>
33 #include <folly/String.h>
34 #include <folly/portability/Unistd.h>
36 #include "hphp/runtime/base/configs/configs.h"
37 #include "hphp/runtime/base/ini-setting.h"
38 #include "hphp/runtime/base/runtime-option.h"
39 #include "hphp/runtime/base/variable-serializer.h"
40 #include "hphp/runtime/base/vm-worker.h"
41 #include "hphp/hhvm/process-init.h"
42 #include "hphp/runtime/vm/native.h"
43 #include "hphp/runtime/vm/preclass-emitter.h"
44 #include "hphp/runtime/vm/repo-autoload-map-builder.h"
45 #include "hphp/runtime/vm/repo-file.h"
46 #include "hphp/runtime/vm/repo-global-data.h"
47 #include "hphp/runtime/vm/treadmill.h"
49 #include "hphp/hhbbc/misc.h"
50 #include "hphp/hhbbc/options.h"
51 #include "hphp/hhbbc/stats.h"
52 #include "hphp/hhbbc/parallel.h"
53 #include "hphp/hhbbc/representation.h"
55 #include "hphp/util/configs/jit.h"
56 #include "hphp/util/configs/php7.h"
57 #include "hphp/util/logger.h"
58 #include "hphp/util/rds-local.h"
60 namespace HPHP {
62 using namespace extern_worker;
63 namespace coro = folly::coro;
65 namespace HHBBC {
67 namespace {
69 //////////////////////////////////////////////////////////////////////
71 std::string output_repo;
72 std::string input_repo;
73 bool logging = true;
75 //////////////////////////////////////////////////////////////////////
77 void parse_options(int argc, char** argv) {
78 namespace po = boost::program_options;
80 auto const defaultThreadCount =
81 std::max<long>(sysconf(_SC_NPROCESSORS_ONLN) - 1, 1);
82 auto const defaultFinalThreadCount =
83 std::max<long>(defaultThreadCount - 1, 1);
85 std::vector<std::string> trace_fns;
86 std::vector<std::string> trace_bcs;
87 bool no_logging = false;
88 bool no_cores = false;
90 po::options_description basic("Options");
91 basic.add_options()
92 ("help", "display help message")
93 ("output,o",
94 po::value(&output_repo)->default_value("hhvm.hhbbc"),
95 "output hhbc repo path")
96 ("input",
97 po::value(&input_repo)->default_value("hhvm.hhbc"),
98 "input hhbc repo path")
99 ("stats-file",
100 po::value(&options.stats_file)->default_value(""),
101 "stats file path")
102 ("no-logging",
103 po::bool_switch(&no_logging),
104 "turn off logging")
105 ("no-cores",
106 po::bool_switch(&no_cores),
107 "turn off core dumps (useful when running lots of tests in parallel)")
108 ("extended-stats",
109 po::bool_switch(&options.extendedStats),
110 "Spend time to produce extra stats")
111 ("profile-memory",
112 po::value(&options.profileMemory)->default_value(""),
113 "If non-empty, dump jemalloc memory profiles at key points")
114 ("parallel-num-threads",
115 po::value(&parallel::num_threads)->default_value(defaultThreadCount),
116 "Number of threads to use for parallelism")
117 ("parallel-final-threads",
118 po::value(&parallel::final_threads)->default_value(
119 defaultFinalThreadCount),
120 "Number of threads to use for the final pass")
121 ("trace",
122 po::value(&trace_fns)->composing(),
123 "Add a function to increase tracing level on (for debugging)")
124 ("trace-bytecode",
125 po::value(&trace_bcs)->composing(),
126 "Add a bytecode to trace (for debugging)")
129 // Some extra esoteric options that aren't exposed in --help for
130 // now.
131 po::options_description extended("Extended Options");
132 extended.add_options()
133 ("analyze-func-wlimit", po::value(&options.analyzeFuncWideningLimit))
134 ("analyze-class-wlimit", po::value(&options.analyzeClassWideningLimit))
135 ("return-refine-limit", po::value(&options.returnTypeRefineLimit))
136 ("public-sprop-refine-limit", po::value(&options.publicSPropRefineLimit))
139 po::options_description oflags("Optimization Flags");
140 oflags.add_options()
141 ("context-sensitive-interp", po::value(&options.ContextSensitiveInterp))
144 po::options_description eflags("Extern-Worker Flags");
145 eflags.add_options()
146 ("extern-worker-use-case", po::value(&options.ExternWorkerUseCase))
147 ("extern-worker-working-dir", po::value(&options.ExternWorkerWorkingDir))
148 ("extern-worker-timeout-secs", po::value(&options.ExternWorkerTimeoutSecs))
149 ("extern-worker-throttle-retries", po::value(&options.ExternWorkerThrottleRetries))
150 ("extern-worker-throttle-base-wait-msecs", po::value(&options.ExternWorkerThrottleBaseWaitMSecs))
151 ("extern-worker-use-exec-cache", po::value(&options.ExternWorkerUseExecCache))
152 ("extern-worker-cleanup", po::value(&options.ExternWorkerCleanup))
153 ("extern-worker-use-rich-client", po::value(&options.ExternWorkerUseRichClient))
154 ("extern-worker-use-zippy-rich-client", po::value(&options.ExternWorkerUseZippyRichClient))
155 ("extern-worker-use-p2p", po::value(&options.ExternWorkerUseP2P))
156 ("extern-worker-verbose-logging", po::value(&options.ExternWorkerVerboseLogging))
157 ("extern-worker-async-cleanup", po::value(&options.ExternWorkerAsyncCleanup))
160 po::options_description all;
161 all.add(basic).add(extended).add(oflags).add(eflags);
163 po::positional_options_description pd;
164 pd.add("input", 1);
166 po::variables_map vm;
167 po::store(
168 po::command_line_parser(argc, argv)
169 .options(all)
170 .positional(pd)
171 .extra_parser(
172 [&] (const std::string& s) -> std::pair<std::string,std::string> {
173 if (s.find("-f") == 0) {
174 if (s.substr(2, 3) == "no-") {
175 return { s.substr(5), "false" };
177 return { s.substr(2), "true" };
179 return {};
181 .run(),
184 po::notify(vm);
186 if (vm.count("help")) {
187 std::cout << basic << "\n"
188 "Individual optimizations may be turned on and off using gcc-style -fflag\n"
189 "and -fno-flag arguments. The various optimization flags are documented\n"
190 "in the code.\n";
191 std::exit(0);
194 options.CoreDump = !no_cores;
195 add_to_method_map(options.TraceFunctions, trace_fns);
197 if (!options.profileMemory.empty()) {
198 mallctlWrite("prof.active", true);
199 mallctlWrite("prof.thread_active_init", true);
202 logging = !no_logging;
205 UNUSED void validate_options() {
206 if (parallel::num_threads < 1) {
207 std::cerr << "Invalid parallelism configuration.\n";
208 std::exit(1);
212 //////////////////////////////////////////////////////////////////////
214 RepoGlobalData get_global_data() {
215 auto const now = std::chrono::high_resolution_clock::now();
216 auto const nanos =
217 std::chrono::duration_cast<std::chrono::nanoseconds>(
218 now.time_since_epoch()
221 auto gd = RepoGlobalData{};
222 gd.Signature = nanos.count();
224 Cfg::StoreToGlobalData(gd);
226 gd.AbortBuildOnVerifyError = RuntimeOption::EvalAbortBuildOnVerifyError;
227 gd.EnableArgsInBacktraces = RuntimeOption::EnableArgsInBacktraces;
228 gd.EvalCoeffectEnforcementLevels = RO::EvalCoeffectEnforcementLevels;
229 gd.SourceRootForFileBC = options.SourceRootForFileBC;
231 for (auto const& elm : RuntimeOption::ConstantFunctions) {
232 auto const s = internal_serialize(tvAsCVarRef(elm.second));
233 gd.ConstantFunctions.emplace_back(elm.first, s.toCppString());
235 std::sort(gd.ConstantFunctions.begin(), gd.ConstantFunctions.end());
237 return gd;
240 //////////////////////////////////////////////////////////////////////
242 // Receives UnitEmitters and turns them into WholeProgramInput Key and
243 // Values. This "loads" them locally and uploads them into an
244 // extern_worker::Client in the way that whole_program expects.
245 struct LoadRepoJob {
246 using W = WholeProgramInput;
248 static std::string name() { return "hhbbc-load-repo"; }
249 static void init(const Config& config) {
250 process_init(config.o, config.gd, false);
252 static std::vector<W::Key> fini() {
253 process_exit();
254 return std::move(s_keys);
256 static Variadic<W::Value> run(UnitEmitterSerdeWrapper wrapper) {
257 std::vector<W::Value> values;
258 for (auto& [key, value] : W::make(std::move(wrapper.m_ue))) {
259 s_keys.emplace_back(std::move(key));
260 values.emplace_back(std::move(value));
262 return Variadic<W::Value>{std::move(values)};
265 static std::vector<W::Key> s_keys;
267 std::vector<WholeProgramInput::Key> LoadRepoJob::s_keys;
268 Job<LoadRepoJob> s_loadRepoJob;
270 // Load all UnitEmitters from the RepoFile and convert them into what
271 // whole_program expects as input.
272 std::pair<WholeProgramInput, Config> load_repo(TicketExecutor& executor,
273 Client& client,
274 StructuredLogEntry& sample) {
275 trace_time timer("load repo", &sample);
277 SCOPE_EXIT { RepoFile::destroy(); };
278 RepoFile::loadGlobalTables(false);
279 auto const units = RepoFile::enumerateUnits();
280 if (logging) std::cout << folly::format("{} units\n", units.size());
282 // Start this as early as possible
283 auto config = Config::get(RepoFile::globalData());
284 CoroAsyncValue<Ref<Config>> storedConfig{
285 [&client, config] () { return client.store(config); },
286 executor.sticky()
289 // Shard the emitters using consistent hashing of their path. Force
290 // all systemlib units into the first bucket (and nothing else in
291 // there).
292 constexpr size_t kLoadGroupSize = 500;
293 auto const numBuckets = std::max<size_t>(
294 (units.size() + kLoadGroupSize - 1) / kLoadGroupSize,
298 std::vector<std::vector<const StringData*>> groups;
299 groups.resize(numBuckets);
301 for (auto const unit : units) {
302 if (FileUtil::isSystemName(unit->slice())) {
303 groups[0].emplace_back(unit);
304 continue;
306 auto const idx = consistent_hash(unit->hash(), numBuckets - 1) + 1;
307 assertx(idx < numBuckets);
308 groups[idx].emplace_back(unit);
311 // Maintain deterministic ordering within each group
312 for (auto& group : groups) {
313 std::sort(
314 group.begin(),
315 group.end(),
316 [] (const StringData* a, const StringData* b) {
317 return strcmp(a->data(), b->data()) < 0;
322 using WPI = WholeProgramInput;
323 WPI inputs;
325 auto const load = [&] (size_t bucketIdx,
326 std::vector<const StringData*> units)
327 -> coro::Task<void> {
329 // Load the UnitEmitters from disk (deferring this until here cuts
330 // down on the number of UnitEmitters we have to keep in memory at
331 // once).
332 std::vector<UnitEmitterSerdeWrapper> ues;
333 ues.reserve(units.size());
334 for (auto const unit : units) {
335 assertx((bucketIdx == 0) == FileUtil::isSystemName(unit->slice()));
336 ues.emplace_back(
337 RepoFile::loadUnitEmitter(unit, nullptr, false)
341 // Process the first bucket locally. Systemlib units need full
342 // process init and we don't currently do that in extern-worker
343 // jobs.
344 if (bucketIdx == 0) {
345 std::vector<WPI::Key> keys;
346 std::vector<WPI::Value> values;
347 for (auto& ue : ues) {
348 assertx(FileUtil::isSystemName(ue.m_ue->m_filepath->slice()));
349 for (auto& [key, value] : WPI::make(std::move(ue.m_ue))) {
350 keys.emplace_back(std::move(key));
351 values.emplace_back(std::move(value));
354 if (keys.empty()) co_return;
355 auto valueRefs = co_await client.storeMulti(std::move(values));
356 auto const numKeys = keys.size();
357 assertx(valueRefs.size() == numKeys);
358 for (size_t i = 0; i < numKeys; ++i) {
359 inputs.add(std::move(keys[i]), std::move(valueRefs[i]));
361 co_return;
364 // Store them (and the config we'll use).
365 auto [refs, configRef] = co_await coro::collectAll(
366 client.storeMulti(std::move(ues)),
367 storedConfig.getCopy()
370 std::vector<std::tuple<Ref<UnitEmitterSerdeWrapper>>> tuplized;
371 tuplized.reserve(refs.size());
372 for (auto& r : refs) tuplized.emplace_back(std::move(r));
374 // Run the job and get refs to the keys and values.
375 auto [valueRefs, keyRefs] = co_await
376 client.exec(
377 s_loadRepoJob,
378 std::move(configRef),
379 std::move(tuplized)
382 // We need the keys locally, so load them. Values can stay as
383 // Refs.
384 auto keys = co_await client.load(std::move(keyRefs));
386 auto const numKeys = keys.size();
387 size_t keyIdx = 0;
388 for (auto& v : valueRefs) {
389 for (auto& r : v) {
390 always_assert(keyIdx < numKeys);
391 inputs.add(std::move(keys[keyIdx]), std::move(r));
392 ++keyIdx;
395 always_assert(keyIdx == numKeys);
397 co_return;
400 std::vector<coro::TaskWithExecutor<void>> tasks;
401 for (size_t i = 0; i < groups.size(); ++i) {
402 auto& group = groups[i];
403 if (group.empty()) continue;
404 tasks.emplace_back(load(i, std::move(group)).scheduleOn(executor.sticky()));
406 coro::blockingWait(coro::collectAllRange(std::move(tasks)));
407 return std::make_pair(std::move(inputs), std::move(config));
410 extern_worker::Options make_extern_worker_options() {
411 extern_worker::Options opts;
412 opts
413 .setUseCase(options.ExternWorkerUseCase)
414 .setUseSubprocess(options.ExternWorkerUseCase.empty()
415 ? extern_worker::Options::UseSubprocess::Always
416 : extern_worker::Options::UseSubprocess::Never)
417 .setCacheExecs(options.ExternWorkerUseExecCache)
418 .setCleanup(options.ExternWorkerCleanup)
419 .setUseRichClient(options.ExternWorkerUseRichClient)
420 .setUseZippyRichClient(options.ExternWorkerUseZippyRichClient)
421 .setUseP2P(options.ExternWorkerUseP2P)
422 .setVerboseLogging(options.ExternWorkerVerboseLogging)
423 .setCasConnectionCount(options.ExternWorkerCASConnectionCount)
424 .setEngineConnectionCount(options.ExternWorkerEngineConnectionCount)
425 .setAcConnectionCount(options.ExternWorkerActionCacheConnectionCount)
426 .setFeaturesFile(options.ExternWorkerFeaturesFile);
427 if (options.ExternWorkerTimeoutSecs > 0) {
428 opts.setTimeout(std::chrono::seconds{options.ExternWorkerTimeoutSecs});
430 if (!options.ExternWorkerWorkingDir.empty()) {
431 opts.setWorkingDir(options.ExternWorkerWorkingDir);
433 if (options.ExternWorkerThrottleRetries >= 0) {
434 opts.setThrottleRetries(options.ExternWorkerThrottleRetries);
436 if (options.ExternWorkerThrottleBaseWaitMSecs >= 0) {
437 opts.setThrottleBaseWait(
438 std::chrono::milliseconds{options.ExternWorkerThrottleBaseWaitMSecs}
441 return opts;
444 void compile_repo() {
445 auto executor = std::make_unique<TicketExecutor>(
446 "HHBBCWorker",
448 parallel::num_threads,
449 [] {
450 hphp_thread_init();
451 hphp_session_init(Treadmill::SessionKind::HHBBC);
453 [] {
454 hphp_context_exit();
455 hphp_session_exit();
456 hphp_thread_exit();
458 std::chrono::minutes{15}
460 auto client = std::make_unique<Client>(
461 executor->sticky(),
462 make_extern_worker_options()
464 trace_time::register_client_stats(client->getStatsPtr());
466 StructuredLogEntry sample;
467 sample.setStr("debug", debug ? "true" : "false");
468 sample.setStr("use_case", options.ExternWorkerUseCase);
469 sample.setInt("use_rich_client", options.ExternWorkerUseRichClient);
470 sample.setInt("use_zippy_rich_client", options.ExternWorkerUseZippyRichClient);
471 sample.setInt("use_p2p", options.ExternWorkerUseP2P);
472 sample.setInt("force_subprocess", options.ExternWorkerForceSubprocess);
473 sample.setInt("use_exec_cache", options.ExternWorkerUseExecCache);
474 sample.setInt("timeout_secs", options.ExternWorkerTimeoutSecs);
475 sample.setInt("cleanup", options.ExternWorkerCleanup);
476 sample.setInt("throttle_retries", options.ExternWorkerThrottleRetries);
477 sample.setInt("throttle_base_wait_ms",
478 options.ExternWorkerThrottleBaseWaitMSecs);
479 sample.setStr("working_dir", options.ExternWorkerWorkingDir);
480 sample.setStr("use_hphpc", "false");
481 sample.setStr("use_hhbbc", "true");
482 sample.setInt("hhbbc_thread_count", executor->numThreads());
483 sample.setInt("hhbbc_async_cleanup", options.ExternWorkerAsyncCleanup);
484 sample.setStr("extern_worker_impl", client->implName());
485 sample.setStr("extern_worker_session", client->session());
487 // Package Info must be read prior to load_repo as loading the repo
488 // destroys the repo file
489 auto const packageInfo = RepoFile::packageInfo();
491 auto [inputs, config] = load_repo(*executor, *client, sample);
493 RepoAutoloadMapBuilder autoload;
494 RepoFileBuilder repo{output_repo};
495 std::mutex repoLock;
496 std::atomic<size_t> numUnits{0};
498 auto const emit = [&] (std::unique_ptr<UnitEmitter> ue) {
499 ++numUnits;
500 autoload.addUnit(*ue);
501 RepoFileBuilder::EncodedUE encoded{*ue};
502 std::scoped_lock<std::mutex> _{repoLock};
503 repo.add(encoded);
506 std::thread asyncDispose;
507 SCOPE_EXIT { if (asyncDispose.joinable()) asyncDispose.join(); };
508 auto const dispose = [&] (std::unique_ptr<TicketExecutor> e,
509 std::unique_ptr<Client> c) {
510 if (!options.ExternWorkerAsyncCleanup) {
511 // If we don't want to cleanup asynchronously, do so now.
512 c.reset();
513 e.reset();
514 return;
516 // All the thread does is reset the unique_ptr to run the dtor.
517 asyncDispose = std::thread{
518 [e = std::move(e), c = std::move(c)] () mutable {
519 c.reset();
520 e.reset();
525 HphpSession session{Treadmill::SessionKind::HHBBC};
526 whole_program(
527 std::move(inputs),
528 std::move(config),
529 std::move(executor),
530 std::move(client),
531 emit,
532 dispose,
533 &sample
536 trace_time timer{"finalizing repo", &sample};
537 repo.finish(get_global_data(), autoload, packageInfo);
539 // Only log big builds.
540 if (numUnits >= RO::EvalHHBBCMinUnitsToLog) {
541 sample.force_init = true;
542 StructuredLog::log("hhvm_whole_program", sample);
546 //////////////////////////////////////////////////////////////////////
550 void process_init(const Options& o,
551 const RepoGlobalData& gd,
552 bool fullInit) {
553 if (!o.CoreDump) {
554 struct rlimit rl{};
555 rl.rlim_cur = 0;
556 rl.rlim_max = 0;
557 setrlimit(RLIMIT_CORE, &rl);
560 rds::local::init();
561 SCOPE_FAIL { rds::local::fini(); };
563 Hdf config;
564 IniSetting::Map ini = IniSetting::Map::object;
566 // We need to write correct coeffects before we load
567 for (auto const& [name, value] : gd.EvalCoeffectEnforcementLevels) {
568 config["Eval"]["CoeffectEnforcementLevels"][name] = value;
571 // These need to be set before RO::Load, because it triggers the
572 // table resizing.
573 if (gd.Eval_InitialTypeTableSize) {
574 Cfg::Eval::InitialTypeTableSize = gd.Eval_InitialTypeTableSize;
576 if (gd.Eval_InitialFuncTableSize) {
577 Cfg::Eval::InitialFuncTableSize = gd.Eval_InitialFuncTableSize;
579 if (gd.Eval_InitialStaticStringTableSize) {
580 Cfg::Eval::InitialStaticStringTableSize = gd.Eval_InitialStaticStringTableSize;
583 RO::Load(ini, config);
584 RO::RepoAuthoritative = false;
585 Cfg::Jit::Enabled = false;
586 RO::EvalLowStaticArrays = false;
587 RO::RepoDebugInfo = false;
588 Logger::LogLevel = Logger::LogError;
590 // Load RepoGlobalData first because hphp_process_init can read
591 // RuntimeOptions.
592 gd.load(false);
594 register_process_init();
595 hphp_process_init(!fullInit);
596 SCOPE_FAIL { hphp_process_exit(); };
598 options = o;
599 // Now that process state is set up, we can safely do a full load of
600 // RepoGlobalData.
601 gd.load();
603 // When running hhbbc, these options are loaded from GD, and will
604 // override CLI. When running hhvm, these options are not loaded
605 // from GD, but read from CLI. NB: These are only needed if
606 // RepoGlobalData::load() does not currently write them which is
607 // called in hphp_process_init().
608 Cfg::LoadFromGlobalDataOnlyHHBBC(gd);
610 options.SourceRootForFileBC = gd.SourceRootForFileBC;
613 void process_exit() {
614 hphp_process_exit();
615 rds::local::fini();
618 int main(int argc, char** argv) try {
619 parse_options(argc, argv);
621 if (std::filesystem::exists(output_repo)) {
622 std::cout << "output repo already exists; removing it\n";
623 if (unlink(output_repo.c_str())) {
624 std::cerr << "failed to unlink output repo: "
625 << strerror(errno) << '\n';
626 return 1;
629 if (!std::filesystem::exists(input_repo)) {
630 std::cerr << "input repo `" << input_repo << "' not found\n";
631 return 1;
634 RepoFile::init(input_repo);
636 auto const& gd = RepoFile::globalData();
637 gd.load(false);
639 process_init(options, gd, true);
640 SCOPE_EXIT { process_exit(); };
642 Logger::LogLevel = logging ? Logger::LogInfo : Logger::LogError;
643 Logger::Escape = false;
644 Logger::AlwaysEscapeLog = false;
646 Trace::BumpRelease bumper(Trace::hhbbc_time, -1, logging);
647 compile_repo();
648 return 0;
651 catch (std::exception& e) {
652 Logger::Error("std::exception: %s", e.what());
653 return 1;
656 //////////////////////////////////////////////////////////////////////