Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions NativeScript/runtime/ArgConverter.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ class ArgConverter {
v8::Local<v8::Object> receiver,
V8Args& args, const MethodMeta* meta,
bool isMethodCallback);
// skipGCRegistration: for ObjC wrappers the object gets a strong handle and
// no finalizer; for Struct wrappers the caller must call
// ObjectManager::Register itself, exactly once.
static v8::Local<v8::Value> ConvertArgument(
v8::Local<v8::Context> context, BaseDataWrapper* wrapper,
bool skipGCRegistration = false,
Expand All @@ -54,8 +57,6 @@ class ArgConverter {
std::vector<std::string>());
static std::shared_ptr<v8::Persistent<v8::Value>> CreateEmptyObject(
v8::Local<v8::Context> context, bool skipGCRegistration = false);
static std::shared_ptr<v8::Persistent<v8::Value>> CreateEmptyStruct(
v8::Local<v8::Context> context);
static const Meta* FindMeta(Class klass,
const TypeEncoding* typeEncoding = nullptr);
// Looks up the JS wrapper cached for `target` in Caches::Instances, dropping
Expand Down Expand Up @@ -101,6 +102,8 @@ class ArgConverter {
v8::Local<v8::Context> context,
v8::NamedPropertyGetterCallback propertyGetter = nullptr,
v8::NamedPropertySetterCallbackV2 propertySetter = nullptr);
static v8::Local<v8::Object> NewEmptyInstance(
v8::Local<v8::Context> context, v8::Persistent<v8::Function>* ctorFunc);
static std::shared_ptr<v8::Persistent<v8::Value>> CreateEmptyInstance(
v8::Local<v8::Context> context, v8::Persistent<v8::Function>* ctorFunc,
bool skipGCRegistration = false);
Expand Down
46 changes: 25 additions & 21 deletions NativeScript/runtime/ArgConverter.mm
Original file line number Diff line number Diff line change
Expand Up @@ -550,7 +550,7 @@
const Meta* meta = ArgConverter::GetMeta(structName);
tns::Assert(meta != nullptr && meta->type() == MetaType::Struct, isolate);
const StructMeta* structMeta = static_cast<const StructMeta*>(meta);
StructInfo structInfo = FFICall::GetStructInfo(structMeta);
const StructInfo& structInfo = FFICall::GetStructInfo(structMeta);
Interop::InitializeStruct(context, retValue, structInfo.Fields(), value);
return;
} else if (baseWrapper->Type() == WrapperType::Struct) {
Expand Down Expand Up @@ -842,20 +842,25 @@

if (wrapper->Type() == WrapperType::Struct) {
if (receiver.IsEmpty()) {
std::shared_ptr<Persistent<Value>> poStruct = CreateEmptyStruct(context);
receiver = poStruct->Get(isolate).As<Object>();
Persistent<v8::Function>* ctorFunc = Caches::Get(isolate)->EmptyStructCtorFunc.get();
tns::Assert(ctorFunc != nullptr, isolate);
receiver = NewEmptyInstance(context, ctorFunc);
// A struct object must carry exactly one finalizer registration; callers
// passing skipGCRegistration keep the handle and register it themselves.
if (!skipGCRegistration) {
ObjectManager::Register(context, receiver);
}
}

StructWrapper* structWrapper = static_cast<StructWrapper*>(wrapper);
StructInfo structInfo = structWrapper->StructInfo();
const StructInfo& structInfo = structWrapper->StructInfo();
auto cache = Caches::Get(isolate);
Local<v8::Function> structCtorFunc = cache->StructCtorInitializer(context, structInfo);
Local<Value> proto;
bool success =
structCtorFunc->Get(context, tns::ToV8String(isolate, "prototype")).ToLocal(&proto);

if (success && !proto.IsEmpty()) {
success = receiver->SetPrototype(context, proto).FromMaybe(false);
Local<Object> proto;
if (cache->StructPrototypeInitializer) {
proto = cache->StructPrototypeInitializer(context, structInfo);
}
if (!proto.IsEmpty()) {
bool success = receiver->SetPrototype(context, proto).FromMaybe(false);
tns::Assert(success, isolate);
}

Expand Down Expand Up @@ -1084,23 +1089,22 @@
return ArgConverter::CreateEmptyInstance(context, ctorFunc, skipGCRegistration);
}

std::shared_ptr<Persistent<Value>> ArgConverter::CreateEmptyStruct(Local<Context> context) {
Isolate* isolate = v8::Isolate::GetCurrent();
Persistent<v8::Function>* ctorFunc = Caches::Get(isolate)->EmptyStructCtorFunc.get();
tns::Assert(ctorFunc != nullptr, isolate);
return ArgConverter::CreateEmptyInstance(context, ctorFunc);
}

std::shared_ptr<Persistent<Value>> ArgConverter::CreateEmptyInstance(
Local<Context> context, Persistent<v8::Function>* ctorFunc, bool skipGCRegistration) {
Local<Object> ArgConverter::NewEmptyInstance(Local<Context> context,
Persistent<v8::Function>* ctorFunc) {
Isolate* isolate = v8::Isolate::GetCurrent();
Local<v8::Function> emptyCtorFunc = ctorFunc->Get(isolate);
Local<Value> value;
if (!emptyCtorFunc->CallAsConstructor(context, 0, nullptr).ToLocal(&value) || value.IsEmpty() ||
!value->IsObject()) {
tns::Assert(false, isolate);
}
Local<Object> result = value.As<Object>();
return value.As<Object>();
}

std::shared_ptr<Persistent<Value>> ArgConverter::CreateEmptyInstance(
Local<Context> context, Persistent<v8::Function>* ctorFunc, bool skipGCRegistration) {
Isolate* isolate = v8::Isolate::GetCurrent();
Local<Object> result = NewEmptyInstance(context, ctorFunc);

std::shared_ptr<Persistent<Value>> poValue;
if (!skipGCRegistration) {
Expand Down
1 change: 0 additions & 1 deletion NativeScript/runtime/Caches.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ Caches::~Caches() {
this->CtorFuncTemplates.clear();
this->CtorFuncs.clear();
this->ProtocolCtorFuncs.clear();
this->StructConstructorFunctions.clear();
this->PrimitiveInteropTypes.clear();
this->CFunctions.clear();

Expand Down
15 changes: 9 additions & 6 deletions NativeScript/runtime/Caches.h
Original file line number Diff line number Diff line change
Expand Up @@ -155,9 +155,6 @@ class Caches {
robin_hood::unordered_map<std::string,
std::unique_ptr<v8::Persistent<v8::Function>>>
ProtocolCtorFuncs;
robin_hood::unordered_map<std::string,
std::unique_ptr<v8::Persistent<v8::Function>>>
StructConstructorFunctions;
robin_hood::unordered_map<BinaryTypeEncodingType,
std::unique_ptr<v8::Persistent<v8::Object>>>
PrimitiveInteropTypes;
Expand All @@ -167,7 +164,9 @@ class Caches {

robin_hood::unordered_map<id, std::shared_ptr<v8::Persistent<v8::Value>>>
Instances;
robin_hood::unordered_map<std::pair<void*, std::string>,
// Root struct objects by (backing buffer, struct type). Child views created
// for nested-struct fields are never entered here.
robin_hood::unordered_map<std::pair<void*, const StructInfo*>,
std::shared_ptr<v8::Persistent<v8::Value>>,
pair_hash>
StructInstances;
Expand All @@ -184,8 +183,12 @@ class Caches {
v8::Local<v8::Context>, const BaseClassMeta*, KnownUnknownClassPair,
const std::vector<std::string>&)>
ObjectCtorInitializer;
std::function<v8::Local<v8::Function>(v8::Local<v8::Context>, StructInfo)>
StructCtorInitializer;
// Resolves the prototype a struct instance of the given type must carry.
// Indirected through std::function so this header stays free of
// MetadataBuilder.
std::function<v8::Local<v8::Object>(v8::Local<v8::Context>,
const StructInfo&)>
StructPrototypeInitializer;
robin_hood::unordered_map<const InterfaceMeta*,
std::vector<const MethodMeta*>>
Initializers;
Expand Down
30 changes: 19 additions & 11 deletions NativeScript/runtime/DataWrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -109,13 +109,13 @@ struct StructField {
const TypeEncoding* encoding)
: offset_(offset), ffiType_(ffiType), name_(name), encoding_(encoding) {}

ptrdiff_t Offset() { return this->offset_; }
ptrdiff_t Offset() const { return this->offset_; }

ffi_type* FFIType() { return this->ffiType_; }
ffi_type* FFIType() const { return this->ffiType_; }

std::string Name() { return this->name_; }
const std::string& Name() const { return this->name_; }

const TypeEncoding* Encoding() { return this->encoding_; }
const TypeEncoding* Encoding() const { return this->encoding_; }

private:
ptrdiff_t offset_;
Expand All @@ -124,17 +124,24 @@ struct StructField {
const TypeEncoding* encoding_;
};

// Owned by FFICall's process-wide struct-info cache and immutable once
// built, so every consumer (wrappers, call sites) holds a pointer or
// reference into that cache instead of a copy. Non-copyable to keep it that
// way: a copy would make the wrappers' pointers dangle.
struct StructInfo {
public:
StructInfo(std::string name, ffi_type* ffiType,
std::vector<StructField> fields)
: name_(name), ffiType_(ffiType), fields_(fields) {}
: name_(std::move(name)), ffiType_(ffiType), fields_(std::move(fields)) {}

StructInfo(const StructInfo&) = delete;
StructInfo& operator=(const StructInfo&) = delete;

std::string Name() const { return this->name_; }
const std::string& Name() const { return this->name_; }

ffi_type* FFIType() const { return this->ffiType_; }

std::vector<StructField> Fields() { return this->fields_; }
const std::vector<StructField>& Fields() const { return this->fields_; }

private:
std::string name_;
Expand Down Expand Up @@ -296,19 +303,20 @@ class PrimitiveDataWrapper : public BaseDataWrapper {

class StructTypeWrapper : public BaseDataWrapper {
public:
StructTypeWrapper(StructInfo structInfo) : structInfo_(structInfo) {}
StructTypeWrapper(const struct StructInfo& structInfo)
: structInfo_(&structInfo) {}

const WrapperType Type() { return WrapperType::StructType; }

const StructInfo StructInfo() { return this->structInfo_; }
const struct StructInfo& StructInfo() const { return *this->structInfo_; }

private:
struct StructInfo structInfo_;
const struct StructInfo* structInfo_;
};

class StructWrapper : public StructTypeWrapper {
public:
StructWrapper(struct StructInfo structInfo, void* data,
StructWrapper(const struct StructInfo& structInfo, void* data,
std::shared_ptr<v8::Persistent<v8::Value>> parent)
: StructTypeWrapper(structInfo),
data_(data),
Expand Down
78 changes: 44 additions & 34 deletions NativeScript/runtime/FFICall.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,8 @@ ffi_type* FFICall::GetArgumentType(const TypeEncoding* typeEncoding, bool isStru
tns::Assert(meta->type() == MetaType::Struct);
const StructMeta* structMeta = static_cast<const StructMeta*>(meta);

StructInfo structInfo = FFICall::GetStructInfo(structMeta, structName);
const StructInfo& structInfo =
FFICall::GetStructInfo(structMeta, structName);
return structInfo.FFIType();
}
case BinaryTypeEncodingType::ConstantArrayEncoding: {
Expand All @@ -127,7 +128,8 @@ ffi_type* FFICall::GetArgumentType(const TypeEncoding* typeEncoding, bool isStru
size_t count = typeEncoding->details.anonymousRecord.fieldsCount;
const TypeEncoding* fieldEncoding = typeEncoding->details.anonymousRecord.getFieldsEncodings();
const String* fieldNames = typeEncoding->details.anonymousRecord.getFieldNames();
StructInfo structInfo = FFICall::GetStructInfo(count, fieldEncoding, fieldNames);
const StructInfo& structInfo =
FFICall::GetStructInfo(count, fieldEncoding, fieldNames);
return structInfo.FFIType();
}
default: {
Expand Down Expand Up @@ -172,42 +174,47 @@ void FFICall::DisposeFFIType(ffi_type* type, const TypeEncoding* typeEncoding) {

}

StructInfo FFICall::GetStructInfo(const StructMeta* structMeta, std::string structName) {
size_t fieldsCount = structMeta->fieldsCount();
const TypeEncoding* fieldEncoding = structMeta->fieldsEncodings()->first();
const String* fieldNames = structMeta->fieldNames().first();
if (structName.empty()) {
structName = structMeta->name();
}
StructInfo structInfo = FFICall::GetStructInfo(fieldsCount, fieldEncoding, fieldNames, structName);
return structInfo;
const StructInfo& FFICall::GetStructInfo(const StructMeta* structMeta,
std::string structName) {
size_t fieldsCount = structMeta->fieldsCount();
const TypeEncoding* fieldEncoding = structMeta->fieldsEncodings()->first();
const String* fieldNames = structMeta->fieldNames().first();
if (structName.empty()) {
structName = structMeta->name();
}
return FFICall::GetStructInfo(fieldsCount, fieldEncoding, fieldNames,
structName);
}

StructInfo FFICall::GetStructInfo(size_t fieldsCount, const TypeEncoding* fieldEncoding, const String* fieldNames, std::string structName) {
if (structName.empty()) {
const TypeEncoding* temp = fieldEncoding;
std::stringstream ss;
for (int i = 0; i < fieldsCount; i++) {
std::string fieldName = fieldNames[i].valuePtr();
ss << fieldName << "_" << temp->type;
temp = temp->next();
}
structName = ss.str();
const StructInfo& FFICall::GetStructInfo(size_t fieldsCount,
const TypeEncoding* fieldEncoding,
const String* fieldNames,
std::string structName) {
if (structName.empty()) {
const TypeEncoding* temp = fieldEncoding;
std::stringstream ss;
for (int i = 0; i < fieldsCount; i++) {
std::string fieldName = fieldNames[i].valuePtr();
ss << fieldName << "_" << temp->type;
temp = temp->next();
}
structName = ss.str();
}

{
SpinLock lock(structInfosCacheMutex_);
auto it = structInfosCache_.find(structName);
if (it != structInfosCache_.end()) {
return it->second;
}
{
SpinLock lock(structInfosCacheMutex_);
auto it = structInfosCache_.find(structName);
if (it != structInfosCache_.end()) {
return *it->second;
}
}

std::vector<StructField> fields;
fields.reserve(fieldsCount);
ffi_type* ffiType = new ffi_type({ .size = 0, .alignment = 0, .type = FFI_TYPE_STRUCT });
std::vector<StructField> fields;
fields.reserve(fieldsCount);
ffi_type* ffiType =
new ffi_type({.size = 0, .alignment = 0, .type = FFI_TYPE_STRUCT});

ffiType->elements = new ffi_type*[fieldsCount + 1];
ffiType->elements = new ffi_type*[fieldsCount + 1];

#if defined(__x86_64__)
bool hasNestedStruct = false;
Expand Down Expand Up @@ -272,14 +279,16 @@ StructInfo FFICall::GetStructInfo(size_t fieldsCount, const TypeEncoding* fieldE
}
#endif

StructInfo structInfo(structName, ffiType, fields);
auto structInfo =
std::make_unique<StructInfo>(structName, ffiType, std::move(fields));

// The lock cannot be held while building: nested struct fields recurse
// through GetArgumentType back into GetStructInfo. Concurrent builders are
// therefore possible; the first emplace wins and everyone returns the
// cached entry (the loser's ffi_type allocations are abandoned).
SpinLock lock(structInfosCacheMutex_);
return structInfosCache_.emplace(structName, structInfo).first->second;
return *structInfosCache_.emplace(structName, std::move(structInfo))
.first->second;
}

ParametrizedCall* ParametrizedCall::Get(const TypeEncoding* typeEncoding, const int initialParameterIndex, const int argsCount) {
Expand Down Expand Up @@ -319,6 +328,7 @@ ParametrizedCall* ParametrizedCall::Get(const TypeEncoding* typeEncoding, const

robin_hood::unordered_map<const TypeEncoding*, ParametrizedCall*> ParametrizedCall::callsCache_;
SpinMutex ParametrizedCall::callsCacheMutex_;
robin_hood::unordered_map<std::string, StructInfo> FFICall::structInfosCache_;
robin_hood::unordered_map<std::string, std::unique_ptr<StructInfo>>
FFICall::structInfosCache_;
SpinMutex FFICall::structInfosCacheMutex_;
}
18 changes: 11 additions & 7 deletions NativeScript/runtime/FFICall.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <malloc/malloc.h>

#include <map>
#include <memory>

#include "DataWrapper.h"
#include "Metadata.h"
Expand Down Expand Up @@ -104,12 +105,14 @@ class FFICall : public BaseCall {
static ffi_type* GetArgumentType(const TypeEncoding* typeEncoding,
bool isStructMember = false);
static void DisposeFFIType(ffi_type* type, const TypeEncoding* typeEncoding);
static StructInfo GetStructInfo(const StructMeta* structMeta,
std::string structName = "");
static StructInfo GetStructInfo(size_t fieldsCount,
const TypeEncoding* fieldEncoding,
const String* fieldNames,
std::string structName = "");
// The returned reference points into a process-wide cache that is never
// pruned, so it stays valid for the lifetime of the process.
static const StructInfo& GetStructInfo(const StructMeta* structMeta,
std::string structName = "");
static const StructInfo& GetStructInfo(size_t fieldsCount,
const TypeEncoding* fieldEncoding,
const String* fieldNames,
std::string structName = "");

inline void* ArgumentBuffer(unsigned index) {
return this->argsArray_[index];
Expand All @@ -118,7 +121,8 @@ class FFICall : public BaseCall {
inline void** ArgsArray() { return this->argsArray_; }

private:
static robin_hood::unordered_map<std::string, StructInfo> structInfosCache_;
static robin_hood::unordered_map<std::string, std::unique_ptr<StructInfo>>
structInfosCache_;
static SpinMutex structInfosCacheMutex_;
void** argsArray_;
bool useDynamicBuffer_;
Expand Down
Loading
Loading