2 +----------------------------------------------------------------------+
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"
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"
62 using namespace extern_worker
;
63 namespace coro
= folly::coro
;
69 //////////////////////////////////////////////////////////////////////
71 std::string output_repo
;
72 std::string input_repo
;
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");
92 ("help", "display help message")
94 po::value(&output_repo
)->default_value("hhvm.hhbbc"),
95 "output hhbc repo path")
97 po::value(&input_repo
)->default_value("hhvm.hhbc"),
98 "input hhbc repo path")
100 po::value(&options
.stats_file
)->default_value(""),
103 po::bool_switch(&no_logging
),
106 po::bool_switch(&no_cores
),
107 "turn off core dumps (useful when running lots of tests in parallel)")
109 po::bool_switch(&options
.extendedStats
),
110 "Spend time to produce extra stats")
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(¶llel::num_threads
)->default_value(defaultThreadCount
),
116 "Number of threads to use for parallelism")
117 ("parallel-final-threads",
118 po::value(¶llel::final_threads
)->default_value(
119 defaultFinalThreadCount
),
120 "Number of threads to use for the final pass")
122 po::value(&trace_fns
)->composing(),
123 "Add a function to increase tracing level on (for debugging)")
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
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");
141 ("context-sensitive-interp", po::value(&options
.ContextSensitiveInterp
))
144 po::options_description
eflags("Extern-Worker Flags");
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
;
166 po::variables_map vm
;
168 po::command_line_parser(argc
, argv
)
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" };
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"
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";
212 //////////////////////////////////////////////////////////////////////
214 RepoGlobalData
get_global_data() {
215 auto const now
= std::chrono::high_resolution_clock::now();
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());
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.
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() {
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
,
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
); },
289 // Shard the emitters using consistent hashing of their path. Force
290 // all systemlib units into the first bucket (and nothing else in
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
);
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
) {
316 [] (const StringData
* a
, const StringData
* b
) {
317 return strcmp(a
->data(), b
->data()) < 0;
322 using WPI
= WholeProgramInput
;
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
332 std::vector
<UnitEmitterSerdeWrapper
> ues
;
333 ues
.reserve(units
.size());
334 for (auto const unit
: units
) {
335 assertx((bucketIdx
== 0) == FileUtil::isSystemName(unit
->slice()));
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
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
]));
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
378 std::move(configRef
),
382 // We need the keys locally, so load them. Values can stay as
384 auto keys
= co_await client
.load(std::move(keyRefs
));
386 auto const numKeys
= keys
.size();
388 for (auto& v
: valueRefs
) {
390 always_assert(keyIdx
< numKeys
);
391 inputs
.add(std::move(keys
[keyIdx
]), std::move(r
));
395 always_assert(keyIdx
== numKeys
);
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
;
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
}
444 void compile_repo() {
445 auto executor
= std::make_unique
<TicketExecutor
>(
448 parallel::num_threads
,
451 hphp_session_init(Treadmill::SessionKind::HHBBC
);
458 std::chrono::minutes
{15}
460 auto client
= std::make_unique
<Client
>(
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
};
496 std::atomic
<size_t> numUnits
{0};
498 auto const emit
= [&] (std::unique_ptr
<UnitEmitter
> ue
) {
500 autoload
.addUnit(*ue
);
501 RepoFileBuilder::EncodedUE encoded
{*ue
};
502 std::scoped_lock
<std::mutex
> _
{repoLock
};
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.
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 {
525 HphpSession session
{Treadmill::SessionKind::HHBBC
};
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
,
557 setrlimit(RLIMIT_CORE
, &rl
);
561 SCOPE_FAIL
{ rds::local::fini(); };
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
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
594 register_process_init();
595 hphp_process_init(!fullInit
);
596 SCOPE_FAIL
{ hphp_process_exit(); };
599 // Now that process state is set up, we can safely do a full load of
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() {
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';
629 if (!std::filesystem::exists(input_repo
)) {
630 std::cerr
<< "input repo `" << input_repo
<< "' not found\n";
634 RepoFile::init(input_repo
);
636 auto const& gd
= RepoFile::globalData();
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
);
651 catch (std::exception
& e
) {
652 Logger::Error("std::exception: %s", e
.what());
656 //////////////////////////////////////////////////////////////////////