Skip to content

Commit fa8e889

Browse files
committed
Add foundation call benchmarks and runtime comparison scripts
- Introduced `foundation_calls.js` for benchmarking various Foundation API calls. - Created `run_foundation_bench.js` to execute benchmarks and compare GSD vs non-GSD runtimes. - Enhanced memory test script to accept a custom runtime path via `--runtime` argument. - Updated `SignatureDispatchEmitter.cpp` to improve signature handling and NAPI wrapper generation. - Added support for canonicalizing signature types and optimized argument conversion for NAPI.
1 parent 3beec7c commit fa8e889

12 files changed

Lines changed: 1441 additions & 106405 deletions

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,6 @@ dist
6363
packages/*/types
6464

6565
SwiftBindgen
66+
67+
# Generated Objective-C/C dispatch wrappers
68+
NativeScript/ffi/GeneratedSignatureDispatch.inc

NativeScript/ffi/CFunction.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ class CFunction {
1616
void* fnptr;
1717
Cif* cif = nullptr;
1818
uint8_t dispatchFlags = 0;
19+
bool dispatchLookupCached = false;
20+
uint64_t dispatchLookupSignatureHash = 0;
21+
uint64_t dispatchId = 0;
22+
void* preparedInvoker = nullptr;
23+
void* napiInvoker = nullptr;
1924
};
2025

2126
} // namespace nativescript

NativeScript/ffi/CFunction.mm

Lines changed: 60 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,33 @@
1111

1212
namespace nativescript {
1313

14+
inline void ensureCFunctionDispatchLookup(CFunction* function, Cif* cif) {
15+
if (function == nullptr || cif == nullptr || cif->signatureHash == 0) {
16+
if (function != nullptr) {
17+
function->dispatchLookupCached = true;
18+
function->dispatchLookupSignatureHash = 0;
19+
function->dispatchId = 0;
20+
function->preparedInvoker = nullptr;
21+
function->napiInvoker = nullptr;
22+
}
23+
return;
24+
}
25+
26+
if (function->dispatchLookupCached &&
27+
function->dispatchLookupSignatureHash == cif->signatureHash) {
28+
return;
29+
}
30+
31+
function->dispatchLookupSignatureHash = cif->signatureHash;
32+
function->dispatchId = composeSignatureDispatchId(
33+
cif->signatureHash, SignatureCallKind::CFunction, function->dispatchFlags);
34+
function->preparedInvoker =
35+
reinterpret_cast<void*>(lookupCFunctionPreparedInvoker(function->dispatchId));
36+
function->napiInvoker =
37+
reinterpret_cast<void*>(lookupCFunctionNapiInvoker(function->dispatchId));
38+
function->dispatchLookupCached = true;
39+
}
40+
1441
void ObjCBridgeState::registerFunctionGlobals(napi_env env, napi_value global) {
1542
MDSectionOffset offset = metadata->functionsOffset;
1643
while (offset < metadata->protocolsOffset) {
@@ -65,20 +92,40 @@
6592
auto func = bridgeState->getCFunction(env, offset);
6693

6794
auto cif = func->cif;
68-
69-
const uint64_t dispatchId =
70-
cif != nullptr && cif->signatureHash != 0
71-
? composeSignatureDispatchId(cif->signatureHash, SignatureCallKind::CFunction,
72-
func->dispatchFlags)
73-
: 0;
74-
auto invoker = dispatchId != 0 ? lookupCFunctionPreparedInvoker(dispatchId) : nullptr;
95+
ensureCFunctionDispatchLookup(func, cif);
96+
auto preparedInvoker = reinterpret_cast<CFunctionPreparedInvoker>(func->preparedInvoker);
97+
auto napiInvoker = reinterpret_cast<CFunctionNapiInvoker>(func->napiInvoker);
7598

7699
MDFunctionFlag functionFlags = bridgeState->metadata->getFunctionFlag(
77100
offset + sizeof(MDSectionOffset) * 2);
78101

79102
size_t argc = cif->argc;
80103
napi_get_cb_info(env, cbinfo, &argc, cif->argv, nullptr, nullptr);
81104

105+
uint32_t toJSFlags = kCStringAsReference;
106+
if ((functionFlags & mdFunctionReturnOwned) != 0) {
107+
toJSFlags |= kReturnOwned;
108+
}
109+
110+
const bool isMainEntrypoint =
111+
strcmp(name, "UIApplicationMain") == 0 || strcmp(name, "NSApplicationMain") == 0;
112+
113+
if (napiInvoker != nullptr && !isMainEntrypoint) {
114+
@try {
115+
if (!napiInvoker(env, cif, func->fnptr, cif->argv, cif->rvalue)) {
116+
return nullptr;
117+
}
118+
} @catch (NSException* exception) {
119+
std::string message = exception.description.UTF8String;
120+
NSLog(@"ObjC->JS: Exception in CFunction (direct): %s", message.c_str());
121+
nativescript::NativeScriptException nativeScriptException(message);
122+
nativeScriptException.ReThrowToJS(env);
123+
return nullptr;
124+
}
125+
126+
return cif->returnType->toJS(env, cif->rvalue, toJSFlags);
127+
}
128+
82129
void* avalues[cif->argc];
83130
void* rvalue = cif->rvalue;
84131

@@ -94,18 +141,18 @@
94141
}
95142

96143
#ifdef ENABLE_JS_RUNTIME
97-
if (strcmp(name, "UIApplicationMain") == 0 || strcmp(name, "NSApplicationMain") == 0) {
144+
if (isMainEntrypoint) {
98145
void** avaluesPtr = new void*[cif->argc];
99146
memcpy(avaluesPtr, avalues, cif->argc * sizeof(void*));
100147

101-
Tasks::Register([env, cif, func, invoker, rvalue, avaluesPtr]() {
148+
Tasks::Register([env, cif, func, preparedInvoker, rvalue, avaluesPtr]() {
102149
void* avalues[cif->argc];
103150
memcpy(avalues, avaluesPtr, cif->argc * sizeof(void*));
104151
delete[] avaluesPtr;
105152

106153
@try {
107-
if (invoker != nullptr) {
108-
invoker(func->fnptr, avalues, rvalue);
154+
if (preparedInvoker != nullptr) {
155+
preparedInvoker(func->fnptr, avalues, rvalue);
109156
} else {
110157
ffi_call(&cif->cif, FFI_FN(func->fnptr), rvalue, avalues);
111158
}
@@ -123,8 +170,8 @@
123170
#endif
124171

125172
@try {
126-
if (invoker != nullptr) {
127-
invoker(func->fnptr, avalues, rvalue);
173+
if (preparedInvoker != nullptr) {
174+
preparedInvoker(func->fnptr, avalues, rvalue);
128175
} else {
129176
ffi_call(&cif->cif, FFI_FN(func->fnptr), rvalue, avalues);
130177
}
@@ -156,11 +203,6 @@
156203
}
157204
}
158205

159-
uint32_t toJSFlags = kCStringAsReference;
160-
if ((functionFlags & mdFunctionReturnOwned) != 0) {
161-
toJSFlags |= kReturnOwned;
162-
}
163-
164206
return cif->returnType->toJS(env, rvalue, toJSFlags);
165207
}
166208

NativeScript/ffi/Cif.mm

Lines changed: 176 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
#include <algorithm>
44
#include <cstring>
55
#include <iostream>
6+
#include <type_traits>
7+
#include <unordered_set>
68
#include <vector>
79
#include "Metadata.h"
810
#include "MetadataReader.h"
@@ -16,16 +18,181 @@
1618
constexpr uint64_t kFNV64OffsetBasis = 14695981039346656037ull;
1719
constexpr uint64_t kFNV64Prime = 1099511628211ull;
1820

19-
uint64_t hashBytesFnv1a(const void* data, size_t size) {
21+
uint64_t hashBytesFnv1a(const void* data, size_t size,
22+
uint64_t seed = kFNV64OffsetBasis) {
2023
const auto* bytes = static_cast<const uint8_t*>(data);
21-
uint64_t hash = kFNV64OffsetBasis;
24+
uint64_t hash = seed;
2225
for (size_t i = 0; i < size; i++) {
2326
hash ^= static_cast<uint64_t>(bytes[i]);
2427
hash *= kFNV64Prime;
2528
}
2629
return hash;
2730
}
2831

32+
MDTypeKind canonicalizeSignatureTypeKind(MDTypeKind kind) {
33+
switch (kind) {
34+
case mdTypeAnyObject:
35+
case mdTypeProtocolObject:
36+
case mdTypeClassObject:
37+
case mdTypeInstanceObject:
38+
case mdTypeNSStringObject:
39+
case mdTypeNSMutableStringObject:
40+
return mdTypeAnyObject;
41+
default:
42+
return kind;
43+
}
44+
}
45+
46+
template <typename T>
47+
void appendIntegralToHash(uint64_t* hash, T value) {
48+
using Unsigned = typename std::make_unsigned<T>::type;
49+
Unsigned unsignedValue = static_cast<Unsigned>(value);
50+
for (size_t i = 0; i < sizeof(Unsigned); i++) {
51+
const uint8_t byte = static_cast<uint8_t>((unsignedValue >> (i * 8)) & 0xFF);
52+
*hash = hashBytesFnv1a(&byte, sizeof(byte), *hash);
53+
}
54+
}
55+
56+
bool appendMetadataSignatureHash(MDMetadataReader* reader,
57+
MDSectionOffset signatureOffset,
58+
std::unordered_set<MDSectionOffset>* activeSignatures,
59+
uint64_t* hash);
60+
61+
bool appendMetadataTypeHash(MDMetadataReader* reader, MDSectionOffset* offset,
62+
std::unordered_set<MDSectionOffset>* activeSignatures,
63+
uint64_t* hash) {
64+
if (reader == nullptr || offset == nullptr || hash == nullptr ||
65+
activeSignatures == nullptr) {
66+
return false;
67+
}
68+
69+
const MDTypeKind kindWithFlags = reader->getTypeKind(*offset);
70+
*offset += sizeof(MDTypeKind);
71+
const MDTypeKind rawKind =
72+
static_cast<MDTypeKind>((kindWithFlags & ~mdTypeFlagNext) &
73+
~mdTypeFlagVariadic);
74+
75+
appendIntegralToHash<uint8_t>(hash, 0xB0);
76+
const MDTypeKind canonicalKind = canonicalizeSignatureTypeKind(rawKind);
77+
appendIntegralToHash<uint8_t>(hash, static_cast<uint8_t>(canonicalKind));
78+
79+
switch (rawKind) {
80+
case mdTypeArray:
81+
case mdTypeVector:
82+
case mdTypeExtVector:
83+
case mdTypeComplex: {
84+
const auto arraySize = reader->getArraySize(*offset);
85+
*offset += sizeof(uint16_t);
86+
appendIntegralToHash<uint16_t>(hash, arraySize);
87+
if (!appendMetadataTypeHash(reader, offset, activeSignatures, hash)) {
88+
return false;
89+
}
90+
break;
91+
}
92+
93+
case mdTypeStruct: {
94+
const auto structOffset = reader->getOffset(*offset);
95+
*offset += sizeof(MDSectionOffset);
96+
appendIntegralToHash<MDSectionOffset>(hash, structOffset);
97+
break;
98+
}
99+
100+
case mdTypeClassObject: {
101+
auto classOffset = reader->getOffset(*offset);
102+
*offset += sizeof(MDSectionOffset);
103+
bool hasNext = (classOffset & mdSectionOffsetNext) != 0;
104+
while (hasNext) {
105+
auto protocolOffset = reader->getOffset(*offset);
106+
*offset += sizeof(MDSectionOffset);
107+
hasNext = (protocolOffset & mdSectionOffsetNext) != 0;
108+
}
109+
break;
110+
}
111+
112+
case mdTypeProtocolObject: {
113+
bool hasNext = true;
114+
while (hasNext) {
115+
auto protocolOffset = reader->getOffset(*offset);
116+
*offset += sizeof(MDSectionOffset);
117+
hasNext = (protocolOffset & mdSectionOffsetNext) != 0;
118+
}
119+
break;
120+
}
121+
122+
case mdTypePointer:
123+
if (!appendMetadataTypeHash(reader, offset, activeSignatures, hash)) {
124+
return false;
125+
}
126+
break;
127+
128+
case mdTypeBlock:
129+
case mdTypeFunctionPointer: {
130+
const auto nestedSignatureOffset = reader->getOffset(*offset);
131+
*offset += sizeof(MDSectionOffset);
132+
if (nestedSignatureOffset != MD_SECTION_OFFSET_NULL) {
133+
const auto nestedAbsoluteOffset =
134+
reader->signaturesOffset + nestedSignatureOffset;
135+
if (!appendMetadataSignatureHash(reader, nestedAbsoluteOffset,
136+
activeSignatures, hash)) {
137+
return false;
138+
}
139+
}
140+
break;
141+
}
142+
143+
default:
144+
break;
145+
}
146+
147+
appendIntegralToHash<uint8_t>(hash, 0xBF);
148+
return true;
149+
}
150+
151+
bool appendMetadataSignatureHash(MDMetadataReader* reader,
152+
MDSectionOffset signatureOffset,
153+
std::unordered_set<MDSectionOffset>* activeSignatures,
154+
uint64_t* hash) {
155+
if (reader == nullptr || hash == nullptr || activeSignatures == nullptr) {
156+
return false;
157+
}
158+
159+
if (activeSignatures->find(signatureOffset) != activeSignatures->end()) {
160+
appendIntegralToHash<uint8_t>(hash, 0xEE);
161+
return true;
162+
}
163+
activeSignatures->insert(signatureOffset);
164+
165+
MDSectionOffset offset = signatureOffset;
166+
const MDTypeKind returnTypeKind = reader->getTypeKind(offset);
167+
bool next = (returnTypeKind & mdTypeFlagNext) != 0;
168+
const bool isVariadic = (returnTypeKind & mdTypeFlagVariadic) != 0;
169+
170+
appendIntegralToHash<uint8_t>(hash, 0xA0);
171+
appendIntegralToHash<uint8_t>(hash, isVariadic ? 1 : 0);
172+
173+
if (!appendMetadataTypeHash(reader, &offset, activeSignatures, hash)) {
174+
activeSignatures->erase(signatureOffset);
175+
return false;
176+
}
177+
178+
uint32_t argCount = 0;
179+
while (next) {
180+
const MDTypeKind argTypeKind = reader->getTypeKind(offset);
181+
next = (argTypeKind & mdTypeFlagNext) != 0;
182+
if (!appendMetadataTypeHash(reader, &offset, activeSignatures, hash)) {
183+
activeSignatures->erase(signatureOffset);
184+
return false;
185+
}
186+
argCount++;
187+
}
188+
189+
appendIntegralToHash<uint32_t>(hash, argCount);
190+
appendIntegralToHash<uint8_t>(hash, 0xAF);
191+
192+
activeSignatures->erase(signatureOffset);
193+
return true;
194+
}
195+
29196
} // namespace
30197

31198
// Essentially, we cache libffi structures per unique method signature,
@@ -267,11 +434,13 @@ uint64_t hashBytesFnv1a(const void* data, size_t size) {
267434
rvalue = malloc(cif.rtype->size);
268435
rvalueLength = cif.rtype->size;
269436

270-
const size_t signatureLength = static_cast<size_t>(offset - signatureStart);
271-
if (signatureLength > 0) {
272-
const auto* signatureBytes =
273-
reinterpret_cast<const uint8_t*>(reader->data) + signatureStart;
274-
signatureHash = hashBytesFnv1a(signatureBytes, signatureLength);
437+
if (signatureStart != MD_SECTION_OFFSET_NULL) {
438+
uint64_t canonicalSignatureHash = kFNV64OffsetBasis;
439+
std::unordered_set<MDSectionOffset> activeSignatures;
440+
if (appendMetadataSignatureHash(reader, signatureStart, &activeSignatures,
441+
&canonicalSignatureHash)) {
442+
signatureHash = canonicalSignatureHash;
443+
}
275444
}
276445
}
277446

NativeScript/ffi/ClassMember.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ class MethodDescriptor {
2727
uint8_t dispatchFlags = 0;
2828
std::string encoding;
2929
bool isProperty = false;
30+
bool dispatchLookupCached = false;
31+
uint64_t dispatchLookupSignatureHash = 0;
32+
uint8_t dispatchLookupFlags = 0;
33+
uint64_t dispatchId = 0;
34+
void* preparedInvoker = nullptr;
35+
void* napiInvoker = nullptr;
3036

3137
MethodDescriptor() {}
3238

0 commit comments

Comments
 (0)