Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions NativeScript/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COMMON_FLAGS}")
# Arguments
set(TARGET_PLATFORM "macos" CACHE STRING "Target platform for the Objective-C bridge")
set(TARGET_ENGINE "v8" CACHE STRING "Target JS engine for the NativeScript runtime")
set(NS_GSD_BACKEND "auto" CACHE STRING "Generated signature dispatch backend: auto, v8, jsc, quickjs, hermes, napi, or none")
set(METADATA_SIZE 0 CACHE STRING "Size of embedded metadata in bytes")
set(BUILD_CLI_BINARY OFF CACHE BOOL "Build the NativeScript CLI binary")
set(BUILD_MACOS_NODE_API OFF CACHE BOOL "Build the NativeScript macOS Node API dylib")
set_property(CACHE NS_GSD_BACKEND PROPERTY STRINGS auto v8 jsc quickjs hermes napi none)

if (BUILD_MACOS_NODE_API)
set(BUILD_FRAMEWORK OFF)
Expand Down Expand Up @@ -132,6 +134,44 @@ message(STATUS "TARGET_ENGINE = ${TARGET_ENGINE}")
message(STATUS "ENABLE_JS_RUNTIME = ${ENABLE_JS_RUNTIME}")
message(STATUS "GENERIC_NAPI = ${GENERIC_NAPI}")

if(NS_GSD_BACKEND STREQUAL "auto")
if(TARGET_ENGINE_V8)
set(NS_EFFECTIVE_GSD_BACKEND "v8")
elseif(TARGET_ENGINE_JSC)
set(NS_EFFECTIVE_GSD_BACKEND "jsc")
elseif(TARGET_ENGINE_QUICKJS)
set(NS_EFFECTIVE_GSD_BACKEND "quickjs")
elseif(TARGET_ENGINE_HERMES)
set(NS_EFFECTIVE_GSD_BACKEND "hermes")
else()
set(NS_EFFECTIVE_GSD_BACKEND "napi")
endif()
elseif(NS_GSD_BACKEND STREQUAL "v8" OR
NS_GSD_BACKEND STREQUAL "jsc" OR
NS_GSD_BACKEND STREQUAL "quickjs" OR
NS_GSD_BACKEND STREQUAL "hermes" OR
NS_GSD_BACKEND STREQUAL "napi" OR
NS_GSD_BACKEND STREQUAL "none")
set(NS_EFFECTIVE_GSD_BACKEND "${NS_GSD_BACKEND}")
else()
message(FATAL_ERROR "Unknown NS_GSD_BACKEND: ${NS_GSD_BACKEND}")
endif()

if(NS_EFFECTIVE_GSD_BACKEND STREQUAL "v8" AND NOT TARGET_ENGINE_V8)
message(FATAL_ERROR "NS_GSD_BACKEND=v8 requires TARGET_ENGINE=v8")
endif()
if(NS_EFFECTIVE_GSD_BACKEND STREQUAL "jsc" AND NOT TARGET_ENGINE_JSC)
message(FATAL_ERROR "NS_GSD_BACKEND=jsc requires TARGET_ENGINE=jsc")
endif()
if(NS_EFFECTIVE_GSD_BACKEND STREQUAL "quickjs" AND NOT TARGET_ENGINE_QUICKJS)
message(FATAL_ERROR "NS_GSD_BACKEND=quickjs requires TARGET_ENGINE=quickjs")
endif()
if(NS_EFFECTIVE_GSD_BACKEND STREQUAL "hermes" AND NOT TARGET_ENGINE_HERMES)
message(FATAL_ERROR "NS_GSD_BACKEND=hermes requires TARGET_ENGINE=hermes")
endif()

message(STATUS "NS_GSD_BACKEND = ${NS_GSD_BACKEND} (${NS_EFFECTIVE_GSD_BACKEND})")

# Set up sources
include_directories(
./
Expand All @@ -158,6 +198,7 @@ set(SOURCE_FILES
ffi/Variable.mm
ffi/Object.mm
ffi/CFunction.mm
ffi/EngineDirectCall.mm
ffi/Interop.mm
ffi/InlineFunctions.mm
ffi/ClassBuilder.mm
Expand Down Expand Up @@ -207,6 +248,7 @@ if(ENABLE_JS_RUNTIME)
napi/v8/v8-module-loader.cpp
napi/v8/jsr.cpp
napi/v8/SimpleAllocator.cpp
ffi/V8FastNativeApi.mm
)

elseif(TARGET_ENGINE_HERMES)
Expand All @@ -231,6 +273,7 @@ if(ENABLE_JS_RUNTIME)
set(SOURCE_FILES
${SOURCE_FILES}
napi/hermes/jsr.cpp
ffi/HermesFastNativeApi.mm
)

elseif(TARGET_ENGINE_QUICKJS)
Expand Down Expand Up @@ -263,6 +306,7 @@ if(ENABLE_JS_RUNTIME)
# napi
napi/quickjs/quickjs-api.c
napi/quickjs/jsr.cpp
ffi/QuickJSFastNativeApi.mm
)

elseif(TARGET_ENGINE_JSC)
Expand All @@ -275,6 +319,7 @@ if(ENABLE_JS_RUNTIME)
set(SOURCE_FILES ${SOURCE_FILES}
napi/jsc/jsc-api.cpp
napi/jsc/jsr.cpp
ffi/JSCFastNativeApi.mm
)
endif()
else()
Expand Down Expand Up @@ -345,6 +390,38 @@ if(TARGET_ENGINE_V8 AND TARGET_PLATFORM_IOS)
)
endif()

if(ENABLE_JS_RUNTIME)
target_compile_definitions(${NAME} PRIVATE ENABLE_JS_RUNTIME)
endif()

if(TARGET_PLATFORM_MACOS)
target_compile_definitions(${NAME} PRIVATE TARGET_PLATFORM_MACOS)
endif()

if(TARGET_ENGINE_HERMES)
target_compile_definitions(${NAME} PRIVATE TARGET_ENGINE_HERMES)
elseif(TARGET_ENGINE_V8)
target_compile_definitions(${NAME} PRIVATE TARGET_ENGINE_V8)
elseif(TARGET_ENGINE_QUICKJS)
target_compile_definitions(${NAME} PRIVATE TARGET_ENGINE_QUICKJS)
elseif(TARGET_ENGINE_JSC)
target_compile_definitions(${NAME} PRIVATE TARGET_ENGINE_JSC)
endif()

if(NS_EFFECTIVE_GSD_BACKEND STREQUAL "v8")
target_compile_definitions(${NAME} PRIVATE NS_GSD_BACKEND_V8=1 NS_GSD_BACKEND_JSC=0 NS_GSD_BACKEND_QUICKJS=0 NS_GSD_BACKEND_HERMES=0 NS_GSD_BACKEND_NAPI=0)
elseif(NS_EFFECTIVE_GSD_BACKEND STREQUAL "jsc")
target_compile_definitions(${NAME} PRIVATE NS_GSD_BACKEND_V8=0 NS_GSD_BACKEND_JSC=1 NS_GSD_BACKEND_QUICKJS=0 NS_GSD_BACKEND_HERMES=0 NS_GSD_BACKEND_NAPI=0)
elseif(NS_EFFECTIVE_GSD_BACKEND STREQUAL "quickjs")
target_compile_definitions(${NAME} PRIVATE NS_GSD_BACKEND_V8=0 NS_GSD_BACKEND_JSC=0 NS_GSD_BACKEND_QUICKJS=1 NS_GSD_BACKEND_HERMES=0 NS_GSD_BACKEND_NAPI=0)
elseif(NS_EFFECTIVE_GSD_BACKEND STREQUAL "hermes")
target_compile_definitions(${NAME} PRIVATE NS_GSD_BACKEND_V8=0 NS_GSD_BACKEND_JSC=0 NS_GSD_BACKEND_QUICKJS=0 NS_GSD_BACKEND_HERMES=1 NS_GSD_BACKEND_NAPI=0)
elseif(NS_EFFECTIVE_GSD_BACKEND STREQUAL "napi")
target_compile_definitions(${NAME} PRIVATE NS_GSD_BACKEND_V8=0 NS_GSD_BACKEND_JSC=0 NS_GSD_BACKEND_QUICKJS=0 NS_GSD_BACKEND_HERMES=0 NS_GSD_BACKEND_NAPI=1)
else()
target_compile_definitions(${NAME} PRIVATE NS_GSD_BACKEND_V8=0 NS_GSD_BACKEND_JSC=0 NS_GSD_BACKEND_QUICKJS=0 NS_GSD_BACKEND_HERMES=0 NS_GSD_BACKEND_NAPI=0)
endif()

set(FRAMEWORK_VERSION_VALUE "${VERSION}")
if(TARGET_PLATFORM_MACOS)
# macOS framework consumers (including Xcode's copy/sign phases) expect
Expand Down
5 changes: 5 additions & 0 deletions NativeScript/ffi/Block.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#ifndef BLOCK_H
#define BLOCK_H

#include <cstdint>
#include <cstdlib>

#include "Cif.h"
Expand All @@ -15,6 +16,10 @@ class FunctionPointer {
metagen::MDSectionOffset offset;
Cif* cif;
bool ownsCif = false;
bool dispatchLookupCached = false;
uint64_t dispatchLookupSignatureHash = 0;
uint64_t dispatchId = 0;
void* preparedInvoker = nullptr;

static napi_value wrap(napi_env env, void* function,
metagen::MDSectionOffset offset, bool isBlock);
Expand Down
62 changes: 54 additions & 8 deletions NativeScript/ffi/Block.mm
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <unordered_map>
#include "Interop.h"
#include "ObjCBridge.h"
#include "SignatureDispatch.h"
#include "js_native_api.h"
#include "js_native_api_types.h"
#include "node_api_util.h"
Expand Down Expand Up @@ -65,8 +66,32 @@ inline bool removeCachedBlockJsFunctionEntry(void* blockPtr, BlockJsFunctionEntr
}

inline void deleteBlockReferenceOnOwningLoop(const BlockJsFunctionEntry& entry) {
nativescript::DeleteReferenceOnOwningThread(entry.env, entry.bridgeState,
entry.bridgeStateToken, entry.ref);
nativescript::DeleteReferenceOnOwningThread(entry.env, entry.bridgeState, entry.bridgeStateToken,
entry.ref);
}

inline nativescript::BlockPreparedInvoker ensureFunctionPointerPreparedInvoker(
nativescript::FunctionPointer* ref, nativescript::SignatureCallKind kind) {
if (ref == nullptr || ref->cif == nullptr || ref->cif->signatureHash == 0) {
return nullptr;
}

if (!ref->dispatchLookupCached ||
ref->dispatchLookupSignatureHash != ref->cif->signatureHash) {
ref->dispatchLookupSignatureHash = ref->cif->signatureHash;
ref->dispatchId =
nativescript::composeSignatureDispatchId(ref->cif->signatureHash, kind, 0);
if (kind == nativescript::SignatureCallKind::BlockInvoke) {
ref->preparedInvoker =
reinterpret_cast<void*>(nativescript::lookupBlockPreparedInvoker(ref->dispatchId));
} else {
ref->preparedInvoker =
reinterpret_cast<void*>(nativescript::lookupCFunctionPreparedInvoker(ref->dispatchId));
}
ref->dispatchLookupCached = true;
}

return reinterpret_cast<nativescript::BlockPreparedInvoker>(ref->preparedInvoker);
}

void block_copy(void* dest, void* src) {
Expand Down Expand Up @@ -144,8 +169,7 @@ inline void cacheBlockJsFunction(napi_env env, void* blockPtr, napi_value jsFunc
entry.ref = nativescript::make_ref(env, jsFunction, 0);
entry.env = env;
entry.bridgeState = nativescript::ObjCBridgeState::InstanceData(env);
entry.bridgeStateToken =
entry.bridgeState != nullptr ? entry.bridgeState->lifetimeToken : 0;
entry.bridgeStateToken = entry.bridgeState != nullptr ? entry.bridgeState->lifetimeToken : 0;
entry.jsThreadId = closure != nullptr ? closure->jsThreadId : std::this_thread::get_id();
entry.jsRunLoop = closure != nullptr ? closure->jsRunLoop : CFRunLoopGetCurrent();
g_blockToJsFunction[blockPtr] = entry;
Expand Down Expand Up @@ -174,7 +198,7 @@ void block_finalize_now(napi_env env, void* data, void* hint) {

free(block);
}

void finalizeFunctionPointerNow(napi_env env, void* finalize_data, void* finalize_hint) {
auto ref = static_cast<nativescript::FunctionPointer*>(finalize_data);
if (ref == nullptr) {
Expand Down Expand Up @@ -259,14 +283,23 @@ bool isObjCBlockObject(id obj) {
return false;
}

static thread_local std::unordered_map<Class, bool> blockClassCache;
auto cached = blockClassCache.find(cls);
if (cached != blockClassCache.end()) {
return cached->second;
}

const char* className = class_getName(cls);
if (className == nullptr) {
blockClassCache.emplace(cls, false);
return false;
}

// Runtime block classes are typically internal names like
// __NSGlobalBlock__, __NSMallocBlock__, __NSStackBlock__.
return className[0] == '_' && className[1] == '_' && strstr(className, "Block") != nullptr;
bool isBlock = className[0] == '_' && className[1] == '_' && strstr(className, "Block") != nullptr;
blockClassCache.emplace(cls, isBlock);
return isBlock;
}

const char* getObjCBlockSignature(void* blockPtr) {
Expand Down Expand Up @@ -444,7 +477,13 @@ bool isObjCBlockObject(id obj) {
}
}

ffi_call(&cif->cif, FFI_FN(ref->function), rvalue, avalues);
auto preparedInvoker =
ensureFunctionPointerPreparedInvoker(ref, SignatureCallKind::CFunction);
if (preparedInvoker != nullptr) {
preparedInvoker(ref->function, avalues, rvalue);
} else {
ffi_call(&cif->cif, FFI_FN(ref->function), rvalue, avalues);
}

if (shouldFreeAny) {
for (unsigned int i = 0; i < cif->argc; i++) {
Expand Down Expand Up @@ -484,7 +523,14 @@ bool isObjCBlockObject(id obj) {
}
}

ffi_call(&cif->cif, FFI_FN(block->invoke), rvalue, avalues);
BlockPreparedInvoker preparedInvoker =
ensureFunctionPointerPreparedInvoker(ref, SignatureCallKind::BlockInvoke);

if (preparedInvoker != nullptr) {
preparedInvoker(block->invoke, avalues, rvalue);
} else {
ffi_call(&cif->cif, FFI_FN(block->invoke), rvalue, avalues);
}

if (shouldFreeAny) {
for (unsigned int i = 0; i < cif->argc; i++) {
Expand Down
9 changes: 9 additions & 0 deletions NativeScript/ffi/CFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,34 @@
#define C_FUNCTION_H

#include <cstdint>

#include "Cif.h"

namespace nativescript {

class ObjCBridgeState;

class CFunction {
public:
static napi_value jsCall(napi_env env, napi_callback_info cbinfo);
static napi_value jsCallDirect(napi_env env, MDSectionOffset offset,
size_t actualArgc,
const napi_value* callArgs);

CFunction(void* fnptr) : fnptr(fnptr) {}
~CFunction();

void* fnptr;
ObjCBridgeState* bridgeState = nullptr;
Cif* cif = nullptr;
uint8_t dispatchFlags = 0;
bool dispatchLookupCached = false;
uint64_t dispatchLookupSignatureHash = 0;
uint64_t dispatchId = 0;
void* preparedInvoker = nullptr;
void* napiInvoker = nullptr;
void* engineDirectInvoker = nullptr;
void* v8Invoker = nullptr;
};

} // namespace nativescript
Expand Down
Loading
Loading