From 6f586b22736af94578b3e444a9fd630cd0b23063 Mon Sep 17 00:00:00 2001 From: riteshshukla04 Date: Fri, 4 Sep 2026 19:56:59 +0530 Subject: [PATCH] =?UTF-8?q?perf:=20cache=20NativeState=E2=86=92HybridObjec?= =?UTF-8?q?t=20dynamic=5Fcast=20offset=20per=20type=20in=20HybridFunction?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cpp/core/HybridFunction.hpp | 71 ++++++++++++++----- 1 file changed, 55 insertions(+), 16 deletions(-) diff --git a/packages/react-native-nitro-modules/cpp/core/HybridFunction.hpp b/packages/react-native-nitro-modules/cpp/core/HybridFunction.hpp index c6c88aa21b..e5aed2531c 100644 --- a/packages/react-native-nitro-modules/cpp/core/HybridFunction.hpp +++ b/packages/react-native-nitro-modules/cpp/core/HybridFunction.hpp @@ -18,12 +18,15 @@ struct JSIConverter; #include "NitroDefines.hpp" #include "NitroTypeInfo.hpp" #include "PropNameIDCache.hpp" +#include +#include #include #include #include #include #include #include +#include namespace margelo::nitro { @@ -91,8 +94,9 @@ class HybridFunction final { /* HybridObject */ const jsi::Value& thisValue, /* JS arguments */ const jsi::Value* NON_NULL args, /* argument size */ size_t count) -> jsi::Value { - // 1. Get actual `HybridObject` instance from `thisValue` (it's stored as `NativeState`) - std::shared_ptr hybridInstance = getHybridObjectNativeState(runtime, thisValue, kind, name); + // 1. Get actual `HybridObject` instance from `thisValue` (stored as `NativeState`) - `nativeState` keeps it alive during the call + std::shared_ptr nativeState; + THybrid* hybridInstance = getHybridObjectNativeState(runtime, thisValue, kind, name, nativeState); // 2. Make sure the given arguments match, either with a static size, or with potentially optional arguments size. constexpr size_t optionalArgsCount = trailing_optionals_count_v; @@ -101,7 +105,7 @@ class HybridFunction final { bool isWithinArgsRange = (count >= minArgsCount && count <= maxArgsCount); if (!isWithinArgsRange) [[unlikely]] { // invalid amount of arguments passed! - std::string funcName = getHybridFuncFullName(kind, name, hybridInstance.get()); + std::string funcName = getHybridFuncFullName(kind, name, hybridInstance); if constexpr (minArgsCount == maxArgsCount) { // min and max args length is the same, so we don't have any optional parameters. fixed count throw jsi::JSError(runtime, "`" + funcName + "` expected " + std::to_string(maxArgsCount) + " arguments, but received " + @@ -116,15 +120,15 @@ class HybridFunction final { try { // 3. Actually call the method with JSI values as arguments and return a JSI value again. // Internally, this method converts the JSI values to C++ values using `JSIConverter`. - return callMethod(hybridInstance.get(), method, runtime, args, count, std::index_sequence_for{}); + return callMethod(hybridInstance, method, runtime, args, count, std::index_sequence_for{}); } catch (const std::exception& exception) { // Some exception was thrown - add method name information and re-throw as `JSError`. - std::string funcName = getHybridFuncFullName(kind, name, hybridInstance.get()); + std::string funcName = getHybridFuncFullName(kind, name, hybridInstance); std::string message = exception.what(); throw jsi::JSError(runtime, funcName + ": " + message); } catch (...) { // Some unknown exception was thrown - add method name information and re-throw as `JSError`. - std::string funcName = getHybridFuncFullName(kind, name, hybridInstance.get()); + std::string funcName = getHybridFuncFullName(kind, name, hybridInstance); std::string errorName = TypeInfo::getCurrentExceptionName(); throw jsi::JSError(runtime, "`" + funcName + "` threw an unknown " + errorName + " error."); } @@ -146,12 +150,12 @@ class HybridFunction final { /* HybridObject */ const jsi::Value& thisValue, /* JS arguments */ const jsi::Value* args, /* argument size */ size_t count) -> jsi::Value { - // 1. Get actual `HybridObject` instance from `thisValue` (it's stored as `NativeState`) - std::shared_ptr hybridInstance = getHybridObjectNativeState(runtime, thisValue, FunctionKind::METHOD, name); + // 1. Get actual `HybridObject` instance from `thisValue` (stored as `NativeState`) - `nativeState` keeps it alive during the call + std::shared_ptr nativeState; + Derived* hybridInstance = getHybridObjectNativeState(runtime, thisValue, FunctionKind::METHOD, name, nativeState); // 2. Call the raw JSI method using raw JSI Values. Exceptions are also expected to be handled by the user. - Derived* pointer = hybridInstance.get(); - return (pointer->*method)(runtime, thisValue, args, count); + return (hybridInstance->*method)(runtime, thisValue, args, count); }; return HybridFunction(std::move(hostFunction), expectedArgumentsCount, name); @@ -182,13 +186,48 @@ class HybridFunction final { } private: + // `NativeState` is a unique virtual base, so its offset to `THybrid` is fixed per dynamic type - cache it (8 slots, then `dynamic_cast`) + template + static inline THybrid* NULLABLE castNativeState(jsi::NativeState* NON_NULL nativeState) { + struct CastEntry { + const std::type_info* type; + std::ptrdiff_t offset; + }; + static constexpr size_t kCastCacheSize = 8; + static std::atomic castCache[kCastCacheSize]; + + const std::type_info& actualType = typeid(*nativeState); + size_t freeSlot = kCastCacheSize; + for (size_t i = 0; i < kCastCacheSize; i++) { + const CastEntry* entry = castCache[i].load(std::memory_order_acquire); + if (entry == nullptr) { + freeSlot = i; + break; + } + if (entry->type == &actualType) { + return reinterpret_cast(reinterpret_cast(nativeState) + entry->offset); + } + } + + THybrid* hybridInstance = dynamic_cast(nativeState); + if (hybridInstance != nullptr && freeSlot < kCastCacheSize) { + auto* entry = new CastEntry{&actualType, reinterpret_cast(hybridInstance) - reinterpret_cast(nativeState)}; + const CastEntry* expected = nullptr; + if (!castCache[freeSlot].compare_exchange_strong(expected, entry, std::memory_order_release, std::memory_order_relaxed)) { + delete entry; + } + } + return hybridInstance; + } + /** - * Get the `NativeState` of the given `value`. + * Get the `THybrid` instance stored as `NativeState` in the given `value`. */ template - static inline std::shared_ptr getHybridObjectNativeState(jsi::Runtime& runtime, const jsi::Value& value, - [[maybe_unused]] FunctionKind funcKind, - [[maybe_unused]] const std::string& funcName) { + static inline THybrid* NULLABLE getHybridObjectNativeState(jsi::Runtime& runtime, const jsi::Value& value, + [[maybe_unused]] FunctionKind funcKind, + [[maybe_unused]] const std::string& funcName, + std::shared_ptr& nativeState) { // 1. Convert jsi::Value to jsi::Object #ifdef NITRO_DEBUG if (!value.isObject()) [[unlikely]] { @@ -219,7 +258,7 @@ class HybridFunction final { #endif // 3. Get `NativeState` from the jsi::Object and check if it is non-null - std::shared_ptr nativeState = object.getNativeState(runtime); + nativeState = object.getNativeState(runtime); #ifdef NITRO_DEBUG if (nativeState == nullptr) [[unlikely]] { throw jsi::JSError(runtime, "Cannot " + getHybridFuncDebugInfo(funcKind, funcName) + @@ -229,7 +268,7 @@ class HybridFunction final { #endif // 4. Try casting it to our desired target type. - std::shared_ptr hybridInstance = std::dynamic_pointer_cast(nativeState); + THybrid* hybridInstance = castNativeState(nativeState.get()); #ifdef NITRO_DEBUG if (hybridInstance == nullptr) [[unlikely]] { throw jsi::JSError(runtime, "Cannot " + getHybridFuncDebugInfo(funcKind, funcName) +