This repository has been archived on 2022-03-12. You can view files and clone it, but cannot push or open issues or pull requests.
2021-04-02 02:24:13 +03:00

276 lines
10 KiB
C++

/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#ifdef HERMESVM_API_TRACE
#include <hermes/Public/RuntimeConfig.h>
#include <hermes/Support/SHA1.h>
#include <hermes/SynthTrace.h>
#include <jsi/jsi.h>
#include <llvm/Support/MemoryBuffer.h>
#include <llvm/Support/raw_ostream.h>
#include <map>
#include <unordered_map>
#include <vector>
namespace facebook {
namespace hermes {
namespace tracing {
class TraceInterpreter final {
public:
/// A DefAndUse details the location of a definition of an object id, and its
/// use. It is an index into the global record table.
struct DefAndUse {
/// If an object was not used or not defined, its DefAndUse can store this
/// value.
static constexpr uint64_t kUnused = std::numeric_limits<uint64_t>::max();
uint64_t lastDefBeforeFirstUse{kUnused};
uint64_t lastUse{kUnused};
};
/// A Call is a list of Pieces that represent the entire single call
/// frame, even if it spans multiple control transfers between JS and native.
/// It also contains a map from ObjectIDs to their last definition before a
/// first use, and a last use.
struct Call {
/// A Piece is a series of contiguous records that are part of the same
/// native call, and have no transitions to JS in the middle of them.
struct Piece {
/// The index of the start of the piece in the global record vector.
uint64_t start;
std::vector<const SynthTrace::Record *> records;
explicit Piece() : start(0) {}
explicit Piece(int64_t start) : start(start) {}
};
/// A list of pieces, where each piece stops when a transition occurs
/// between JS and Native. Pieces are guaranteed to be sorted according to
/// their start record (ascending).
std::vector<Piece> pieces;
std::unordered_map<SynthTrace::ObjectID, DefAndUse> locals;
explicit Call() = delete;
explicit Call(const Piece &piece) {
pieces.emplace_back(piece);
}
explicit Call(Piece &&piece) {
pieces.emplace_back(std::move(piece));
}
};
/// A HostFunctionToCalls is a mapping from a host function id to the list of
/// calls associated with that host function's execution. The calls are
/// ordered by invocation (the 0th element is the 1st call).
using HostFunctionToCalls =
std::unordered_map<SynthTrace::ObjectID, std::vector<Call>>;
/// A PropNameToCalls is a mapping from property names to a list of
/// calls on that property. The calls are ordered by invocation (the 0th
/// element is the 1st call).
using PropNameToCalls = std::unordered_map<std::string, std::vector<Call>>;
struct HostObjectInfo final {
explicit HostObjectInfo() = default;
PropNameToCalls propNameToCalls;
std::vector<Call> callsToGetPropertyNames;
std::vector<std::vector<std::string>> resultsOfGetPropertyNames;
};
/// A HostObjectToCalls is a mapping from a host object id to the
/// mapping of property names to calls associated with accessing properties of
/// that host object and the list of calls associated with getPropertyNames.
using HostObjectToCalls =
std::unordered_map<SynthTrace::ObjectID, HostObjectInfo>;
/// Options for executing the trace.
/// \param useTraceConfig If true, command-line options override the
/// config options recorded in the trace. If false, start from the default
/// config.
/// \param snapshotMarker If the given marker is seen, take a heap snapshot.
/// \param snapshotMarkerFileName If the marker given in snapshotMarker
/// is seen, write the heap snapshot out to this file.
/// \param warmupReps Number of initial executions whose stats are discarded.
/// \param reps Number of repetitions of execution. Stats returned are those
/// for the rep with the median totalTime.
/// \param minHeapSize if non-zero, the minimum heap size, overriding
/// the value stored in the trace.
/// \param maxHeapSize if non-zero, the maximum heap size, overriding
/// the value stored in the trace.
/// \param allocInYoung: determines whether the GC initially allocates in
/// the young generation.
/// \param revertToYGAtTTI: if true, and if the GC was not allocating in the
/// young generation, change back to young-gen allocation at TTI.
struct ExecuteOptions {
// These are not config params.
bool useTraceConfig{false};
int warmupReps{0};
int reps{1};
bool forceGCBeforeStats{false};
bool stabilizeInstructionCount{false};
std::string marker;
std::string snapshotMarker;
std::string snapshotMarkerFileName;
// These are the config parameters. We wrap them in llvm::Optional
// to indicate whether the corresponding command line flag was set
// explicitly. We override the trace's config only when that is true.
llvm::Optional<bool> shouldPrintGCStats;
llvm::Optional<::hermes::vm::gcheapsize_t> minHeapSize;
llvm::Optional<::hermes::vm::gcheapsize_t> initHeapSize;
llvm::Optional<::hermes::vm::gcheapsize_t> maxHeapSize;
llvm::Optional<double> occupancyTarget;
llvm::Optional<::hermes::vm::ReleaseUnused> shouldReleaseUnused;
llvm::Optional<bool> allocInYoung;
llvm::Optional<bool> revertToYGAtTTI;
llvm::Optional<bool> shouldTrackIO;
llvm::Optional<unsigned> bytecodeWarmupPercent;
llvm::Optional<double> sanitizeRate;
llvm::Optional<int64_t> sanitizeRandomSeed;
};
private:
jsi::Runtime &rt_;
ExecuteOptions options_;
llvm::raw_ostream *traceStream_;
// Map from source hash to source file to run.
std::map<::hermes::SHA1, std::shared_ptr<const jsi::Buffer>> bundles_;
const SynthTrace &trace_;
const std::unordered_map<SynthTrace::ObjectID, DefAndUse> &globalDefsAndUses_;
const HostFunctionToCalls &hostFunctionCalls_;
const HostObjectToCalls &hostObjectCalls_;
std::unordered_map<SynthTrace::ObjectID, jsi::Function> hostFunctions_;
std::unordered_map<SynthTrace::ObjectID, uint64_t> hostFunctionsCallCount_;
// NOTE: Theoretically a host object property can have both a getter and a
// setter. Since this doesn't occur in practice currently, this
// implementation will ignore it. If it does happen, the value of the
// interior map should turn into a pair of functions, and a pair of function
// counts.
std::unordered_map<SynthTrace::ObjectID, jsi::Object> hostObjects_;
std::unordered_map<
SynthTrace::ObjectID,
std::unordered_map<std::string, uint64_t>>
hostObjectsCallCount_;
std::unordered_map<SynthTrace::ObjectID, uint64_t>
hostObjectsPropertyNamesCallCount_;
std::unordered_map<SynthTrace::ObjectID, jsi::Object> gom_;
std::string stats_;
/// Whether the marker was reached.
bool markerFound_{false};
/// Whether the snapshot marker was reached.
bool snapshotMarkerFound_{false};
/// Depth in the execution stack. Zero is the outermost function.
uint64_t depth_{0};
public:
/// Execute the trace given by \p traceFile, that was the trace of executing
/// the bundle given by \p bytecodeFile.
static void exec(
const std::string &traceFile,
const std::vector<std::string> &bytecodeFiles,
const ExecuteOptions &options);
/// Same as exec, except it prints out the stats of a run.
/// \return The stats collected by the runtime about times and memory usage.
static std::string execAndGetStats(
const std::string &traceFile,
const std::vector<std::string> &bytecodeFiles,
const ExecuteOptions &options);
/// Same as exec, except it additionally traces the execution of the
/// interpreter, to \p *traceStream. (Requires \p traceStream to be
/// non-null.) This trace can be compared to the original to detect
/// correctness issues.
static void execAndTrace(
const std::string &traceFile,
const std::vector<std::string> &bytecodeFiles,
const ExecuteOptions &options,
std::unique_ptr<llvm::raw_ostream> traceStream);
/// \param traceStream If non-null, write a trace of the execution into this
/// stream.
static std::string execFromMemoryBuffer(
std::unique_ptr<llvm::MemoryBuffer> traceBuf,
std::vector<std::unique_ptr<llvm::MemoryBuffer>> codeBufs,
const ExecuteOptions &options,
std::unique_ptr<llvm::raw_ostream> traceStream);
private:
TraceInterpreter(
jsi::Runtime &rt,
const ExecuteOptions &options,
const SynthTrace &trace,
std::map<::hermes::SHA1, std::shared_ptr<const jsi::Buffer>> bundles,
const std::unordered_map<SynthTrace::ObjectID, DefAndUse>
&globalDefsAndUses,
const HostFunctionToCalls &hostFunctionCalls,
const HostObjectToCalls &hostObjectCalls);
static std::string execFromFileNames(
const std::string &traceFile,
const std::vector<std::string> &bytecodeFiles,
const ExecuteOptions &options,
std::unique_ptr<llvm::raw_ostream> traceStream);
static std::string exec(
jsi::Runtime &rt,
const ExecuteOptions &options,
const SynthTrace &trace,
std::map<::hermes::SHA1, std::shared_ptr<const jsi::Buffer>> bundles);
jsi::Function createHostFunction(
const SynthTrace::CreateHostFunctionRecord &rec);
jsi::Object createHostObject(SynthTrace::ObjectID objID);
std::string execEntryFunction(const Call &entryFunc);
jsi::Value execFunction(
const Call &entryFunc,
const jsi::Value &thisVal,
const jsi::Value *args,
uint64_t count);
/// Add \p obj, whose id is \p objID and occurs at \p globalRecordNum, to
/// either the globals or the \p locals depending on if it is used locally or
/// not.
void addObjectToDefs(
const Call &call,
SynthTrace::ObjectID objID,
uint64_t globalRecordNum,
const jsi::Object &obj,
std::unordered_map<SynthTrace::ObjectID, jsi::Object> &locals);
/// Same as above, except it avoids copies on temporary objects.
void addObjectToDefs(
const Call &call,
SynthTrace::ObjectID objID,
uint64_t globalRecordNum,
jsi::Object &&obj,
std::unordered_map<SynthTrace::ObjectID, jsi::Object> &locals);
std::string printStats();
LLVM_ATTRIBUTE_NORETURN void crashOnException(
const std::exception &e,
::hermes::OptValue<uint64_t> globalRecordNum);
};
} // namespace tracing
} // namespace hermes
} // namespace facebook
#endif