Skip to content

Commit ceb251c

Browse files
committed
perf(ios): add engine native dispatch fast paths
1 parent 2e29a4f commit ceb251c

13 files changed

Lines changed: 1018 additions & 57 deletions

NativeScript/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,7 @@ if(ENABLE_JS_RUNTIME)
286286
# napi
287287
napi/quickjs/quickjs-api.c
288288
napi/quickjs/jsr.cpp
289+
ffi/QuickJSFastNativeApi.mm
289290
)
290291

291292
elseif(TARGET_ENGINE_JSC)
@@ -298,6 +299,7 @@ if(ENABLE_JS_RUNTIME)
298299
set(SOURCE_FILES ${SOURCE_FILES}
299300
napi/jsc/jsc-api.cpp
300301
napi/jsc/jsr.cpp
302+
ffi/JSCFastNativeApi.mm
301303
)
302304
endif()
303305
else()

NativeScript/ffi/CFunction.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ class ObjCBridgeState;
1212
class CFunction {
1313
public:
1414
static napi_value jsCall(napi_env env, napi_callback_info cbinfo);
15+
static napi_value jsCallDirect(napi_env env, MDSectionOffset offset,
16+
size_t actualArgc,
17+
const napi_value* callArgs);
1518

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

NativeScript/ffi/CFunction.mm

Lines changed: 48 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include <vector>
55
#include "Block.h"
66
#include "ClassMember.h"
7+
#include "HermesFastCallbackInfo.h"
78
#include "Interop.h"
89
#include "ObjCBridge.h"
910
#include "SignatureDispatch.h"
@@ -81,13 +82,10 @@ inline napi_value createCompatDispatchQueueWrapperForCFunction(napi_env env,
8182
return Pointer::create(env, reinterpret_cast<void*>(queue));
8283
}
8384

84-
inline napi_value tryCallCompatLibdispatchFunction(napi_env env, napi_callback_info cbinfo,
85+
inline napi_value tryCallCompatLibdispatchFunction(napi_env env, size_t argc,
86+
const napi_value* argv,
8587
const char* functionName) {
8688
if (strcmp(functionName, "dispatch_get_global_queue") == 0) {
87-
size_t argc = 2;
88-
napi_value argv[2] = {nullptr, nullptr};
89-
napi_get_cb_info(env, cbinfo, &argc, argv, nullptr, nullptr);
90-
9189
int64_t identifier = 0;
9290
if (argc > 0) {
9391
napi_valuetype identifierType = napi_undefined;
@@ -142,10 +140,6 @@ inline napi_value tryCallCompatLibdispatchFunction(napi_env env, napi_callback_i
142140
}
143141

144142
if (strcmp(functionName, "dispatch_async") == 0) {
145-
size_t argc = 2;
146-
napi_value argv[2] = {nullptr, nullptr};
147-
napi_get_cb_info(env, cbinfo, &argc, argv, nullptr, nullptr);
148-
149143
if (argc < 2) {
150144
napi_throw_type_error(env, nullptr, "dispatch_async expects a queue and callback.");
151145
return nullptr;
@@ -254,18 +248,59 @@ inline void ensureCFunctionDispatchLookup(CFunction* function, Cif* cif) {
254248
}
255249

256250
napi_value CFunction::jsCall(napi_env env, napi_callback_info cbinfo) {
257-
void* _offset;
251+
#ifdef TARGET_ENGINE_HERMES
252+
if (auto* fastInfo = TryGetHermesFastCallbackInfo(env, cbinfo)) {
253+
napi_value stackArgs[16];
254+
std::vector<napi_value> heapArgs;
255+
napi_value* args = stackArgs;
256+
const size_t actualArgc = fastInfo->argc;
257+
if (actualArgc > 16) {
258+
heapArgs.resize(actualArgc);
259+
args = heapArgs.data();
260+
}
261+
for (size_t i = 0; i < actualArgc; i++) {
262+
args[i] = HermesFastArg(fastInfo, i);
263+
}
264+
265+
return jsCallDirect(
266+
env, static_cast<MDSectionOffset>(reinterpret_cast<uintptr_t>(fastInfo->data)),
267+
actualArgc, args);
268+
}
269+
#endif
270+
271+
void* _offset = nullptr;
272+
size_t actualArgc = 16;
273+
napi_value stackArgs[16];
258274

259-
napi_get_cb_info(env, cbinfo, nullptr, nullptr, nullptr, &_offset);
275+
napi_get_cb_info(env, cbinfo, &actualArgc, stackArgs, nullptr, &_offset);
260276

261-
auto bridgeState = ObjCBridgeState::InstanceData(env);
262277
MDSectionOffset offset = (MDSectionOffset)((size_t)_offset);
263278

279+
if (actualArgc > 16) {
280+
std::vector<napi_value> dynamicArgs(actualArgc);
281+
size_t retryArgc = actualArgc;
282+
napi_get_cb_info(env, cbinfo, &retryArgc, dynamicArgs.data(), nullptr, nullptr);
283+
dynamicArgs.resize(retryArgc);
284+
return jsCallDirect(env, offset, retryArgc, dynamicArgs.data());
285+
}
286+
287+
return jsCallDirect(env, offset, actualArgc, stackArgs);
288+
}
289+
290+
napi_value CFunction::jsCallDirect(napi_env env, MDSectionOffset offset,
291+
size_t actualArgc,
292+
const napi_value* callArgs) {
293+
auto bridgeState = ObjCBridgeState::InstanceData(env);
294+
if (bridgeState == nullptr) {
295+
napi_throw_error(env, "NativeScriptException", "Missing Objective-C bridge state.");
296+
return nullptr;
297+
}
298+
264299
auto name = bridgeState->metadata->getString(offset);
265300

266301
if (strcmp(name, "dispatch_async") == 0 || strcmp(name, "dispatch_get_current_queue") == 0 ||
267302
strcmp(name, "dispatch_get_global_queue") == 0) {
268-
return tryCallCompatLibdispatchFunction(env, cbinfo, name);
303+
return tryCallCompatLibdispatchFunction(env, actualArgc, callArgs, name);
269304
}
270305

271306
auto func = bridgeState->getCFunction(env, offset);
@@ -279,23 +314,8 @@ inline void ensureCFunctionDispatchLookup(CFunction* function, Cif* cif) {
279314
bridgeState->metadata->getFunctionFlag(offset + sizeof(MDSectionOffset) * 2);
280315

281316
const napi_value* invocationArgs = nullptr;
282-
std::vector<napi_value> dynamicArgs;
283317
std::vector<napi_value> paddedArgs;
284-
napi_value stackArgs[16];
285318
if (cif->argc > 0) {
286-
size_t actualArgc = 16;
287-
napi_get_cb_info(env, cbinfo, &actualArgc, stackArgs, nullptr, nullptr);
288-
289-
const napi_value* callArgs = stackArgs;
290-
if (actualArgc > 16) {
291-
dynamicArgs.resize(actualArgc);
292-
size_t retryArgc = actualArgc;
293-
napi_get_cb_info(env, cbinfo, &retryArgc, dynamicArgs.data(), nullptr, nullptr);
294-
dynamicArgs.resize(retryArgc);
295-
actualArgc = retryArgc;
296-
callArgs = dynamicArgs.data();
297-
}
298-
299319
invocationArgs = callArgs;
300320
if (actualArgc != cif->argc) {
301321
napi_value jsUndefined = nullptr;

NativeScript/ffi/ClassMember.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,14 @@ class ObjCClassMember {
8383
static napi_value jsGetter(napi_env env, napi_callback_info cbinfo);
8484
static napi_value jsReadOnlySetter(napi_env env, napi_callback_info cbinfo);
8585
static napi_value jsSetter(napi_env env, napi_callback_info cbinfo);
86+
static napi_value jsCallDirect(napi_env env, ObjCClassMember* method,
87+
napi_value jsThis, size_t actualArgc,
88+
const napi_value* callArgs);
89+
static napi_value jsGetterDirect(napi_env env, ObjCClassMember* method,
90+
napi_value jsThis);
91+
static napi_value jsSetterDirect(napi_env env, ObjCClassMember* method,
92+
napi_value jsThis, napi_value value);
93+
static napi_value jsReadOnlySetterDirect(napi_env env);
8694
void addOverload(SEL selector, MDSectionOffset offset, uint8_t dispatchFlags);
8795

8896
ObjCClassMember(ObjCBridgeState* bridgeState, SEL selector,

NativeScript/ffi/ClassMember.mm

Lines changed: 91 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "ClassBuilder.h"
1414
#include "Closure.h"
1515
#include "Interop.h"
16+
#include "HermesFastCallbackInfo.h"
1617
#include "MetadataReader.h"
1718
#include "ObjCBridge.h"
1819
#include "SignatureDispatch.h"
@@ -1419,13 +1420,51 @@ explicit CifReturnStorage(Cif* cif) {
14191420
}
14201421

14211422
napi_value ObjCClassMember::jsCall(napi_env env, napi_callback_info cbinfo) {
1423+
#ifdef TARGET_ENGINE_HERMES
1424+
if (auto* fastInfo = TryGetHermesFastCallbackInfo(env, cbinfo)) {
1425+
napi_value stackArgs[16];
1426+
std::vector<napi_value> heapArgs;
1427+
napi_value* args = stackArgs;
1428+
const size_t actualArgc = fastInfo->argc;
1429+
if (actualArgc > 16) {
1430+
heapArgs.resize(actualArgc);
1431+
args = heapArgs.data();
1432+
}
1433+
for (size_t i = 0; i < actualArgc; i++) {
1434+
args[i] = HermesFastArg(fastInfo, i);
1435+
}
1436+
1437+
return jsCallDirect(env, static_cast<ObjCClassMember*>(fastInfo->data),
1438+
HermesFastThisArg(fastInfo), actualArgc, args);
1439+
}
1440+
#endif
1441+
14221442
napi_value jsThis;
1423-
ObjCClassMember* method;
1443+
ObjCClassMember* method = nullptr;
14241444

14251445
size_t actualArgc = 16;
14261446
napi_value stackArgs[16];
14271447
napi_get_cb_info(env, cbinfo, &actualArgc, stackArgs, &jsThis, (void**)&method);
14281448

1449+
if (actualArgc > 16) {
1450+
std::vector<napi_value> dynamicArgs(actualArgc);
1451+
size_t argcRetry = actualArgc;
1452+
napi_get_cb_info(env, cbinfo, &argcRetry, dynamicArgs.data(), &jsThis, (void**)&method);
1453+
dynamicArgs.resize(argcRetry);
1454+
return jsCallDirect(env, method, jsThis, argcRetry, dynamicArgs.data());
1455+
}
1456+
1457+
return jsCallDirect(env, method, jsThis, actualArgc, stackArgs);
1458+
}
1459+
1460+
napi_value ObjCClassMember::jsCallDirect(napi_env env, ObjCClassMember* method,
1461+
napi_value jsThis, size_t actualArgc,
1462+
const napi_value* rawCallArgs) {
1463+
if (method == nullptr) {
1464+
napi_throw_error(env, "NativeScriptException", "Missing Objective-C method metadata.");
1465+
return nullptr;
1466+
}
1467+
14291468
id self = assertSelf(env, jsThis, method);
14301469

14311470
if (self == nullptr) {
@@ -1461,17 +1500,10 @@ explicit CifReturnStorage(Cif* cif) {
14611500
return resolved;
14621501
};
14631502

1464-
const napi_value* callArgs = stackArgs;
1503+
const napi_value* callArgs = actualArgc > 0 ? rawCallArgs : nullptr;
14651504
std::vector<napi_value> dynamicArgs;
1466-
if (actualArgc > 16) {
1467-
dynamicArgs.resize(actualArgc);
1468-
size_t argcRetry = actualArgc;
1469-
napi_get_cb_info(env, cbinfo, &argcRetry, dynamicArgs.data(), &jsThis, (void**)&method);
1470-
dynamicArgs.resize(argcRetry);
1471-
actualArgc = argcRetry;
1472-
callArgs = dynamicArgs.data();
1473-
} else if (!method->overloads.empty()) {
1474-
dynamicArgs.assign(stackArgs, stackArgs + actualArgc);
1505+
if (!method->overloads.empty() && actualArgc > 0 && rawCallArgs != nullptr) {
1506+
dynamicArgs.assign(rawCallArgs, rawCallArgs + actualArgc);
14751507
callArgs = dynamicArgs.data();
14761508
}
14771509

@@ -1756,11 +1788,28 @@ NativeScriptException nativeScriptException(errorMessage != nullptr ? errorMessa
17561788
}
17571789

17581790
napi_value ObjCClassMember::jsGetter(napi_env env, napi_callback_info cbinfo) {
1791+
#ifdef TARGET_ENGINE_HERMES
1792+
if (auto* fastInfo = TryGetHermesFastCallbackInfo(env, cbinfo)) {
1793+
return jsGetterDirect(env, static_cast<ObjCClassMember*>(fastInfo->data),
1794+
HermesFastThisArg(fastInfo));
1795+
}
1796+
#endif
1797+
17591798
napi_value jsThis;
1760-
ObjCClassMember* method;
1799+
ObjCClassMember* method = nullptr;
17611800

17621801
napi_get_cb_info(env, cbinfo, nullptr, nullptr, &jsThis, (void**)&method);
17631802

1803+
return jsGetterDirect(env, method, jsThis);
1804+
}
1805+
1806+
napi_value ObjCClassMember::jsGetterDirect(napi_env env, ObjCClassMember* method,
1807+
napi_value jsThis) {
1808+
if (method == nullptr) {
1809+
napi_throw_error(env, "NativeScriptException", "Missing Objective-C getter metadata.");
1810+
return nullptr;
1811+
}
1812+
17641813
id self = assertSelf(env, jsThis, method);
17651814

17661815
if (self == nullptr) {
@@ -1822,17 +1871,44 @@ NativeScriptException nativeScriptException(errorMessage != nullptr ? errorMessa
18221871
}
18231872

18241873
napi_value ObjCClassMember::jsReadOnlySetter(napi_env env, napi_callback_info cbinfo) {
1874+
return jsReadOnlySetterDirect(env);
1875+
}
1876+
1877+
napi_value ObjCClassMember::jsReadOnlySetterDirect(napi_env env) {
18251878
napi_throw_error(env, nullptr, "Attempted to assign to readonly property.");
18261879
return nullptr;
18271880
}
18281881

18291882
napi_value ObjCClassMember::jsSetter(napi_env env, napi_callback_info cbinfo) {
1883+
#ifdef TARGET_ENGINE_HERMES
1884+
if (auto* fastInfo = TryGetHermesFastCallbackInfo(env, cbinfo)) {
1885+
napi_value value = nullptr;
1886+
if (fastInfo->argc > 0) {
1887+
value = HermesFastArg(fastInfo, 0);
1888+
} else {
1889+
napi_get_undefined(env, &value);
1890+
}
1891+
return jsSetterDirect(env, static_cast<ObjCClassMember*>(fastInfo->data),
1892+
HermesFastThisArg(fastInfo), value);
1893+
}
1894+
#endif
1895+
18301896
napi_value jsThis, argv;
18311897
size_t argc = 1;
1832-
ObjCClassMember* method;
1898+
ObjCClassMember* method = nullptr;
18331899

18341900
napi_get_cb_info(env, cbinfo, &argc, &argv, &jsThis, (void**)&method);
18351901

1902+
return jsSetterDirect(env, method, jsThis, argv);
1903+
}
1904+
1905+
napi_value ObjCClassMember::jsSetterDirect(napi_env env, ObjCClassMember* method,
1906+
napi_value jsThis, napi_value value) {
1907+
if (method == nullptr) {
1908+
napi_throw_error(env, "NativeScriptException", "Missing Objective-C setter metadata.");
1909+
return nullptr;
1910+
}
1911+
18361912
id self = assertSelf(env, jsThis, method);
18371913

18381914
if (self == nullptr) {
@@ -1850,7 +1926,7 @@ NativeScriptException nativeScriptException(errorMessage != nullptr ? errorMessa
18501926
}
18511927

18521928
if (cif->argc > 0) {
1853-
cif->argv[0] = argv;
1929+
cif->argv[0] = value;
18541930
}
18551931

18561932
bool didDirectInvoke = false;
@@ -1875,7 +1951,7 @@ NativeScriptException nativeScriptException(errorMessage != nullptr ? errorMessa
18751951
void* rvalue = nullptr;
18761952

18771953
bool shouldFree = false;
1878-
cif->argTypes[0]->toNative(env, argv, avalues[2], &shouldFree, &shouldFree);
1954+
cif->argTypes[0]->toNative(env, value, avalues[2], &shouldFree, &shouldFree);
18791955

18801956
if (!objcNativeCall(env, cif, self, receiverIsClass, &method->setter,
18811957
method->setter.dispatchFlags, avalues, rvalue)) {
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
#ifndef NS_HERMES_FAST_CALLBACK_INFO_H
2+
#define NS_HERMES_FAST_CALLBACK_INFO_H
3+
4+
#include "js_native_api.h"
5+
6+
#ifdef TARGET_ENGINE_HERMES
7+
8+
#include <cstddef>
9+
#include <cstdint>
10+
11+
namespace nativescript {
12+
13+
struct HermesFastCallbackInfo {
14+
napi_env env = nullptr;
15+
const uint64_t* thisArg = nullptr;
16+
const uint64_t* argsBase = nullptr;
17+
unsigned int argc = 0;
18+
void* data = nullptr;
19+
const uint64_t* newTarget = nullptr;
20+
};
21+
22+
inline const HermesFastCallbackInfo* TryGetHermesFastCallbackInfo(
23+
napi_env env, napi_callback_info cbinfo) {
24+
if (env == nullptr || cbinfo == nullptr) {
25+
return nullptr;
26+
}
27+
28+
auto* info = reinterpret_cast<const HermesFastCallbackInfo*>(cbinfo);
29+
if (info->env != env || info->thisArg == nullptr || info->argsBase == nullptr) {
30+
return nullptr;
31+
}
32+
33+
return info;
34+
}
35+
36+
inline napi_value HermesFastThisArg(const HermesFastCallbackInfo* info) {
37+
return reinterpret_cast<napi_value>(const_cast<uint64_t*>(info->thisArg));
38+
}
39+
40+
inline napi_value HermesFastArg(const HermesFastCallbackInfo* info,
41+
size_t index) {
42+
if (index >= info->argc) {
43+
return nullptr;
44+
}
45+
46+
return reinterpret_cast<napi_value>(
47+
const_cast<uint64_t*>(info->argsBase - (index + 1)));
48+
}
49+
50+
} // namespace nativescript
51+
52+
#endif // TARGET_ENGINE_HERMES
53+
54+
#endif // NS_HERMES_FAST_CALLBACK_INFO_H

0 commit comments

Comments
 (0)