Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DRAFT] Add Node-API to Hermes #1377

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions API/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.

add_subdirectory(node-api)
add_subdirectory(hermes)
add_subdirectory(hermes_abi)
add_subdirectory(hermes_sandbox)
6 changes: 4 additions & 2 deletions API/hermes/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ add_subdirectory(cdp)
# against the internal functionality they need.
set(api_sources
hermes.cpp
hermes_napi.cpp
MurmurHash.cpp
DebuggerAPI.cpp
AsyncDebuggerAPI.cpp
RuntimeTaskRunner.cpp
Expand Down Expand Up @@ -102,7 +104,7 @@ add_hermes_library(traceInterpreter TraceInterpreter.cpp
LINK_LIBS libhermes hermesInstrumentation synthTrace synthTraceParser)

add_library(libhermes ${api_sources})
target_link_libraries(libhermes PUBLIC jsi PRIVATE hermesVMRuntime ${INSPECTOR_DEPS})
target_link_libraries(libhermes PUBLIC jsi hermesNapi PRIVATE hermesVMRuntime ${INSPECTOR_DEPS})
target_link_options(libhermes PRIVATE ${HERMES_EXTRA_LINKER_FLAGS})

# Export the required header directory
Expand All @@ -113,7 +115,7 @@ set_target_properties(libhermes PROPERTIES OUTPUT_NAME hermes)

# Create a lean version of libhermes in the same way.
add_library(libhermes_lean ${api_sources})
target_link_libraries(libhermes_lean PUBLIC jsi PRIVATE hermesVMRuntimeLean ${INSPECTOR_DEPS})
target_link_libraries(libhermes_lean PUBLIC jsi hermesNapi PRIVATE hermesVMRuntimeLean ${INSPECTOR_DEPS})
target_link_options(libhermes_lean PRIVATE ${HERMES_EXTRA_LINKER_FLAGS})
target_include_directories(libhermes_lean PUBLIC .. ../../public ${HERMES_JSI_DIR})
set_target_properties(libhermes_lean PROPERTIES OUTPUT_NAME hermes_lean)
Expand Down
170 changes: 170 additions & 0 deletions API/hermes/MurmurHash.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
// This code was adapted from https://github.com/aappleby/smhasher/blob/master/src/MurmurHash3.h
//-----------------------------------------------------------------------------
// MurmurHash3 was written by Austin Appleby, and is placed in the public
// domain. The author hereby disclaims copyright to this source code.
#include "MurmurHash.h"
#include <stdlib.h>

#if defined(_MSC_VER)

#define FORCE_INLINE __forceinline
#define ROTL64(x, y) _rotl64(x, y)
#define BIG_CONSTANT(x) (x)

#else // defined(_MSC_VER)

#define FORCE_INLINE inline __attribute__((always_inline))
inline uint64_t rotl64(uint64_t x, int8_t r) {
return (x << r) | (x >> (64 - r));
}
#define ROTL64(x, y) rotl64(x, y)
#define BIG_CONSTANT(x) (x##LLU)

#endif // !defined(_MSC_VER)

using namespace std;

FORCE_INLINE uint64_t fmix64(uint64_t k) {
k ^= k >> 33;
k *= BIG_CONSTANT(0xff51afd7ed558ccd);
k ^= k >> 33;
k *= BIG_CONSTANT(0xc4ceb9fe1a85ec53);
k ^= k >> 33;

return k;
}

bool isAscii(uint64_t k) {
return (k & 0x8080808080808080) == 0ull;
}

bool MurmurHash3_x64_128(const void *key, const int len, const uint32_t seed, void *out) {
const uint8_t *data = (const uint8_t *)key;
const int nblocks = len / 16;

uint64_t h1 = seed;
uint64_t h2 = seed;

const uint64_t c1 = BIG_CONSTANT(0x87c37b91114253d5);
const uint64_t c2 = BIG_CONSTANT(0x4cf5ad432745937f);

//----------
// body

const uint64_t *blocks = (const uint64_t *)(data);

bool isAsciiString{true};
for (int i = 0; i < nblocks; i++) {
uint64_t k1 = blocks[i * 2 + 0];
uint64_t k2 = blocks[i * 2 + 1];

isAsciiString &= isAscii(k1) && isAscii(k2);

k1 *= c1;
k1 = ROTL64(k1, 31);
k1 *= c2;
h1 ^= k1;

h1 = ROTL64(h1, 27);
h1 += h2;
h1 = h1 * 5 + 0x52dce729;

k2 *= c2;
k2 = ROTL64(k2, 33);
k2 *= c1;
h2 ^= k2;

h2 = ROTL64(h2, 31);
h2 += h1;
h2 = h2 * 5 + 0x38495ab5;
}

//----------
// tail

const uint8_t *tail = (const uint8_t *)(data + nblocks * 16);

for (auto i = 0; i < len % 16; i++) {
if (tail[i] > 127) {
isAsciiString = false;
break;
}
}

uint64_t k1 = 0;
uint64_t k2 = 0;

switch (len & 15) {
case 15:
k2 ^= ((uint64_t)tail[14]) << 48;
case 14:
k2 ^= ((uint64_t)tail[13]) << 40;
case 13:
k2 ^= ((uint64_t)tail[12]) << 32;
case 12:
k2 ^= ((uint64_t)tail[11]) << 24;
case 11:
k2 ^= ((uint64_t)tail[10]) << 16;
case 10:
k2 ^= ((uint64_t)tail[9]) << 8;
case 9:
k2 ^= ((uint64_t)tail[8]) << 0;
k2 *= c2;
k2 = ROTL64(k2, 33);
k2 *= c1;
h2 ^= k2;

case 8:
k1 ^= ((uint64_t)tail[7]) << 56;
case 7:
k1 ^= ((uint64_t)tail[6]) << 48;
case 6:
k1 ^= ((uint64_t)tail[5]) << 40;
case 5:
k1 ^= ((uint64_t)tail[4]) << 32;
case 4:
k1 ^= ((uint64_t)tail[3]) << 24;
case 3:
k1 ^= ((uint64_t)tail[2]) << 16;
case 2:
k1 ^= ((uint64_t)tail[1]) << 8;
case 1:
k1 ^= ((uint64_t)tail[0]) << 0;
k1 *= c1;
k1 = ROTL64(k1, 31);
k1 *= c2;
h1 ^= k1;
};

//----------
// finalization

h1 ^= len;
h2 ^= len;

h1 += h2;
h2 += h1;

h1 = fmix64(h1);
h2 = fmix64(h2);

h1 += h2;
h2 += h1;

((uint64_t *)out)[0] = h1;
((uint64_t *)out)[1] = h2;

return isAsciiString;
}

bool murmurhash(const uint8_t *key, size_t length, uint64_t &hash) {
uint64_t hashes[2];

bool isAscii = MurmurHash3_x64_128(key, length, 31, &hashes);

hash = hashes[0];

return isAscii;
}
10 changes: 10 additions & 0 deletions API/hermes/MurmurHash.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once

#include <cstddef>
#include <cstdint>

// Computes the hash of key using MurmurHash3 algorithm, the value is planced in the "hash" output parameter
// The function returns whether or not key is comprised of only ASCII characters (<=127)
bool murmurhash(const uint8_t *key, size_t length, uint64_t &hash);
79 changes: 79 additions & 0 deletions API/hermes/ScriptStore.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once

#include <jsi/jsi.h>
#include <memory>

namespace facebook {
namespace jsi {

// Integer type as it's persist friendly.
using ScriptVersion_t = uint64_t; // It should be std::optional<uint64_t> once we have c++17 available everywhere. Until
// then, 0 implies versioning not available.
using JSRuntimeVersion_t = uint64_t; // 0 implies version can't be computed. We assert whenever that happens.

struct VersionedBuffer {
std::shared_ptr<const facebook::jsi::Buffer> buffer;
ScriptVersion_t version;
};

struct ScriptSignature {
std::string url;
ScriptVersion_t version;
};

struct JSRuntimeSignature {
std::string runtimeName; // e.g. Chakra, V8
JSRuntimeVersion_t version;
};

// Most JSI::Runtime implementation offer some form of prepared JavaScript which offers better performance
// characteristics when loading comparing to plain JavaScript. Embedders can provide an instance of this interface
// (through JSI::Runtime implementation's factory method), to enable persistance of the prepared script and retrieval on
// subsequent evaluation of a script.
struct PreparedScriptStore {
virtual ~PreparedScriptStore() = default;

// Try to retrieve the prepared javascript for a given combination of script & runtime.
// scriptSignature : Javascript url and version
// RuntimeSignature : Javascript engine type and version
// prepareTag : Custom tag to uniquely identify JS engine specific preparation schemes. It is usually useful while
// experimentation and can be null. It is possible that no prepared script is available for a given script & runtime
// signature. This method should null if so
virtual std::shared_ptr<const facebook::jsi::Buffer> tryGetPreparedScript(
const ScriptSignature &scriptSignature,
const JSRuntimeSignature &runtimeSignature,
const char *prepareTag // Optional tag. For e.g. eagerly evaluated vs lazy cache.
) noexcept = 0;

// Persist the prepared javascript for a given combination of script & runtime.
// scriptSignature : Javascript url and version
// RuntimeSignature : Javascript engine type and version
// prepareTag : Custom tag to uniquely identify JS engine specific preparation schemes. It is usually useful while
// experimentation and can be null. It is possible that no prepared script is available for a given script & runtime
// signature. This method should null if so Any failure in persistance should be identified during the subsequent
// retrieval through the integrity mechanism which must be put into the storage.
virtual void persistPreparedScript(
std::shared_ptr<const facebook::jsi::Buffer> preparedScript,
const ScriptSignature &scriptMetadata,
const JSRuntimeSignature &runtimeMetadata,
const char *prepareTag // Optional tag. For e.g. eagerly evaluated vs lazy cache.
) noexcept = 0;
};

// JSI::Runtime implementation must be provided an instance on this interface to enable version sensitive capabilities
// such as usage of pre-prepared javascript script. Alternatively, this entity can be used to directly provide the
// Javascript buffer and rich metadata to the JSI::Runtime instance.
struct ScriptStore {
virtual ~ScriptStore() = default;

// Return the Javascript buffer and version corresponding to a given url.
virtual VersionedBuffer getVersionedScript(const std::string &url) noexcept = 0;

// Return the version of the Javascript buffer corresponding to a given url.
virtual ScriptVersion_t getScriptVersion(const std::string &url) noexcept = 0;
};

} // namespace jsi
} // namespace facebook
85 changes: 85 additions & 0 deletions API/hermes/hermes_api.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

#ifndef HERMES_HERMES_API_H
#define HERMES_HERMES_API_H

#include "js_runtime_api.h"

EXTERN_C_START

typedef struct hermes_local_connection_s *hermes_local_connection;
typedef struct hermes_remote_connection_s *hermes_remote_connection;

//=============================================================================
// jsr_runtime
//=============================================================================

JSR_API hermes_dump_crash_data(jsr_runtime runtime, int32_t fd);
JSR_API hermes_sampling_profiler_enable();
JSR_API hermes_sampling_profiler_disable();
JSR_API hermes_sampling_profiler_add(jsr_runtime runtime);
JSR_API hermes_sampling_profiler_remove(jsr_runtime runtime);
JSR_API hermes_sampling_profiler_dump_to_file(const char *filename);

//=============================================================================
// jsr_config
//=============================================================================

JSR_API hermes_config_enable_default_crash_handler(
jsr_config config,
bool value);

//=============================================================================
// Setting inspector singleton
//=============================================================================

typedef int32_t(NAPI_CDECL *hermes_inspector_add_page_cb)(
const char *title,
const char *vm,
void *connectFunc);

typedef void(NAPI_CDECL *hermes_inspector_remove_page_cb)(int32_t page_id);

JSR_API hermes_set_inspector(
hermes_inspector_add_page_cb add_page_cb,
hermes_inspector_remove_page_cb remove_page_cb);

//=============================================================================
// Local and remote inspector connections.
// Local is defined in Hermes VM, Remote is defined by inspector outside of VM.
//=============================================================================

typedef void(NAPI_CDECL *hermes_remote_connection_send_message_cb)(
hermes_remote_connection remote_connection,
const char *message);

typedef void(NAPI_CDECL *hermes_remote_connection_disconnect_cb)(
hermes_remote_connection remote_connection);

JSR_API hermes_create_local_connection(
void *connect_func,
hermes_remote_connection remote_connection,
hermes_remote_connection_send_message_cb on_send_message_cb,
hermes_remote_connection_disconnect_cb on_disconnect_cb,
jsr_data_delete_cb on_delete_cb,
void *deleter_data,
hermes_local_connection *local_connection);

JSR_API hermes_delete_local_connection(
hermes_local_connection local_connection);

JSR_API hermes_local_connection_send_message(
hermes_local_connection local_connection,
const char *message);

JSR_API hermes_local_connection_disconnect(
hermes_local_connection local_connection);

EXTERN_C_END

#endif // !HERMES_HERMES_API_H