Skip to content

Commit f7006f9

Browse files
committed
perf(ios): tighten generated v8 dispatch
1 parent 5c54d27 commit f7006f9

5 files changed

Lines changed: 327 additions & 75 deletions

File tree

NativeScript/ffi/Cif.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ class Cif {
2121
bool isVariadic = false;
2222
uint64_t signatureHash = 0;
2323
bool skipGeneratedNapiDispatch = false;
24+
bool generatedDispatchHasRoundTripCacheArgument = false;
25+
bool generatedDispatchUsesObjectReturnStorage = false;
26+
bool generatedDispatchSetsV8ReturnDirectly = false;
2427

2528
void* rvalue;
2629
void** avalues;

NativeScript/ffi/Cif.mm

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,17 +75,69 @@ inline bool typeRequiresSlowGeneratedNapiDispatch(const std::shared_ptr<TypeConv
7575
}
7676
}
7777

78+
inline bool typeKindMayUseRoundTripCache(MDTypeKind kind) {
79+
switch (kind) {
80+
case mdTypeAnyObject:
81+
case mdTypeProtocolObject:
82+
case mdTypeClassObject:
83+
case mdTypeInstanceObject:
84+
case mdTypeNSStringObject:
85+
case mdTypeNSMutableStringObject:
86+
return true;
87+
default:
88+
return false;
89+
}
90+
}
91+
92+
inline bool typeKindCanSetV8ReturnDirectly(MDTypeKind kind) {
93+
switch (kind) {
94+
case mdTypeVoid:
95+
case mdTypeBool:
96+
case mdTypeChar:
97+
case mdTypeUChar:
98+
case mdTypeUInt8:
99+
case mdTypeSShort:
100+
case mdTypeUShort:
101+
case mdTypeSInt:
102+
case mdTypeUInt:
103+
case mdTypeSLong:
104+
case mdTypeULong:
105+
case mdTypeSInt64:
106+
case mdTypeUInt64:
107+
case mdTypeFloat:
108+
case mdTypeDouble:
109+
return true;
110+
default:
111+
return false;
112+
}
113+
}
114+
78115
inline void updateGeneratedNapiDispatchCompatibility(Cif* cif) {
79116
if (cif == nullptr) {
80117
return;
81118
}
82119

120+
cif->skipGeneratedNapiDispatch = false;
121+
cif->generatedDispatchHasRoundTripCacheArgument = false;
122+
cif->generatedDispatchUsesObjectReturnStorage = false;
123+
cif->generatedDispatchSetsV8ReturnDirectly = false;
124+
125+
if (cif->returnType != nullptr) {
126+
cif->generatedDispatchUsesObjectReturnStorage =
127+
typeKindMayUseRoundTripCache(cif->returnType->kind);
128+
cif->generatedDispatchSetsV8ReturnDirectly =
129+
typeKindCanSetV8ReturnDirectly(cif->returnType->kind);
130+
}
131+
83132
cif->skipGeneratedNapiDispatch = typeRequiresSlowGeneratedNapiDispatch(cif->returnType);
84133
if (cif->skipGeneratedNapiDispatch) {
85134
return;
86135
}
87136

88137
for (const auto& argType : cif->argTypes) {
138+
if (argType != nullptr && typeKindMayUseRoundTripCache(argType->kind)) {
139+
cif->generatedDispatchHasRoundTripCacheArgument = true;
140+
}
89141
if (typeRequiresSlowGeneratedNapiDispatch(argType)) {
90142
cif->skipGeneratedNapiDispatch = true;
91143
return;

NativeScript/ffi/SignatureDispatch.h

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,14 @@ using CFunctionNapiInvoker = bool (*)(napi_env env, Cif* cif, void* fnptr,
3636

3737
#ifdef TARGET_ENGINE_V8
3838
using ObjCV8Invoker = bool (*)(napi_env env, Cif* cif, void* fnptr, id self,
39-
SEL selector,
39+
SEL selector, void* bridgeState, bool returnOwned,
40+
bool receiverIsClass, bool propertyAccess,
4041
const v8::FunctionCallbackInfo<v8::Value>& info,
41-
void* rvalue);
42+
void* rvalue, bool* didSetReturnValue);
4243
using CFunctionV8Invoker =
4344
bool (*)(napi_env env, Cif* cif, void* fnptr,
44-
const v8::FunctionCallbackInfo<v8::Value>& info, void* rvalue);
45+
const v8::FunctionCallbackInfo<v8::Value>& info, void* rvalue,
46+
bool* didSetReturnValue);
4547
#endif
4648

4749
struct ObjCDispatchEntry {
@@ -111,6 +113,33 @@ static_assert(sizeof(v8::Local<v8::Value>) == sizeof(napi_value),
111113
inline napi_value v8LocalValueToNapiValue(v8::Local<v8::Value> local) {
112114
return reinterpret_cast<napi_value>(*local);
113115
}
116+
117+
inline void setV8DispatchInt64ReturnValue(
118+
v8::Isolate* isolate, const v8::FunctionCallbackInfo<v8::Value>& info,
119+
int64_t value) {
120+
constexpr int64_t kMaxSafeInteger = 9007199254740991LL;
121+
if (value > kMaxSafeInteger || value < -kMaxSafeInteger) {
122+
info.GetReturnValue().Set(v8::BigInt::New(isolate, value));
123+
} else {
124+
info.GetReturnValue().Set(static_cast<double>(value));
125+
}
126+
}
127+
128+
inline void setV8DispatchUInt64ReturnValue(
129+
v8::Isolate* isolate, const v8::FunctionCallbackInfo<v8::Value>& info,
130+
uint64_t value) {
131+
constexpr uint64_t kMaxSafeInteger = 9007199254740991ULL;
132+
if (value > kMaxSafeInteger) {
133+
info.GetReturnValue().Set(v8::BigInt::NewFromUnsigned(isolate, value));
134+
} else {
135+
info.GetReturnValue().Set(static_cast<double>(value));
136+
}
137+
}
138+
139+
bool TryFastSetV8GeneratedObjCObjectReturnValue(
140+
napi_env env, const v8::FunctionCallbackInfo<v8::Value>& info,
141+
Cif* cif, void* bridgeState, id self, SEL selector, id value,
142+
bool returnOwned, bool receiverIsClass, bool propertyAccess);
114143
#endif
115144

116145
} // namespace nativescript

NativeScript/ffi/V8FastNativeApi.mm

Lines changed: 89 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -906,6 +906,48 @@ bool TryFastSetV8ObjectReturnValue(napi_env env,
906906
return true;
907907
}
908908

909+
} // namespace
910+
911+
bool TryFastSetV8GeneratedObjCObjectReturnValue(
912+
napi_env env, const v8::FunctionCallbackInfo<v8::Value>& info, Cif* cif,
913+
void* bridgeState, id self, SEL selector, id value, bool returnOwned,
914+
bool receiverIsClass, bool propertyAccess) {
915+
(void)propertyAccess;
916+
auto* state = static_cast<ObjCBridgeState*>(bridgeState);
917+
if (env == nullptr || state == nullptr || cif == nullptr || cif->returnType == nullptr) {
918+
return false;
919+
}
920+
921+
if (selector == @selector(class)) {
922+
return false;
923+
}
924+
925+
switch (cif->returnType->kind) {
926+
case mdTypeAnyObject:
927+
case mdTypeProtocolObject:
928+
case mdTypeClassObject:
929+
case mdTypeNSStringObject:
930+
break;
931+
default:
932+
return false;
933+
}
934+
935+
if (receiverIsClass && value != nil) {
936+
Class receiverClass = (Class)self;
937+
if ((receiverClass == [NSString class] || receiverClass == [NSMutableString class]) &&
938+
(selector == @selector(string) ||
939+
selector == @selector(stringWithString:) ||
940+
selector == @selector(stringWithCapacity:))) {
941+
return false;
942+
}
943+
}
944+
945+
return TryFastSetV8ObjectReturnValue(
946+
env, info, state, value, returnOwned ? kOwnedObject : kUnownedObject);
947+
}
948+
949+
namespace {
950+
909951
inline size_t alignUpSize(size_t value, size_t alignment) {
910952
if (alignment == 0) {
911953
return value;
@@ -1287,28 +1329,6 @@ CFunctionV8Invoker ensureCFunctionV8Invoker(CFunction* function, Cif* cif) {
12871329
return reinterpret_cast<CFunctionV8Invoker>(function->v8Invoker);
12881330
}
12891331

1290-
inline bool typeKindMayUseRoundTripCache(MDTypeKind kind) {
1291-
switch (kind) {
1292-
case mdTypeAnyObject:
1293-
case mdTypeProtocolObject:
1294-
case mdTypeClassObject:
1295-
case mdTypeInstanceObject:
1296-
case mdTypeNSStringObject:
1297-
case mdTypeNSMutableStringObject:
1298-
return true;
1299-
default:
1300-
return false;
1301-
}
1302-
}
1303-
1304-
inline bool cifMayUseRoundTripCache(Cif* cif) {
1305-
if (cif == nullptr) {
1306-
return false;
1307-
}
1308-
1309-
return cif->returnType != nullptr && typeKindMayUseRoundTripCache(cif->returnType->kind);
1310-
}
1311-
13121332
inline bool selectorEndsWith(SEL selector, const char* suffix) {
13131333
if (selector == nullptr || suffix == nullptr) {
13141334
return false;
@@ -2317,20 +2337,38 @@ bool invokeObjCFast(napi_env env, const v8::FunctionCallbackInfo<v8::Value>& inf
23172337
}
23182338
const bool hasImplicitNSErrorOutArg =
23192339
isNSErrorOutMethod && !cif->isVariadic && actualArgc + 1 == cif->argc;
2320-
if (!isNSErrorOutMethod && !requiresSuperCall &&
2321-
tryInvokeObjCV8DirectFastPath(env, info, method, descriptor, cif, self)) {
2322-
return true;
2340+
const bool canUseGeneratedDispatch = !isNSErrorOutMethod && !requiresSuperCall;
2341+
ObjCV8Invoker invoker =
2342+
canUseGeneratedDispatch ? ensureObjCV8Invoker(cif, descriptor, descriptor->dispatchFlags)
2343+
: nullptr;
2344+
if (invoker == nullptr) {
2345+
if (canUseGeneratedDispatch &&
2346+
tryInvokeObjCV8DirectFastPath(env, info, method, descriptor, cif, self)) {
2347+
return true;
2348+
}
2349+
2350+
return invokeObjCSlow(env, info, method, descriptor, cif, self, receiverIsClass,
2351+
propertyAccess);
23232352
}
23242353

2325-
const bool needsRoundTripCache = cifMayUseRoundTripCache(cif);
2354+
const bool generatedDispatchSetsReturnDirectly =
2355+
cif->generatedDispatchSetsV8ReturnDirectly;
2356+
const bool generatedDispatchUsesObjectReturnStorage =
2357+
!generatedDispatchSetsReturnDirectly && cif->generatedDispatchUsesObjectReturnStorage;
2358+
const bool needsRoundTripCache =
2359+
generatedDispatchUsesObjectReturnStorage &&
2360+
cif->generatedDispatchHasRoundTripCacheArgument;
23262361
std::optional<RoundTripCacheFrameGuard> roundTripCacheFrame;
23272362
if (needsRoundTripCache) {
23282363
roundTripCacheFrame.emplace(env, method->bridgeState);
23292364
}
23302365

23312366
std::optional<V8CifReturnStorage> rvalueStorage;
2367+
id objectRvalue = nil;
23322368
void* rvalue = nullptr;
2333-
if (cif->returnType == nullptr || cif->returnType->kind != mdTypeVoid) {
2369+
if (generatedDispatchUsesObjectReturnStorage) {
2370+
rvalue = &objectRvalue;
2371+
} else if (!generatedDispatchSetsReturnDirectly) {
23342372
rvalueStorage.emplace(cif);
23352373
if (!rvalueStorage->valid()) {
23362374
throwV8Error(info.GetIsolate(),
@@ -2341,26 +2379,32 @@ bool invokeObjCFast(napi_env env, const v8::FunctionCallbackInfo<v8::Value>& inf
23412379
}
23422380

23432381
bool didInvoke = false;
2344-
ObjCV8Invoker invoker =
2345-
requiresSuperCall ? nullptr : ensureObjCV8Invoker(cif, descriptor, descriptor->dispatchFlags);
2346-
if (!isNSErrorOutMethod && invoker != nullptr) {
2347-
@try {
2348-
didInvoke = invoker(env, cif, (void*)objc_msgSend, self, descriptor->selector, info, rvalue);
2349-
} @catch (NSException* exception) {
2350-
std::string message = exception.description.UTF8String;
2351-
NativeScriptException nativeScriptException(message);
2352-
throwNativeScriptExceptionToV8(env, info.GetIsolate(), nativeScriptException);
2353-
return false;
2354-
}
2382+
bool didSetReturnValue = false;
2383+
@try {
2384+
didInvoke = invoker(env, cif, (void*)objc_msgSend, self, descriptor->selector,
2385+
method->bridgeState, method->returnOwned, receiverIsClass,
2386+
propertyAccess, info, rvalue, &didSetReturnValue);
2387+
} @catch (NSException* exception) {
2388+
std::string message = exception.description.UTF8String;
2389+
NativeScriptException nativeScriptException(message);
2390+
throwNativeScriptExceptionToV8(env, info.GetIsolate(), nativeScriptException);
2391+
return false;
23552392
}
23562393

23572394
if (!didInvoke) {
2395+
if (canUseGeneratedDispatch &&
2396+
tryInvokeObjCV8DirectFastPath(env, info, method, descriptor, cif, self)) {
2397+
return true;
2398+
}
2399+
23582400
return invokeObjCSlow(env, info, method, descriptor, cif, self, receiverIsClass,
23592401
propertyAccess);
23602402
}
23612403

2362-
setObjCReturnValue(env, info, method, descriptor, cif, self, receiverIsClass, rvalue,
2363-
propertyAccess);
2404+
if (!didSetReturnValue) {
2405+
setObjCReturnValue(env, info, method, descriptor, cif, self, receiverIsClass, rvalue,
2406+
propertyAccess);
2407+
}
23642408
return true;
23652409
}
23662410

@@ -2557,9 +2601,10 @@ void v8CFunctionCallback(const v8::FunctionCallbackInfo<v8::Value>& info) {
25572601
CFunctionV8Invoker invoker = ensureCFunctionV8Invoker(function, cif);
25582602

25592603
bool didInvoke = false;
2604+
bool didSetReturnValue = false;
25602605
if (invoker != nullptr) {
25612606
@try {
2562-
didInvoke = invoker(env, cif, function->fnptr, info, cif->rvalue);
2607+
didInvoke = invoker(env, cif, function->fnptr, info, cif->rvalue, &didSetReturnValue);
25632608
} @catch (NSException* exception) {
25642609
std::string message = exception.description.UTF8String;
25652610
NativeScriptException nativeScriptException(message);
@@ -2573,7 +2618,9 @@ void v8CFunctionCallback(const v8::FunctionCallbackInfo<v8::Value>& info) {
25732618
return;
25742619
}
25752620

2576-
setCFunctionReturnValue(env, info, function, cif, cif->rvalue);
2621+
if (!didSetReturnValue) {
2622+
setCFunctionReturnValue(env, info, function, cif, cif->rvalue);
2623+
}
25772624
}
25782625

25792626
bool isCompatLibdispatchFunction(ObjCBridgeState* bridgeState, MDSectionOffset offset) {

0 commit comments

Comments
 (0)