/* * 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 #include #include #include #include #include #include #include #include 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::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 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 pieces; std::unordered_map 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>; /// 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>; struct HostObjectInfo final { explicit HostObjectInfo() = default; PropNameToCalls propNameToCalls; std::vector callsToGetPropertyNames; std::vector> 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; /// 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 shouldPrintGCStats; llvm::Optional<::hermes::vm::gcheapsize_t> minHeapSize; llvm::Optional<::hermes::vm::gcheapsize_t> initHeapSize; llvm::Optional<::hermes::vm::gcheapsize_t> maxHeapSize; llvm::Optional occupancyTarget; llvm::Optional<::hermes::vm::ReleaseUnused> shouldReleaseUnused; llvm::Optional allocInYoung; llvm::Optional revertToYGAtTTI; llvm::Optional shouldTrackIO; llvm::Optional bytecodeWarmupPercent; llvm::Optional sanitizeRate; llvm::Optional 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> bundles_; const SynthTrace &trace_; const std::unordered_map &globalDefsAndUses_; const HostFunctionToCalls &hostFunctionCalls_; const HostObjectToCalls &hostObjectCalls_; std::unordered_map hostFunctions_; std::unordered_map 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 hostObjects_; std::unordered_map< SynthTrace::ObjectID, std::unordered_map> hostObjectsCallCount_; std::unordered_map hostObjectsPropertyNamesCallCount_; std::unordered_map 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 &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 &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 &bytecodeFiles, const ExecuteOptions &options, std::unique_ptr traceStream); /// \param traceStream If non-null, write a trace of the execution into this /// stream. static std::string execFromMemoryBuffer( std::unique_ptr traceBuf, std::vector> codeBufs, const ExecuteOptions &options, std::unique_ptr traceStream); private: TraceInterpreter( jsi::Runtime &rt, const ExecuteOptions &options, const SynthTrace &trace, std::map<::hermes::SHA1, std::shared_ptr> bundles, const std::unordered_map &globalDefsAndUses, const HostFunctionToCalls &hostFunctionCalls, const HostObjectToCalls &hostObjectCalls); static std::string execFromFileNames( const std::string &traceFile, const std::vector &bytecodeFiles, const ExecuteOptions &options, std::unique_ptr traceStream); static std::string exec( jsi::Runtime &rt, const ExecuteOptions &options, const SynthTrace &trace, std::map<::hermes::SHA1, std::shared_ptr> 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 &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 &locals); std::string printStats(); LLVM_ATTRIBUTE_NORETURN void crashOnException( const std::exception &e, ::hermes::OptValue globalRecordNum); }; } // namespace tracing } // namespace hermes } // namespace facebook #endif