This commit is contained in:
Yamozha
2021-04-02 02:24:13 +03:00
parent c23950b545
commit 7256d79e2c
31493 changed files with 3036630 additions and 0 deletions

View File

@ -0,0 +1,35 @@
/*
* 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.
*/
#ifndef HERMES_COMPILEJS_H
#define HERMES_COMPILEJS_H
#include <string>
namespace hermes {
/// Compiles JS source \p str and if compilation is successful, returns true
/// and outputs to \p bytecode otherwise returns false.
/// \param sourceURL this will be used as the "file name" of the buffer for
/// errors, stack traces, etc.
/// NOTE: Doesn't throw any exceptions. It's up to the caller to report failure.
///
/// TODO(30388684): Return opaque object that can be run or serialized.
bool compileJS(
const std::string &str,
const std::string &sourceURL,
std::string &bytecode,
bool optimize = true);
bool compileJS(
const std::string &str,
std::string &bytecode,
bool optimize = true);
} // namespace hermes
#endif

View File

@ -0,0 +1,304 @@
/*
* 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.
*/
#ifndef HERMES_DEBUGGERAPI_H
#define HERMES_DEBUGGERAPI_H
#ifdef HERMES_ENABLE_DEBUGGER
#include <hermes/hermes.h>
#include <cassert>
#include <memory>
#include <vector>
#include "hermes/Public/DebuggerTypes.h"
// Forward declarations of internal types.
namespace hermes {
namespace vm {
class CodeBlock;
class Debugger;
struct DebugCommand;
class HermesValue;
} // namespace vm
} // namespace hermes
namespace facebook {
namespace hermes {
class HermesRuntime;
namespace debugger {
class Debugger;
class EventObserver;
/// Represents a variable in the debugger.
struct VariableInfo {
/// Name of the variable in the source.
String name;
/// Value of the variable.
::facebook::jsi::Value value;
};
/// An EvalResult represents the result of an Eval command.
struct EvalResult {
/// The resulting JavaScript object, or the thrown exception.
::facebook::jsi::Value value;
/// Indicates that the result was an exception.
bool isException = false;
/// If isException is true, details about the exception.
ExceptionDetails exceptionDetails;
EvalResult(EvalResult &&) = default;
EvalResult() = default;
EvalResult(
::facebook::jsi::Value value,
bool isException,
ExceptionDetails exceptionDetails)
: value(std::move(value)),
isException(isException),
exceptionDetails(std::move(exceptionDetails)) {}
};
/// ProgramState represents the state of a paused program. An instance of
/// ProgramState is available as the getProgramState() member function of class
/// Debugger.
class ProgramState {
public:
/// \return the reason for the Pause.
PauseReason getPauseReason() const {
return pauseReason_;
}
/// \return the breakpoint if the PauseReason is Breakpoint, otherwise
/// kInvalidBreakpoint.
BreakpointID getBreakpoint() const {
return breakpoint_;
}
/// \return the evaluation result if the PauseReason is due to EvalComplete.
EvalResult getEvalResult() const;
/// \returns a stack trace for the current execution.
const StackTrace &getStackTrace() const {
return stackTrace_;
}
/// \returns lexical information about the state in a given frame.
LexicalInfo getLexicalInfo(uint32_t frameIndex) const;
/// \return information about a variable in a given lexical scope, in a given
/// frame.
VariableInfo getVariableInfo(
uint32_t frameIndex,
ScopeDepth scopeDepth,
uint32_t variableIndexInScope) const;
/// \return information about the `this` value at a given stack depth.
VariableInfo getVariableInfoForThis(uint32_t frameIndex) const;
/// \return the number of variables in a given frame.
/// This is deprecated: prefer using getLexicalInfoInFrame().
uint32_t getVariablesCountInFrame(uint32_t frameIndex) const {
auto info = getLexicalInfo(frameIndex);
uint32_t result = 0;
for (ScopeDepth i = 0, max = info.getScopesCount(); i < max; i++)
result += info.getVariablesCountInScope(i);
return result;
}
/// \return info for a variable at a given index \p variableIndex, in a given
/// frame at index \p frameIndex.
/// This is deprecated. Prefer the getVariableInfo() that takes three
/// parameters.
VariableInfo getVariableInfo(uint32_t frameIndex, uint32_t variableIndex)
const {
LexicalInfo info = getLexicalInfo(frameIndex);
uint32_t remaining = variableIndex;
for (ScopeDepth scope = 0;; scope++) {
assert(scope < info.getScopesCount() && "Index out of bounds");
uint32_t count = info.getVariablesCountInScope(scope);
if (remaining < count) {
return getVariableInfo(frameIndex, scope, remaining);
}
remaining -= count;
}
}
private:
friend Debugger;
/// ProgramState must not be copied, because some of its implementation
/// requires querying the live program state and so the state must not be
/// retained after the pause returns.
/// ProgramState must not be copied.
ProgramState(const ProgramState &) = delete;
ProgramState &operator=(const ProgramState &) = delete;
::hermes::vm::Debugger *impl() const;
ProgramState(Debugger *dbg) : dbg_(dbg) {}
Debugger *dbg_;
PauseReason pauseReason_{};
StackTrace stackTrace_;
EvalResult evalResult_;
BreakpointID breakpoint_{kInvalidBreakpoint};
};
/// Command represents an action that you can request the debugger to perform
/// when returned from didPause().
class Command {
public:
/// Commands may be moved.
Command(Command &&);
Command &operator=(Command &&);
~Command();
/// \return a Command that steps with the given StepMode \p mode.
static Command step(StepMode mode);
/// \return a Command that continues execution.
static Command continueExecution();
/// \return a Command that evaluates JavaScript code \p src in the
/// frame at index \p frameIndex.
static Command eval(const String &src, uint32_t frameIndex);
private:
friend Debugger;
explicit Command(::hermes::vm::DebugCommand &&);
std::unique_ptr<::hermes::vm::DebugCommand> debugCommand_;
};
/// Debugger allows access to the Hermes debugging functionality. An instance of
/// Debugger is available from HermesRuntime, and also passed to your
/// EventObserver.
class Debugger {
public:
/// Set the Debugger event observer. The event observer is notified of
/// debugging event, specifically when the program pauses. This is simply a
/// raw pointer: it is the client's responsibility to clear the event observer
/// if the event observer is deallocated before the Debugger.
void setEventObserver(EventObserver *observer);
/// Sets the property %isDebuggerAttached in %DebuggerInternal object.
void setIsDebuggerAttached(bool isAttached);
/// Asynchronously triggers a pause. This may be called from any thread. This
/// is inherently racey and the exact point at which the program pauses is not
/// guaranteed. You can discover when the program has paused through the event
/// observer.
void triggerAsyncPause(AsyncPauseKind kind);
/// \return the ProgramState representing the state of the paused program.
/// This may only be invoked when the program is paused.
const ProgramState &getProgramState() const {
return state_;
}
/// \return the source map URL for the \p fileId.
String getSourceMappingUrl(uint32_t fileId) const;
/// -- Breakpoint Management --
/// Sets a breakpoint on a given SourceLocation.
/// \return the ID of the breakpoint, 0 if it wasn't created.
BreakpointID setBreakpoint(SourceLocation loc);
/// Sets the condition on breakpoint \p breakpoint.
/// The condition will be stored with the breakpoint,
/// and if non-empty, will be executed to determine whether to actually
/// pause on the breakpoint; only if ToBoolean(condition) is true
/// and does not throw will the debugger pause on \p breakpoint.
/// \param condition the code to execute to determine whether to break;
/// if empty, the condition is considered to not be set.
void setBreakpointCondition(BreakpointID breakpoint, const String &condition);
/// Deletes a breakpoint.
void deleteBreakpoint(BreakpointID breakpoint);
/// Deletes all breakpoints.
void deleteAllBreakpoints();
/// Mark a breakpoint as enabled. Breakpoints are by default enabled.
void setBreakpointEnabled(BreakpointID breakpoint, bool enable);
/// \return information on a breakpoint.
BreakpointInfo getBreakpointInfo(BreakpointID breakpoint);
/// \return a list of extant breakpoints.
std::vector<BreakpointID> getBreakpoints();
/// Set whether the debugger should pause when an exception is thrown.
void setPauseOnThrowMode(PauseOnThrowMode mode);
/// \return whether the debugger pauses when an exception is thrown.
PauseOnThrowMode getPauseOnThrowMode() const;
/// Set whether the debugger should pause after a script was loaded.
void setShouldPauseOnScriptLoad(bool flag);
/// \return whether the debugger should pause after a script was loaded.
bool getShouldPauseOnScriptLoad() const;
private:
friend std::unique_ptr<HermesRuntime> hermes::makeHermesRuntime(
const ::hermes::vm::RuntimeConfig &);
friend std::unique_ptr<jsi::ThreadSafeRuntime>
hermes::makeThreadSafeHermesRuntime(const ::hermes::vm::RuntimeConfig &);
friend ProgramState;
/// Debuggers may not be moved or copied.
Debugger(const Debugger &) = delete;
void operator=(const Debugger &) = delete;
Debugger(Debugger &&) = delete;
void operator=(Debugger &&) = delete;
/// Implementation detail used by ProgramState.
::facebook::jsi::Value jsiValueFromHermesValue(::hermes::vm::HermesValue hv);
explicit Debugger(
::facebook::hermes::HermesRuntime *runtime,
::hermes::vm::Debugger *impl);
::facebook::hermes::HermesRuntime *const runtime_;
EventObserver *eventObserver_ = nullptr;
::hermes::vm::Debugger *impl_;
ProgramState state_;
};
/// A subclass of EventObserver may be set on the Debugger via
/// setEventObserver(). It receives notifications when the Debugger pauses.
class EventObserver {
public:
/// didPause() is invoked when the JavaScript program has paused. The
/// The Debugger \p debugger can be used to manipulate breakpoints and enqueue
/// debugger commands such as stepping, etc. It can also be used to discover
/// the call stack and variables via debugger.getProgramState().
/// \return a Command for the debugger to perform.
virtual Command didPause(Debugger &debugger) = 0;
/// Invoked when the debugger resolves a previously unresolved breakpoint.
/// Note that the debugger is *not* paused during this,
/// and thus debugger.getProgramState() is not valid.
/// This callback may not invoke JavaScript or enqueue debugger commands.
virtual void breakpointResolved(Debugger &debugger, BreakpointID breakpoint) {
}
virtual ~EventObserver();
};
} // namespace debugger
} // namespace hermes
} // namespace facebook
#endif // HERMES_ENABLE_DEBUGGER
#endif // HERMES_DEBUGGERAPI_H

View File

@ -0,0 +1,40 @@
/*
* 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.
*/
#ifndef HERMES_PUBLIC_BUFFER_H
#define HERMES_PUBLIC_BUFFER_H
#include <cstddef>
#include <cstdint>
namespace hermes {
/// A generic buffer interface. E.g. for memmapped bytecode.
class Buffer {
public:
Buffer() : data_(nullptr), size_(0) {}
Buffer(const uint8_t *data, size_t size) : data_(data), size_(size) {}
virtual ~Buffer() {}
const uint8_t *data() const {
return data_;
};
size_t size() const {
return size_;
}
protected:
const uint8_t *data_ = nullptr;
size_t size_ = 0;
};
} // namespace hermes
#endif

View File

@ -0,0 +1,96 @@
/*
* 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.
*/
#ifndef HERMES_PUBLIC_CRASHMANAGER_H
#define HERMES_PUBLIC_CRASHMANAGER_H
#include <cstddef>
#include <functional>
namespace hermes {
namespace vm {
/// A CrashManager provides functions that determine what memory and data is
/// included in dumps in case of crashes.
class CrashManager {
public:
/// CallbackKey is the type of an identifier for a callback supplied to the
/// CrashManager.
using CallbackKey = int;
/// Type for the callback function invoked on crash. The fd supplied is a raw
/// file stream an implementation should write a JSON object to.
using CallbackFunc = std::function<void(int fd)>;
/// Registers some memory to be included in any crash dump that occurs.
/// \param mem A pointer to allocated memory. It must be unregistered
/// before being freed.
/// \param length The number of bytes the memory controls.
virtual void registerMemory(void *mem, size_t length) = 0;
/// Unregisters some memory from being included in any crash dump that occurs.
virtual void unregisterMemory(void *mem) = 0;
/// Registers custom data to be included in any crash dump that occurs.
/// Calling \c setCustomData on the same key twice will overwrite the previous
/// value.
/// \param key A tag to look for in the custom data output. Distinguishes
/// between multiple values.
/// \param val The value to store for the given key.
virtual void setCustomData(const char *key, const char *val) = 0;
/// If the given \p key has an associated custom data string, remove the
/// association.
virtual void removeCustomData(const char *key) = 0;
/// Registers a function to be called after a crash has occurred. This
/// function can examine memory and serialize this to a JSON output stream.
/// Implmentations decide where the stream is routed to.
/// \param callback A function to called after a crash.
/// \return A CallbackKey representing the function you provided. Pass this
/// key into unregisterCallback when it that callback is no longer needed.
virtual CallbackKey registerCallback(CallbackFunc callback) = 0;
/// Unregisters a previously registered callback. After this function returns,
/// the previously registered function will not be executed by this
/// CrashManager during a crash.
virtual void unregisterCallback(CallbackKey key) = 0;
/// the heap information.
struct HeapInformation {
/// The amount of memory that is currently in use
size_t used_{0};
/// The amount of memory that can currently be allocated
/// before a full GC is triggered.
size_t size_{0};
};
/// Record the heap information.
/// \param heapInfo The current heap information
virtual void setHeapInfo(const HeapInformation &heapInfo) = 0;
virtual ~CrashManager() {}
};
/// A CrashManager that does nothing.
class NopCrashManager final : public CrashManager {
public:
void registerMemory(void *, size_t) override {}
void unregisterMemory(void *) override {}
void setCustomData(const char *, const char *) override {}
void removeCustomData(const char *) override {}
CallbackKey registerCallback(CallbackFunc /*callback*/) override {
return 0;
}
void unregisterCallback(CallbackKey /*key*/) override {}
void setHeapInfo(const HeapInformation & /*heapInfo*/) override {}
~NopCrashManager() {}
};
} // namespace vm
} // namespace hermes
#endif

View File

@ -0,0 +1,148 @@
/*
* 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.
*/
#ifndef HERMES_PUBLIC_CTORCONFIG_H
#define HERMES_PUBLIC_CTORCONFIG_H
#include <utility>
/// Defines a new class, called \p NAME representing a constructor config, and
/// an associated builder class.
///
/// The fields of the class (along with their types and default values) are
/// encoded in the \p FIELDS parameter, and any logic to be run whilst building
/// the config can be passed as a code block in \p BUILD_BODY.
///
/// Example:
///
/// Suppose we wish to define a configuration class called Foo, with the
/// following fields and default values:
///
/// int A = 0;
/// int B = 42;
/// std::string C = "hello";
///
/// Such that the value in A is at most the length of \c C.
///
/// We can do so with the following declaration:
///
/// " #define FIELDS(F) \ "
/// " F(int, A) \ "
/// " F(int, B, 42) \ "
/// " F(std::string, C, "hello") "
/// " "
/// " _HERMES_CTORCONFIG_STRUCT(Foo, FIELDS, { "
/// " A_ = std::min(A_, C_.length()); "
/// " }); "
///
/// N.B.
/// - The definition of A does not mention any value -- meaning it is
/// default initialised.
/// - References to the fields in the validation logic have a trailling
/// underscore.
///
#define _HERMES_CTORCONFIG_STRUCT(NAME, FIELDS, BUILD_BODY) \
class NAME { \
FIELDS(_HERMES_CTORCONFIG_FIELD_DECL); \
\
public: \
class Builder; \
friend Builder; \
FIELDS(_HERMES_CTORCONFIG_GETTER); \
\
/* returns a Builder that starts with the current config. */ \
inline Builder rebuild() const; \
\
private: \
inline void doBuild(const Builder &builder); \
}; \
\
class NAME::Builder { \
NAME config_; \
\
FIELDS(_HERMES_CTORCONFIG_FIELD_EXPLICIT_BOOL_DECL); \
\
public: \
Builder() = default; \
\
explicit Builder(const NAME &config) : config_(config){}; \
\
inline const NAME build() { \
config_.doBuild(*this); \
return config_; \
} \
\
/* The explicitly set fields of \p newconfig update \
* the corresponding fields of \p this. */ \
inline Builder update(const NAME::Builder &newConfig); \
\
FIELDS(_HERMES_CTORCONFIG_SETTER); \
FIELDS(_HERMES_CTORCONFIG_FIELD_EXPLICIT_BOOL_ACCESSOR); \
}; \
\
NAME::Builder NAME::rebuild() const { \
return Builder(*this); \
} \
\
NAME::Builder NAME::Builder::update(const NAME::Builder &newConfig) { \
FIELDS(_HERMES_CTORCONFIG_UPDATE); \
return *this; \
} \
\
void NAME::doBuild(const NAME::Builder &builder) { \
(void)builder; \
BUILD_BODY \
}
/// Helper Macros
#define _HERMES_CTORCONFIG_FIELD_DECL(CX, TYPE, NAME, ...) \
TYPE NAME##_{__VA_ARGS__};
/// This ignores the first and trailing arguments, and defines a member
/// indicating whether field NAME was set explicitly.
#define _HERMES_CTORCONFIG_FIELD_EXPLICIT_BOOL_DECL(CX, TYPE, NAME, ...) \
bool NAME##Explicit_{false};
/// This defines an accessor for the "Explicit_" fields defined above.
#define _HERMES_CTORCONFIG_FIELD_EXPLICIT_BOOL_ACCESSOR(CX, TYPE, NAME, ...) \
bool has##NAME() const { \
return NAME##Explicit_; \
}
/// Placeholder token for fields whose defaults are not constexpr, to make the
/// listings more readable.
#define HERMES_NON_CONSTEXPR
#define _HERMES_CTORCONFIG_GETTER(CX, TYPE, NAME, ...) \
inline TYPE get##NAME() const { \
return NAME##_; \
} \
static CX TYPE getDefault##NAME() { \
/* Instead of parens around TYPE (non-standard) */ \
using TypeAsSingleToken = TYPE; \
return TypeAsSingleToken{__VA_ARGS__}; \
}
#define _HERMES_CTORCONFIG_SETTER(CX, TYPE, NAME, ...) \
inline auto with##NAME(TYPE NAME)->decltype(*this) { \
config_.NAME##_ = std::move(NAME); \
NAME##Explicit_ = true; \
return *this; \
}
#define _HERMES_CTORCONFIG_BUILDER_GETTER(CX, TYPE, NAME, ...) \
TYPE get##NAME() const { \
return config_.NAME##_; \
}
#define _HERMES_CTORCONFIG_UPDATE(CX, TYPE, NAME, ...) \
if (newConfig.has##NAME()) { \
with##NAME(newConfig.config_.get##NAME()); \
}
#endif // HERMES_PUBLIC_CTORCONFIG_H

View File

@ -0,0 +1,192 @@
/*
* 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.
*/
#ifndef HERMES_PUBLIC_DEBUGGERTYPES_H
#define HERMES_PUBLIC_DEBUGGERTYPES_H
#include <string>
#include <vector>
namespace hermes {
namespace vm {
class Debugger;
}
} // namespace hermes
namespace facebook {
namespace hermes {
namespace debugger {
class ProgramState;
/// Strings in the Debugger are UTF-8 encoded. When converting from a JavaScript
/// string, valid UTF-16 surrogate pairs are decoded. Surrogate halves are
/// converted into the Unicode replacement character.
using String = std::string;
/// Debugging entities like breakpoints are identified by a unique ID. The
/// Debugger will not re-use IDs even across different entity types. 0 is an
/// invalid ID.
using BreakpointID = uint64_t;
// NOTE: Can't be kInvalidID due to a clash with MacTypes.h's define kInvalidID.
constexpr uint64_t kInvalidBreakpoint = 0;
/// Scripts when loaded are identified by a script ID.
/// These are not reused within one invocation of the VM.
using ScriptID = uint32_t;
/// A SourceLocation is a small value-type representing a location in a source
/// file.
constexpr uint32_t kInvalidLocation = ~0u;
struct SourceLocation {
/// Line in the source.
uint32_t line = kInvalidLocation;
/// Column in the source.
uint32_t column = kInvalidLocation;
/// Identifier of the source file.
ScriptID fileId = kInvalidLocation;
/// Name of the source file.
String fileName;
};
/// CallFrameInfo is a value type representing an entry in a call stack.
struct CallFrameInfo {
/// Name of the function executing in this frame.
String functionName;
/// Source location of the program counter for this frame.
SourceLocation location;
};
/// StackTrace represents a list of call frames, either in the current execution
/// or captured in an exception.
struct StackTrace {
/// \return the number of call frames.
uint32_t callFrameCount() const {
return frames_.size();
}
/// \return call frame info at a given index. 0 represents the topmost
/// (current) frame on the call stack.
CallFrameInfo callFrameForIndex(uint32_t index) const {
return frames_.at(index);
}
StackTrace() {}
private:
explicit StackTrace(std::vector<CallFrameInfo> frames)
: frames_(std::move(frames)){};
friend ProgramState;
friend ::hermes::vm::Debugger;
std::vector<CallFrameInfo> frames_;
};
/// ExceptionDetails is a value type describing an exception.
struct ExceptionDetails {
/// Textual description of the exception.
String text;
/// Location where the exception was thrown.
SourceLocation location;
/// Get the stack trace associated with the exception.
const StackTrace &getStackTrace() const {
return stackTrace_;
}
private:
friend ::hermes::vm::Debugger;
StackTrace stackTrace_;
};
/// A list of possible reasons for a Pause.
enum class PauseReason {
ScriptLoaded, /// A script file was loaded, and the debugger has requested
/// pausing after script load.
DebuggerStatement, /// A debugger; statement was hit.
Breakpoint, /// A breakpoint was hit.
StepFinish, /// A Step operation completed.
Exception, /// An Exception was thrown.
AsyncTrigger, /// The Pause is the result of triggerAsyncPause().
EvalComplete, /// An eval() function finished.
};
/// When stepping, the mode with which to step.
enum class StepMode {
Into, /// Enter into any function calls.
Over, /// Skip over any function calls.
Out, /// Step until the current function exits.
};
/// When setting pause on throw, this specifies when to pause.
enum class PauseOnThrowMode {
None, /// Never pause on exceptions.
Uncaught, /// Only pause on uncaught exceptions.
All, /// Pause any time an exception is thrown.
};
/// When requesting an async break, this specifies whether it was an implicit
/// break from the inspector or a user-requested explicit break.
enum class AsyncPauseKind {
/// Implicit pause to allow movement of jsi::Value types between threads.
/// The user will not be running commands and the inspector will immediately
/// request a Continue.
Implicit,
/// Explicit pause requested by the user.
/// Clears any stepping state and allows the user to run their own commands.
Explicit,
};
/// A type representing depth in a lexical scope chain.
using ScopeDepth = uint32_t;
/// Information about lexical entities (for now, just variable names).
struct LexicalInfo {
/// \return the number of scopes.
ScopeDepth getScopesCount() const {
return variableCountsByScope_.size();
}
/// \return the number of variables in a given scope.
uint32_t getVariablesCountInScope(ScopeDepth depth) const {
return variableCountsByScope_.at(depth);
}
private:
friend ::hermes::vm::Debugger;
std::vector<uint32_t> variableCountsByScope_;
};
/// Information about a breakpoint.
struct BreakpointInfo {
/// ID of the breakpoint.
/// kInvalidBreakpoint if the info is not valid.
BreakpointID id;
/// Whether the breakpoint is enabled.
bool enabled;
/// Whether the breakpoint has been resolved.
bool resolved;
/// The originally requested location of the breakpoint.
SourceLocation requestedLocation;
/// The resolved location of the breakpoint if resolved is true.
SourceLocation resolvedLocation;
};
} // namespace debugger
} // namespace hermes
} // namespace facebook
#endif

View File

@ -0,0 +1,188 @@
/*
* 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.
*/
#ifndef HERMES_PUBLIC_GCCONFIG_H
#define HERMES_PUBLIC_GCCONFIG_H
#include "hermes/Public/CtorConfig.h"
#include "hermes/Public/GCTripwireContext.h"
#include "hermes/Public/MemoryEventTracker.h"
#include <algorithm>
#include <cassert>
#include <chrono>
#include <cstdint>
#include <functional>
#include <limits>
#include <memory>
#include <string>
namespace hermes {
namespace vm {
/// A type big enough to accomodate the entire allocated address space.
/// Individual allocations are always 'uint32_t', but on a 64-bit machine we
/// might want to accommodate a larger total heap (or not, in which case we keep
/// it 32-bit).
using gcheapsize_t = uint32_t;
struct GCAnalyticsEvent {
std::string runtimeDescription;
std::string gcKind;
std::string collectionType;
std::chrono::milliseconds duration;
std::chrono::milliseconds cpuDuration;
uint64_t preAllocated;
uint64_t preSize;
uint64_t postAllocated;
uint64_t postSize;
double survivalRatio;
};
/// Parameters to control a tripwire function called when the live set size
/// surpasses a given threshold after collections. Check documentation in
/// README.md
#define GC_TRIPWIRE_FIELDS(F) \
/* If the heap size is above this threshold after a collection, the tripwire \
* is triggered. */ \
F(constexpr, gcheapsize_t, Limit, std::numeric_limits<gcheapsize_t>::max()) \
\
/* The callback to call when the tripwire is considered triggered. */ \
F(HERMES_NON_CONSTEXPR, \
std::function<void(GCTripwireContext &)>, \
Callback, \
nullptr) \
/* GC_TRIPWIRE_FIELDS END */
_HERMES_CTORCONFIG_STRUCT(GCTripwireConfig, GC_TRIPWIRE_FIELDS, {});
#undef HEAP_TRIPWIRE_FIELDS
#define GC_HANDLESAN_FIELDS(F) \
/* The probability with which the GC should keep moving the heap */ \
/* to detect stale GC handles. */ \
F(constexpr, double, SanitizeRate, 0.0) \
/* Random seed to use for basis of decisions whether or not to */ \
/* sanitize. A negative value will mean a seed will be chosen at */ \
/* random. */ \
F(constexpr, int64_t, RandomSeed, -1) \
/* GC_HANDLESAN_FIELDS END */
_HERMES_CTORCONFIG_STRUCT(GCSanitizeConfig, GC_HANDLESAN_FIELDS, {});
#undef GC_HANDLESAN_FIELDS
/// How aggressively to return unused memory to the OS.
enum ReleaseUnused {
kReleaseUnusedNone = 0, /// Don't try to release unused memory.
kReleaseUnusedOld, /// Only old gen, on full collections.
kReleaseUnusedYoungOnFull, /// Also young gen, but only on full collections.
kReleaseUnusedYoungAlways /// Also young gen, also on young gen collections.
};
enum class GCEventKind {
CollectionStart,
CollectionEnd,
};
/// Parameters for GC Initialisation. Check documentation in README.md
/// constexpr indicates that the default value is constexpr.
#define GC_FIELDS(F) \
/* Minimum heap size hint. */ \
F(constexpr, gcheapsize_t, MinHeapSize, 0) \
\
/* Initial heap size hint. */ \
F(constexpr, gcheapsize_t, InitHeapSize, 32 << 20) \
\
/* Maximum heap size hint. */ \
F(constexpr, gcheapsize_t, MaxHeapSize, 512 << 20) \
\
/* Sizing heuristic: fraction of heap to be occupied by live data. */ \
F(constexpr, double, OccupancyTarget, 0.5) \
\
/* Number of consecutive full collections considered to be an OOM. */ \
F(constexpr, \
unsigned, \
EffectiveOOMThreshold, \
std::numeric_limits<unsigned>::max()) \
\
/* Sanitizer configuration for the GC. */ \
F(constexpr, GCSanitizeConfig, SanitizeConfig) \
\
/* Whether the GC should spread allocations across all its "spaces". */ \
F(constexpr, bool, ShouldRandomizeAllocSpace, false) \
\
/* Whether to Keep track of GC Statistics. */ \
F(constexpr, bool, ShouldRecordStats, false) \
\
/* How aggressively to return unused memory to the OS. */ \
F(constexpr, ReleaseUnused, ShouldReleaseUnused, kReleaseUnusedOld) \
\
/* Name for this heap in logs. */ \
F(HERMES_NON_CONSTEXPR, std::string, Name, "") \
\
/* Configuration for the Heap Tripwire. */ \
F(HERMES_NON_CONSTEXPR, GCTripwireConfig, TripwireConfig) \
\
/* Whether to (initially) allocate from the young gen (true) or the */ \
/* old gen (false). */ \
F(constexpr, bool, AllocInYoung, true) \
\
/* Whether to revert, if necessary, to young-gen allocation at TTI. */ \
F(constexpr, bool, RevertToYGAtTTI, false) \
\
/* Whether to use mprotect on GC metadata between GCs. */ \
F(constexpr, bool, ProtectMetadata, false) \
\
/* Whether to track allocation traces starting in the Runtime ctor. */ \
F(constexpr, bool, AllocationLocationTrackerFromStart, false) \
\
/* Pointer to the memory profiler (Memory Event Tracker). */ \
F(HERMES_NON_CONSTEXPR, \
std::shared_ptr<MemoryEventTracker>, \
MemEventTracker, \
nullptr) \
\
/* Callout for an analytics event. */ \
F(HERMES_NON_CONSTEXPR, \
std::function<void(const GCAnalyticsEvent &)>, \
AnalyticsCallback, \
nullptr) \
\
/* Called at GC events (see GCEventKind enum for the list). The */ \
/* second argument contains human-readable details about the event. */ \
/* NOTE: The function MUST NOT invoke any methods on the Runtime. */ \
F(HERMES_NON_CONSTEXPR, \
std::function<void(GCEventKind, const char *)>, \
Callback, \
nullptr) \
/* GC_FIELDS END */
_HERMES_CTORCONFIG_STRUCT(GCConfig, GC_FIELDS, {
if (builder.hasMinHeapSize()) {
if (builder.hasInitHeapSize()) {
// If both are specified, normalize the initial size up to the minimum,
// if necessary.
InitHeapSize_ = std::max(MinHeapSize_, InitHeapSize_);
} else {
// If the minimum is set explicitly, but the initial heap size is not,
// use the minimum as the initial size.
InitHeapSize_ = MinHeapSize_;
}
}
assert(InitHeapSize_ >= MinHeapSize_);
// Make sure the max is at least the Init.
MaxHeapSize_ = std::max(InitHeapSize_, MaxHeapSize_);
});
#undef GC_FIELDS
} // namespace vm
} // namespace hermes
#endif // HERMES_PUBLIC_GCCONFIG_H

View File

@ -0,0 +1,34 @@
/*
* 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.
*/
#ifndef HERMES_PUBLIC_GCTRIPWIRECONTEXT_H
#define HERMES_PUBLIC_GCTRIPWIRECONTEXT_H
#include <string>
#include <system_error>
namespace hermes {
namespace vm {
/// Interface passed to the GC tripwire callback when it fires.
class GCTripwireContext {
public:
virtual ~GCTripwireContext() = default;
/// Captures the heap to a file
///
/// \param path to save the heap capture
///
/// \return Empty error code if the heap capture succeeded, else a real error
/// code.
virtual std::error_code createSnapshotToFile(const std::string &path) = 0;
};
} // namespace vm
} // namespace hermes
#endif // HERMES_PUBLIC_GCTRIPWIRECONTEXT_H

View File

@ -0,0 +1,30 @@
/*
* 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.
*/
#ifndef HERMES_VM_PROFILER_MEMORYEVENTTRACKER_H
#define HERMES_VM_PROFILER_MEMORYEVENTTRACKER_H
#include <cstdint>
namespace hermes {
namespace vm {
/// This is the interface for the memory profiler that will track
/// allocations, calls, returns, moves, and deletes. It should
/// be inhertied from and implemented to the profilign needs, i.e
/// to a file, to a different format, to JSON, etc.
class MemoryEventTracker {
public:
virtual ~MemoryEventTracker() = default;
// Emit an allocation of a cell with kind `kind` and allocated
// size `size`.
virtual void emitAlloc(uint32_t kind, uint32_t size) = 0;
};
} // namespace vm
} // namespace hermes
#endif // HERMES_VM_PROFILER_MEMORYEVENTTRACKER_H

View File

@ -0,0 +1,136 @@
/*
* 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.
*/
#ifndef HERMES_PUBLIC_RUNTIMECONFIG_H
#define HERMES_PUBLIC_RUNTIMECONFIG_H
#include "hermes/Public/CrashManager.h"
#include "hermes/Public/CtorConfig.h"
#include "hermes/Public/GCConfig.h"
#include <memory>
#ifdef HERMESVM_SERIALIZE
#include <vector>
namespace llvm {
class MemoryBuffer;
class raw_ostream;
} // namespace llvm
#endif
namespace hermes {
namespace vm {
class PinnedHermesValue;
#ifdef HERMESVM_SERIALIZE
class Serializer;
class Deserializer;
#endif
// Parameters for Runtime initialisation. Check documentation in README.md
// constexpr indicates that the default value is constexpr.
#define RUNTIME_FIELDS_BASE(F) \
/* Parameters to be passed on to the GC. */ \
F(HERMES_NON_CONSTEXPR, vm::GCConfig, GCConfig) \
\
/* Pre-allocated Register Stack */ \
F(constexpr, PinnedHermesValue *, RegisterStack, nullptr) \
\
/* Register Stack Size */ \
F(constexpr, unsigned, MaxNumRegisters, 1024 * 1024) \
\
/* Whether or not the JIT is enabled */ \
F(constexpr, bool, EnableJIT, false) \
\
/* Whether to allow eval and Function ctor */ \
F(constexpr, bool, EnableEval, true) \
\
/* Whether to verify the IR generated by eval and Function ctor */ \
F(constexpr, bool, VerifyEvalIR, false) \
\
/* Support for ES6 Proxy. */ \
F(constexpr, bool, ES6Proxy, false) \
\
/* Support for ES6 Symbol. */ \
F(constexpr, bool, ES6Symbol, true) \
\
/* Trace non-deterministic JS behavior */ \
F(constexpr, bool, TraceEnvironmentInteractions, false) \
\
/* Enable sampling certain statistics. */ \
F(constexpr, bool, EnableSampledStats, false) \
\
/* Whether to enable sampling profiler */ \
F(constexpr, bool, EnableSampleProfiling, false) \
\
/* Whether to randomize stack placement etc. */ \
F(constexpr, bool, RandomizeMemoryLayout, false) \
\
/* Eagerly read bytecode into page cache. */ \
F(constexpr, unsigned, BytecodeWarmupPercent, 0) \
\
/* Signal-based I/O tracking. Slows down execution. If enabled, */ \
/* all bytecode buffers > 64 kB passed to Hermes must be mmap:ed. */ \
F(constexpr, bool, TrackIO, false) \
\
/* Enable contents of HermesInternal */ \
F(constexpr, bool, EnableHermesInternal, true) \
\
/* Enable methods exposed to JS for testing */ \
F(constexpr, bool, EnableHermesInternalTestMethods, false) \
\
/* Allows Function.toString() to return the original source code */ \
/* if available. For this to work code must have been compiled at */ \
/* runtime with CompileFlags::allowFunctionToStringWithRuntimeSource set. */ \
F(constexpr, bool, AllowFunctionToStringWithRuntimeSource, false) \
\
/* An interface for managing crashes. */ \
F(HERMES_NON_CONSTEXPR, \
std::shared_ptr<CrashManager>, \
CrashMgr, \
new NopCrashManager) \
\
/* The flags passed from a VM experiment */ \
F(constexpr, uint32_t, VMExperimentFlags, 0) \
/* RUNTIME_FIELDS END */
#ifdef HERMESVM_SERIALIZE
using ExternalPointersVectorFunction = std::vector<void *>();
#define RUNTIME_FIELDS_SD(F) \
/* Should serialize after initialization */ \
F(HERMES_NON_CONSTEXPR, \
std::shared_ptr<llvm::raw_ostream>, \
SerializeAfterInitFile, \
nullptr) \
/* Should deserialize instead of initialization */ \
F(HERMES_NON_CONSTEXPR, \
std::shared_ptr<llvm::MemoryBuffer>, \
DeserializeFile, \
nullptr) \
/* A function to get pointer values not visible to Runtime. e.g. \
* function pointers defined in ConsoleHost*/ \
F(constexpr, \
ExternalPointersVectorFunction *, \
ExternalPointersVectorCallBack, \
nullptr)
#define RUNTIME_FIELDS(F) \
RUNTIME_FIELDS_BASE(F) \
RUNTIME_FIELDS_SD(F)
#else // ifndef HERMESVM_SERIALIZE
#define RUNTIME_FIELDS(F) RUNTIME_FIELDS_BASE(F)
#endif // HERMESVM_SERIALIZE
_HERMES_CTORCONFIG_STRUCT(RuntimeConfig, RUNTIME_FIELDS, {});
#undef RUNTIME_FIELDS
} // namespace vm
} // namespace hermes
#endif // HERMES_PUBLIC_RUNTIMECONFIG_H

View File

@ -0,0 +1,865 @@
/*
* 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.
*/
#ifndef HERMES_SYNTHTRACE_H
#define HERMES_SYNTHTRACE_H
#ifdef HERMESVM_API_TRACE
#include "hermes/Public/RuntimeConfig.h"
#include "hermes/Support/JSONEmitter.h"
#include "hermes/Support/StringSetVector.h"
#include "hermes/VM/HermesValue.h"
#include "hermes/VM/MockedEnvironment.h"
#include "hermes/VM/Operations.h"
#include <chrono>
#include <cstdlib>
#include <memory>
#include <string>
#include <vector>
namespace llvm {
// Forward declaration to avoid including llvm headers.
class raw_ostream;
} // namespace llvm
namespace facebook {
namespace hermes {
namespace tracing {
/// A SynthTrace is a list of events that occur in a run of a JS file by a
/// runtime that uses JSI.
/// It can be serialized into JSON and written to a llvm::raw_ostream.
class SynthTrace {
public:
using ObjectID = uint64_t;
/// A tagged union representing different types available in the trace.
/// HermesValue doesn't have to be used, but it is an efficient way
/// of encoding a type tag and a value. std::variant is another option.
/// NOTE: Since HermesValue can only store 64-bit values, strings need to be
/// changed into a table index.
using TraceValue = ::hermes::vm::HermesValue;
/// A TimePoint is a time when some event occurred.
using TimePoint = std::chrono::steady_clock::time_point;
using TimeSinceStart = std::chrono::milliseconds;
static constexpr size_t kHashNumBytes = 20;
/// RecordType is a tag used to differentiate which type of record it is.
/// There should be a unique tag for each record type.
enum class RecordType {
BeginExecJS,
EndExecJS,
Marker,
CreateObject,
CreateHostObject,
CreateHostFunction,
GetProperty,
SetProperty,
HasProperty,
GetPropertyNames,
CreateArray,
ArrayRead,
ArrayWrite,
CallFromNative,
ConstructFromNative,
ReturnFromNative,
ReturnToNative,
CallToNative,
GetPropertyNative,
GetPropertyNativeReturn,
SetPropertyNative,
SetPropertyNativeReturn,
GetNativePropertyNames,
GetNativePropertyNamesReturn,
};
/// A Record is one element of a trace.
struct Record {
/// The time at which this event occurred with respect to the start of
/// execution.
/// NOTE: This is not compared in the \c operator= in order for tests to
/// pass.
const TimeSinceStart time_;
explicit Record() = delete;
explicit Record(TimeSinceStart time) : time_(time) {}
virtual ~Record() = default;
/// Write out a serialization of this Record.
/// \param json An emitter connected to an ostream which will write out
/// JSON.
void toJSON(::hermes::JSONEmitter &json, const SynthTrace &trace) const;
virtual RecordType getType() const = 0;
/// \return A list of object ids that are defined by this record.
/// Defined means that the record would produce that object as a locally
/// accessible value if it were executed.
virtual std::vector<ObjectID> defs() const {
return {};
}
/// \return A list of object ids that are used by this record.
/// Used means that the record would use that object as a value if it were
/// executed.
/// If a record uses an object, then some preceding record (either in the
/// same function invocation, or somewhere globally) must provide a
/// definition.
virtual std::vector<ObjectID> uses() const {
return {};
}
protected:
/// Compare records for equality. Derived classes should override this, call
/// their parent, and mark any public versions as "final".
virtual bool operator==(const Record &that) const {
return getType() == that.getType();
}
/// Emit JSON fields into \p os, excluding the closing curly brace.
/// NOTE: This is overridable, and non-abstract children should call the
/// parent.
virtual void toJSONInternal(
::hermes::JSONEmitter &json,
const SynthTrace &trace) const;
};
/// If \p traceStream is non-null, the trace will be written to that
/// stream. Otherwise, no trace is written.
explicit SynthTrace(
ObjectID globalObjID,
const ::hermes::vm::RuntimeConfig &conf,
std::unique_ptr<llvm::raw_ostream> traceStream = nullptr);
template <typename T, typename... Args>
void emplace_back(Args &&... args) {
records_.emplace_back(new T(std::forward<Args>(args)...));
flushRecordsIfNecessary();
}
const std::vector<std::unique_ptr<Record>> &records() const {
return records_;
}
ObjectID globalObjID() const {
return globalObjID_;
}
/// Given a trace value, turn it into its typed string.
std::string encode(TraceValue value) const;
/// Encode an undefined JS value for the trace.
static TraceValue encodeUndefined();
/// Encode a null JS value for the trace.
static TraceValue encodeNull();
/// Encode a boolean JS value for the trace.
static TraceValue encodeBool(bool value);
/// Encodes a numeric value for the trace.
static TraceValue encodeNumber(double value);
/// Encodes an object for the trace as a unique id.
static TraceValue encodeObject(ObjectID objID);
/// Encodes a string for the trace. Adds it to the string table if it hasn't
/// been seen before.
TraceValue encodeString(const std::string &value);
/// Decodes a string into a trace value. It can add to the string table.
TraceValue decode(const std::string &);
/// Extracts an object ID from a trace value.
/// \pre The value must be an object.
static ObjectID decodeObject(TraceValue value);
/// Extracts a string from a trace value.
/// \pre The value must be a string.
const std::string &decodeString(TraceValue value) const;
static bool equal(TraceValue x, TraceValue y) {
// We are encoding random numbers into strings, and can't use the library
// functions.
// For now, ignore differences that result from NaN, +0/-0.
return x.getRaw() == y.getRaw();
}
/// The version of the Synth Benchmark
constexpr static uint32_t synthVersion() {
return 2;
}
static const char *nameFromReleaseUnused(::hermes::vm::ReleaseUnused ru);
static ::hermes::vm::ReleaseUnused releaseUnusedFromName(const char *name);
private:
llvm::raw_ostream &os() const {
return (*traceStream_);
}
/// If we're tracing to a file, and the number of accumulated
/// records has reached the limit kTraceRecordsToFlush, below,
/// flush the records to the file, and reset the accumulated records
/// to be empty.
void flushRecordsIfNecessary();
/// Assumes we're tracing to a file; flush accumulated records to
/// the file, and reset the accumulated records to be empty.
void flushRecords();
static constexpr unsigned kTraceRecordsToFlush = 100;
/// If we're tracing to a file, pointer to a stream onto
/// traceFilename_. Null otherwise.
std::unique_ptr<llvm::raw_ostream> traceStream_;
/// If we're tracing to a file, pointer to a JSONEmitter writting
/// into *traceStream_. Null otherwise.
std::unique_ptr<::hermes::JSONEmitter> json_;
/// The records currently being accumulated in the trace. If we are
/// tracing to a file, these will be only the records not yet
/// written to the file.
std::vector<std::unique_ptr<Record>> records_;
/// The id of the global object.
const ObjectID globalObjID_;
/// A table of strings to avoid repeated strings taking up memory. Similar to
/// the IdentifierTable, except it doesn't need to be collected (it stores
/// strings forever).
/// Strings are stored in the trace objects as an index into this table.
::hermes::StringSetVector stringTable_;
public:
/// @name Record classes
/// @{
/// A MarkerRecord is an event that simply records an interesting event that
/// is not necessarily meaningful to the interpreter. It comes with a tag that
/// says what type of marker it was.
struct MarkerRecord : public Record {
static constexpr RecordType type{RecordType::Marker};
const std::string tag_;
explicit MarkerRecord(TimeSinceStart time, const std::string &tag)
: Record(time), tag_(tag) {}
RecordType getType() const override {
return type;
}
protected:
void toJSONInternal(::hermes::JSONEmitter &json, const SynthTrace &trace)
const override;
bool operator==(const Record &that) const override;
};
/// A BeginExecJSRecord is an event where execution begins of JS source
/// code. This is not necessarily the first record, since native code can
/// inject values into the VM before any source code is run.
struct BeginExecJSRecord final : public Record {
static constexpr RecordType type{RecordType::BeginExecJS};
explicit BeginExecJSRecord(
TimeSinceStart time,
std::string sourceURL,
::hermes::SHA1 sourceHash)
: Record(time),
sourceURL_(std::move(sourceURL)),
sourceHash_(std::move(sourceHash)) {}
RecordType getType() const override {
return type;
}
const std::string &sourceURL() const {
return sourceURL_;
}
const ::hermes::SHA1 &sourceHash() const {
return sourceHash_;
}
private:
void toJSONInternal(::hermes::JSONEmitter &json, const SynthTrace &trace)
const override;
/// The URL providing the source file mapping for the file being executed.
/// Can be empty.
std::string sourceURL_;
/// A hash of the source that was executed. The source hash must match up
/// when the file is replayed.
/// The hash is optional, and will be all zeros if not provided.
::hermes::SHA1 sourceHash_;
};
struct ReturnMixin {
const TraceValue retVal_;
explicit ReturnMixin(TraceValue value) : retVal_(value) {}
void toJSONInternal(::hermes::JSONEmitter &json, const SynthTrace &trace)
const;
bool operator==(const ReturnMixin &that) const;
};
/// A EndExecJSRecord is an event where execution of JS source code stops.
/// This does not mean that the source code will never be entered again, just
/// that it has an entered a phase where it is waiting for native code to call
/// into the JS. This event is not guaranteed to be the last event, for the
/// aforementioned reason. The logged retVal is the result of the evaluation
/// ("undefined" in the majority of cases).
struct EndExecJSRecord final : public MarkerRecord, public ReturnMixin {
static constexpr RecordType type{RecordType::EndExecJS};
EndExecJSRecord(TimeSinceStart time, TraceValue retVal)
: MarkerRecord(time, "end_global_code"), ReturnMixin(retVal) {}
RecordType getType() const override {
return type;
}
bool operator==(const Record &that) const final;
virtual void toJSONInternal(
::hermes::JSONEmitter &json,
const SynthTrace &trace) const final;
std::vector<ObjectID> defs() const override {
auto defs = MarkerRecord::defs();
if (retVal_.isObject()) {
defs.push_back(decodeObject(retVal_));
}
return defs;
}
};
/// A CreateObjectRecord is an event where an empty object is created by the
/// native code.
struct CreateObjectRecord : public Record {
static constexpr RecordType type{RecordType::CreateObject};
const ObjectID objID_;
explicit CreateObjectRecord(TimeSinceStart time, ObjectID objID)
: Record(time), objID_(objID) {}
bool operator==(const Record &that) const override;
void toJSONInternal(::hermes::JSONEmitter &json, const SynthTrace &trace)
const override;
RecordType getType() const override {
return type;
}
std::vector<ObjectID> defs() const override {
return {objID_};
}
std::vector<ObjectID> uses() const override {
return {};
}
};
struct CreateHostObjectRecord final : public CreateObjectRecord {
static constexpr RecordType type{RecordType::CreateHostObject};
using CreateObjectRecord::CreateObjectRecord;
RecordType getType() const override {
return type;
}
};
struct CreateHostFunctionRecord final : public CreateObjectRecord {
static constexpr RecordType type{RecordType::CreateHostFunction};
const std::string functionName_;
const unsigned paramCount_;
CreateHostFunctionRecord(
TimeSinceStart time,
ObjectID objID,
std::string functionName,
unsigned paramCount)
: CreateObjectRecord(time, objID),
functionName_(std::move(functionName)),
paramCount_(paramCount) {}
bool operator==(const Record &that) const override;
void toJSONInternal(::hermes::JSONEmitter &json, const SynthTrace &trace)
const override;
RecordType getType() const override {
return type;
}
};
struct GetOrSetPropertyRecord : public Record {
const ObjectID objID_;
const std::string propName_;
const TraceValue value_;
explicit GetOrSetPropertyRecord(
TimeSinceStart time,
ObjectID objID,
const std::string &propName,
TraceValue value)
: Record(time), objID_(objID), propName_(propName), value_(value) {}
bool operator==(const Record &that) const final;
std::vector<ObjectID> uses() const override {
return {objID_};
}
void toJSONInternal(::hermes::JSONEmitter &json, const SynthTrace &trace)
const override;
};
/// A GetPropertyRecord is an event where native code accesses the property
/// of a JS object.
struct GetPropertyRecord : public GetOrSetPropertyRecord {
static constexpr RecordType type{RecordType::GetProperty};
using GetOrSetPropertyRecord::GetOrSetPropertyRecord;
RecordType getType() const override {
return type;
}
std::vector<ObjectID> defs() const override {
auto defs = GetOrSetPropertyRecord::defs();
if (value_.isObject()) {
defs.push_back(decodeObject(value_));
}
return defs;
}
};
/// A SetPropertyRecord is an event where native code writes to the property
/// of a JS object.
struct SetPropertyRecord : public GetOrSetPropertyRecord {
static constexpr RecordType type{RecordType::SetProperty};
using GetOrSetPropertyRecord::GetOrSetPropertyRecord;
RecordType getType() const override {
return type;
}
std::vector<ObjectID> uses() const override {
auto uses = GetOrSetPropertyRecord::uses();
if (value_.isObject()) {
uses.push_back(decodeObject(value_));
}
return uses;
}
};
/// A HasPropertyRecord is an event where native code queries whether a
/// property exists on an object. (We don't care about the result because
/// it cannot influence the trace.)
struct HasPropertyRecord final : public Record {
static constexpr RecordType type{RecordType::HasProperty};
const ObjectID objID_;
const std::string propName_;
explicit HasPropertyRecord(
TimeSinceStart time,
ObjectID objID,
const std::string &propName)
: Record(time), objID_(objID), propName_(propName) {}
bool operator==(const Record &that) const final;
void toJSONInternal(::hermes::JSONEmitter &json, const SynthTrace &trace)
const override;
RecordType getType() const override {
return type;
}
std::vector<ObjectID> uses() const override {
return {objID_};
}
};
struct GetPropertyNamesRecord final : public Record {
static constexpr RecordType type{RecordType::GetPropertyNames};
const ObjectID objID_;
// Since getPropertyNames always returns an array, this can be an object id
// rather than a TraceValue.
const ObjectID propNamesID_;
explicit GetPropertyNamesRecord(
TimeSinceStart time,
ObjectID objID,
ObjectID propNamesID)
: Record(time), objID_(objID), propNamesID_(propNamesID) {}
bool operator==(const Record &that) const final;
void toJSONInternal(::hermes::JSONEmitter &json, const SynthTrace &trace)
const override;
RecordType getType() const override {
return type;
}
std::vector<ObjectID> defs() const override {
return {propNamesID_};
}
std::vector<ObjectID> uses() const override {
return {objID_};
}
};
/// A CreateArrayRecord is an event where a new array is created of a specific
/// length.
struct CreateArrayRecord final : public Record {
static constexpr RecordType type{RecordType::CreateArray};
const ObjectID objID_;
const size_t length_;
explicit CreateArrayRecord(
TimeSinceStart time,
ObjectID objID,
size_t length)
: Record(time), objID_(objID), length_(length) {}
bool operator==(const Record &that) const final;
void toJSONInternal(::hermes::JSONEmitter &json, const SynthTrace &trace)
const override;
RecordType getType() const override {
return type;
}
std::vector<ObjectID> defs() const override {
return {objID_};
}
};
struct ArrayReadOrWriteRecord : public Record {
const ObjectID objID_;
const size_t index_;
const TraceValue value_;
explicit ArrayReadOrWriteRecord(
TimeSinceStart time,
ObjectID objID,
size_t index,
TraceValue value)
: Record(time), objID_(objID), index_(index), value_(value) {}
bool operator==(const Record &that) const final;
void toJSONInternal(::hermes::JSONEmitter &json, const SynthTrace &trace)
const override;
std::vector<ObjectID> uses() const override {
return {objID_};
}
};
/// An ArrayReadRecord is an event where a value was read from an index
/// of an array.
/// It is modeled separately from GetProperty because it is more efficient to
/// read from a numeric index on an array than a string.
struct ArrayReadRecord final : public ArrayReadOrWriteRecord {
static constexpr RecordType type{RecordType::ArrayRead};
using ArrayReadOrWriteRecord::ArrayReadOrWriteRecord;
RecordType getType() const override {
return type;
}
std::vector<ObjectID> defs() const override {
auto defs = ArrayReadOrWriteRecord::defs();
if (value_.isObject()) {
defs.push_back(decodeObject(value_));
}
return defs;
}
};
/// An ArrayWriteRecord is an event where a value was written into an index
/// of an array.
struct ArrayWriteRecord final : public ArrayReadOrWriteRecord {
static constexpr RecordType type{RecordType::ArrayWrite};
using ArrayReadOrWriteRecord::ArrayReadOrWriteRecord;
RecordType getType() const override {
return type;
}
std::vector<ObjectID> uses() const override {
auto uses = ArrayReadOrWriteRecord::uses();
if (value_.isObject()) {
uses.push_back(decodeObject(value_));
}
return uses;
}
};
struct CallRecord : public Record {
/// The functionID_ is the id of the function JS object that is called from
/// JS.
const ObjectID functionID_;
const TraceValue thisArg_;
/// The arguments given to a call (excluding the this parameter),
/// already JSON stringified.
const std::vector<TraceValue> args_;
explicit CallRecord(
TimeSinceStart time,
ObjectID functionID,
TraceValue thisArg,
const std::vector<TraceValue> &args)
: Record(time),
functionID_(functionID),
thisArg_(thisArg),
args_(args) {}
bool operator==(const Record &that) const final;
void toJSONInternal(::hermes::JSONEmitter &json, const SynthTrace &trace)
const override;
std::vector<ObjectID> uses() const override {
// The function is used regardless of direction.
return {functionID_};
}
protected:
std::vector<ObjectID> getArgObjects() const {
std::vector<ObjectID> objs;
if (thisArg_.isObject()) {
objs.push_back(decodeObject(thisArg_));
}
for (const auto &arg : args_) {
if (arg.isObject()) {
objs.push_back(decodeObject(arg));
}
}
return objs;
}
};
/// A CallFromNativeRecord is an event where native code calls into a JS
/// function.
struct CallFromNativeRecord : public CallRecord {
static constexpr RecordType type{RecordType::CallFromNative};
using CallRecord::CallRecord;
RecordType getType() const override {
return type;
}
std::vector<ObjectID> uses() const override {
auto uses = CallRecord::uses();
auto objs = CallRecord::getArgObjects();
uses.insert(uses.end(), objs.begin(), objs.end());
return uses;
}
};
/// A ConstructFromNativeRecord is the same as \c CallFromNativeRecord, except
/// the function is called with the new operator.
struct ConstructFromNativeRecord final : public CallFromNativeRecord {
static constexpr RecordType type{RecordType::ConstructFromNative};
using CallFromNativeRecord::CallFromNativeRecord;
RecordType getType() const override {
return type;
}
};
/// A ReturnFromNativeRecord is an event where a native function returns to a
/// JS caller.
/// It pairs with \c CallToNativeRecord.
struct ReturnFromNativeRecord final : public Record, public ReturnMixin {
static constexpr RecordType type{RecordType::ReturnFromNative};
ReturnFromNativeRecord(TimeSinceStart time, TraceValue retVal)
: Record(time), ReturnMixin(retVal) {}
RecordType getType() const override {
return type;
}
std::vector<ObjectID> uses() const override {
auto uses = Record::uses();
if (retVal_.isObject()) {
uses.push_back(decodeObject(retVal_));
}
return uses;
}
bool operator==(const Record &that) const final;
void toJSONInternal(::hermes::JSONEmitter &json, const SynthTrace &trace)
const override;
};
/// A ReturnToNativeRecord is an event where a JS function returns to a native
/// caller.
/// It pairs with \c CallFromNativeRecord.
struct ReturnToNativeRecord final : public Record, public ReturnMixin {
static constexpr RecordType type{RecordType::ReturnToNative};
ReturnToNativeRecord(TimeSinceStart time, TraceValue retVal)
: Record(time), ReturnMixin(retVal) {}
RecordType getType() const override {
return type;
}
std::vector<ObjectID> defs() const override {
auto defs = Record::defs();
if (retVal_.isObject()) {
defs.push_back(decodeObject(retVal_));
}
return defs;
}
bool operator==(const Record &that) const final;
void toJSONInternal(::hermes::JSONEmitter &json, const SynthTrace &trace)
const override;
};
/// A CallToNativeRecord is an event where JS code calls into a natively
/// defined function.
struct CallToNativeRecord final : public CallRecord {
static constexpr RecordType type{RecordType::CallToNative};
using CallRecord::CallRecord;
RecordType getType() const override {
return type;
}
std::vector<ObjectID> defs() const override {
auto defs = CallRecord::defs();
auto objs = CallRecord::getArgObjects();
defs.insert(defs.end(), objs.begin(), objs.end());
return defs;
}
};
struct GetOrSetPropertyNativeRecord : public Record {
const ObjectID hostObjectID_;
const std::string propName_;
explicit GetOrSetPropertyNativeRecord(
TimeSinceStart time,
ObjectID hostObjectID,
std::string propName)
: Record(time), hostObjectID_(hostObjectID), propName_(propName) {}
void toJSONInternal(::hermes::JSONEmitter &json, const SynthTrace &trace)
const override;
std::vector<ObjectID> uses() const override {
return {hostObjectID_};
}
protected:
bool operator==(const Record &that) const override;
};
/// A GetPropertyNativeRecord is an event where JS tries to access a property
/// on a native object.
/// This needs to be modeled as a call with no arguments, since native code
/// can arbitrarily affect the JS heap during the accessor.
struct GetPropertyNativeRecord final : public GetOrSetPropertyNativeRecord {
static constexpr RecordType type{RecordType::GetPropertyNative};
using GetOrSetPropertyNativeRecord::GetOrSetPropertyNativeRecord;
RecordType getType() const override {
return type;
}
bool operator==(const Record &that) const final;
};
struct GetPropertyNativeReturnRecord final : public Record,
public ReturnMixin {
static constexpr RecordType type{RecordType::GetPropertyNativeReturn};
GetPropertyNativeReturnRecord(TimeSinceStart time, TraceValue retVal)
: Record(time), ReturnMixin(retVal) {}
RecordType getType() const override {
return type;
}
std::vector<ObjectID> uses() const override {
auto uses = Record::uses();
if (retVal_.isObject()) {
uses.push_back(decodeObject(retVal_));
}
return uses;
}
bool operator==(const Record &that) const final;
protected:
void toJSONInternal(::hermes::JSONEmitter &json, const SynthTrace &trace)
const override;
};
/// A SetPropertyNativeRecord is an event where JS code writes to the property
/// of a Native object.
/// This needs to be modeled as a call with one argument, since native code
/// can arbitrarily affect the JS heap during the accessor.
struct SetPropertyNativeRecord final : public GetOrSetPropertyNativeRecord {
static constexpr RecordType type{RecordType::SetPropertyNative};
TraceValue value_;
explicit SetPropertyNativeRecord(
TimeSinceStart time,
ObjectID hostObjectID,
std::string propName,
TraceValue value)
: GetOrSetPropertyNativeRecord(time, hostObjectID, propName),
value_(value) {}
bool operator==(const Record &that) const final;
void toJSONInternal(::hermes::JSONEmitter &json, const SynthTrace &trace)
const override;
RecordType getType() const override {
return type;
}
std::vector<ObjectID> defs() const override {
auto defs = GetOrSetPropertyNativeRecord::defs();
if (value_.isObject()) {
defs.push_back(decodeObject(value_));
}
return defs;
}
};
/// A SetPropertyNativeReturnRecord needs to record no extra information
struct SetPropertyNativeReturnRecord final : public Record {
static constexpr RecordType type{RecordType::SetPropertyNativeReturn};
using Record::Record;
RecordType getType() const override {
return type;
}
bool operator==(const Record &that) const final {
// Since there are no fields to compare, any two will always be the same.
return Record::operator==(that);
}
};
/// A GetNativePropertyNamesRecord records an event where JS asked for a list
/// of property names available on a host object. It records the object, and
/// the returned list of property names.
struct GetNativePropertyNamesRecord : public Record {
static constexpr RecordType type{RecordType::GetNativePropertyNames};
const ObjectID hostObjectID_;
explicit GetNativePropertyNamesRecord(
TimeSinceStart time,
ObjectID hostObjectID)
: Record(time), hostObjectID_(hostObjectID) {}
RecordType getType() const override {
return type;
}
void toJSONInternal(::hermes::JSONEmitter &json, const SynthTrace &trace)
const override;
std::vector<ObjectID> uses() const override {
return {hostObjectID_};
}
bool operator==(const Record &that) const override;
};
/// A GetNativePropertyNamesReturnRecord records what property names were
/// returned by the GetNativePropertyNames query.
struct GetNativePropertyNamesReturnRecord final : public Record {
static constexpr RecordType type{RecordType::GetNativePropertyNamesReturn};
const std::vector<std::string> propNames_;
explicit GetNativePropertyNamesReturnRecord(
TimeSinceStart time,
const std::vector<std::string> &propNames)
: Record(time), propNames_(propNames) {}
RecordType getType() const override {
return type;
}
void toJSONInternal(::hermes::JSONEmitter &json, const SynthTrace &trace)
const override;
bool operator==(const Record &that) const override;
};
/// Completes writing of the trace to the trace stream. If writing
/// to a file, disables further writing to the file, or accumulation
/// of data.
void flushAndDisable(const ::hermes::vm::MockedEnvironment &env);
};
llvm::raw_ostream &operator<<(
llvm::raw_ostream &os,
SynthTrace::RecordType type);
std::istream &operator>>(std::istream &is, SynthTrace::RecordType &type);
} // namespace tracing
} // namespace hermes
} // namespace facebook
#endif // HERMESVM_API_TRACE
#endif // HERMES_SYNTHTRACE_H

View File

@ -0,0 +1,43 @@
/*
* 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.
*/
#ifndef HERMES_SYNTHTRACEPARSER_H
#define HERMES_SYNTHTRACEPARSER_H
#ifdef HERMESVM_API_TRACE
#include <tuple>
#include "hermes/Public/RuntimeConfig.h"
#include "hermes/SynthTrace.h"
#include "hermes/VM/MockedEnvironment.h"
namespace facebook {
namespace hermes {
namespace tracing {
/// Parse a trace from a JSON string stored in a MemoryBuffer.
std::tuple<
SynthTrace,
::hermes::vm::RuntimeConfig,
::hermes::vm::MockedEnvironment>
parseSynthTrace(std::unique_ptr<llvm::MemoryBuffer> trace);
/// Parse a trace from a JSON string stored in the given file name.
std::tuple<
SynthTrace,
::hermes::vm::RuntimeConfig,
::hermes::vm::MockedEnvironment>
parseSynthTrace(const std::string &tracefile);
} // namespace tracing
} // namespace hermes
} // namespace facebook
#endif // HERMESVM_API_TRACE
#endif // HERMES_SYNTHTRACEPARSER_H

View File

@ -0,0 +1,275 @@
/*
* 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

View File

@ -0,0 +1,198 @@
/*
* 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.
*/
#ifndef HERMES_TRACINGRUNTIME_H
#define HERMES_TRACINGRUNTIME_H
#ifdef HERMESVM_API_TRACE
#include "SynthTrace.h"
#include <hermes/hermes.h>
#include <jsi/decorator.h>
#include "llvm/Support/raw_ostream.h"
namespace facebook {
namespace hermes {
namespace tracing {
class TracingRuntime : public jsi::RuntimeDecorator<jsi::Runtime> {
public:
using RD = RuntimeDecorator<jsi::Runtime>;
TracingRuntime(
std::unique_ptr<jsi::Runtime> runtime,
uint64_t globalID,
const ::hermes::vm::RuntimeConfig &conf,
std::unique_ptr<llvm::raw_ostream> traceStream);
virtual SynthTrace::ObjectID getUniqueID(const jsi::Object &o) = 0;
virtual void flushAndDisableTrace() = 0;
/// @name jsi::Runtime methods.
/// @{
jsi::Value evaluateJavaScript(
const std::shared_ptr<const jsi::Buffer> &buffer,
const std::string &sourceURL) override;
jsi::Object createObject() override;
jsi::Object createObject(std::shared_ptr<jsi::HostObject> ho) override;
jsi::Value getProperty(const jsi::Object &obj, const jsi::String &name)
override;
jsi::Value getProperty(const jsi::Object &obj, const jsi::PropNameID &name)
override;
bool hasProperty(const jsi::Object &obj, const jsi::String &name) override;
bool hasProperty(const jsi::Object &obj, const jsi::PropNameID &name)
override;
void setPropertyValue(
jsi::Object &obj,
const jsi::String &name,
const jsi::Value &value) override;
void setPropertyValue(
jsi::Object &obj,
const jsi::PropNameID &name,
const jsi::Value &value) override;
jsi::Array getPropertyNames(const jsi::Object &o) override;
jsi::WeakObject createWeakObject(const jsi::Object &o) override;
jsi::Value lockWeakObject(const jsi::WeakObject &wo) override;
jsi::Array createArray(size_t length) override;
size_t size(const jsi::Array &arr) override;
size_t size(const jsi::ArrayBuffer &buf) override;
uint8_t *data(const jsi::ArrayBuffer &buf) override;
jsi::Value getValueAtIndex(const jsi::Array &arr, size_t i) override;
void setValueAtIndexImpl(jsi::Array &arr, size_t i, const jsi::Value &value)
override;
jsi::Function createFunctionFromHostFunction(
const jsi::PropNameID &name,
unsigned int paramCount,
jsi::HostFunctionType func) override;
jsi::Value call(
const jsi::Function &func,
const jsi::Value &jsThis,
const jsi::Value *args,
size_t count) override;
jsi::Value callAsConstructor(
const jsi::Function &func,
const jsi::Value *args,
size_t count) override;
/// @}
void addMarker(const std::string &marker);
SynthTrace &trace() {
return trace_;
}
const SynthTrace &trace() const {
return trace_;
}
private:
SynthTrace::TraceValue toTraceValue(const jsi::Value &value);
std::vector<SynthTrace::TraceValue> argStringifyer(
const jsi::Value *args,
size_t count);
SynthTrace::TimeSinceStart getTimeSinceStart() const;
std::unique_ptr<jsi::Runtime> runtime_;
SynthTrace trace_;
const SynthTrace::TimePoint startTime_{std::chrono::steady_clock::now()};
};
// TracingRuntime is *almost* vm independent. This provides the
// vm-specific bits. And, it's not a HermesRuntime, but it holds one.
class TracingHermesRuntime final : public TracingRuntime {
public:
TracingHermesRuntime(
std::unique_ptr<HermesRuntime> runtime,
const ::hermes::vm::RuntimeConfig &runtimeConfig,
std::unique_ptr<llvm::raw_ostream> traceStream,
const std::string &traceFilename);
~TracingHermesRuntime();
SynthTrace::ObjectID getUniqueID(const jsi::Object &o) override {
return static_cast<SynthTrace::ObjectID>(hermesRuntime().getUniqueID(o));
}
void flushAndDisableTrace() override;
std::string flushAndDisableBridgeTrafficTrace() override;
jsi::Value evaluateJavaScript(
const std::shared_ptr<const jsi::Buffer> &buffer,
const std::string &sourceURL) override;
HermesRuntime &hermesRuntime() {
return static_cast<HermesRuntime &>(plain());
}
const HermesRuntime &hermesRuntime() const {
return static_cast<const HermesRuntime &>(plain());
}
private:
// Why do we have a private ctor executed from the public one,
// instead of just having a single public ctor which calls
// getUniqueID() to initialize the base class? This one weird trick
// is needed to avoid undefined behavior in that case. Otherwise,
// when calling the base class ctor, the order of evaluating the
// globalID value and the side effect of moving the runtime would be
// unspecified.
TracingHermesRuntime(
std::unique_ptr<HermesRuntime> &runtime,
uint64_t globalID,
const ::hermes::vm::RuntimeConfig &runtimeConfig,
std::unique_ptr<llvm::raw_ostream> traceStream,
const std::string &traceFilename);
void crashCallback(int fd);
const ::hermes::vm::RuntimeConfig conf_;
const std::string traceFilename_;
const llvm::Optional<::hermes::vm::CrashManager::CallbackKey>
crashCallbackKey_;
};
/// Creates and returns a HermesRuntime that traces JSI interactions.
/// If \p traceStream is non-null, writes the trace to \p traceStream.
/// If non-empty, \p traceFilename is the file to which \p traceStream writes.
/// The \p forReplay parameter indicates whether the runtime is being used
/// in trace replay. (Its behavior can differ slightly in that case.)
std::unique_ptr<TracingHermesRuntime> makeTracingHermesRuntime(
std::unique_ptr<HermesRuntime> hermesRuntime,
const ::hermes::vm::RuntimeConfig &runtimeConfig,
std::unique_ptr<llvm::raw_ostream> traceStream = nullptr,
const std::string &traceFilename = "",
bool forReplay = false);
} // namespace tracing
} // namespace hermes
} // namespace facebook
#endif // HERMESVM_API_TRACE
#endif // HERMES_TRACINGRUNTIME_H

View File

@ -0,0 +1,190 @@
/*
* 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.
*/
#ifndef HERMES_HERMES_H
#define HERMES_HERMES_H
#include <exception>
#include <list>
#include <memory>
#include <string>
#include <hermes/Public/RuntimeConfig.h>
#include <jsi/jsi.h>
struct HermesTestHelper;
namespace llvm {
class raw_ostream;
}
namespace hermes {
namespace vm {
struct MockedEnvironment;
} // namespace vm
} // namespace hermes
namespace facebook {
namespace jsi {
class ThreadSafeRuntime;
}
namespace hermes {
#ifdef HERMES_ENABLE_DEBUGGER
namespace debugger {
class Debugger;
}
#endif
class HermesRuntimeImpl;
/// Represents a Hermes JS runtime.
class HermesRuntime : public jsi::Runtime {
public:
static bool isHermesBytecode(const uint8_t *data, size_t len);
// (EXPERIMENTAL) Issues madvise calls for portions of the given
// bytecode file that will likely be used when loading the bytecode
// file and running its global function.
static void prefetchHermesBytecode(const uint8_t *data, size_t len);
// Returns whether the data is valid HBC with more extensive checks than
// isHermesBytecode and returns why it isn't in errorMessage (if nonnull)
// if not.
static bool hermesBytecodeSanityCheck(
const uint8_t *data,
size_t len,
std::string *errorMessage = nullptr);
static void setFatalHandler(void (*handler)(const std::string &));
// Assuming that \p data is valid HBC bytecode data, returns a pointer to then
// first element of the epilogue, data append to the end of the bytecode
// stream. Return pair contain ptr to data and header.
static std::pair<const uint8_t *, size_t> getBytecodeEpilogue(
const uint8_t *data,
size_t len);
/// Enable sampling profiler.
static void enableSamplingProfiler();
/// Disable the sampling profiler
static void disableSamplingProfiler();
/// Dump sampled stack trace to the given file name.
static void dumpSampledTraceToFile(const std::string &fileName);
/// Return the executed JavaScript function info.
/// Each function info is a 64bit integer with the module id encoded in
/// upper 32bit and function virtual offset in lower 32bit.
static std::vector<int64_t> getExecutedFunctions();
/// Enable code coverage profiler.
static void enableCodeCoverageProfiler();
/// Disable code coverage profiler.
static void disableCodeCoverageProfiler();
// The base class declares most of the interesting methods. This
// just declares new methods which are specific to HermesRuntime.
// The actual implementations of the pure virtual methods are
// provided by a class internal to the .cpp file, which is created
// by the factory.
/// Load a new segment into the Runtime.
/// The \param context must be a valid RequireContext retrieved from JS
/// using `require.context`.
void loadSegment(
std::unique_ptr<const jsi::Buffer> buffer,
const jsi::Value &context);
/// Gets a guaranteed unique id for an object, which is assigned at
/// allocation time and is static throughout that object's lifetime.
uint64_t getUniqueID(const jsi::Object &o) const;
/// Get a structure representing the enviroment-dependent behavior, so
/// it can be written into the trace for later replay.
const ::hermes::vm::MockedEnvironment &getMockedEnvironment() const;
/// Make the runtime read from \p env to replay its environment-dependent
/// behavior.
void setMockedEnvironment(const ::hermes::vm::MockedEnvironment &env);
/// Get IO tracking (aka HBC page access) info as a JSON string.
/// See hermes::vm::Runtime::getIOTrackingInfoJSON() for conditions
/// needed for there to be useful output.
std::string getIOTrackingInfoJSON();
#ifdef HERMESVM_PROFILER_BB
/// Write the trace to the given stream.
void dumpBasicBlockProfileTrace(llvm::raw_ostream &os) const;
#endif
#ifdef HERMESVM_PROFILER_OPCODE
/// Write the opcode stats to the given stream.
void dumpOpcodeStats(llvm::raw_ostream &os) const;
#endif
#ifdef HERMESVM_PROFILER_EXTERN
/// Dump map of profiler symbols to given file name.
void dumpProfilerSymbolsToFile(const std::string &fileName) const;
#endif
#ifdef HERMES_ENABLE_DEBUGGER
/// \return a reference to the Debugger for this Runtime.
debugger::Debugger &getDebugger();
struct DebugFlags {
bool lazy{false};
};
/// Evaluate the given code in an unoptimized form,
/// used for debugging.
void debugJavaScript(
const std::string &src,
const std::string &sourceURL,
const DebugFlags &debugFlags);
#endif
/// Register this runtime for sampling profiler.
void registerForProfiling();
/// Unregister this runtime for sampling profiler.
void unregisterForProfiling();
/// Register this runtime for execution time limit monitoring, with a time
/// limit of \p timeoutInMs milliseconds.
/// All JS compiled to bytecode via prepareJS, or evaluateJS, will support the
/// time limit monitoring. If JS prepared in other ways is executed, care
/// must be taken to ensure that it is compiled in a mode that supports the
/// monitoring (i.e., the emitted code contains async break checks).
void watchTimeLimit(uint32_t timeoutInMs);
/// Unregister this runtime for execution time limit monitoring.
void unwatchTimeLimit();
private:
// Only HermesRuntimeImpl can subclass this.
HermesRuntime() = default;
friend class HermesRuntimeImpl;
friend struct ::HermesTestHelper;
size_t rootsListLength() const;
// Do not add any members here. This ensures that there are no
// object size inconsistencies. All data should be in the impl
// class in the .cpp file.
};
std::unique_ptr<HermesRuntime> makeHermesRuntime(
const ::hermes::vm::RuntimeConfig &runtimeConfig =
::hermes::vm::RuntimeConfig());
std::unique_ptr<jsi::ThreadSafeRuntime> makeThreadSafeHermesRuntime(
const ::hermes::vm::RuntimeConfig &runtimeConfig =
::hermes::vm::RuntimeConfig());
} // namespace hermes
} // namespace facebook
#endif

View File

@ -0,0 +1,28 @@
/*
* 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.
*/
#ifndef HERMES_HERMES_TRACING_H
#define HERMES_HERMES_TRACING_H
#include <hermes/hermes.h>
namespace facebook {
namespace hermes {
/// Like the method above, except takes a file descriptor instead of
/// a stream. If the \p traceFileDescriptor argument is -1, do not write
/// the trace to a file.
std::unique_ptr<jsi::Runtime> makeTracingHermesRuntime(
std::unique_ptr<HermesRuntime> hermesRuntime,
const ::hermes::vm::RuntimeConfig &runtimeConfig,
int traceFileDescriptor,
const std::string &traceFilename);
} // namespace hermes
} // namespace facebook
#endif

View File

@ -0,0 +1,52 @@
/*
* 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.
*/
#ifndef HERMES_API_SYNTHTEST_TESTS_TESTFUNCTIONS
#define HERMES_API_SYNTHTEST_TESTS_TESTFUNCTIONS
#define FOREACH_TEST(F) \
F(callbacksCallJSFunction) \
F(dateAsFunction) \
F(dateAsNew) \
F(dateNow) \
F(getInstrumentedStats) \
F(getInstrumentedStatsAllowsEmpty) \
F(globalReturnObject) \
F(getPropertyNames) \
F(hostCallsJS) \
F(hostCallsJSCallsHost) \
F(hostCallsJSWithThis) \
F(hostFunctionCachesObject) \
F(hostFunctionCreatesObjects) \
F(hostFunctionMutatesGlobalObject) \
F(hostFunctionMutatesObject) \
F(hostFunctionNameAndParams) \
F(hostFunctionReturn) \
F(hostFunctionReturnArgument) \
F(hostGlobalObject) \
F(mathRandom) \
F(nativePropertyNames) \
F(nativeSetsConstant) \
F(parseGCConfig) \
F(surrogatePairString)
#define TEST_FUNC_FORWARD_DECL(name) \
const char *name##Trace(); \
const char *name##Source();
namespace facebook {
namespace hermes {
namespace synthtest {
// Forward decls for all of the functions used.
FOREACH_TEST(TEST_FUNC_FORWARD_DECL)
} // namespace synthtest
} // namespace hermes
} // namespace facebook
#endif