From 643958b6a4c3698567edde3fd03052873b2644dc Mon Sep 17 00:00:00 2001 From: Eduardo Speroni Date: Wed, 29 Nov 2023 13:37:45 -0300 Subject: [PATCH] feat: use node module bindings like the iOS runtime (#1795) --- test-app/runtime/CMakeLists.txt | 1 + .../runtime/src/main/cpp/IsolateDisposer.cpp | 8 + .../runtime/src/main/cpp/IsolateDisposer.h | 27 + .../runtime/src/main/cpp/ModuleBinding.cpp | 101 ++++ test-app/runtime/src/main/cpp/ModuleBinding.h | 98 ++++ test-app/runtime/src/main/cpp/Runtime.cpp | 4 +- test-app/runtime/src/main/cpp/Runtime.h | 2 - test-app/runtime/src/main/cpp/Timers.cpp | 36 +- test-app/runtime/src/main/cpp/Timers.h | 2 + test-app/runtime/src/main/cpp/Util.cpp | 472 ++++++++++++++---- test-app/runtime/src/main/cpp/Util.h | 192 +++++++ 11 files changed, 836 insertions(+), 107 deletions(-) create mode 100644 test-app/runtime/src/main/cpp/ModuleBinding.cpp create mode 100644 test-app/runtime/src/main/cpp/ModuleBinding.h diff --git a/test-app/runtime/CMakeLists.txt b/test-app/runtime/CMakeLists.txt index 60b0a1714..13e770527 100644 --- a/test-app/runtime/CMakeLists.txt +++ b/test-app/runtime/CMakeLists.txt @@ -115,6 +115,7 @@ add_library( src/main/cpp/MetadataReader.cpp src/main/cpp/MetadataTreeNode.cpp src/main/cpp/MethodCache.cpp + src/main/cpp/ModuleBinding.cpp src/main/cpp/ModuleInternal.cpp src/main/cpp/NativeScriptException.cpp src/main/cpp/NumericCasts.cpp diff --git a/test-app/runtime/src/main/cpp/IsolateDisposer.cpp b/test-app/runtime/src/main/cpp/IsolateDisposer.cpp index b5453289a..c495f0dbe 100644 --- a/test-app/runtime/src/main/cpp/IsolateDisposer.cpp +++ b/test-app/runtime/src/main/cpp/IsolateDisposer.cpp @@ -9,11 +9,19 @@ #include + namespace tns { void disposeIsolate(v8::Isolate *isolate) { tns::ArgConverter::onDisposeIsolate(isolate); tns::MetadataNode::onDisposeIsolate(isolate); tns::V8GlobalHelpers::onDisposeIsolate(isolate); tns::Console::onDisposeIsolate(isolate); + // clear all isolate bound objects + std::lock_guard lock(isolateBoundObjectsMutex_); + auto it = isolateBoundObjects_.find(isolate); + if (it != isolateBoundObjects_.end()) { + it->second->clear(); + isolateBoundObjects_.erase(it); + } } } diff --git a/test-app/runtime/src/main/cpp/IsolateDisposer.h b/test-app/runtime/src/main/cpp/IsolateDisposer.h index 7ce85997c..7fdf757de 100644 --- a/test-app/runtime/src/main/cpp/IsolateDisposer.h +++ b/test-app/runtime/src/main/cpp/IsolateDisposer.h @@ -5,9 +5,36 @@ #ifndef TEST_APP_ISOLATEDISPOSER_H #define TEST_APP_ISOLATEDISPOSER_H #include "v8.h" +#include "robin_hood.h" +#include +#include +#include namespace tns { void disposeIsolate(v8::Isolate* isolate); + using unique_void_ptr = std::unique_ptr; + template + auto unique_void(T * ptr) -> unique_void_ptr + { + return unique_void_ptr(ptr, [](void const * data) { + T const * p = static_cast(data); + delete p; + }); + } + robin_hood::unordered_map>> isolateBoundObjects_; + std::mutex isolateBoundObjectsMutex_; + template + void registerIsolateBoundObject(v8::Isolate* isolate, T *ptr) { + std::lock_guard lock(isolateBoundObjectsMutex_); + auto it = isolateBoundObjects_.find(isolate); + if (it == isolateBoundObjects_.end()) { + auto vec = std::make_shared>(); + vec->push_back(unique_void(ptr)); + isolateBoundObjects_.emplace(isolate, vec); + } else { + it->second->push_back(unique_void(ptr)); + } + } } #endif //TEST_APP_ISOLATEDISPOSER_H diff --git a/test-app/runtime/src/main/cpp/ModuleBinding.cpp b/test-app/runtime/src/main/cpp/ModuleBinding.cpp new file mode 100644 index 000000000..5050ff1d5 --- /dev/null +++ b/test-app/runtime/src/main/cpp/ModuleBinding.cpp @@ -0,0 +1,101 @@ +// +// ModuleBinding.cpp +// NativeScript +// +// Created by Eduardo Speroni on 5/11/23. +// Copyright © 2023 Progress. All rights reserved. +// + +#include "ModuleBinding.h" + +// TODO: add here +//#define NSC_BUILTIN_STANDARD_BINDINGS(V) \ +//V(fs) + +#define NSC_BUILTIN_STANDARD_BINDINGS(V) + + +#define NSC_BUILTIN_BINDINGS(V) \ +NSC_BUILTIN_STANDARD_BINDINGS(V) + +// This is used to load built-in bindings. Instead of using +// __attribute__((constructor)), we call the _register_ +// function for each built-in bindings explicitly in +// binding::RegisterBuiltinBindings(). This is only forward declaration. +// The definitions are in each binding's implementation when calling +// the NODE_BINDING_CONTEXT_AWARE_INTERNAL. +#define V(modname) void _register_##modname(); +NSC_BUILTIN_BINDINGS(V) +#undef V + + + + +#define V(modname) \ + void _register_isolate_##modname(v8::Isolate* isolate, \ + v8::Local target); +NODE_BINDINGS_WITH_PER_ISOLATE_INIT(V) +#undef V + + + +using v8::Context; +using v8::EscapableHandleScope; +using v8::Exception; +using v8::FunctionCallbackInfo; +using v8::FunctionTemplate; +using v8::HandleScope; +using v8::Isolate; +using v8::Local; +using v8::Object; +using v8::String; +using v8::Value; + +namespace tns { +// Globals per process +static ns_module* modlist_internal; +static ns_module* modlist_linked; +static thread_local ns_module* thread_local_modpending; +bool node_is_initialized = false; + +extern "C" void nativescript_module_register(void* m) { + struct ns_module* mp = reinterpret_cast(m); + + if (mp->nm_flags & NM_F_INTERNAL) { + mp->nm_link = modlist_internal; + modlist_internal = mp; + } else if (!node_is_initialized) { + // "Linked" modules are included as part of the node project. + // Like builtins they are registered *before* node::Init runs. + mp->nm_flags = NM_F_LINKED; + mp->nm_link = modlist_linked; + modlist_linked = mp; + } else { + thread_local_modpending = mp; + } +} + +namespace binding { + +void RegisterBuiltinBindings() { +#define V(modname) _register_##modname(); + NSC_BUILTIN_BINDINGS(V) +#undef V +} + +void CreateInternalBindingTemplates(v8::Isolate* isolate, Local templ) { +#define V(modname) \ + do { \ + /*templ->InstanceTemplate()->SetInternalFieldCount( \ + BaseObject::kInternalFieldCount);*/ \ + _register_isolate_##modname(isolate, templ); \ + /*isolate_data->set_##modname##_binding(templ);*/ \ + } while (0); + NODE_BINDINGS_WITH_PER_ISOLATE_INIT(V) +#undef V +} + +}; + +}; + diff --git a/test-app/runtime/src/main/cpp/ModuleBinding.h b/test-app/runtime/src/main/cpp/ModuleBinding.h new file mode 100644 index 000000000..258b8d9bf --- /dev/null +++ b/test-app/runtime/src/main/cpp/ModuleBinding.h @@ -0,0 +1,98 @@ +// +// ModuleBinding.hpp +// NativeScript +// +// Created by Eduardo Speroni on 5/11/23. +// Copyright © 2023 Progress. All rights reserved. +// + +#ifndef ModuleBinding_hpp +#define ModuleBinding_hpp + +#include "v8.h" + + +namespace tns { + +#define NODE_BINDING_CONTEXT_AWARE_CPP(modname, regfunc, priv, flags) \ + static tns::ns_module _module = { \ + NODE_MODULE_VERSION, \ + flags, \ + nullptr, \ + __FILE__, \ + nullptr, \ + (tns::addon_context_register_func)(regfunc), \ + NODE_STRINGIFY(modname), \ + priv, \ + nullptr}; \ + void _register_##modname() { node_module_register(&_module); } + +#define NODE_BINDING_CONTEXT_AWARE_INTERNAL(modname, regfunc) \ + NODE_BINDING_CONTEXT_AWARE_CPP(modname, regfunc, nullptr, NM_F_INTERNAL) + + + + + + + + +#define NODE_BINDING_PER_ISOLATE_INIT(modname, per_isolate_func) \ + void _register_isolate_##modname(v8::Isolate* isolate, \ + v8::Local target) { \ + per_isolate_func(isolate, target); \ + } + + +// This is a helepr that gives the target as an ObjectTemplate (using target.PrototypeTemplate()) + +#define NODE_BINDING_PER_ISOLATE_INIT_OBJ(modname, per_isolate_func) \ + void _register_isolate_##modname(v8::Isolate* isolate, \ + v8::Local target) { \ + per_isolate_func(isolate, target->PrototypeTemplate()); \ + } + + +#define NODE_BINDINGS_WITH_PER_ISOLATE_INIT(V) \ +V(timers) + +enum { + NM_F_BUILTIN = 1 << 0, // Unused. + NM_F_LINKED = 1 << 1, + NM_F_INTERNAL = 1 << 2, + NM_F_DELETEME = 1 << 3, +}; + +typedef void (*addon_register_func)( + v8::Local exports, + v8::Local module, + void* priv); + +typedef void (*addon_context_register_func)( + v8::Local exports, + v8::Local module, + v8::Local context, + void* priv); + +struct ns_module { + int nm_version; + unsigned int nm_flags; + void* nm_dso_handle; + const char* nm_filename; + tns::addon_register_func nm_register_func; + tns::addon_context_register_func nm_context_register_func; + const char* nm_modname; + void* nm_priv; + struct ns_module* nm_link; +}; + +namespace binding { +void RegisterBuiltinBindings(); +void CreateInternalBindingTemplates(v8::Isolate* isolate, v8::Local templ); +}; + + +}; + + +#endif /* ModuleBinding_hpp */ diff --git a/test-app/runtime/src/main/cpp/Runtime.cpp b/test-app/runtime/src/main/cpp/Runtime.cpp index 45cfc5604..7e542170b 100644 --- a/test-app/runtime/src/main/cpp/Runtime.cpp +++ b/test-app/runtime/src/main/cpp/Runtime.cpp @@ -30,6 +30,7 @@ #include #include #include "File.h" +#include "ModuleBinding.h" #ifdef APPLICATION_IN_DEBUG // #include "NetworkDomainCallbackHandlers.h" @@ -500,6 +501,7 @@ Isolate* Runtime::PrepareV8Runtime(const string& filesPath, const string& native auto globalFunctionTemplate = FunctionTemplate::New(isolate); globalFunctionTemplate->SetClassName(ArgConverter::ConvertToV8String(isolate, "NativeScriptGlobalObject")); + tns::binding::CreateInternalBindingTemplates(isolate, globalFunctionTemplate); auto globalTemplate = ObjectTemplate::New(isolate, globalFunctionTemplate); const auto readOnlyFlags = static_cast(PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly); @@ -582,8 +584,6 @@ Isolate* Runtime::PrepareV8Runtime(const string& filesPath, const string& native CallbackHandlers::CreateGlobalCastFunctions(isolate, globalTemplate); - m_timers.Init(isolate, globalTemplate); - Local context = Context::New(isolate, nullptr, globalTemplate); auto global = context->Global(); diff --git a/test-app/runtime/src/main/cpp/Runtime.h b/test-app/runtime/src/main/cpp/Runtime.h index 848336ff7..995f76b89 100644 --- a/test-app/runtime/src/main/cpp/Runtime.h +++ b/test-app/runtime/src/main/cpp/Runtime.h @@ -94,8 +94,6 @@ class Runtime { WeakRef m_weakRef; - Timers m_timers; - Profiler m_profiler; MessageLoopTimer* m_loopTimer; diff --git a/test-app/runtime/src/main/cpp/Timers.cpp b/test-app/runtime/src/main/cpp/Timers.cpp index 939a9a6ab..31d417866 100644 --- a/test-app/runtime/src/main/cpp/Timers.cpp +++ b/test-app/runtime/src/main/cpp/Timers.cpp @@ -5,6 +5,9 @@ #include #include #include +#include "ModuleBinding.h" +#include "IsolateDisposer.h" +#include "Util.h" /** @@ -15,7 +18,6 @@ * ALL changes and scheduling of a TimerTask MUST be done when locked in an isolate to ensure consistency */ -using namespace tns; using namespace v8; // Takes a value and transform into a positive number @@ -43,22 +45,18 @@ static double now_ms() { return 1000.0 * res.tv_sec + (double) res.tv_nsec / 1e6; } +namespace tns { + + + void Timers::Init(v8::Isolate *isolate, v8::Local &globalObjectTemplate) { isolate_ = isolate; // TODO: remove the __ns__ prefix once this is validated - globalObjectTemplate->Set(ArgConverter::ConvertToV8String(isolate, "__ns__setTimeout"), - FunctionTemplate::New(isolate, SetTimeoutCallback, - External::New(isolate, this))); - globalObjectTemplate->Set(ArgConverter::ConvertToV8String(isolate, "__ns__setInterval"), - FunctionTemplate::New(isolate, SetIntervalCallback, - External::New(isolate, this))); - globalObjectTemplate->Set(ArgConverter::ConvertToV8String(isolate, "__ns__clearTimeout"), - FunctionTemplate::New(isolate, ClearTimer, - External::New(isolate, this))); - globalObjectTemplate->Set(ArgConverter::ConvertToV8String(isolate, "__ns__clearInterval"), - FunctionTemplate::New(isolate, ClearTimer, - External::New(isolate, this))); + SetMethod(isolate, globalObjectTemplate, "__ns__setTimeout", SetTimeoutCallback, External::New(isolate, this)); + SetMethod(isolate, globalObjectTemplate, "__ns__setInterval", SetIntervalCallback, External::New(isolate, this)); + SetMethod(isolate, globalObjectTemplate, "__ns__clearTimeout", ClearTimer, External::New(isolate, this)); + SetMethod(isolate, globalObjectTemplate, "__ns__clearInterval", ClearTimer, External::New(isolate, this)); auto res = pipe(fd_); assert(res != -1); res = fcntl(fd_[1], F_SETFL, O_NONBLOCK); @@ -324,4 +322,14 @@ int Timers::PumpTimerLoopCallback(int fd, int events, void *data) { } thiz->bufferFull.notify_one(); return 1; -} \ No newline at end of file +} + +void Timers::InitStatic(v8::Isolate* isolate, v8::Local globalObjectTemplate) { + auto timers = new Timers(); + timers->Init(isolate, globalObjectTemplate); + registerIsolateBoundObject(isolate, timers); +} + +}; + +NODE_BINDING_PER_ISOLATE_INIT_OBJ(timers, tns::Timers::InitStatic); \ No newline at end of file diff --git a/test-app/runtime/src/main/cpp/Timers.h b/test-app/runtime/src/main/cpp/Timers.h index ef6d8ccbb..eb55631aa 100644 --- a/test-app/runtime/src/main/cpp/Timers.h +++ b/test-app/runtime/src/main/cpp/Timers.h @@ -76,6 +76,8 @@ namespace tns { */ void Init(v8::Isolate *isolate, v8::Local &globalObjectTemplate); + static void InitStatic(v8::Isolate* isolate, v8::Local globalObjectTemplate); + /** * Disposes the timers. This will clear all references and stop all thread. * MUST be called in the same thread Init was called diff --git a/test-app/runtime/src/main/cpp/Util.cpp b/test-app/runtime/src/main/cpp/Util.cpp index c1cf1418f..990985f45 100644 --- a/test-app/runtime/src/main/cpp/Util.cpp +++ b/test-app/runtime/src/main/cpp/Util.cpp @@ -5,112 +5,113 @@ using namespace v8; using namespace std; -using namespace tns; +namespace tns { -string Util::JniClassPathToCanonicalName(const string& jniClassPath) { - std::string canonicalName; - const char prefix = jniClassPath[0]; + string Util::JniClassPathToCanonicalName(const string &jniClassPath) { + std::string canonicalName; - std::string rest; - int lastIndex; + const char prefix = jniClassPath[0]; - switch (prefix) { - case 'L': - canonicalName = jniClassPath.substr(1, jniClassPath.size() - 2); - std::replace(canonicalName.begin(), canonicalName.end(), '/', '.'); - std::replace(canonicalName.begin(), canonicalName.end(), '$', '.'); - break; + std::string rest; + int lastIndex; - case '[': - canonicalName = jniClassPath; - lastIndex = canonicalName.find_last_of('['); - rest = canonicalName.substr(lastIndex + 1); - canonicalName = canonicalName.substr(0, lastIndex + 1); - canonicalName.append(JniClassPathToCanonicalName(rest)); - break; + switch (prefix) { + case 'L': + canonicalName = jniClassPath.substr(1, jniClassPath.size() - 2); + std::replace(canonicalName.begin(), canonicalName.end(), '/', '.'); + std::replace(canonicalName.begin(), canonicalName.end(), '$', '.'); + break; - default: - // TODO: - canonicalName = jniClassPath; - break; + case '[': + canonicalName = jniClassPath; + lastIndex = canonicalName.find_last_of('['); + rest = canonicalName.substr(lastIndex + 1); + canonicalName = canonicalName.substr(0, lastIndex + 1); + canonicalName.append(JniClassPathToCanonicalName(rest)); + break; + + default: + // TODO: + canonicalName = jniClassPath; + break; + } + return canonicalName; } - return canonicalName; -} -void Util::SplitString(const string& str, const string& delimiters, vector& tokens) { - string::size_type delimPos = 0, tokenPos = 0, pos = 0; + void Util::SplitString(const string &str, const string &delimiters, vector &tokens) { + string::size_type delimPos = 0, tokenPos = 0, pos = 0; - if (str.length() < 1) { - return; - } + if (str.length() < 1) { + return; + } - while (true) { - delimPos = str.find_first_of(delimiters, pos); - tokenPos = str.find_first_not_of(delimiters, pos); + while (true) { + delimPos = str.find_first_of(delimiters, pos); + tokenPos = str.find_first_not_of(delimiters, pos); - if (string::npos != delimPos) { - if (string::npos != tokenPos) { - if (tokenPos < delimPos) { - tokens.push_back(str.substr(pos, delimPos - pos)); + if (string::npos != delimPos) { + if (string::npos != tokenPos) { + if (tokenPos < delimPos) { + tokens.push_back(str.substr(pos, delimPos - pos)); + } else { + tokens.emplace_back(""); + } } else { tokens.emplace_back(""); } + pos = delimPos + 1; } else { - tokens.emplace_back(""); - } - pos = delimPos + 1; - } else { - if (string::npos != tokenPos) { - tokens.push_back(str.substr(pos)); - } else { - tokens.emplace_back(""); + if (string::npos != tokenPos) { + tokens.push_back(str.substr(pos)); + } else { + tokens.emplace_back(""); + } + break; } - break; } } -} -bool Util::EndsWith(const string& str, const string& suffix) { - bool res = false; - if (str.size() > suffix.size()) { - res = equal(suffix.rbegin(), suffix.rend(), str.rbegin()); + bool Util::EndsWith(const string &str, const string &suffix) { + bool res = false; + if (str.size() > suffix.size()) { + res = equal(suffix.rbegin(), suffix.rend(), str.rbegin()); + } + return res; } - return res; -} - -string Util::ConvertFromJniToCanonicalName(const string& name) { - string converted = name; - replace(converted.begin(), converted.end(), '/', '.'); - return converted; -} -string Util::ConvertFromCanonicalToJniName(const string& name) { - string converted = name; - replace(converted.begin(), converted.end(), '.', '/'); - return converted; -} - -string Util::ReplaceAll(string& str, const string& from, const string& to) { - if (from.empty()) { - return str; + string Util::ConvertFromJniToCanonicalName(const string &name) { + string converted = name; + replace(converted.begin(), converted.end(), '/', '.'); + return converted; } - size_t start_pos = 0; - while ((start_pos = str.find(from, start_pos)) != string::npos) { - str.replace(start_pos, from.length(), to); - start_pos += to.length(); + string Util::ConvertFromCanonicalToJniName(const string &name) { + string converted = name; + replace(converted.begin(), converted.end(), '.', '/'); + return converted; } - return str; -} + string Util::ReplaceAll(string &str, const string &from, const string &to) { + if (from.empty()) { + return str; + } -u16string Util::ConvertFromUtf8ToUtf16(const string& str) { - auto utf16String = - std::wstring_convert, char16_t>().from_bytes(str); + size_t start_pos = 0; + while ((start_pos = str.find(from, start_pos)) != string::npos) { + str.replace(start_pos, from.length(), to); + start_pos += to.length(); + } + + return str; + } - return utf16String; -} + u16string Util::ConvertFromUtf8ToUtf16(const string &str) { + auto utf16String = + std::wstring_convert, char16_t>().from_bytes(str); + + return utf16String; + } //uint16_t* Util::ConvertFromUtf8ToProtocolUtf16(const string& str) { // auto utf16String = @@ -118,19 +119,312 @@ u16string Util::ConvertFromUtf8ToUtf16(const string& str) { // return (uint16_t *) utf16String.c_str(); //} -void Util::JoinString(const std::vector& list, const std::string& delimiter, - std::string& out) { - out.clear(); + void Util::JoinString(const std::vector &list, const std::string &delimiter, + std::string &out) { + out.clear(); - stringstream ss; + stringstream ss; - for (auto it = list.begin(); it != list.end(); ++it) { - ss << *it; + for (auto it = list.begin(); it != list.end(); ++it) { + ss << *it; - if (it != list.end() - 1) { - ss << delimiter; + if (it != list.end() - 1) { + ss << delimiter; + } } + + out = ss.str(); + } + + Local NewFunctionTemplate( + v8::Isolate *isolate, + v8::FunctionCallback callback, + Local data, + Local signature, + v8::ConstructorBehavior behavior, + v8::SideEffectType side_effect_type, + const v8::CFunction *c_function) { + return v8::FunctionTemplate::New(isolate, + callback, + data, + signature, + 0, + behavior, + side_effect_type, + c_function); } - out = ss.str(); -} \ No newline at end of file + void SetMethod(Local context, + Local that, + const char *name, + v8::FunctionCallback callback, + Local data) { + Isolate *isolate = context->GetIsolate(); + Local function = + NewFunctionTemplate(isolate, + callback, + data, + Local(), + v8::ConstructorBehavior::kThrow, + v8::SideEffectType::kHasSideEffect) + ->GetFunction(context) + .ToLocalChecked(); + // kInternalized strings are created in the old space. + const v8::NewStringType type = v8::NewStringType::kInternalized; + Local name_string = + v8::String::NewFromUtf8(isolate, name, type).ToLocalChecked(); + that->Set(context, name_string, function).Check(); + function->SetName(name_string); // NODE_SET_METHOD() compatibility. + } + + void SetMethod(v8::Isolate *isolate, + v8::Local that, + const char *name, + v8::FunctionCallback callback, + Local data) { + Local t = + NewFunctionTemplate(isolate, + callback, + data, + Local(), + v8::ConstructorBehavior::kThrow, + v8::SideEffectType::kHasSideEffect); + // kInternalized strings are created in the old space. + const v8::NewStringType type = v8::NewStringType::kInternalized; + Local name_string = + v8::String::NewFromUtf8(isolate, name, type).ToLocalChecked(); + that->Set(name_string, t); + } + + void SetFastMethod(Isolate *isolate, + Local