Skip to content

Commit c589999

Browse files
committed
refactor(runtime): split engine backends and harden lifetimes (#51)
* refactor(runtime): split engine backends and harden lifetimes * refactor(runtime): consolidate engine selector dispatch
1 parent c5edc94 commit c589999

33 files changed

Lines changed: 3331 additions & 3480 deletions

NativeScript/ffi/objc/hermes/NativeApiJsi.mm

Lines changed: 43 additions & 164 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ void SetNativeApiObjectPrototype(Runtime& runtime, Object& object,
9191

9292
#include "NativeApiJsiGsd.mm"
9393

94+
#include "../shared/bridge/SelectorGroupCall.h"
95+
9496

9597
void* lookupGeneratedEngineObjCGsdInvoker(uint64_t dispatchId) {
9698
return reinterpret_cast<void*>(lookupObjCGsdInvoker(dispatchId));
@@ -123,207 +125,84 @@ Function CreateNativeApiSelectorGroupFunctionImpl(
123125
std::shared_ptr<
124126
std::vector<std::shared_ptr<NativeApiPreparedObjCInvocation>>>
125127
preparedInvocations,
126-
std::weak_ptr<NativeApiObjectHostObject> boundReceiver = {},
127-
std::shared_ptr<NativeApiObjectLifetimeState> boundReceiverState =
128-
nullptr) {
128+
std::weak_ptr<NativeApiObjectHostObject> boundReceiver,
129+
std::shared_ptr<NativeApiObjectLifetimeState> boundReceiverState) {
130+
NativeApiSelectorGroupState state(
131+
std::move(bridge), lookupClass, receiverIsClass, std::move(selectors),
132+
std::move(preparedInvocations), std::move(boundReceiver),
133+
std::move(boundReceiverState));
129134
return Function::createFromHostFunction(
130135
runtime, PropNameID::forAscii(runtime, "__nativeSelectorGroup"), 0,
131-
[bridge = std::move(bridge), lookupClass, receiverIsClass,
132-
selectors = std::move(selectors),
133-
preparedInvocations = std::move(preparedInvocations),
134-
boundReceiver = std::move(boundReceiver),
135-
boundReceiverState = std::move(boundReceiverState),
136-
cachedReceiverClass = Class(Nil),
137-
cachedDispatchClass = Class(Nil)](
136+
[state = std::move(state)](
138137
Runtime& runtime, const Value& thisValue, const Value* args,
139138
size_t count) mutable -> Value {
140-
NativeApiRoundTripCacheFrameGuard roundTripFrame(bridge);
141-
if (count >= selectors->size() ||
142-
(*selectors)[count].selectorName.empty()) {
143-
throw JSError(runtime,
144-
"Objective-C selector is not available for the provided "
145-
"arguments count.");
146-
}
147-
148-
NativeApiSelectorGroupEntry& entry = (*selectors)[count];
149-
auto& prepared = (*preparedInvocations)[count];
150-
Class selectorLookupClass = lookupClass;
151-
id receiver = receiverIsClass ? static_cast<id>(lookupClass) : nil;
139+
NativeApiRoundTripCacheFrameGuard roundTripFrame(state.bridge);
152140
std::shared_ptr<NativeApiObjectHostObject> receiverHostObject;
153-
if (!receiverIsClass) {
154-
if (boundReceiverState != nullptr) {
155-
receiver = boundReceiverState->object();
156-
if (receiver == nil) {
157-
throw JSError(runtime,
158-
"Objective-C selector requires a native receiver.");
159-
}
141+
auto resolveReceiverHost = [&]() {
142+
if (receiverHostObject) {
143+
return receiverHostObject;
144+
}
145+
if (state.boundReceiverState != nullptr) {
146+
receiverHostObject = state.boundReceiver.lock();
160147
} else if (thisValue.isObject()) {
161148
Object receiverObject = thisValue.asObject(runtime);
162149
if (receiverObject.isHostObject<NativeApiObjectHostObject>(
163150
runtime)) {
164151
receiverHostObject =
165152
receiverObject.getHostObject<NativeApiObjectHostObject>(
166153
runtime);
167-
receiver = receiverHostObject->object();
168-
}
169-
}
170-
}
171-
if (receiver == nil) {
172-
throw JSError(runtime,
173-
"Objective-C selector requires a native receiver.");
174-
}
175-
176-
const bool propertyGetterCall =
177-
entry.hasMember && entry.member.property && count == 0;
178-
const std::string* selectorNamePtr = &entry.selectorName;
179-
const NativeApiMember* selectedMember =
180-
entry.hasMember ? &entry.member : nullptr;
181-
bool callTargetCanPrepare = true;
182-
if (prepared == nullptr || propertyGetterCall) {
183-
NativeApiSelectorGroupCallTarget callTarget =
184-
selectorGroupCallTargetForEntry(receiver, selectorLookupClass,
185-
receiverIsClass, entry, count);
186-
selectorNamePtr = callTarget.selectorName;
187-
selectedMember = callTarget.member;
188-
callTargetCanPrepare = callTarget.canPrepare;
189-
if (prepared != nullptr && prepared->selectorName != *selectorNamePtr) {
190-
prepared = nullptr;
191-
}
192-
}
193-
const std::string& selectorName =
194-
prepared != nullptr && !propertyGetterCall ? prepared->selectorName
195-
: *selectorNamePtr;
196-
197-
if (receiverIsClass) {
198-
Class methodClass = prepared != nullptr ? prepared->receiverClass : Nil;
199-
if (methodClass == Nil) {
200-
SEL selector = sel_registerName(selectorName.c_str());
201-
methodClass =
202-
NativeApiClassHostObject::classRespondingToClassSelector(
203-
lookupClass, selector);
204-
}
205-
if (methodClass == Nil) {
206-
throw JSError(runtime,
207-
"Objective-C selector is not available: " +
208-
entry.selectorName);
209-
}
210-
selectorLookupClass = methodClass;
211-
receiver = static_cast<id>(methodClass);
212-
}
213-
if (propertyGetterCall && !callTargetCanPrepare) {
214-
return callObjCSelector(runtime, bridge, receiver, receiverIsClass,
215-
selectorName, selectedMember, nullptr, 0);
216-
}
217-
218-
if (prepared == nullptr) {
219-
if (!receiverIsClass) {
220-
SEL selector = sel_registerName(selectorName.c_str());
221-
if (class_getInstanceMethod(selectorLookupClass, selector) == nullptr) {
222-
Class receiverClass = object_getClass(receiver);
223-
if (class_getInstanceMethod(receiverClass, selector) != nullptr) {
224-
selectorLookupClass = receiverClass;
225-
}
226-
}
227-
}
228-
prepared = prepareNativeApiObjCInvocation(
229-
runtime, bridge, selectorLookupClass, receiverIsClass, selectorName,
230-
selectedMember);
231-
// Look up the engine-neutral GSD invoker for this signature.
232-
if (prepared->engineInvoker == nullptr) {
233-
uint64_t dispatchId = dispatchIdForEngineSignature(
234-
prepared->signature, SignatureCallKind::ObjCMethod);
235-
if (auto gsdInvoker = lookupObjCGsdInvoker(dispatchId)) {
236-
prepared->engineInvoker = reinterpret_cast<void*>(gsdInvoker);
237-
configureGeneratedEngineObjCInvocation(*prepared);
238154
}
239155
}
156+
return receiverHostObject;
157+
};
158+
auto call = resolveNativeApiSelectorGroupCall<false>(
159+
runtime, state, count,
160+
[&]() -> id {
161+
auto host = resolveReceiverHost();
162+
return host != nullptr ? host->object() : nil;
163+
},
164+
resolveReceiverHost,
165+
[](uint64_t dispatchId) {
166+
return lookupObjCGsdInvoker(dispatchId);
167+
});
168+
if (call.hasImmediateResult) {
169+
return std::move(call.immediateResult);
240170
}
241171

242-
// Memoized dispatch-superclass resolution (pure function of the
243-
// receiver's class + lookupClass) — avoids a per-call
244-
// class_conformsToProtocol probe.
245-
Class gsdDispatchClass = Nil;
246-
if (!receiverIsClass) {
247-
Class receiverClass = object_getClass(receiver);
248-
if (receiverClass == cachedReceiverClass) {
249-
gsdDispatchClass = cachedDispatchClass;
250-
} else {
251-
gsdDispatchClass =
252-
dispatchSuperclassForEngineDerivedReceiver(receiver, lookupClass);
253-
cachedReceiverClass = receiverClass;
254-
cachedDispatchClass = gsdDispatchClass;
255-
}
256-
}
257172
// GSD fast path: read jsi args directly, call objc_msgSend with a
258173
// typed cast, produce the jsi return value — bypassing all generic
259174
// marshalling. Only engages for plain calls (no super dispatch, init
260175
// disown handling, or implicit NSError-out argument).
261-
if (prepared->gsdEngineCallable && gsdDispatchClass == Nil &&
262-
count == prepared->gsdEngineArgumentCount &&
263-
!(!receiverIsClass && prepared->isInitMethod)) {
176+
if (call.prepared->gsdEngineCallable && call.dispatchClass == Nil &&
177+
count == call.prepared->gsdEngineArgumentCount &&
178+
!(!state.receiverIsClass && call.prepared->isInitMethod)) {
264179
auto invoker =
265-
reinterpret_cast<ObjCGsdInvoker>(prepared->engineInvoker);
266-
GsdObjCContext ctx{runtime, bridge, receiver, prepared->selector,
267-
args, prepared->signature.returnType};
180+
reinterpret_cast<ObjCGsdInvoker>(call.prepared->engineInvoker);
181+
GsdObjCContext ctx{runtime, state.bridge, call.receiver,
182+
call.prepared->selector, args,
183+
call.prepared->signature.returnType};
268184
if (invoker(ctx)) {
269185
return std::move(ctx.result);
270186
}
271187
}
272188

273-
if (receiverIsClass) {
274-
return callPreparedObjCSelector(runtime, bridge, receiver, true,
275-
*prepared, args, count, Nil);
189+
if (state.receiverIsClass) {
190+
return callPreparedObjCSelector(runtime, state.bridge, call.receiver,
191+
true, *call.prepared, args, count,
192+
Nil);
276193
}
277194
if (!receiverHostObject) {
278-
if (boundReceiverState != nullptr) {
279-
if (auto bound = boundReceiver.lock()) {
280-
receiverHostObject = std::move(bound);
281-
}
282-
} else if (thisValue.isObject()) {
283-
Object receiverObject = thisValue.asObject(runtime);
284-
if (receiverObject.isHostObject<NativeApiObjectHostObject>(
285-
runtime)) {
286-
receiverHostObject =
287-
receiverObject.getHostObject<NativeApiObjectHostObject>(
288-
runtime);
289-
}
290-
}
195+
receiverHostObject = resolveReceiverHost();
291196
}
292197
if (!receiverHostObject) {
293198
throw JSError(runtime,
294199
"Objective-C selector requires a native receiver.");
295200
}
296201
return receiverHostObject->callPreparedObjectSelector(
297-
runtime, *prepared, args, count, gsdDispatchClass);
202+
runtime, *call.prepared, args, count, call.dispatchClass);
298203
});
299204
}
300205

301-
Function CreateNativeApiSelectorGroupFunction(
302-
Runtime& runtime, std::shared_ptr<NativeApiBridge> bridge,
303-
Class lookupClass, bool receiverIsClass,
304-
std::shared_ptr<std::vector<NativeApiSelectorGroupEntry>> selectors,
305-
std::shared_ptr<
306-
std::vector<std::shared_ptr<NativeApiPreparedObjCInvocation>>>
307-
preparedInvocations) {
308-
return CreateNativeApiSelectorGroupFunctionImpl(
309-
runtime, std::move(bridge), lookupClass, receiverIsClass,
310-
std::move(selectors), std::move(preparedInvocations), {}, nullptr);
311-
}
312-
313-
Function CreateNativeApiBoundSelectorGroupFunction(
314-
Runtime& runtime, std::shared_ptr<NativeApiBridge> bridge, Class lookupClass,
315-
std::shared_ptr<NativeApiObjectHostObject> receiverHostObject,
316-
std::shared_ptr<std::vector<NativeApiSelectorGroupEntry>> selectors,
317-
std::shared_ptr<
318-
std::vector<std::shared_ptr<NativeApiPreparedObjCInvocation>>>
319-
preparedInvocations) {
320-
return CreateNativeApiSelectorGroupFunctionImpl(
321-
runtime, std::move(bridge), lookupClass, false, std::move(selectors),
322-
std::move(preparedInvocations), receiverHostObject,
323-
receiverHostObject != nullptr ? receiverHostObject->lifetimeState()
324-
: nullptr);
325-
}
326-
327206
} // namespace
328207

329208
#include "../shared/bridge/Install.mm"

NativeScript/ffi/objc/jsc/NativeApiJSCHostObjects.mm

Lines changed: 2 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include "NativeApiJSCRuntime.h"
2+
#include "../shared/NativeApiStackValueArray.h"
23

34
#ifdef TARGET_ENGINE_JSC
45

@@ -15,44 +16,6 @@
1516
JSClassRef functionClass(Runtime& runtime);
1617
void setFunctionPrototype(JSGlobalContextRef context, JSObjectRef function);
1718

18-
template <size_t InlineCount>
19-
class StackValueArray {
20-
public:
21-
explicit StackValueArray(size_t count) : count_(count) {
22-
if (count_ > InlineCount) {
23-
values_ = static_cast<Value*>(::operator new(sizeof(Value) * count_));
24-
} else {
25-
values_ = reinterpret_cast<Value*>(inlineStorage_);
26-
}
27-
}
28-
29-
~StackValueArray() {
30-
for (size_t i = 0; i < constructed_; i++) {
31-
values_[i].~Value();
32-
}
33-
if (count_ > InlineCount) {
34-
::operator delete(values_);
35-
}
36-
}
37-
38-
StackValueArray(const StackValueArray&) = delete;
39-
StackValueArray& operator=(const StackValueArray&) = delete;
40-
41-
void emplace(size_t index, Value&& value) {
42-
new (&values_[index]) Value(std::move(value));
43-
constructed_++;
44-
}
45-
46-
Value* data() { return count_ == 0 ? nullptr : values_; }
47-
size_t size() const { return count_; }
48-
49-
private:
50-
size_t count_ = 0;
51-
size_t constructed_ = 0;
52-
Value* values_ = nullptr;
53-
alignas(Value) unsigned char inlineStorage_[sizeof(Value) * InlineCount];
54-
};
55-
5619
bool isNativeInstancePrototypeBypassExcluded(JSStringRef propertyName) {
5720
return JSStringIsEqualToUTF8CString(propertyName, "kind") ||
5821
JSStringIsEqualToUTF8CString(propertyName, "className") ||
@@ -159,7 +122,7 @@ JSValueRef functionCall(JSContextRef context, JSObjectRef function, JSObjectRef
159122
return JSValueMakeUndefined(context);
160123
}
161124
Runtime runtime(holder->state);
162-
StackValueArray<8> args(argumentCount);
125+
StackValueArray<Value, 8> args(argumentCount);
163126
for (size_t i = 0; i < argumentCount; i++) {
164127
args.emplace(i, Value::borrowed(runtime, arguments[i]));
165128
}

NativeScript/ffi/objc/jsc/NativeApiJSCRuntime.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,12 @@ class Value {
374374
return value;
375375
}
376376

377+
static bool strictEquals(Runtime& runtime, const Value& lhs,
378+
const Value& rhs) {
379+
return JSValueIsStrictEqual(runtime.context(), lhs.local(runtime),
380+
rhs.local(runtime));
381+
}
382+
377383
bool isUndefined() const {
378384
return kind_ == jscengine::ValueStorage::Kind::Undefined ||
379385
(isJSC() && JSValueIsUndefined(jscContext(), jscValue()));

0 commit comments

Comments
 (0)