Skip to content

Commit 5a47736

Browse files
committed
feat: add Hermes JSI native API bridge
1 parent 78cf61a commit 5a47736

52 files changed

Lines changed: 4059 additions & 235 deletions

Some content is hidden

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

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,10 @@ SwiftBindgen
6666

6767
# Generated Objective-C/C dispatch wrappers
6868
NativeScript/ffi/GeneratedSignatureDispatch.inc
69+
NativeScript/ffi/GeneratedSignatureDispatch.inc.stamp
70+
71+
# React Native TurboModule package staging
72+
packages/react-native-ios-hermes/dist/
73+
packages/react-native-ios-hermes/ios/vendor/
74+
packages/react-native-ios-hermes/metadata/
75+
packages/react-native-ios-hermes/native-api-jsi/

NativeScript/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,7 @@ if(ENABLE_JS_RUNTIME)
273273
set(SOURCE_FILES
274274
${SOURCE_FILES}
275275
napi/hermes/jsr.cpp
276+
ffi/jsi/NativeApiJsi.mm
276277
ffi/HermesFastNativeApi.mm
277278
)
278279

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#ifndef NativeScript_Prefix_pch
22
#define NativeScript_Prefix_pch
33

4-
#define NATIVESCRIPT_VERSION "9.0.0-napi-v8.0"
4+
#define NATIVESCRIPT_VERSION "0.0.1"
55

66
#endif /* NativeScript_Prefix_pch */

NativeScript/ffi/CFunction.mm

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include <cstring>
44
#include <vector>
55
#include "Block.h"
6+
#include "CallbackThreading.h"
67
#include "ClassMember.h"
78
#include "HermesFastCallbackInfo.h"
89
#include "HermesFastNativeApi.h"
@@ -376,6 +377,7 @@ inline void ensureCFunctionDispatchLookup(CFunction* function, Cif* cif) {
376377
(napiInvoker != nullptr && !cif->skipGeneratedNapiDispatch)) &&
377378
!isMainEntrypoint) {
378379
@try {
380+
NativeCallRuntimeUnlockScope unlockRuntime(env);
379381
bool invoked = engineDirectInvoker != nullptr
380382
? engineDirectInvoker(env, cif, func->fnptr, invocationArgs, cif->rvalue)
381383
: napiInvoker(env, cif, func->fnptr, invocationArgs, cif->rvalue);
@@ -443,6 +445,7 @@ inline void ensureCFunctionDispatchLookup(CFunction* function, Cif* cif) {
443445
#endif
444446

445447
@try {
448+
NativeCallRuntimeUnlockScope unlockRuntime(env);
446449
if (preparedInvoker != nullptr) {
447450
preparedInvoker(func->fnptr, avalues, rvalue);
448451
} else {
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
#ifndef CALLBACK_THREADING_H
2+
#define CALLBACK_THREADING_H
3+
4+
#include "js_native_api.h"
5+
6+
#include <atomic>
7+
#include <memory>
8+
9+
#if defined(ENABLE_JS_RUNTIME)
10+
#include "jsr.h"
11+
#endif
12+
13+
namespace nativescript {
14+
15+
namespace detail {
16+
17+
#if defined(ENABLE_JS_RUNTIME) && defined(TARGET_ENGINE_HERMES)
18+
inline std::atomic<int> native_call_unlocked_runtime_count{0};
19+
inline thread_local int native_caller_thread_callback_depth = 0;
20+
#endif
21+
22+
} // namespace detail
23+
24+
inline bool shouldInvokeCallbackOnNativeCallerThread() {
25+
#if defined(ENABLE_JS_RUNTIME) && defined(TARGET_ENGINE_HERMES)
26+
return true;
27+
#else
28+
return false;
29+
#endif
30+
}
31+
32+
inline bool isNativeCallRuntimeUnlockedForCallbacks() {
33+
#if defined(ENABLE_JS_RUNTIME) && defined(TARGET_ENGINE_HERMES)
34+
return detail::native_call_unlocked_runtime_count.load(
35+
std::memory_order_acquire) > 0;
36+
#else
37+
return false;
38+
#endif
39+
}
40+
41+
inline bool isNativeCallerThreadCallbackActive() {
42+
#if defined(ENABLE_JS_RUNTIME) && defined(TARGET_ENGINE_HERMES)
43+
return detail::native_caller_thread_callback_depth > 0;
44+
#else
45+
return false;
46+
#endif
47+
}
48+
49+
class NativeCallRuntimeUnlockScope final {
50+
public:
51+
explicit NativeCallRuntimeUnlockScope(napi_env env) {
52+
#if defined(ENABLE_JS_RUNTIME) && defined(TARGET_ENGINE_HERMES)
53+
if (isNativeCallerThreadCallbackActive()) {
54+
return;
55+
}
56+
57+
auto it = JSR::env_to_jsr_cache.find(env);
58+
if (it == JSR::env_to_jsr_cache.end() || it->second == nullptr) {
59+
return;
60+
}
61+
62+
jsr_ = it->second;
63+
unlockedDepth_ = js_current_env_lock_depth(env);
64+
for (int i = 0; i < unlockedDepth_; i++) {
65+
jsr_->unlock();
66+
}
67+
if (unlockedDepth_ == 0 && jsr_->runtime != nullptr) {
68+
runtime_ = jsr_->runtime.get();
69+
runtime_->unlock();
70+
unlockedRuntime_ = true;
71+
}
72+
if (unlockedDepth_ > 0 || unlockedRuntime_) {
73+
didUnlock_ = true;
74+
detail::native_call_unlocked_runtime_count.fetch_add(
75+
1, std::memory_order_release);
76+
}
77+
#else
78+
(void)env;
79+
#endif
80+
}
81+
82+
~NativeCallRuntimeUnlockScope() {
83+
#if defined(ENABLE_JS_RUNTIME) && defined(TARGET_ENGINE_HERMES)
84+
if (didUnlock_) {
85+
detail::native_call_unlocked_runtime_count.fetch_sub(
86+
1, std::memory_order_release);
87+
}
88+
if (jsr_ != nullptr) {
89+
for (int i = 0; i < unlockedDepth_; i++) {
90+
jsr_->lock();
91+
}
92+
}
93+
if (unlockedRuntime_ && runtime_ != nullptr) {
94+
runtime_->lock();
95+
}
96+
#endif
97+
}
98+
99+
NativeCallRuntimeUnlockScope(const NativeCallRuntimeUnlockScope&) = delete;
100+
NativeCallRuntimeUnlockScope& operator=(const NativeCallRuntimeUnlockScope&) =
101+
delete;
102+
103+
private:
104+
#if defined(ENABLE_JS_RUNTIME) && defined(TARGET_ENGINE_HERMES)
105+
JSR* jsr_ = nullptr;
106+
facebook::jsi::ThreadSafeRuntime* runtime_ = nullptr;
107+
#endif
108+
int unlockedDepth_ = 0;
109+
bool unlockedRuntime_ = false;
110+
bool didUnlock_ = false;
111+
};
112+
113+
class NativeCallbackScope final {
114+
public:
115+
explicit NativeCallbackScope(napi_env env) : env_(env) {
116+
#if defined(ENABLE_JS_RUNTIME) && defined(TARGET_ENGINE_HERMES)
117+
if (isNativeCallRuntimeUnlockedForCallbacks()) {
118+
auto it = JSR::env_to_jsr_cache.find(env_);
119+
if (it != JSR::env_to_jsr_cache.end() && it->second != nullptr) {
120+
jsr_ = it->second;
121+
jsr_->js_mutex.lock();
122+
detail::native_caller_thread_callback_depth += 1;
123+
napi_open_handle_scope(env_, &napiHandleScope_);
124+
return;
125+
}
126+
}
127+
#endif
128+
#if defined(ENABLE_JS_RUNTIME)
129+
napiScope_ = std::make_unique<NapiScope>(env_);
130+
#else
131+
(void)env_;
132+
#endif
133+
}
134+
135+
~NativeCallbackScope() {
136+
#if defined(ENABLE_JS_RUNTIME) && defined(TARGET_ENGINE_HERMES)
137+
if (napiHandleScope_ != nullptr) {
138+
napi_close_handle_scope(env_, napiHandleScope_);
139+
}
140+
if (jsr_ != nullptr) {
141+
detail::native_caller_thread_callback_depth -= 1;
142+
jsr_->js_mutex.unlock();
143+
}
144+
#endif
145+
}
146+
147+
NativeCallbackScope(const NativeCallbackScope&) = delete;
148+
NativeCallbackScope& operator=(const NativeCallbackScope&) = delete;
149+
150+
private:
151+
napi_env env_;
152+
#if defined(ENABLE_JS_RUNTIME) && defined(TARGET_ENGINE_HERMES)
153+
JSR* jsr_ = nullptr;
154+
napi_handle_scope napiHandleScope_ = nullptr;
155+
#endif
156+
#if defined(ENABLE_JS_RUNTIME)
157+
std::unique_ptr<NapiScope> napiScope_;
158+
#endif
159+
};
160+
161+
} // namespace nativescript
162+
163+
#endif // CALLBACK_THREADING_H

NativeScript/ffi/ClassBuilder.mm

Lines changed: 81 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,58 @@ napi_env resolveClassBuilderEnv(id self) {
128128
})
129129
)";
130130

131+
const char* kInstallClassFromStringAliasSource = R"(
132+
(function (global) {
133+
if (global.__nsClassFromStringAliasInstalled === true) {
134+
return;
135+
}
136+
137+
const original = global.NSClassFromString;
138+
if (typeof original !== "function") {
139+
return;
140+
}
141+
142+
Object.defineProperty(global, "__nsOriginalNSClassFromString", {
143+
configurable: true,
144+
enumerable: false,
145+
writable: false,
146+
value: original
147+
});
148+
149+
Object.defineProperty(global, "NSClassFromString", {
150+
configurable: true,
151+
enumerable: true,
152+
writable: true,
153+
value: function (name) {
154+
const cls = original.call(this, name);
155+
if (cls != null) {
156+
try {
157+
const registry = global.__nsConstructorsByObjCClassName;
158+
const stringFromClass = global.NSStringFromClass;
159+
if (registry != null && typeof stringFromClass === "function") {
160+
const runtimeName = stringFromClass(cls);
161+
const constructor = registry[runtimeName];
162+
if (constructor != null) {
163+
return constructor;
164+
}
165+
}
166+
} catch (_) {
167+
}
168+
}
169+
170+
return cls;
171+
}
172+
});
173+
174+
Object.defineProperty(global, "__nsClassFromStringAliasInstalled", {
175+
configurable: true,
176+
enumerable: false,
177+
writable: false,
178+
value: true
179+
});
180+
})
181+
)";
182+
131183
const char* NSFastEnumerationMethodEncoding() {
132184
static const char* encoding = nullptr;
133185
if (encoding == nullptr) {
@@ -856,24 +908,6 @@ NSUInteger JS_SymbolIteratorCountByEnumerating(id self, SEL _cmd, NSFastEnumerat
856908
}
857909
}
858910

859-
bool hasOwnOverrides = false;
860-
napi_value overridePropertyNames = nullptr;
861-
napi_get_all_property_names(env, args[0], napi_key_own_only, napi_key_skip_symbols,
862-
napi_key_numbers_to_strings, &overridePropertyNames);
863-
uint32_t overridePropertyCount = 0;
864-
napi_get_array_length(env, overridePropertyNames, &overridePropertyCount);
865-
hasOwnOverrides = overridePropertyCount > 0;
866-
867-
bool hasExposedMethodsOption = false;
868-
bool hasProtocolsOption = false;
869-
if (hasOptionsObject) {
870-
napi_has_named_property(env, options, "exposedMethods", &hasExposedMethodsOption);
871-
napi_has_named_property(env, options, "protocols", &hasProtocolsOption);
872-
}
873-
874-
bool shouldReuseExistingClass = false;
875-
Class existingExternalClass = nullptr;
876-
877911
// Create a class name.
878912
napi_value baseClassName;
879913
napi_get_named_property(env, thisArg, "name", &baseClassName);
@@ -902,22 +936,23 @@ NSUInteger JS_SymbolIteratorCountByEnumerating(id self, SEL _cmd, NSFastEnumerat
902936
}
903937

904938
std::string newClassName;
939+
bool shouldAliasRequestedName = false;
905940
if (!requestedName.empty()) {
906941
newClassName = requestedName;
907942
Class existingClass = objc_lookUpClass(newClassName.c_str());
908-
if (existingClass != nullptr &&
909-
class_conformsToProtocol(existingClass, @protocol(ObjCBridgeClassBuilderProtocol))) {
943+
auto nextAvailableClassName = [](const std::string& baseName) {
910944
size_t suffix = 1;
911945
std::string candidate;
912946
do {
913-
candidate = requestedName + std::to_string(suffix++);
947+
candidate = baseName + std::to_string(suffix++);
914948
} while (objc_lookUpClass(candidate.c_str()) != nullptr);
915-
newClassName = candidate;
916-
} else if (existingClass != nullptr && !hasOwnOverrides && !hasExposedMethodsOption &&
917-
!hasProtocolsOption) {
918-
// Name-only extensions should resolve to an existing external class when present.
919-
shouldReuseExistingClass = true;
920-
existingExternalClass = existingClass;
949+
return candidate;
950+
};
951+
if (existingClass != nullptr) {
952+
newClassName = nextAvailableClassName(requestedName);
953+
shouldAliasRequestedName =
954+
!class_conformsToProtocol(existingClass,
955+
@protocol(ObjCBridgeClassBuilderProtocol));
921956
}
922957
} else {
923958
newClassName = baseClassNameBuf;
@@ -1000,14 +1035,27 @@ NSUInteger JS_SymbolIteratorCountByEnumerating(id self, SEL _cmd, NSFastEnumerat
10001035
napi_get_named_property(env, registryGlobal, "__nsConstructorsByObjCClassName", &classRegistry);
10011036
}
10021037

1003-
if (classRegistry != nullptr) {
1004-
napi_set_named_property(env, classRegistry, newClassName.c_str(), newConstructor);
1038+
napi_value installClassFromStringAliasScript = nullptr;
1039+
napi_value installClassFromStringAlias = nullptr;
1040+
if (napi_create_string_utf8(env, kInstallClassFromStringAliasSource, NAPI_AUTO_LENGTH,
1041+
&installClassFromStringAliasScript) == napi_ok &&
1042+
napi_run_script(env, installClassFromStringAliasScript, &installClassFromStringAlias) ==
1043+
napi_ok &&
1044+
installClassFromStringAlias != nullptr) {
1045+
napi_value aliasArgs[] = {registryGlobal};
1046+
if (napi_call_function(env, registryGlobal, installClassFromStringAlias, 1, aliasArgs,
1047+
nullptr) != napi_ok) {
1048+
clearPendingException(env);
1049+
}
1050+
} else {
1051+
clearPendingException(env);
10051052
}
10061053

1007-
if (shouldReuseExistingClass && existingExternalClass != nullptr) {
1008-
napi_remove_wrap(env, newConstructor, nullptr);
1009-
napi_wrap(env, newConstructor, (void*)existingExternalClass, nullptr, nullptr, nullptr);
1010-
return newConstructor;
1054+
if (classRegistry != nullptr) {
1055+
napi_set_named_property(env, classRegistry, newClassName.c_str(), newConstructor);
1056+
if (shouldAliasRequestedName && !requestedName.empty()) {
1057+
napi_set_named_property(env, classRegistry, requestedName.c_str(), newConstructor);
1058+
}
10111059
}
10121060

10131061
// Use ClassBuilder to create the native class and bridge the methods

0 commit comments

Comments
 (0)