Skip to content

Commit ce7407e

Browse files
committed
feat: expand generic NativeScript runtime parity
1 parent cf6c03b commit ce7407e

45 files changed

Lines changed: 5365 additions & 1539 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

NativeScript/ffi/jsc/NativeApiJSCSelectorGroups.mm

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -173,11 +173,11 @@ NativeApiReturnStorage returnStorage(
173173
#if defined(__x86_64__)
174174
bool isStret = signature.returnType.ffiType->size > 16 &&
175175
signature.returnType.ffiType->type == FFI_TYPE_STRUCT;
176-
void* target = dispatchSuperClass != Nil
177-
? (isStret ? FFI_FN(objc_msgSendSuper_stret)
178-
: FFI_FN(objc_msgSendSuper))
179-
: (isStret ? FFI_FN(objc_msgSend_stret)
180-
: FFI_FN(objc_msgSend));
176+
void (*target)(void) =
177+
dispatchSuperClass != Nil
178+
? (isStret ? FFI_FN(objc_msgSendSuper_stret)
179+
: FFI_FN(objc_msgSendSuper))
180+
: (isStret ? FFI_FN(objc_msgSend_stret) : FFI_FN(objc_msgSend));
181181
ffi_call(const_cast<ffi_cif*>(&signature.cif), target,
182182
returnStorage.data(), values.data());
183183
#else
@@ -456,4 +456,3 @@ Function CreateNativeApiBoundSelectorGroupFunction(
456456
receiverHostObject != nullptr ? receiverHostObject->lifetimeState()
457457
: nullptr);
458458
}
459-

NativeScript/ffi/napi/ObjCBridge.mm

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include <mach-o/getsect.h>
2727
#include <objc/runtime.h>
2828
#include <atomic>
29+
#include <cstdlib>
2930
#include <cstring>
3031
#include <initializer_list>
3132
#include <mutex>
@@ -46,6 +47,18 @@ const unsigned char __attribute__((section("__objc_metadata,__objc_metadata")))
4647
std::atomic<uint64_t> gNextBridgeStateToken{1};
4748
constexpr const char* kNativePointerProperty = "__ns_native_ptr";
4849

50+
bool envFlagEnabled(const char* name) {
51+
const char* value = std::getenv(name);
52+
if (value == nullptr || value[0] == '\0') {
53+
return false;
54+
}
55+
56+
return std::strcmp(value, "0") != 0 && std::strcmp(value, "false") != 0 &&
57+
std::strcmp(value, "FALSE") != 0 && std::strcmp(value, "False") != 0 &&
58+
std::strcmp(value, "off") != 0 && std::strcmp(value, "OFF") != 0 &&
59+
std::strcmp(value, "no") != 0 && std::strcmp(value, "NO") != 0;
60+
}
61+
4962
inline void deleteReferenceNow(napi_env env, napi_ref ref, bool unrefFirst) {
5063
if (env == nullptr || ref == nullptr) {
5164
return;
@@ -958,7 +971,7 @@ void registerLegacyCompatGlobals(napi_env env, napi_value global, ObjCBridgeStat
958971

959972
napi_value result = object;
960973
const bool nativeIsArray = [nativeObject isKindOfClass:NSArray.class];
961-
bool shouldProxyArray = nativeIsArray;
974+
bool shouldProxyArray = nativeIsArray && !envFlagEnabled("NS_DISABLE_NAPI_ARRAY_PROXY");
962975
if (shouldProxyArray) {
963976
napi_value factory = get_ref_value(env, createNativeProxy);
964977
napi_value transferOwnershipFunc = get_ref_value(env, this->transferOwnershipToNative);

NativeScript/ffi/quickjs/NativeApiQuickJSSelectorGroups.mm

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -173,11 +173,11 @@ NativeApiReturnStorage returnStorage(
173173
#if defined(__x86_64__)
174174
bool isStret = signature.returnType.ffiType->size > 16 &&
175175
signature.returnType.ffiType->type == FFI_TYPE_STRUCT;
176-
void* target = dispatchSuperClass != Nil
177-
? (isStret ? FFI_FN(objc_msgSendSuper_stret)
178-
: FFI_FN(objc_msgSendSuper))
179-
: (isStret ? FFI_FN(objc_msgSend_stret)
180-
: FFI_FN(objc_msgSend));
176+
void (*target)(void) =
177+
dispatchSuperClass != Nil
178+
? (isStret ? FFI_FN(objc_msgSendSuper_stret)
179+
: FFI_FN(objc_msgSendSuper))
180+
: (isStret ? FFI_FN(objc_msgSend_stret) : FFI_FN(objc_msgSend));
181181
ffi_call(const_cast<ffi_cif*>(&signature.cif), target,
182182
returnStorage.data(), values.data());
183183
#else
@@ -462,4 +462,3 @@ Function CreateNativeApiBoundSelectorGroupFunction(
462462
receiverHostObject != nullptr ? receiverHostObject->lifetimeState()
463463
: nullptr);
464464
}
465-

NativeScript/ffi/shared/NativeApiBackendConfig.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ struct NativeApiBackendConfig {
2020
std::shared_ptr<NativeApiBackendScheduler> scheduler = nullptr;
2121
std::function<void(std::function<void()>)> nativeInvocationInvoker = nullptr;
2222
std::function<void(std::function<void()>)> nativeCallbackInvoker = nullptr;
23+
std::function<void(std::function<void()>)> runtimeCallbackInvoker = nullptr;
2324
std::function<void(std::function<void()>)> jsThreadCallbackInvoker = nullptr;
2425
std::function<void(std::function<void()>)> jsThreadAsyncCallbackInvoker = nullptr;
2526
bool invokeCallbacksOnNativeCallerThread = false;

NativeScript/ffi/shared/bridge/Callbacks.mm

Lines changed: 136 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ explicit NativeApiRuntimeScope(Runtime&) {}
4141
enum class NativeApiCallbackThreadPolicy {
4242
Default,
4343
JS,
44+
Runtime,
4445
};
4546

4647
NativeApiCallbackThreadPolicy readEngineCallbackThreadPolicy(
@@ -58,6 +59,9 @@ NativeApiCallbackThreadPolicy readEngineCallbackThreadPolicy(
5859
if (policy == "js") {
5960
return NativeApiCallbackThreadPolicy::JS;
6061
}
62+
if (policy == "runtime" || policy == "worklet") {
63+
return NativeApiCallbackThreadPolicy::Runtime;
64+
}
6165
} catch (const std::exception&) {
6266
}
6367
return NativeApiCallbackThreadPolicy::Default;
@@ -579,6 +583,7 @@ void invoke(void* ret, void* args[]) {
579583
std::string error;
580584
auto call = [&]() { invokeOnCurrentThread(ret, args, &error); };
581585
const auto& nativeCallbackInvoker = bridge_->nativeCallbackInvoker();
586+
const auto& runtimeCallbackInvoker = bridge_->runtimeCallbackInvoker();
582587
const auto& jsThreadCallbackInvoker = bridge_->jsThreadCallbackInvoker();
583588
bool currentThreadIsJs =
584589
std::this_thread::get_id() == bridge_->jsThreadId();
@@ -611,6 +616,17 @@ void invoke(void* ret, void* args[]) {
611616
}
612617
error = "Native callback was invoked off the JS thread without a JS scheduler.";
613618
};
619+
auto callOnRuntimeThread = [&]() {
620+
if (currentThreadIsJs) {
621+
call();
622+
return;
623+
}
624+
if (runtimeCallbackInvoker) {
625+
runtimeCallbackInvoker(call);
626+
return;
627+
}
628+
error = "Native callback was invoked off its owning runtime thread without a runtime scheduler.";
629+
};
614630

615631
if (threadPolicy_ == NativeApiCallbackThreadPolicy::JS) {
616632
callOnJSThread();
@@ -621,20 +637,21 @@ void invoke(void* ret, void* args[]) {
621637
}
622638
return;
623639
}
640+
if (threadPolicy_ == NativeApiCallbackThreadPolicy::Runtime) {
641+
callOnRuntimeThread();
642+
if (!error.empty()) {
643+
if (!recordNativeCallbackException(error)) {
644+
throwNativeApiCallbackException(error);
645+
}
646+
}
647+
return;
648+
}
624649

625650
bool returnsVoid = signature_->returnType.kind == metagen::mdTypeVoid;
626-
bool activeSynchronousNativeInvocation =
627-
gActiveSynchronousNativeInvocationDepth.load(
628-
std::memory_order_acquire) > 0;
629651
bool nativeCallerThreadCallbacks =
630652
bridge_->invokeCallbacksOnNativeCallerThread();
631-
bool nativeCallerThreadCallback =
632-
nativeCallerThreadCallbacks && !currentThreadIsJs &&
633-
activeSynchronousNativeInvocation &&
634-
(block_ || bindThis_ || !returnsVoid);
635653
bool direct = currentThreadIsJs ||
636-
gSynchronousNativeInvocationDepth > 0 ||
637-
nativeCallerThreadCallback;
654+
gSynchronousNativeInvocationDepth > 0;
638655
bool waitForNativeThreadCallback =
639656
currentThreadIsJs && nativeCallbackInvoker &&
640657
gActiveNativeThreadEngineCallbacks.load(std::memory_order_acquire) > 0;
@@ -671,21 +688,14 @@ void invoke(void* ret, void* args[]) {
671688
return false;
672689
};
673690

674-
if (dispatchZeroArgVoidBlockAsync()) {
691+
if (nativeCallerThreadCallbacks && !currentThreadIsJs) {
692+
callOnNativeCallerThread();
693+
} else if (dispatchZeroArgVoidBlockAsync()) {
675694
return;
676-
}
677-
678-
if (direct && !waitForNativeThreadCallback) {
679-
if (nativeCallerThreadCallback) {
680-
callOnNativeCallerThread();
681-
} else {
682-
call();
683-
}
684-
} else if (!currentThreadIsJs && !nativeCallerThreadCallbacks) {
695+
} else if (direct && !waitForNativeThreadCallback) {
696+
call();
697+
} else if (!currentThreadIsJs) {
685698
callOnJSThread();
686-
} else if (!currentThreadIsJs && returnsVoid && block_ &&
687-
jsThreadCallbackInvoker) {
688-
jsThreadCallbackInvoker(call);
689699
} else if (nativeCallbackInvoker) {
690700
bool nativeThreadCallback = !currentThreadIsJs;
691701
if (nativeThreadCallback) {
@@ -1472,6 +1482,31 @@ NativeApiType parseMetadataEngineType(MDMetadataReader* metadata,
14721482
return signature;
14731483
}
14741484

1485+
bool prepareEngineCallbackSignature(NativeApiSignature* signature) {
1486+
if (signature == nullptr) {
1487+
return false;
1488+
}
1489+
1490+
signature->ffiTypes.clear();
1491+
signature->ffiTypes.reserve(signature->argumentTypes.size() +
1492+
signature->implicitArgumentCount);
1493+
for (unsigned int i = 0; i < signature->implicitArgumentCount; i++) {
1494+
signature->ffiTypes.push_back(&ffi_type_pointer);
1495+
}
1496+
for (const auto& argType : signature->argumentTypes) {
1497+
signature->ffiTypes.push_back(ffiTypeForEngineArgument(argType));
1498+
}
1499+
1500+
ffi_status status = ffi_prep_cif(
1501+
&signature->cif, FFI_DEFAULT_ABI,
1502+
static_cast<unsigned int>(signature->ffiTypes.size()),
1503+
signature->returnType.ffiType != nullptr ? signature->returnType.ffiType
1504+
: &ffi_type_void,
1505+
signature->ffiTypes.empty() ? nullptr : signature->ffiTypes.data());
1506+
signature->prepared = status == FFI_OK;
1507+
return signature->prepared;
1508+
}
1509+
14751510
const char* skipObjCTypeQualifiers(const char* encoding) {
14761511
while (encoding != nullptr && *encoding != '\0' &&
14771512
std::strchr("rnNoORV", *encoding) != nullptr) {
@@ -1480,6 +1515,13 @@ NativeApiType parseMetadataEngineType(MDMetadataReader* metadata,
14801515
return encoding;
14811516
}
14821517

1518+
const char* skipObjCTypeFrameOffset(const char* encoding) {
1519+
while (encoding != nullptr && *encoding >= '0' && *encoding <= '9') {
1520+
encoding++;
1521+
}
1522+
return encoding;
1523+
}
1524+
14831525
const char* skipObjCTypeFieldName(const char* encoding, std::string* name) {
14841526
if (encoding == nullptr || *encoding != '"') {
14851527
return encoding;
@@ -1930,6 +1972,48 @@ NativeApiType parseObjCEncodedEngineType(
19301972
return finishPrimitive(encoding + 1);
19311973
}
19321974

1975+
std::optional<NativeApiSignature> parseObjCCallbackEngineSignature(
1976+
const std::string& encodingString, bool block, NativeApiBridge* bridge) {
1977+
const char* cursor = skipObjCTypeQualifiers(encodingString.c_str());
1978+
if (cursor == nullptr || *cursor == '\0') {
1979+
return std::nullopt;
1980+
}
1981+
1982+
NativeApiSignature signature;
1983+
signature.implicitArgumentCount = block ? 1 : 0;
1984+
1985+
const char* returnEnd = cursor;
1986+
signature.returnType = parseObjCEncodedEngineType(cursor, bridge, &returnEnd);
1987+
if (returnEnd == cursor) {
1988+
return std::nullopt;
1989+
}
1990+
cursor = skipObjCTypeFrameOffset(returnEnd);
1991+
1992+
if (block) {
1993+
const char* blockSelf = skipObjCTypeQualifiers(cursor);
1994+
if (blockSelf != nullptr && blockSelf[0] == '@' && blockSelf[1] == '?') {
1995+
cursor = skipObjCTypeFrameOffset(blockSelf + 2);
1996+
}
1997+
}
1998+
1999+
while (cursor != nullptr && *cursor != '\0') {
2000+
const char* argStart = skipObjCTypeQualifiers(cursor);
2001+
if (argStart == nullptr || *argStart == '\0') {
2002+
break;
2003+
}
2004+
const char* argEnd = argStart;
2005+
NativeApiType argType = parseObjCEncodedEngineType(argStart, bridge, &argEnd);
2006+
if (argEnd == argStart) {
2007+
return std::nullopt;
2008+
}
2009+
signature.argumentTypes.push_back(std::move(argType));
2010+
cursor = skipObjCTypeFrameOffset(argEnd);
2011+
}
2012+
2013+
prepareEngineCallbackSignature(&signature);
2014+
return signature;
2015+
}
2016+
19332017
std::optional<NativeApiSignature> parseObjCMethodEngineSignature(
19342018
Method method, NativeApiBridge* bridge = nullptr) {
19352019
if (method == nullptr) {
@@ -2110,6 +2194,36 @@ throw JSError(
21102194
return callback;
21112195
}
21122196

2197+
std::shared_ptr<NativeApiCallback> createEngineCallback(
2198+
Runtime& runtime, const std::shared_ptr<NativeApiBridge>& bridge,
2199+
const std::string& objcSignatureEncoding, Function function, bool block,
2200+
NativeApiCallbackThreadPolicy threadPolicy =
2201+
NativeApiCallbackThreadPolicy::Default,
2202+
uintptr_t roundTripValidationKey = 0) {
2203+
if (bridge == nullptr || objcSignatureEncoding.empty()) {
2204+
throw JSError(runtime, "Native callback encoding is unavailable.");
2205+
}
2206+
2207+
auto parsed = parseObjCCallbackEngineSignature(
2208+
objcSignatureEncoding, block, bridge.get());
2209+
if (!parsed || !signatureSupportedForEngineCallback(*parsed)) {
2210+
throw JSError(
2211+
runtime, "Native callback signature is not supported by backend.");
2212+
}
2213+
2214+
auto signature =
2215+
std::make_shared<NativeApiSignature>(std::move(*parsed));
2216+
auto callback = std::make_shared<NativeApiCallback>(
2217+
runtime, bridge, std::move(signature), std::move(function), block,
2218+
threadPolicy, false, roundTripValidationKey);
2219+
if (block) {
2220+
callback->retainInitialBlockLifetime(callback);
2221+
} else {
2222+
bridge->retainEngineLifetime(callback);
2223+
}
2224+
return callback;
2225+
}
2226+
21132227
std::shared_ptr<NativeApiCallback> createEngineMethodCallback(
21142228
Runtime& runtime, const std::shared_ptr<NativeApiBridge>& bridge,
21152229
const std::string& selectorName, MDSectionOffset signatureOffset,

0 commit comments

Comments
 (0)