tools/synth/synth.cpp (285 lines of code) (raw):
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include <hermes/ConsoleHost/RuntimeFlags.h>
#include <hermes/Support/Algorithms.h>
#include <hermes/Support/MemoryBuffer.h>
#include <hermes/TraceInterpreter.h>
#include <hermes/hermes.h>
#include "llvh/ADT/Statistic.h"
#include "llvh/Support/CommandLine.h"
#include "llvh/Support/PrettyStackTrace.h"
#include "llvh/Support/Signals.h"
#include <iostream>
#include <tuple>
using MarkerAction =
facebook::hermes::tracing::TraceInterpreter::ExecuteOptions::MarkerAction;
namespace cl {
using llvh::cl::desc;
using llvh::cl::init;
using llvh::cl::list;
using llvh::cl::OneOrMore;
using llvh::cl::opt;
using llvh::cl::Positional;
using llvh::cl::Required;
/// @name Synth benchmark specific flags
/// @{
static opt<std::string>
TraceFile(desc("input trace file"), Positional, Required);
static list<std::string>
BytecodeFiles(desc("input bytecode files"), Positional, OneOrMore);
static opt<std::string> Marker(
"marker",
desc("marker to stop at, \"end\" means end of trace"),
init("end"));
static llvh::cl::alias
MarkerA("m", desc("alias for -marker"), llvh::cl::aliasopt(Marker));
static opt<MarkerAction> Action(
"action-at-marker",
desc("Take a snapshot at the given marker"),
init(MarkerAction::NONE),
llvh::cl::values(
clEnumValN(MarkerAction::NONE, "stop", "Stop the trace and get stats"),
clEnumValN(
MarkerAction::SNAPSHOT,
"snapshot",
"Take a heap snapshot at the marker to stop at"),
clEnumValN(
MarkerAction::TIMELINE,
"timeline",
"Take a heap timeline from the beginning of execution until the "
"marker to stop at"),
clEnumValN(
MarkerAction::SAMPLE_MEMORY,
"sample-memory",
"Take a heap sampling profile at the marker to stop at"),
clEnumValN(
MarkerAction::SAMPLE_TIME,
"sample-time",
"Take a CPU sampling profile at the marker to stop at")));
static opt<bool> UseTraceConfig(
"use-trace-config",
desc("Controls what RuntimeConfig as the default that the various config "
"modify. True says to use the recorded config of the trace, false "
"means start from the default config."),
init(true));
static opt<std::string> Trace(
"trace",
desc(
"Take a trace of the synthetic benchmark running. Can be used to verify that the replay made the same trace again. Outputs to the file given"),
init(""));
#if !defined(NDEBUG) || defined(LLVM_ENABLE_STATS)
static opt<bool>
PrintStats("print-stats", desc("Print statistics"), init(false));
#endif
static opt<unsigned> BytecodeWarmupPercent(
"bytecode-warmup-percent",
desc("Eagerly read some bytecode into page cache. May yield faster startup "
"for large bytecode files."),
init(0));
static opt<int> Reps(
"reps",
desc(
"Number of repetitions of execution. Any GC stats printed are those for the "
"rep with the median \"totalTime\"."),
init(1));
static opt<bool> DisableSourceHashCheck(
"disable-source-hash-check",
desc("Remove the requirement that the input bytecode was compiled from the "
"same source used to record the trace. There must only be one input "
"bytecode file in this case. If its observable behavior deviates "
"from the trace, the results are undefined."),
init(false));
/// @}
/// @name Common flags from Hermes VM
/// @{
static opt<bool> GCAllocYoung(
"gc-alloc-young",
desc("Determines whether to (initially) allocate in the young generation"),
cat(GCCategory),
init(false));
static opt<bool> GCRevertToYGAtTTI(
"gc-revert-to-yg-at-tti",
desc("Determines whether to revert to young generation, if necessary, at "
"TTI notification"),
cat(GCCategory),
init(true));
static opt<bool> GCBeforeStats(
"gc-before-stats",
desc("Perform a full GC just before printing statistics at exit"),
cat(GCCategory),
init(false));
static opt<bool> GCPrintStats(
"gc-print-stats",
desc("Output summary garbage collection statistics at exit"),
cat(GCCategory),
init(true));
static opt<::hermes::vm::ReleaseUnused> ShouldReleaseUnused(
"release-unused",
desc("How aggressively to return unused memory to the OS."),
init(GCConfig::getDefaultShouldReleaseUnused()),
llvh::cl::values(
clEnumValN(
::hermes::vm::kReleaseUnusedNone,
"none",
"Don't try to release unused memory."),
clEnumValN(
::hermes::vm::kReleaseUnusedOld,
"old",
"Only old gen, on full collections."),
clEnumValN(
::hermes::vm::kReleaseUnusedYoungOnFull,
"young-on-full",
"Also young gen, but only on full collections."),
clEnumValN(
::hermes::vm::kReleaseUnusedYoungAlways,
"young-always",
"Also young gen, also on young gen collections")));
/// @}
} // namespace cl
// Helper functions.
template <typename T>
static llvh::Optional<T> execOption(const cl::opt<T> &clOpt) {
if (clOpt.getNumOccurrences() > 0) {
return static_cast<T>(clOpt);
} else {
return llvh::None;
}
}
// Must do this special case explicitly, because of the MemorySizeParser.
static llvh::Optional<::hermes::vm::gcheapsize_t> execOption(
const cl::opt<cl::MemorySize, false, cl::MemorySizeParser> &clOpt) {
if (clOpt.getNumOccurrences() > 0) {
return clOpt.bytes;
} else {
return llvh::None;
}
}
static const char *fileExtensionForAction(MarkerAction action) {
switch (action) {
case MarkerAction::SNAPSHOT:
return "heapsnapshot";
case MarkerAction::TIMELINE:
return "heaptimeline";
case MarkerAction::SAMPLE_MEMORY:
return "heapprofile";
case MarkerAction::SAMPLE_TIME:
return "cpuprofile";
default:
llvm_unreachable(
"Should never call fileExtensionForAction with a none action");
}
}
int main(int argc, char **argv) {
// Print a stack trace if we signal out.
llvh::sys::PrintStackTraceOnErrorSignal("Hermes synth");
llvh::PrettyStackTraceProgram X(argc, argv);
// Call llvm_shutdown() on exit to print stats and free memory.
llvh::llvm_shutdown_obj Y;
llvh::cl::ParseCommandLineOptions(argc, argv, "Hermes synth trace driver\n");
using namespace facebook::hermes::tracing;
try {
TraceInterpreter::ExecuteOptions options;
// These are not config parameters: just set them according to the
// runtime flag.
options.useTraceConfig = cl::UseTraceConfig;
options.reps = cl::Reps;
options.marker = cl::Marker;
options.action = cl::Action;
if (options.action != MarkerAction::NONE) {
llvh::SmallVector<char, 16> tmpfile;
llvh::sys::fs::createTemporaryFile(
options.marker, fileExtensionForAction(options.action), tmpfile);
options.profileFileName = std::string{tmpfile.begin(), tmpfile.end()};
}
options.forceGCBeforeStats = cl::GCBeforeStats;
options.stabilizeInstructionCount = cl::StableInstructionCount;
options.disableSourceHashCheck = cl::DisableSourceHashCheck;
// These are the config parameters.
// We want to print the GC stats by default. We won't print them
// if -gc-print-stats is specified false explicitly, and
// -gc-before-stats is also false, or if we're trying to get
// a stable instruction count.
bool shouldPrintGCStats = true;
if (cl::GCPrintStats.getNumOccurrences() > 0) {
shouldPrintGCStats = (cl::GCPrintStats || cl::GCBeforeStats) &&
!cl::StableInstructionCount;
}
shouldPrintGCStats = shouldPrintGCStats && !cl::StableInstructionCount;
llvh::Optional<::hermes::vm::gcheapsize_t> minHeapSize =
execOption(cl::MinHeapSize);
llvh::Optional<::hermes::vm::gcheapsize_t> initHeapSize =
execOption(cl::InitHeapSize);
llvh::Optional<::hermes::vm::gcheapsize_t> maxHeapSize =
execOption(cl::MaxHeapSize);
llvh::Optional<double> occupancyTarget = execOption(cl::OccupancyTarget);
llvh::Optional<::hermes::vm::ReleaseUnused> shouldReleaseUnused =
execOption(cl::ShouldReleaseUnused);
llvh::Optional<bool> allocInYoung = execOption(cl::GCAllocYoung);
llvh::Optional<bool> revertToYGAtTTI = execOption(cl::GCRevertToYGAtTTI);
options.shouldTrackIO = execOption(cl::TrackBytecodeIO);
options.bytecodeWarmupPercent = execOption(cl::BytecodeWarmupPercent);
llvh::Optional<double> sanitizeRate = execOption(cl::GCSanitizeRate);
// The type of this case is complicated, so just do it explicitly.
llvh::Optional<int64_t> sanitizeRandomSeed;
if (cl::GCSanitizeRandomSeed) {
sanitizeRandomSeed = cl::GCSanitizeRandomSeed;
}
#if !defined(NDEBUG) || defined(LLVM_ENABLE_STATS)
if (cl::PrintStats)
llvh::EnableStatistics();
#endif
options.gcConfigBuilder.withShouldRecordStats(shouldPrintGCStats);
if (minHeapSize) {
options.gcConfigBuilder.withMinHeapSize(*minHeapSize);
}
if (initHeapSize) {
options.gcConfigBuilder.withMinHeapSize(*initHeapSize);
}
if (maxHeapSize) {
options.gcConfigBuilder.withMaxHeapSize(*maxHeapSize);
}
if (occupancyTarget) {
options.gcConfigBuilder.withOccupancyTarget(*occupancyTarget);
}
if (shouldReleaseUnused) {
options.gcConfigBuilder.withShouldReleaseUnused(*shouldReleaseUnused);
}
if (allocInYoung) {
options.gcConfigBuilder.withAllocInYoung(*allocInYoung);
}
if (revertToYGAtTTI) {
options.gcConfigBuilder.withRevertToYGAtTTI(*revertToYGAtTTI);
}
if (sanitizeRate || sanitizeRandomSeed) {
auto sanitizeConfigBuilder = ::hermes::vm::GCSanitizeConfig::Builder();
if (sanitizeRate) {
sanitizeConfigBuilder.withSanitizeRate(*sanitizeRate);
}
if (sanitizeRandomSeed) {
sanitizeConfigBuilder.withRandomSeed(*sanitizeRandomSeed);
}
options.gcConfigBuilder.withSanitizeConfig(sanitizeConfigBuilder.build());
}
std::vector<std::string> bytecodeFiles{
cl::BytecodeFiles.begin(), cl::BytecodeFiles.end()};
if (cl::DisableSourceHashCheck && bytecodeFiles.size() != 1) {
throw std::invalid_argument(
"Must have single bytecode file to disable source hash check");
}
if (!cl::Trace.empty()) {
// If this is tracing mode, get the trace instead of the stats.
options.gcConfigBuilder.withShouldRecordStats(false);
options.shouldTrackIO = false;
std::error_code ec;
auto os = std::make_unique<llvh::raw_fd_ostream>(
cl::Trace.c_str(),
ec,
llvh::sys::fs::CD_CreateAlways,
llvh::sys::fs::FA_Write,
llvh::sys::fs::OF_Text);
if (ec) {
throw std::system_error(ec);
}
TraceInterpreter::execAndTrace(
cl::TraceFile, bytecodeFiles, options, std::move(os));
llvh::outs() << "\nWrote output trace to: " << cl::Trace << "\n";
} else {
llvh::outs() << TraceInterpreter::execAndGetStats(
cl::TraceFile, bytecodeFiles, options)
<< "\n";
}
#if !defined(NDEBUG) || defined(LLVM_ENABLE_STATS)
if (cl::PrintStats)
llvh::PrintStatistics(llvh::outs());
#endif
if (!options.profileFileName.empty()) {
llvh::outs() << "Wrote profile for marker \"" << options.marker
<< "\" to " << options.profileFileName << "\n";
}
return 0;
} catch (const std::invalid_argument &e) {
std::cerr << "Invalid argument: " << e.what() << std::endl;
} catch (const std::system_error &e) {
std::cerr << "System error: " << e.what() << std::endl;
}
return 1;
}