diff --git a/NativeScript/runtime/FFICall.h b/NativeScript/runtime/FFICall.h index c2f35549..46666405 100644 --- a/NativeScript/runtime/FFICall.h +++ b/NativeScript/runtime/FFICall.h @@ -2,117 +2,127 @@ #define FFICall_h #include + #include -#include "robin_hood.h" -#include "Metadata.h" + #include "DataWrapper.h" +#include "Metadata.h" #include "libffi.h" +#include "robin_hood.h" namespace tns { class BaseCall { -public: - BaseCall(uint8_t* buffer, size_t returnOffset = 0) - : buffer_(buffer), - returnOffset_(returnOffset) { - } + public: + BaseCall(uint8_t* buffer, size_t returnOffset = 0) + : buffer_(buffer), returnOffset_(returnOffset) {} - ~BaseCall() { - } + ~BaseCall() {} - inline void* ResultBuffer() { - return this->buffer_ + this->returnOffset_; - } + inline void* ResultBuffer() { return this->buffer_ + this->returnOffset_; } - template - inline T& GetResult() { - return *static_cast(this->ResultBuffer()); - } -protected: - uint8_t* buffer_; - size_t returnOffset_; + template + inline T& GetResult() { + return *static_cast(this->ResultBuffer()); + } + + protected: + uint8_t* buffer_; + size_t returnOffset_; }; class ParametrizedCall { -public: - ParametrizedCall(ffi_cif* cif) - : Cif(cif), - ReturnOffset(0), - StackSize(0) { - unsigned int argsCount = cif->nargs; - this->StackSize = 0; - - if (argsCount > 0) { - this->StackSize = malloc_good_size(sizeof(void* [argsCount])); - } - - this->ReturnOffset = this->StackSize; - - this->StackSize += malloc_good_size(std::max(cif->rtype->size, sizeof(ffi_arg))); - - this->ArgValueOffsets.reserve(argsCount); - for (size_t i = 0; i < argsCount; i++) { - this->ArgValueOffsets.push_back(this->StackSize); - ffi_type* argType = cif->arg_types[i]; - this->StackSize += malloc_good_size(std::max(argType->size, sizeof(ffi_arg))); - } + public: + ParametrizedCall(ffi_cif* cif) : Cif(cif), ReturnOffset(0), StackSize(0) { + unsigned int argsCount = cif->nargs; + this->StackSize = 0; + + if (argsCount > 0) { + // compute total bytes = number of pointers × size of a pointer + size_t needed = sizeof(void*) * static_cast(argsCount); + this->StackSize = malloc_good_size(needed); } - static ParametrizedCall* Get(const TypeEncoding* typeEncoding, const int initialParameterIndex, const int argsCount); + this->ReturnOffset = this->StackSize; - ffi_cif* Cif; - size_t ReturnOffset; - size_t StackSize; - std::vector ArgValueOffsets; -private: - static robin_hood::unordered_map callsCache_; -}; + this->StackSize += + malloc_good_size(std::max(cif->rtype->size, sizeof(ffi_arg))); -class FFICall: public BaseCall { -public: - FFICall(ParametrizedCall* parametrizedCall): BaseCall(nullptr) { - this->returnOffset_ = parametrizedCall->ReturnOffset; - this->useDynamicBuffer_ = parametrizedCall->StackSize > 512; - if(this->useDynamicBuffer_) { - this->buffer_ = reinterpret_cast(malloc(parametrizedCall->StackSize)); - } else { - this->buffer_ = reinterpret_cast(this->staticBuffer); - } - - this->argsArray_ = reinterpret_cast(this->buffer_); - for (size_t i = 0; i < parametrizedCall->Cif->nargs; i++) { - this->argsArray_[i] = this->buffer_ + parametrizedCall->ArgValueOffsets[i]; - } + this->ArgValueOffsets.reserve(argsCount); + for (size_t i = 0; i < argsCount; i++) { + this->ArgValueOffsets.push_back(this->StackSize); + ffi_type* argType = cif->arg_types[i]; + this->StackSize += + malloc_good_size(std::max(argType->size, sizeof(ffi_arg))); } + } - ~FFICall() { - if(this->useDynamicBuffer_) { - free(this->buffer_); - } - } + static ParametrizedCall* Get(const TypeEncoding* typeEncoding, + const int initialParameterIndex, + const int argsCount); - /** - When calling this, always make another call to DisposeFFIType with the same parameters - */ - 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 = ""); + ffi_cif* Cif; + size_t ReturnOffset; + size_t StackSize; + std::vector ArgValueOffsets; + + private: + static robin_hood::unordered_map + callsCache_; +}; + +class FFICall : public BaseCall { + public: + FFICall(ParametrizedCall* parametrizedCall) : BaseCall(nullptr) { + this->returnOffset_ = parametrizedCall->ReturnOffset; + this->useDynamicBuffer_ = parametrizedCall->StackSize > 512; + if (this->useDynamicBuffer_) { + this->buffer_ = + reinterpret_cast(malloc(parametrizedCall->StackSize)); + } else { + this->buffer_ = reinterpret_cast(this->staticBuffer); + } - inline void* ArgumentBuffer(unsigned index) { - return this->argsArray_[index]; + this->argsArray_ = reinterpret_cast(this->buffer_); + for (size_t i = 0; i < parametrizedCall->Cif->nargs; i++) { + this->argsArray_[i] = + this->buffer_ + parametrizedCall->ArgValueOffsets[i]; } + } - inline void** ArgsArray() { - return this->argsArray_; + ~FFICall() { + if (this->useDynamicBuffer_) { + free(this->buffer_); } -private: - static robin_hood::unordered_map structInfosCache_; - void** argsArray_; - bool useDynamicBuffer_; - uint8_t staticBuffer[512]; + } + + /** + When calling this, always make another call to DisposeFFIType with the same + parameters + */ + 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 = ""); + + inline void* ArgumentBuffer(unsigned index) { + return this->argsArray_[index]; + } + + inline void** ArgsArray() { return this->argsArray_; } + + private: + static robin_hood::unordered_map structInfosCache_; + void** argsArray_; + bool useDynamicBuffer_; + uint8_t staticBuffer[512]; }; -} +} // namespace tns #endif /* FFICall_h */ diff --git a/NativeScript/runtime/Timers.cpp b/NativeScript/runtime/Timers.cpp index 9afe55d5..90599266 100644 --- a/NativeScript/runtime/Timers.cpp +++ b/NativeScript/runtime/Timers.cpp @@ -7,275 +7,288 @@ // #include "Timers.hpp" + #include + +#include + +#include "Caches.h" #include "Helpers.h" #include "ModuleBinding.hpp" -#include "Caches.h" #include "Runtime.h" using namespace v8; - - // Takes a value and transform into a positive number // returns a negative number if the number is negative or invalid -inline static double -ToMaybePositiveValue(const v8::Local &v, const v8::Local &ctx) { - double value = -1; - if (v->IsNullOrUndefined()) { - return -1; +inline static double ToMaybePositiveValue(const v8::Local& v, + const v8::Local& ctx) { + double value = -1; + if (v->IsNullOrUndefined()) { + return -1; + } + Local numberValue; + auto success = v->ToNumber(ctx).ToLocal(&numberValue); + if (success) { + value = numberValue->Value(); + if (isnan(value)) { + value = -1; } - Local numberValue; - auto success = v->ToNumber(ctx).ToLocal(&numberValue); - if (success) { - value = numberValue->Value(); - if (isnan(value)) { - value = -1; - } - } - return value; + } + return value; } static double now_ms() { - struct timespec res; - clock_gettime(CLOCK_MONOTONIC, &res); - return 1000.0 * res.tv_sec + (double) res.tv_nsec / 1e6; + struct timespec res; + clock_gettime(CLOCK_MONOTONIC, &res); + return 1000.0 * res.tv_sec + (double)res.tv_nsec / 1e6; } namespace tns { class TimerState { -public: - std::mutex timerMutex_; - std::atomic currentTimerId = 0; - robin_hood::unordered_map> timerMap_; - CFRunLoopRef runloop; - - void removeTask(const std::shared_ptr &task) { - removeTask(task->id_); - } - - void removeTask(const int &taskId) { - auto it = timerMap_.find(taskId); - if (it != timerMap_.end()) { - //auto wasScheduled = it->second->queued_; - auto timer = it->second->timer; - it->second->Unschedule(); - timerMap_.erase(it); - CFRunLoopTimerInvalidate(timer); - // timer and context will be released by the retain function - //CFRunLoopTimerContext context; - //CFRunLoopTimerGetContext(timer, &context); - //delete static_cast*>(context.info); - // CFRelease(timer); - } + public: + std::mutex timerMutex_; + std::atomic currentTimerId = 0; + robin_hood::unordered_map> timerMap_; + CFRunLoopRef runloop; + + void removeTask(const std::shared_ptr& task) { + removeTask(task->id_); + } + + void removeTask(const int& taskId) { + auto it = timerMap_.find(taskId); + if (it != timerMap_.end()) { + // auto wasScheduled = it->second->queued_; + auto timer = it->second->timer; + it->second->Unschedule(); + timerMap_.erase(it); + CFRunLoopTimerInvalidate(timer); + // timer and context will be released by the retain function + // CFRunLoopTimerContext context; + // CFRunLoopTimerGetContext(timer, &context); + // delete static_cast*>(context.info); + // CFRelease(timer); } - - // this all comes from the android runtime implementation - void addTask(std::shared_ptr task) { - if (task->queued_) { - return; - } - // auto now = now_ms(); - // task->nestingLevel_ = nesting + 1; - task->queued_ = true; - // theoretically this should be >5 on the spec, but we're following chromium behavior here again - // if (task->nestingLevel_ >= 5 && task->frequency_ < 4) { - // task->frequency_ = 4; - // task->startTime_ = now; - // } - timerMap_.emplace(task->id_, task); - // not needed on the iOS runtime for now - // auto newTime = task->NextTime(now); - // task->dueTime_ = newTime; + } + + // this all comes from the android runtime implementation + void addTask(std::shared_ptr task) { + if (task->queued_) { + return; } - + // auto now = now_ms(); + // task->nestingLevel_ = nesting + 1; + task->queued_ = true; + // theoretically this should be >5 on the spec, but we're following chromium + // behavior here again + // if (task->nestingLevel_ >= 5 && task->frequency_ < 4) { + // task->frequency_ = 4; + // task->startTime_ = now; + // } + timerMap_.emplace(task->id_, task); + // not needed on the iOS runtime for now + // auto newTime = task->NextTime(now); + // task->dueTime_ = newTime; + } }; // this class is attached to the timer object itself // we use a retain/release flow because we want to bind this to the Timer itself // additionally it helps if we deal with timers on different threads -// The current implementation puts the timers on the runtime's runloop, so it shouldn't be necessary. +// The current implementation puts the timers on the runtime's runloop, so it +// shouldn't be necessary. class TimerContext { -public: - std::atomic retainCount{0}; - std::shared_ptr task; - TimerState* state; - ~TimerContext() { - task->Unschedule(); - CFRelease(task->timer); - } - - static const void* TimerRetain(const void* ret) { - auto v = (TimerContext*)(ret); - v->retainCount++; - return ret; - } - - static void TimerRelease(const void* ret) { - auto v = (TimerContext*)(ret); - if(--v->retainCount <= 0) { - delete v; - }; - } -}; - - + public: + std::atomic retainCount{0}; + std::shared_ptr task; + TimerState* state; + ~TimerContext() { + task->Unschedule(); + CFRelease(task->timer); + } + static const void* TimerRetain(const void* ret) { + auto v = (TimerContext*)(ret); + v->retainCount++; + return ret; + } + static void TimerRelease(const void* ret) { + auto v = (TimerContext*)(ret); + if (--v->retainCount <= 0) { + delete v; + }; + } +}; void Timers::Init(Isolate* isolate, Local globalTemplate) { - auto timerState = new TimerState(); - timerState->runloop = Runtime::GetRuntime(isolate)->RuntimeLoop(); - Caches::Get(isolate)->registerCacheBoundObject(timerState); - tns::NewFunctionTemplate(isolate, Timers::SetTimeoutCallback, v8::External::New(isolate, timerState)); - tns::SetMethod(isolate, globalTemplate, "__ns__setTimeout", Timers::SetTimeoutCallback, v8::External::New(isolate, timerState)); - tns::SetMethod(isolate, globalTemplate, "__ns__setInterval", Timers::SetIntervalCallback, v8::External::New(isolate, timerState)); - tns::SetMethod(isolate, globalTemplate, "__ns__clearTimeout", Timers::ClearTimeoutCallback, v8::External::New(isolate, timerState)); - tns::SetMethod(isolate, globalTemplate, "__ns__clearInterval", Timers::ClearTimeoutCallback, v8::External::New(isolate, timerState)); - Caches::Get(isolate)->registerCacheBoundObject(new TimerState()); - + auto timerState = new TimerState(); + timerState->runloop = Runtime::GetRuntime(isolate)->RuntimeLoop(); + Caches::Get(isolate)->registerCacheBoundObject(timerState); + tns::NewFunctionTemplate(isolate, Timers::SetTimeoutCallback, + v8::External::New(isolate, timerState)); + tns::SetMethod(isolate, globalTemplate, "__ns__setTimeout", + Timers::SetTimeoutCallback, + v8::External::New(isolate, timerState)); + tns::SetMethod(isolate, globalTemplate, "__ns__setInterval", + Timers::SetIntervalCallback, + v8::External::New(isolate, timerState)); + tns::SetMethod(isolate, globalTemplate, "__ns__clearTimeout", + Timers::ClearTimeoutCallback, + v8::External::New(isolate, timerState)); + tns::SetMethod(isolate, globalTemplate, "__ns__clearInterval", + Timers::ClearTimeoutCallback, + v8::External::New(isolate, timerState)); + Caches::Get(isolate)->registerCacheBoundObject(new TimerState()); } +void TimerCallback(CFRunLoopTimerRef timer, void* info) { + TimerContext* data = (TimerContext*)info; + auto task = data->task; + // we check for this first so we can be 100% sure that this task is still + // alive since we're always dealing with the runtime's runloop, it should + // always work if we even support firing the timers in a another runloop, then + // this is useful as it'll avoid use-after-free issues + if (!task->queued_ || !task->wrapper.IsValid()) { + return; + } + auto isolate = task->isolate_; + v8::Locker locker(isolate); + v8::Isolate::Scope isolate_scope(isolate); + v8::HandleScope handleScope(isolate); + // ensure we're still queued after locking + if (!task->queued_) { + return; + } -void TimerCallback(CFRunLoopTimerRef timer, void *info) { - TimerContext* data = (TimerContext*)info; - auto task = data->task; - // we check for this first so we can be 100% sure that this task is still alive - // since we're always dealing with the runtime's runloop, it should always work - // if we even support firing the timers in a another runloop, then this is useful as it'll avoid use-after-free issues - if (!task->queued_ || !task->wrapper.IsValid()) { - return; - } - auto isolate = task->isolate_; - - v8::Locker locker(isolate); - v8::Isolate::Scope isolate_scope(isolate); - v8::HandleScope handleScope(isolate); - // ensure we're still queued after locking - if(!task->queued_) { - return; - } - - v8::Local cb = task->callback_.Get(isolate); - v8::Local context = cb->GetCreationContextChecked(); - Context::Scope context_scope(context); - auto argc = task->args_.get() == nullptr ? 0 : task->args_->size(); - if (argc > 0) { - Local argv[argc]; - for (int i = 0; i < argc; i++) { - argv[i] = task->args_->at(i)->Get(isolate); - } - (void)cb->Call(context, context->Global(), (int)argc, argv); - } else { - (void)cb->Call(context, context->Global(), 0, nullptr); - } - - if (!task->repeats_) { - data->state->removeTask(task); + v8::Local cb = task->callback_.Get(isolate); + v8::Local context = cb->GetCreationContextChecked(); + Context::Scope context_scope(context); + int argc = task->args_ ? static_cast(task->args_->size()) : 0; + if (argc > 0) { + // allocate an array of the right size + std::vector> argv(argc); + + for (int i = 0; i < argc; ++i) { + argv[i] = task->args_->at(i)->Get(isolate); } - + + // pass pointer to the first element + (void)cb->Call(context, context->Global(), argc, argv.data()); + } else { + (void)cb->Call(context, context->Global(), 0, nullptr); + } + + if (!task->repeats_) { + data->state->removeTask(task); + } } -void Timers::SetTimer(const v8::FunctionCallbackInfo& args, bool repeatable) { - auto argLength = args.Length(); - auto extData = args.Data().As(); - TimerState* state = reinterpret_cast(extData->Value()); - int id = ++state->currentTimerId; - if (argLength >= 1) { - if (!args[0]->IsFunction()) { - args.GetReturnValue().Set(-1); - return; - } - auto handler = args[0].As(); - auto isolate = args.GetIsolate(); - auto ctx = isolate->GetCurrentContext(); - long timeout = 0; - if (argLength >= 2) { - timeout = (long) ToMaybePositiveValue(args[1], ctx); - if (timeout < 0) { - timeout = 0; - } - } - std::shared_ptr>>> argArray; - if (argLength >= 3) { - auto otherArgLength = argLength - 2; - argArray = std::make_shared>>>( - otherArgLength); - for (int i = 0; i < otherArgLength; i++) { - (*argArray)[i] = std::make_shared>(isolate, args[i + 2]); +void Timers::SetTimer(const v8::FunctionCallbackInfo& args, + bool repeatable) { + auto argLength = args.Length(); + auto extData = args.Data().As(); + TimerState* state = reinterpret_cast(extData->Value()); + int id = ++state->currentTimerId; + if (argLength >= 1) { + if (!args[0]->IsFunction()) { + args.GetReturnValue().Set(-1); + return; + } + auto handler = args[0].As(); + auto isolate = args.GetIsolate(); + auto ctx = isolate->GetCurrentContext(); + long timeout = 0; + if (argLength >= 2) { + timeout = (long)ToMaybePositiveValue(args[1], ctx); + if (timeout < 0) { + timeout = 0; + } + } + std::shared_ptr>>> argArray; + if (argLength >= 3) { + auto otherArgLength = argLength - 2; + argArray = + std::make_shared>>>( + otherArgLength); + for (int i = 0; i < otherArgLength; i++) { + (*argArray)[i] = + std::make_shared>(isolate, args[i + 2]); #ifdef DEBUG - (*argArray)[i]->AnnotateStrongRetainer("timer_argument"); + (*argArray)[i]->AnnotateStrongRetainer("timer_argument"); #endif - } - } - - auto task = std::make_shared(isolate, handler, timeout, repeatable, argArray, id, now_ms()); + } + } + + auto task = std::make_shared(isolate, handler, timeout, + repeatable, argArray, id, now_ms()); #ifdef DEBUG - task->callback_.AnnotateStrongRetainer("timer"); + task->callback_.AnnotateStrongRetainer("timer"); #endif - task->repeats_ = repeatable; - - CFRunLoopTimerContext timerContext = {0, NULL, NULL, NULL, NULL}; - auto timerData = new TimerContext(); - timerData->task = task; - timerData->state = state; - timerContext.info = timerData; - timerContext.retain = TimerContext::TimerRetain; - timerContext.release = TimerContext::TimerRelease; - - // we do this because the timer should take hold of exactly 1 retaincount after scheduling - // so if by our manual release the retain is 0 then we need to cleanup the TimerContext - TimerContext::TimerRetain(timerData); - - // timeout should be bigger than 0 if it's repeatable and 0 - auto timeoutInSeconds = repeatable && timeout == 0 ? 0.0000001f : timeout / 1000.f; - auto timer = CFRunLoopTimerCreate(kCFAllocatorDefault, - CFAbsoluteTimeGetCurrent() + timeoutInSeconds, - repeatable ? timeoutInSeconds : 0, - 0, 0, - TimerCallback, - &timerContext); - state->addTask(task); - // set the actual timer we created - task->timer = timer; - CFRunLoopAddTimer(state->runloop, timer, kCFRunLoopCommonModes); - TimerContext::TimerRelease(timerData); - // auto task = std::make_shared(isolate, handler, timeout, repeatable, - // argArray, id, now_ms()); - // thiz->addTask(task); - } - args.GetReturnValue().Set(id); -} + task->repeats_ = repeatable; -void Timers::SetTimeoutCallback(const v8::FunctionCallbackInfo& args) { - Timers::SetTimer(args, false); - + CFRunLoopTimerContext timerContext = {0, NULL, NULL, NULL, NULL}; + auto timerData = new TimerContext(); + timerData->task = task; + timerData->state = state; + timerContext.info = timerData; + timerContext.retain = TimerContext::TimerRetain; + timerContext.release = TimerContext::TimerRelease; + + // we do this because the timer should take hold of exactly 1 retaincount + // after scheduling so if by our manual release the retain is 0 then we need + // to cleanup the TimerContext + TimerContext::TimerRetain(timerData); + + // timeout should be bigger than 0 if it's repeatable and 0 + auto timeoutInSeconds = + repeatable && timeout == 0 ? 0.0000001f : timeout / 1000.f; + auto timer = CFRunLoopTimerCreate( + kCFAllocatorDefault, CFAbsoluteTimeGetCurrent() + timeoutInSeconds, + repeatable ? timeoutInSeconds : 0, 0, 0, TimerCallback, &timerContext); + state->addTask(task); + // set the actual timer we created + task->timer = timer; + CFRunLoopAddTimer(state->runloop, timer, kCFRunLoopCommonModes); + TimerContext::TimerRelease(timerData); + // auto task = std::make_shared(isolate, handler, timeout, + // repeatable, + // argArray, id, now_ms()); + // thiz->addTask(task); + } + args.GetReturnValue().Set(id); } -void Timers::SetIntervalCallback(const v8::FunctionCallbackInfo& args) { - Timers::SetTimer(args, true); - +void Timers::SetTimeoutCallback( + const v8::FunctionCallbackInfo& args) { + Timers::SetTimer(args, false); } -void Timers::ClearTimeoutCallback(const v8::FunctionCallbackInfo &args) { - auto argLength = args.Length(); - auto extData = args.Data().As(); - auto thiz = reinterpret_cast(extData->Value()); - int id = -1; - if (argLength > 0) { - auto isolate = args.GetIsolate(); - auto ctx = isolate->GetCurrentContext(); - id = (int) ToMaybePositiveValue(args[0], ctx); - } - // ids start at 1 - if (id > 0) { - thiz->removeTask(id); - } +void Timers::SetIntervalCallback( + const v8::FunctionCallbackInfo& args) { + Timers::SetTimer(args, true); } +void Timers::ClearTimeoutCallback( + const v8::FunctionCallbackInfo& args) { + auto argLength = args.Length(); + auto extData = args.Data().As(); + auto thiz = reinterpret_cast(extData->Value()); + int id = -1; + if (argLength > 0) { + auto isolate = args.GetIsolate(); + auto ctx = isolate->GetCurrentContext(); + id = (int)ToMaybePositiveValue(args[0], ctx); + } + // ids start at 1 + if (id > 0) { + thiz->removeTask(id); + } } +} // namespace tns NODE_BINDING_PER_ISOLATE_INIT_OBJ(timers, tns::Timers::Init)