diff --git a/.gitignore b/.gitignore index 062b862..b0b0ff4 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,9 @@ local.properties .idea/caches .idea +# Visual Studio +.vs/ + # External native build folder generated in Android Studio 2.2 and later .externalNativeBuild diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index 3eb3da1..de440e1 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -38,6 +38,12 @@ else () add_definitions(-DENABLE_PROFILE=0) endif () +if (${ENABLE_LOG}) + add_definitions(-DENABLE_LOG=1) +else() + add_definitions(-DENABLE_LOG=0) +endif() + if(NOT MSVC) if (${CMAKE_BUILD_TYPE} STREQUAL "Release" OR ${CMAKE_BUILD_TYPE} STREQUAL "RelWithDebInfo") include(CheckIPOSupported) diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h index 12c3df8..d39c3ca 100644 --- a/bridge/bindings/qjs/converter_impl.h +++ b/bridge/bindings/qjs/converter_impl.h @@ -230,6 +230,10 @@ struct Converter> : public ConverterBase } static JSValue ToValue(JSContext* ctx, const std::string& str) { return Converter::ToValue(ctx, str); } static JSValue ToValue(JSContext* ctx, typename Converter::ImplType value) { + if (value == AtomicString::Null()) { + return JS_UNDEFINED; + } + return Converter::ToValue(ctx, std::move(value)); } }; @@ -243,7 +247,12 @@ struct Converter> : public ConverterBase } static JSValue ToValue(JSContext* ctx, const std::string& value) { return AtomicString(ctx, value).ToQuickJS(ctx); } - static JSValue ToValue(JSContext* ctx, const AtomicString& value) { return value.ToQuickJS(ctx); } + static JSValue ToValue(JSContext* ctx, const AtomicString& value) { + if (value == AtomicString::Null()) { + return JS_NULL; + } + return value.ToQuickJS(ctx); + } }; template <> @@ -340,6 +349,8 @@ struct Converter>> : public ConverterBase>::FromValue(ctx, value, exception_state); } + + static JSValue ToValue(JSContext* ctx, ImplType value) { return Converter>::ToValue(ctx, value); } }; template @@ -395,6 +406,17 @@ template <> struct Converter : public ConverterBase { static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { assert(!JS_IsException(value)); + if (JS_IsObject(value) && !JS_IsFunction(ctx, value)) { + JSValue handleEventMethod = JS_GetPropertyStr(ctx, value, "handleEvent"); + + if (JS_IsFunction(ctx, handleEventMethod)) { + auto result = JSEventListener::CreateOrNull(QJSFunction::Create(ctx, handleEventMethod, value)); + JS_FreeValue(ctx, handleEventMethod); + return result; + } + + return JSEventListener::CreateOrNull(nullptr); + } return JSEventListener::CreateOrNull(QJSFunction::Create(ctx, value)); } }; @@ -445,6 +467,10 @@ struct Converter> : public ConverterBase::FromValue(ctx, value, exception_state); } @@ -458,7 +484,12 @@ struct Converter return T::Create(ctx, value, exception_state); } - static JSValue ToValue(JSContext* ctx, typename T::ImplType value) { return value->toQuickJS(ctx); } + static JSValue ToValue(JSContext* ctx, typename T::ImplType value) { + if (value == nullptr) + return JS_NULL; + + return value->toQuickJS(ctx); + } }; template @@ -532,8 +563,16 @@ struct ConverterclassName)); return nullptr; } - static JSValue ToValue(JSContext* ctx, T* value) { return value->ToQuickJS(); } - static JSValue ToValue(JSContext* ctx, const T* value) { return value->ToQuickJS(); } + static JSValue ToValue(JSContext* ctx, T* value) { + if (value == nullptr) + return JS_NULL; + return value->ToQuickJS(); + } + static JSValue ToValue(JSContext* ctx, const T* value) { + if (value == nullptr) + return JS_NULL; + return value->ToQuickJS(); + } }; template diff --git a/bridge/bindings/qjs/cppgc/member.h b/bridge/bindings/qjs/cppgc/member.h index 73ec8e5..fa55455 100644 --- a/bridge/bindings/qjs/cppgc/member.h +++ b/bridge/bindings/qjs/cppgc/member.h @@ -25,24 +25,21 @@ class ScriptWrappable; template > class Member { public: + struct KeyHasher { + std::size_t operator()(const Member& k) const { return reinterpret_cast(k.raw_); } + }; + Member() = default; Member(T* ptr) { SetRaw(ptr); } Member(const Member& other) { raw_ = other.raw_; runtime_ = other.runtime_; js_object_ptr_ = other.js_object_ptr_; + ((JSRefCountHeader*)other.js_object_ptr_)->ref_count++; } ~Member() { if (raw_ != nullptr) { - assert(runtime_ != nullptr); - // There are two ways to free the member values: - // One is by GC marking and sweep stage. - // Two is by free directly when running out of function body. - // We detect the GC phase to handle case two, and free our members by hand(call JS_FreeValueRT directly). - JSGCPhaseEnum phase = JS_GetEnginePhase(runtime_); - if (phase == JS_GC_PHASE_DECREF || phase == JS_GC_PHASE_REMOVE_CYCLES) { - JS_FreeValueRT(runtime_, JS_MKPTR(JS_TAG_OBJECT, js_object_ptr_)); - } + JS_FreeValueRT(runtime_, JS_MKPTR(JS_TAG_OBJECT, js_object_ptr_)); } }; @@ -57,9 +54,9 @@ class Member { JS_FreeValueRT(runtime_, JS_MKPTR(JS_TAG_OBJECT, js_object_ptr_)); } else { auto* wrappable = To(raw_); - assert(wrappable->GetExecutingContext()->HasMutationScope()); + assert(wrappable->executingContext()->HasMutationScope()); // Record the free operation to avoid JSObject had been freed immediately. - wrappable->GetExecutingContext()->mutationScope()->RecordFree(wrappable); + wrappable->executingContext()->mutationScope()->RecordFree(wrappable); } raw_ = nullptr; js_object_ptr_ = nullptr; @@ -70,6 +67,7 @@ class Member { raw_ = other.raw_; runtime_ = other.runtime_; js_object_ptr_ = other.js_object_ptr_; + ((JSRefCountHeader*)other.js_object_ptr_)->ref_count++; return *this; } // Move assignment. @@ -94,11 +92,17 @@ class Member { T* operator->() const { return Get(); } T& operator*() const { return *Get(); } + T* Release() { + T* result = Get(); + Clear(); + return result; + } + private: void SetRaw(T* p) { if (p != nullptr) { auto* wrappable = To(p); - assert_m(wrappable->GetExecutingContext()->HasMutationScope(), + assert_m(wrappable->executingContext()->HasMutationScope(), "Member must be used after MemberMutationScope allcated."); runtime_ = wrappable->runtime(); js_object_ptr_ = JS_VALUE_GET_PTR(wrappable->ToQuickJSUnsafe()); diff --git a/bridge/bindings/qjs/exception_state.cc b/bridge/bindings/qjs/exception_state.cc index 32a8daa..9bd304b 100644 --- a/bridge/bindings/qjs/exception_state.cc +++ b/bridge/bindings/qjs/exception_state.cc @@ -42,4 +42,8 @@ JSValue ExceptionState::ToQuickJS() { return exception_; } +JSValue ExceptionState::CurrentException(JSContext* ctx) { + return JS_GetException(ctx); +} + } // namespace mercury diff --git a/bridge/bindings/qjs/exception_state.h b/bridge/bindings/qjs/exception_state.h index ba73a09..f91f56f 100644 --- a/bridge/bindings/qjs/exception_state.h +++ b/bridge/bindings/qjs/exception_state.h @@ -29,6 +29,8 @@ class ExceptionState { JSValue ToQuickJS(); + static JSValue CurrentException(JSContext* ctx); + private: JSValue exception_{JS_NULL}; }; diff --git a/bridge/bindings/qjs/heap_vector.h b/bridge/bindings/qjs/heap_vector.h index 58b7419..32ed906 100644 --- a/bridge/bindings/qjs/heap_vector.h +++ b/bridge/bindings/qjs/heap_vector.h @@ -12,19 +12,27 @@ class HeapVector final { public: HeapVector() = default; - void Trace(GCVisitor* visitor) const; + void TraceValue(GCVisitor* visitor) const; + void TraceMember(GCVisitor* visitor) const; private: std::vector entries_; }; template -void HeapVector::Trace(GCVisitor* visitor) const { +void HeapVector::TraceValue(GCVisitor* visitor) const { for (auto& item : entries_) { visitor->TraceValue(item); } } +template +void HeapVector::TraceMember(GCVisitor* visitor) const { + for (auto& item : entries_) { + visitor->TraceMember(item); + } +} + } // namespace mercury #endif // BRIDGE_BINDINGS_QJS_HEAP_VECTOR_H_ diff --git a/bridge/bindings/qjs/idl_type.h b/bridge/bindings/qjs/idl_type.h index a86652d..a1b4aad 100644 --- a/bridge/bindings/qjs/idl_type.h +++ b/bridge/bindings/qjs/idl_type.h @@ -44,7 +44,7 @@ struct IDLInt64 final : public IDLTypeBaseHelper {}; struct IDLUint32 final : public IDLTypeBaseHelper {}; struct IDLDouble final : public IDLTypeBaseHelper {}; -class SharedNativeString; +struct SharedNativeString; // DOMString is UTF-16 strings. // https://stackoverflow.com/questions/35123890/what-is-a-domstring-really struct IDLDOMString final : public IDLTypeBaseHelper {}; diff --git a/bridge/bindings/qjs/qjs_function.cc b/bridge/bindings/qjs/qjs_function.cc index b48578f..b647006 100644 --- a/bridge/bindings/qjs/qjs_function.cc +++ b/bridge/bindings/qjs/qjs_function.cc @@ -56,11 +56,14 @@ ScriptValue QJSFunction::Invoke(JSContext* ctx, const ScriptValue& this_val, int for (int i = 0; i < argc; i++) { argv[0 + i] = arguments[i].QJSValue(); } + ExecutingContext* context = ExecutingContext::From(ctx); + // prof: context->dartIsolateContext()->profiler()->StartTrackSteps("JS_Call"); - JSValue returnValue = JS_Call(ctx, function_, this_val.QJSValue(), argc, argv); + JSValue returnValue = JS_Call(ctx, function_, JS_IsNull(this_val_) ? this_val.QJSValue() : this_val_, argc, argv); - ExecutingContext* context = ExecutingContext::From(ctx); - context->DrainPendingPromiseJobs(); + // prof: context->dartIsolateContext()->profiler()->FinishTrackSteps(); + + context->DrainMicrotasks(); // Free the previous duplicated function. JS_FreeValue(ctx, function_); @@ -72,6 +75,7 @@ ScriptValue QJSFunction::Invoke(JSContext* ctx, const ScriptValue& this_val, int void QJSFunction::Trace(GCVisitor* visitor) const { visitor->TraceValue(function_); + visitor->TraceValue(this_val_); } } // namespace mercury diff --git a/bridge/bindings/qjs/qjs_function.h b/bridge/bindings/qjs/qjs_function.h index 3bb9070..5833533 100644 --- a/bridge/bindings/qjs/qjs_function.h +++ b/bridge/bindings/qjs/qjs_function.h @@ -28,10 +28,21 @@ class QJSFunction { void* private_data) { return std::make_shared(ctx, qjs_function_callback, length, private_data); } + static std::shared_ptr Create(JSContext* ctx, JSValue function, JSValue this_val) { + return std::make_shared(ctx, function, this_val); + }; explicit QJSFunction(JSContext* ctx, JSValue function) : ctx_(ctx), runtime_(JS_GetRuntime(ctx)), function_(JS_DupValue(ctx, function)){}; explicit QJSFunction(JSContext* ctx, QJSFunctionCallback qjs_function_callback, int32_t length, void* private_data); - ~QJSFunction() { JS_FreeValueRT(runtime_, function_); } + explicit QJSFunction(JSContext* ctx, JSValue function, JSValue this_val) + : ctx_(ctx), + runtime_(JS_GetRuntime(ctx)), + function_(JS_DupValue(ctx, function)), + this_val_(JS_DupValue(ctx, this_val)) {} + ~QJSFunction() { + JS_FreeValueRT(runtime_, function_); + JS_FreeValueRT(runtime_, this_val_); + } bool IsFunction(JSContext* ctx); @@ -52,6 +63,7 @@ class QJSFunction { JSContext* ctx_{nullptr}; JSRuntime* runtime_{nullptr}; JSValue function_{JS_NULL}; + JSValue this_val_{JS_NULL}; }; } // namespace mercury diff --git a/bridge/bindings/qjs/script_promise_resolver.cc b/bridge/bindings/qjs/script_promise_resolver.cc index 780960e..faad9b8 100644 --- a/bridge/bindings/qjs/script_promise_resolver.cc +++ b/bridge/bindings/qjs/script_promise_resolver.cc @@ -36,6 +36,7 @@ ScriptPromise ScriptPromiseResolver::Promise() { } void ScriptPromiseResolver::ResolveOrRejectImmediately(JSValue value) { + // prof: context_->dartIsolateContext()->profiler()->StartTrackAsyncEvaluation(); { if (state_ == kResolving) { JSValue arguments[] = {value}; @@ -54,7 +55,8 @@ void ScriptPromiseResolver::ResolveOrRejectImmediately(JSValue value) { JS_FreeValue(context_->ctx(), return_value); } } - context_->DrainPendingPromiseJobs(); + context_->DrainMicrotasks(); + // prof: context_->dartIsolateContext()->profiler()->FinishTrackAsyncEvaluation(); } } // namespace mercury diff --git a/bridge/bindings/qjs/script_wrappable.cc b/bridge/bindings/qjs/script_wrappable.cc index c2c3ae2..bd432f8 100644 --- a/bridge/bindings/qjs/script_wrappable.cc +++ b/bridge/bindings/qjs/script_wrappable.cc @@ -30,6 +30,10 @@ ScriptValue ScriptWrappable::ToValue() { return ScriptValue(ctx_, jsObject_); } +multi_threading::Dispatcher* ScriptWrappable::GetDispatcher() const { + return context_->dartIsolateContext()->dispatcher().get(); +} + /// This callback will be called when QuickJS GC is running at marking stage. /// Users of this class should override `void TraceMember(JSRuntime* rt, JSValueConst val, JS_MarkFunc* mark_func)` to /// tell GC which member of their class should be collected by GC. @@ -47,8 +51,8 @@ static void HandleJSObjectFinalized(JSRuntime* rt, JSValue val) { // When a JSObject got finalized by QuickJS GC, we can not guarantee the ExecutingContext are still alive and // accessible. if (isContextValid(object->contextId())) { - ExecutingContext* context = object->GetExecutingContext(); - MemberMutationScope scope{object->GetExecutingContext()}; + ExecutingContext* context = object->executingContext(); + MemberMutationScope scope{object->executingContext()}; delete object; } else { delete object; @@ -240,6 +244,8 @@ void ScriptWrappable::InitializeQuickJSObject() { desc->value = return_value; desc->getter = JS_NULL; desc->setter = JS_NULL; + } else { + JS_FreeValue(ctx, return_value); } return true; } @@ -254,6 +260,8 @@ void ScriptWrappable::InitializeQuickJSObject() { desc->value = return_value; desc->getter = JS_NULL; desc->setter = JS_NULL; + } else { + JS_FreeValue(ctx, return_value); } return true; } @@ -285,7 +293,7 @@ void ScriptWrappable::InitializeQuickJSObject() { JS_SetOpaque(jsObject_, this); // Let our instance into inherit prototype methods. - JSValue prototype = GetExecutingContext()->contextData()->prototypeForType(wrapper_type_info); + JSValue prototype = executingContext()->contextData()->prototypeForType(wrapper_type_info); JS_SetPrototype(ctx_, jsObject_, prototype); } diff --git a/bridge/bindings/qjs/script_wrappable.h b/bridge/bindings/qjs/script_wrappable.h index 4ce97c8..a6ac80e 100644 --- a/bridge/bindings/qjs/script_wrappable.h +++ b/bridge/bindings/qjs/script_wrappable.h @@ -9,6 +9,7 @@ #include #include "bindings/qjs/cppgc/garbage_collected.h" #include "foundation/macros.h" +#include "multiple_threading/dispatcher.h" #include "wrapper_type_info.h" namespace mercury { @@ -51,7 +52,8 @@ class ScriptWrappable : public GarbageCollected { JSValue ToQuickJSUnsafe() const; ScriptValue ToValue(); - FORCE_INLINE ExecutingContext* GetExecutingContext() const { return context_; }; + FORCE_INLINE ExecutingContext* executingContext() const { return context_; }; + multi_threading::Dispatcher* GetDispatcher() const; FORCE_INLINE JSContext* ctx() const { return ctx_; } FORCE_INLINE JSRuntime* runtime() const { return runtime_; } FORCE_INLINE int64_t contextId() const { return context_id_; } @@ -91,8 +93,8 @@ Local::~Local() { return; auto* wrappable = To(raw_); // Record the free operation to avoid JSObject had been freed immediately. - if (LIKELY(wrappable->GetExecutingContext()->HasMutationScope())) { - wrappable->GetExecutingContext()->mutationScope()->RecordFree(wrappable); + if (LIKELY(wrappable->executingContext()->HasMutationScope())) { + wrappable->executingContext()->mutationScope()->RecordFree(wrappable); } else { assert_m(false, "LocalHandle must be used after MemberMutationScope allcated."); } diff --git a/bridge/core/api/api.cc b/bridge/core/api/api.cc new file mode 100644 index 0000000..f0aec74 --- /dev/null +++ b/bridge/core/api/api.cc @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "api.h" +#include "core/dart_isolate_context.h" +#include "core/mercury_isolate.h" +#include "multiple_threading/dispatcher.h" + +namespace mercury { + +static void ReturnEvaluateScriptsInternal(Dart_PersistentHandle persistent_handle, + EvaluateQuickjsByteCodeCallback result_callback, + bool is_success) { + Dart_Handle handle = Dart_HandleFromPersistent_DL(persistent_handle); + result_callback(handle, is_success ? 1 : 0); + Dart_DeletePersistentHandle_DL(persistent_handle); +} + +void evaluateScriptsInternal(void* mercury_isolate_, + const char* code, + uint64_t code_len, + uint8_t** parsed_bytecodes, + uint64_t* bytecode_len, + const char* bundleFilename, + int32_t startLine, + int64_t profile_id, + Dart_Handle persistent_handle, + EvaluateScriptsCallback result_callback) { + auto mercury_isolate = reinterpret_cast(mercury_isolate_); + assert(std::this_thread::get_id() == mercury_isolate->currentThread()); + + // prof: mercury_isolate->dartIsolateContext()->profiler()->StartTrackEvaluation(profile_id); + + bool is_success = mercury_isolate->evaluateScript(code, code_len, parsed_bytecodes, bytecode_len, bundleFilename, startLine); + + // prof: mercury_isolate->dartIsolateContext()->profiler()->FinishTrackEvaluation(profile_id); + + mercury_isolate->dartIsolateContext()->dispatcher()->PostToDart(mercury_isolate->isDedicated(), ReturnEvaluateScriptsInternal, + persistent_handle, result_callback, is_success); +} + +static void ReturnEvaluateQuickjsByteCodeResultToDart(Dart_PersistentHandle persistent_handle, + EvaluateQuickjsByteCodeCallback result_callback, + bool is_success) { + Dart_Handle handle = Dart_HandleFromPersistent_DL(persistent_handle); + result_callback(handle, is_success ? 1 : 0); + Dart_DeletePersistentHandle_DL(persistent_handle); +} + +void evaluateQuickjsByteCodeInternal(void* mercury_isolate_, + uint8_t* bytes, + int32_t byteLen, + int64_t profile_id, + Dart_PersistentHandle persistent_handle, + EvaluateQuickjsByteCodeCallback result_callback) { + auto mercury_isolate = reinterpret_cast(mercury_isolate_); + assert(std::this_thread::get_id() == mercury_isolate->currentThread()); + + // prof: mercury_isolate->dartIsolateContext()->profiler()->StartTrackEvaluation(profile_id); + + bool is_success = mercury_isolate->evaluateByteCode(bytes, byteLen); + + // prof: mercury_isolate->dartIsolateContext()->profiler()->FinishTrackEvaluation(profile_id); + + mercury_isolate->dartIsolateContext()->dispatcher()->PostToDart(mercury_isolate->isDedicated(), ReturnEvaluateQuickjsByteCodeResultToDart, + persistent_handle, result_callback, is_success); +} + +static void ReturnInvokeEventResultToDart(Dart_Handle persistent_handle, + InvokeModuleEventCallback result_callback, + mercury::NativeValue* result) { + Dart_Handle handle = Dart_HandleFromPersistent_DL(persistent_handle); + result_callback(handle, result); + Dart_DeletePersistentHandle_DL(persistent_handle); +} + +void invokeModuleEventInternal(void* mercury_isolate_, + void* module_name, + const char* eventType, + void* event, + void* extra, + Dart_Handle persistent_handle, + InvokeModuleEventCallback result_callback) { + auto mercury_isolate = reinterpret_cast(mercury_isolate_); + auto dart_isolate_context = mercury_isolate->executingContext()->dartIsolateContext(); + assert(std::this_thread::get_id() == mercury_isolate->currentThread()); + + // prof: mercury_isolate->dartIsolateContext()->profiler()->StartTrackAsyncEvaluation(); + + auto* result = mercury_isolate->invokeModuleEvent(reinterpret_cast(module_name), eventType, event, + reinterpret_cast(extra)); + + // prof: mercury_isolate->dartIsolateContext()->profiler()->FinishTrackAsyncEvaluation(); + + dart_isolate_context->dispatcher()->PostToDart(mercury_isolate->isDedicated(), ReturnInvokeEventResultToDart, persistent_handle, + result_callback, result); +} + +static void ReturnDumpByteCodeResultToDart(Dart_Handle persistent_handle, DumpQuickjsByteCodeCallback result_callback) { + Dart_Handle handle = Dart_HandleFromPersistent_DL(persistent_handle); + result_callback(handle); + Dart_DeletePersistentHandle_DL(persistent_handle); +} + +void dumpQuickJsByteCodeInternal(void* mercury_isolate_, + int64_t profile_id, + const char* code, + int32_t code_len, + uint8_t** parsed_bytecodes, + uint64_t* bytecode_len, + const char* url, + Dart_PersistentHandle persistent_handle, + DumpQuickjsByteCodeCallback result_callback) { + auto mercury_isolate = reinterpret_cast(mercury_isolate_); + auto dart_isolate_context = mercury_isolate->executingContext()->dartIsolateContext(); + + // prof: dart_isolate_context->profiler()->StartTrackEvaluation(profile_id); + + assert(std::this_thread::get_id() == mercury_isolate->currentThread()); + uint8_t* bytes = mercury_isolate->dumpByteCode(code, code_len, url, bytecode_len); + *parsed_bytecodes = bytes; + + // prof: dart_isolate_context->profiler()->FinishTrackEvaluation(profile_id); + + dart_isolate_context->dispatcher()->PostToDart(mercury_isolate->isDedicated(), ReturnDumpByteCodeResultToDart, persistent_handle, + result_callback); +} + +} // namespace mercury diff --git a/bridge/core/api/api.h b/bridge/core/api/api.h new file mode 100644 index 0000000..89267c5 --- /dev/null +++ b/bridge/core/api/api.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef MERCURY_CORE_API_API_H_ +#define MERCURY_CORE_API_API_H_ + +#include +#include "include/mercury_bridge.h" + +namespace mercury { + +void evaluateScriptsInternal(void* page_, + const char* code, + uint64_t code_len, + uint8_t** parsed_bytecodes, + uint64_t* bytecode_len, + const char* bundleFilename, + int32_t startLine, + int64_t profile_id, + Dart_Handle dart_handle, + EvaluateScriptsCallback result_callback); + +void evaluateQuickjsByteCodeInternal(void* page_, + uint8_t* bytes, + int32_t byteLen, + int64_t profile_id, + Dart_PersistentHandle persistent_handle, + EvaluateQuickjsByteCodeCallback result_callback); + +void invokeModuleEventInternal(void* page_, + void* module_name, + const char* eventType, + void* event, + void* extra, + Dart_Handle dart_handle, + InvokeModuleEventCallback result_callback); + +void dumpQuickJsByteCodeInternal(void* page_, + int64_t profile_id, + const char* code, + int32_t code_len, + uint8_t** parsed_bytecodes, + uint64_t* bytecode_len, + const char* url, + Dart_PersistentHandle persistent_handle, + DumpQuickjsByteCodeCallback result_callback); + +} // namespace mercury + +#endif // MERCURY_CORE_API_API_H_ diff --git a/bridge/core/binding_object.cc b/bridge/core/binding_object.cc index bf1764f..a7c3d91 100644 --- a/bridge/core/binding_object.cc +++ b/bridge/core/binding_object.cc @@ -11,6 +11,7 @@ #include "core/executing_context.h" #include "foundation/native_string.h" #include "foundation/native_value_converter.h" +#include "logging.h" namespace mercury { @@ -24,8 +25,13 @@ void NativeBindingObject::HandleCallFromDartSide(NativeBindingObject* binding_ob binding_object->binding_target_->ctx(), std::unique_ptr(reinterpret_cast(native_method->u.ptr))); NativeValue result = binding_object->binding_target_->HandleCallFromDartSide(method, argc, argv, dart_object); - if (return_value != nullptr) - *return_value = result; + auto* return_value = new NativeValue(); + std::memcpy(return_value, &result, sizeof(NativeValue)); + + // prof: dart_isolate_context->profiler()->FinishTrackEvaluation(profile_id); + + dart_isolate_context->dispatcher()->PostToDart(binding_object->binding_target_->GetExecutingContext()->isDedicated(), + ReturnEventResultToDart, dart_object, return_value, result_callback); } BindingObject::BindingObject(JSContext* ctx) : ScriptWrappable(ctx), binding_object_(new NativeBindingObject(this)) {} @@ -39,14 +45,14 @@ BindingObject::~BindingObject() { // When a JSObject got finalized by QuickJS GC, we can not guarantee the ExecutingContext are still alive and // accessible. if (isContextValid(contextId())) { - GetExecutingContext()->isolateCommandBuffer()->addCommand(IsolateCommand::kDisposeBindingObject, nullptr, bindingObject(), + executingContext()->isolateCommandBuffer()->AddCommand(IsolateCommand::kDisposeBindingObject, nullptr, bindingObject(), nullptr, false); } } BindingObject::BindingObject(JSContext* ctx, NativeBindingObject* native_binding_object) : ScriptWrappable(ctx) { native_binding_object->binding_target_ = this; - native_binding_object->invoke_binding_methods_from_dart = NativeBindingObject::HandleCallFromDartSide; + native_binding_object->invoke_binding_methods_from_dart = HandleCallFromDartSideWrapper; binding_object_ = native_binding_object; } @@ -68,49 +74,138 @@ NativeValue BindingObject::HandleCallFromDartSide(const AtomicString& method, NativeValue BindingObject::InvokeBindingMethod(const AtomicString& method, int32_t argc, const NativeValue* argv, + uint32_t reason, ExceptionState& exception_state) const { - if (binding_object_->invoke_bindings_methods_from_native == nullptr) { - GetExecutingContext()->FlushIsolateCommand(); - exception_state.ThrowException(GetExecutingContext()->ctx(), ErrorType::InternalError, - "Failed to call dart method: invoke_bindings_methods_from_native not initialized."); - return Native_NewNull(); - } + auto* context = GetExecutingContext(); + // prof: auto* profiler = context->dartIsolateContext()->profiler(); + + // prof: profiler->StartTrackSteps("BindingObject::InvokeBindingMethod"); + + std::vector invoke_objects_deps; + // Collect all objects in arguments. + CollectObjectDepsOnArgs(invoke_objects_deps, argc, argv); + // Make sure all these objects are ready in dart. + context->FlushIsolateCommand(this, reason, invoke_objects_deps); NativeValue return_value = Native_NewNull(); - NativeValue native_method = - NativeValueConverter::ToNativeValue(GetExecutingContext()->ctx(), method); - binding_object_->invoke_bindings_methods_from_native(GetExecutingContext()->contextId(), binding_object_, - &return_value, &native_method, argc, argv); + +#if ENABLE_LOG + WEBF_LOG(INFO) << "[Dispatcher]: PostToDartSync method: InvokeBindingMethod; Call Begin"; +#endif + + // prof: profiler->StartTrackLinkSteps("Call To Dart"); + + NativeValue native_method = NativeValueConverter::ToNativeValue(binding_method_call_operation); + GetDispatcher()->PostToDartSync( + GetExecutingContext()->isDedicated(), contextId(), + [&](bool cancel, double contextId, int64_t profile_id, const NativeBindingObject* binding_object, + NativeValue* return_value, NativeValue* method, int32_t argc, const NativeValue* argv) { + if (cancel) + return; + +#if ENABLE_LOG + WEBF_LOG(INFO) << "[Dispatcher]: PostToDartSync method: InvokeBindingMethod; Callback Start"; +#endif + + if (binding_object_->invoke_bindings_methods_from_native == nullptr) { + WEBF_LOG(DEBUG) << "invoke_bindings_methods_from_native is nullptr" << std::endl; + return; + } + binding_object_->invoke_bindings_methods_from_native(contextId, profile_id, binding_object, return_value, + method, argc, argv); +#if ENABLE_LOG + WEBF_LOG(INFO) << "[Dispatcher]: PostToDartSync method: InvokeBindingMethod; Callback End"; +#endif + }, + context->contextId(), /* prof: profiler->link_id() */, binding_object_, &return_value, &native_method, argc, argv); + +#if ENABLE_LOG + WEBF_LOG(INFO) << "[Dispatcher]: PostToDartSync method: InvokeBindingMethod; Call End"; +#endif + + // prof: profiler->FinishTrackLinkSteps(); + // prof: profiler->FinishTrackSteps(); + return return_value; } NativeValue BindingObject::InvokeBindingMethod(BindingMethodCallOperations binding_method_call_operation, size_t argc, const NativeValue* argv, + uint32_t reason, ExceptionState& exception_state) const { - if (binding_object_->invoke_bindings_methods_from_native == nullptr) { - GetExecutingContext()->FlushIsolateCommand(); - exception_state.ThrowException(GetExecutingContext()->ctx(), ErrorType::InternalError, - "Failed to call dart method: invoke_bindings_methods_from_native not initialized."); - return Native_NewNull(); - } + auto* context = GetExecutingContext(); + // prof: auto* profiler = context->dartIsolateContext()->profiler(); + + // prof: profiler->StartTrackSteps("BindingObject::InvokeBindingMethod"); + + std::vector invoke_objects_deps; + // Collect all objects in arguments. + CollectObjectDepsOnArgs(invoke_objects_deps, argc, argv); + // Make sure all these objects are ready in dart. + context->FlushIsolateCommand(this, reason, invoke_objects_deps); NativeValue return_value = Native_NewNull(); - NativeValue native_method = NativeValueConverter::ToNativeValue(binding_method_call_operation); - binding_object_->invoke_bindings_methods_from_native(GetExecutingContext()->contextId(), binding_object_, - &return_value, &native_method, argc, argv); + NativeValue native_method = + NativeValueConverter::ToNativeValue(GetExecutingContext()->ctx(), method); + +#if ENABLE_LOG + WEBF_LOG(INFO) << "[Dispatcher]: PostToDartSync method: InvokeBindingMethod; Call Begin"; +#endif + + profiler->StartTrackLinkSteps("Call To Dart"); + + GetDispatcher()->PostToDartSync( + GetExecutingContext()->isDedicated(), contextId(), + [&](bool cancel, double contextId, int64_t profile_id, const NativeBindingObject* binding_object, + NativeValue* return_value, NativeValue* method, int32_t argc, const NativeValue* argv) { + if (cancel) + return; + +#if ENABLE_LOG + WEBF_LOG(INFO) << "[Dispatcher]: PostToDartSync method: InvokeBindingMethod; Callback Start"; +#endif + + if (binding_object_->invoke_bindings_methods_from_native == nullptr) { + WEBF_LOG(DEBUG) << "invoke_bindings_methods_from_native is nullptr" << std::endl; + return; + } + binding_object_->invoke_bindings_methods_from_native(contextId, profile_id, binding_object, return_value, + method, argc, argv); +#if ENABLE_LOG + WEBF_LOG(INFO) << "[Dispatcher]: PostToDartSync method: InvokeBindingMethod; Callback End"; +#endif + }, + GetExecutingContext()->contextId(),/* prof: profiler->link_id() */, binding_object_, &return_value, &native_method, argc, + argv); + +#if ENABLE_LOG + WEBF_LOG(INFO) << "[Dispatcher]: PostToDartSync method: InvokeBindingMethod; Call End"; +#endif + + // prof: profiler->FinishTrackLinkSteps(); + // prof: profiler->FinishTrackSteps(); + return return_value; } -NativeValue BindingObject::GetBindingProperty(const AtomicString& prop, ExceptionState& exception_state) const { +NativeValue BindingObject::GetBindingProperty(const AtomicString& prop, + uint32_t reason, + ExceptionState& exception_state) const { if (UNLIKELY(binding_object_->disposed_)) { exception_state.ThrowException( ctx(), ErrorType::InternalError, "Can not get binding property on BindingObject, dart binding object had been disposed"); return Native_NewNull(); } + // prof: GetExecutingContext()->dartIsolateContext()->profiler()->StartTrackSteps("BindingObject::GetBindingProperty"); + const NativeValue argv[] = {Native_NewString(prop.ToNativeString(GetExecutingContext()->ctx()).release())}; - return InvokeBindingMethod(BindingMethodCallOperations::kGetProperty, 1, argv, exception_state); + NativeValue result = InvokeBindingMethod(BindingMethodCallOperations::kGetProperty, 1, argv, reason, exception_state); + + // prof: GetExecutingContext()->dartIsolateContext()->profiler()->FinishTrackSteps(); + + return result; } NativeValue BindingObject::SetBindingProperty(const AtomicString& prop, @@ -122,9 +217,10 @@ NativeValue BindingObject::SetBindingProperty(const AtomicString& prop, "Can not set binding property on BindingObject, dart binding object had been disposed"); return Native_NewNull(); } - GetExecutingContext()->FlushIsolateCommand(); + const NativeValue argv[] = {Native_NewString(prop.ToNativeString(GetExecutingContext()->ctx()).release()), value}; - return InvokeBindingMethod(BindingMethodCallOperations::kSetProperty, 2, argv, exception_state); + return InvokeBindingMethod(BindingMethodCallOperations::kSetProperty, 2, argv, + FlushIsolateCommandReason::kDependentsOnObject, exception_state); } ScriptValue BindingObject::AnonymousFunctionCallback(JSContext* ctx, @@ -146,15 +242,16 @@ ScriptValue BindingObject::AnonymousFunctionCallback(JSContext* ctx, } if (exception_state.HasException()) { - event_target->GetExecutingContext()->HandleException(exception_state); + event_target->executingContext()->HandleException(exception_state); return ScriptValue::Empty(ctx); } - NativeValue result = event_target->InvokeBindingMethod(BindingMethodCallOperations::kAnonymousFunctionCall, - arguments.size(), arguments.data(), exception_state); + NativeValue result = + event_target->InvokeBindingMethod(BindingMethodCallOperations::kAnonymousFunctionCall, arguments.size(), + arguments.data(), FlushIsolateCommandReason::kDependentsOnObject, exception_state); if (exception_state.HasException()) { - event_target->GetExecutingContext()->HandleException(exception_state); + event_target->executingContext()->HandleException(exception_state); return ScriptValue::Empty(ctx); } return ScriptValue(ctx, result); @@ -162,7 +259,7 @@ ScriptValue BindingObject::AnonymousFunctionCallback(JSContext* ctx, void BindingObject::HandleAnonymousAsyncCalledFromDart(void* ptr, NativeValue* native_value, - int32_t contextId, + double contextId, const char* errmsg) { auto* promise_context = static_cast(ptr); if (!promise_context->context->IsContextValid()) @@ -188,6 +285,17 @@ void BindingObject::HandleAnonymousAsyncCalledFromDart(void* ptr, delete promise_context; } + +static void HandleAnonymousAsyncCalledFromDartWrapper(void* ptr, + NativeValue* native_value, + double contextId, + const char* errmsg) { + auto* promise_context = static_cast(ptr); + promise_context->context->dartIsolateContext()->dispatcher()->PostToJs( + promise_context->context->isDedicated(), contextId, BindingObject::HandleAnonymousAsyncCalledFromDart, + promise_context, native_value, contextId, errmsg); +} + ScriptValue BindingObject::AnonymousAsyncFunctionCallback(JSContext* ctx, const ScriptValue& this_val, uint32_t argc, @@ -196,10 +304,10 @@ ScriptValue BindingObject::AnonymousAsyncFunctionCallback(JSContext* ctx, auto* data = reinterpret_cast(private_data); auto* event_target = toScriptWrappable(this_val.QJSValue()); - auto promise_resolver = ScriptPromiseResolver::Create(event_target->GetExecutingContext()); + auto promise_resolver = ScriptPromiseResolver::Create(event_target->executingContext()); auto* promise_context = - new BindingObjectPromiseContext{{}, event_target->GetExecutingContext(), event_target, promise_resolver}; + new BindingObjectPromiseContext{{}, event_target->executingContext(), event_target, promise_resolver}; event_target->TrackPendingPromiseBindingContext(promise_context); std::vector arguments; @@ -207,11 +315,11 @@ ScriptValue BindingObject::AnonymousAsyncFunctionCallback(JSContext* ctx, arguments.emplace_back(NativeValueConverter::ToNativeValue(data->method_name)); arguments.emplace_back( - NativeValueConverter::ToNativeValue(event_target->GetExecutingContext()->contextId())); + NativeValueConverter::ToNativeValue(event_target->executingContext()->contextId())); arguments.emplace_back( NativeValueConverter>::ToNativeValue(promise_context)); arguments.emplace_back(NativeValueConverter>::ToNativeValue( - reinterpret_cast(HandleAnonymousAsyncCalledFromDart))); + reinterpret_cast(HandleAnonymousAsyncCalledFromDartWrapper))); ExceptionState exception_state; @@ -220,10 +328,10 @@ ScriptValue BindingObject::AnonymousAsyncFunctionCallback(JSContext* ctx, } event_target->InvokeBindingMethod(BindingMethodCallOperations::kAsyncAnonymousFunction, argc + 4, arguments.data(), - exception_state); + FlushIsolateCommandReason::kDependentsOnObject, exception_state); if (exception_state.HasException()) { - event_target->GetExecutingContext()->HandleException(exception_state); + event_target->executingContext()->HandleException(exception_state); return ScriptValue::Empty(ctx); } @@ -231,8 +339,22 @@ ScriptValue BindingObject::AnonymousAsyncFunctionCallback(JSContext* ctx, } NativeValue BindingObject::GetAllBindingPropertyNames(ExceptionState& exception_state) const { - GetExecutingContext()->FlushIsolateCommand(); - return InvokeBindingMethod(BindingMethodCallOperations::kGetAllPropertyNames, 0, nullptr, exception_state); + return InvokeBindingMethod(BindingMethodCallOperations::kGetAllPropertyNames, 0, nullptr, + FlushIsolateCommandReason::kDependentsOnObject, exception_state); +} + +void BindingObject::CollectObjectDepsOnArgs(std::vector& deps, + size_t argc, + const webf::NativeValue* args) const { + for (int i = 0; i < argc; i++) { + const NativeValue& native_value = args[i]; + if (native_value.tag == NativeTag::TAG_POINTER && + GetPointerTypeOfNativePointer(native_value) == JSPointerType::NativeBindingObject) { + NativeBindingObject* ptr = + NativeValueConverter>::FromNativeValue(native_value); + deps.emplace_back(ptr); + } + } } void BindingObject::Trace(GCVisitor* visitor) const { diff --git a/bridge/core/binding_object.h b/bridge/core/binding_object.h index 5bbafd6..9405b14 100644 --- a/bridge/core/binding_object.h +++ b/bridge/core/binding_object.h @@ -8,10 +8,11 @@ #include #include -#include +#include #include "bindings/qjs/cppgc/member.h" #include "bindings/qjs/atomic_string.h" #include "bindings/qjs/script_wrappable.h" +#include "core/dart_methods.h" #include "foundation/native_type.h" #include "foundation/native_value.h" @@ -23,36 +24,43 @@ class ExceptionState; class GCVisitor; class ScriptPromiseResolver; -using InvokeBindingsMethodsFromNative = void (*)(int32_t contextId, +using InvokeBindingsMethodsFromNative = void (*)(double contextId, +// prof: int64_t profile_id, const NativeBindingObject* binding_object, NativeValue* return_value, NativeValue* method, int32_t argc, const NativeValue* argv); +using DartInvokeResultCallback = void (*)(Dart_Handle dart_object, NativeValue* result); using InvokeBindingMethodsFromDart = void (*)(NativeBindingObject* binding_object, - NativeValue* return_value, +// prof: int64_t profile_id, NativeValue* method, int32_t argc, NativeValue* argv, - Dart_Handle dart_object); + Dart_Handle dart_object, + DartInvokeResultCallback result_callback); struct NativeBindingObject : public DartReadable { NativeBindingObject() = delete; explicit NativeBindingObject(BindingObject* target) : binding_target_(target), invoke_binding_methods_from_dart(HandleCallFromDartSide){}; - static void HandleCallFromDartSide(NativeBindingObject* binding_object, + static void HandleCallFromDartSide(DartIsolateContext* dart_isolate_context, + NativeBindingObject* binding_object, +// prof: int64_t profile_id, NativeValue* return_value, NativeValue* method, int32_t argc, NativeValue* argv, - Dart_Handle dart_object); + Dart_PersistentHandle dart_object, + DartInvokeResultCallback result_callback); bool disposed_{false}; BindingObject* binding_target_{nullptr}; InvokeBindingMethodsFromDart invoke_binding_methods_from_dart{nullptr}; InvokeBindingsMethodsFromNative invoke_bindings_methods_from_native{nullptr}; + void* extra{nullptr}; }; enum BindingMethodCallOperations { @@ -90,7 +98,7 @@ class BindingObject : public ScriptWrappable { void* private_data); static void HandleAnonymousAsyncCalledFromDart(void* ptr, NativeValue* native_value, - int32_t contextId, + double contextId, const char* errmsg); BindingObject() = delete; @@ -106,11 +114,14 @@ class BindingObject : public ScriptWrappable { NativeValue InvokeBindingMethod(const AtomicString& method, int32_t argc, const NativeValue* args, + uint32_t reason, ExceptionState& exception_state) const; NativeValue GetBindingProperty(const AtomicString& prop, ExceptionState& exception_state) const; - NativeValue SetBindingProperty(const AtomicString& prop, NativeValue value, ExceptionState& exception_state) const; + NativeValue SetBindingProperty(const AtomicString& prop, uint32_t reason, NativeValue value, ExceptionState& exception_state) const; NativeValue GetAllBindingPropertyNames(ExceptionState& exception_state) const; + void CollectObjectDepsOnArgs(std::vector& deps, size_t argc, const NativeValue* args) const; + FORCE_INLINE NativeBindingObject* bindingObject() const { return binding_object_; } void Trace(GCVisitor* visitor) const; @@ -133,6 +144,7 @@ class BindingObject : public ScriptWrappable { NativeValue InvokeBindingMethod(BindingMethodCallOperations binding_method_call_operation, size_t argc, const NativeValue* args, + uint32_t reason, ExceptionState& exception_state) const; // NativeBindingObject may allocated at Dart side. Binding this with Dart allocated NativeBindingObject. @@ -140,7 +152,7 @@ class BindingObject : public ScriptWrappable { private: NativeBindingObject* binding_object_ = nullptr; - std::set pending_promise_contexts_; + std::unordered_set pending_promise_contexts_; }; } // namespace mercury diff --git a/bridge/core/dart_isolate_context.cc b/bridge/core/dart_isolate_context.cc index 6957b0f..a9f4c25 100644 --- a/bridge/core/dart_isolate_context.cc +++ b/bridge/core/dart_isolate_context.cc @@ -3,14 +3,31 @@ */ #include "dart_isolate_context.h" -#include +#include #include "event_factory.h" +#include "logging.h" +#include "multiple_threading/looper.h" #include "mercury_isolate.h" #include "names_installer.h" namespace mercury { -thread_local std::set alive_wires; +thread_local std::unordered_set alive_wires; + +IsolateGroup::~IsolateGroup() { + for (auto isolate : isolates_) { + delete isolate; + } +} + +void IsolateGroup::AddNewIsolate(mercury::MercuryIsolate* new_isolate) { + assert(std::find(isolates_.begin(), isolates_.end(), new_isolate) == isolates_.end()); + isolates_.push_back(new_isolate); +} + +void IsolateGroup::RemoveIsolate(mercury::MercuryIsolate* isolate) { + isolates_.erase(std::find(isolates_.begin(), isolates_.end(), isolate)); +} void WatchDartWire(DartWireContext* wire) { alive_wires.emplace(wire); @@ -25,9 +42,10 @@ void DeleteDartWire(DartWireContext* wire) { delete wire; } -static void ClearUpWires() { +static void ClearUpWires(JSRuntime* runtime) { for (auto& wire : alive_wires) { - delete wire; + JS_FreeValueRT(runtime, wire->jsObject.QJSValue()); + wire->disposed = true; } alive_wires.clear(); } @@ -39,9 +57,9 @@ const std::unique_ptr& DartIsolateContext::EnsureData() const { return data_; } -thread_local JSRuntime* DartIsolateContext::runtime_{nullptr}; +thread_local JSRuntime* runtime_{nullptr}; +thread_local uint32_t running_dart_isolates = 0; thread_local bool is_name_installed_ = false; -thread_local int64_t running_isolates_ = 0; void InitializeBuiltInStrings(JSContext* ctx) { if (!is_name_installed_) { @@ -50,14 +68,10 @@ void InitializeBuiltInStrings(JSContext* ctx) { } } -DartIsolateContext::DartIsolateContext(const uint64_t* dart_methods, int32_t dart_methods_length) - : is_valid_(true), - running_thread_(std::this_thread::get_id()), - dart_method_ptr_(std::make_unique(dart_methods, dart_methods_length)) { - if (runtime_ == nullptr) { - runtime_ = JS_NewRuntime(); - } - running_isolates_++; +void DartIsolateContext::InitializeJSRuntime() { + if (runtime_ != nullptr) + return; + runtime_ = JS_NewRuntime(); // Avoid stack overflow when running in multiple threads. JS_UpdateStackTop(runtime_); // Bump up the built-in classId. To make sure the created classId are larger than JS_CLASS_CUSTOM_CLASS_INIT_COUNT. @@ -65,34 +79,173 @@ DartIsolateContext::DartIsolateContext(const uint64_t* dart_methods, int32_t dar JSClassID id{0}; JS_NewClassID(&id); } +} + +void DartIsolateContext::FinalizeJSRuntime() { + if (running_dart_isolates > 0) + return; + + // Prebuilt strings stored in JSRuntime. Only needs to dispose when runtime disposed. + names_installer::Dispose(); + HTMLElementFactory::Dispose(); + SVGElementFactory::Dispose(); + EventFactory::Dispose(); + ClearUpWires(runtime_); + JS_TurnOnGC(runtime_); + JS_FreeRuntime(runtime_); + runtime_ = nullptr; + is_name_installed_ = false; +} + +DartIsolateContext::DartIsolateContext(const uint64_t* dart_methods, int32_t dart_methods_length, bool profile_enabled) + : is_valid_(true), + running_thread_(std::this_thread::get_id()), + // prof: profiler_(std::make_unique(profile_enabled)), + dart_method_ptr_(std::make_unique(this, dart_methods, dart_methods_length)) { is_valid_ = true; + running_dart_isolates++; + InitializeJSRuntime(); } -DartIsolateContext::~DartIsolateContext() { - is_valid_ = false; - mercury_isolates_.clear(); - running_isolates_--; +JSRuntime* DartIsolateContext::runtime() { + return runtime_; +} - if (running_isolates_ == 0) { - // Prebuilt strings stored in JSRuntime. Only needs to dispose when runtime disposed. - names_installer::Dispose(); - EventFactory::Dispose(); - ClearUpWires(); +DartIsolateContext::~DartIsolateContext() {} + +void DartIsolateContext::Dispose(multi_threading::Callback callback) { + dispatcher_->Dispose([this, &callback]() { + is_valid_ = false; data_.reset(); - JS_FreeRuntime(runtime_); - runtime_ = nullptr; - is_name_installed_ = false; + isolates_in_isolate_thread_.clear(); + running_dart_isolates--; + FinalizeJSRuntime(); + callback(); + }); +} + +void DartIsolateContext::InitializeNewIsolateInJSThread(IsolateGroup* isolate_group, + DartIsolateContext* dart_isolate_context, + double isolate_context_id, + int32_t sync_buffer_size, + Dart_Handle dart_handle, + AllocateNewIsolateCallback result_callback) { + dart_isolate_context->profiler()->StartTrackInitialize(); + DartIsolateContext::InitializeJSRuntime(); + auto* isolate = new MercuryIsolate(dart_isolate_context, true, sync_buffer_size, isolate_context_id, nullptr); + + dart_isolate_context->profiler()->FinishTrackInitialize(); + + dart_isolate_context->dispatcher_->PostToDart(true, HandleNewIsolateResult, isolate_group, dart_handle, result_callback, + isolate); +} + +void DartIsolateContext::DisposeIsolateAndKilledJSThread(DartIsolateContext* dart_isolate_context, + MercuryIsolate* isolate, + int thread_group_id, + Dart_Handle dart_handle, + DisposeIsolateCallback result_callback) { + delete isolate; + dart_isolate_context->dispatcher_->PostToDart(true, HandleDisposeIsolateAndKillJSThread, dart_isolate_context, + thread_group_id, dart_handle, result_callback); +} + +void DartIsolateContext::DisposeIsolateInJSThread(DartIsolateContext* dart_isolate_context, + MercuryIsolate* isolate, + Dart_Handle dart_handle, + DisposeIsolateCallback result_callback) { + delete isolate; + dart_isolate_context->dispatcher_->PostToDart(true, HandleDisposeIsolate, dart_handle, result_callback); +} + +void* DartIsolateContext::AddNewIsolate(double thread_identity, + int32_t sync_buffer_size, + Dart_Handle dart_handle, + AllocateNewIsolateCallback result_callback) { + bool is_in_flutter_isolate_thread = thread_identity < 0; + assert(is_in_flutter_isolate_thread == false); + + int thread_group_id = static_cast(thread_identity); + + IsolateGroup* isolate_group; + if (!dispatcher_->IsThreadGroupExist(thread_group_id)) { + dispatcher_->AllocateNewJSThread(thread_group_id); + isolate_group = new IsolateGroup(); + dispatcher_->SetOpaqueForJSThread(thread_group_id, isolate_group, [](void* p) { + delete static_cast(p); + DartIsolateContext::FinalizeJSRuntime(); + }); + } else { + isolate_group = static_cast(dispatcher_->GetOpaque(thread_group_id)); } + + dispatcher_->PostToJs(true, thread_group_id, InitializeNewIsolateInJSThread, isolate_group, this, thread_identity, + sync_buffer_size, dart_handle, result_callback); + return nullptr; } -void DartIsolateContext::AddNewIsolate(std::unique_ptr&& new_isolate) { - mercury_isolates_.insert(std::move(new_isolate)); +void* DartIsolateContext::AddNewIsolateSync(double thread_identity) { + profiler()->StartTrackSteps("MercuryIsolate::Initialize"); + auto isolate = std::make_unique(this, false, 0, thread_identity, nullptr); + profiler()->FinishTrackSteps(); + + void* p = isolate.get(); + isolates_in_isolate_thread_.emplace(std::move(isolate)); + return p; +} + +void DartIsolateContext::HandleNewIsolateResult(IsolateGroup* isolate_group, + Dart_Handle persistent_handle, + AllocateNewIsolateCallback result_callback, + MercuryIsolate* new_isolate) { + isolate_group->AddNewIsolate(new_isolate); + Dart_Handle handle = Dart_HandleFromPersistent_DL(persistent_handle); + result_callback(handle, new_isolate); + Dart_DeletePersistentHandle_DL(persistent_handle); +} + +void DartIsolateContext::HandleDisposeIsolate(Dart_Handle persistent_handle, DisposeIsolateCallback result_callback) { + Dart_Handle handle = Dart_HandleFromPersistent_DL(persistent_handle); + result_callback(handle); + Dart_DeletePersistentHandle_DL(persistent_handle); +} + +void DartIsolateContext::HandleDisposeIsolateAndKillJSThread(DartIsolateContext* dart_isolate_context, + int thread_group_id, + Dart_Handle persistent_handle, + DisposeIsolateCallback result_callback) { + dart_isolate_context->dispatcher_->KillJSThreadSync(thread_group_id); + + Dart_Handle handle = Dart_HandleFromPersistent_DL(persistent_handle); + result_callback(handle); + Dart_DeletePersistentHandle_DL(persistent_handle); +} + +void DartIsolateContext::RemoveIsolate(double thread_identity, + MercuryIsolate* isolate, + Dart_Handle dart_handle, + DisposeIsolateCallback result_callback) { + bool is_in_flutter_isolate_thread = thread_identity < 0; + assert(is_in_flutter_isolate_thread == false); + + int thread_group_id = static_cast(isolate->contextId()); + auto isolate_group = static_cast(dispatcher_->GetOpaque(thread_group_id)); + + isolate_group->RemoveIsolate(isolate); + + if (isolate_group->Empty()) { + isolate->executingContext()->SetContextInValid(); + dispatcher_->PostToJs(true, thread_group_id, DisposeIsolateAndKilledJSThread, this, isolate, thread_group_id, dart_handle, + result_callback); + } else { + dispatcher_->PostToJs(true, thread_group_id, DisposeIsolateInJSThread, this, isolate, dart_handle, result_callback); + } } -void DartIsolateContext::RemoveIsolate(const MercuryIsolate* isolate) { - for (auto it = mercury_isolates_.begin(); it != mercury_isolates_.end(); ++it) { +void DartIsolateContext::RemoveIsolateSync(double thread_identity, MercuryIsolate* isolate) { + for (auto it = isolates_in_isolate_thread_.begin(); it != isolates_in_isolate_thread_.end(); ++it) { if (it->get() == isolate) { - mercury_isolates_.erase(it); + isolates_in_isolate_thread_.erase(it); break; } } diff --git a/bridge/core/dart_isolate_context.h b/bridge/core/dart_isolate_context.h index 857156e..71bb308 100644 --- a/bridge/core/dart_isolate_context.h +++ b/bridge/core/dart_isolate_context.h @@ -9,14 +9,32 @@ #include "bindings/qjs/script_value.h" #include "dart_context_data.h" #include "dart_methods.h" +#include "multiple_threading/dispatcher.h" namespace mercury { class MercuryIsolate; class DartIsolateContext; +class IsolateGroup { + public: + ~IsolateGroup(); + void AddNewIsolate(MercuryIsolate* new_isolate); + void RemoveIsolate(MercuryIsolate* isolate); + bool Empty() { return isolates_.empty(); } + + std::vector* isolates() { return &isolates_; }; + + private: + std::vector isolates_; +}; + struct DartWireContext { ScriptValue jsObject; + bool is_dedicated; + double context_id; + bool disposed; + multi_threading::Dispatcher* dispatcher; }; void InitializeBuiltInStrings(JSContext* ctx); @@ -28,28 +46,64 @@ void DeleteDartWire(DartWireContext* wire); // DartIsolateContext has a 1:1 correspondence with a dart isolates. class DartIsolateContext { public: - explicit DartIsolateContext(const uint64_t* dart_methods, int32_t dart_methods_length); + explicit DartIsolateContext(const uint64_t* dart_methods, int32_t dart_methods_length, bool profile_enabled); - FORCE_INLINE JSRuntime* runtime() { return runtime_; } - FORCE_INLINE bool valid() { return is_valid_ && std::this_thread::get_id() == running_thread_; } - FORCE_INLINE const std::unique_ptr& dartMethodPtr() const { - assert(std::this_thread::get_id() == running_thread_); - return dart_method_ptr_; + JSRuntime* runtime(); + FORCE_INLINE bool valid() { return is_valid_; } + FORCE_INLINE DartMethodPointer* dartMethodPtr() const { return dart_method_ptr_.get(); } + FORCE_INLINE const std::unique_ptr& dispatcher() const { return dispatcher_; } + FORCE_INLINE void SetDispatcher(std::unique_ptr&& dispatcher) { + dispatcher_ = std::move(dispatcher); } + // prof: FORCE_INLINE WebFProfiler* profiler() const { return profiler_.get(); }; const std::unique_ptr& EnsureData() const; - void AddNewIsolate(std::unique_ptr&& new_isolate); - void RemoveIsolate(const MercuryIsolate* isolate); + void* AddNewIsolate(double thread_identity, + int32_t sync_buffer_size, + Dart_Handle dart_handle, + AllocateNewIsolateCallback result_callback); + void* AddNewIsolateSync(double thread_identity); + void RemoveIsolate(double thread_identity, MercuryIsolate* isolate, Dart_Handle dart_handle, DisposeIsolateCallback result_callback); + void RemoveIsolateSync(double thread_identity, MercuryIsolate* isolate); ~DartIsolateContext(); + void Dispose(multi_threading::Callback callback); private: + static void InitializeJSRuntime(); + static void FinalizeJSRuntime(); + static void InitializeNewIsolateInJSThread(IsolateGroup* isolate_group, + DartIsolateContext* dart_isolate_context, + double isolate_context_id, + int32_t sync_buffer_size, + Dart_Handle dart_handle, + AllocateNewIsolateCallback result_callback); + static void DisposeIsolateAndKilledJSThread(DartIsolateContext* dart_isolate_context, + MercuryIsolate* isolate, + int thread_group_id, + Dart_Handle dart_handle, + DisposeIsolateCallback result_callback); + static void DisposeIsolateInJSThread(DartIsolateContext* dart_isolate_context, + MercuryIsolate* isolate, + Dart_Handle dart_handle, + DisposeIsolateCallback result_callback); + static void HandleNewIsolateResult(IsolateGroup* isolate_group, + Dart_Handle persistent_handle, + AllocateNewIsolateCallback result_callback, + MercuryIsolate* new_isolate); + static void HandleDisposeIsolate(Dart_Handle persistent_handle, DisposeIsolateCallback result_callback); + static void HandleDisposeIsolateAndKillJSThread(DartIsolateContext* dart_isolate_context, + int thread_group_id, + Dart_Handle persistent_handle, + DisposeIsolateCallback result_callback); + + // prof: std::unique_ptr profiler_; int is_valid_{false}; - std::set> mercury_isolates_; std::thread::id running_thread_; mutable std::unique_ptr data_; - static thread_local JSRuntime* runtime_; + std::unordered_set> isolates_in_ui_thread_; + std::unique_ptr dispatcher_ = nullptr; // Dart methods ptr should keep alive when ExecutingContext is disposing. const std::unique_ptr dart_method_ptr_ = nullptr; }; diff --git a/bridge/core/dart_methods.cc b/bridge/core/dart_methods.cc index a90eb19..233ed03 100644 --- a/bridge/core/dart_methods.cc +++ b/bridge/core/dart_methods.cc @@ -4,23 +4,203 @@ */ #include "dart_methods.h" +#include #include +#include "dart_isolate_context.h" +#include "foundation/native_type.h" + +using namespace mercury; namespace mercury { -mercury::DartMethodPointer::DartMethodPointer(const uint64_t* dart_methods, int32_t dart_methods_length) { - size_t i = 0; - invokeModule = reinterpret_cast(dart_methods[i++]); - reloadApp = reinterpret_cast(dart_methods[i++]); - setTimeout = reinterpret_cast(dart_methods[i++]); - setInterval = reinterpret_cast(dart_methods[i++]); - clearTimeout = reinterpret_cast(dart_methods[i++]); - flushIsolateCommand = reinterpret_cast(dart_methods[i++]); - create_binding_object = reinterpret_cast(dart_methods[i++]); +int32_t start_timer_id = 1; - onJsError = reinterpret_cast(dart_methods[i++]); - onJsLog = reinterpret_cast(dart_methods[i++]); +DartMethodPointer::DartMethodPointer(DartIsolateContext* dart_isolate_context, + const uint64_t* dart_methods, + int32_t dart_methods_length) + : dart_isolate_context_(dart_isolate_context) { + size_t i = 0; + invoke_module_ = reinterpret_cast(dart_methods[i++]); + reload_app_ = reinterpret_cast(dart_methods[i++]); + set_timeout_ = reinterpret_cast(dart_methods[i++]); + set_interval_ = reinterpret_cast(dart_methods[i++]); + clear_timeout_ = reinterpret_cast(dart_methods[i++]); + to_blob_ = reinterpret_cast(dart_methods[i++]); + flush_isolate_command_ = reinterpret_cast(dart_methods[i++]); + create_binding_object_ = reinterpret_cast(dart_methods[i++]); + on_js_error_ = reinterpret_cast(dart_methods[i++]); + on_js_log_ = reinterpret_cast(dart_methods[i++]); assert_m(i == dart_methods_length, "Dart native methods count is not equal with C++ side method registrations."); } + +NativeValue* DartMethodPointer::invokeModule(bool is_dedicated, + void* callback_context, + double context_id, + int64_t profile_link_id, + SharedNativeString* moduleName, + SharedNativeString* method, + NativeValue* params, + AsyncModuleCallback callback) { +#if ENABLE_LOG + MERCURY_LOG(INFO) << "[Dispatcher] DartMethodPointer::invokeModule callSync START"; +#endif + NativeValue* result = dart_isolate_context_->dispatcher()->PostToDartSync( + is_dedicated, context_id, + [&](bool cancel, void* callback_context, double context_id, int64_t profile_link_id, + SharedNativeString* moduleName, SharedNativeString* method, NativeValue* params, + AsyncModuleCallback callback) -> mercury::NativeValue* { + if (cancel) + return nullptr; + return invoke_module_(callback_context, context_id, profile_link_id, moduleName, method, params, callback); + }, + callback_context, context_id, profile_link_id, moduleName, method, params, callback); + +#if ENABLE_LOG + MERCURY_LOG(INFO) << "[Dispatcher] DartMethodPointer::invokeModule callSync END"; +#endif + + return result; +} + +void DartMethodPointer::reloadApp(bool is_dedicated, double context_id) { +#if ENABLE_LOG + MERCURY_LOG(INFO) << "[Dispatcher] DartMethodPointer::reloadApp Call"; +#endif + + dart_isolate_context_->dispatcher()->PostToDart(is_dedicated, reload_app_, context_id); +} + +int32_t DartMethodPointer::setTimeout(bool is_dedicated, + void* callback_context, + double context_id, + AsyncCallback callback, + int32_t timeout) { +#if ENABLE_LOG + MERCURY_LOG(INFO) << "[Dispatcher] DartMethodPointer::setTimeout callSync START"; +#endif + + int32_t new_timer_id = start_timer_id++; + + dart_isolate_context_->dispatcher()->PostToDart(is_dedicated, set_timeout_, new_timer_id, callback_context, + context_id, callback, timeout); + +#if ENABLE_LOG + MERCURY_LOG(INFO) << "[Dispatcher] DartMethodPointer::setTimeout callSync END"; +#endif + + return new_timer_id; +} + +int32_t DartMethodPointer::setInterval(bool is_dedicated, + void* callback_context, + double context_id, + AsyncCallback callback, + int32_t timeout) { +#if ENABLE_LOG + MERCURY_LOG(INFO) << "[Dispatcher] DartMethodPointer::setInterval callSync START"; +#endif + + int32_t new_timer_id = start_timer_id++; + + dart_isolate_context_->dispatcher()->PostToDart(is_dedicated, set_interval_, new_timer_id, callback_context, + context_id, callback, timeout); +#if ENABLE_LOG + MERCURY_LOG(INFO) << "[Dispatcher] DartMethodPointer::setInterval callSync END"; +#endif + return new_timer_id; +} + +void DartMethodPointer::clearTimeout(bool is_dedicated, double context_id, int32_t timer_id) { +#if ENABLE_LOG + MERCURY_LOG(VERBOSE) << "[CPP] ClearTimeoutWrapper call" << std::endl; +#endif + + dart_isolate_context_->dispatcher()->PostToDart(is_dedicated, clear_timeout_, context_id, timer_id); +} + +void DartMethodPointer::flushIsolateCommand(bool is_dedicated, double context_id, void* native_binding_object) { +#if ENABLE_LOG + MERCURY_LOG(INFO) << "[Dispatcher] DartMethodPointer::flushIsolateCommand SYNC call START"; +#endif + + dart_isolate_context_->dispatcher()->PostToDartSync( + is_dedicated, context_id, + [&](bool cancel, double context_id, void* native_binding_object) -> void { + if (cancel) + return; + + flush_isolate_command_(context_id, native_binding_object); + }, + context_id, native_binding_object); + +#if ENABLE_LOG + MERCURY_LOG(INFO) << "[Dispatcher] DartMethodPointer::flushIsolateCommand SYNC call END"; +#endif +} + +void DartMethodPointer::createBindingObject(bool is_dedicated, + double context_id, + void* native_binding_object, + int32_t type, + void* args, + int32_t argc) { +#if ENABLE_LOG + MERCURY_LOG(INFO) << "[Dispatcher] DartMethodPointer::createBindingObject SYNC call START"; +#endif + + dart_isolate_context_->dispatcher()->PostToDartSync( + is_dedicated, context_id, + [&](bool cancel, double context_id, void* native_binding_object, int32_t type, void* args, int32_t argc) -> void { + if (cancel) + return; + create_binding_object_(context_id, native_binding_object, type, args, argc); + }, + context_id, native_binding_object, type, args, argc); + +#if ENABLE_LOG + MERCURY_LOG(INFO) << "[Dispatcher] DartMethodPointer::createBindingObject SYNC call END"; +#endif +} + +void DartMethodPointer::onJSError(bool is_dedicated, double context_id, const char* error) { + dart_isolate_context_->dispatcher()->PostToDart(is_dedicated, on_js_error_, context_id, error); +} + +void DartMethodPointer::onJSLog(bool is_dedicated, double context_id, int32_t level, const char* log) { + if (on_js_log_ == nullptr) + return; + int log_length = strlen(log) + 1; + char* log_str = (char*)dart_malloc(sizeof(char) * log_length); + snprintf(log_str, log_length, "%s", log); + + dart_isolate_context_->dispatcher()->PostToDart(is_dedicated, on_js_log_, context_id, level, log_str); +} + +const char* DartMethodPointer::environment(bool is_dedicated, double context_id) { +#if ENABLE_LOG + MERCURY_LOG(INFO) << "[Dispatcher] DartMethodPointer::environment callSync START"; +#endif + const char* result = + dart_isolate_context_->dispatcher()->PostToDartSync(is_dedicated, context_id, [&](bool cancel) -> const char* { + if (cancel) + return nullptr; + return environment_(); + }); + +#if ENABLE_LOG + MERCURY_LOG(INFO) << "[Dispatcher] DartMethodPointer::environment callSync END"; +#endif + + return result; +} + +void DartMethodPointer::SetOnJSError(mercury::OnJSError func) { + on_js_error_ = func; +} + +void DartMethodPointer::SetEnvironment(Environment func) { + environment_ = func; +} + } // namespace mercury diff --git a/bridge/core/dart_methods.h b/bridge/core/dart_methods.h index 8c92456..d9a31a4 100644 --- a/bridge/core/dart_methods.h +++ b/bridge/core/dart_methods.h @@ -3,8 +3,8 @@ * Copyright (C) 2022-present The WebF authors. All rights reserved. */ -#ifndef MERCURY_DART_METHODS_H_ -#define MERCURY_DART_METHODS_H_ +#ifndef WEBF_DART_METHODS_H_ +#define WEBF_DART_METHODS_H_ /// Functions implements at dart side, including timer, Rendering and module API. /// Communicate via Dart FFI. @@ -13,63 +13,63 @@ #include #include "foundation/native_string.h" #include "foundation/native_value.h" +#include "include/dart_api.h" #if defined(_WIN32) -#define MERCURY_EXPORT_C extern "C" __declspec(dllexport) -#define MERCURY_EXPORT __declspec(dllexport) +#define WEBF_EXPORT_C extern "C" __declspec(dllexport) +#define WEBF_EXPORT __declspec(dllexport) #else -#define MERCURY_EXPORT_C extern "C" __attribute__((visibility("default"))) __attribute__((used)) -#define MERCURY_EXPORT __attribute__((__visibility__("default"))) +#define WEBF_EXPORT_C extern "C" __attribute__((visibility("default"))) __attribute__((used)) +#define WEBF_EXPORT __attribute__((__visibility__("default"))) #endif namespace mercury { -using AsyncCallback = void (*)(void* callback_context, int32_t context_id, const char* errmsg); -using AsyncRAFCallback = void (*)(void* callback_context, int32_t context_id, double result, const char* errmsg); +using InvokeModuleResultCallback = void (*)(Dart_PersistentHandle persistent_handle, NativeValue* result); +using AsyncCallback = void (*)(void* callback_context, double context_id, char* errmsg); +using AsyncRAFCallback = void (*)(void* callback_context, double context_id, double result, char* errmsg); using AsyncModuleCallback = NativeValue* (*)(void* callback_context, - int32_t context_id, + double context_id, const char* errmsg, - NativeValue* value); + NativeValue* value, + Dart_PersistentHandle persistent_handle, + InvokeModuleResultCallback result_callback); + using AsyncBlobCallback = - void (*)(void* callback_context, int32_t context_id, const char* error, uint8_t* bytes, int32_t length); + void (*)(void* callback_context, double context_id, char* error, uint8_t* bytes, int32_t length); typedef NativeValue* (*InvokeModule)(void* callback_context, - int32_t context_id, + double context_id, + int64_t profile_link_id, SharedNativeString* moduleName, SharedNativeString* method, NativeValue* params, AsyncModuleCallback callback); -typedef void (*RequestBatchUpdate)(int32_t context_id); -typedef void (*ReloadApp)(int32_t context_id); -typedef int32_t (*SetTimeout)(void* callback_context, int32_t context_id, AsyncCallback callback, int32_t timeout); -typedef int32_t (*SetInterval)(void* callback_context, int32_t context_id, AsyncCallback callback, int32_t timeout); -typedef int32_t (*RequestAnimationFrame)(void* callback_context, int32_t context_id, AsyncRAFCallback callback); -typedef void (*ClearTimeout)(int32_t context_id, int32_t timerId); -typedef void (*CancelAnimationFrame)(int32_t context_id, int32_t id); +typedef void (*RequestBatchUpdate)(double context_id); +typedef void (*ReloadApp)(double context_id); +typedef void (*SetTimeout)(int32_t new_timer_id, + void* callback_context, + double context_id, + AsyncCallback callback, + int32_t timeout); +typedef void (*SetInterval)(int32_t new_timer_id, + void* callback_context, + double context_id, + AsyncCallback callback, + int32_t timeout); +typedef void (*RequestAnimationFrame)(int32_t new_frame_id, + void* callback_context, + double context_id, + AsyncRAFCallback callback); +typedef void (*ClearTimeout)(double context_id, int32_t timerId); +typedef void (*CancelAnimationFrame)(double context_id, int32_t id); typedef void (*ToBlob)(void* callback_context, - int32_t context_id, + double context_id, AsyncBlobCallback blobCallback, void* element_ptr, double devicePixelRatio); -typedef void (*OnJSError)(int32_t context_id, const char*); -typedef void (*OnJSLog)(int32_t context_id, int32_t level, const char*); -typedef void (*FlushIsolateCommand)(int32_t context_id); -typedef void ( - *CreateBindingObject)(int32_t context_id, void* native_binding_object, int32_t type, void* args, int32_t argc); - -using MatchImageSnapshotCallback = void (*)(void* callback_context, int32_t context_id, int8_t, const char* errmsg); -using MatchImageSnapshot = void (*)(void* callback_context, - int32_t context_id, - uint8_t* bytes, - int32_t length, - SharedNativeString* name, - MatchImageSnapshotCallback callback); -using MatchImageSnapshotBytes = void (*)(void* callback_context, - int32_t context_id, - uint8_t* image_a_bytes, - int32_t image_a_size, - uint8_t* image_b_bytes, - int32_t image_b_size, - MatchImageSnapshotCallback callback); +typedef void (*OnJSError)(double context_id, const char*); +typedef void (*OnJSLog)(double context_id, int32_t level, const char*); +typedef void (*FlushIsolateCommand)(double context_id, void* native_binding_object); using Environment = const char* (*)(); #if ENABLE_PROFILE @@ -80,36 +80,79 @@ struct NativePerformanceEntryList { typedef NativePerformanceEntryList* (*GetPerformanceEntries)(int32_t); #endif -struct MousePointer { - int32_t context_id; - double x; - double y; - double change; - int32_t signal_kind; - double delta_x; - double delta_y; +enum FlushIsolateCommandReason : uint32_t { + kStandard = 1, + kDependentsOnObject = 1 << 2, + kDependentsOnLayout = 1 << 3, // unused in mercury + kDependentsAll = 1 << 4 }; -using SimulatePointer = - void (*)(void* ptr, MousePointer*, int32_t length, int32_t pointer, AsyncCallback async_callback); -using SimulateInputText = void (*)(SharedNativeString* nativeString); -struct DartMethodPointer { +inline bool isIsolateCommandReasonDependsOnObject(uint32_t reason) { + return (reason & kDependentsOnObject) != 0; +} + +inline bool isIsolateCommandReasonDependsOnLayout(uint32_t reason) { + return (reason & kDependentsOnLayout) != 0; +} + +inline bool isIsolateCommandReasonDependsOnAll(uint32_t reason) { + return (reason & kDependentsAll) != 0; +} + +class DartIsolateContext; + +class DartMethodPointer { DartMethodPointer() = delete; - explicit DartMethodPointer(const uint64_t* dart_methods, int32_t dartMethodsLength); - - InvokeModule invokeModule{nullptr}; - RequestBatchUpdate requestBatchUpdate{nullptr}; - ReloadApp reloadApp{nullptr}; - SetTimeout setTimeout{nullptr}; - SetInterval setInterval{nullptr}; - ClearTimeout clearTimeout{nullptr}; - OnJSError onJsError{nullptr}; - OnJSLog onJsLog{nullptr}; - FlushIsolateCommand flushIsolateCommand{nullptr}; - CreateBindingObject create_binding_object{nullptr}; -#if ENABLE_PROFILE - GetPerformanceEntries getPerformanceEntries{nullptr}; -#endif + + public: + explicit DartMethodPointer(DartIsolateContext* dart_isolate_context, + const uint64_t* dart_methods, + int32_t dartMethodsLength); + NativeValue* invokeModule(bool is_dedicated, + void* callback_context, + double context_id, + int64_t profile_link_id, + SharedNativeString* moduleName, + SharedNativeString* method, + NativeValue* params, + AsyncModuleCallback callback); + void reloadApp(bool is_dedicated, double context_id); + int32_t setTimeout(bool is_dedicated, + void* callback_context, + double context_id, + AsyncCallback callback, + int32_t timeout); + int32_t setInterval(bool is_dedicated, + void* callback_context, + double context_id, + AsyncCallback callback, + int32_t timeout); + void clearTimeout(bool is_dedicated, double context_id, int32_t timerId); + void flushIsolateCommand(bool is_dedicated, double context_id, void* native_binding_object); + void createBindingObject(bool is_dedicated, + double context_id, + void* native_binding_object, + int32_t type, + void* args, + int32_t argc); + void onJSError(bool is_dedicated, double context_id, const char*); + void onJSLog(bool is_dedicated, double context_id, int32_t level, const char*); + + const char* environment(bool is_dedicated, double context_id); + void SetOnJSError(OnJSError func); + void SetEnvironment(Environment func); + + private: + DartIsolateContext* dart_isolate_context_{nullptr}; + InvokeModule invoke_module_{nullptr}; + ReloadApp reload_app_{nullptr}; + SetTimeout set_timeout_{nullptr}; + SetInterval set_interval_{nullptr}; + ClearTimeout clear_timeout_{nullptr}; + FlushIsolateCommand flush_ui_command_{nullptr}; + OnJSError on_js_error_{nullptr}; + OnJSLog on_js_log_{nullptr}; + Environment environment_{nullptr}; }; } // namespace mercury diff --git a/bridge/core/event/event_target.cc b/bridge/core/event/event_target.cc index 18f5023..7c41ecd 100644 --- a/bridge/core/event/event_target.cc +++ b/bridge/core/event/event_target.cc @@ -63,15 +63,15 @@ EventTarget* EventTarget::Create(ExecutingContext* context, EventTarget::~EventTarget() { #if UNIT_TEST // Callback to unit test specs before eventTarget finalized. - if (TEST_getEnv(GetExecutingContext()->uniqueId())->on_event_target_disposed != nullptr) { - TEST_getEnv(GetExecutingContext()->uniqueId())->on_event_target_disposed(this); + if (TEST_getEnv(executingContext()->uniqueId())->on_event_target_disposed != nullptr) { + TEST_getEnv(executingContext()->uniqueId())->on_event_target_disposed(this); } #endif } EventTarget::EventTarget(ExecutingContext* context, const AtomicString& constructor_name) : className_(constructor_name), BindingObject(context->ctx()) { - GetExecutingContext()->isolateCommandBuffer()->addCommand( + executingContext()->isolateCommandBuffer()->AddCommand( IsolateCommand::kCreateEventTarget, std::move(constructor_name.ToNativeString(ctx())), bindingObject(), nullptr); } @@ -90,6 +90,7 @@ bool EventTarget::addEventListener(const AtomicString& event_type, const std::shared_ptr& event_listener, const std::shared_ptr& options, ExceptionState& exception_state) { + if (event_listener == nullptr) return false; std::shared_ptr event_listener_options; if (options == nullptr) { event_listener_options = AddEventListenerOptions::Create(); @@ -159,7 +160,7 @@ bool EventTarget::dispatchEvent(Event* event, ExceptionState& exception_state) { return false; } - if (!GetExecutingContext()) + if (!executingContext()) return false; event->SetTrusted(false); @@ -302,7 +303,7 @@ bool EventTarget::AddEventListenerInternal(const AtomicString& event_type, listener_options->passive = options->passive(); } - GetExecutingContext()->isolateCommandBuffer()->addCommand( + executingContext()->isolateCommandBuffer()->AddCommand( IsolateCommand::kAddEvent, std::move(event_type.ToNativeString(ctx())), bindingObject(), listener_options); } @@ -350,7 +351,7 @@ bool EventTarget::RemoveEventListenerInternal(const AtomicString& event_type, if (listener_count == 0) { bool has_capture = options->hasCapture() && options->capture(); - GetExecutingContext()->isolateCommandBuffer()->addCommand(IsolateCommand::kRemoveEvent, + executingContext()->isolateCommandBuffer()->AddCommand(IsolateCommand::kRemoveEvent, std::move(event_type.ToNativeString(ctx())), bindingObject(), has_capture ? (void*)0x01 : nullptr); } @@ -373,7 +374,7 @@ NativeValue EventTarget::HandleCallFromDartSide(const AtomicString& method, Dart_Handle dart_object) { if (!isContextValid(contextId())) return Native_NewNull(); - MemberMutationScope mutation_scope{GetExecutingContext()}; + MemberMutationScope mutation_scope{executingContext()}; if (method == binding_call_methods::kdispatchEvent) { return HandleDispatchEventFromDart(argc, argv, dart_object); @@ -385,6 +386,8 @@ NativeValue EventTarget::HandleCallFromDartSide(const AtomicString& method, } NativeValue EventTarget::HandleDispatchEventFromDart(int32_t argc, const NativeValue* argv, Dart_Handle dart_object) { + // prof: executingContext()->dartIsolateContext()->profiler()->StartTrackSteps("EventTarget::HandleDispatchEventFromDart"); + assert(argc >= 2); NativeValue native_event_type = argv[0]; NativeValue native_is_capture = argv[2]; @@ -393,9 +396,15 @@ NativeValue EventTarget::HandleDispatchEventFromDart(int32_t argc, const NativeV NativeValueConverter::FromNativeValue(ctx(), std::move(native_event_type)); RawEvent* raw_event = NativeValueConverter>::FromNativeValue(argv[1]); - Event* event = EventFactory::Create(GetExecutingContext(), event_type, raw_event); + Event* event = EventFactory::Create(executingContext(), event_type, raw_event); assert(event->target() != nullptr); assert(event->currentTarget() != nullptr); + + auto* global = DynamicTo(event->target()); + if (global != nullptr && (event->type() == event_type_names::kload || event->type() == event_type_names::kgcopen)) { + global->OnLoadEventFired(); + } + ExceptionState exception_state; event->SetTrusted(false); event->SetEventPhase(Event::kAtTarget); @@ -404,24 +413,43 @@ NativeValue EventTarget::HandleDispatchEventFromDart(int32_t argc, const NativeV auto* wire = new DartWireContext(); wire->jsObject = event->ToValue(); + wire->is_dedicated = executingContext()->isDedicated(); + wire->context_id = executingContext()->contextId(); + wire->dispatcher = GetDispatcher(); + wire->disposed = false; auto dart_object_finalize_callback = [](void* isolate_callback_data, void* peer) { auto* wire = (DartWireContext*)(peer); - if (IsDartWireAlive(wire)) { - DeleteDartWire(wire); - } + + if (wire->disposed) + return; + + wire->dispatcher->PostToJs( + wire->is_dedicated, wire->context_id, + [](DartWireContext* wire) -> void { + if (IsDartWireAlive(wire)) { + DeleteDartWire(wire); + } + }, + wire); }; WatchDartWire(wire); - Dart_NewFinalizableHandle_DL(dart_object, reinterpret_cast(wire), sizeof(DartWireContext), - dart_object_finalize_callback); + GetDispatcher()->PostToDart( + executingContext()->isDedicated(), + [](Dart_Handle object, void* peer, intptr_t external_allocation_size, Dart_HandleFinalizer callback) { + Dart_NewFinalizableHandle_DL(object, peer, external_allocation_size, callback); + }, + dart_object, reinterpret_cast(wire), sizeof(DartWireContext), dart_object_finalize_callback); if (exception_state.HasException()) { JSValue error = JS_GetException(ctx()); - GetExecutingContext()->ReportError(error); + executingContext()->ReportError(error); JS_FreeValue(ctx(), error); } + // prof: executingContext()->dartIsolateContext()->profiler()->FinishTrackSteps(); + auto* result = new EventDispatchResult{.canceled = dispatch_result == DispatchEventResult::kCanceledByEventHandler, .propagationStopped = event->propagationStopped()}; return NativeValueConverter>::ToNativeValue(result); @@ -434,7 +462,7 @@ RegisteredEventListener* EventTarget::GetAttributeRegisteredEventListener(const for (auto& event_listener : *listener_vector) { auto listener = event_listener.Callback(); - if (GetExecutingContext() && listener->IsEventHandler()) + if (executingContext() && listener->IsEventHandler()) return &event_listener; } return nullptr; @@ -449,7 +477,7 @@ bool EventTarget::FireEventListeners(Event& event, // dispatch. Conveniently, all new event listeners will be added after or at // index |size|, so iterating up to (but not including) |size| naturally // excludes new event listeners. - ExecutingContext* context = GetExecutingContext(); + ExecutingContext* context = executingContext(); if (!context) return false; @@ -505,7 +533,7 @@ void EventTargetWithInlineData::Trace(GCVisitor* visitor) const { } bool EventTarget::NamedPropertyQuery(const AtomicString& key, ExceptionState& exception_state) { - return GetExecutingContext()->dartIsolateContext()->EnsureData()->HasWidgetElementShape(key); + return executingContext()->dartIsolateContext()->EnsureData()->HasWidgetElementShape(key); } void EventTarget::NamedPropertyEnumerator(std::vector& names, ExceptionState& exception_state) { @@ -524,15 +552,15 @@ ScriptValue EventTarget::item(const AtomicString& key, ExceptionState& exception return unimplemented_properties_[key]; } - if (!GetExecutingContext()->dartIsolateContext()->EnsureData()->HasWidgetElementShape(className())) { - GetExecutingContext()->FlushIsolateCommand(); + if (!executingContext()->dartIsolateContext()->EnsureData()->HasWidgetElementShape(className())) { + executingContext()->FlushIsolateCommand(); } if (key == built_in_string::kSymbol_toStringTag) { return ScriptValue(ctx(), className().ToNativeString(ctx()).release()); } - auto shape = GetExecutingContext()->dartIsolateContext()->EnsureData()->GetWidgetElementShape(className()); + auto shape = executingContext()->dartIsolateContext()->EnsureData()->GetWidgetElementShape(className()); if (shape != nullptr) { if (shape->built_in_properties_.find(key) != shape->built_in_properties_.end()) { return ScriptValue(ctx(), GetBindingProperty(key, exception_state)); @@ -563,11 +591,11 @@ ScriptValue EventTarget::item(const AtomicString& key, ExceptionState& exception } bool EventTarget::SetItem(const AtomicString& key, const ScriptValue& value, ExceptionState& exception_state) { - if (!GetExecutingContext()->dartIsolateContext()->EnsureData()->HasWidgetElementShape(className())) { - GetExecutingContext()->FlushIsolateCommand(); + if (!executingContext()->dartIsolateContext()->EnsureData()->HasWidgetElementShape(className())) { + executingContext()->FlushIsolateCommand(); } - auto shape = GetExecutingContext()->dartIsolateContext()->EnsureData()->GetWidgetElementShape(className()); + auto shape = executingContext()->dartIsolateContext()->EnsureData()->GetWidgetElementShape(className()); // This property is defined in the Dart side if (shape != nullptr && shape->built_in_properties_.count(key) > 0) { NativeValue result = SetBindingProperty(key, value.ToNative(ctx(), exception_state), exception_state); @@ -576,7 +604,7 @@ bool EventTarget::SetItem(const AtomicString& key, const ScriptValue& value, Exc // This property is defined in WidgetElement.prototype, should return false to let it handled in the prototype // methods. - JSValue prototypeObject = GetExecutingContext()->contextData()->prototypeForType(GetWrapperTypeInfo()); + JSValue prototypeObject = executingContext()->contextData()->prototypeForType(GetWrapperTypeInfo()); if (JS_HasProperty(ctx(), prototypeObject, key.Impl())) { return false; } @@ -593,7 +621,7 @@ bool EventTarget::DeleteItem(const AtomicString& key, ExceptionState& exception_ NativeValue EventTarget::HandleSyncPropertiesAndMethodsFromDart(int32_t argc, const NativeValue* argv) { assert(argc == 3); AtomicString key = className(); - assert(!GetExecutingContext()->dartIsolateContext()->EnsureData()->HasWidgetElementShape(key)); + assert(!executingContext()->dartIsolateContext()->EnsureData()->HasWidgetElementShape(key)); auto shape = std::make_shared(); @@ -613,7 +641,7 @@ NativeValue EventTarget::HandleSyncPropertiesAndMethodsFromDart(int32_t argc, co shape->built_in_async_methods_.emplace(method); } - GetExecutingContext()->dartIsolateContext()->EnsureData()->SetWidgetElementShape(key, shape); + executingContext()->dartIsolateContext()->EnsureData()->SetWidgetElementShape(key, shape); return Native_NewBool(true); } diff --git a/bridge/core/event/event_test.cc b/bridge/core/event/event_test.cc index 61647c7..3d99e8c 100644 --- a/bridge/core/event/event_test.cc +++ b/bridge/core/event/event_test.cc @@ -15,7 +15,7 @@ TEST(MouseEvent, init) { EXPECT_STREQ(message.c_str(), "10"); logCalled = true; }; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); + auto env = TEST_init([](double contextId, const char* errmsg) { errorCalled = true; }); auto context = env->page()->getContext(); const char* code = "let mouseEvent = new MouseEvent('click', {clientX: 10, clientY: 20}); console.log(mouseEvent.clientX);"; diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index 6c81fc5..d2cec52 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -14,48 +14,79 @@ #include "event_type_names.h" #include "polyfill.h" #include "qjs_global.h" - +#include "script_forbidden_scope.h" +// prof: #include "timing/performance.h" namespace mercury { static std::atomic context_unique_id{0}; #define MAX_JS_CONTEXT 8192 -bool valid_contexts[MAX_JS_CONTEXT]; +thread_local std::unordered_map valid_contexts; std::atomic running_context_list{0}; ExecutingContext::ExecutingContext(DartIsolateContext* dart_isolate_context, - int32_t contextId, + bool is_dedicated, + size_t sync_buffer_size, + double context_id, JSExceptionHandler handler, void* owner) : dart_isolate_context_(dart_isolate_context), - context_id_(contextId), + context_id_(context_id), handler_(std::move(handler)), owner_(owner), + is_dedicated_(is_dedicated), unique_id_(context_unique_id++), is_context_valid_(true) { + if (is_dedicated) { + // Set up the sync command size for dedicated thread mode. + // Bigger size introduce more isolate consistence and lower size led to more high performance by the reason of + // concurrency. + isolate_command_buffer_.ConfigureSyncCommandBufferSize(sync_buffer_size); + } + // @FIXME: maybe contextId will larger than MAX_JS_CONTEXT - assert_m(valid_contexts[contextId] != true, "Conflict context found!"); - valid_contexts[contextId] = true; - if (contextId > running_context_list) - running_context_list = contextId; + assert_m(valid_contexts[context_id] != true, "Conflict context found!"); + valid_contexts[context_id] = true; + if (context_id > running_context_list) + running_context_list = context_id; time_origin_ = std::chrono::system_clock::now(); JSContext* ctx = script_state_.ctx(); global_object_ = JS_GetGlobalObject(script_state_.ctx()); + // Turn off quickjs GC to avoid performance issue at loading status. + // When the `load` event fired in global, the GC will turn back on. + JS_TurnOffGC(script_state_.runtime()); JS_SetContextOpaque(ctx, this); JS_SetHostPromiseRejectionTracker(script_state_.runtime(), promiseRejectTracker, nullptr); + // prof: dart_isolate_context->profiler()->StartTrackSteps("ExecutingContext::InstallBindings"); + // Register all built-in native bindings. InstallBindings(this); + // prof: dart_isolate_context->profiler()->FinishTrackSteps(); + // prof: dart_isolate_context->profiler()->StartTrackSteps("ExecutingContext::InstallGlobal"); + // Binding global object and global. InstallGlobal(); + // prof: dart_isolate_context->profiler()->FinishTrackSteps(); + // prof: dart_isolate_context->profiler()->StartTrackSteps("ExecutingContext::InstallPerformance"); + + // Install performance + // prof: InstallPerformance(); + + // prof: dart_isolate_context->profiler()->FinishTrackSteps(); + // prof: dart_isolate_context->profiler()->StartTrackSteps("ExecutingContext::initWebFPolyFill"); + initMercuryPolyFill(this); + // prof: dart_isolate_context->profiler()->FinishTrackSteps(); + // prof: dart_isolate_context->profiler()->StartTrackSteps("ExecutingContext::InitializePlugin"); + for (auto& p : plugin_byte_code) { EvaluateByteCode(p.second.bytes, p.second.length); } @@ -63,6 +94,10 @@ ExecutingContext::ExecutingContext(DartIsolateContext* dart_isolate_context, for (auto& p : plugin_string_code) { EvaluateJavaScript(p.second.c_str(), p.second.size(), p.first.c_str(), 0); } + + // prof: dart_isolate_context->profiler()->FinishTrackSteps(); + + // prof: ui_command_buffer_.AddCommand(UICommand::kFinishRecordingCommand, nullptr, nullptr, nullptr); } ExecutingContext::~ExecutingContext() { @@ -89,64 +124,106 @@ ExecutingContext* ExecutingContext::From(JSContext* ctx) { return static_cast(JS_GetContextOpaque(ctx)); } -bool ExecutingContext::EvaluateJavaScript(const uint16_t* code, - size_t codeLength, +bool ExecutingContext::EvaluateJavaScript(const char16_t* code, size_t length, const char* sourceURL, int startLine) { + std::string utf8Code = toUTF8(std::u16string(reinterpret_cast(code), length)); + JSValue result = JS_Eval(script_state_.ctx(), utf8Code.c_str(), utf8Code.size(), sourceURL, JS_EVAL_TYPE_GLOBAL); + DrainMicrotasks(); + bool success = HandleException(&result); + JS_FreeValue(script_state_.ctx(), result); + return success; +} + +bool ExecutingContext::EvaluateJavaScript(const char* code, size_t codeLength, const char* sourceURL, int startLine) { + JSValue result = JS_Eval(script_state_.ctx(), code, codeLength, sourceURL, JS_EVAL_TYPE_GLOBAL); + DrainMicrotasks(); + bool success = HandleException(&result); + JS_FreeValue(script_state_.ctx(), result); + return success; +} + +bool ExecutingContext::EvaluateJavaScript(const char* code, + size_t code_len, uint8_t** parsed_bytecodes, uint64_t* bytecode_len, const char* sourceURL, int startLine) { - std::string utf8Code = toUTF8(std::u16string(reinterpret_cast(code), codeLength)); + if (ScriptForbiddenScope::IsScriptForbidden()) { + return false; + } + // prof: dart_isolate_context_->profiler()->StartTrackSteps("ExecutingContext::EvaluateJavaScript"); + JSValue result; if (parsed_bytecodes == nullptr) { - result = JS_Eval(script_state_.ctx(), utf8Code.c_str(), utf8Code.size(), sourceURL, JS_EVAL_TYPE_GLOBAL); + // prof: dart_isolate_context_->profiler()->StartTrackSteps("JS_Eval"); + + result = JS_Eval(script_state_.ctx(), code, code_len, sourceURL, JS_EVAL_TYPE_GLOBAL); + + // prof: dart_isolate_context_->profiler()->FinishTrackSteps(); } else { - JSValue byte_object = JS_Eval(script_state_.ctx(), utf8Code.c_str(), utf8Code.size(), sourceURL, - JS_EVAL_TYPE_GLOBAL | JS_EVAL_FLAG_COMPILE_ONLY); + // prof: dart_isolate_context_->profiler()->StartTrackSteps("JS_Eval"); + + JSValue byte_object = + JS_Eval(script_state_.ctx(), code, code_len, sourceURL, JS_EVAL_TYPE_GLOBAL | JS_EVAL_FLAG_COMPILE_ONLY); + + // prof: dart_isolate_context_->profiler()->FinishTrackSteps(); + if (JS_IsException(byte_object)) { HandleException(&byte_object); + // prof: dart_isolate_context_->profiler()->FinishTrackSteps(); return false; } + + // prof: dart_isolate_context_->profiler()->StartTrackSteps("JS_Eval"); size_t len; *parsed_bytecodes = JS_WriteObject(script_state_.ctx(), &len, byte_object, JS_WRITE_OBJ_BYTECODE); *bytecode_len = len; + // prof: dart_isolate_context_->profiler()->FinishTrackSteps(); + // prof: dart_isolate_context_->profiler()->StartTrackSteps("JS_EvalFunction"); + result = JS_EvalFunction(script_state_.ctx(), byte_object); + + // prof: dart_isolate_context_->profiler()->FinishTrackSteps(); } - DrainPendingPromiseJobs(); + DrainMicrotasks(); bool success = HandleException(&result); JS_FreeValue(script_state_.ctx(), result); - return success; -} - -bool ExecutingContext::EvaluateJavaScript(const char16_t* code, size_t length, const char* sourceURL, int startLine) { - std::string utf8Code = toUTF8(std::u16string(reinterpret_cast(code), length)); - JSValue result = JS_Eval(script_state_.ctx(), utf8Code.c_str(), utf8Code.size(), sourceURL, JS_EVAL_TYPE_GLOBAL); - DrainPendingPromiseJobs(); - bool success = HandleException(&result); - JS_FreeValue(script_state_.ctx(), result); - return success; -} + // prof: dart_isolate_context_->profiler()->FinishTrackSteps(); -bool ExecutingContext::EvaluateJavaScript(const char* code, size_t codeLength, const char* sourceURL, int startLine) { - JSValue result = JS_Eval(script_state_.ctx(), code, codeLength, sourceURL, JS_EVAL_TYPE_GLOBAL); - DrainPendingPromiseJobs(); - bool success = HandleException(&result); - JS_FreeValue(script_state_.ctx(), result); return success; } bool ExecutingContext::EvaluateByteCode(uint8_t* bytes, size_t byteLength) { + // prof: dart_isolate_context_->profiler()->StartTrackSteps("ExecutingContext::EvaluateByteCode"); + JSValue obj, val; + + // prof: dart_isolate_context_->profiler()->StartTrackSteps("JS_EvalFunction"); + obj = JS_ReadObject(script_state_.ctx(), bytes, byteLength, JS_READ_OBJ_BYTECODE); - if (!HandleException(&obj)) + + // prof: dart_isolate_context_->profiler()->FinishTrackSteps(); + + if (!HandleException(&obj)) { + // prof: dart_isolate_context_->profiler()->FinishTrackSteps(); return false; + } + + // prof: dart_isolate_context_->profiler()->StartTrackSteps("JS_EvalFunction"); + val = JS_EvalFunction(script_state_.ctx(), obj); - DrainPendingPromiseJobs(); - if (!HandleException(&val)) + + // prof: dart_isolate_context_->profiler()->FinishTrackSteps(); + + DrainMicrotasks(); + if (!HandleException(&val)) { + // prof: dart_isolate_context_->profiler()->FinishTrackSteps(); return false; + } JS_FreeValue(script_state_.ctx(), val); + // prof: dart_isolate_context_->profiler()->FinishTrackSteps(); return true; } @@ -154,6 +231,10 @@ bool ExecutingContext::IsContextValid() const { return is_context_valid_; } +void ExecutingContext::SetContextInValid() { + is_context_valid_ = false; +} + bool ExecutingContext::IsCtxValid() const { return script_state_.Invalid(); } @@ -216,16 +297,14 @@ void ExecutingContext::ReportError(JSValueConst error) { uint32_t messageLength = strlen(type) + strlen(title); if (stack != nullptr) { messageLength += 4 + strlen(stack); - char* message = new char[messageLength]; + char* message = (char*)dart_malloc(messageLength * sizeof(char)); snprintf(message, messageLength, "%s: %s\n%s", type, title, stack); handler_(this, message); - delete[] message; } else { messageLength += 3; - char* message = new char[messageLength]; + char* message = (char*)dart_malloc(messageLength * sizeof(char)); snprintf(message, messageLength, "%s: %s", type, title); handler_(this, message); - delete[] message; } JS_FreeValue(ctx, errorTypeValue); @@ -236,12 +315,62 @@ void ExecutingContext::ReportError(JSValueConst error) { JS_FreeCString(ctx, type); } +void ExecutingContext::DrainMicrotasks() { + // prof: dart_isolate_context_->profiler()->StartTrackSteps("ExecutingContext::DrainMicrotasks"); + + DrainPendingPromiseJobs(); + + // prof: dart_isolate_context_->profiler()->FinishTrackSteps(); + + // prof ui_command_buffer_.AddCommand(IsolateCommand::kFinishRecordingCommand, nullptr, nullptr, nullptr); +} + +namespace { + +struct MicroTaskDeliver { + MicrotaskCallback callback; + void* data; +}; + +} // namespace + +void ExecutingContext::EnqueueMicrotask(MicrotaskCallback callback, void* data) { + JSValue proxy_data = JS_NewObject(ctx()); + + auto* deliver = new MicroTaskDeliver(); + deliver->data = data; + deliver->callback = callback; + + JS_SetOpaque(proxy_data, deliver); + + JS_EnqueueJob( + ctx(), + [](JSContext* ctx, int argc, JSValueConst* argv) -> JSValue { + auto* deliver = static_cast(JS_GetOpaque(argv[0], JS_CLASS_OBJECT)); + deliver->callback(deliver->data); + + delete deliver; + return JS_NULL; + }, + 1, &proxy_data); + + JS_FreeValue(ctx(), proxy_data); +} + void ExecutingContext::DrainPendingPromiseJobs() { // should executing pending promise jobs. JSContext* pctx; + + // prof: dart_isolate_context_->profiler()->StartTrackSteps("JS_ExecutePendingJob"); + int finished = JS_ExecutePendingJob(script_state_.runtime(), &pctx); + + // prof: dart_isolate_context_->profiler()->FinishTrackSteps(); + while (finished != 0) { + // prof: dart_isolate_context_->profiler()->StartTrackSteps("JS_ExecutePendingJob"); finished = JS_ExecutePendingJob(script_state_.runtime(), &pctx); + // prof: dart_isolate_context_->profiler()->FinishTrackSteps(); if (finished == -1) { break; } @@ -264,14 +393,27 @@ ExecutionContextData* ExecutingContext::contextData() { uint8_t* ExecutingContext::DumpByteCode(const char* code, uint32_t codeLength, const char* sourceURL, - size_t* bytecodeLength) { + uint64_t* bytecodeLength) { + // prof: dart_isolate_context_->profiler()->StartTrackSteps("JS_Eval"); + JSValue object = JS_Eval(script_state_.ctx(), code, codeLength, sourceURL, JS_EVAL_TYPE_GLOBAL | JS_EVAL_FLAG_COMPILE_ONLY); + + // prof: dart_isolate_context_->profiler()->FinishTrackSteps(); + bool success = HandleException(&object); if (!success) return nullptr; - uint8_t* bytes = JS_WriteObject(script_state_.ctx(), bytecodeLength, object, JS_WRITE_OBJ_BYTECODE); + + // prof: dart_isolate_context_->profiler()->StartTrackSteps("JS_WriteObject"); + + size_t len; + uint8_t* bytes = JS_WriteObject(script_state_.ctx(), &len, object, JS_WRITE_OBJ_BYTECODE); + *bytecodeLength = len; JS_FreeValue(script_state_.ctx(), object); + + // prof: dart_isolate_context_->profiler()->FinishTrackSteps(); + return bytes; } @@ -302,12 +444,54 @@ static void DispatchPromiseRejectionEvent(const AtomicString& event_type, } } -void ExecutingContext::FlushIsolateCommand() { +void ExecutingContext::FlushIsolateCommand(const BindingObject* self, uint32_t reason) { + std::vector deps; + FlushIsolateCommand(self, reason, deps); +} + +void ExecutingContext::FlushIsolateCommand(const webf::BindingObject* self, + uint32_t reason, + std::vector& deps) { if (!isolateCommandBuffer()->empty()) { - dartMethodPtr()->flushIsolateCommand(context_id_); + if (is_dedicated_) { + bool should_swap_isolate_commands = false; + if (isIsolateCommandReasonDependsOnObject(reason)) { + bool object_mounted_on_dart = self->bindingObject()->invoke_bindings_methods_from_native != nullptr; + bool is_deps_objects_mounted_on_dart = true; + + for (auto binding : deps) { + if (binding->invoke_bindings_methods_from_native == nullptr) { + is_deps_objects_mounted_on_dart = false; + } + } + + if (!object_mounted_on_dart || !is_deps_objects_mounted_on_dart) { + should_swap_isolate_commands = true; + } + } + + if (isIsolateCommandReasonDependsOnLayout(reason) || isIsolateCommandReasonDependsOnAll(reason)) { + should_swap_isolate_commands = true; + } + + // Sync commands to dart when caller dependents on Object. + if (should_swap_isolate_commands) { + isolate_command_buffer_.SyncToActive(); + } + } + + dartMethodPtr()->flushIsolateCommand(is_dedicated_, context_id_, self->bindingObject()); } } +void ExecutingContext::TurnOnJavaScriptGC() { + JS_TurnOnGC(script_state_.runtime()); +} + +void ExecutingContext::TurnOffJavaScriptGC() { + JS_TurnOffGC(script_state_.runtime()); +} + void ExecutingContext::DispatchErrorEvent(ErrorEvent* error_event) { if (in_dispatch_error_event_) { return; @@ -408,9 +592,11 @@ void ExecutingContext::InActiveScriptWrappers(ScriptWrappable* script_wrappable) } // A lock free context validator. -bool isContextValid(int32_t contextId) { +bool isContextValid(double contextId) { if (contextId > running_context_list) return false; + if (valid_contexts.count(contextId) == 0) + return false; return valid_contexts[contextId]; } diff --git a/bridge/core/executing_context.h b/bridge/core/executing_context.h index 0aea0bd..812a339 100644 --- a/bridge/core/executing_context.h +++ b/bridge/core/executing_context.h @@ -16,6 +16,7 @@ #include #include #include +#include #include "bindings/qjs/rejected_promises.h" #include "bindings/qjs/script_value.h" #include "dart_isolate_context.h" @@ -30,6 +31,8 @@ #include "script_state.h" #include "defined_properties.h" +#include "shared_isolate_command.h" + namespace mercury { struct NativeByteCode { @@ -42,11 +45,14 @@ class Global; class MemberMutationScope; class ErrorEvent; class DartContext; +class BindingObject; +struct NativeBindingObject; class ScriptWrappable; using JSExceptionHandler = std::function; +using MicrotaskCallback = void (*)(void* data); -bool isContextValid(int32_t contextId); +bool isContextValid(double contextId); // An environment in which script can execute. This class exposes the common // properties of script execution environments on the mercury. @@ -55,14 +61,16 @@ class ExecutingContext { public: ExecutingContext() = delete; ExecutingContext(DartIsolateContext* dart_isolate_context, - int32_t contextId, + bool is_dedicated, + size_t sync_buffer_size, + double context_id, JSExceptionHandler handler, void* owner); ~ExecutingContext(); static ExecutingContext* From(JSContext* ctx); - bool EvaluateJavaScript(const uint16_t* code, + bool EvaluateJavaScript(const char* code, size_t codeLength, uint8_t** parsed_bytecodes, uint64_t* bytecode_len, @@ -72,20 +80,22 @@ class ExecutingContext { bool EvaluateJavaScript(const char* code, size_t codeLength, const char* sourceURL, int startLine); bool EvaluateByteCode(uint8_t* bytes, size_t byteLength); bool IsContextValid() const; + void SetContextInValid(); bool IsCtxValid() const; JSValue GlobalObject(); JSContext* ctx(); - FORCE_INLINE int32_t contextId() const { return context_id_; }; + FORCE_INLINE double contextId() const { return context_id_; }; FORCE_INLINE int32_t uniqueId() const { return unique_id_; } void* owner(); bool HandleException(JSValue* exc); bool HandleException(ScriptValue* exc); bool HandleException(ExceptionState& exception_state); void ReportError(JSValueConst error); - void DrainPendingPromiseJobs(); + void DrainMicrotasks(); + void EnqueueMicrotask(MicrotaskCallback callback, void* data = nullptr); void DefineGlobalProperty(const char* prop, JSValueConst value); ExecutionContextData* contextData(); - uint8_t* DumpByteCode(const char* code, uint32_t codeLength, const char* sourceURL, size_t* bytecodeLength); + uint8_t* DumpByteCode(const char* code, uint32_t codeLength, const char* sourceURL, uint64_t* bytecodeLength); // Make global object inherit from GlobalProperties. void InstallGlobal(); @@ -115,17 +125,23 @@ class ExecutingContext { void ClearMutationScope(); FORCE_INLINE Global* global() const { return global_; } - IsolateCommandBuffer isolate_command_buffer_{this}; FORCE_INLINE DartIsolateContext* dartIsolateContext() const { return dart_isolate_context_; }; - FORCE_INLINE const std::unique_ptr& dartMethodPtr() { + // prof: FORCE_INLINE Performance* performance() const { return performance_; } + FORCE_INLINE SharedIsolateCommand* isolateCommandBuffer() { return &isolate_command_buffer_; }; + FORCE_INLINE DartMethodPointer* dartMethodPtr() const { assert(dart_isolate_context_->valid()); return dart_isolate_context_->dartMethodPtr(); } + FORCE_INLINE bool isDedicated() { return is_dedicated_; } FORCE_INLINE std::chrono::time_point timeOrigin() const { return time_origin_; } - FORCE_INLINE IsolateCommandBuffer* isolateCommandBuffer() { return &isolate_command_buffer_; }; + FORCE_INLINE SharedIsolateCommand* isolateCommandBuffer() { return &isolate_command_buffer_; }; + + void FlushIsolateCommand(const BindingObject* self, uint32_t reason); + void FlushIsolateCommand(const BindingObject* self, uint32_t reason, std::vector& deps); - void FlushIsolateCommand(); + void TurnOnJavaScriptGC(); + void TurnOffJavaScriptGC(); void DispatchErrorEvent(ErrorEvent* error_event); void DispatchErrorEventInterval(ErrorEvent* error_event); @@ -146,6 +162,9 @@ class ExecutingContext { std::chrono::time_point time_origin_; int32_t unique_id_; + void DrainPendingPromiseJobs(); + void EnsureEnqueueMicrotask(); + static void promiseRejectTracker(JSContext* ctx, JSValueConst promise, JSValueConst reason, @@ -162,8 +181,8 @@ class ExecutingContext { // ---------------------------------------------------------------------- // All members below will be free before ScriptState freed. // ---------------------------------------------------------------------- - bool is_context_valid_{false}; - int32_t context_id_; + std::atomic is_context_valid_{false}; + double context_id_; JSExceptionHandler handler_; void* owner_; JSValue global_object_{JS_NULL}; @@ -175,7 +194,8 @@ class ExecutingContext { bool in_dispatch_error_event_{false}; RejectedPromises rejected_promises_; MemberMutationScope* active_mutation_scope{nullptr}; - std::set active_wrappers_; + std::unordered_set active_wrappers_; + bool is_dedicated_; }; class ObjectProperty { diff --git a/bridge/core/fileapi/blob.cc b/bridge/core/fileapi/blob.cc index 830c0dc..8c93e71 100644 --- a/bridge/core/fileapi/blob.cc +++ b/bridge/core/fileapi/blob.cc @@ -127,20 +127,20 @@ void Blob::SetMineType(const std::string& mine_type) { } ScriptPromise Blob::arrayBuffer(ExceptionState& exception_state) { - auto resolver = ScriptPromiseResolver::Create(GetExecutingContext()); - new BlobReaderClient(GetExecutingContext(), this, resolver, BlobReaderClient::ReadType::kReadAsArrayBuffer); + auto resolver = ScriptPromiseResolver::Create(executingContext()); + new BlobReaderClient(executingContext(), this, resolver, BlobReaderClient::ReadType::kReadAsArrayBuffer); return resolver->Promise(); } ScriptPromise Blob::text(ExceptionState& exception_state) { - auto resolver = ScriptPromiseResolver::Create(GetExecutingContext()); - new BlobReaderClient(GetExecutingContext(), this, resolver, BlobReaderClient::ReadType::kReadAsText); + auto resolver = ScriptPromiseResolver::Create(executingContext()); + new BlobReaderClient(executingContext(), this, resolver, BlobReaderClient::ReadType::kReadAsText); return resolver->Promise(); } ScriptPromise Blob::base64(ExceptionState& exception_state) { - auto resolver = ScriptPromiseResolver::Create(GetExecutingContext()); - new BlobReaderClient(GetExecutingContext(), this, resolver, BlobReaderClient::ReadType::kReadAsBase64); + auto resolver = ScriptPromiseResolver::Create(executingContext()); + new BlobReaderClient(executingContext(), this, resolver, BlobReaderClient::ReadType::kReadAsBase64); return resolver->Promise(); } diff --git a/bridge/core/mercury_isolate.cc b/bridge/core/mercury_isolate.cc index 4eeff3e..b52bf24 100644 --- a/bridge/core/mercury_isolate.cc +++ b/bridge/core/mercury_isolate.cc @@ -19,8 +19,12 @@ namespace mercury { ConsoleMessageHandler MercuryIsolate::consoleMessageHandler{nullptr}; -MercuryIsolate::MercuryIsolate(DartIsolateContext* dart_isolate_context, int32_t contextId, const JSExceptionHandler& handler) - : contextId(contextId), ownerThreadId(std::this_thread::get_id()) { +MercuryIsolate::MercuryIsolate(DartIsolateContext* dart_isolate_context, + bool is_dedicated, + size_t sync_buffer_size, + double context_id, + const JSExceptionHandler& handler) + : ownerThreadId(std::this_thread::get_id()), dart_isolate_context_(dart_isolate_context) { context_ = new ExecutingContext( dart_isolate_context, contextId, [](ExecutingContext* context, const char* message) { @@ -78,7 +82,8 @@ NativeValue* MercuryIsolate::invokeModuleEvent(SharedNativeString* native_module return return_value; } -bool MercuryIsolate::evaluateScript(const SharedNativeString* script, +bool MercuryIsolate::evaluateScript(const char* script, + uint64_t script_len, uint8_t** parsed_bytecodes, uint64_t* bytecode_len, const char* url, @@ -97,7 +102,7 @@ bool MercuryIsolate::evaluateScript(const uint16_t* script, int startLine) { if (!context_->IsContextValid()) return false; - return context_->EvaluateJavaScript(script, length, parsed_bytecodes, bytecode_len, url, startLine); + return context_->EvaluateJavaScript(script, script_len, parsed_bytecodes, bytecode_len, url, startLine); } void MercuryIsolate::evaluateScript(const char* script, size_t length, const char* url, int startLine) { @@ -106,7 +111,7 @@ void MercuryIsolate::evaluateScript(const char* script, size_t length, const cha context_->EvaluateJavaScript(script, length, url, startLine); } -uint8_t* MercuryIsolate::dumpByteCode(const char* script, size_t length, const char* url, size_t* byteLength) { +uint8_t* MercuryIsolate::dumpByteCode(const char* script, size_t length, const char* url, uint64_t* byteLength) { if (!context_->IsContextValid()) return nullptr; return context_->DumpByteCode(script, length, url, byteLength); diff --git a/bridge/core/mercury_isolate.h b/bridge/core/mercury_isolate.h index eca2a0c..588fd24 100644 --- a/bridge/core/mercury_isolate.h +++ b/bridge/core/mercury_isolate.h @@ -32,14 +32,19 @@ class MercuryIsolate final { public: static ConsoleMessageHandler consoleMessageHandler; MercuryIsolate() = delete; - MercuryIsolate(DartIsolateContext* dart_isolate_context, int32_t jsContext, const JSExceptionHandler& handler); + MercuryIsolate(DartIsolateContext* dart_isolate_context, + bool is_dedicated, + size_t sync_buffer_size, + double context_id, + const JSExceptionHandler& handler); ~MercuryIsolate(); // Bytecodes which registered by mercury plugins. static std::unordered_map pluginByteCode; // evaluate JavaScript source codes in standard mode. - bool evaluateScript(const SharedNativeString* script, + bool evaluateScript(const char* script, + uint64_t script_len, uint8_t** parsed_bytecodes, uint64_t* bytecode_len, const char* url, @@ -51,12 +56,13 @@ class MercuryIsolate final { const char* url, int startLine); void evaluateScript(const char* script, size_t length, const char* url, int startLine); - uint8_t* dumpByteCode(const char* script, size_t length, const char* url, size_t* byteLength); + uint8_t* dumpByteCode(const char* script, size_t length, const char* url, uint64_t* byteLength); bool evaluateByteCode(uint8_t* bytes, size_t byteLength); std::thread::id currentThread() const; - [[nodiscard]] ExecutingContext* GetExecutingContext() const { return context_; } + [[nodiscard]] ExecutingContext* executingContext() const { return context_; } + [[nodiscard]] DartIsolateContext* dartIsolateContext() const { return dart_isolate_context_; } NativeValue* invokeModuleEvent(SharedNativeString* moduleName, const char* eventType, @@ -64,7 +70,8 @@ class MercuryIsolate final { NativeValue* extra); void reportError(const char* errmsg); - int32_t contextId; + FORCE_INLINE bool isDedicated() { return context_->isDedicated(); }; + FORCE_INLINE double contextId() { return context_->contextId(); } #if IS_TEST // the owner pointer which take JSBridge as property. void* owner; @@ -75,6 +82,7 @@ class MercuryIsolate final { // FIXME: we must to use raw pointer instead of unique_ptr because we needs to access context_ when dispose page. // TODO: Raw pointer is dangerous and just works but it's fragile. We needs refactor this for more stable and // maintainable. + DartIsolateContext* dart_isolate_context_; ExecutingContext* context_; JSExceptionHandler handler_; }; diff --git a/bridge/core/module/global.cc b/bridge/core/module/global.cc index 034888c..28a1520 100644 --- a/bridge/core/module/global.cc +++ b/bridge/core/module/global.cc @@ -17,7 +17,7 @@ namespace mercury { Global::Global(ExecutingContext* context) : EventTargetWithInlineData(context, built_in_string::kglobalThis) { - context->isolateCommandBuffer()->addCommand(IsolateCommand::kCreateGlobal, nullptr, (void*)bindingObject(), nullptr); + context->isolateCommandBuffer()->AddCommand(IsolateCommand::kCreateGlobal, nullptr, bindingObject(), nullptr); } // https://infra.spec.whatwg.org/#ascii-whitespace @@ -116,7 +116,7 @@ void Global::postMessage(const ScriptValue& message, ExceptionState& exception_s auto event_init = MessageEventInit::Create(); event_init->setData(message); auto* message_event = - MessageEvent::Create(GetExecutingContext(), event_type_names::kmessage, event_init, exception_state); + MessageEvent::Create(executingContext(), event_type_names::kmessage, event_init, exception_state); dispatchEvent(message_event, exception_state); } @@ -127,10 +127,14 @@ void Global::postMessage(const ScriptValue& message, event_init->setData(message); event_init->setOrigin(target_origin); auto* message_event = - MessageEvent::Create(GetExecutingContext(), event_type_names::kmessage, event_init, exception_state); + MessageEvent::Create(executingContext(), event_type_names::kmessage, event_init, exception_state); dispatchEvent(message_event, exception_state); } +void Global::OnLoadEventFired() { + executingContext()->TurnOnJavaScriptGC(); +} + bool Global::IsGlobalOrWorkerScope() const { return true; } diff --git a/bridge/core/module/global.h b/bridge/core/module/global.h index fabaa6e..f28ede8 100644 --- a/bridge/core/module/global.h +++ b/bridge/core/module/global.h @@ -36,6 +36,7 @@ class Global : public EventTargetWithInlineData { double requestAnimationFrame(const std::shared_ptr& callback, ExceptionState& exceptionState); void cancelAnimationFrame(double request_id, ExceptionState& exception_state); + void OnLoadEventFired(); bool IsGlobalOrWorkerScope() const override; void Trace(GCVisitor* visitor) const override; diff --git a/bridge/core/module/global_or_worker_scope.cc b/bridge/core/module/global_or_worker_scope.cc index 9d10ed7..03580b1 100644 --- a/bridge/core/module/global_or_worker_scope.cc +++ b/bridge/core/module/global_or_worker_scope.cc @@ -14,17 +14,24 @@ static void handleTimerCallback(Timer* timer, const char* errmsg) { JSValue exception = JS_ThrowTypeError(context->ctx(), "%s", errmsg); context->HandleException(&exception); context->Timers()->forceStopTimeoutById(timer->timerId()); + dart_free(errmsg); return; } if (context->Timers()->getTimerById(timer->timerId()) == nullptr) return; + // prof: context->dartIsolateContext()->profiler()->StartTrackAsyncEvaluation(); + // prof: context->dartIsolateContext()->profiler()->StartTrackSteps("handleTimerCallback"); + // Trigger timer callbacks. timer->Fire(); + + // prof: context->dartIsolateContext()->profiler()->FinishTrackSteps(); + // prof: context->dartIsolateContext()->profiler()->FinishTrackAsyncEvaluation(); } -static void handleTransientCallback(void* ptr, int32_t contextId, const char* errmsg) { +static void handleTransientCallback(void* ptr, double contextId, char* errmsg) { if (!isContextValid(contextId)) return; @@ -45,7 +52,7 @@ static void handleTransientCallback(void* ptr, int32_t contextId, const char* er context->Timers()->removeTimeoutById(timer->timerId()); } -static void handlePersistentCallback(void* ptr, int32_t contextId, const char* errmsg) { +static void handlePersistentCallback(void* ptr, double contextId, char* errmsg) { if (!isContextValid(contextId)) return; @@ -75,28 +82,48 @@ static void handlePersistentCallback(void* ptr, int32_t contextId, const char* e timer->SetStatus(Timer::TimerStatus::kFinished); } +static void handleTransientCallbackWrapper(void* ptr, double contextId, char* errmsg) { + auto* timer = static_cast(ptr); + auto* context = timer->context(); + + if (!context->IsContextValid()) + return; + + context->dartIsolateContext()->dispatcher()->PostToJs(context->isDedicated(), contextId, + mercury::handleTransientCallback, ptr, contextId, errmsg); +} + +static void handlePersistentCallbackWrapper(void* ptr, double contextId, char* errmsg) { + auto* timer = static_cast(ptr); + auto* context = timer->context(); + + if (!context->IsContextValid()) + return; + + context->dartIsolateContext()->dispatcher()->PostToJs(context->isDedicated(), contextId, + mercury::handlePersistentCallback, ptr, contextId, errmsg); +} + int GlobalOrWorkerScope::setTimeout(ExecutingContext* context, - std::shared_ptr handler, + const std::shared_ptr& handler, ExceptionState& exception) { return setTimeout(context, handler, 0.0, exception); } int GlobalOrWorkerScope::setTimeout(ExecutingContext* context, - std::shared_ptr handler, + const std::shared_ptr& handler, int32_t timeout, ExceptionState& exception) { -#if FLUTTER_BACKEND - if (context->dartMethodPtr()->setTimeout == nullptr) { - exception.ThrowException(context->ctx(), ErrorType::InternalError, - "Failed to execute 'setTimeout': dart method (setTimeout) is not registered."); + + if (handler == nullptr) { + exception.ThrowException(context->ctx(), ErrorType::InternalError, "Timeout callback is null"); return -1; } -#endif // Create a timer object to keep track timer callback. auto timer = Timer::create(context, handler, Timer::TimerKind::kOnce); - auto timerId = - context->dartMethodPtr()->setTimeout(timer.get(), context->contextId(), handleTransientCallback, timeout); + int32_t timerId = context->dartMethodPtr()->setInterval(context->isDedicated(), timer.get(), context->contextId(), + handlePersistentCallbackWrapper, timeout); // Register timerId. timer->setTimerId(timerId); @@ -113,47 +140,35 @@ int GlobalOrWorkerScope::setInterval(ExecutingContext* context, } int GlobalOrWorkerScope::setInterval(ExecutingContext* context, - std::shared_ptr handler, + const std::shared_ptr& handler, int32_t timeout, ExceptionState& exception) { - if (context->dartMethodPtr()->setInterval == nullptr) { - exception.ThrowException(context->ctx(), ErrorType::InternalError, - "Failed to execute 'setInterval': dart method (setInterval) is not registered."); + if (handler == nullptr) { + exception.ThrowException(context->ctx(), ErrorType::InternalError, "Timeout callback is null"); return -1; } // Create a timer object to keep track timer callback. - auto timer = Timer::create(context, handler, Timer::TimerKind::kMultiple); + auto timer_id = context->dartMethodPtr()->setTimeout(context->isDedicated(), timer.get(), context->contextId(), + handleTransientCallbackWrapper, timeout); - int32_t timerId = + int32_t timer_id = context->dartMethodPtr()->setInterval(timer.get(), context->contextId(), handlePersistentCallback, timeout); // Register timerId. - timer->setTimerId(timerId); - context->Timers()->installNewTimer(context, timerId, timer); + timer->setTimerId(timer_id); + context->Timers()->installNewTimer(context, timer_id, timer); - return timerId; + return timer_id; } void GlobalOrWorkerScope::clearTimeout(ExecutingContext* context, int32_t timerId, ExceptionState& exception) { - if (context->dartMethodPtr()->clearTimeout == nullptr) { - exception.ThrowException(context->ctx(), ErrorType::InternalError, - "Failed to execute 'clearTimeout': dart method (clearTimeout) is not registered."); - return; - } - - context->dartMethodPtr()->clearTimeout(context->contextId(), timerId); + context->dartMethodPtr()->clearTimeout(context->isDedicated(), context->contextId(), timerId); context->Timers()->forceStopTimeoutById(timerId); } void GlobalOrWorkerScope::clearInterval(ExecutingContext* context, int32_t timerId, ExceptionState& exception) { - if (context->dartMethodPtr()->clearTimeout == nullptr) { - exception.ThrowException(context->ctx(), ErrorType::InternalError, - "Failed to execute 'clearTimeout': dart method (clearTimeout) is not registered."); - return; - } - - context->dartMethodPtr()->clearTimeout(context->contextId(), timerId); + context->dartMethodPtr()->clearTimeout(context->isDedicated(), context->contextId(), timerId); context->Timers()->forceStopTimeoutById(timerId); } @@ -168,7 +183,7 @@ ScriptValue GlobalOrWorkerScope::__memory_usage__(ExecutingContext* context, Exc char buff[2048]; snprintf(buff, 2048, - R"({"malloc_size": %ld, "malloc_limit": %ld, "memory_used_size": %ld, "memory_used_count": %ld})", + R"({"malloc_size": %lld, "malloc_limit": %lld, "memory_used_size": %lld, "memory_used_count": %lld})", memory_usage.malloc_size, memory_usage.malloc_limit, memory_usage.memory_used_size, memory_usage.memory_used_count); diff --git a/bridge/core/module/global_or_worker_scope.h b/bridge/core/module/global_or_worker_scope.h index d1c0ef8..0eb48ee 100644 --- a/bridge/core/module/global_or_worker_scope.h +++ b/bridge/core/module/global_or_worker_scope.h @@ -14,12 +14,14 @@ namespace mercury { class GlobalOrWorkerScope { public: static int setTimeout(ExecutingContext* context, - std::shared_ptr handler, + const std::shared_ptr& handler, int32_t timeout, ExceptionState& exception); - static int setTimeout(ExecutingContext* context, std::shared_ptr handler, ExceptionState& exception); + static int setTimeout(ExecutingContext* context, + const std::shared_ptr& handler, + ExceptionState& exception); static int setInterval(ExecutingContext* context, - std::shared_ptr handler, + const std::shared_ptr& handler, int32_t timeout, ExceptionState& exception); static int setInterval(ExecutingContext* context, std::shared_ptr handler, ExceptionState& exception); diff --git a/bridge/core/module/module_manager.cc b/bridge/core/module/module_manager.cc index 8076745..da22c6c 100644 --- a/bridge/core/module/module_manager.cc +++ b/bridge/core/module/module_manager.cc @@ -5,19 +5,14 @@ #include "module_manager.h" #include "core/executing_context.h" #include "foundation/logging.h" +#include "foundation/native_value.h" +#include "include/dart_api.h" #include "module_callback.h" namespace mercury { -struct ModuleContext { - ModuleContext(ExecutingContext* context, const std::shared_ptr& callback) - : context(context), callback(callback) {} - ExecutingContext* context; - std::shared_ptr callback; -}; - NativeValue* handleInvokeModuleTransientCallback(void* ptr, - int32_t contextId, + double contextId, const char* errmsg, NativeValue* extra_data) { auto* moduleContext = static_cast(ptr); @@ -38,6 +33,9 @@ NativeValue* handleInvokeModuleTransientCallback(void* ptr, if (ctx == nullptr) return nullptr; + // prof: context->dartIsolateContext()->profiler()->StartTrackAsyncEvaluation(); + // prof: context->dartIsolateContext()->profiler()->StartTrackSteps("handleInvokeModuleTransientCallback"); + ExceptionState exception_state; NativeValue* return_value = nullptr; @@ -62,6 +60,9 @@ NativeValue* handleInvokeModuleTransientCallback(void* ptr, memcpy(return_value, &native_result, sizeof(NativeValue)); } + // prof: context->dartIsolateContext()->profiler()->FinishTrackSteps(); + // prof: context->dartIsolateContext()->profiler()->FinishTrackAsyncEvaluation(); + if (exception_state.HasException()) { context->HandleException(exception_state); return nullptr; @@ -70,10 +71,45 @@ NativeValue* handleInvokeModuleTransientCallback(void* ptr, return return_value; } +static void ReturnResultToDart(Dart_PersistentHandle persistent_handle, + NativeValue* result, + InvokeModuleResultCallback result_callback) { + Dart_Handle handle = Dart_HandleFromPersistent_DL(persistent_handle); + result_callback(handle, result); + Dart_DeletePersistentHandle_DL(persistent_handle); +} + +static NativeValue* handleInvokeModuleTransientCallbackWrapper(void* ptr, + double context_id, + const char* errmsg, + NativeValue* extra_data, + Dart_Handle dart_handle, + InvokeModuleResultCallback result_callback) { + auto* moduleContext = static_cast(ptr); + +#if FLUTTER_BACKEND + Dart_PersistentHandle persistent_handle = Dart_NewPersistentHandle_DL(dart_handle); + moduleContext->context->dartIsolateContext()->dispatcher()->PostToJs( + moduleContext->context->isDedicated(), moduleContext->context->contextId(), + [](ModuleContext* module_context, double context_id, const char* errmsg, NativeValue* extra_data, + Dart_PersistentHandle persistent_handle, InvokeModuleResultCallback result_callback) { + NativeValue* result = handleInvokeModuleTransientCallback(module_context, context_id, errmsg, extra_data); + module_context->context->dartIsolateContext()->dispatcher()->PostToDart( + module_context->context->isDedicated(), ReturnResultToDart, persistent_handle, result, result_callback); + }, + moduleContext, context_id, errmsg, extra_data, persistent_handle, result_callback); + return nullptr; +#else + return handleInvokeModuleTransientCallback(moduleContext, context_id, errmsg, extra_data); +#endif +} + NativeValue* handleInvokeModuleUnexpectedCallback(void* callbackContext, - int32_t contextId, + double contextId, const char* errmsg, - NativeValue* extra_data) { + NativeValue* extra_data, + Dart_Handle dart_handle, + InvokeModuleResultCallback result_callback) { static_assert("Unexpected module callback, please check your invokeModule implementation on the dart side."); return nullptr; } @@ -106,33 +142,42 @@ ScriptValue ModuleManager::__mercury_invoke_module__(ExecutingContext* context, return ScriptValue::Empty(context->ctx()); } - if (context->dartMethodPtr()->invokeModule == nullptr) { - exception.ThrowException( - context->ctx(), ErrorType::InternalError, - "Failed to execute '__mercury_invoke_module__': dart method (invokeModule) is not registered."); - return ScriptValue::Empty(context->ctx()); - } - NativeValue* result; + auto module_name_string = module_name.ToNativeString(context->ctx()); + auto method_name_string = method.ToNativeString(context->ctx()); + + // prof: context->dartIsolateContext()->profiler()->StartTrackLinkSteps("Call To Dart"); + if (callback != nullptr) { auto module_callback = ModuleCallback::Create(callback); auto module_context = std::make_shared(context, module_callback); context->ModuleContexts()->AddModuleContext(module_context); - result = context->dartMethodPtr()->invokeModule( - module_context.get(), context->contextId(), module_name.ToNativeString(context->ctx()).release(), - method.ToNativeString(context->ctx()).release(), ¶ms, handleInvokeModuleTransientCallback); + result = context->dartMethodPtr()->invokeModule(context->isDedicated(), module_context.get(), context->contextId(), + // prof: context->dartIsolateContext()->profiler()->link_id(), + module_name_string.get(), method_name_string.get(), ¶ms, + handleInvokeModuleTransientCallbackWrapper); } else { result = context->dartMethodPtr()->invokeModule( - nullptr, context->contextId(), module_name.ToNativeString(context->ctx()).release(), - method.ToNativeString(context->ctx()).release(), ¶ms, handleInvokeModuleUnexpectedCallback); + context->isDedicated(), + nullptr, + context->contextId(), + // prof: context->dartIsolateContext()->profiler()->link_id(), + module_name_string.get(), + method_name_string.get(), + ¶ms, + handleInvokeModuleUnexpectedCallback + ); } + // prof: context->dartIsolateContext()->profiler()->FinishTrackLinkSteps(); + + if (result == nullptr) { return ScriptValue::Empty(context->ctx()); } ScriptValue return_value = ScriptValue(context->ctx(), *result); - delete result; + dart_free(result); return return_value; } diff --git a/bridge/core/module/module_manager.h b/bridge/core/module/module_manager.h index ef275a7..7ad6b58 100644 --- a/bridge/core/module/module_manager.h +++ b/bridge/core/module/module_manager.h @@ -12,6 +12,13 @@ namespace mercury { +struct ModuleContext { + ModuleContext(ExecutingContext* context, const std::shared_ptr& callback) + : context(context), callback(callback) {} + ExecutingContext* context; + std::shared_ptr callback; +}; + class ModuleManager { public: static ScriptValue __mercury_invoke_module__(ExecutingContext* context, diff --git a/bridge/core/module/module_manager_test.cc b/bridge/core/module/module_manager_test.cc index 8d881fe..51899fe 100644 --- a/bridge/core/module/module_manager_test.cc +++ b/bridge/core/module/module_manager_test.cc @@ -10,10 +10,10 @@ namespace mercury { TEST(ModuleManager, ShouldReturnCorrectValue) { bool static errorCalled = false; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); + auto env = TEST_init([](double contextId, const char* errmsg) { errorCalled = true; }); mercury::MercuryMain::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) {}; - auto context = env->page()->GetExecutingContext(); + auto context = env->page()->executingContext(); std::string code = std::string(R"( let object = { @@ -35,14 +35,14 @@ console.log(result); TEST(ModuleManager, shouldThrowErrorWhenBadJSON) { bool static errorCalled = false; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { + auto env = TEST_init([](double contextId, const char* errmsg) { std::string stdErrorMsg = std::string(errmsg); EXPECT_EQ(stdErrorMsg.find("TypeError: circular reference") != std::string::npos, true); errorCalled = true; }); mercury::MercuryMain::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) {}; - auto context = env->page()->GetExecutingContext(); + auto context = env->page()->executingContext(); std::string code = std::string(R"( let object = { @@ -64,7 +64,7 @@ mercury.methodChannel.invokeMethod('abc', 'fn', object); TEST(ModuleManager, invokeModuleError) { bool static logCalled = false; - auto env = TEST_init([](int32_t contextId, const char* errmsg) {}); + auto env = TEST_init([](double contextId, const char* errmsg) {}); mercury::MercuryMain::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; EXPECT_STREQ( @@ -75,7 +75,7 @@ TEST(ModuleManager, invokeModuleError) { "'}"); }; - auto context = env->page()->GetExecutingContext(); + auto context = env->page()->executingContext(); std::string code = std::string(R"( function f() { diff --git a/bridge/core/script_forbidden.h b/bridge/core/script_forbidden.h new file mode 100644 index 0000000..e58c2e7 --- /dev/null +++ b/bridge/core/script_forbidden.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef MERCURY_CORE_SCRIPT_FORBIDDEN_SCOPE_H_ +#define MERCURY_CORE_SCRIPT_FORBIDDEN_SCOPE_H_ + +#include +#include "foundation/macros.h" + +namespace mercury { + +// Scoped disabling of script execution. +class ScriptForbiddenScope final { + MERCURY_STACK_ALLOCATED(); + + public: + ScriptForbiddenScope() { Enter(); } + ScriptForbiddenScope(const ScriptForbiddenScope&) = delete; + ScriptForbiddenScope& operator=(const ScriptForbiddenScope&) = delete; + ~ScriptForbiddenScope() { Exit(); } + + static bool IsScriptForbidden() { return g_main_thread_counter_ > 0; } + + private: + static void Enter() { ++g_main_thread_counter_; } + static void Exit() { + assert(IsScriptForbidden()); + --g_main_thread_counter_; + } + + static unsigned g_main_thread_counter_; +}; + +} // namespace mercury + +#endif // MERCURY_CORE_SCRIPT_FORBIDDEN_SCOPE_H_ diff --git a/bridge/core/script_forbidden_scope.cc b/bridge/core/script_forbidden_scope.cc new file mode 100644 index 0000000..469b1ac --- /dev/null +++ b/bridge/core/script_forbidden_scope.cc @@ -0,0 +1,11 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "script_forbidden_scope.h" + +namespace mercury { + +unsigned ScriptForbiddenScope::g_main_thread_counter_ = 0; + +} diff --git a/bridge/core/script_state.cc b/bridge/core/script_state.cc index cedc0d1..5afb0d7 100644 --- a/bridge/core/script_state.cc +++ b/bridge/core/script_state.cc @@ -24,6 +24,7 @@ JSRuntime* ScriptState::runtime() { ScriptState::~ScriptState() { ctx_invalid_ = true; JSRuntime* rt = JS_GetRuntime(ctx_); + JS_TurnOnGC(rt); JS_FreeContext(ctx_); // Run GC to clean up remaining objects about m_ctx; diff --git a/bridge/core/script_state.h b/bridge/core/script_state.h index a12bd28..c1ce455 100644 --- a/bridge/core/script_state.h +++ b/bridge/core/script_state.h @@ -24,7 +24,7 @@ class ScriptState { inline bool Invalid() const { return !ctx_invalid_; } inline JSContext* ctx() { - assert(!ctx_invalid_ && "GetExecutingContext has been released"); + assert(!ctx_invalid_ && "executingContext has been released"); return ctx_; } JSRuntime* runtime(); diff --git a/bridge/foundation/dart_readable.cc b/bridge/foundation/dart_readable.cc index c5ca523..ea21849 100644 --- a/bridge/foundation/dart_readable.cc +++ b/bridge/foundation/dart_readable.cc @@ -1,14 +1,15 @@ /* -* Copyright (C) 2022-present The WebF authors. All rights reserved. -*/ + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ #include "dart_readable.h" +#include +#include #if WIN32 #include #endif - namespace mercury { void* dart_malloc(std::size_t size) { @@ -35,5 +36,4 @@ void DartReadable::operator delete(void* memory) noexcept { dart_free(memory); } - -} +} // namespace mercury diff --git a/bridge/foundation/dart_readable.h b/bridge/foundation/dart_readable.h index 1d44bf3..c0f0757 100644 --- a/bridge/foundation/dart_readable.h +++ b/bridge/foundation/dart_readable.h @@ -1,6 +1,6 @@ /* -* Copyright (C) 2022-present The WebF authors. All rights reserved. -*/ + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ #ifndef MERCURY_DART_READABLE_H #define MERCURY_DART_READABLE_H @@ -19,6 +19,6 @@ struct DartReadable { static void operator delete(void* memory) noexcept; }; -} +} // namespace mercury #endif // MERCURY_DART_READABLE_H diff --git a/bridge/foundation/inspector_task_queue.h b/bridge/foundation/inspector_task_queue.h index 7e8a610..1061abd 100644 --- a/bridge/foundation/inspector_task_queue.h +++ b/bridge/foundation/inspector_task_queue.h @@ -15,7 +15,7 @@ using Task = void (*)(void*); class InspectorTaskQueue : public TaskQueue { public: - static fml::RefPtr instance(int32_t contextId) { + static fml::RefPtr instance(double contextId) { std::lock_guard guard(inspector_task_creation_mutex_); if (!instance_) { instance_ = fml::MakeRefCounted(); diff --git a/bridge/foundation/isolate_command_buffer.cc b/bridge/foundation/isolate_command_buffer.cc index 160970f..8a08614 100644 --- a/bridge/foundation/isolate_command_buffer.cc +++ b/bridge/foundation/isolate_command_buffer.cc @@ -11,6 +11,20 @@ namespace mercury { +IsolateCommandKind GetKindFromIsolateCommand(IsolateCommand command) { + switch (command) { + case IsolateCommand::kCreateGlobal: + case IsolateCommand::kAddEvent: + case IsolateCommand::kRemoveEvent: + return IsolateCommandKind::kEvent; + case IsolateCommand::kDisposeBindingObject: + return IsolateCommandKind::kDisposeBindingObject; + // prof: case IsolateCommand::kStartRecordingCommand: + // prof: case IsolateCommand::kFinishRecordingCommand: + // prof: return IsolateCommandKind::kOperation; + } +} + IsolateCommandBuffer::IsolateCommandBuffer(ExecutingContext* context) : context_(context), buffer_((IsolateCommandItem*)malloc(sizeof(IsolateCommandItem) * MAXIMUM_ISOLATE_COMMAND_SIZE)) {} @@ -18,15 +32,21 @@ IsolateCommandBuffer::~IsolateCommandBuffer() { free(buffer_); } -void IsolateCommandBuffer::addCommand(IsolateCommand type, +void IsolateCommandBuffer::addCommand(IsolateCommand command, std::unique_ptr&& args_01, void* nativePtr, void* nativePtr2, bool request_isolate_update) { - IsolateCommandItem item{static_cast(type), args_01.get(), nativePtr, nativePtr2}; + IsolateCommandItem item{static_cast(command), args_01.get(), nativePtr, nativePtr2}; + updateFlags(command); addCommand(item, request_isolate_update); } +void IsolateCommandBuffer::updateFlags(IsolateCommand command) { + IsolateCommandKind type = GetKindFromIsolateCommand(command); + kind_flag = kind_flag | type; +} + void IsolateCommandBuffer::addCommand(const IsolateCommandItem& item, bool request_isolate_update) { if (UNLIKELY(!context_->dartIsolateContext()->valid())) { return; @@ -38,9 +58,8 @@ void IsolateCommandBuffer::addCommand(const IsolateCommandItem& item, bool reque } #if FLUTTER_BACKEND - if (UNLIKELY(request_isolate_update && !update_batched_ && context_->IsContextValid() && - context_->dartMethodPtr()->requestBatchUpdate != nullptr)) { - context_->dartMethodPtr()->requestBatchUpdate(context_->contextId()); + if (UNLIKELY(request_isolate_update && !update_batched_ && context_->IsContextValid())) { + context_->dartMethodPtr()->requestBatchUpdate(context_->isDedicated(), context_->contextId()); update_batched_ = true; } #endif @@ -49,10 +68,36 @@ void IsolateCommandBuffer::addCommand(const IsolateCommandItem& item, bool reque size_++; } +void IsolateCommandBuffer::addCommands(const mercury::IsolateCommandItem* items, int64_t item_size, bool request_isolate_update) { + if (UNLIKELY(!context_->dartIsolateContext()->valid())) { + return; + } + + int64_t target_size = size_ + item_size; + if (target_size > max_size_) { + buffer_ = (IsolateCommandItem*)realloc(buffer_, sizeof(IsolateCommandItem) * target_size * 2); + max_size_ = target_size * 2; + } + +#if FLUTTER_BACKEND + if (UNLIKELY(request_isolate_update && !update_batched_ && context_->IsContextValid())) { + context_->dartMethodPtr()->requestBatchUpdate(context_->isDedicated(), context_->contextId()); + update_batched_ = true; + } +#endif + + std::memcpy(buffer_ + size_, items, sizeof(IsolateCommandItem) * item_size); + size_ = target_size; +} + IsolateCommandItem* IsolateCommandBuffer::data() { return buffer_; } +uint32_t IsolateCommandBuffer::kindFlag() { + return kind_flag; +} + int64_t IsolateCommandBuffer::size() { return size_; } @@ -62,8 +107,9 @@ bool IsolateCommandBuffer::empty() { } void IsolateCommandBuffer::clear() { + memset(buffer_, 0, sizeof(IsolateCommandItem) * size_); size_ = 0; - memset(buffer_, 0, sizeof(buffer_)); + kind_flag = 0; update_batched_ = false; } diff --git a/bridge/foundation/isolate_command_buffer.h b/bridge/foundation/isolate_command_buffer.h index f37ad81..66a6316 100644 --- a/bridge/foundation/isolate_command_buffer.h +++ b/bridge/foundation/isolate_command_buffer.h @@ -14,12 +14,20 @@ namespace mercury { class ExecutingContext; +enum UICommandKind : uint32_t { + kEvent = 1, + kDisposeBindingObject = 1 << 2, + // prof: kOperation = 1 << 3 +}; + enum class IsolateCommand { + // prof: kStartRecordingCommand, kCreateGlobal, kCreateEventTarget, kDisposeBindingObject, kAddEvent, kRemoveEvent, + // prof: kFinishRecordingCommand, }; #define MAXIMUM_ISOLATE_COMMAND_SIZE 2048 @@ -39,6 +47,8 @@ struct IsolateCommandItem { int64_t nativePtr2{0}; }; +UICommandKind GetKindFromIsolateCommand(IsolateCommand type); + class IsolateCommandBuffer { public: IsolateCommandBuffer() = delete; @@ -50,20 +60,24 @@ class IsolateCommandBuffer { void* nativePtr2, bool request_isolate_update = true); IsolateCommandItem* data(); + uint32_t kindFlag(); int64_t size(); bool empty(); void clear(); private: void addCommand(const IsolateCommandItem& item, bool request_isolate_update = true); + void addCommands(const IsolateCommandItem* items, int64_t item_size, bool request_isolate_update = true); + void updateFlags(IsolateCommand command); ExecutingContext* context_{nullptr}; IsolateCommandItem* buffer_{nullptr}; bool update_batched_{false}; int64_t size_{0}; int64_t max_size_{MAXIMUM_ISOLATE_COMMAND_SIZE}; + friend class SharedIsolateCommand; }; } // namespace mercury -#endif // BRIDGE_FOUNDATION_Isolate_COMMAND_BUFFER_H_ +#endif // BRIDGE_FOUNDATION_ISOLATE_COMMAND_BUFFER_H_ diff --git a/bridge/foundation/isolate_command_strategy.cc b/bridge/foundation/isolate_command_strategy.cc new file mode 100644 index 0000000..dd0f6f9 --- /dev/null +++ b/bridge/foundation/isolate_command_strategy.cc @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "isolate_command_strategy.h" +#include +#include "core/binding_object.h" +#include "logging.h" +#include "shared_isolate_command.h" + +namespace mercury { + +static uint64_t set_nth_bit_to_zero(uint64_t source, size_t nth) { + uint64_t bitmask = ~(1ULL << nth); + return source & bitmask; +} + +uint64_t WaitingStatus::MaxSize() { + return 64 * storage.size(); +} + +void WaitingStatus::Reset() { + for (auto& i : storage) { + i = IsolateNT64_MAX; + } +} + +bool WaitingStatus::IsFullActive() { + return std::all_of(storage.begin(), storage.end(), [](uint64_t i) { return i == 0; }); +} + +void WaitingStatus::SetActiveAtIndex(uint64_t index) { + size_t storage_index = floor(index / 64); + + if (storage_index < storage.size()) { + storage[storage_index] = set_nth_bit_to_zero(storage[storage_index], index % 64); + } +} + +IsolateCommandSyncStrategy::IsolateCommandSyncStrategy(SharedIsolateCommand* host) : host_(host) {} + +bool IsolateCommandSyncStrategy::ShouldSync() { + return should_sync; +} + +void IsolateCommandSyncStrategy::Reset() { + should_sync = false; + waiting_status.Reset(); + frequency_map_.clear(); +} +void IsolateCommandSyncStrategy::RecordIsolateCommand(IsolateCommand type, + std::unique_ptr& args_01, + NativeBindingObject* native_binding_object, + void* native_ptr2, + bool request_isolate_update) { + switch (type) { + // prof: case IsolateCommand::kStartRecordingCommand: + case IsolateCommand::kCreateGlobal: + case IsolateCommand::kRemoveEvent: + case IsolateCommand::kAddEvent: + case IsolateCommand::kDisposeBindingObject: { + host_->waiting_buffer_->addCommand(type, std::move(args_01), native_binding_object, native_ptr2, + request_isolate_update); + break; + } + // prof: case IsolateCommand::kFinishRecordingCommand: + // prof: break; + } +} + +void IsolateCommandSyncStrategy::ConfigWaitingBufferSize(size_t size) { + waiting_status.storage.reserve(size); + for (int i = 0; i < size; i++) { + waiting_status.storage.emplace_back(IsolateNT64_MAX); + } +} + +void IsolateCommandSyncStrategy::SyncToReserve() { + host_->SyncToReserve(); + waiting_status.Reset(); + frequency_map_.clear(); + should_sync = true; +} + +void IsolateCommandSyncStrategy::SyncToReserveIfNecessary() { + if (frequency_map_.size() > waiting_status.MaxSize() && waiting_status.IsFullActive()) { + SyncToReserve(); + } +} + +void IsolateCommandSyncStrategy::RecordOperationForPointer(NativeBindingObject* ptr) { + size_t index; + if (frequency_map_.count(ptr) == 0) { + index = frequency_map_.size(); + + // Store the bit wise index for ptr. + frequency_map_[ptr] = index; + } else { + index = frequency_map_[ptr]; + } + + // Update flag's nth bit wise to 0 + waiting_status.SetActiveAtIndex(index); + + SyncToReserveIfNecessary(); +} + +} // namespace mercury diff --git a/bridge/foundation/isolate_command_strategy.h b/bridge/foundation/isolate_command_strategy.h new file mode 100644 index 0000000..5939d89 --- /dev/null +++ b/bridge/foundation/isolate_command_strategy.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef MULTI_THREADING_ISOLATE_COMMAND_STRATEGY_H +#define MULTI_THREADING_ISOLATE_COMMAND_STRATEGY_H + +#include +#include +#include "foundation/isolate_command_buffer.h" + +namespace mercury { + +class SharedIsolateCommand; +struct SharedNativeString; +struct NativeBindingObject; + +struct WaitingStatus { + std::vector storage; + uint64_t MaxSize(); + void Reset(); + bool IsFullActive(); + void SetActiveAtIndex(uint64_t index); +}; + +class IsolateCommandSyncStrategy { + public: + IsolateCommandSyncStrategy(SharedIsolateCommand* shared_isolate_command); + + bool ShouldSync(); + void Reset(); + void RecordIsolateCommand(IsolateCommand type, + std::unique_ptr& args_01, + NativeBindingObject* native_ptr, + void* native_ptr2, + bool request_isolate_update); + void ConfigWaitingBufferSize(size_t size); + + private: + void SyncToReserve(); + void SyncToReserveIfNecessary(); + void RecordOperationForPointer(NativeBindingObject* ptr); + + bool should_sync{false}; + SharedIsolateCommand* host_; + WaitingStatus waiting_status; + size_t sync_buffer_size_; + std::unordered_map frequency_map_; + friend class SharedIsolateCommand; +}; + +} // namespace mercury + +#endif // MULTI_THREADING_ISOLATE_COMMAND_STRATEGY_H diff --git a/bridge/foundation/logging.cc b/bridge/foundation/logging.cc index b5e016e..e6c3bca 100644 --- a/bridge/foundation/logging.cc +++ b/bridge/foundation/logging.cc @@ -126,9 +126,8 @@ void printLog(ExecutingContext* context, std::stringstream& stream, std::string mercury::MercuryIsolate::consoleMessageHandler(ctx, stream.str(), static_cast(_log_level)); } - if (context->dartMethodPtr()->onJsLog != nullptr) { - context->dartMethodPtr()->onJsLog(context->contextId(), static_cast(_log_level), stream.str().c_str()); - } + context->dartMethodPtr()->onJSLog(context->isDedicated(), context->contextId(), static_cast(_log_level), + stream.str().c_str()); } } // namespace mercury diff --git a/bridge/foundation/native_value.cc b/bridge/foundation/native_value.cc index 76afa0b..b1ca8c8 100644 --- a/bridge/foundation/native_value.cc +++ b/bridge/foundation/native_value.cc @@ -142,4 +142,9 @@ NativeValue Native_NewJSON(JSContext* ctx, const ScriptValue& value, ExceptionSt #endif } +JSPointerType GetPointerTypeOfNativePointer(NativeValue native_value) { + assert(native_value.tag == NativeTag::TAG_POINTER); + return static_cast(native_value.uint32); +} + } // namespace mercury diff --git a/bridge/foundation/native_value.h b/bridge/foundation/native_value.h index 97fa7f9..963597a 100644 --- a/bridge/foundation/native_value.h +++ b/bridge/foundation/native_value.h @@ -78,6 +78,8 @@ NativeValue Native_NewList(uint32_t argc, NativeValue* argv); NativeValue Native_NewPtr(JSPointerType pointerType, void* ptr); NativeValue Native_NewJSON(JSContext* ctx, const ScriptValue& value, ExceptionState& exception_state); +JSPointerType GetPointerTypeOfNativePointer(NativeValue native_value); + } // namespace mercury #endif // BRIDGE_NATIVE_VALUE_H diff --git a/bridge/foundation/native_value_converter.h b/bridge/foundation/native_value_converter.h index 19ad482..19c4696 100644 --- a/bridge/foundation/native_value_converter.h +++ b/bridge/foundation/native_value_converter.h @@ -60,6 +60,10 @@ struct NativeValueConverter : public NativeValueConverterBase : public NativeValueConverterBase : public NativeValueConverterBase< static NativeValue ToNativeValue(ImplType value) { return Native_NewFloat64(value); } static ImplType FromNativeValue(NativeValue value) { + if (value.tag == NativeTag::TAG_NULL) { + return 0.0; + } + assert(value.tag == NativeTag::TAG_FLOAT64); double result; memcpy(&result, reinterpret_cast(&value.u.int64), sizeof(double)); @@ -93,6 +105,10 @@ struct NativeValueConverter : public NativeValueConverterBase(value.u.ptr); return ScriptValue::CreateJsonObject(ctx, str, strlen(str)); @@ -107,10 +123,18 @@ struct NativeValueConverter, std::enable_if_t> { static NativeValue ToNativeValue(T* value) { return Native_NewPtr(JSPointerType::Others, value); } static T* FromNativeValue(NativeValue value) { + if (value.tag == NativeTag::TAG_NULL) { + return nullptr; + } + assert(value.tag == NativeTag::TAG_POINTER); return static_cast(value.u.ptr); } static T* FromNativeValue(JSContext* ctx, NativeValue value) { + if (value.tag == NativeTag::TAG_NULL) { + return nullptr; + } + assert(value.tag == NativeTag::TAG_POINTER); return static_cast(value.u.ptr); } @@ -121,10 +145,18 @@ struct NativeValueConverter, std::enable_if_t> { static NativeValue ToNativeValue(T* value) { return Native_NewPtr(JSPointerType::Others, value); } static T* FromNativeValue(NativeValue value) { + if (value.tag == NativeTag::TAG_NULL) { + return nullptr; + } + assert(value.tag == NativeTag::TAG_POINTER); return static_cast(value.u.ptr); } static T* FromNativeValue(JSContext* ctx, NativeValue value) { + if (value.tag == NativeTag::TAG_NULL) { + return nullptr; + } + assert(value.tag == NativeTag::TAG_POINTER); return static_cast(value.u.ptr); } @@ -186,6 +218,10 @@ struct NativeValueConverter> : public NativeValueConverterBas } static ImplType FromNativeValue(JSContext* ctx, NativeValue native_value) { + if (native_value.tag == NativeTag::TAG_NULL) { + return std::vector(); + } + assert(native_value.tag == NativeTag::TAG_LIST); size_t length = native_value.uint32; auto* arr = static_cast(native_value.u.ptr); diff --git a/bridge/foundation/shared_isolate_command.cc b/bridge/foundation/shared_isolate_command.cc new file mode 100644 index 0000000..b3509cf --- /dev/null +++ b/bridge/foundation/shared_isolate_command.cc @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "shared_isolate_command.h" +#include "core/executing_context.h" +#include "foundation/logging.h" +#include "isolate_command_buffer.h" + +namespace mercury { + +SharedIsolateCommand::SharedIsolateCommand(ExecutingContext* context) + : context_(context), + active_buffer(std::make_unique(context)), + reserve_buffer_(std::make_unique(context)), + waiting_buffer_(std::make_unique(context)), + isolate_command_sync_strategy_(std::make_unique(this)), + is_blocking_writing_(false) {} + +void SharedIsolateCommand::AddCommand(IsolateCommand type, + std::unique_ptr&& args_01, + NativeBindingObject* native_binding_object, + void* nativePtr2, + bool request_isolate_update) { + if (!context_->isDedicated()) { + active_buffer->addCommand(type, std::move(args_01), native_binding_object, nativePtr2, request_isolate_update); + return; + } + + if (type == IsolateCommand::kFinishRecordingCommand || isolate_command_sync_strategy_->ShouldSync()) { + SyncToActive(); + } + + isolate_command_sync_strategy_->RecordIsolateCommand(type, args_01, native_binding_object, nativePtr2, request_isolate_update); +} + +// first called by dart to being read commands. +void* SharedIsolateCommand::data() { + // simply spin wait for the swapBuffers to finish. + while (is_blocking_writing_.load(std::memory_order::memory_order_acquire)) { + } + + return active_buffer->data(); +} + +uint32_t SharedIsolateCommand::kindFlag() { + return active_buffer->kindFlag(); +} + +// second called by dart to get the size of commands. +int64_t SharedIsolateCommand::size() { + return active_buffer->size(); +} + +// third called by dart to clear commands. +void SharedIsolateCommand::clear() { + active_buffer->clear(); +} + +// called by c++ to check if there are commands. +bool SharedIsolateCommand::empty() { + if (context_->isDedicated()) { + return reserve_buffer_->empty() && waiting_buffer_->empty(); + } + + return active_buffer->empty(); +} + +void SharedIsolateCommand::SyncToReserve() { + if (waiting_buffer_->empty()) + return; + + size_t waiting_size = waiting_buffer_->size(); + size_t origin_reserve_size = reserve_buffer_->size(); + + if (reserve_buffer_->empty()) { + swap(reserve_buffer_, waiting_buffer_); + } else { + appendCommand(reserve_buffer_, waiting_buffer_); + } + + assert(waiting_buffer_->empty()); + assert(reserve_buffer_->size() == waiting_size + origin_reserve_size); +} + +void SharedIsolateCommand::ConfigureSyncCommandBufferSize(size_t size) { + isolate_command_sync_strategy_->ConfigWaitingBufferSize(size); +} + +void SharedIsolateCommand::SyncToActive() { + SyncToReserve(); + + assert(waiting_buffer_->empty()); + + if (reserve_buffer_->empty()) + return; + + isolate_command_sync_strategy_->Reset(); + context_->dartMethodPtr()->requestBatchUpdate(context_->isDedicated(), context_->contextId()); + + size_t reserve_size = reserve_buffer_->size(); + size_t origin_active_size = active_buffer->size(); + appendCommand(active_buffer, reserve_buffer_); + assert(reserve_buffer_->empty()); + assert(active_buffer->size() == reserve_size + origin_active_size); +} + +void SharedIsolateCommand::swap(std::unique_ptr& target, std::unique_ptr& original) { + is_blocking_writing_.store(true, std::memory_order::memory_order_release); + std::swap(target, original); + is_blocking_writing_.store(false, std::memory_order::memory_order_release); +} + +void SharedIsolateCommand::appendCommand(std::unique_ptr& target, + std::unique_ptr& original) { + is_blocking_writing_.store(true, std::memory_order::memory_order_release); + + IsolateCommandItem* command_item = original->data(); + target->addCommands(command_item, original->size()); + + original->clear(); + + is_blocking_writing_.store(false, std::memory_order::memory_order_release); +} + +} // namespace mercury diff --git a/bridge/foundation/shared_isolate_command.h b/bridge/foundation/shared_isolate_command.h new file mode 100644 index 0000000..b89ac9e --- /dev/null +++ b/bridge/foundation/shared_isolate_command.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef MULTI_THREADING_ISOLATE_COMMAND_H_ +#define MULTI_THREADING_ISOLATE_COMMAND_H_ + +#include +#include +#include "foundation/native_type.h" +#include "foundation/isolate_command_buffer.h" +#include "foundation/isolate_command_strategy.h" + +namespace mercury { + +struct NativeBindingObject; + +class SharedIsolateCommand : public DartReadable { + public: + SharedIsolateCommand(ExecutingContext* context); + + void AddCommand(IsolateCommand type, + std::unique_ptr&& args_01, + NativeBindingObject* native_binding_object, + void* nativePtr2, + bool request_isolate_update = true); + + void* data(); + uint32_t kindFlag(); + int64_t size(); + bool empty(); + void clear(); + void SyncToActive(); + void SyncToReserve(); + + void ConfigureSyncCommandBufferSize(size_t size); + + private: + void swap(std::unique_ptr& original, std::unique_ptr& target); + void appendCommand(std::unique_ptr& original, std::unique_ptr& target); + std::unique_ptr active_buffer = nullptr; // The isolate commands which accessible from Dart side + std::unique_ptr reserve_buffer_ = nullptr; // The isolate commands which are ready to swap to active. + std::unique_ptr waiting_buffer_ = + nullptr; // The isolate commands which recorded from JS operations and sync to reserve_buffer by once. + std::atomic is_blocking_writing_; + ExecutingContext* context_; + std::unique_ptr isolate_command_sync_strategy_ = nullptr; + friend class IsolateCommandBuffer; + friend class IsolateCommandSyncStrategy; +}; + +} // namespace mercury + +#endif // MULTI_THREADING_ISOLATE_COMMAND_H_ diff --git a/bridge/include/mercury_bridge.h b/bridge/include/mercury_bridge.h index 306535e..2296247 100644 --- a/bridge/include/mercury_bridge.h +++ b/bridge/include/mercury_bridge.h @@ -7,6 +7,7 @@ #define MERCURY_BRIDGE_EXPORT_H #include +#include #include #if defined(_WIN32) @@ -30,41 +31,95 @@ struct MercuryInfo { }; typedef void (*Task)(void*); +typedef std::function DartWork; +typedef void (*AllocateNewIsolateCallback)(Dart_Handle dart_handle, void*); +typedef void (*DisposeIsolateCallback)(Dart_Handle dart_handle); +typedef void (*InvokeModuleEventCallback)(Dart_Handle dart_handle, void*); +typedef void (*EvaluateQuickjsByteCodeCallback)(Dart_Handle dart_handle, int8_t); +typedef void (*DumpQuickjsByteCodeCallback)(Dart_Handle); +typedef void (*ParseHTMLCallback)(Dart_Handle); +typedef void (*EvaluateScriptsCallback)(Dart_Handle dart_handle, int8_t); + +MERCURY_EXPORT_C +void* initDartIsolateContextSync(int64_t dart_port, + uint64_t* dart_methods, + int32_t dart_methods_len, + int8_t enable_profile); + MERCURY_EXPORT_C -void* initDartIsolateContext(uint64_t* dart_methods, int32_t dart_methods_len); +void allocateNewIsolate(double thread_identity, + int32_t sync_buffer_size, + void* dart_isolate_context, + Dart_Handle dart_handle, + AllocateNewIsolateCallback result_callback); + +MERCURY_EXPORT_C +void* allocateNewIsolateSync(uint64_t* dart_methods, int32_t dart_methods_len); MERCURY_EXPORT_C -void* allocateNewMercuryIsolate(void* dart_isolate_context, int32_t target_mercury_isolate_id); +int64_t newMercuryIsolateIdSync(); MERCURY_EXPORT_C -int64_t newMercuryIsolateId(); +void disposeMercuryIsolate(double dedicated_thread, + void* dart_isolate_context, + void* isolate, + Dart_Handle dart_handle, + DisposePageCallback result_callback); MERCURY_EXPORT_C -void disposeMercuryIsolate(void* dart_isolate_context, void* ptr); +void evaluateScripts(void* isolate, + const char* code, + uint64_t code_len, + uint8_t** parsed_bytecodes, + uint64_t* bytecode_len, + const char* bundleFilename, + int32_t start_line, + int64_t profile_id, + Dart_Handle dart_handle, + EvaluateQuickjsByteCodeCallback result_callback); MERCURY_EXPORT_C -int8_t evaluateScripts(void* ptr, - SharedNativeString* code, - uint8_t** parsed_bytecodes, - uint64_t* bytecode_len, - const char* bundleFilename, - int32_t startLine); +void evaluateQuickjsByteCode(void* isolate, + uint8_t* bytes, + int32_t byteLen, + int64_t profile_id, + Dart_Handle dart_handle, + EvaluateQuickjsByteCodeCallback result_callback); + MERCURY_EXPORT_C -int8_t evaluateQuickjsByteCode(void* ptr, uint8_t* bytes, int32_t byteLen); +void dumpQuickjsByteCode(void* isolate, + int64_t profile_id, + const char* code, + int32_t code_len, + uint8_t** parsed_bytecodes, + uint64_t* bytecode_len, + const char* url, + Dart_Handle dart_handle, + DumpQuickjsByteCodeCallback result_callback); MERCURY_EXPORT_C -NativeValue* invokeModuleEvent(void* ptr, - SharedNativeString* module, - const char* eventType, - void* event, - NativeValue* extra); +void invokeModuleEvent(void* isolate, + SharedNativeString* module, + const char* eventType, + void* event, + NativeValue* extra, + Dart_Handle dart_handle, + InvokeModuleEventCallback result_callback); +// prof: WEBF_EXPORT_C +// prof: void collectNativeProfileData(void* ptr, const char** data, uint32_t* len); +// prof: WEBF_EXPORT_C +// prof: void clearNativeProfileData(void* ptr); MERCURY_EXPORT_C MercuryInfo* getMercuryInfo(); MERCURY_EXPORT_C -void* getIsolateCommandItems(void* page); +void* getIsolateCommandItems(void* isolate); +WEBF_EXPORT_C +uint32_t getIsolateCommandKindFlag(void* isolate); MERCURY_EXPORT_C -int64_t getIsolateCommandItemSize(void* page); +int64_t getIsolateCommandItemSize(void* isolate); MERCURY_EXPORT_C -void clearIsolateCommandItems(void* page); +void clearIsolateCommandItems(void* isolate); +WEBF_EXPORT_C int8_t isJSThreadBlocked(void* dart_isolate_context, double context_id); +WEBF_EXPORT_C void executeNativeCallback(DartWork* work_ptr); MERCURY_EXPORT_C void init_dart_dynamic_linking(void* data); MERCURY_EXPORT_C diff --git a/bridge/mercury_bridge.cc b/bridge/mercury_bridge.cc index 5c9d872..c457424 100644 --- a/bridge/mercury_bridge.cc +++ b/bridge/mercury_bridge.cc @@ -13,6 +13,12 @@ #include "foundation/isolate_command_buffer.h" #include "foundation/logging.h" #include "include/mercury_bridge.h" +#include "core/api/api.h" +#include "core/dart_isolate_context.h" +#include "foundation/native_type.h" +#include "include/dart_api.h" +#include "multiple_threading/dispatcher.h" +#include "multiple_threading/task.h" #if defined(_WIN32) #define SYSTEM_NAME "windows" // Windows @@ -37,65 +43,181 @@ #define SYSTEM_NAME "unknown" #endif -static std::atomic unique_page_id{0}; +static std::atomic unique_isolate_id{1}; + +int64_t newIsolateIdSync() { + return unique_isolate_id++; +} + +void* initDartIsolateContextSync(int64_t dart_port, + uint64_t* dart_methods, + int32_t dart_methods_len, + int8_t enable_profile) { + auto dispatcher = std::make_unique(dart_port); + +#if ENABLE_LOG + MERCURY_LOG(INFO) << "[Dispatcher]: initDartIsolateContextSync Call BEGIN"; +#endif + auto* dart_isolate_context = new mercury::DartIsolateContext(dart_methods, dart_methods_len, enable_profile == 1); + dart_isolate_context->SetDispatcher(std::move(dispatcher)); + +#if ENABLE_LOG + MERCURY_LOG(INFO) << "[Dispatcher]: initDartIsolateContextSync Call END"; +#endif -void* initDartIsolateContext(uint64_t* dart_methods, int32_t dart_methods_len) { - void* ptr = new mercury::DartIsolateContext(dart_methods, dart_methods_len); - return ptr; + return dart_isolate_context; } -void* allocateNewMercuryIsolate(void* dart_isolate_context, int32_t target_mercury_isolate_id) { +void* allocateNewIsolateSync(double thread_identity, void* ptr) { +#if ENABLE_LOG + MERCURY_LOG(INFO) << "[Dispatcher]: allocateNewIsolateSync Call BEGIN"; +#endif + auto* dart_isolate_context = (mercury::DartIsolateContext*)ptr; assert(dart_isolate_context != nullptr); - auto mercury_isolate = - std::make_unique((mercury::DartIsolateContext*)dart_isolate_context, - target_mercury_isolate_id, nullptr); - void* ptr = mercury_isolate.get(); - ((mercury::DartIsolateContext*)dart_isolate_context)->AddNewIsolate(std::move(mercury_isolate)); - return ptr; + + // prof: dart_isolate_context->profiler()->StartTrackInitialize(); + + void* result = static_cast(dart_isolate_context)->AddNewIsolateSync(thread_identity); +#if ENABLE_LOG + MERCURY_LOG(INFO) << "[Dispatcher]: allocateNewIsolateSync Call END"; +#endif + + // prof: dart_isolate_context->profiler()->FinishTrackInitialize(); + + return result; +} + +void allocateNewIsolate(double thread_identity, + int32_t sync_buffer_size, + void* ptr, + Dart_Handle dart_handle, + AllocateNewIsolateCallback result_callback) { +#if ENABLE_LOG + MERCURY_LOG(INFO) << "[Dispatcher]: allocateNewIsolate Call BEGIN"; +#endif + auto* dart_isolate_context = (mercury::DartIsolateContext*)ptr; + assert(dart_isolate_context != nullptr); + Dart_PersistentHandle persistent_handle = Dart_NewPersistentHandle_DL(dart_handle); + + static_cast(dart_isolate_context) + ->AddNewIsolate(thread_identity, sync_buffer_size, persistent_handle, result_callback); +#if ENABLE_LOG + MERCURY_LOG(INFO) << "[Dispatcher]: allocateNewIsolate Call END"; +#endif } int64_t newMercuryIsolateId() { - return unique_page_id++; + return unique_isolate_id++; +} + +void disposeMercuryIsolateSync(double thread_identity, void* ptr, void* isolate_) { +#if ENABLE_LOG + MERCURY_LOG(INFO) << "[Dispatcher]: disposeMercuryIsolateSync Call BEGIN"; +#endif + auto* dart_isolate_context = (mercury::DartIsolateContext*)ptr; + ((mercury::DartIsolateContext*)dart_isolate_context) + ->RemoveIsolateSync(thread_identity, static_cast(isolate_)); +#if ENABLE_LOG + MERCURY_LOG(INFO) << "[Dispatcher]: disposeMercuryIsolateSync Call END"; +#endif +} + +void evaluateScripts(void* isolate_, + const char* code, + uint64_t code_len, + uint8_t** parsed_bytecodes, + uint64_t* bytecode_len, + const char* bundleFilename, + int32_t start_line, + int64_t profile_id, + Dart_Handle dart_handle, + EvaluateScriptsCallback result_callback) { +#if ENABLE_LOG + MERCURY_LOG(VERBOSE) << "[Dart] evaluateScriptsWrapper call" << std::endl; +#endif + auto isolate = reinterpret_cast(isolate_); + Dart_PersistentHandle persistent_handle = Dart_NewPersistentHandle_DL(dart_handle); + isolate->executingContext()->dartIsolateContext()->dispatcher()->PostToJs( + isolate->isDedicated(), isolate->contextId(), mercury::evaluateScriptsInternal, isolate_, code, code_len, parsed_bytecodes, + bytecode_len, bundleFilename, start_line, profile_id, persistent_handle, result_callback); +} + +void dumpQuickjsByteCode(void* isolate_, + int64_t profile_id, + const char* code, + int32_t code_len, + uint8_t** parsed_bytecodes, + uint64_t* bytecode_len, + const char* url, + Dart_Handle dart_handle, + DumpQuickjsByteCodeCallback result_callback) { +#if ENABLE_LOG + MERCURY_LOG(VERBOSE) << "[Dart] dumpQuickjsByteCode call" << std::endl; +#endif + + auto isolate = reinterpret_cast(isolate_); + Dart_PersistentHandle persistent_handle = Dart_NewPersistentHandle_DL(dart_handle); + isolate->dartIsolateContext()->dispatcher()->PostToJs( + isolate->isDedicated(), isolate->contextId(), mercury::dumpQuickJsByteCodeInternal, isolate, profile_id, code, code_len, + parsed_bytecodes, bytecode_len, url, persistent_handle, result_callback); } -void disposeMercuryIsolate(void* dart_isolate_context, void* ptr) { - auto* mercury_isolate = reinterpret_cast(ptr); - assert(std::this_thread::get_id() == mercury_isolate->currentThread()); - ((mercury::DartIsolateContext*)dart_isolate_context)->RemoveIsolate(mercury_isolate); +void evaluateQuickjsByteCode(void* isolate_, + uint8_t* bytes, + int32_t byteLen, + int64_t profile_id, + Dart_Handle dart_handle, + EvaluateQuickjsByteCodeCallback result_callback) { +#if ENABLE_LOG + MERCURY_LOG(VERBOSE) << "[Dart] evaluateQuickjsByteCodeWrapper call" << std::endl; +#endif + auto isolate = reinterpret_cast(isolate_); + Dart_PersistentHandle persistent_handle = Dart_NewPersistentHandle_DL(dart_handle); + isolate->dartIsolateContext()->dispatcher()->PostToJs(isolate->isDedicated(), isolate->contextId(), + mercury::evaluateQuickjsByteCodeInternal, isolate_, bytes, byteLen, + profile_id, persistent_handle, result_callback); } -int8_t evaluateScripts(void* ptr, - SharedNativeString* code, - uint8_t** parsed_bytecodes, - uint64_t* bytecode_len, - const char* bundleFilename, - int32_t startLine) { - auto mercury_isolate = reinterpret_cast(ptr); - assert(std::this_thread::get_id() == mercury_isolate->currentThread()); - return mercury_isolate->evaluateScript(reinterpret_cast(code), parsed_bytecodes, bytecode_len, - bundleFilename, startLine) - ? 1 - : 0; +void registerPluginByteCode(uint8_t* bytes, int32_t length, const char* pluginName) { + mercury::ExecutingContext::plugin_byte_code[pluginName] = mercury::NativeByteCode{bytes, length}; } -int8_t evaluateQuickjsByteCode(void* ptr, uint8_t* bytes, int32_t byteLen) { - auto mercury_isolate = reinterpret_cast(ptr); - assert(std::this_thread::get_id() == mercury_isolate->currentThread()); - return mercury_isolate->evaluateByteCode(bytes, byteLen) ? 1 : 0; +void registerPluginCode(const char* code, int32_t length, const char* pluginName) { + mercury::ExecutingContext::plugin_string_code[pluginName] = std::string(code, length); } -NativeValue* invokeModuleEvent(void* ptr, - SharedNativeString* module_name, - const char* eventType, - void* event, - NativeValue* extra) { - auto mercury_isolate = reinterpret_cast(ptr); - assert(std::this_thread::get_id() == mercury_isolate->currentThread()); - auto* result = mercury_isolate->invokeModuleEvent(reinterpret_cast(module_name), eventType, event, - reinterpret_cast(extra)); - return reinterpret_cast(result); +void invokeModuleEvent(void* page_, + SharedNativeString* module, + const char* eventType, + void* event, + NativeValue* extra, + Dart_Handle dart_handle, + InvokeModuleEventCallback result_callback) { + Dart_PersistentHandle persistent_handle = Dart_NewPersistentHandle_DL(dart_handle); + auto page = reinterpret_cast(page_); + auto dart_isolate_context = page->executingContext()->dartIsolateContext(); + auto is_dedicated = page->executingContext()->isDedicated(); + auto context_id = page->contextId(); + dart_isolate_context->dispatcher()->PostToJs(is_dedicated, context_id, mercury::invokeModuleEventInternal, page_, module, + eventType, event, extra, persistent_handle, result_callback); } +// prof: stuff + +// void collectNativeProfileData(void* ptr, const char** data, uint32_t* len) { +// auto* dart_isolate_context = static_cast(ptr); +// std::string result = dart_isolate_context->profiler()->ToJSON(); + +// *data = static_cast(webf::dart_malloc(sizeof(char) * result.size() + 1)); +// memcpy((void*)*data, result.c_str(), sizeof(char) * result.size() + 1); +// *len = result.size(); +// } + +// void clearNativeProfileData(void* ptr) { +// auto* dart_isolate_context = static_cast(ptr); +// dart_isolate_context->profiler()->clear(); +// } + static MercuryInfo* mercuryInfo{nullptr}; MercuryInfo* getMercuryInfo() { @@ -112,26 +234,34 @@ MercuryInfo* getMercuryInfo() { void* getIsolateCommandItems(void* isolate_) { auto isolate = reinterpret_cast(isolate_); - assert(std::this_thread::get_id() == isolate->currentThread()); - return isolate->GetExecutingContext()->isolateCommandBuffer()->data(); + return isolate->executingContext()->isolateCommandBuffer()->data(); +} + +uint32_t getUICommandKindFlag(void* page_) { + auto page = reinterpret_cast(page_); + return page->executingContext()->isolateCommandBuffer()->kindFlag(); } int64_t getIsolateCommandItemSize(void* isolate_) { auto isolate = reinterpret_cast(isolate_); - assert(std::this_thread::get_id() == isolate->currentThread()); - return isolate->GetExecutingContext()->isolateCommandBuffer()->size(); + return isolate->executingContext()->isolateCommandBuffer()->size(); } void clearIsolateCommandItems(void* isolate_) { auto isolate = reinterpret_cast(isolate_); - assert(std::this_thread::get_id() == isolate->currentThread()); - isolate->GetExecutingContext()->isolateCommandBuffer()->clear(); + isolate->executingContext()->isolateCommandBuffer()->clear(); } // Callbacks when dart context object was finalized by Dart GC. static void finalize_dart_context(void* isolate_callback_data, void* peer) { + MERCURY_LOG(VERBOSE) << "[Dispatcher]: BEGIN FINALIZE DART CONTEXT: "; auto* dart_isolate_context = (mercury::DartIsolateContext*)peer; - delete dart_isolate_context; + dart_isolate_context->Dispose([dart_isolate_context]() { + free(dart_isolate_context); +#if ENABLE_LOG + MERCURY_LOG(VERBOSE) << "[Dispatcher]: SUCCESS FINALIZE DART CONTEXT"; +#endif + }); } void init_dart_dynamic_linking(void* data) { @@ -144,3 +274,16 @@ void register_dart_context_finalizer(Dart_Handle dart_handle, void* dart_isolate Dart_NewFinalizableHandle_DL(dart_handle, reinterpret_cast(dart_isolate_context), sizeof(mercury::DartIsolateContext), finalize_dart_context); } + +int8_t isJSThreadBlocked(void* dart_isolate_context_, double context_id) { + auto* dart_isolate_context = static_cast(dart_isolate_context_); + auto thread_group_id = static_cast(context_id); + return dart_isolate_context->dispatcher()->IsThreadBlocked(thread_group_id) ? 1 : 0; +} + +// run in the dart isolate thread +void executeNativeCallback(DartWork* work_ptr) { + auto dart_work = *(work_ptr); + dart_work(false); + delete work_ptr; +} diff --git a/bridge/multiple_threading/dispatcher.cc b/bridge/multiple_threading/dispatcher.cc new file mode 100644 index 0000000..b89f029 --- /dev/null +++ b/bridge/multiple_threading/dispatcher.cc @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "dispatcher.h" + +#include "core/dart_isolate_context.h" +#include "core/mercury_isolate.h" +#include "foundation/logging.h" + +using namespace mercury; + +namespace mercury { + +namespace multi_threading { + +Dispatcher::Dispatcher(Dart_Port dart_port) : dart_port_(dart_port) {} + +Dispatcher::~Dispatcher() {} + +void Dispatcher::AllocateNewJSThread(int32_t js_context_id) { + assert(js_threads_.count(js_context_id) == 0); + js_threads_[js_context_id] = std::make_unique(js_context_id); + js_threads_[js_context_id]->Start(); +} + +bool Dispatcher::IsThreadGroupExist(int32_t js_context_id) { + return js_threads_.count(js_context_id) > 0; +} + +bool Dispatcher::IsThreadBlocked(int32_t js_context_id) { + if (js_threads_.count(js_context_id) == 0) + return false; + + auto& loop = js_threads_[js_context_id]; + return loop->isBlocked(); +} + +void Dispatcher::KillJSThreadSync(int32_t js_context_id) { + assert(js_threads_.count(js_context_id) > 0); + auto& looper = js_threads_[js_context_id]; + PostToJsSync( + true, js_context_id, [](bool cancel, Looper* looper) { looper->ExecuteOpaqueFinalizer(); }, + js_threads_[js_context_id].get()); + looper->Stop(); + js_threads_.erase(js_context_id); +} + +void Dispatcher::SetOpaqueForJSThread(int32_t js_context_id, void* opaque, OpaqueFinalizer finalizer) { + assert(js_threads_.count(js_context_id) > 0); + js_threads_[js_context_id]->SetOpaque(opaque, finalizer); +} + +void* Dispatcher::GetOpaque(int32_t js_context_id) { + assert(js_threads_.count(js_context_id) > 0); + return js_threads_[js_context_id]->opaque(); +} + +void Dispatcher::Dispose(mercury::multi_threading::Callback callback) { +#if ENABLE_LOG + MERCURY_LOG(VERBOSE) << "[Dispatcher]: BEGIN EXE OPAQUE FINALIZER "; +#endif + + for (auto&& thread : js_threads_) { + auto* isolate_group = static_cast(thread.second->opaque()); + for (auto& isolate : (*isolate_group->isolates())) { + isolate->executingContext()->SetContextInValid(); + } + } + + std::set pending_tasks = pending_dart_tasks_; + + for (auto task : pending_tasks) { + const DartWork dart_work = *task; +#if ENABLE_LOG + MERCURY_LOG(VERBOSE) << "[Dispatcher]: BEGIN EXEC SYNC DART WORKER"; +#endif + dart_work(true); +#if ENABLE_LOG + MERCURY_LOG(VERBOSE) << "[Dispatcher]: FINISH EXEC SYNC DART WORKER"; +#endif + } + +#if ENABLE_LOG + MERCURY_LOG(VERBOSE) << "[Dispatcher]: BEGIN FINALIZE ALL JS THREAD"; +#endif + + FinalizeAllJSThreads([this, &callback]() { + StopAllJSThreads(); + callback(); + }); +} + +std::unique_ptr& Dispatcher::looper(int32_t js_context_id) { + assert(js_threads_.count(js_context_id) > 0); + return js_threads_[js_context_id]; +} + +// run in the cpp thread +bool Dispatcher::NotifyDart(const DartWork* work_ptr, bool is_sync) { + const intptr_t work_addr = reinterpret_cast(work_ptr); + + Dart_CObject** array = new Dart_CObject*[3]; + + array[0] = new Dart_CObject(); + array[0]->type = Dart_CObject_Type::Dart_CObject_kInt64; + array[0]->value.as_int64 = is_sync ? 1 : 0; + + array[1] = new Dart_CObject(); + array[1]->type = Dart_CObject_Type::Dart_CObject_kInt64; + array[1]->value.as_int64 = work_addr; + + array[2] = new Dart_CObject(); + array[2]->type = Dart_CObject_Type ::Dart_CObject_kInt64; + size_t thread_id = std::hash{}(std::this_thread::get_id()); + array[2]->value.as_int64 = thread_id; + + Dart_CObject dart_object; + dart_object.type = Dart_CObject_kArray; + dart_object.value.as_array.length = 3; + dart_object.value.as_array.values = array; + +#if ENABLE_LOG + if (is_sync) { + MERCURY_LOG(WARN) << " SYNC BLOCK THREAD " << std::this_thread::get_id() << " FOR A DART CALLBACK TO RECOVER"; + } +#endif + + const bool result = Dart_PostCObject_DL(dart_port_, &dart_object); + if (!result) { + delete work_ptr; + return false; + } + + delete array[0]; + delete array[1]; + delete array[2]; + delete[] array; + return true; +} + +void Dispatcher::FinalizeAllJSThreads(mercury::multi_threading::Callback callback) { + std::atomic unfinished_thread = js_threads_.size(); + + std::atomic is_final_async_dart_task_complete{false}; + + if (unfinished_thread == 0) { + is_final_async_dart_task_complete = true; + } + + for (auto&& thread : js_threads_) { + PostToJs( + true, thread.first, + [&unfinished_thread, &thread, &is_final_async_dart_task_complete](Looper* looper) { +#if ENABLE_LOG + MERCURY_LOG(VERBOSE) << "[Dispatcher]: RUN JS FINALIZER, context_id: " << thread.first; +#endif + looper->ExecuteOpaqueFinalizer(); + unfinished_thread--; + +#if ENABLE_LOG + MERCURY_LOG(VERBOSE) << "[Dispatcher]: UNFINISHED THREAD: " << unfinished_thread; +#endif + if (unfinished_thread == 0) { + is_final_async_dart_task_complete = true; + return; + } + }, + thread.second.get()); +#if ENABLE_LOG + MERCURY_LOG(VERBOSE) << "[Dispatcher]: POST TO JS THREAD"; +#endif + } + +#if ENABLE_LOG + MERCURY_LOG(VERBOSE) << "[Dispatcher]: WAITING FOR JS THREAD COMPLETE"; +#endif + // Waiting for the final dart task return. + while (!is_final_async_dart_task_complete) { + } + +#if ENABLE_LOG + MERCURY_LOG(VERBOSE) << "[Dispatcher]: ALL JS THREAD FINALIZED SUCCESS"; +#endif + callback(); +} + +void Dispatcher::StopAllJSThreads() { +#if ENABLE_LOG + MERCURY_LOG(VERBOSE) << "[Dispatcher]: FINISH EXEC OPAQUE FINALIZER "; +#endif + for (auto&& thread : js_threads_) { + thread.second->Stop(); + } +#if ENABLE_LOG + MERCURY_LOG(VERBOSE) << "[Dispatcher]: ALL THREAD STOPPED"; +#endif +} + +} // namespace multi_threading + +} // namespace mercury diff --git a/bridge/multiple_threading/dispatcher.h b/bridge/multiple_threading/dispatcher.h new file mode 100644 index 0000000..d0e4b04 --- /dev/null +++ b/bridge/multiple_threading/dispatcher.h @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef MULTI_THREADING_DISPATCHER_H +#define MULTI_THREADING_DISPATCHER_H + +#include +#include +#include +#include +#include +#include +#include + +#include "logging.h" +#include "looper.h" +#include "task.h" + +#if defined(_WIN32) +#define MERCURY_EXPORT_C extern "C" __declspec(dllexport) +#define MERCURY_EXPORT __declspec(dllexport) +#else +#define MERCURY_EXPORT_C extern "C" __attribute__((visibility("default"))) __attribute__((used)) +#define MERCURY_EXPORT __attribute__((__visibility__("default"))) +#endif + +namespace mercury { + +namespace multi_threading { + +/** + * @brief thread dispatcher, used to dispatch tasks to dart thread or js thread. + * + */ +class Dispatcher { + public: + explicit Dispatcher(Dart_Port dart_port); + ~Dispatcher(); + + void AllocateNewJSThread(int32_t js_context_id); + bool IsThreadGroupExist(int32_t js_context_id); + bool IsThreadBlocked(int32_t js_context_id); + void KillJSThreadSync(int32_t js_context_id); + void SetOpaqueForJSThread(int32_t js_context_id, void* opaque, OpaqueFinalizer finalizer); + void* GetOpaque(int32_t js_context_id); + void Dispose(Callback callback); + + std::unique_ptr& looper(int32_t js_context_id); + + template + void PostToDart(bool dedicated_thread, Func&& func, Args&&... args) { + if (!dedicated_thread) { + std::invoke(std::forward(func), std::forward(args)...); + return; + } + + auto task = std::make_shared>(std::forward(func), std::forward(args)...); + DartWork work = [task](bool cancel) { (*task)(); }; + + const DartWork* work_ptr = new DartWork(work); + NotifyDart(work_ptr, false); + } + + template + void PostToDartAndCallback(bool dedicated_thread, Func&& func, Callback callback, Args&&... args) { + if (!dedicated_thread) { + std::invoke(std::forward(func), std::forward(args)...); + callback(); + return; + } + + auto task = std::make_shared>( + std::forward(func), std::forward(args)..., std::forward(callback)); + const DartWork work = [task]() { (*task)(); }; + + const DartWork* work_ptr = new DartWork(work); + NotifyDart(work_ptr, false); + } + + template + auto PostToDartSync(bool dedicated_thread, double js_context_id, Func&& func, Args&&... args) + -> std::invoke_result_t { + if (!dedicated_thread) { + return std::invoke(std::forward(func), false, std::forward(args)...); + } + + auto task = + std::make_shared>(std::forward(func), std::forward(args)...); + auto thread_group_id = static_cast(js_context_id); + auto& looper = js_threads_[thread_group_id]; + const DartWork work = [task, &looper](bool cancel) { +#if ENABLE_LOG + MERCURY_LOG(WARN) << " BLOCKED THREAD " << std::this_thread::get_id() << " HAD BEEN RESUMED" + << " is_cancel: " << cancel; +#endif + + looper->is_blocked_ = false; + (*task)(cancel); + }; + + DartWork* work_ptr = new DartWork(work); + pending_dart_tasks_.insert(work_ptr); + + bool success = NotifyDart(work_ptr, true); + if (!success) { + pending_dart_tasks_.erase(work_ptr); + return std::invoke(std::forward(func), true, std::forward(args)...); + } + + looper->is_blocked_ = true; + task->wait(); + pending_dart_tasks_.erase(work_ptr); + + return task->getResult(); + } + + // template + // void PostToDartWithoutResSync(bool dedicated_thread, Func&& func, Args&&... args) { + // if (!dedicated_thread) { + // std::invoke(std::forward(func), std::forward(args)...); + // } + // + // auto task = + // std::make_shared>(std::forward(func), std::forward(args)...); + // const DartWork work = [task]() { (*task)(); }; + // + // const DartWork* work_ptr = new DartWork(work); + // NotifyDart(work_ptr, true); + // + // task->wait(); + // } + + template + void PostToJs(bool dedicated_thread, int32_t js_context_id, Func&& func, Args&&... args) { + if (!dedicated_thread) { + std::invoke(std::forward(func), std::forward(args)...); + return; + } + + assert(js_threads_.count(js_context_id) > 0); + auto& looper = js_threads_[js_context_id]; + looper->PostMessage(std::forward(func), std::forward(args)...); + } + + template + void PostToJsAndCallback(bool dedicated_thread, + int32_t js_context_id, + Func&& func, + Callback&& callback, + Args&&... args) { + if (!dedicated_thread) { + std::invoke(std::forward(func), std::forward(args)...); + callback(); + return; + } + + assert(js_threads_.count(js_context_id) > 0); + auto& looper = js_threads_[js_context_id]; + looper->PostMessageAndCallback(std::forward(func), std::forward(callback), + std::forward(args)...); + } + + template + auto PostToJsSync(bool dedicated_thread, int32_t js_context_id, Func&& func, Args&&... args) + -> std::invoke_result_t { + if (!dedicated_thread) { + return std::invoke(std::forward(func), false, std::forward(args)...); + } + + assert(js_threads_.count(js_context_id) > 0); + auto& looper = js_threads_[js_context_id]; + return looper->PostMessageSync(std::forward(func), std::forward(args)...); + } + + private: + bool NotifyDart(const DartWork* work_ptr, bool is_sync); + + void FinalizeAllJSThreads(Callback callback); + void StopAllJSThreads(); + + private: + Dart_Port dart_port_; + std::unordered_map> js_threads_; + std::set pending_dart_tasks_; + friend Looper; +}; + +} // namespace multi_threading + +} // namespace mercury + +#endif // MULTI_THREADING_DISPATCHER_H diff --git a/bridge/multiple_threading/looper.cc b/bridge/multiple_threading/looper.cc new file mode 100644 index 0000000..e3baf48 --- /dev/null +++ b/bridge/multiple_threading/looper.cc @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "looper.h" +#include + +#include + +#include "logging.h" + +namespace mercury { + +namespace multi_threading { + +static void setThreadName(const std::string& name) { +#if defined(__APPLE__) && defined(__MACH__) // Apple OSX and iOS (Darwin) + pthread_setname_np(name.c_str()); +#elif defined(__ANDROID__) + pthread_setname_np(pthread_self(), name.c_str()); +#endif +} + +Looper::Looper(int32_t js_id) : js_id_(js_id), running_(false), paused_(false) {} + +Looper::~Looper() {} + +void Looper::Start() { + std::lock_guard lock(mutex_); + if (!worker_.joinable()) { + running_ = true; + worker_ = std::thread([this] { + std::string thread_name = "JS Worker " + std::to_string(js_id_); + setThreadName(thread_name.c_str()); + this->Run(); + }); + } +} + +void Looper::Stop() { + { + std::lock_guard lock(mutex_); + running_ = false; + } + cv_.notify_one(); + if (worker_.joinable()) { + worker_.join(); + } +} + +// private methods +void Looper::Run() { + while (true) { + std::shared_ptr task = nullptr; + { + std::unique_lock lock(mutex_); + cv_.wait(lock, [this] { return !running_ || (!tasks_.empty() && !paused_); }); + + if (!running_) { + return; + } + + if (!paused_ && !tasks_.empty()) { + task = std::move(tasks_.front()); + tasks_.pop(); + } + } + if (task != nullptr && running_) { + (*task)(false); + } + } +} + +void Looper::SetOpaque(void* p, OpaqueFinalizer finalizer) { + opaque_ = p; + opaque_finalizer_ = finalizer; +} + +bool Looper::isBlocked() { + return is_blocked_; +} + +void* Looper::opaque() { + return opaque_; +} + +void Looper::ExecuteOpaqueFinalizer() { + opaque_finalizer_(opaque_); +} + +} // namespace multi_threading + +} // namespace mercury diff --git a/bridge/multiple_threading/looper.h b/bridge/multiple_threading/looper.h new file mode 100644 index 0000000..4062e0f --- /dev/null +++ b/bridge/multiple_threading/looper.h @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef MULTI_THREADING_LOOPER_H_ +#define MULTI_THREADING_LOOPER_H_ + +#include +#include +#include +#include +#include + +#include "foundation/logging.h" +#include "task.h" + +namespace mercury { + +namespace multi_threading { + +typedef void (*OpaqueFinalizer)(void* p); + +class Dispatcher; + +/** + * @brief thread looper, used to Run tasks in a thread. + * + */ +class Looper { + public: + Looper(int32_t js_id); + ~Looper(); + + void Start(); + + template + void PostMessage(Func&& func, Args&&... args) { + auto task = std::make_shared>(std::forward(func), std::forward(args)...); + { + std::unique_lock lock(mutex_); + tasks_.emplace(std::move(task)); + } + cv_.notify_one(); + } + + template + void PostMessageAndCallback(Func&& func, Callback&& callback, Args&&... args) { + auto task = std::make_shared>( + std::forward(func), std::forward(args)..., std::forward(callback)); + { + std::unique_lock lock(mutex_); + tasks_.emplace(std::move(task)); + } + cv_.notify_one(); + } + + template + auto PostMessageSync(Func&& func, Args&&... args) -> std::invoke_result_t { + auto task = + std::make_shared>(std::forward(func), std::forward(args)...); + auto task_copy = task; + { + std::unique_lock lock(mutex_); + tasks_.emplace(std::move(task)); + } + cv_.notify_one(); + task_copy->wait(); + + return task_copy->getResult(); + } + + void Stop(); + + void SetOpaque(void* p, OpaqueFinalizer finalizer); + void* opaque(); + + bool isBlocked(); + + void ExecuteOpaqueFinalizer(); + + private: + void Run(); + + std::condition_variable cv_; + std::mutex mutex_; + std::queue> tasks_; + std::thread worker_; + bool paused_; + bool running_; + void* opaque_; + OpaqueFinalizer opaque_finalizer_; + int32_t js_id_; + std::atomic is_blocked_; + friend Dispatcher; +}; + +} // namespace multi_threading + +} // namespace mercury + +#endif // MULTI_THREADING_LOOPER_H_ diff --git a/bridge/multiple_threading/task.h b/bridge/multiple_threading/task.h new file mode 100644 index 0000000..f38180a --- /dev/null +++ b/bridge/multiple_threading/task.h @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef MULTI_THREADING_TASK_H +#define MULTI_THREADING_TASK_H + +#include +#include +#include +#include + +#include "foundation/logging.h" + +namespace mercury { + +namespace multi_threading { + +using Callback = std::function; + +class Task { + public: + virtual ~Task() = default; + virtual void operator()(bool cancel = false) = 0; +}; + +template +class ConcreteTask : public Task { + public: + ConcreteTask(Func&& f, Args&&... args) : func_(std::bind(std::forward(f), std::forward(args)...)) {} + + void operator()(bool cancel = false) override { + if (func_) { + func_(); + } + } + + private: + std::function func_; +}; + +template +class ConcreteCallbackTask : public Task { + public: + ConcreteCallbackTask(Func&& f, Args&&... args, Callback&& callback) + : func_(std::bind(std::forward(f), std::forward(args)...)), + callback_(std::forward(callback)) {} + + void operator()(bool cancel = false) override { + if (func_) { + func_(); + } + if (callback_) { + callback_(); + } + } + + private: + std::function func_; + Callback callback_; +}; + +class SyncTask : public Task { + public: + virtual ~SyncTask() = default; + virtual void wait() = 0; +}; + +template +class ConcreteSyncTask : public SyncTask { + public: + using ReturnType = std::invoke_result_t; + + ConcreteSyncTask(Func&& func, Args&&... args) + : task_(std::bind(std::forward(func), std::placeholders::_1, std::forward(args)...)), + future_(task_.get_future()) {} + + void operator()(bool cancel = false) override { +#if ENABLE_LOG + MERCURY_LOG(VERBOSE) << "[ConcreteSyncTask]: CALL SYNC CONCRETE TASK"; +#endif + task_(cancel); + } + + void wait() override { +#ifdef DDEBUG + future_.wait(); +#else + auto status = future_.wait_for(std::chrono::milliseconds(2000)); + if (status == std::future_status::timeout) { + MERCURY_LOG(ERROR) << "SyncTask wait timeout" << std::endl; + } +#endif + } + + ReturnType getResult() { return future_.get(); } + + private: + std::packaged_task task_; + std::future future_; +}; + +} // namespace multi_threading + +} // namespace mercury + +#endif // MULTI_THREADING_TASK_H diff --git a/bridge/polyfill/src/abort-signal.ts b/bridge/polyfill/src/abort-signal.ts new file mode 100644 index 0000000..3419fbb --- /dev/null +++ b/bridge/polyfill/src/abort-signal.ts @@ -0,0 +1,53 @@ +const SECRET = {}; + +// @ts-ignore +export class _AbortSignal extends EventTarget { + public _aborted: boolean; + + get aborted() { + return this._aborted; + } + + constructor(secret: any) { + super(); + if (secret !== SECRET) { + throw new TypeError("Illegal constructor."); + } + this._aborted = false; + } + + private _onabort: any; + get onabort() { + return this._aborted; + } + set onabort(callback: any) { + const existing = this._onabort; + if (existing) { + this.removeEventListener("abort", existing); + } + this._onabort = callback; + this.addEventListener("abort", callback); + } + +} + +export class _AbortController { + public _signal: _AbortSignal; + constructor() { + this._signal = new _AbortSignal(SECRET); + } + + get signal() { + return this._signal; + } + + abort() { + const signal = this.signal; + if (!signal.aborted) { + signal._aborted = true; + signal.dispatchEvent(new Event("abort")); + } + } +} + + diff --git a/bridge/polyfill/src/fetch.ts b/bridge/polyfill/src/fetch.ts index b6d41c4..7e8a677 100644 --- a/bridge/polyfill/src/fetch.ts +++ b/bridge/polyfill/src/fetch.ts @@ -205,6 +205,13 @@ export class Request extends Body { } this.method = normalizeMethod(init.method || this.method || 'GET'); this.mode = init.mode || this.mode || null; + this.signal = init.signal || this.signal || (function () { + if ('AbortController' in window) { + let ctrl = new AbortController(); + return ctrl.signal; + } + return undefined; + }()); if ((this.method === 'GET' || this.method === 'HEAD') && body) { throw new TypeError('Body not allowed for GET or HEAD requests') @@ -223,7 +230,7 @@ export class Request extends Body { // readonly redirect: RequestRedirect; // not supported // readonly referrer: string; // not supported // readonly referrerPolicy: ReferrerPolicy; - // readonly signal: AbortSignal; // not supported + readonly signal: AbortSignal; readonly url: string; readonly method: string; @@ -294,18 +301,22 @@ export class Response extends Body { export function fetch(input: Request | string, init?: RequestInit) { return new Promise((resolve, reject) => { - let url = typeof input === 'string' ? input : input.url; - init = init || {method: 'GET'}; - let headers = init.headers || new Headers(); + let request = new Request(input, init); + + if (request.signal && request.signal.aborted) { + return reject(new DOMException('Aborted', 'AbortError')) + } + let headers = request.headers || new Headers(); - if (!(headers instanceof Headers)) { - headers = new Headers(headers); + function abortRequest() { + webf.invokeModule('Fetch', 'abortRequest'); } - mercury.invokeModule('Fetch', url, ({ + webf.invokeModule('Fetch', request.url, ({ ...init, headers: (headers as Headers).map }), (e, data) => { + request.signal.removeEventListener('abort', abortRequest); if (e) return reject(e); let [err, statusCode, body] = data; // network error didn't have statusCode @@ -318,10 +329,14 @@ export function fetch(input: Request | string, init?: RequestInit) { status: statusCode }); - res.url = url; + res.url = request.url; return resolve(res); }); + + if (request.signal) { + request.signal.addEventListener('abort', abortRequest) + } } ); } diff --git a/bridge/polyfill/src/index.ts b/bridge/polyfill/src/index.ts index 2ebb633..6c24981 100644 --- a/bridge/polyfill/src/index.ts +++ b/bridge/polyfill/src/index.ts @@ -10,6 +10,7 @@ import { URLSearchParams } from './url-search-params'; import { URL } from './url'; import { mercury } from './mercury'; import { WebSocket } from './websocket' +import { _AbortController, _AbortSignal } from './abort-signal'; defineGlobalProperty('console', console); defineGlobalProperty('Request', Request); @@ -21,6 +22,8 @@ defineGlobalProperty('URLSearchParams', URLSearchParams); defineGlobalProperty('URL', URL); defineGlobalProperty('mercury', mercury); defineGlobalProperty('WebSocket', WebSocket); +defineGlobalProperty('AbortSignal', _AbortSignal); +defineGlobalProperty('AbortController', _AbortController); function defineGlobalProperty(key: string, value: any, isEnumerable: boolean = true) { Object.defineProperty(globalThis, key, { diff --git a/bridge/scripts/code_generator/src/idl/analyzer.ts b/bridge/scripts/code_generator/src/idl/analyzer.ts index 2d83c6d..68ab7f5 100644 --- a/bridge/scripts/code_generator/src/idl/analyzer.ts +++ b/bridge/scripts/code_generator/src/idl/analyzer.ts @@ -127,7 +127,8 @@ function getParameterBaseType(type: ts.TypeNode, mode?: ParameterMode): Paramete return argument.typeName.text; } else if (identifier === 'DartImpl') { if (mode) mode.dartImpl = true; - let argument = typeReference.typeArguments![0]; + let argument: ts.TypeNode = typeReference.typeArguments![0] as unknown as ts.TypeNode; + // @ts-ignore return getParameterBaseType(argument); } else if (identifier === 'StaticMember') { diff --git a/bridge/scripts/code_generator/src/idl/generateSource.ts b/bridge/scripts/code_generator/src/idl/generateSource.ts index 2d4d7b4..2e085ad 100644 --- a/bridge/scripts/code_generator/src/idl/generateSource.ts +++ b/bridge/scripts/code_generator/src/idl/generateSource.ts @@ -350,7 +350,7 @@ auto* self = toScriptWrappable<${getClassName(blob)}>(JS_IsUndefined(this_val) ? ${nativeArguments.length > 0 ? `NativeValue arguments[] = { ${nativeArguments.join(',\n')} }` : 'NativeValue* arguments = nullptr;'}; -${returnValueAssignment}self->InvokeBindingMethod(binding_call_methods::k${declare.name}, ${nativeArguments.length}, arguments, exception_state); +${returnValueAssignment}self->InvokeBindingMethod(binding_call_methods::k${declare.name}, ${nativeArguments.length}, arguments, FlushUICommandReason::kDependentsOnObject, exception_state); ${returnValueAssignment.length > 0 ? `return Converter<${generateIDLTypeConverter(declare.returnType)}>::ToValue(NativeValueConverter<${generateNativeValueTypeConverter(declare.returnType)}>::FromNativeValue(native_value))` : ''}; `.trim(); } @@ -724,4 +724,4 @@ export function generateUnionTypeSource(unionType: ParameterType): string { }).split('\n').filter(str => { return str.trim().length > 0; }).join('\n'); -} \ No newline at end of file +} diff --git a/bridge/scripts/code_generator/templates/idl_templates/interface.cc.tpl b/bridge/scripts/code_generator/templates/idl_templates/interface.cc.tpl index 25cc20e..ff5b940 100644 --- a/bridge/scripts/code_generator/templates/idl_templates/interface.cc.tpl +++ b/bridge/scripts/code_generator/templates/idl_templates/interface.cc.tpl @@ -13,11 +13,14 @@ JSValue QJS<%= className %>::ConstructorCallback(JSContext* ctx, JSValue func_ob auto* self = toScriptWrappable<<%= className %>>(obj); ExceptionState exception_state; ExecutingContext* context = ExecutingContext::From(ctx); + if (!context->IsContextValid()) return false; + // prof: context->dartIsolateContext()->profiler()->StartTrackSteps("QJS<%= className %>::PropertyCheckerCallback"); auto* wrapper_type_info = <%= className %>::GetStaticWrapperTypeInfo(); MemberMutationScope scope{context}; JSValue prototype = context->contextData()->prototypeForType(wrapper_type_info); if (JS_HasProperty(ctx, prototype, key)) return true; bool result = self->NamedPropertyQuery(AtomicString(ctx, key), exception_state); + // prof: context->dartIsolateContext()->profiler()->FinishTrackSteps(); if (UNLIKELY(exception_state.HasException())) { return false; } @@ -26,7 +29,10 @@ JSValue QJS<%= className %>::ConstructorCallback(JSContext* ctx, JSValue func_ob int QJS<%= className %>::PropertyEnumerateCallback(JSContext* ctx, JSPropertyEnum** ptab, uint32_t* plen, JSValue obj) { auto* self = toScriptWrappable<<%= className %>>(obj); ExceptionState exception_state; - MemberMutationScope scope{ExecutingContext::From(ctx)}; + ExecutingContext* context = ExecutingContext::From(ctx); + if (!context->IsContextValid()) return 0; + MemberMutationScope scope{context}; + // prof: context->dartIsolateContext()->profiler()->StartTrackSteps("QJS<%= className %>::PropertyEnumerateCallback"); std::vector props; self->NamedPropertyEnumerator(props, exception_state); auto size = props.size() == 0 ? 1 : props.size(); @@ -38,18 +44,23 @@ JSValue QJS<%= className %>::ConstructorCallback(JSContext* ctx, JSValue func_ob *plen = props.size(); *ptab = tabs; + // prof: context->dartIsolateContext()->profiler()->FinishTrackSteps(); return 0; } <% if (object.indexedProp.indexKeyType == 'number') { %> JSValue QJS<%= className %>::IndexedPropertyGetterCallback(JSContext* ctx, JSValue obj, uint32_t index) { ExceptionState exception_state; - MemberMutationScope scope{ExecutingContext::From(ctx)}; + ExecutingContext* context = ExecutingContext::From(ctx); + if (!context->IsContextValid()) return JS_NULL; + MemberMutationScope scope{context}; auto* self = toScriptWrappable<<%= className %>>(obj); if (index >= self->length()) { return JS_UNDEFINED; } + // prof: context->dartIsolateContext()->profiler()->StartTrackSteps("QJS<%= className %>::IndexedPropertyGetterCallback"); <%= generateCoreTypeValue(object.indexedProp.type) %> result = self->item(index, exception_state); + // prof: context->dartIsolateContext()->profiler()->FinishTrackSteps(); if (UNLIKELY(exception_state.HasException())) { return exception_state.ToQuickJS(); } @@ -60,8 +71,12 @@ JSValue QJS<%= className %>::ConstructorCallback(JSContext* ctx, JSValue func_ob JSValue QJS<%= className %>::StringPropertyGetterCallback(JSContext* ctx, JSValue obj, JSAtom key) { auto* self = toScriptWrappable<<%= className %>>(obj); ExceptionState exception_state; - MemberMutationScope scope{ExecutingContext::From(ctx)}; + ExecutingContext* context = ExecutingContext::From(ctx); + if (!context->IsContextValid()) return JS_NULL; + // prof: context->dartIsolateContext()->profiler()->StartTrackSteps("QJS<%= className %>::StringPropertyGetterCallback"); + MemberMutationScope scope{context}; ${generateCoreTypeValue(object.indexedProp.type)} result = self->item(AtomicString(ctx, key), exception_state); + // prof: context->dartIsolateContext()->profiler()->FinishTrackSteps(); if (UNLIKELY(exception_state.HasException())) { return exception_state.ToQuickJS(); } @@ -73,12 +88,16 @@ JSValue QJS<%= className %>::ConstructorCallback(JSContext* ctx, JSValue func_ob bool QJS<%= className %>::IndexedPropertySetterCallback(JSContext* ctx, JSValueConst obj, uint32_t index, JSValueConst value) { auto* self = toScriptWrappable<<%= className %>>(obj); ExceptionState exception_state; - MemberMutationScope scope{ExecutingContext::From(ctx)}; + ExecutingContext* context = ExecutingContext::From(ctx); + if (!context->IsContextValid()) return false; + MemberMutationScope scope{context}; auto&& v = Converter<<%= generateIDLTypeConverter(object.indexedProp.type, object.indexedProp.optional) %>>::FromValue(ctx, value, exception_state); if (UNLIKELY(exception_state.HasException())) { return false; } + // prof: context->dartIsolateContext()->profiler()->StartTrackSteps("QJS<%= className %>::IndexedPropertySetterCallback"); bool success = self->SetItem(index, v, exception_state); + // prof: context->dartIsolateContext()->profiler()->FinishTrackSteps(); if (UNLIKELY(exception_state.HasException())) { return false; } @@ -88,12 +107,16 @@ JSValue QJS<%= className %>::ConstructorCallback(JSContext* ctx, JSValue func_ob bool QJS<%= className %>::StringPropertySetterCallback(JSContext* ctx, JSValueConst obj, JSAtom key, JSValueConst value) { auto* self = toScriptWrappable<<%= className %>>(obj); ExceptionState exception_state; - MemberMutationScope scope{ExecutingContext::From(ctx)}; + ExecutingContext* context = ExecutingContext::From(ctx); + if (!context->IsContextValid()) return false; + MemberMutationScope scope{context}; auto&& v = Converter<<%= generateIDLTypeConverter(object.indexedProp.type, object.indexedProp.optional) %>>::FromValue(ctx, value, exception_state); if (UNLIKELY(exception_state.HasException())) { return false; } + // prof: context->dartIsolateContext()->profiler()->StartTrackSteps("QJS<%= className %>::StringPropertySetterCallback"); bool success = self->SetItem(AtomicString(ctx, key), v, exception_state); + // prof: context->dartIsolateContext()->profiler()->FinishTrackSteps(); if (UNLIKELY(exception_state.HasException())) { return false; } @@ -103,11 +126,15 @@ JSValue QJS<%= className %>::ConstructorCallback(JSContext* ctx, JSValue func_ob bool QJS<%= className %>::StringPropertyDeleterCallback(JSContext* ctx, JSValueConst obj, JSAtom key) { auto* self = toScriptWrappable<<%= className %>>(obj); ExceptionState exception_state; - MemberMutationScope scope{ExecutingContext::From(ctx)}; + ExecutingContext* context = ExecutingContext::From(ctx); + if (!context->IsContextValid()) return false; + MemberMutationScope scope{context}; if (UNLIKELY(exception_state.HasException())) { return false; } + // prof: context->dartIsolateContext()->profiler()->StartTrackSteps("QJS<%= className %>::StringPropertyDeleterCallback"); bool success = self->DeleteItem(AtomicString(ctx, key), exception_state); + // prof: context->dartIsolateContext()->profiler()->FinishTrackSteps(); if (UNLIKELY(exception_state.HasException())) { return false; } @@ -154,7 +181,11 @@ static JSValue <%= prop.name %>AttributeGetCallback(JSContext* ctx, JSValueConst auto* <%= blob.filename %> = toScriptWrappable<<%= className %>>(this_val); assert(<%= blob.filename %> != nullptr); - MemberMutationScope scope{ExecutingContext::From(ctx)}; + ExecutingContext* context = ExecutingContext::From(ctx); + if (!context->IsContextValid()) return JS_NULL; + MemberMutationScope scope{context}; + + // TODO: The rest of this needs to be finished <% if (prop.typeMode && prop.typeMode.dartImpl) { %> ExceptionState exception_state; diff --git a/bridge/scripts/code_generator/templates/idl_templates/union.cc.tpl b/bridge/scripts/code_generator/templates/idl_templates/union.cc.tpl index fc57a1a..35ddc62 100644 --- a/bridge/scripts/code_generator/templates/idl_templates/union.cc.tpl +++ b/bridge/scripts/code_generator/templates/idl_templates/union.cc.tpl @@ -37,7 +37,7 @@ std::shared_ptr<<%= generateUnionTypeClassName(unionType) %>> <%= generateUnionT <% }) %> <%= generateUnionTypeClassName(unionType) %> ::~<%= generateUnionTypeClassName(unionType) %> () { - Clear(); + } JSValue <%= generateUnionTypeClassName(unionType) %>::ToQuickJSValue(JSContext* ctx, ExceptionState& exception_state) const { diff --git a/bridge/third_party/quickjs/include/quickjs/quickjs-opcode.h b/bridge/third_party/quickjs/include/quickjs/quickjs-opcode.h index 4c2a34e..39a2f07 100644 --- a/bridge/third_party/quickjs/include/quickjs/quickjs-opcode.h +++ b/bridge/third_party/quickjs/include/quickjs/quickjs-opcode.h @@ -1,6 +1,6 @@ /* * QuickJS opcode definitions - * + * * Copyright (c) 2017-2018 Fabrice Bellard * Copyright (c) 2017-2018 Charlie Gordon * @@ -165,14 +165,14 @@ DEF( set_loc, 3, 1, 1, loc) /* must come after put_loc */ DEF( get_arg, 3, 0, 1, arg) DEF( put_arg, 3, 1, 0, arg) /* must come after get_arg */ DEF( set_arg, 3, 1, 1, arg) /* must come after put_arg */ -DEF( get_var_ref, 3, 0, 1, var_ref) +DEF( get_var_ref, 3, 0, 1, var_ref) DEF( put_var_ref, 3, 1, 0, var_ref) /* must come after get_var_ref */ DEF( set_var_ref, 3, 1, 1, var_ref) /* must come after put_var_ref */ DEF(set_loc_uninitialized, 3, 0, 0, loc) DEF( get_loc_check, 3, 0, 1, loc) DEF( put_loc_check, 3, 1, 0, loc) /* must come after get_loc_check */ DEF( put_loc_check_init, 3, 1, 0, loc) -DEF(get_var_ref_check, 3, 0, 1, var_ref) +DEF(get_var_ref_check, 3, 0, 1, var_ref) DEF(put_var_ref_check, 3, 1, 0, var_ref) /* must come after get_var_ref_check */ DEF(put_var_ref_check_init, 3, 1, 0, var_ref) DEF( close_loc, 3, 0, 0, loc) @@ -261,7 +261,7 @@ DEF( mul_pow10, 1, 2, 1, none) DEF( math_mod, 1, 2, 1, none) #endif /* must be the last non short and non temporary opcode */ -DEF( nop, 1, 0, 0, none) +DEF( nop, 1, 0, 0, none) /* temporary opcodes: never emitted in the final bytecode */ @@ -282,7 +282,7 @@ def(scope_get_private_field2, 7, 1, 2, atom_u16) /* obj -> obj value, emitted in def(scope_put_private_field, 7, 1, 1, atom_u16) /* obj value ->, emitted in phase 1, removed in phase 2 */ def( set_class_name, 5, 1, 1, u32) /* emitted in phase 1, removed in phase 2 */ - + def( line_num, 5, 0, 0, u32) /* emitted in phase 1, removed in phase 3 */ def( column_num, 5, 0, 0, u32) /* emitted in phase 1, removed in phase 3 */ @@ -364,6 +364,7 @@ DEF( typeof_is_function, 1, 1, 1, none) DEF( get_field_ic, 5, 1, 1, none) DEF( get_field2_ic, 5, 1, 2, none) DEF( put_field_ic, 5, 2, 0, none) +DEF( debugger, 1, 0, 0, none) #undef DEF #undef def diff --git a/bridge/third_party/quickjs/include/quickjs/quickjs.h b/bridge/third_party/quickjs/include/quickjs/quickjs.h index 5d0035e..60a3244 100644 --- a/bridge/third_party/quickjs/include/quickjs/quickjs.h +++ b/bridge/third_party/quickjs/include/quickjs/quickjs.h @@ -354,6 +354,8 @@ JSRuntime *JS_NewRuntime(void); void JS_SetRuntimeInfo(JSRuntime *rt, const char *info); void JS_SetMemoryLimit(JSRuntime *rt, size_t limit); void JS_SetGCThreshold(JSRuntime *rt, size_t gc_threshold); +void JS_TurnOffGC(JSRuntime *rt); +void JS_TurnOnGC(JSRuntime *rt); /* use 0 to disable maximum stack size check */ void JS_SetMaxStackSize(JSRuntime *rt, size_t stack_size); /* should be called when changing thread to update the stack top value diff --git a/bridge/third_party/quickjs/src/core/function.c b/bridge/third_party/quickjs/src/core/function.c index 4f7e413..0e01fa1 100644 --- a/bridge/third_party/quickjs/src/core/function.c +++ b/bridge/third_party/quickjs/src/core/function.c @@ -1481,7 +1481,7 @@ JSValue JS_CallInternal(JSContext* caller_ctx, JSAtom atom; atom = get_u32(pc); pc += 4; - + val = JS_GetPropertyInternal(ctx, sp[-1], atom, sp[-1], ic, FALSE); if (unlikely(JS_IsException(val))) goto exception; @@ -1504,7 +1504,7 @@ JSValue JS_CallInternal(JSContext* caller_ctx, ic_offset = get_u32(pc); atom = get_ic_atom(ic, ic_offset); pc += 4; - + val = JS_GetPropertyInternalWithIC(ctx, sp[-1], atom, sp[-1], ic, ic_offset, FALSE); ic->updated = FALSE; if (unlikely(JS_IsException(val))) @@ -1520,7 +1520,7 @@ JSValue JS_CallInternal(JSContext* caller_ctx, atom = get_u32(pc); pc += 4; - val = JS_GetPropertyInternal(ctx, sp[-1], atom, sp[-1], NULL, FALSE); + val = JS_GetPropertyInternal(ctx, sp[-1], atom, sp[-1], ic, FALSE); if (unlikely(JS_IsException(val))) goto exception; if (ic != NULL && ic->updated == TRUE) { @@ -1541,7 +1541,7 @@ JSValue JS_CallInternal(JSContext* caller_ctx, ic_offset = get_u32(pc); atom = get_ic_atom(ic, ic_offset); pc += 4; - + val = JS_GetPropertyInternalWithIC(ctx, sp[-1], atom, sp[-1], ic, ic_offset, FALSE); ic->updated = FALSE; if (unlikely(JS_IsException(val))) @@ -1578,7 +1578,7 @@ JSValue JS_CallInternal(JSContext* caller_ctx, ic_offset = get_u32(pc); atom = get_ic_atom(ic, ic_offset); pc += 4; - + ret = JS_SetPropertyInternalWithIC(ctx, sp[-2], atom, sp[-1], JS_PROP_THROW_STRICT, ic, ic_offset); ic->updated = FALSE; JS_FreeValue(ctx, sp[-2]); @@ -1588,6 +1588,9 @@ JSValue JS_CallInternal(JSContext* caller_ctx, } BREAK; + CASE(OP_debugger): + BREAK; + CASE(OP_private_symbol) : { JSAtom atom; JSValue val; diff --git a/bridge/third_party/quickjs/src/core/gc.c b/bridge/third_party/quickjs/src/core/gc.c index f840632..09e8964 100644 --- a/bridge/third_party/quickjs/src/core/gc.c +++ b/bridge/third_party/quickjs/src/core/gc.c @@ -645,9 +645,12 @@ void mark_children(JSRuntime* rt, JSGCObjectHeader* gp, JS_MarkFunc* mark_func) if (b->ic) { for (i = 0; i < b->ic->count; i++) { buffer = b->ic->cache[i].buffer; - for (j = 0; j < IC_CACHE_ITEM_CAPACITY; j++) + for (j = 0; j < IC_CACHE_ITEM_CAPACITY; j++) { if (buffer[j].shape) mark_func(rt, &buffer[j].shape->header); + if (buffer[j].proto) + mark_func(rt, &buffer[j].proto->header); + } } } } @@ -792,6 +795,9 @@ void gc_free_cycles(JSRuntime* rt) { } void JS_RunGC(JSRuntime* rt) { + /* Turn off the GC running for some special reasons. */ + if (rt->gc_off) return; + /* decrement the reference of the children of each object. mark = 1 after this pass. */ gc_decref(rt); @@ -803,6 +809,14 @@ void JS_RunGC(JSRuntime* rt) { gc_free_cycles(rt); } +void JS_TurnOffGC(JSRuntime *rt) { + rt->gc_off = TRUE; +} + +void JS_TurnOnGC(JSRuntime *rt) { + rt->gc_off = FALSE; +} + /* Return false if not an object or if the object has already been freed (zombie objects are visible in finalizers when freeing cycles). */ @@ -812,4 +826,4 @@ BOOL JS_IsLiveObject(JSRuntime* rt, JSValueConst obj) { return FALSE; p = JS_VALUE_GET_OBJ(obj); return !p->free_mark; -} \ No newline at end of file +} diff --git a/bridge/third_party/quickjs/src/core/ic.c b/bridge/third_party/quickjs/src/core/ic.c index f6c484a..c1257d8 100644 --- a/bridge/third_party/quickjs/src/core/ic.c +++ b/bridge/third_party/quickjs/src/core/ic.c @@ -101,13 +101,23 @@ int resize_ic_hash(InlineCache *ic) { int free_ic(InlineCache *ic) { uint32_t i, j; + JSRuntime *rt; + JSShape *sh; InlineCacheHashSlot *ch, *ch_next; InlineCacheRingItem *buffer; for (i = 0; i < ic->count; i++) { buffer = ic->cache[i].buffer; JS_FreeAtom(ic->ctx, ic->cache[i].atom); for (j = 0; j < IC_CACHE_ITEM_CAPACITY; j++) { - js_free_shape_null(ic->ctx->rt, buffer[j].shape); + sh = buffer[j].shape; + o = buffer[j].watchpoint_ref; + if (o) { + if (o->free_callback) + o->free_callback(rt, o->ref, o->atom); + list_del(&o->link); + js_free_rt(rt, o); + } + js_free_shape_null(rt, sh); } } for (i = 0; i < ic->capacity; i++) { @@ -123,15 +133,26 @@ int free_ic(InlineCache *ic) { js_free(ic->ctx, ic); return 0; } - +#if _MSC_VER uint32_t add_ic_slot(InlineCache *ic, JSAtom atom, JSObject *object, - uint32_t prop_offset) { + uint32_t prop_offset, JSObject* prototype) +#else +force_inline uint32_t add_ic_slot(InlineCache *ic, JSAtom atom, JSObject *object, + uint32_t prop_offset, JSObject* prototype) +#endif +{ int32_t i; uint32_t h; InlineCacheHashSlot *ch; InlineCacheRingSlot *cr; + InlineCacheRingItem *ci; + JSRuntime* rt; JSShape *sh; + JSObject *proto; cr = NULL; + rt = ic->ctx->rt; + sh = NULL; + proto = NULL; h = get_index_hash(atom, ic->hash_bits); for (ch = ic->hash[h]; ch != NULL; ch = ch->next) if (ch->atom == atom) { @@ -142,25 +163,45 @@ uint32_t add_ic_slot(InlineCache *ic, JSAtom atom, JSObject *object, assert(cr != NULL); i = cr->index; for (;;) { - if (object->shape == cr->buffer[i].shape) { - cr->buffer[i].prop_offset = prop_offset; + ci = cr->buffer + i; + if (object->shape == ci->shape && prototype == ci->proto) { + ci->prop_offset = prop_offset; goto end; } i = (i + 1) % IC_CACHE_ITEM_CAPACITY; - if (unlikely(i == cr->index)) + if (unlikely(i == cr->index)) { + cr->index = (cr->index + 1) % IC_CACHE_ITEM_CAPACITY; break; + } } - sh = cr->buffer[i].shape; - cr->buffer[i].shape = js_dup_shape(object->shape); - js_free_shape_null(ic->ctx->rt, sh); - cr->buffer[i].prop_offset = prop_offset; + ci = cr->buffer + cr->index; + sh = ci->shape; + if (ci->watchpoint_ref) + // must be called before js_free_shape_null + js_shape_delete_watchpoints(rt, sh, ci); + ci->prop_offset = prop_offset; + ci->shape = js_dup_shape(object->shape); + js_free_shape_null(rt, sh); + if (prototype) { + // the atom and prototype SHOULE BE freed by watchpoint_remove/clear_callback + JS_DupValue(ic->ctx, JS_MKPTR(JS_TAG_OBJECT, prototype)); + ci->proto = prototype; + ci->watchpoint_ref = js_shape_create_watchpoint(rt, ci->shape, (intptr_t)ci, + JS_DupAtom(ic->ctx, atom), + ic_watchpoint_delete_handler, + ic_watchpoint_free_handler); end: return ch->index; } -uint32_t add_ic_slot1(InlineCache *ic, JSAtom atom) { +#if _MSC_VER +uint32_t add_ic_slot1(InlineCache *ic, JSAtom atom) +#else +force_inline uint32_t add_ic_slot1(InlineCache *ic, JSAtom atom) +#endif +{ uint32_t h; InlineCacheHashSlot *ch; if (ic->count + 1 >= ic->capacity && resize_ic_hash(ic)) @@ -179,4 +220,90 @@ uint32_t add_ic_slot1(InlineCache *ic, JSAtom atom) { ic->count += 1; end: return 0; -} \ No newline at end of file +} + + +int ic_watchpoint_delete_handler(JSRuntime* rt, intptr_t ref, JSAtom atom, void* target) { + InlineCacheRingItem *ci; + ci = (InlineCacheRingItem *)ref; + if(ref != (intptr_t)target) + return 1; + assert(ci->proto != NULL); + // the shape and prop_offset WILL BE handled by add_ic_slot + // !!! MUST NOT CALL js_free_shape0 TO DOUBLE FREE HERE !!! + JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_OBJECT, ci->proto)); + JS_FreeAtomRT(rt, atom); + ci->watchpoint_ref = NULL; + ci->proto = NULL; + ci->prop_offset = 0; + ci->shape = NULL; + return 0; +} + +int ic_watchpoint_free_handler(JSRuntime* rt, intptr_t ref, JSAtom atom) { + InlineCacheRingItem *ci; + ci = (InlineCacheRingItem *)ref; + assert(ci->watchpoint_ref != NULL); + assert(ci->proto != NULL); + // the watchpoint_clear_callback ONLY CAN BE called by js_free_shape0 + // !!! MUST NOT CALL js_free_shape0 TO DOUBLE FREE HERE !!! + JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_OBJECT, ci->proto)); + JS_FreeAtomRT(rt, atom); + ci->watchpoint_ref = NULL; + ci->proto = NULL; + ci->prop_offset = 0; + ci->shape = NULL; + return 0; +} + +int ic_delete_shape_proto_watchpoints(JSRuntime *rt, JSShape *shape, JSAtom atom) { + struct list_head *el, *el1; + JSObject *p; + JSAtom *prop; + InlineCacheRingItem *ci; + JSShape *sh; + p = shape->proto; + while(likely(p)) { + if (p->shape->watchpoint) + list_for_each_safe(el, el1, p->shape->watchpoint) { + ICWatchpoint *o = list_entry(el, ICWatchpoint, link); + if (o->atom == atom) { + ci = (InlineCacheRingItem *)o->ref; + sh = ci->shape; + o->delete_callback = NULL; + o->free_callback = NULL; + ic_watchpoint_free_handler(rt, o->ref, o->atom); + js_free_shape_null(rt, shape); + list_del(el); + js_free_rt(rt, o); + } + } + p = p->shape->proto; + } + return 0; +} + +int ic_free_shape_proto_watchpoints(JSRuntime *rt, JSShape *shape) { + struct list_head *el, *el1; + JSObject *p; + JSAtom *prop; + InlineCacheRingItem *ci; + JSShape *sh; + p = shape->proto; + while(likely(p)) { + if (p->shape->watchpoint) + list_for_each_safe(el, el1, p->shape->watchpoint) { + ICWatchpoint *o = list_entry(el, ICWatchpoint, link); + ci = (InlineCacheRingItem *)o->ref; + sh = ci->shape; + o->delete_callback = NULL; + o->free_callback = NULL; + ic_watchpoint_free_handler(rt, o->ref, o->atom); + js_free_shape_null(rt, shape); + list_del(el); + js_free_rt(rt, o); + } + p = p->shape->proto; + } + return 0; +} diff --git a/bridge/third_party/quickjs/src/core/ic.h b/bridge/third_party/quickjs/src/core/ic.h index 319acda..04b226a 100644 --- a/bridge/third_party/quickjs/src/core/ic.h +++ b/bridge/third_party/quickjs/src/core/ic.h @@ -35,10 +35,10 @@ int rebuild_ic(InlineCache *ic); int resize_ic_hash(InlineCache *ic); int free_ic(InlineCache *ic); uint32_t add_ic_slot(InlineCache *ic, JSAtom atom, JSObject *object, - uint32_t prop_offset); + uint32_t prop_offset, JSObject* prototype); uint32_t add_ic_slot1(InlineCache *ic, JSAtom atom); force_inline int32_t get_ic_prop_offset(InlineCache *ic, uint32_t cache_offset, - JSShape *shape) { + JSShape *shape, JSObject **prototype) { uint32_t i; InlineCacheRingSlot *cr; InlineCacheRingItem *buffer; @@ -49,6 +49,7 @@ force_inline int32_t get_ic_prop_offset(InlineCache *ic, uint32_t cache_offset, buffer = cr->buffer + i; if (likely(buffer->shape == shape)) { cr->index = i; + *prototype = buffer->proto; return buffer->prop_offset; } @@ -58,6 +59,7 @@ force_inline int32_t get_ic_prop_offset(InlineCache *ic, uint32_t cache_offset, } } + *prototype = NULL; return -1; } force_inline JSAtom get_ic_atom(InlineCache *ic, uint32_t cache_offset) { @@ -65,4 +67,8 @@ force_inline JSAtom get_ic_atom(InlineCache *ic, uint32_t cache_offset) { return ic->cache[cache_offset].atom; } -#endif \ No newline at end of file +int ic_watchpoint_delete_handler(JSRuntime* rt, intptr_t ref, JSAtom atom, void* target); +int ic_watchpoint_free_handler(JSRuntime* rt, intptr_t ref, JSAtom atom); +int ic_delete_shape_proto_watchpoints(JSRuntime *rt, JSShape *shape, JSAtom atom); +int ic_free_shape_proto_watchpoints(JSRuntime *rt, JSShape *shape); +#endif diff --git a/bridge/third_party/quickjs/src/core/object.c b/bridge/third_party/quickjs/src/core/object.c index 40330ac..cdcb67c 100644 --- a/bridge/third_party/quickjs/src/core/object.c +++ b/bridge/third_party/quickjs/src/core/object.c @@ -162,7 +162,6 @@ JSValue JS_GetPropertyStr(JSContext* ctx, JSValueConst this_obj, const char* pro error. */ JSProperty* add_property(JSContext* ctx, JSObject* p, JSAtom prop, int prop_flags) { JSShape *sh, *new_sh; - sh = p->shape; if (sh->is_hashed) { /* try to find an existing shape */ @@ -274,11 +273,10 @@ int delete_property(JSContext* ctx, JSObject* p, JSAtom atom) { pr->flags = 0; pr->atom = JS_ATOM_NULL; pr1->u.value = JS_UNDEFINED; - + ic_delete_shape_proto_watchpoints(ctx->rt, sh, atom); /* compact the properties if too many deleted properties */ - if (sh->deleted_prop_count >= 8 && sh->deleted_prop_count >= ((unsigned)sh->prop_count / 2)) { + if (sh->deleted_prop_count >= 8 && sh->deleted_prop_count >= ((unsigned)sh->prop_count / 2)) compact_properties(ctx, p); - } return TRUE; } lpr = pr; @@ -390,7 +388,7 @@ JSValue JS_GetPropertyInternal(JSContext *ctx, JSValueConst obj, JSAtom prop, JSValueConst this_obj, InlineCache *ic, BOOL throw_ref_error) { - JSObject *p; + JSObject *p, *p1; JSProperty *pr; JSShapeProperty *prs; uint32_t tag, offset, proto_depth; @@ -434,6 +432,7 @@ JSValue JS_GetPropertyInternal(JSContext *ctx, JSValueConst obj, p = JS_VALUE_GET_OBJ(obj); } + p1 = p; for(;;) { prs = find_own_property_ic(&pr, p, prop, &offset); if (prs) { @@ -461,9 +460,9 @@ JSValue JS_GetPropertyInternal(JSContext *ctx, JSValueConst obj, } } else { // basic poly ic is only used for fast path - if (ic != NULL && proto_depth == 0 && p->shape->is_hashed) { + if (ic && p1->shape->is_hashed && p->shape->is_hashed) { ic->updated = TRUE; - ic->updated_offset = add_ic_slot(ic, prop, p, offset); + ic->updated_offset = add_ic_slot(ic, prop, p1, offset, proto_depth > 0 ? p : NULL); } return JS_DupValue(ctx, pr->u.value); } @@ -539,22 +538,32 @@ JSValue JS_GetPropertyInternal(JSContext *ctx, JSValueConst obj, } } +#if _MSC_VER JSValue JS_GetPropertyInternalWithIC(JSContext *ctx, JSValueConst obj, - JSAtom prop, JSValueConst this_obj, - InlineCache *ic, int32_t offset, - BOOL throw_ref_error) + JSAtom prop, JSValueConst this_obj, + InlineCache *ic, int32_t offset, + BOOL throw_ref_error) +#else +force_inline JSValue JS_GetPropertyInternalWithIC(JSContext *ctx, JSValueConst obj, + JSAtom prop, JSValueConst this_obj, + InlineCache *ic, int32_t offset, + BOOL throw_ref_error) +#endif { uint32_t tag; - JSObject *p; + JSObject *p, *proto; tag = JS_VALUE_GET_TAG(obj); if (unlikely(tag != JS_TAG_OBJECT)) goto slow_path; p = JS_VALUE_GET_OBJ(obj); - offset = get_ic_prop_offset(ic, offset, p->shape); - if (likely(offset >= 0)) + offset = get_ic_prop_offset(ic, offset, p->shape, &proto); + if (likely(offset >= 0)) { + if (proto) + p = proto; return JS_DupValue(ctx, p->prop[offset].u.value); + } slow_path: - return JS_GetPropertyInternal(ctx, obj, prop, this_obj, ic, throw_ref_error); + return JS_GetPropertyInternal(ctx, obj, prop, this_obj, ic, throw_ref_error); } JSValue JS_GetOwnPropertyNames2(JSContext* ctx, JSValueConst obj1, int flags, int kind) { @@ -1771,7 +1780,8 @@ int JS_SetPropertyInternal(JSContext* ctx, JSValueConst this_obj, JSAtom prop, J JSProperty* pr; uint32_t tag; JSPropertyDescriptor desc; - int ret, offset; + uint32_t offset; + int ret; #if 0 printf("JS_SetPropertyInternal: "); print_atom(ctx, prop); printf("\n"); #endif @@ -1800,9 +1810,9 @@ int JS_SetPropertyInternal(JSContext* ctx, JSValueConst this_obj, JSAtom prop, J if (prs) { if (likely((prs->flags & (JS_PROP_TMASK | JS_PROP_WRITABLE | JS_PROP_LENGTH)) == JS_PROP_WRITABLE)) { /* fast case */ - if (ic != NULL && p->shape->is_hashed) { + if (ic && p->shape->is_hashed) { ic->updated = TRUE; - ic->updated_offset = add_ic_slot(ic, prop, p, offset); + ic->updated_offset = add_ic_slot(ic, prop, p, offset, NULL); } set_value(ctx, &pr->u.value, val); return TRUE; @@ -1966,24 +1976,36 @@ int JS_SetPropertyInternal(JSContext* ctx, JSValueConst this_obj, JSAtom prop, J } } + ic_delete_shape_proto_watchpoints(ctx->rt, p->shape, prop); pr = add_property(ctx, p, prop, JS_PROP_C_W_E); if (unlikely(!pr)) { JS_FreeValue(ctx, val); return -1; } pr->u.value = val; + /* fast case */ + if (ic && p->shape->is_hashed) { + ic->updated = TRUE; + ic->updated_offset = add_ic_slot(ic, prop, p, p->shape->prop_count - 1, NULL); + } return TRUE; } +#if _MSC_VER int JS_SetPropertyInternalWithIC(JSContext* ctx, JSValueConst this_obj, JSAtom prop, JSValue val, int flags, InlineCache *ic, int32_t offset) { +#else +force_inline int JS_SetPropertyInternalWithIC(JSContext* ctx, JSValueConst this_obj, JSAtom prop, JSValue val, int flags, InlineCache *ic, int32_t offset) { +#endif uint32_t tag; - JSObject *p; + JSObject *p, *proto; tag = JS_VALUE_GET_TAG(this_obj); if (unlikely(tag != JS_TAG_OBJECT)) goto slow_path; p = JS_VALUE_GET_OBJ(this_obj); - offset = get_ic_prop_offset(ic, offset, p->shape); + offset = get_ic_prop_offset(ic, offset, p->shape, &proto); if (likely(offset >= 0)) { + if (proto) + goto slow_path; set_value(ctx, &p->prop[offset].u.value, val); return TRUE; } @@ -2260,4 +2282,4 @@ JSValue JS_NewObjectProto(JSContext* ctx, JSValueConst proto) { JSValue JS_NewObject(JSContext* ctx) { /* inline JS_NewObjectClass(ctx, JS_CLASS_OBJECT); */ return JS_NewObjectProtoClass(ctx, ctx->class_proto[JS_CLASS_OBJECT], JS_CLASS_OBJECT); -} \ No newline at end of file +} diff --git a/bridge/third_party/quickjs/src/core/parser.c b/bridge/third_party/quickjs/src/core/parser.c index 03b20e3..157cb44 100644 --- a/bridge/third_party/quickjs/src/core/parser.c +++ b/bridge/third_party/quickjs/src/core/parser.c @@ -149,9 +149,9 @@ int __attribute__((format(printf, 2, 3))) js_parse_error(JSParseState *s, const if (s->cur_func && s->cur_func->backtrace_barrier) backtrace_flags = JS_BACKTRACE_FLAG_SINGLE_LEVEL; int column_num = calc_column_position(s); - build_backtrace(ctx, ctx->rt->current_exception, + build_backtrace(ctx, ctx->rt->current_exception, s->filename, s->line_num, - column_num < 0 ? -1 : column_num, + column_num < 0 ? -1 : column_num, backtrace_flags); return -1; } @@ -1085,7 +1085,7 @@ static __exception int next_token(JSParseState *s) s->token.column_num = calc_column_position(s); } - // dump_token(s, &s->token); + // dump_token(s, &s->token); return 0; fail: @@ -2652,9 +2652,9 @@ static __exception int js_parse_object_literal(JSParseState *s) else if (prop_type == PROP_TYPE_ASYNC_STAR) func_kind = JS_FUNC_ASYNC_GENERATOR; } - if (js_parse_function_decl(s, func_type, + if (js_parse_function_decl(s, func_type, func_kind, JS_ATOM_NULL, - start_ptr, start_line, + start_ptr, start_line, start_column)) goto fail; if (name == JS_ATOM_NULL) { @@ -3042,7 +3042,7 @@ static __exception int js_parse_class(JSParseState *s, BOOL is_class_expr, if (js_parse_function_decl2(s, JS_PARSE_FUNC_GETTER + is_set, JS_FUNC_NORMAL, JS_ATOM_NULL, start_ptr, s->token.line_num, - s->token.column_num, JS_PARSE_EXPORT_NONE, + s->token.column_num, JS_PARSE_EXPORT_NONE, &method_fd)) goto fail; if (is_private) { @@ -3187,10 +3187,10 @@ static __exception int js_parse_class(JSParseState *s, BOOL is_class_expr, if (add_brand(s, &class_fields[is_static]) < 0) goto fail; } - if (js_parse_function_decl2(s, func_type, - func_kind, JS_ATOM_NULL, - start_ptr, s->token.line_num, - s->token.column_num, JS_PARSE_EXPORT_NONE, + if (js_parse_function_decl2(s, func_type, + func_kind, JS_ATOM_NULL, + start_ptr, s->token.line_num, + s->token.column_num, JS_PARSE_EXPORT_NONE, &method_fd)) goto fail; if (func_type == JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR || @@ -4882,7 +4882,7 @@ static __exception int js_parse_postfix_expr(JSParseState *s, int parse_flags) } else if (s->token.val == '.') { if (next_token(s)) return -1; - + column_num = s->token.column_num; emit_column(s, column_num); parse_property: @@ -5164,7 +5164,7 @@ static __exception int js_parse_expr_binary(JSParseState *s, int level, int parse_flags) { int op, opcode; - + if (level == 0) { return js_parse_unary(s, (parse_flags & PF_ARROW_FUNC) | PF_POW_ALLOWED); @@ -6993,6 +6993,7 @@ static __exception int js_parse_statement_or_decl(JSParseState *s, int decl_mask goto fail; if (js_parse_expect_semi(s)) goto fail; + emit_u8(s, OP_debugger); break; case TOK_ENUM: @@ -7124,7 +7125,7 @@ static __exception int js_parse_export(JSParseState *s) return js_parse_function_decl2(s, JS_PARSE_FUNC_STATEMENT, JS_FUNC_NORMAL, JS_ATOM_NULL, s->token.ptr, s->token.line_num, - s->token.column_num, JS_PARSE_EXPORT_NAMED, + s->token.column_num, JS_PARSE_EXPORT_NAMED, NULL); } @@ -7235,7 +7236,7 @@ static __exception int js_parse_export(JSParseState *s) return js_parse_function_decl2(s, JS_PARSE_FUNC_STATEMENT, JS_FUNC_NORMAL, JS_ATOM_NULL, s->token.ptr, s->token.line_num, - s->token.column_num, JS_PARSE_EXPORT_DEFAULT, + s->token.column_num, JS_PARSE_EXPORT_DEFAULT, NULL); } else { if (js_parse_assign_expr(s)) @@ -7457,8 +7458,8 @@ JSFunctionDef *js_new_function_def(JSContext *ctx, JSFunctionDef *parent, BOOL is_eval, BOOL is_func_expr, - const char *filename, - int line_num, + const char *filename, + int line_num, int column_num) { JSFunctionDef *fd; @@ -7533,6 +7534,10 @@ static void js_free_function_def(JSContext *ctx, JSFunctionDef *fd) free_bytecode_atoms(ctx->rt, fd->byte_code.buf, fd->byte_code.size, fd->use_short_opcodes); + if (fd->ic) { + rebuild_ic(fd->ic); + free_ic(fd->ic); + } dbuf_free(&fd->byte_code); js_free(ctx, fd->jump_slots); js_free(ctx, fd->label_slots); @@ -9865,7 +9870,7 @@ static void add_pc2line_info(JSFunctionDef *s, uint32_t pc, int line_num) } /* the pc2col table gives a column number for each PC value */ -static void add_pc2col_info(JSFunctionDef *s, uint32_t pc, int column_num) +static void add_pc2col_info(JSFunctionDef *s, uint32_t pc, int column_num) { if(s->column_number_slots != NULL && s->column_number_count < s->column_number_size @@ -9918,7 +9923,7 @@ static void compute_pc2line_info(JSFunctionDef *s) } } -static void compute_pc2column_info(JSFunctionDef *s) +static void compute_pc2column_info(JSFunctionDef *s) { if(!(s->js_mode & JS_MODE_STRIP) && s->column_number_slots) { int last_column_num = s->column_num; @@ -11491,6 +11496,7 @@ static JSValue js_create_function(JSContext *ctx, JSFunctionDef *fd) return JS_EXCEPTION; } +/* parse "use strict/math/strip" and determine if has semi */ static __exception int js_parse_directives(JSParseState *s) { char str[20]; @@ -11642,8 +11648,8 @@ static JSFunctionDef *js_parse_function_class_fields_init(JSParseState *s) { JSFunctionDef *fd; - fd = js_new_function_def(s->ctx, s->cur_func, - FALSE, FALSE, s->filename, + fd = js_new_function_def(s->ctx, s->cur_func, + FALSE, FALSE, s->filename, 0, 0); if (!fd) return NULL; @@ -11786,7 +11792,7 @@ static __exception int js_parse_function_decl2(JSParseState *s, } fd = js_new_function_def(ctx, fd, FALSE, is_expr, - s->filename, function_line_num, + s->filename, function_line_num, function_column_num); if (!fd) { JS_FreeAtom(ctx, func_name); @@ -12225,7 +12231,7 @@ static __exception int js_parse_function_decl(JSParseState *s, int function_column_num) { return js_parse_function_decl2(s, func_type, func_kind, func_name, ptr, - function_line_num, function_column_num, + function_line_num, function_column_num, JS_PARSE_EXPORT_NONE, NULL); } diff --git a/bridge/third_party/quickjs/src/core/parser.h b/bridge/third_party/quickjs/src/core/parser.h index 153e02d..659952e 100644 --- a/bridge/third_party/quickjs/src/core/parser.h +++ b/bridge/third_party/quickjs/src/core/parser.h @@ -368,7 +368,7 @@ typedef struct JSParseState { JSContext *ctx; int last_line_num; /* line number of last token */ int line_num; /* line number of current offset */ - const uint8_t *column_ptr; + const uint8_t *column_ptr; /* column head pointer on every line */ const uint8_t *column_last_ptr; int column_num_count; const char *filename; @@ -439,4 +439,4 @@ JSValue __JS_EvalInternal(JSContext *ctx, JSValueConst this_obj, const char *input, size_t input_len, const char *filename, int flags, int scope_idx); -#endif \ No newline at end of file +#endif diff --git a/bridge/third_party/quickjs/src/core/runtime.c b/bridge/third_party/quickjs/src/core/runtime.c index e4efd1b..5dbe331 100644 --- a/bridge/third_party/quickjs/src/core/runtime.c +++ b/bridge/third_party/quickjs/src/core/runtime.c @@ -352,6 +352,7 @@ int JS_SetPrototypeInternal(JSContext* ctx, JSValueConst obj, JSValueConst proto if (js_shape_prepare_update(ctx, p, NULL)) return -1; sh = p->shape; + ic_free_shape_proto_watchpoints(ctx->rt, p->shape); if (sh->proto) JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, sh->proto)); sh->proto = proto; @@ -3005,7 +3006,8 @@ JSRuntime* JS_NewRuntime2(const JSMallocFunctions* mf, void* opaque) { rt->mf.js_malloc_usable_size = js_malloc_usable_size_unknown; } rt->malloc_state = ms; - rt->malloc_gc_threshold = 256 * 1024; + rt->malloc_gc_threshold = 2 * 1024 * 1024; // 2 MB as a start + rt->gc_off = FALSE; #ifdef CONFIG_BIGNUM bf_context_init(&rt->bf_ctx, js_bf_realloc, rt); @@ -3155,4 +3157,4 @@ JSValue JS_EvalFunctionInternal(JSContext* ctx, JSValue fun_obj, JSValueConst th JSValue JS_EvalFunction(JSContext* ctx, JSValue fun_obj) { return JS_EvalFunctionInternal(ctx, fun_obj, ctx->global_obj, NULL, NULL); -} \ No newline at end of file +} diff --git a/bridge/third_party/quickjs/src/core/shape.c b/bridge/third_party/quickjs/src/core/shape.c index ae9465d..3a2bf98 100644 --- a/bridge/third_party/quickjs/src/core/shape.c +++ b/bridge/third_party/quickjs/src/core/shape.c @@ -134,6 +134,7 @@ no_inline JSShape* js_new_shape2(JSContext* ctx, JSObject* proto, int hash_size, sh->hash = shape_initial_hash(proto); sh->is_hashed = TRUE; sh->has_small_array_index = FALSE; + sh->watchpoint = NULL; js_shape_hash_link(ctx->rt, sh); return sh; } @@ -162,6 +163,7 @@ JSShape* js_clone_shape(JSContext* ctx, JSShape* sh1) { sh->header.ref_count = 1; add_gc_object(ctx->rt, &sh->header, JS_GC_OBJ_TYPE_SHAPE); sh->is_hashed = FALSE; + sh->watchpoint = NULL; if (sh->proto) { JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, sh->proto)); } @@ -183,9 +185,9 @@ void js_free_shape0(JSRuntime* rt, JSShape* sh) { assert(sh->header.ref_count == 0); if (sh->is_hashed) js_shape_hash_unlink(rt, sh); - if (sh->proto != NULL) { + if (sh->proto != NULL) JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_OBJECT, sh->proto)); - } + js_shape_free_watchpoints(rt, sh); pr = get_shape_prop(sh); for (i = 0; i < sh->prop_count; i++) { JS_FreeAtomRT(rt, pr->atom); @@ -215,7 +217,7 @@ no_inline int resize_properties(JSContext* ctx, JSShape** psh, JSObject* p, uint intptr_t h; sh = *psh; - new_size = max_int(count, sh->prop_size * 9 / 2); + new_size = max_int(count, sh->prop_size * 3 / 2); /* Reallocate prop array first to avoid crash or size inconsistency in case of memory allocation failure */ if (p) { @@ -584,4 +586,59 @@ int js_shape_prepare_update(JSContext* ctx, JSObject* p, JSShapeProperty** pprs) } } return 0; -} \ No newline at end of file +} + +int js_shape_delete_watchpoints(JSRuntime *rt, JSShape *shape, void* target) { + struct list_head *el, *el1; + if (unlikely(!shape || !shape->watchpoint)) + goto end; + list_for_each_safe(el, el1, shape->watchpoint) { + ICWatchpoint *o = list_entry(el, ICWatchpoint, link); + if (o->delete_callback) + if (!o->delete_callback(rt, o->ref, o->atom, target)) { + list_del(el); + js_free_rt(rt, o); + break; + } + } +end: + return 0; +} + +int js_shape_free_watchpoints(JSRuntime* rt, JSShape *shape) { + struct list_head *el, *el1; + if (unlikely(!shape || !shape->watchpoint)) + goto end; + list_for_each_safe(el, el1, shape->watchpoint) { + ICWatchpoint *o = list_entry(el, ICWatchpoint, link); + if (o->free_callback) { + o->free_callback(rt, o->ref, o->atom); + } + list_del(el); + js_free_rt(rt, o); + } + list_empty(shape->watchpoint); + js_free_rt(rt, shape->watchpoint); +end: + return 0; +} + +ICWatchpoint* js_shape_create_watchpoint(JSRuntime *rt, JSShape *shape, intptr_t ptr, JSAtom atom, + watchpoint_delete_callback *remove_callback, + watchpoint_free_callback *clear_callback) { + ICWatchpoint *o; + o = (ICWatchpoint *)js_malloc_rt(rt, sizeof(ICWatchpoint)); + if(unlikely(!o)) + return NULL; + o->ref = ptr; + o->atom = atom; + o->delete_callback = remove_callback; + o->free_callback = clear_callback; + if (!shape->watchpoint) { + shape->watchpoint = (struct list_head *)js_malloc_rt(rt, sizeof(struct list_head)); + init_list_head(shape->watchpoint); + } + init_list_head(&o->link); + list_add_tail(&o->link, shape->watchpoint); + return o; +} diff --git a/bridge/third_party/quickjs/src/core/shape.h b/bridge/third_party/quickjs/src/core/shape.h index e829212..d9ac373 100644 --- a/bridge/third_party/quickjs/src/core/shape.h +++ b/bridge/third_party/quickjs/src/core/shape.h @@ -88,4 +88,11 @@ JSValue JS_NewObjectFromShape(JSContext* ctx, JSShape* sh, JSClassID class_id); /* ensure that the shape can be safely modified */ int js_shape_prepare_update(JSContext* ctx, JSObject* p, JSShapeProperty** pprs); +/* the watch point of shape for prototype inline cache or something else */ +int js_shape_delete_watchpoints(JSRuntime *rt, JSShape *shape, void* target); +int js_shape_free_watchpoints(JSRuntime *rt, JSShape *shape); +ICWatchpoint* js_shape_create_watchpoint(JSRuntime *rt, JSShape *shape, intptr_t ptr, JSAtom atom, + watchpoint_delete_callback *remove_callback, + watchpoint_free_callback *clear_callback); + #endif diff --git a/bridge/third_party/quickjs/src/core/types.h b/bridge/third_party/quickjs/src/core/types.h index 740a218..4b5bb38 100644 --- a/bridge/third_party/quickjs/src/core/types.h +++ b/bridge/third_party/quickjs/src/core/types.h @@ -192,6 +192,7 @@ struct JSRuntime { struct list_head gc_zero_ref_count_list; struct list_head tmp_obj_list; /* used during GC */ JSGCPhaseEnum gc_phase : 8; + BOOL gc_off: 8; size_t malloc_gc_threshold; #ifdef DUMP_LEAKS struct list_head string_list; /* list of JSString.link */ @@ -515,7 +516,7 @@ typedef struct JSVarDef { #define PC2COLUMN_RANGE 5 #define PC2COLUMN_OP_FIRST 1 #define PC2COLUMN_DIFF_PC_MAX ((255 - PC2COLUMN_OP_FIRST) / PC2COLUMN_RANGE) -#define IC_CACHE_ITEM_CAPACITY 8 +#define IC_CACHE_ITEM_CAPACITY 4 typedef enum JSFunctionKindEnum { JS_FUNC_NORMAL = 0, @@ -524,9 +525,22 @@ typedef enum JSFunctionKindEnum { JS_FUNC_ASYNC_GENERATOR = (JS_FUNC_GENERATOR | JS_FUNC_ASYNC), } JSFunctionKindEnum; +typedef int watchpoint_delete_callback(JSRuntime* rt, intptr_t ref, JSAtom atom, void* target); +typedef int watchpoint_free_callback(JSRuntime* rt, intptr_t ref, JSAtom atom); + +typedef struct ICWatchpoint { + intptr_t ref; + JSAtom atom; + watchpoint_delete_callback *delete_callback; + watchpoint_free_callback *free_callback; + struct list_head link; +} ICWatchpoint; + typedef struct InlineCacheRingItem { - JSShape* shape; + JSObject* proto; + JSShape *shape; uint32_t prop_offset; + ICWatchpoint *watchpoint_ref; } InlineCacheRingItem; typedef struct InlineCacheRingSlot { @@ -834,6 +848,7 @@ struct JSShape { int deleted_prop_count; JSShape *shape_hash_next; /* in JSRuntime.shape_hash[h] list */ JSObject *proto; + struct list_head *watchpoint; JSShapeProperty prop[0]; /* prop_size elements */ }; diff --git a/mercury/example/assets/bundle.js b/mercury/example/assets/bundle.js index ada7c0d..678ff57 100644 --- a/mercury/example/assets/bundle.js +++ b/mercury/example/assets/bundle.js @@ -1,17 +1,31 @@ console.log('does this even work?'); +mercury.methodChannel.addMethodCallHandler('test', function(state) { + // Here, 'state' holds the data sent from Dart. + + console.log(typeof state['test']) + + console.log('Received state: ' + JSON.stringify(state)); + + // You can also send back a response to Dart, if needed. + return { + message: 'Received state successfully.', + testing: 127, + }; +}); + let seconds = 0; console.log('how about here?') -const hello = async () => { - //const res = await fetch('https://api.ipify.org'); - //const txt = await res.text(); - setInterval(() => { - console.log('yup') - mercury.dispatcher.dispatch('example', { message: `foo: ${seconds} seconds.`}); - seconds++; - }, 1000) -}; +// const hello = async () => { +// //const res = await fetch('https://api.ipify.org'); +// //const txt = await res.text(); +// setInterval(() => { +// console.log('yup') +// mercury.dispatcher.dispatch('example', { message: `foo: ${seconds} seconds.`}); +// seconds++; +// }, 1000) +// }; console.log('surely not') diff --git a/mercury/example/lib/main.dart b/mercury/example/lib/main.dart index 2efb8bb..f6bdcb1 100644 --- a/mercury/example/lib/main.dart +++ b/mercury/example/lib/main.dart @@ -19,13 +19,13 @@ class MyApp extends StatelessWidget { title: 'Mercury Example', // theme: ThemeData.dark(), debugShowCheckedModeBanner: false, - home: MyBrowser(), + home: HomePage(title: 'Landing Page'), ); } } -class MyBrowser extends StatefulWidget { - MyBrowser({Key? key, this.title}) : super(key: key); +class HomePage extends StatefulWidget { + HomePage({Key? key, this.title}) : super(key: key); // This widget is the home page of your application. It is stateful, meaning // that it has a State object (defined below) that contains fields that affect @@ -39,10 +39,10 @@ class MyBrowser extends StatefulWidget { final String? title; @override - _MyHomePageState createState() => _MyHomePageState(); + _HomePageState createState() => _HomePageState(); } -class _MyHomePageState extends State { +class _HomePageState extends State { OutlineInputBorder outlineBorder = OutlineInputBorder( borderSide: BorderSide(color: Colors.transparent, width: 0.0), borderRadius: const BorderRadius.all( @@ -56,8 +56,11 @@ class _MyHomePageState extends State { @override Widget build(BuildContext context) { + final javaScriptChannel = MercuryJavaScriptChannel(); + mercuryjs ??= Mercury( devToolsService: ChromeDevToolsService(), + javaScriptChannel: javaScriptChannel, bundle: MercuryBundle.fromUrl('assets:assets/bundle.js'), onControllerCreated: (controller) { setState(() { @@ -67,13 +70,16 @@ class _MyHomePageState extends State { setState(() { message = 'Context loading...'; }); - controller.context.dispatcher?.subscribe('example', (args) { - print('bar'); - setState(() { - message = args[0]['message']; - }); + javaScriptChannel.invokeMethod('test', {'state': 'bar', 'test': 472 }).then((value) { + print(value); }); - controller.context.evaluateJavaScripts('hello();'); + // controller.context.dispatcher?.subscribe('example', (args) { + // print('bar'); + // setState(() { + // message = args[0]['message']; + // }); + // }); + // controller.context.evaluateJavaScripts('hello();'); }; } ); diff --git a/mercury/ios/mercuryjs.podspec b/mercury/ios/mercuryjs.podspec index 298632c..4e21a83 100644 --- a/mercury/ios/mercuryjs.podspec +++ b/mercury/ios/mercuryjs.podspec @@ -17,8 +17,8 @@ Pod::Spec.new do |s| s.dependency 'Flutter' s.platform = :ios, '11.0' s.prepare_command = 'bash prepare.sh' - s.vendored_frameworks = 'Frameworks/*.xcframework' - s.resource = 'Frameworks/*.*' + s.vendored_frameworks = ['Frameworks/*.xcframework'] + s.resource = 'Frameworks/*.xcframework' # Flutter.framework does not contain a i386 slice. Only x86_64 simulators are supported. s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS[sdk=iphonesimulator*]' => 'x86_64' } diff --git a/mercury/lib/bridge.dart b/mercury/lib/bridge.dart index f8b5beb..d7127ec 100644 --- a/mercury/lib/bridge.dart +++ b/mercury/lib/bridge.dart @@ -10,3 +10,5 @@ export 'src/bridge/to_native.dart'; export 'src/bridge/from_native.dart'; export 'src/bridge/native_types.dart'; export 'src/bridge/native_value.dart'; +export 'src/bridge/isolate_command.dart'; +export 'src/bridge/multiple_thread.dart'; diff --git a/mercury/lib/main.dart b/mercury/lib/main.dart index acf6fd9..72510b0 100644 --- a/mercury/lib/main.dart +++ b/mercury/lib/main.dart @@ -62,8 +62,9 @@ class Mercury { this.onLoadError, this.onJSError }) { - controller = MercuryController(shortHash(this), - entrypoint: bundle, + controller = MercuryController( + name: shortHash(this), + bundle: bundle, onLoad: onLoad, onLoadError: onLoadError, onJSError: onJSError, diff --git a/mercury/lib/src/bridge/binding.dart b/mercury/lib/src/bridge/binding.dart index 635bc66..cd00782 100644 --- a/mercury/lib/src/bridge/binding.dart +++ b/mercury/lib/src/bridge/binding.dart @@ -5,6 +5,7 @@ // Bind the JavaScript side object, // provide interface such as property setter/getter, call a property as function. +import 'dart:async'; import 'dart:ffi'; import 'package:ffi/ffi.dart'; @@ -24,11 +25,11 @@ enum BindingMethodCallOperations { } typedef NativeAsyncAnonymousFunctionCallback = Void Function( - Pointer callbackContext, Pointer nativeValue, Int32 contextId, Pointer errmsg); + Pointer callbackContext, Pointer nativeValue, Double contextId, Pointer errmsg); typedef DartAsyncAnonymousFunctionCallback = void Function( - Pointer callbackContext, Pointer nativeValue, int contextId, Pointer errmsg); + Pointer callbackContext, Pointer nativeValue, double contextId, Pointer errmsg); -typedef BindingCallFunc = dynamic Function(BindingObject bindingObject, List args); +typedef BindingCallFunc = dynamic Function(BindingObject bindingObject, List args, { BindingOpItem? profileOp }); List bindingCallMethodDispatchTable = [ getterBindingCall, @@ -39,17 +40,84 @@ List bindingCallMethodDispatchTable = [ ]; // Dispatch the event to the binding side. -void _dispatchNomalEventToNative(Event event) { - _dispatchEventToNative(event, false); +Future _dispatchNomalEventToNative(Event event) async { + await _dispatchEventToNative(event, false); } -void _dispatchCaptureEventToNative(Event event) { - _dispatchEventToNative(event, true); +Future _dispatchCaptureEventToNative(Event event) async { + await _dispatchEventToNative(event, true); } -void _dispatchEventToNative(Event event, bool isCapture) { + +void _handleDispatchResult(_DispatchEventResultContext context, Pointer returnValue) { + Pointer dispatchResult = fromNativeValue(context.controller.view, returnValue).cast(); + Event event = context.event; + event.cancelable = dispatchResult.ref.canceled; + event.propagationStopped = dispatchResult.ref.propagationStopped; + event.sharedJSProps = Pointer.fromAddress(context.rawEvent.ref.bytes.elementAt(8).value); + event.propLen = context.rawEvent.ref.bytes.elementAt(9).value; + event.allocateLen = context.rawEvent.ref.bytes.elementAt(10).value; + + if (enableMercuryCommandLog) { // && context.stopwatch != null + print('dispatch event to native side: target: ${event.target} arguments: ${context.dispatchEventArguments}'); // time: ${context.stopwatch!.elapsedMicroseconds}us + } + + // Free the allocated arguments. + malloc.free(context.rawEvent); + malloc.free(context.method); + malloc.free(context.allocatedNativeArguments); + malloc.free(dispatchResult); + malloc.free(returnValue); + + // if (enableMercuryProfileTracking) { + // WebFProfiler.instance.finishTrackEvaluate(context.profileOp!); + // } + + context.completer.complete(); +} + +class _DispatchEventResultContext { + Completer completer; + Stopwatch? stopwatch; + Event event; + Pointer method; + Pointer allocatedNativeArguments; + Pointer rawEvent; + List dispatchEventArguments; + MercuryController controller; + // prof: EvaluateOpItem? profileOp; + _DispatchEventResultContext( + this.completer, + this.event, + this.method, + this.allocatedNativeArguments, + this.rawEvent, + this.controller, + this.dispatchEventArguments, + // prof: this.stopwatch, + // prof: this.profileOp, + ); +} + +Future _dispatchEventToNative(Event event, bool isCapture) async { Pointer? pointer = event.currentTarget?.pointer; - int? contextId = event.target?.contextId; + double? contextId = event.target?.contextId; MercuryController controller = MercuryController.getControllerOfJSContextId(contextId)!; - if (contextId != null && pointer != null && pointer.ref.invokeBindingMethodFromDart != nullptr) { + + if (controller.context.disposed) return; + + if (contextId != null && + pointer != null && + pointer.ref.invokeBindingMethodFromDart != nullptr && + event.target?.pointer?.ref.disposed != true && + event.currentTarget?.pointer?.ref.disposed != true + ) { + Completer completer = Completer(); + + // prof: + // EvaluateOpItem? currentProfileOp; + // if (enableMercuryProfileTracking) { + // currentProfileOp = MercuryProfiler.instance.startTrackEvaluate('_dispatchEventToNative'); + // } + BindingObject bindingObject = controller.context.getBindingObject(pointer); // Call methods implements at C++ side. DartInvokeBindingMethodsFromDart f = pointer.ref.invokeBindingMethodFromDart.asFunction(); @@ -57,35 +125,36 @@ void _dispatchEventToNative(Event event, bool isCapture) { Pointer rawEvent = event.toRaw().cast(); List dispatchEventArguments = [event.type, rawEvent, isCapture]; - Stopwatch? stopwatch; - if (isEnabledLog) { - stopwatch = Stopwatch()..start(); - } + // prof: + // Stopwatch? stopwatch; + // if (enableMercuryCommandLog) { + // stopwatch = Stopwatch()..start(); + // } Pointer method = malloc.allocate(sizeOf()); toNativeValue(method, 'dispatchEvent'); Pointer allocatedNativeArguments = makeNativeValueArguments(bindingObject, dispatchEventArguments); - Pointer returnValue = malloc.allocate(sizeOf()); - f(pointer, returnValue, method, dispatchEventArguments.length, allocatedNativeArguments, event); - Pointer dispatchResult = fromNativeValue(controller.context, returnValue).cast(); - event.cancelable = dispatchResult.ref.canceled; - event.propagationStopped = dispatchResult.ref.propagationStopped; + _DispatchEventResultContext context = _DispatchEventResultContext( + completer, + event, + method, + allocatedNativeArguments, + rawEvent, + controller, + dispatchEventArguments, + // prof: stopwatch + // prof: currentProfileOp + ); - event.sharedJSProps = Pointer.fromAddress(rawEvent.ref.bytes.elementAt(8).value); - event.propLen = rawEvent.ref.bytes.elementAt(9).value; - event.allocateLen = rawEvent.ref.bytes.elementAt(10).value; + Pointer> resultCallback = Pointer.fromFunction(_handleDispatchResult); - if (isEnabledLog) { - print('dispatch event to native side: target: ${event.target} arguments: $dispatchEventArguments time: ${stopwatch!.elapsedMicroseconds}us'); - } + // prof: + Future.microtask(() { + f(pointer, /* currentProfileOp?.hashCode ?? 0,*/ method, dispatchEventArguments.length, allocatedNativeArguments, context, resultCallback); + }); - // Free the allocated arguments. - malloc.free(rawEvent); - malloc.free(method); - malloc.free(allocatedNativeArguments); - malloc.free(dispatchResult); - malloc.free(returnValue); + return completer.future; } } @@ -99,6 +168,10 @@ class DummyObject extends BindingObject { @override void initializeProperties(Map properties) { } + + @override + void dispose() { + } } enum CreateBindingObjectType { @@ -112,7 +185,7 @@ abstract class BindingBridge { static Pointer> get nativeInvokeBindingMethod => _invokeBindingMethodFromNative; - static void createBindingObject(int contextId, Pointer pointer, CreateBindingObjectType type, Pointer args, int argc) { + static void createBindingObject(double contextId, Pointer pointer, CreateBindingObjectType type, Pointer args, int argc) { MercuryController controller = MercuryController.getControllerOfJSContextId(contextId)!; switch(type) { case CreateBindingObjectType.createDummyObject: { diff --git a/mercury/lib/src/bridge/bridge.dart b/mercury/lib/src/bridge/bridge.dart index 3fde661..aaa0ca4 100644 --- a/mercury/lib/src/bridge/bridge.dart +++ b/mercury/lib/src/bridge/bridge.dart @@ -3,30 +3,38 @@ * Copyright (C) 2022-present The WebF authors. All rights reserved. */ +import 'dart:async'; import 'dart:ffi'; import 'package:mercuryjs/launcher.dart'; import 'binding.dart'; import 'from_native.dart'; import 'to_native.dart'; +import 'multiple_thread.dart'; class DartContext { - DartContext() : pointer = initDartIsolateContext(makeDartMethodsData()) { + DartContext() : pointer = allocateNewMercuryIsolateSync(makeDartMethodsData()) { initDartDynamicLinking(); registerDartContextFinalizer(this); } final Pointer pointer; } -DartContext dartContext = DartContext(); +DartContext? dartContext; + +bool isJSRunningInDedicatedThread(double contextId) { + return contextId >= 0; +} /// Init bridge -int initBridge(MercuryContextController view) { +FutureOr initBridge(MercuryContextController view, MercuryThread runningThread) async { + dartContext ??= DartContext(); + // Setup binding bridge. BindingBridge.setup(); - int mercuryIsolateId = newMercuryIsolateId(); - allocateNewMercuryIsolate(mercuryIsolateId); + double newContextId = runningThread.identity(); + await allocateNewIsolate(runningThread is FlutterIsolateThread, newContextId, runningThread.syncBufferSize()); - return mercuryIsolateId; + return newContextId; } diff --git a/mercury/lib/src/bridge/from_native.dart b/mercury/lib/src/bridge/from_native.dart index 4f4bf03..c049b48 100644 --- a/mercury/lib/src/bridge/from_native.dart +++ b/mercury/lib/src/bridge/from_native.dart @@ -8,6 +8,7 @@ import 'dart:typed_data'; import 'package:ffi/ffi.dart'; import 'package:mercuryjs/bridge.dart'; +import 'package:mercuryjs/foundation.dart'; import 'package:mercuryjs/launcher.dart'; String uint16ToString(Pointer pointer, int length) { @@ -29,6 +30,14 @@ Pointer stringToNativeString(String string) { return nativeString; } +Pointer uint8ListToPointer(Uint8List data) { + Pointer ptr = malloc.allocate(sizeOf() * data.length + 1); + Uint8List dataContext = ptr.asTypedList(data.length + 1); + dataContext.setAll(0, data); + dataContext[data.length] = 0; + return ptr; +} + int doubleToUint64(double value) { var byteData = ByteData(8); byteData.setFloat64(0, value); @@ -67,25 +76,73 @@ void freeNativeString(Pointer pointer) { // Register InvokeModule typedef NativeAsyncModuleCallback = Pointer Function( - Pointer callbackContext, Int32 contextId, Pointer errmsg, Pointer ptr); + Pointer callbackContext, + Double contextId, + Pointer errmsg, + Pointer ptr, + Handle context, + Pointer> handleResult); typedef DartAsyncModuleCallback = Pointer Function( - Pointer callbackContext, int contextId, Pointer errmsg, Pointer ptr); + Pointer callbackContext, + double contextId, + Pointer errmsg, + Pointer ptr, + Object context, + Pointer> handleResult); + +typedef NativeHandleInvokeModuleResult = Void Function(Handle context, Pointer result); typedef NativeInvokeModule = Pointer Function( Pointer callbackContext, - Int32 contextId, + Double contextId, + // prof: Int64 profileId, Pointer module, Pointer method, Pointer params, Pointer>); +class _InvokeModuleResultContext { + Completer completer; + Pointer? errmsgPtr; + Stopwatch? stopwatch; + MercuryContextController currentContext; + Pointer? data; + String moduleName; + String method; + dynamic params; + + _InvokeModuleResultContext(this.completer, this.currentContext, this.moduleName, this.method, this.params, + {this.errmsgPtr, this.data, this.stopwatch}); +} + +void _handleInvokeModuleResult(_InvokeModuleResultContext context, Pointer result) { + var returnValue = fromNativeValue(context.currentContext, result); + + // prof: + if (enableMercuryCommandLog) { // && context.stopwatch != null + print( + 'Invoke module callback from(name: ${context.moduleName} method: ${context.method}, params: ${context.params}) ' + 'return: $returnValue'); // time: ${context.stopwatch!.elapsedMicroseconds}us + } + + malloc.free(result); + if (context.errmsgPtr != null) { + malloc.free(context.errmsgPtr!); + } else if (context.data != null) { + malloc.free(context.data!); + } + + context.completer.complete(returnValue); +} + + dynamic invokeModule(Pointer callbackContext, MercuryController controller, String moduleName, String method, params, - DartAsyncModuleCallback callback) { - MercuryContextController currentView = controller.context; + DartAsyncModuleCallback callback) { // , { BindingOpItem? profileOp } + MercuryContextController currentContext = controller.context; dynamic result; Stopwatch? stopwatch; - if (isEnabledLog) { + if (enableMercuryCommandLog) { stopwatch = Stopwatch()..start(); } @@ -95,41 +152,47 @@ dynamic invokeModule(Pointer callbackContext, MercuryController controller // To make sure Promise then() and catch() executed before Promise callback called at JavaScript side. // We should make callback always async. Future.microtask(() { - if (controller.context != currentView || currentView.disposed) return; - Pointer callbackResult = nullptr; + if (controller.context != currentContext || currentContext.disposed) return; + Pointer> handleResult = + Pointer.fromFunction(_handleInvokeModuleResult); if (error != null) { Pointer errmsgPtr = error.toNativeUtf8(); - callbackResult = callback(callbackContext, currentView.contextId, errmsgPtr, nullptr); + _InvokeModuleResultContext context = _InvokeModuleResultContext( + completer, currentContext, moduleName, method, params, + errmsgPtr: errmsgPtr, stopwatch: stopwatch); malloc.free(errmsgPtr); } else { Pointer dataPtr = malloc.allocate(sizeOf()); toNativeValue(dataPtr, data); - callbackResult = callback(callbackContext, currentView.contextId, nullptr, dataPtr); - malloc.free(dataPtr); - } - - var returnValue = fromNativeValue(currentView, callbackResult); - if (isEnabledLog) { - print('Invoke module callback from(name: $moduleName method: $method, params: $params) return: $returnValue time: ${stopwatch!.elapsedMicroseconds}us'); + _InvokeModuleResultContext context = _InvokeModuleResultContext( + completer, currentContext, moduleName, method, params, + data: dataPtr, stopwatch: stopwatch); + callback(callbackContext, currentContext.contextId, nullptr, dataPtr, context, handleResult);] } - - malloc.free(callbackResult); - completer.complete(returnValue); }); return completer.future; } - result = controller.module.moduleManager.invokeModule( - moduleName, method, params, invokeModuleCallback); + // prof: + // if (enableMercuryProfileTracking) { + // MercuryProfiler.instance.startTrackBindingSteps(profileOp!, 'moduleManager.invokeModule'); + // } + + result = controller.module.moduleManager.invokeModule(moduleName, method, params, invokeModuleCallback); + + // prof: + // if (enableMercuryProfileTracking) { + // MercuryProfiler.instance.finishTrackBindingSteps(profileOp!); + // } } catch (e, stack) { - if (isEnabledLog) { + if (enableMercuryCommandLog) { print('Invoke module failed: $e\n$stack'); } String error = '$e\n$stack'; - callback(callbackContext, currentView.contextId, error.toNativeUtf8(), nullptr); + callback(callbackContext, currentContext.contextId, error.toNativeUtf8(), nullptr, {}, nullptr); } - if (isEnabledLog) { + if (enableMercuryCommandLog) { print('Invoke module name: $moduleName method: $method, params: $params return: $result time: ${stopwatch!.elapsedMicroseconds}us'); } @@ -138,27 +201,51 @@ dynamic invokeModule(Pointer callbackContext, MercuryController controller Pointer _invokeModule( Pointer callbackContext, - int contextId, + double contextId, + // prof: int profileLinkId, Pointer module, Pointer method, Pointer params, Pointer> callback) { + + // prof: + // BindingOpItem? currentProfileOp; + // if (enableMercuryProfileTracking) { + // currentProfileOp = MercuryProfiler.instance.startTrackBinding(profileLinkId); + // } + MercuryController controller = MercuryController.getControllerOfJSContextId(contextId)!; - dynamic result = invokeModule(callbackContext, controller, nativeStringToString(module), nativeStringToString(method), - fromNativeValue(controller.context, params), callback.asFunction()); + // if (enableMercuryProfileTracking) { + // MercuryProfiler.instance.startTrackBindingSteps(currentProfileOp!, 'fromNativeValue'); + // } + + String moduleValue = nativeStringToString(module); + String methodValue = nativeStringToString(method); + dynamic paramsValue = fromNativeValue(controller.context, params); + + // if (enableMercuryProfileTracking) { + // MercuryProfiler.instance.finishTrackBindingSteps(currentProfileOp!); + // MercuryProfiler.instance.startTrackBindingSteps(currentProfileOp, 'invokeModule'); + // } + + dynamic result = invokeModule(callbackContext, controller, moduleValue, methodValue, + paramsValue, callback.asFunction()); // prof: , profileOp: currentProfileOp + + // if (enableMercuryProfileTracking) { + // MercuryProfiler.instance.finishTrackBindingSteps(currentProfileOp!); + // MercuryProfiler.instance.startTrackBindingSteps(currentProfileOp, 'toNativeValue'); + // } Pointer returnValue = malloc.allocate(sizeOf()); toNativeValue(returnValue, result); - freeNativeString(module); - freeNativeString(method); return returnValue; } final Pointer> _nativeInvokeModule = Pointer.fromFunction(_invokeModule); // Register reloadApp -typedef NativeReloadApp = Void Function(Int32 contextId); +typedef NativeReloadApp = Void Function(Double contextId); -void _reloadApp(int contextId) async { +void _reloadApp(double contextId) async { MercuryController controller = MercuryController.getControllerOfJSContextId(contextId)!; try { @@ -170,32 +257,31 @@ void _reloadApp(int contextId) async { final Pointer> _nativeReloadApp = Pointer.fromFunction(_reloadApp); -typedef NativeAsyncCallback = Void Function(Pointer callbackContext, Int32 contextId, Pointer errmsg); -typedef DartAsyncCallback = void Function(Pointer callbackContext, int contextId, Pointer errmsg); +typedef NativeAsyncCallback = Void Function(Pointer callbackContext, Double contextId, Pointer errmsg); +typedef DartAsyncCallback = void Function(Pointer callbackContext, double contextId, Pointer errmsg); typedef NativeRAFAsyncCallback = Void Function( - Pointer callbackContext, Int32 contextId, Double data, Pointer errmsg); -typedef DartRAFAsyncCallback = void Function(Pointer, int contextId, double data, Pointer errmsg); + Pointer callbackContext, Double contextId, Double data, Pointer errmsg); +typedef DartRAFAsyncCallback = void Function(Pointer, double contextId, double data, Pointer errmsg); // Register setTimeout typedef NativeSetTimeout = Int32 Function( - Pointer callbackContext, Int32 contextId, Pointer>, Int32); + Pointer callbackContext, Double contextId, Pointer>, Int32); -int _setTimeout( - Pointer callbackContext, int contextId, Pointer> callback, int timeout) { +void _setTimeout( + Pointer callbackContext, double contextId, Pointer> callback, int timeout) { MercuryController controller = MercuryController.getControllerOfJSContextId(contextId)!; - MercuryContextController currentView = controller.context; + MercuryContextController currentContext = controller.context; - return controller.module.setTimeout(timeout, () { + controller.module.setTimeout(timeout, () { DartAsyncCallback func = callback.asFunction(); void _runCallback() { - if (controller.context != currentView || currentView.disposed) return; + if (controller.context != currentContext || currentContext.disposed) return; try { func(callbackContext, contextId, nullptr); } catch (e, stack) { Pointer nativeErrorMessage = ('Error: $e\n$stack').toNativeUtf8(); func(callbackContext, contextId, nativeErrorMessage); - malloc.free(nativeErrorMessage); } } @@ -208,21 +294,19 @@ int _setTimeout( }); } -const int SET_TIMEOUT_ERROR = -1; -final Pointer> _nativeSetTimeout = - Pointer.fromFunction(_setTimeout, SET_TIMEOUT_ERROR); +final Pointer> _nativeSetTimeout = Pointer.fromFunction(_setTimeout); // Register setInterval -typedef NativeSetInterval = Int32 Function( - Pointer callbackContext, Int32 contextId, Pointer>, Int32); +typedef NativeSetInterval = Void Function( + Pointer callbackContext, Double contextId, Pointer>, Int32); -int _setInterval( - Pointer callbackContext, int contextId, Pointer> callback, int timeout) { +void _setInterval( + Pointer callbackContext, double contextId, Pointer> callback, int timeout) { MercuryController controller = MercuryController.getControllerOfJSContextId(contextId)!; - MercuryContextController currentView = controller.context; - return controller.module.setInterval(timeout, () { + MercuryContextController currentContext = controller.context; + controller.module.setInterval(timeout, () { void _runCallbacks() { - if (controller.context != currentView || currentView.disposed) return; + if (controller.context != currentContext || currentContext.disposed) return; DartAsyncCallback func = callback.asFunction(); try { @@ -230,7 +314,6 @@ int _setInterval( } catch (e, stack) { Pointer nativeErrorMessage = ('Dart Error: $e\n$stack').toNativeUtf8(); func(callbackContext, contextId, nativeErrorMessage); - malloc.free(nativeErrorMessage); } } @@ -243,54 +326,53 @@ int _setInterval( }); } -const int SET_INTERVAL_ERROR = -1; -final Pointer> _nativeSetInterval = - Pointer.fromFunction(_setInterval, SET_INTERVAL_ERROR); +final Pointer> _nativeSetInterval = Pointer.fromFunction(_setInterval); // Register clearTimeout -typedef NativeClearTimeout = Void Function(Int32 contextId, Int32); +typedef NativeClearTimeout = Void Function(Double contextId, Int32); -void _clearTimeout(int contextId, int timerId) { +void _clearTimeout(double contextId, int timerId) { MercuryController controller = MercuryController.getControllerOfJSContextId(contextId)!; return controller.module.clearTimeout(timerId); } final Pointer> _nativeClearTimeout = Pointer.fromFunction(_clearTimeout); -typedef NativeFlushIsolateCommand = Void Function(Int32 contextId); -typedef DartFlushIsolateCommand = void Function(int contextId); +typedef NativeFlushIsolateCommand = Void Function(Double contextId, Pointer selfPointer); +typedef DartFlushIsolateCommand = void Function(double contextId, Pointer selfPointer); -void _flushIsolateCommand(int contextId) { - flushIsolateCommandWithContextId(contextId); +void _flushIsolateCommand(double contextId, Pointer selfPointer) { + flushIsolateCommandWithContextId(contextId, selfPointer); } final Pointer> _nativeFlushIsolateCommand = Pointer.fromFunction(_flushIsolateCommand); -typedef NativeCreateBindingObject = Void Function(Int32 contextId, Pointer nativeBindingObject, Int32 type, Pointer args, Int32 argc); -typedef DartCreateBindingObject = void Function(int contextId, Pointer nativeBindingObject, int type, Pointer args, int argc); +typedef NativeCreateBindingObject = Void Function(Double contextId, Pointer nativeBindingObject, Int32 type, Pointer args, Int32 argc); +typedef DartCreateBindingObject = void Function(double contextId, Pointer nativeBindingObject, int type, Pointer args, int argc); -void _createBindingObject(int contextId, Pointer nativeBindingObject, int type, Pointer args, int argc) { +void _createBindingObject(double contextId, Pointer nativeBindingObject, int type, Pointer args, int argc) { BindingBridge.createBindingObject(contextId, nativeBindingObject, CreateBindingObjectType.values[type], args, argc); } final Pointer> _nativeCreateBindingObject = Pointer.fromFunction(_createBindingObject); -typedef NativeJSError = Void Function(Int32 contextId, Pointer); +typedef NativeJSError = Void Function(Double contextId, Pointer); -void _onJSError(int contextId, Pointer charStr) { +void _onJSError(double contextId, Pointer charStr) { MercuryController? controller = MercuryController.getControllerOfJSContextId(contextId); JSErrorHandler? handler = controller?.onJSError; if (handler != null) { String msg = charStr.toDartString(); handler(msg); } + malloc.free(charStr); } final Pointer> _nativeOnJsError = Pointer.fromFunction(_onJSError); -typedef NativeJSLog = Void Function(Int32 contextId, Int32 level, Pointer); +typedef NativeJSLog = Void Function(Double contextId, Int32 level, Pointer); -void _onJSLog(int contextId, int level, Pointer charStr) { +void _onJSLog(double contextId, int level, Pointer charStr) { String msg = charStr.toDartString(); MercuryController? controller = MercuryController.getControllerOfJSContextId(contextId); if (controller != null) { @@ -299,6 +381,7 @@ void _onJSLog(int contextId, int level, Pointer charStr) { jsLogHandler(level, msg); } } + malloc.free(charStr); } final Pointer> _nativeOnJsLog = Pointer.fromFunction(_onJSLog); diff --git a/mercury/lib/src/bridge/isolate_command.dart b/mercury/lib/src/bridge/isolate_command.dart new file mode 100644 index 0000000..84cc53d --- /dev/null +++ b/mercury/lib/src/bridge/isolate_command.dart @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +import 'dart:io'; +import 'dart:ffi'; +import 'package:ffi/ffi.dart'; +import 'package:flutter/foundation.dart'; +import 'package:mercuryjs/bridge.dart'; +import 'package:mercuryjs/foundation.dart'; +import 'package:mercuryjs/launcher.dart'; +import 'package:mercuryjs/module.dart'; + +class IsolateCommand { + late final IsolateCommandType type; + late final String args; + late final Pointer nativePtr; + late final Pointer nativePtr2; + + IsolateCommand(); + IsolateCommand.from(this.type, this.args, this.nativePtr, this.nativePtr2); + + @override + String toString() { + return 'IsolateCommand(type: $type, args: $args, nativePtr: $nativePtr, nativePtr2: $nativePtr2)'; + } +} + +// struct IsolateCommandItem { +// int32_t type; // offset: 0 ~ 0.5 +// int32_t args_01_length; // offset: 0.5 ~ 1 +// const uint16_t *string_01;// offset: 1 +// void* nativePtr; // offset: 2 +// void* nativePtr2; // offset: 3 +// }; +const int nativeCommandSize = 4; +const int typeAndArgs01LenMemOffset = 0; +const int args01StringMemOffset = 1; +const int nativePtrMemOffset = 2; +const int native2PtrMemOffset = 3; + +const int commandBufferPrefix = 1; + +bool enableMercuryCommandLog = !kReleaseMode && Platform.environment['ENABLE_WEBF_JS_LOG'] == 'true'; + +// We found there are performance bottleneck of reading native memory with Dart FFI API. +// So we align all Isolate instructions to a whole block of memory, and then convert them into a dart array at one time, +// To ensure the fastest subsequent random access. +List nativeIsolateCommandToDart(List rawMemory, int commandLength, double contextId) { + List results = List.generate(commandLength, (int _i) { + int i = _i * nativeCommandSize; + IsolateCommand command = IsolateCommand(); + + int typeArgs01Combine = rawMemory[i + typeAndArgs01LenMemOffset]; + + // int32_t int32_t + // +-------------+-----------------+ + // | type | args_01_length | + // +-------------+-----------------+ + int args01Length = (typeArgs01Combine >> 32).toSigned(32); + int type = (typeArgs01Combine ^ (args01Length << 32)).toSigned(32); + + command.type = IsolateCommandType.values[type]; + + int args01StringMemory = rawMemory[i + args01StringMemOffset]; + if (args01StringMemory != 0) { + Pointer args_01 = Pointer.fromAddress(args01StringMemory); + command.args = uint16ToString(args_01, args01Length); + malloc.free(args_01); + } else { + command.args = ''; + } + + int nativePtrValue = rawMemory[i + nativePtrMemOffset]; + command.nativePtr = nativePtrValue != 0 ? Pointer.fromAddress(rawMemory[i + nativePtrMemOffset]) : nullptr; + + int nativePtr2Value = rawMemory[i + native2PtrMemOffset]; + command.nativePtr2 = nativePtr2Value != 0 ? Pointer.fromAddress(nativePtr2Value) : nullptr; + return command; + }, growable: false); + + return results; +} + +void execIsolateCommands(MercuryContextController context, List commands) { + Map pendingStylePropertiesTargets = {}; + + for(IsolateCommand command in commands) { + IsolateCommandType commandType = command.type; + + if (enableMercuryCommandLog) { + String printMsg; + switch(command.type) { + default: + printMsg = 'nativePtr: ${command.nativePtr} type: ${command.type} args: ${command.args} nativePtr2: ${command.nativePtr2}'; + } + print(printMsg); + } + + // prof: if (commandType == IsolateCommandType.startRecordingCommand || commandType == IsolateCommandType.finishRecordingCommand) continue; + + Pointer nativePtr = command.nativePtr; + + // prof: + try { + switch (commandType) { + case IsolateCommandType.createGlobal: + // if (enableMercuryProfileTracking) { + // MercuryProfiler.instance.startTrackIsolateCommandStep('FlushIsolateCommand.createWindow'); + // } + + context.initGlobal(context, nativePtr.cast()); + + // if (enableMercuryProfileTracking) { + // MercuryProfiler.instance.finishTrackIsolateCommandStep(); + // } + break; + case IsolateCommandType.disposeBindingObject: + // if (enableMercuryProfileTracking) { + // MercuryProfiler.instance.startTrackIsolateCommandStep('FlushIsolateCommand.createWindow'); + // } + + context.disposeBindingObject(context, nativePtr.cast()); + + // if (enableMercuryProfileTracking) { + // MercuryProfiler.instance.finishTrackIsolateCommandStep(); + // } + break; + case IsolateCommandType.addEvent: + // if (enableMercuryProfileTracking) { + // MercuryProfiler.instance.startTrackIsolateCommandStep('FlushIsolateCommand.addEvent'); + // } + + Pointer eventListenerOptions = command.nativePtr2.cast(); + context.addEvent(nativePtr.cast(), command.args, + addEventListenerOptions: eventListenerOptions); + + // if (enableMercuryProfileTracking) { + // MercuryProfiler.instance.finishTrackIsolateCommandStep(); + // } + + break; + case IsolateCommandType.removeEvent: + // if (enableMercuryProfileTracking) { + // MercuryProfiler.instance.startTrackIsolateCommandStep('FlushIsolateCommand.removeEvent'); + // } + bool isCapture = command.nativePtr2.address == 1; + context.removeEvent(nativePtr.cast(), command.args, isCapture: isCapture); + // if (enableMercuryProfileTracking) { + // MercuryProfiler.instance.finishTrackIsolateCommandStep(); + // } + break; + default: + break; + } + } catch (e, stack) { + print('$e\n$stack'); + } + } + + // if (enableMercuryProfileTracking) { + // MercuryProfiler.instance.finishTrackIsolateCommandStep(); + // } +} diff --git a/mercury/lib/src/bridge/multiple_thread.dart b/mercury/lib/src/bridge/multiple_thread.dart new file mode 100644 index 0000000..2f52a09 --- /dev/null +++ b/mercury/lib/src/bridge/multiple_thread.dart @@ -0,0 +1,67 @@ +import 'to_native.dart'; + +abstract class MercuryThread { + /// The unique ID for the current thread. + /// [identity] < 0 represent running in Flutter UI Thread. + /// [identity] >= 0 represent running in dedicated thread. + /// [identity] with integer part are the same represent they are running in the same thread, for example, 1.1 and 1.2 + /// will the grouped into one thread. + double identity(); + + /// In dedicated thread mode, Mercury creates a shared buffer to record the UI operations that are generated from the JS thread. + /// This approach allows the UI and JavaScript threads to run concurrently as much as possible in most use cases. + /// Once the recorded commands reach the maximum buffer size, commands will be packaged by the JS thread and sent to + /// the UI thread to be executed and apply visual UI changes. + /// Setting this value to 0 in dedicated thread mode can achieve 100% concurrency but may reduce isolate speed because the + /// generated UI commands will be executed on the UI thread immediately while the JS thread is still running. + /// However, this concurrency sometimes leads to inconsistent UI rendering results, + /// so it's advisable to adjust this value based on specific use cases. + int syncBufferSize(); +} + +/// Executes your JavaScript code within the Flutter UI thread. +class FlutterUIThread extends MercuryThread { + FlutterUIThread(); + + @override + int syncBufferSize() { + return 0; + } + + @override + double identity() { + return (-newIsolateId()).toDouble(); + } +} + +/// Executes your JavaScript code in a dedicated thread. +class DedicatedThread extends MercuryThread { + double? _identity; + final int _syncBufferSize; + + DedicatedThread({ int syncBufferSize = 4 }): _syncBufferSize = syncBufferSize; + DedicatedThread._(this._identity, { int syncBufferSize = 4 }): _syncBufferSize = syncBufferSize; + + @override + int syncBufferSize() { + return _syncBufferSize; + } + + @override + double identity() { + return _identity ?? (newIsolateId()).toDouble(); + } +} + +/// Executes multiple JavaScript contexts in a single thread. +class DedicatedThreadGroup { + int _slaveCount = 0; + final int _identity = newIsolateId(); + + DedicatedThreadGroup(); + + DedicatedThread slave({ int syncBufferSize = 4 }) { + String input = '$_identity.${_slaveCount++}'; + return DedicatedThread._(double.parse(input), syncBufferSize: syncBufferSize); + } +} diff --git a/mercury/lib/src/bridge/native_types.dart b/mercury/lib/src/bridge/native_types.dart index 0e653ac..6ba41aa 100644 --- a/mercury/lib/src/bridge/native_types.dart +++ b/mercury/lib/src/bridge/native_types.dart @@ -60,62 +60,12 @@ class AddEventListenerOptions extends Struct { external bool once; } -class NativeTouchList extends Struct { - @Int64() - external int length; - external Pointer touches; -} - -class NativeTouch extends Struct { - @Int64() - external int identifier; - - external Pointer target; - - @Double() - external double clientX; - - @Double() - external double clientY; - - @Double() - external double screenX; - - @Double() - external double screenY; - - @Double() - external double pageX; - - @Double() - external double pageY; - - @Double() - external double radiusX; - - @Double() - external double radiusY; - - @Double() - external double rotationAngle; - - @Double() - external double force; - - @Double() - external double altitudeAngle; - - @Double() - external double azimuthAngle; -} - -typedef InvokeBindingsMethodsFromNative = Void Function(Int32 contextId, Pointer binding_object, +typedef InvokeBindingsMethodsFromNative = Void Function(Double contextId, Pointer binding_object, Pointer return_value, Pointer method, Int32 argc, Pointer argv); -typedef InvokeBindingMethodsFromDart = Void Function(Pointer binding_object, - Pointer return_value, Pointer method, Int32 argc, Pointer argv, Handle bindingDartObject); -typedef DartInvokeBindingMethodsFromDart = void Function(Pointer binding_object, - Pointer return_value, Pointer method, int argc, Pointer argv, Object bindingDartObject); +// prof: +typedef InvokeBindingMethodsFromDart = Void Function(Pointer binding_object, Pointer method, Int32 argc, Pointer argv, Handle bindingDartObject, Pointer> result_callback); // Int64 profileId +typedef DartInvokeBindingMethodsFromDart = void Function(Pointer binding_object, Pointer method, int argc, Pointer argv, Object bindingDartObject, Pointer> result_callback); // Int64 profileId class NativeBindingObject extends Struct { @Bool() @@ -124,6 +74,7 @@ class NativeBindingObject extends Struct { external Pointer> invokeBindingMethodFromDart; // Shared method called by JS side. external Pointer> invokeBindingMethodFromNative; + external Pointer extra; } Pointer allocateNewBindingObject() { diff --git a/mercury/lib/src/bridge/to_native.dart b/mercury/lib/src/bridge/to_native.dart index bfe4cea..004fd25 100644 --- a/mercury/lib/src/bridge/to_native.dart +++ b/mercury/lib/src/bridge/to_native.dart @@ -1,11 +1,12 @@ /* * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. + * Copyright (C) 2022-present The Mercury authors. All rights reserved. */ +import 'dart:async'; import 'dart:collection'; import 'dart:ffi'; -import 'dart:io'; +import 'dart:isolate'; import 'dart:typed_data'; import 'package:ffi/ffi.dart'; @@ -47,55 +48,119 @@ class MercuryInfo { } } -typedef NativeGetMercuryInfo = Pointer Function(); -typedef DartGetMercuryInfo = Pointer Function(); -final DartGetMercuryInfo _getMercuryInfo = - MercuryDynamicLibrary.ref.lookup>('getMercuryInfo').asFunction(); +// Register Native Callback Port +final interactiveCppRequests = RawReceivePort((message) { + requestExecuteCallback(message); +}); -final MercuryInfo _cachedInfo = MercuryInfo(_getMercuryInfo()); +final int nativePort = interactiveCppRequests.sendPort.nativePort; -final HashMap> _allocatedMercuryIsolates = HashMap(); +class NativeWork extends Opaque {} -Pointer? getAllocatedMercuryIsolate(int contextId) { - return _allocatedMercuryIsolates[contextId]; +final _executeNativeCallback = MercuryDynamicLibrary.ref + .lookupFunction), void Function(Pointer)>('executeNativeCallback'); + +Completer? _working_completer; + +FutureOr waitingSyncTaskComplete(double contextId) async { + if (_working_completer != null) { + return _working_completer!.future; + } } -MercuryInfo getMercuryInfo() { - return _cachedInfo; +void requestExecuteCallback(message) { + try { + final List data = message; + final bool isSync = data[0] == 1; + if (isSync) { + _working_completer = Completer(); + } + + final int workAddress = data[1]; + final work = Pointer.fromAddress(workAddress); + _executeNativeCallback(work); + _working_completer?.complete(); + _working_completer = null; + } catch (e, stack) { + print('requestExecuteCallback error: $e\n$stack'); + } } // Register invokeEventListener -typedef NativeInvokeEventListener = Pointer Function( - Pointer, Pointer, Pointer eventType, Pointer nativeEvent, Pointer); -typedef DartInvokeEventListener = Pointer Function( - Pointer, Pointer, Pointer eventType, Pointer nativeEvent, Pointer); +typedef NativeInvokeEventListener = Void Function( + Pointer, + Pointer, + Pointer eventType, + Pointer nativeEvent, + Pointer, + Handle object, + Pointer> returnCallback); +typedef DartInvokeEventListener = void Function( + Pointer, + Pointer, + Pointer eventType, + Pointer nativeEvent, + Pointer, + Object object, + Pointer> returnCallback); +typedef NativeInvokeModuleCallback = Void Function(Handle object, Pointer result); final DartInvokeEventListener _invokeModuleEvent = MercuryDynamicLibrary.ref.lookup>('invokeModuleEvent').asFunction(); -dynamic invokeModuleEvent(int contextId, String moduleName, Event? event, extra) { +void _invokeModuleCallback(_InvokeModuleCallbackContext context, Pointer dispatchResult) { + dynamic result = fromNativeValue(context.controller.context, dispatchResult); + malloc.free(dispatchResult); + malloc.free(context.extraData); + context.completer.complete(result); +} + +class _InvokeModuleCallbackContext { + Completer completer; + MercuryController controller; + Pointer extraData; + + _InvokeModuleCallbackContext(this.completer, this.controller, this.extraData); +} + +dynamic invokeModuleEvent(double contextId, String moduleName, Event? event, extra) { if (MercuryController.getControllerOfJSContextId(contextId) == null) { return null; } + Completer completer = Completer(); MercuryController controller = MercuryController.getControllerOfJSContextId(contextId)!; + + if (controller.context.disposed) return null; + Pointer nativeModuleName = stringToNativeString(moduleName); Pointer rawEvent = event == null ? nullptr : event.toRaw().cast(); Pointer extraData = malloc.allocate(sizeOf()); toNativeValue(extraData, extra); assert(_allocatedMercuryIsolates.containsKey(contextId)); - Pointer dispatchResult = _invokeModuleEvent( - _allocatedMercuryIsolates[contextId]!, nativeModuleName, event == null ? nullptr : event.type.toNativeUtf8(), rawEvent, extraData); - dynamic result = fromNativeValue(controller.context, dispatchResult); - malloc.free(dispatchResult); - malloc.free(extraData); - return result; + + Pointer> callback = + Pointer.fromFunction(_invokeModuleCallback); + + _InvokeModuleCallbackContext callbackContext = _InvokeModuleCallbackContext(completer, controller, extraData); + + scheduleMicrotask(() { + if (controller.context.disposed) { + callbackContext.completer.complete(null); + return; + } + + _invokeModuleEvent(_allocatedMercuryIsolates[contextId]!, nativeModuleName, + event == null ? nullptr : event.type.toNativeUtf8(), rawEvent, extraData, callbackContext, callback); + }); + + return completer.future; } -typedef DartDispatchEvent = int Function(int contextId, Pointer nativeBindingObject, +typedef DartDispatchEvent = int Function(double contextId, Pointer nativeBindingObject, Pointer eventType, Pointer nativeEvent, int isCustomEvent); -dynamic emitModuleEvent(int contextId, String moduleName, Event? event, extra) { +dynamic emitModuleEvent(double contextId, String moduleName, Event? event, extra) { return invokeModuleEvent(contextId, moduleName, event, extra); } @@ -111,29 +176,72 @@ Pointer createScreen(double width, double height) { } // Register evaluateScripts -typedef NativeEvaluateScripts = Int8 Function( - Pointer, Pointer code, Pointer> parsedBytecodes, Pointer bytecodeLen, Pointer url, Int32 startLine); -typedef DartEvaluateScripts = int Function( - Pointer, Pointer code, Pointer> parsedBytecodes, Pointer bytecodeLen, Pointer url, int startLine); - -// Register parseHTML -typedef NativeParseHTML = Void Function(Pointer, Pointer code, Int32 length); -typedef DartParseHTML = void Function(Pointer, Pointer code, int length); +typedef NativeEvaluateScripts = Void Function( + Pointer, + Pointer code, + Uint64 code_len, + Pointer> parsedBytecodes, + Pointer bytecodeLen, + Pointer url, + Int32 startLine, + // prof: Int64 profileId, + Handle object, + Pointer> resultCallback); +typedef DartEvaluateScripts = void Function( + Pointer, + Pointer code, + int code_len, + Pointer> parsedBytecodes, + Pointer bytecodeLen, + Pointer url, + int startLine, + // prof: int profileId, + Object object, + Pointer> resultCallback); + +typedef NativeEvaluateJavaScriptCallback = Void Function(Handle object, Int8 result); final DartEvaluateScripts _evaluateScripts = MercuryDynamicLibrary.ref.lookup>('evaluateScripts').asFunction(); -final DartParseHTML _parseHTML = - MercuryDynamicLibrary.ref.lookup>('parseHTML').asFunction(); - int _anonymousScriptEvaluationId = 0; class ScriptByteCode { ScriptByteCode(); + late Uint8List bytes; } -Future evaluateScripts(int contextId, String code, {String? url, int line = 0}) async { +class _EvaluateScriptsContext { + Completer completer; + String? cacheKey; + Pointer codePtr; + Pointer url; + Pointer>? bytecodes; + Pointer? bytecodeLen; + Uint8List originalCodeBytes; + + _EvaluateScriptsContext(this.completer, this.originalCodeBytes, this.codePtr, this.url, this.cacheKey); +} + +void handleEvaluateScriptsResult(_EvaluateScriptsContext context, int result) { + if (context.bytecodes != null) { + Uint8List bytes = context.bytecodes!.value.asTypedList(context.bytecodeLen!.value); + // Save to disk cache + QuickJSByteCodeCache.putObject(context.originalCodeBytes, bytes, cacheKey: context.cacheKey).then((_) { + malloc.free(context.codePtr); + malloc.free(context.url); + context.completer.complete(result == 1); + }); + } else { + malloc.free(context.codePtr); + malloc.free(context.url); + context.completer.complete(result == 1); + } +} + +// prof: +Future evaluateScripts(double contextId, Uint8List codeBytes, {String? url, String? cacheKey, int line = 0 }) async { // EvaluateOpItem? profileOp if (MercuryController.getControllerOfJSContextId(contextId) == null) { return false; } @@ -143,9 +251,12 @@ Future evaluateScripts(int contextId, String code, {String? url, int line _anonymousScriptEvaluationId++; } - QuickJSByteCodeCacheObject cacheObject = await QuickJSByteCodeCache.getCacheObject(code); - if (QuickJSByteCodeCacheObject.cacheMode == ByteCodeCacheMode.DEFAULT && cacheObject.valid && cacheObject.bytes != null) { - bool result = evaluateQuickjsByteCode(contextId, cacheObject.bytes!); + QuickJSByteCodeCacheObject cacheObject = await QuickJSByteCodeCache.getCacheObject(codeBytes, cacheKey: cacheKey); + if (QuickJSByteCodeCacheObject.cacheMode == ByteCodeCacheMode.DEFAULT && + cacheObject.valid && + cacheObject.bytes != null) { + // prof: + bool result = await evaluateQuickjsByteCode(contextId, cacheObject.bytes!); // profileOp: profileOp // If the bytecode evaluate failed, remove the cached file and fallback to raw javascript mode. if (!result) { await cacheObject.remove(); @@ -153,117 +264,267 @@ Future evaluateScripts(int contextId, String code, {String? url, int line return result; } else { - Pointer nativeString = stringToNativeString(code); Pointer _url = url.toNativeUtf8(); + Pointer codePtr = uint8ListToPointer(codeBytes); + Completer completer = Completer(); + + _EvaluateScriptsContext context = _EvaluateScriptsContext(completer, codeBytes, codePtr, _url, cacheKey); + Pointer> resultCallback = + Pointer.fromFunction(handleEvaluateScriptsResult); + try { assert(_allocatedMercuryIsolates.containsKey(contextId)); - int result; - if (QuickJSByteCodeCache.isCodeNeedCache(code)) { + if (QuickJSByteCodeCache.isCodeNeedCache(codeBytes)) { // Export the bytecode from scripts Pointer> bytecodes = malloc.allocate(sizeOf>()); Pointer bytecodeLen = malloc.allocate(sizeOf()); - result = _evaluateScripts(_allocatedMercuryIsolates[contextId]!, nativeString, bytecodes, bytecodeLen, _url, line); - Uint8List bytes = bytecodes.value.asTypedList(bytecodeLen.value); - // Save to disk cache - QuickJSByteCodeCache.putObject(code, bytes); + + context.bytecodes = bytecodes; + context.bytecodeLen = bytecodeLen; + + // prof: + _evaluateScripts(_allocatedMercuryIsolates[contextId]!, codePtr, codeBytes.length, bytecodes, bytecodeLen, _url, line, context, resultCallback); // profileOp?.hashCode ?? 0 } else { - result = _evaluateScripts(_allocatedMercuryIsolates[contextId]!, nativeString, nullptr, nullptr, _url, line); + // prof: + _evaluateScripts(_allocatedMercuryIsolates[contextId]!, codePtr, codeBytes.length, nullptr, nullptr, _url, line, context, resultCallback); // profileOp?.hashCode ?? 0 } - return result == 1; + return completer.future; } catch (e, stack) { print('$e\n$stack'); } - freeNativeString(nativeString); - malloc.free(_url); + + return completer.future; } - return false; } -typedef NativeEvaluateQuickjsByteCode = Int8 Function(Pointer, Pointer bytes, Int32 byteLen); -typedef DartEvaluateQuickjsByteCode = int Function(Pointer, Pointer bytes, int byteLen); +// prof: +typedef NativeEvaluateQuickjsByteCode = Void Function(Pointer, Pointer bytes, Int32 byteLen, Handle object, // Int64 profileId + Pointer> callback); +typedef DartEvaluateQuickjsByteCode = void Function(Pointer, Pointer bytes, int byteLen, Object object, // Int64 profileId + Pointer> callback); + +typedef NativeEvaluateQuickjsByteCodeCallback = Void Function(Handle object, Int8 result); final DartEvaluateQuickjsByteCode _evaluateQuickjsByteCode = MercuryDynamicLibrary.ref .lookup>('evaluateQuickjsByteCode') .asFunction(); -bool evaluateQuickjsByteCode(int contextId, Uint8List bytes) { +class _EvaluateQuickjsByteCodeContext { + Completer completer; + Pointer bytes; + + _EvaluateQuickjsByteCodeContext(this.completer, this.bytes); +} + +void handleEvaluateQuickjsByteCodeResult(_EvaluateQuickjsByteCodeContext context, int result) { + malloc.free(context.bytes); + context.completer.complete(result == 1); +} + +// prof: +Future evaluateQuickjsByteCode(double contextId, Uint8List bytes) async { // { EvaluateOpItem? profileOp } if (MercuryController.getControllerOfJSContextId(contextId) == null) { return false; } + Completer completer = Completer(); Pointer byteData = malloc.allocate(sizeOf() * bytes.length); byteData.asTypedList(bytes.length).setAll(0, bytes); assert(_allocatedMercuryIsolates.containsKey(contextId)); - int result = _evaluateQuickjsByteCode(_allocatedMercuryIsolates[contextId]!, byteData, bytes.length); - malloc.free(byteData); - return result == 1; + + _EvaluateQuickjsByteCodeContext context = _EvaluateQuickjsByteCodeContext(completer, byteData); + + Pointer> nativeCallback = + Pointer.fromFunction(handleEvaluateQuickjsByteCodeResult); + + // prof: + _evaluateQuickjsByteCode(_allocatedMercuryIsolates[contextId]!, byteData, bytes.length, context, nativeCallback); // profileOp?.hashCode ?? 0 + + return completer.future; } -void parseHTML(int contextId, String code) { - if (MercuryController.getControllerOfJSContextId(contextId) == null) { - return; - } - Pointer nativeCode = code.toNativeUtf8(); - try { - assert(_allocatedMercuryIsolates.containsKey(contextId)); - _parseHTML(_allocatedMercuryIsolates[contextId]!, nativeCode, nativeCode.length); - } catch (e, stack) { - print('$e\n$stack'); +typedef NativeDumpQuickjsByteCodeResultCallback = Void Function(Handle object); + +typedef NativeDumpQuickjsByteCode = Void Function( + Pointer, + // prof: Int64 profileId, + Pointer code, + Int32 code_len, + Pointer> parsedBytecodes, + Pointer bytecodeLen, + Pointer url, + Handle context, + Pointer> resultCallback); +typedef DartDumpQuickjsByteCode = void Function( + Pointer, + // prof: int profileId, + Pointer code, + int code_len, + Pointer> parsedBytecodes, + Pointer bytecodeLen, + Pointer url, + Object context, + Pointer> resultCallback); + +final DartDumpQuickjsByteCode _dumpQuickjsByteCode = + MercuryDynamicLibrary.ref.lookup>('dumpQuickjsByteCode').asFunction(); + +class _DumpQuickjsByteCodeContext { + Completer completer; + Pointer> bytecodes; + Pointer bytecodeLen; + + _DumpQuickjsByteCodeContext(this.completer, this.bytecodes, this.bytecodeLen); +} + +void _handleQuickjsByteCodeResults(_DumpQuickjsByteCodeContext context) { + Uint8List bytes = context.bytecodes.value.asTypedList(context.bytecodeLen.value); + context.completer.complete(bytes); +} + +Future dumpQuickjsByteCode(double contextId, Uint8List code, {String? url, EvaluateOpItem? profileOp}) async { + Completer completer = Completer(); + // Assign `vm://$id` for no url (anonymous scripts). + if (url == null) { + url = 'vm://$_anonymousScriptEvaluationId'; + _anonymousScriptEvaluationId++; } - malloc.free(nativeCode); + + Pointer codePtr = uint8ListToPointer(code); + + Pointer _url = url.toNativeUtf8(); + // Export the bytecode from scripts + Pointer> bytecodes = malloc.allocate(sizeOf>()); + Pointer bytecodeLen = malloc.allocate(sizeOf()); + + _DumpQuickjsByteCodeContext context = _DumpQuickjsByteCodeContext(completer, bytecodes, bytecodeLen); + Pointer> resultCallback = + Pointer.fromFunction(_handleQuickjsByteCodeResults); + + // prof: + _dumpQuickjsByteCode( + _allocatedMercuryIsolates[contextId]!, codePtr, code.length, bytecodes, bytecodeLen, _url, context, resultCallback); // profileOp?.hashCode ?? 0 + + // return bytes; + return completer.future; } // Register initJsEngine -typedef NativeInitDartIsolateContext = Pointer Function(Pointer dartMethods, Int32 methodsLength); -typedef DartInitDartIsolateContext = Pointer Function(Pointer dartMethods, int methodsLength); - -final DartInitDartIsolateContext _initDartIsolateContext = - MercuryDynamicLibrary.ref.lookup>('initDartIsolateContext').asFunction(); +// prof: +typedef NativeInitDartIsolateContext = Pointer Function( + Int64 sendPort, Pointer dartMethods, Int32 methodsLength); // , Int8 enableProfile +typedef DartInitDartIsolateContext = Pointer Function( + int sendPort, Pointer dartMethods, int methodsLength); // , Int8 enableProfile + +final DartInitDartIsolateContext _initDartIsolateContext = MercuryDynamicLibrary.ref + .lookup>('initDartIsolateContextSync') + .asFunction(); Pointer initDartIsolateContext(List dartMethods) { Pointer bytes = malloc.allocate(sizeOf() * dartMethods.length); Uint64List nativeMethodList = bytes.asTypedList(dartMethods.length); - nativeMethodList.setAll(0, dartMethods); - return _initDartIsolateContext(bytes, dartMethods.length); + nativeMethodList.setAll(0, dartMethods); // prof: + return _initDartIsolateContext(nativePort, bytes, dartMethods.length); // , enableMercuryProfileTracking ? 1 : 0 } -typedef NativeDisposeMercuryIsolate = Void Function(Pointer, Pointer mercuryIsolate); -typedef DartDisposeMercuryIsolate = void Function(Pointer, Pointer mercuryIsolate); +typedef HandleDisposeMercuryIsolateResult = Void Function(Handle context); +typedef NativeDisposeMercuryIsolate = Void Function(Double contextId, Pointer, Pointer mercury_isolate, Handle context, + Pointer> resultCallback); +typedef DartDisposeMercuryIsolate = void Function(double, Pointer, Pointer mercury_isolate, Object context, + Pointer> resultCallback); final DartDisposeMercuryIsolate _disposeMercuryIsolate = MercuryDynamicLibrary.ref.lookup>('disposeMercuryIsolate').asFunction(); -void disposeMercuryIsolate(int contextId) { - Pointer mercuryIsolate = _allocatedMercuryIsolates[contextId]!; - _disposeMercuryIsolate(dartContext.pointer, mercuryIsolate); - _allocatedMercuryIsolates.remove(contextId); +typedef NativeDisposeMercuryIsolateSync = Void Function(Double contextId, Pointer, Pointer mercury_isolate); +typedef DartDisposeMercuryIsolateSync = void Function(double, Pointer, Pointer mercury_isolate); + +final DartDisposeMercuryIsolateSync _disposeMercuryIsolateSync = + MercuryDynamicLibrary.ref.lookup>('disposeMercuryIsolateSync').asFunction(); + +void _handleDisposeMercuryIsolateResult(_DisposeMercuryIsolateContext context) { + context.completer.complete(); +} + +class _DisposeMercuryIsolateContext { + Completer completer; + + _DisposeMercuryIsolateContext(this.completer); +} + +FutureOr disposeMercuryIsolate(bool isSync, double contextId) async { + Pointer mercury_isolate = _allocatedMercuryIsolates[contextId]!; + + if (isSync) { + _disposeMercuryIsolateSync(contextId, dartContext!.pointer, mercury_isolate); + _allocatedMercuryIsolates.remove(contextId); + } else { + Completer completer = Completer(); + _DisposeMercuryIsolateContext context = _DisposeMercuryIsolateContext(completer); + Pointer> f = Pointer.fromFunction(_handleDisposeMercuryIsolateResult); + _disposeMercuryIsolate(contextId, dartContext!.pointer, mercury_isolate, context, f); + return completer.future; + } } typedef NativeNewMercuryIsolateId = Int64 Function(); typedef DartNewMercuryIsolateId = int Function(); -final DartNewMercuryIsolateId _newMercuryIsolateId = MercuryDynamicLibrary.ref.lookup>('newMercuryIsolateId').asFunction(); +final DartNewMercuryIsolateId _newMercuryIsolateId = + MercuryDynamicLibrary.ref.lookup>('newMercuryIsolateIdSync').asFunction(); int newMercuryIsolateId() { return _newMercuryIsolateId(); } -typedef NativeAllocateNewMercuryIsolate = Pointer Function(Pointer, Int32); -typedef DartAllocateNewMercuryIsolate = Pointer Function(Pointer, int); +typedef NativeAllocateNewMercuryIsolateSync = Pointer Function(Double, Pointer); +typedef DartAllocateNewMercuryIsolateSync = Pointer Function(double, Pointer); +typedef HandleAllocateNewMercuryIsolateResult = Void Function(Handle object, Pointer mercury_isolate); +typedef NativeAllocateNewMercuryIsolate = Void Function( + Double, Int32, Pointer, Handle object, Pointer> handle_result); +typedef DartAllocateNewMercuryIsolate = void Function( + double, int, Pointer, Object object, Pointer> handle_result); + +final DartAllocateNewMercuryIsolateSync _allocateNewMercuryIsolateSync = + MercuryDynamicLibrary.ref.lookup>('allocateNewMercuryIsolateSync').asFunction(); final DartAllocateNewMercuryIsolate _allocateNewMercuryIsolate = MercuryDynamicLibrary.ref.lookup>('allocateNewMercuryIsolate').asFunction(); -void allocateNewMercuryIsolate(int targetContextId) { - Pointer mercuryIsolate = _allocateNewMercuryIsolate(dartContext.pointer, targetContextId); - assert(!_allocatedMercuryIsolates.containsKey(targetContextId)); - _allocatedMercuryIsolates[targetContextId] = mercuryIsolate; +void _handleAllocateNewMercuryIsolateResult(_AllocateNewMercuryIsolateContext context, Pointer mercury_isolate) { + assert(!_allocatedMercuryIsolates.containsKey(context.contextId)); + _allocatedMercuryIsolates[context.contextId] = mercury_isolate; + context.completer.complete(); +} + +class _AllocateNewMercuryIsolateContext { + Completer completer; + double contextId; + + _AllocateNewMercuryIsolateContext(this.completer, this.contextId); +} + +Future allocateNewMercuryIsolate(bool sync, double newContextId, int syncBufferSize) async { + await waitingSyncTaskComplete(newContextId); + + if (!sync) { + Completer completer = Completer(); + _AllocateNewMercuryIsolateContext context = _AllocateNewMercuryIsolateContext(completer, newContextId); + Pointer> f = Pointer.fromFunction(_handleAllocateNewMercuryIsolateResult); + _allocateNewMercuryIsolate(newContextId, syncBufferSize, dartContext!.pointer, context, f); + return completer.future; + } else { + Pointer mercury_isolate = _allocateNewMercuryIsolateSync(newContextId, dartContext!.pointer); + assert(!_allocatedMercuryIsolates.containsKey(newContextId)); + _allocatedMercuryIsolates[newContextId] = mercury_isolate; + } } typedef NativeInitDartDynamicLinking = Void Function(Pointer data); typedef DartInitDartDynamicLinking = void Function(Pointer data); -final DartInitDartDynamicLinking _initDartDynamicLinking = - MercuryDynamicLibrary.ref.lookup>('init_dart_dynamic_linking').asFunction(); +final DartInitDartDynamicLinking _initDartDynamicLinking = MercuryDynamicLibrary.ref + .lookup>('init_dart_dynamic_linking') + .asFunction(); void initDartDynamicLinking() { _initDartDynamicLinking(NativeApi.initializeApiDLData); @@ -272,8 +533,9 @@ void initDartDynamicLinking() { typedef NativeRegisterDartContextFinalizer = Void Function(Handle object, Pointer dart_context); typedef DartRegisterDartContextFinalizer = void Function(Object object, Pointer dart_context); -final DartRegisterDartContextFinalizer _registerDartContextFinalizer = - MercuryDynamicLibrary.ref.lookup>('register_dart_context_finalizer').asFunction(); +final DartRegisterDartContextFinalizer _registerDartContextFinalizer = MercuryDynamicLibrary.ref + .lookup>('register_dart_context_finalizer') + .asFunction(); void registerDartContextFinalizer(DartContext dartContext) { _registerDartContextFinalizer(dartContext, dartContext.pointer); @@ -291,22 +553,43 @@ void registerPluginByteCode(Uint8List bytecode, String name) { _registerPluginByteCode(bytes, bytecode.length, name.toNativeUtf8()); } -final bool isEnabledLog = !kReleaseMode && Platform.environment['ENABLE_MERCURYJS_LOG'] == 'true'; +typedef NativeCollectNativeProfileData = Void Function( + Pointer mercury_isolatePtr, Pointer> data, Pointer len); +typedef DartCollectNativeProfileData = void Function( + Pointer mercury_isolatePtr, Pointer> data, Pointer len); + +final DartCollectNativeProfileData _collectNativeProfileData = MercuryDynamicLibrary.ref + .lookup>('collectNativeProfileData') + .asFunction(); + +String collectNativeProfileData() { + Pointer> string = malloc.allocate(sizeOf()); + Pointer len = malloc.allocate(sizeOf()); + _collectNativeProfileData(dartContext!.pointer, string, len); -typedef NativeDispatchIsolateTask = Void Function(Int32 contextId, Pointer context, Pointer callback); -typedef DartDispatchIsolateTask = void Function(int contextId, Pointer context, Pointer callback); + return string.value.toDartString(length: len.value); +} + +typedef NativeClearNativeProfileData = Void Function(Pointer mercury_isolatePtr); +typedef DartClearNativeProfileData = void Function(Pointer mercury_isolatePtr); + +final DartClearNativeProfileData _clearNativeProfileData = MercuryDynamicLibrary.ref + .lookup>('clearNativeProfileData') + .asFunction(); -void dispatchIsolateTask(int contextId, Pointer context, Pointer callback) { - // _dispatchIsolateTask(contextId, context, callback); +void clearNativeProfileData() { + _clearNativeProfileData(dartContext!.pointer); } enum IsolateCommandType { + // prof: startRecordingCommand, createGlobal, - createEventTarget, disposeBindingObject, addEvent, removeEvent, + // perf optimize + // prof: finishRecordingCommand, } class IsolateCommandItem extends Struct { @@ -330,6 +613,12 @@ typedef DartGetIsolateCommandItems = Pointer Function(Pointer); final DartGetIsolateCommandItems _getIsolateCommandItems = MercuryDynamicLibrary.ref.lookup>('getIsolateCommandItems').asFunction(); +typedef NativeGetIsolateCommandKindFlags = Uint32 Function(Pointer); +typedef DartGetIsolateCommandKindFlags = int Function(Pointer); + +final DartGetIsolateCommandKindFlags _getIsolateCommandKindFlags = + MercuryDynamicLibrary.ref.lookup>('getIsolateCommandKindFlag').asFunction(); + typedef NativeGetIsolateCommandItemSize = Int64 Function(Pointer); typedef DartGetIsolateCommandItemSize = int Function(Pointer); @@ -342,133 +631,98 @@ typedef DartClearIsolateCommandItems = void Function(Pointer); final DartClearIsolateCommandItems _clearIsolateCommandItems = MercuryDynamicLibrary.ref.lookup>('clearIsolateCommandItems').asFunction(); -class IsolateCommand { - late final IsolateCommandType type; - late final String args; - late final Pointer nativePtr; - late final Pointer nativePtr2; +typedef NativeIsJSThreadBlocked = Int8 Function(Pointer, Double); +typedef DartIsJSThreadBlocked = int Function(Pointer, double); - @override - String toString() { - return 'IsolateCommand(type: $type, args: $args, nativePtr: $nativePtr, nativePtr2: $nativePtr2)'; - } +final DartIsJSThreadBlocked _isJSThreadBlocked = + MercuryDynamicLibrary.ref.lookup>('isJSThreadBlocked').asFunction(); + +bool isJSThreadBlocked(double contextId) { + return _isJSThreadBlocked(dartContext!.pointer, contextId) == 1; } -// struct IsolateCommandItem { -// int32_t type; // offset: 0 ~ 0.5 -// int32_t args_01_length; // offset: 0.5 ~ 1 -// const uint16_t *string_01;// offset: 1 -// void* nativePtr; // offset: 2 -// void* nativePtr2; // offset: 3 -// }; -const int nativeCommandSize = 4; -const int typeAndArgs01LenMemOffset = 0; -const int args01StringMemOffset = 1; -const int nativePtrMemOffset = 2; -const int native2PtrMemOffset = 3; - -// We found there are performance bottleneck of reading native memory with Dart FFI API. -// So we align all Isolate instructions to a whole block of memory, and then convert them into a dart array at one time, -// To ensure the fastest subsequent random access. -List readNativeIsolateCommandToDart(Pointer nativeCommandItems, int commandLength, int contextId) { - List rawMemory = - nativeCommandItems.cast().asTypedList(commandLength * nativeCommandSize).toList(growable: false); - List results = List.generate(commandLength, (int _i) { - int i = _i * nativeCommandSize; - IsolateCommand command = IsolateCommand(); - - int typeArgs01Combine = rawMemory[i + typeAndArgs01LenMemOffset]; - - // int32_t int32_t - // +-------------+-----------------+ - // | type | args_01_length | - // +-------------+-----------------+ - int args01Length = (typeArgs01Combine >> 32).toSigned(32); - int type = (typeArgs01Combine ^ (args01Length << 32)).toSigned(32); - - command.type = IsolateCommandType.values[type]; - - int args01StringMemory = rawMemory[i + args01StringMemOffset]; - if (args01StringMemory != 0) { - Pointer args_01 = Pointer.fromAddress(args01StringMemory); - command.args = uint16ToString(args_01, args01Length); - malloc.free(args_01); - } else { - command.args = ''; - } +void clearIsolateCommand(double contextId) { + assert(_allocatedMercuryIsolates.containsKey(contextId)); - int nativePtrValue = rawMemory[i + nativePtrMemOffset]; - command.nativePtr = nativePtrValue != 0 ? Pointer.fromAddress(rawMemory[i + nativePtrMemOffset]) : nullptr; + _clearIsolateCommandItems(_allocatedMercuryIsolates[contextId]!); +} - int nativePtr2Value = rawMemory[i + native2PtrMemOffset]; - command.nativePtr2 = nativePtr2Value != 0 ? Pointer.fromAddress(nativePtr2Value) : nullptr; +void flushIsolateCommandWithContextId(double contextId, Pointer selfPointer) { + MercuryController? controller = MercuryController.getControllerOfJSContextId(contextId); + if (controller != null) { + flushIsolateCommand(controller.context, selfPointer); + } +} - if (isEnabledLog) { - String printMsg = 'nativePtr: ${command.nativePtr} type: ${command.type} args: ${command.args} nativePtr2: ${command.nativePtr2}'; - print(printMsg); - } - return command; - }, growable: false); +class _NativeCommandData { + static _NativeCommandData empty() { + return _NativeCommandData(0, 0, []); + } - // Clear native command. - _clearIsolateCommandItems(_allocatedMercuryIsolates[contextId]!); + int length; + int kindFlag; + List rawMemory; - return results; + _NativeCommandData(this.kindFlag, this.length, this.rawMemory); } -void clearIsolateCommand(int contextId) { - assert(_allocatedMercuryIsolates.containsKey(contextId)); - _clearIsolateCommandItems(_allocatedMercuryIsolates[contextId]!); -} +_NativeCommandData readNativeIsolateCommandMemory(double contextId) { + Pointer nativeCommandItemPointer = _getIsolateCommandItems(_allocatedMercuryIsolates[contextId]!); + int flag = _getIsolateCommandKindFlags(_allocatedMercuryIsolates[contextId]!); + int commandLength = _getIsolateCommandItemSize(_allocatedMercuryIsolates[contextId]!); -void flushIsolateCommandWithContextId(int contextId) { - MercuryController? controller = MercuryController.getControllerOfJSContextId(contextId); - if (controller != null) { - flushIsolateCommand(controller.context); + if (commandLength == 0 || nativeCommandItemPointer == nullptr) { + return _NativeCommandData.empty(); } + + List rawMemory = + nativeCommandItemPointer.cast().asTypedList((commandLength) * nativeCommandSize).toList(growable: false); + _clearIsolateCommandItems(_allocatedMercuryIsolates[contextId]!); + + return _NativeCommandData(flag, commandLength, rawMemory); } -void flushIsolateCommand(MercuryContextController context) { +void flushIsolateCommand(MercuryContextController context, Pointer selfPointer) { assert(_allocatedMercuryIsolates.containsKey(context.contextId)); - Pointer nativeCommandItems = _getIsolateCommandItems(_allocatedMercuryIsolates[context.contextId]!); - int commandLength = _getIsolateCommandItemSize(_allocatedMercuryIsolates[context.contextId]!); + if (context.disposed) return; - if (commandLength == 0 || nativeCommandItems == nullptr) { - return; - } + // if (enableMercuryProfileTracking) { + // MercuryProfiler.instance.startTrackIsolateCommand(); + // MercuryProfiler.instance.startTrackIsolateCommandStep('readNativeIsolateCommandMemory'); + // } - List commands = readNativeIsolateCommandToDart(nativeCommandItems, commandLength, context.contextId); + _NativeCommandData rawCommands = readNativeIsolateCommandMemory(context.contextId); - // For new isolate commands, we needs to tell engine to update frames. - for (int i = 0; i < commandLength; i++) { - IsolateCommand command = commands[i]; - IsolateCommandType commandType = command.type; - Pointer nativePtr = command.nativePtr; + // prof: + // if (enableMercuryProfileTracking) { + // MercuryProfiler.instance.finishTrackIsolateCommandStep(); + // } - try { - switch (commandType) { - case IsolateCommandType.createGlobal: - context.initGlobal(context, nativePtr.cast()); - break; - case IsolateCommandType.createEventTarget: - context.createEventTarget(context, command.args, nativePtr.cast()); - break; - case IsolateCommandType.disposeBindingObject: - context.disposeBindingObject(context, nativePtr.cast()); - break; - case IsolateCommandType.addEvent: - Pointer eventListenerOptions = command.nativePtr2.cast(); - context.addEvent(nativePtr.cast(), command.args, addEventListenerOptions: eventListenerOptions); - break; - case IsolateCommandType.removeEvent: - bool isCapture = command.nativePtr2.address == 1; - context.removeEvent(nativePtr.cast(), command.args, isCapture: isCapture); - break; - default: - break; - } - } catch (e, stack) { - print('$e\n$stack'); - } + List? commands; + if (rawCommands.rawMemory.isNotEmpty) { + // prof: + // if (enableMercuryProfileTracking) { + // MercuryProfiler.instance.startTrackIsolateCommandStep('nativeIsolateCommandToDart'); + // } + + commands = nativeIsolateCommandToDart(rawCommands.rawMemory, rawCommands.length, context.contextId); + + // prof: + // if (enableMercuryProfileTracking) { + // MercuryProfiler.instance.finishTrackIsolateCommandStep(); + // MercuryProfiler.instance.startTrackIsolateCommandStep('execIsolateCommands'); + // } + + execIsolateCommands(context, commands); + + // prof: + // if (enableMercuryProfileTracking) { + // MercuryProfiler.instance.finishTrackIsolateCommandStep(); + // } } + + // prof: + // if (enableMercuryProfileTracking) { + // MercuryProfiler.instance.finishTrackIsolateCommand(); + // } } diff --git a/mercury/lib/src/devtools/inspector.dart b/mercury/lib/src/devtools/inspector.dart index 8d9f832..9a30cb8 100644 --- a/mercury/lib/src/devtools/inspector.dart +++ b/mercury/lib/src/devtools/inspector.dart @@ -55,7 +55,7 @@ class InspectorMethodResult { } class InspectorReload { - int contextId; + double contextId; InspectorReload(this.contextId); } diff --git a/mercury/lib/src/foundation/binding.dart b/mercury/lib/src/foundation/binding.dart index ed4713a..1ecc78b 100644 --- a/mercury/lib/src/foundation/binding.dart +++ b/mercury/lib/src/foundation/binding.dart @@ -1,19 +1,20 @@ /* * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. + * Copyright (C) 2022-present The Mercury authors. All rights reserved. */ +import 'dart:async'; import 'dart:ffi'; -import 'dart:collection'; import 'package:collection/collection.dart'; import 'package:ffi/ffi.dart'; import 'package:flutter/foundation.dart'; import 'package:mercuryjs/bridge.dart'; +import 'package:mercuryjs/foundation.dart'; import 'package:mercuryjs/launcher.dart'; typedef BindingObjectOperation = void Function(MercuryContextController? context, BindingObject bindingObject); class BindingContext { - final int contextId; + final double contextId; final MercuryContextController context; final Pointer pointer; @@ -56,12 +57,9 @@ abstract class BindingObject extends Iterable { static BindingObjectOperation? bind; static BindingObjectOperation? unbind; - // To make sure same kind of WidgetElement only sync once. - static final Map _alreadySyncClasses = {}; - final BindingContext? _context; - int? get contextId => _context?.contextId; + double? get contextId => _context?.contextId; final MercuryContextController? _ownerContext; MercuryContextController get ownerContext => _ownerContext!; @@ -69,50 +67,49 @@ abstract class BindingObject extends Iterable { BindingObject([BindingContext? context]) : _context = context, _ownerContext = context?.context { _bind(_ownerContext); - initializeProperties(_properties); - initializeMethods(_methods); + } - if (!_alreadySyncClasses.containsKey(runtimeType)) { - bool success = _syncPropertiesAndMethodsToNativeSlow(); - if (success) { - _alreadySyncClasses[runtimeType] = true; - } + // Bind dart side object method to receive invoking from native side. + void _bind(MercuryContextController? ownerContext) { + if (bind != null) { + bind!(ownerContext, this); } } - bool _syncPropertiesAndMethodsToNativeSlow() { - assert(pointer != null); - if (pointer!.ref.invokeBindingMethodFromDart == nullptr) return false; + void _unbind(MercuryContextController? ownerContext) { + if (unbind != null) { + unbind!(ownerContext, this); + } + } - List properties = _properties.keys.toList(growable: false); - List syncMethods = []; - List asyncMethods = []; + @override + Iterator get iterator => Iterable.empty().iterator; - _methods.forEach((key, method) { - if (method is BindingObjectMethodSync) { - syncMethods.add(key); - } else if (method is AsyncBindingObjectMethod) { - asyncMethods.add(key); - } - }); + @mustCallSuper + void dispose(); +} - Pointer arguments = malloc.allocate(sizeOf() * 3); - toNativeValue(arguments.elementAt(0), properties); - toNativeValue(arguments.elementAt(1), syncMethods); - toNativeValue(arguments.elementAt(2), asyncMethods); +abstract class StaticBindingObject extends BindingObject { + StaticBindingObject(BindingContext context): super(context) { + context.pointer.ref.extra = buildExtraNativeData(); + } - DartInvokeBindingMethodsFromDart f = pointer!.ref.invokeBindingMethodFromDart.asFunction(); - Pointer returnValue = malloc.allocate(sizeOf()); + Pointer buildExtraNativeData(); - Pointer method = malloc.allocate(sizeOf()); - toNativeValue(method, 'syncPropertiesAndMethods'); - f(pointer!, returnValue, method, 3, arguments, {}); - malloc.free(arguments); - return fromNativeValue(ownerContext, returnValue) == true; + @override + void dispose() { + malloc.free(pointer!.ref.extra); } +} - final SplayTreeMap _properties = SplayTreeMap(); - final SplayTreeMap _methods = SplayTreeMap(); +abstract class DynamicBindingObject extends BindingObject { + DynamicBindingObject([BindingContext? context]): super(context) { + initializeProperties(_properties); + initializeMethods(_methods); + } + + final Map _properties = {}; + final Map _methods = {}; @mustCallSuper void initializeProperties(Map properties); @@ -120,17 +117,24 @@ abstract class BindingObject extends Iterable { @mustCallSuper void initializeMethods(Map methods); - // Bind dart side object method to receive invoking from native side. - void _bind(MercuryContextController? ownerContext) { - if (bind != null) { - bind!(ownerContext, this); - } - } + void nativeGetPropertiesAndMethods(Pointer data) async { + assert(pointer != null); - void _unbind(MercuryContextController? ownerContext) { - if (unbind != null) { - unbind!(ownerContext, this); - } + List properties = _properties.keys.toList(growable: false); + List syncMethods = []; + List asyncMethods = []; + + _methods.forEach((key, method) { + if (method is BindingObjectMethodSync) { + syncMethods.add(key); + } else if (method is AsyncBindingObjectMethod) { + asyncMethods.add(key); + } + }); + + toNativeValue(data.elementAt(0), properties); + toNativeValue(data.elementAt(1), syncMethods); + toNativeValue(data.elementAt(2), asyncMethods); } // Call a method, eg: @@ -148,9 +152,6 @@ abstract class BindingObject extends Iterable { return null; } - @override - Iterator get iterator => Iterable.empty().iterator; - dynamic _invokeBindingMethodAsync(String method, List args) { BindingObjectMethod? fn = _methods[method]; if (fn == null) { @@ -158,32 +159,32 @@ abstract class BindingObject extends Iterable { } if (fn is AsyncBindingObjectMethod) { - int contextId = args[0]; + double contextId = args[0]; // Async callback should hold a context to store the current execution environment. Pointer callbackContext = (args[1] as Pointer).cast(); DartAsyncAnonymousFunctionCallback callback = - (args[2] as Pointer).cast>().asFunction(); + (args[2] as Pointer).cast>().asFunction(); List functionArguments = args.sublist(3); Future p = fn.call(functionArguments); p.then((result) { Stopwatch? stopwatch; - if (isEnabledLog) { + if (enableMercuryCommandLog) { stopwatch = Stopwatch()..start(); } Pointer nativeValue = malloc.allocate(sizeOf()); toNativeValue(nativeValue, result, this); callback(callbackContext, nativeValue, contextId, nullptr); - if (isEnabledLog) { + if (enableMercuryCommandLog) { print('AsyncAnonymousFunction call resolved callback: $method arguments:[$result] time: ${stopwatch!.elapsedMicroseconds}us'); } }).catchError((e, stack) { String errorMessage = '$e\n$stack'; Stopwatch? stopwatch; - if (isEnabledLog) { + if (enableMercuryCommandLog) { stopwatch = Stopwatch()..start(); } callback(callbackContext, nullptr, contextId, errorMessage.toNativeUtf8()); - if (isEnabledLog) { + if (enableMercuryCommandLog) { print('AsyncAnonymousFunction call rejected callback: $method, arguments:[$errorMessage] time: ${stopwatch!.elapsedMicroseconds}us'); } }); @@ -192,7 +193,7 @@ abstract class BindingObject extends Iterable { return null; } - @mustCallSuper + @override void dispose() async { _unbind(_ownerContext); _properties.clear(); @@ -200,100 +201,169 @@ abstract class BindingObject extends Iterable { } } -dynamic getterBindingCall(BindingObject bindingObject, List args) { +dynamic getterBindingCall(BindingObject bindingObject, List args) { // { BindingOpItem? profileOp } prof assert(args.length == 1); - BindingObjectProperty? property = bindingObject._properties[args[0]]; + BindingObjectProperty? property = (bindingObject as DynamicBindingObject)._properties[args[0]]; Stopwatch? stopwatch; - if (isEnabledLog && property != null) { + if (enableMercuryCommandLog && property != null) { stopwatch = Stopwatch()..start(); } + // prof: + // if (enableMercuryProfileTracking) { + // MercuryProfiler.instance.startTrackBindingSteps(profileOp!, 'getterBindingCall'); + // } + + dynamic result = null; if (property != null) { - dynamic result = property.getter(); - if (isEnabledLog) { + result = property.getter(); + if (enableMercuryCommandLog) { print('$bindingObject getBindingProperty key: ${args[0]} result: ${property.getter()} time: ${stopwatch!.elapsedMicroseconds}us'); } - return result; } - return null; + + // prof: + // if (enableMercuryProfileTracking) { + // MercuryProfiler.instance.finishTrackBindingSteps(profileOp!); + // } + + return result; } -dynamic setterBindingCall(BindingObject bindingObject, List args) { +dynamic setterBindingCall(BindingObject bindingObject, List args) { // { BindingOpItem? profileOp } prof assert(args.length == 2); - if (isEnabledLog) { + if (enableMercuryCommandLog) { print('$bindingObject setBindingProperty key: ${args[0]} value: ${args[1]}'); } + // prof: + // if (enableMercuryProfileTracking) { + // MercuryProfiler.instance.startTrackBindingSteps(profileOp!, 'setterBindingCall'); + // } + String key = args[0]; dynamic value = args[1]; - BindingObjectProperty? property = bindingObject._properties[key]; + BindingObjectProperty? property = (bindingObject as DynamicBindingObject)._properties[key]; if (property != null && property.setter != null) { property.setter!(value); } + // prof: + // if (enableMercuryProfileTracking) { + // MercuryProfiler.instance.finishTrackBindingSteps(profileOp!); + // } + return true; } -dynamic getPropertyNamesBindingCall(BindingObject bindingObject, List args) { - List properties = bindingObject._properties.keys.toList(); +dynamic getPropertyNamesBindingCall(BindingObject bindingObject, List args) { // { BindingOpItem? profileOp } prof + assert(bindingObject is DynamicBindingObject); + + // prof: + // if (enableMercuryProfileTracking) { + // MercuryProfiler.instance.startTrackBindingSteps(profileOp!, 'getPropertyNamesBindingCall'); + // } + + List properties = (bindingObject as DynamicBindingObject)._properties.keys.toList(); List methods = bindingObject._methods.keys.toList(); properties.addAll(methods); - if (isEnabledLog) { + if (enableMercuryCommandLog) { print('$bindingObject getPropertyNamesBindingCall value: $properties'); } + // prof: + // if (enableMercuryProfileTracking) { + // MercuryProfiler.instance.finishTrackBindingSteps(profileOp!); + // } + return properties; } -dynamic invokeBindingMethodSync(BindingObject bindingObject, List args) { +dynamic invokeBindingMethodSync(BindingObject bindingObject, List args) { // , { BindingOpItem? profileOp } prof Stopwatch? stopwatch; - if (isEnabledLog) { + if (enableMercuryCommandLog) { stopwatch = Stopwatch()..start(); } - dynamic result = bindingObject._invokeBindingMethodSync(args[0], args.slice(1)); - if (isEnabledLog) { + + // prof: + // if (enableMercuryProfileTracking) { + // MercuryProfiler.instance.startTrackBindingSteps(profileOp!, 'invokeBindingMethodSync'); + // } + + assert(bindingObject is DynamicBindingObject); + dynamic result = (bindingObject as DynamicBindingObject)._invokeBindingMethodSync(args[0], args.slice(1)); + if (enableMercuryCommandLog) { print('$bindingObject invokeBindingMethodSync method: ${args[0]} args: ${args.slice(1)} time: ${stopwatch!.elapsedMilliseconds}ms'); } + + // prof: + // if (enableMercuryProfileTracking) { + // MercuryProfiler.instance.finishTrackBindingSteps(profileOp!); + // } + return result; } -dynamic invokeBindingMethodAsync(BindingObject bindingObject, List args) { - if (isEnabledLog) { +dynamic invokeBindingMethodAsync(BindingObject bindingObject, List args) { // , { BindingOpItem? profileOp } prof + if (enableMercuryCommandLog) { print('$bindingObject invokeBindingMethodSync method: ${args[0]} args: ${args.slice(1)}'); } - return bindingObject._invokeBindingMethodAsync(args[0], args.slice(1)); + return (bindingObject as DynamicBindingObject)._invokeBindingMethodAsync(args[0], args.slice(1)); } // This function receive calling from binding side. -void invokeBindingMethodFromNativeImpl(int contextId, Pointer nativeBindingObject, +void invokeBindingMethodFromNativeImpl(double contextId, Pointer nativeBindingObject, Pointer returnValue, Pointer nativeMethod, int argc, Pointer argv) { + + // prof: + // BindingOpItem? currentProfileOp; + // if (enableMercuryProfileTracking) { + // currentProfileOp = MercuryProfiler.instance.startTrackBinding(profileId); + // } + MercuryController controller = MercuryController.getControllerOfJSContextId(contextId)!; + + // prof: + // if (enableMercuryProfileTracking) { + // MercuryProfiler.instance.startTrackBindingSteps(currentProfileOp!, 'fromNativeValue'); + // } + dynamic method = fromNativeValue(controller.context, nativeMethod); List values = List.generate(argc, (i) { Pointer nativeValue = argv.elementAt(i); return fromNativeValue(controller.context, nativeValue); }); + // prof: + // if (enableMercuryProfileTracking) { + // MercuryProfiler.instance.finishTrackBindingSteps(currentProfileOp!); + // } + BindingObject bindingObject = controller.context.getBindingObject(nativeBindingObject); + // prof: + // if (enableMercuryProfileTracking) { + // MercuryProfiler.instance.startTrackBindingSteps(currentProfileOp!, 'invokeDartMethods'); + // } + var result = null; try { // Method is binding call method operations from internal. if (method is int) { // Get and setter ops - result = bindingCallMethodDispatchTable[method](bindingObject, values); + result = bindingCallMethodDispatchTable[method](bindingObject, values); // , profileOp: currentProfileOp } else { BindingObject bindingObject = controller.context.getBindingObject(nativeBindingObject); // invokeBindingMethod directly Stopwatch? stopwatch; - if (isEnabledLog) { + if (enableMercuryCommandLog) { stopwatch = Stopwatch()..start(); } - result = bindingObject._invokeBindingMethodSync(method, values); - if (isEnabledLog) { + result = (bindingObject as DynamicBindingObject)._invokeBindingMethodSync(method, values); + if (enableMercuryCommandLog) { print('$bindingObject invokeBindingMethod method: $method args: $values result: $result time: ${stopwatch!.elapsedMicroseconds}us'); } } @@ -302,4 +372,13 @@ void invokeBindingMethodFromNativeImpl(int contextId, Pointer resolve(int? contextId) async { + Future resolve(double? contextId) async { if (isResolved) return; // Source is input by user, do not trust it's a valid URL. @@ -197,7 +199,7 @@ class NetworkBundle extends MercuryBundle { Map? additionalHttpHeaders = {}; @override - Future resolve(int? contextId) async { + Future resolve(double? contextId) async { super.resolve(contextId); final HttpClientRequest request = await _sharedHttpClient.getUrl(_uri!); @@ -236,7 +238,7 @@ class AssetsBundle extends MercuryBundle with _ExtensionContentTypeResolver { AssetsBundle(String url) : super(url); @override - Future resolve(int? contextId) async { + Future resolve(double? contextId) async { super.resolve(contextId); final Uri? _resolvedUri = resolvedUri; if (_resolvedUri != null) { @@ -268,7 +270,7 @@ class FileBundle extends MercuryBundle with _ExtensionContentTypeResolver { FileBundle(String url) : super(url); @override - Future resolve(int? contextId) async { + Future resolve(double? contextId) async { super.resolve(contextId); Uri uri = _uri!; diff --git a/mercury/lib/src/foundation/bytecode_cache.dart b/mercury/lib/src/foundation/bytecode_cache.dart index 3b4f673..f5ba037 100644 --- a/mercury/lib/src/foundation/bytecode_cache.dart +++ b/mercury/lib/src/foundation/bytecode_cache.dart @@ -7,6 +7,7 @@ import 'package:archive/archive.dart'; import 'package:path/path.dart' as path; import 'package:flutter/foundation.dart'; import 'package:quiver/collection.dart'; +import 'package:quiver/core.dart'; import 'package:mercuryjs/bridge.dart'; import 'package:mercuryjs/foundation.dart'; @@ -58,7 +59,7 @@ class QuickJSByteCodeCacheObject { try { bytes = await cacheFile.readAsBytes(); - int fileCheckSum = getCrc32(bytes!.toList()); + int fileCheckSum = getCrc32(bytes as List); bool isCheckSumExist = await _checksum.exists(); @@ -86,7 +87,7 @@ class QuickJSByteCodeCacheObject { Future write() async { if (bytes != null) { - int fileSum = getCrc32(bytes!.toList()); + int fileSum = getCrc32(bytes!); File tmp = File(path.join(cacheDirectory, '$hash.tmp')); await Future.wait([ @@ -132,19 +133,24 @@ class QuickJSByteCodeCache { return _cacheDirectory = cacheDirectory; } - static String _getCacheHash(String code) { + static String _getCacheHashSlow(Uint8List code) { MercuryInfo mercuryInfo = getMercuryInfo(); // Uri uriWithoutFragment = uri; // return uriWithoutFragment.toString(); - return '%${code.hashCode}_${mercuryInfo.appRevision}%'; + return '%${hashObjects(code)}_${MercuryInfo.appRevision}%'; + } + + static String _getCacheHashFast(String originalCacheKey) { + MercuryInfo mercuryInfo = getMercuryInfo(); + return '%${originalCacheKey}_${MercuryInfo.appRevision}%'; } // Get the CacheObject by uri, no validation needed here. - static Future getCacheObject(String code) async { + static Future getCacheObject(Uint8List codeBytes, { String? cacheKey }) async { QuickJSByteCodeCacheObject cacheObject; // L2 cache in memory. - final String hash = _getCacheHash(code); + final String hash = cacheKey != null ? _getCacheHashFast(cacheKey) : _getCacheHashSlow(codeBytes); if (_caches.containsKey(hash)) { cacheObject = _caches[hash]!; } else { @@ -159,8 +165,8 @@ class QuickJSByteCodeCache { } // Add or update the httpCacheObject to memory cache. - static void putObject(String code, Uint8List bytes) async { - final String key = _getCacheHash(code); + static Future putObject(Uint8List codeBytes, Uint8List bytes, { String? cacheKey }) async { + final String key = cacheKey != null ? _getCacheHashFast(cacheKey) : _getCacheHashSlow(codeBytes); final Directory cacheDirectory = await getCacheDirectory(); QuickJSByteCodeCacheObject cacheObject = @@ -170,13 +176,8 @@ class QuickJSByteCodeCache { await cacheObject.write(); } - static void removeObject(String code) { - final String key = _getCacheHash(code); - _caches.remove(key); - } - - static bool isCodeNeedCache(String source) { + static bool isCodeNeedCache(Uint8List codeBytes) { return QuickJSByteCodeCacheObject.cacheMode == ByteCodeCacheMode.DEFAULT && - source.length > 1024 * 10; // >= 50 KB + codeBytes.length > 1024 * 10; // >= 50 KB } } diff --git a/mercury/lib/src/foundation/convert.dart b/mercury/lib/src/foundation/convert.dart index a3cd647..77e13ff 100644 --- a/mercury/lib/src/foundation/convert.dart +++ b/mercury/lib/src/foundation/convert.dart @@ -7,6 +7,31 @@ import 'dart:convert'; import 'package:flutter/foundation.dart'; +bool isValidUTF8String(Uint8List data) { + int count = 0; + for (int byte in data) { + if (count == 0) { + if ((byte >> 7) == 0) { + continue; + } else if ((byte >> 5) == 0x6) { // 0b110 in hex + count = 1; + } else if ((byte >> 4) == 0xE) { // 0b1110 in hex + count = 2; + } else if ((byte >> 3) == 0x1E) { // 0b11110 in hex + count = 3; + } else { + return false; // Invalid starting byte + } + } else { + if ((byte >> 6) != 0x2) { // 0b10 in hex + return false; // Not a valid continuation byte + } + count--; + } + } + return count == 0; +} + FutureOr resolveStringFromData(final List data, {Codec codec = utf8, bool preferSync = false}) async { if (codec == utf8) { return _resolveUtf8StringFromData(data, preferSync); diff --git a/mercury/lib/src/foundation/http_cache.dart b/mercury/lib/src/foundation/http_cache.dart index 481d867..e558bae 100644 --- a/mercury/lib/src/foundation/http_cache.dart +++ b/mercury/lib/src/foundation/http_cache.dart @@ -44,7 +44,7 @@ class HttpCacheController { return _cacheDirectory = cacheDirectory; } - static String _getCacheKey(Uri uri) { + static String getCacheKey(Uri uri) { // Fragment not included in cache. Uri uriWithoutFragment = uri; if (uriWithoutFragment.hasFragment) { @@ -81,7 +81,7 @@ class HttpCacheController { HttpCacheObject cacheObject; // L2 cache in memory. - final String key = _getCacheKey(uri); + final String key = getCacheKey(uri); if (_caches.containsKey(key)) { cacheObject = _caches[key]!; } else { @@ -101,12 +101,12 @@ class HttpCacheController { if (_caches.length == _maxCachedObjects) { _caches.remove(_caches.lastKey()); } - final String key = _getCacheKey(uri); + final String key = getCacheKey(uri); _caches.update(key, (value) => cacheObject, ifAbsent: () => cacheObject); } void removeObject(Uri uri) { - final String key = _getCacheKey(uri); + final String key = getCacheKey(uri); _caches.remove(key); } @@ -125,7 +125,7 @@ class HttpCacheController { if (response.statusCode == HttpStatus.ok) { // Create cache object. HttpCacheObject cacheObject = - HttpCacheObject.fromResponse(_getCacheKey(request.uri), response, (await getCacheDirectory()).path); + HttpCacheObject.fromResponse(getCacheKey(request.uri), response, (await getCacheDirectory()).path); // Cache the object. if (cacheObject.valid) { diff --git a/mercury/lib/src/foundation/http_client_request.dart b/mercury/lib/src/foundation/http_client_request.dart index 2a642ef..1b90191 100644 --- a/mercury/lib/src/foundation/http_client_request.dart +++ b/mercury/lib/src/foundation/http_client_request.dart @@ -103,7 +103,7 @@ class ProxyHttpClientRequest extends HttpClientRequest { @override Future close() async { - int? contextId = MercuryHttpOverrides.getContextHeader(headers); + double? contextId = MercuryHttpOverrides.getContextHeader(headers); HttpClientRequest request = this; if (contextId != null) { diff --git a/mercury/lib/src/foundation/http_overrides.dart b/mercury/lib/src/foundation/http_overrides.dart index fd40889..914d814 100644 --- a/mercury/lib/src/foundation/http_overrides.dart +++ b/mercury/lib/src/foundation/http_overrides.dart @@ -19,35 +19,35 @@ class MercuryHttpOverrides extends HttpOverrides { return _instance!; } - static int? getContextHeader(HttpHeaders headers) { - String? intVal = headers.value(HttpHeaderContext); - if (intVal == null) { + static double? getContextHeader(HttpHeaders headers) { + String? doubleVal = headers.value(HttpHeaderContext); + if (doubleVal == null) { return null; } - return int.tryParse(intVal); + return double.tryParse(doubleVal); } - static void setContextHeader(HttpHeaders headers, int contextId) { + static void setContextHeader(HttpHeaders headers, double contextId) { headers.set(HttpHeaderContext, contextId.toString()); } final HttpOverrides? parentHttpOverrides = HttpOverrides.current; - final Map _contextIdToHttpClientInterceptorMap = {}; + final Map _contextIdToHttpClientInterceptorMap = {}; - void registerMercuryContext(int contextId, HttpClientInterceptor httpClientInterceptor) { + void registerMercuryContext(double contextId, HttpClientInterceptor httpClientInterceptor) { _contextIdToHttpClientInterceptorMap[contextId] = httpClientInterceptor; } - bool unregisterMercuryContext(int contextId) { + bool unregisterMercuryContext(double contextId) { // Returns true if [value] was in the map, false otherwise. return _contextIdToHttpClientInterceptorMap.remove(contextId) != null; } - bool hasInterceptor(int contextId) { + bool hasInterceptor(double contextId) { return _contextIdToHttpClientInterceptorMap.containsKey(contextId); } - HttpClientInterceptor getInterceptor(int contextId) { + HttpClientInterceptor getInterceptor(double contextId) { return _contextIdToHttpClientInterceptorMap[contextId]!; } @@ -77,7 +77,7 @@ class MercuryHttpOverrides extends HttpOverrides { } } -MercuryHttpOverrides setupHttpOverrides(HttpClientInterceptor? httpClientInterceptor, {required int contextId}) { +MercuryHttpOverrides setupHttpOverrides(HttpClientInterceptor? httpClientInterceptor, {required double contextId}) { final MercuryHttpOverrides httpOverrides = MercuryHttpOverrides.instance(); if (httpClientInterceptor != null) { @@ -98,8 +98,8 @@ String getOrigin(Uri uri) { } // @TODO: Remove controller dependency. -Uri getEntrypointUri(int? contextId) { +Uri getEntrypointUri(double? contextId) { MercuryController? controller = MercuryController.getControllerOfJSContextId(contextId); String url = controller?.url ?? ''; - return Uri.tryParse(url) ?? MercuryController.fallbackBundleUri(contextId ?? 0); + return Uri.tryParse(url) ?? MercuryController.fallbackBundleUri(contextId ?? 0.0); } diff --git a/mercury/lib/src/foundation/queue.dart b/mercury/lib/src/foundation/queue.dart index 7cb1007..e523a8b 100644 --- a/mercury/lib/src/foundation/queue.dart +++ b/mercury/lib/src/foundation/queue.dart @@ -21,7 +21,8 @@ class _QueuedFuture { completer.complete(null); } await Future.microtask(() {}); - } catch (e) { + } catch (e, stack) { + print('$e\n$stack'); completer.completeError(e); } finally { if (onComplete != null) onComplete!(); diff --git a/mercury/lib/src/foundation/type.dart b/mercury/lib/src/foundation/type.dart index 13493c6..ace82c1 100644 --- a/mercury/lib/src/foundation/type.dart +++ b/mercury/lib/src/foundation/type.dart @@ -8,18 +8,3 @@ T castToType(value) { assert(value is T, '$value is not or not a subtype of $T'); return value as T; } - -class Dimension { - const Dimension(this.width, this.height); - - final int width; - final int height; - - @override - bool operator ==(Object other) { - return other is Dimension && other.width == width && other.height == height; - } - - @override - int get hashCode => Object.hash(width, height); -} diff --git a/mercury/lib/src/global/event_target.dart b/mercury/lib/src/global/event_target.dart index e3f0ee2..8b5642f 100644 --- a/mercury/lib/src/global/event_target.dart +++ b/mercury/lib/src/global/event_target.dart @@ -6,9 +6,9 @@ import 'package:flutter/foundation.dart'; import 'package:mercuryjs/src/global/event.dart'; import 'package:mercuryjs/foundation.dart'; -typedef EventHandler = void Function(Event event); +typedef EventHandler = Future Function(Event event); -abstract class EventTarget extends BindingObject { +abstract class EventTarget extends DynamicBindingObject { EventTarget(BindingContext? context) : super(context); bool _disposed = false; @@ -62,16 +62,16 @@ abstract class EventTarget extends BindingObject { } @mustCallSuper - void dispatchEvent(Event event) { + Future dispatchEvent(Event event) async { if (_disposed) return; event.target = this; - _handlerCaptureEvent(event); - _dispatchEventInDOM(event); + await _handlerCaptureEvent(event); + await _dispatchEventInGlobal(event); } - void _handlerCaptureEvent(Event event) { + Future _handlerCaptureEvent(Event event) async { - parentEventTarget?._handlerCaptureEvent(event); + await parentEventTarget?._handlerCaptureEvent(event); String eventType = event.type; List? existHandler = _eventCaptureHandlers[eventType]; if (existHandler != null) { @@ -81,7 +81,7 @@ abstract class EventTarget extends BindingObject { // with error, copy the handlers here. try { for (EventHandler handler in [...existHandler]) { - handler(event); + await handler(event); } } catch (e, stack) { print('$e\n$stack'); @@ -90,7 +90,7 @@ abstract class EventTarget extends BindingObject { } } // Refs: https://github.com/WebKit/WebKit/blob/main/Source/WebCore/dom/EventDispatcher.cpp#L85 - void _dispatchEventInDOM(Event event) { + Future _dispatchEventInGlobal(Event event) async { // TODO: Invoke capturing event listeners in the reverse order. String eventType = event.type; @@ -102,7 +102,7 @@ abstract class EventTarget extends BindingObject { // with error, copy the handlers here. try { for (EventHandler handler in [...existHandler]) { - handler(event); + await handler(event); } } catch (e, stack) { print('$e\n$stack'); @@ -112,7 +112,7 @@ abstract class EventTarget extends BindingObject { // Invoke bubbling event listeners. if (event.bubbles && !event.propagationStopped) { - parentEventTarget?._dispatchEventInDOM(event); + await parentEventTarget?._dispatchEventInGlobal(event); } } diff --git a/mercury/lib/src/global/global.dart b/mercury/lib/src/global/global.dart index 24de24c..879a4f1 100644 --- a/mercury/lib/src/global/global.dart +++ b/mercury/lib/src/global/global.dart @@ -11,7 +11,10 @@ const String GLOBAL = 'GLOBAL'; class Global extends EventTarget { Global(BindingContext? context) - : super(context); + : super(context) { + BindingBridge.listenEvent(this, 'load'); + BindingBridge.listenEvent(this, 'gcopen'); + } @override EventTarget? get parentEventTarget => null; @@ -25,11 +28,11 @@ class Global extends EventTarget { } @override - void dispatchEvent(Event event) { + Future dispatchEvent(Event event) async { if (contextId != null && event.type == EVENT_LOAD || event.type == EVENT_ERROR) { - flushIsolateCommandWithContextId(contextId!); + flushIsolateCommandWithContextId(contextId!, pointer!); } - super.dispatchEvent(event); + return super.dispatchEvent(event); } @override diff --git a/mercury/lib/src/launcher/controller.dart b/mercury/lib/src/launcher/controller.dart index 7f05e8b..06705c0 100644 --- a/mercury/lib/src/launcher/controller.dart +++ b/mercury/lib/src/launcher/controller.dart @@ -13,6 +13,8 @@ import 'package:ffi/ffi.dart'; import 'package:flutter/foundation.dart'; import 'package:mercuryjs/devtools.dart'; import 'package:mercuryjs/mercuryjs.dart'; +import 'package:mercuryjs/src/bridge/isolate_command.dart'; +import 'package:mercuryjs/src/bridge/multiple_thread.dart'; // Error handler when load bundle failed. typedef LoadErrorHandler = void Function(FlutterError error, StackTrace stack); @@ -40,8 +42,8 @@ abstract class DevToolsService { /// More detail see [InspectPageModule.handleReloadPage]. static DevToolsService? prevDevTools; - static final Map _contextDevToolMap = {}; - static DevToolsService? getDevToolOfContextId(int contextId) { + static final Map _contextDevToolMap = {}; + static DevToolsService? getDevToolOfContextId(double contextId) { return _contextDevToolMap[contextId]; } @@ -110,19 +112,29 @@ class MercuryContextController { MercuryDispatcher? dispatcher; + final List> pendingIsolateCommands = []; + + MercuryThread runningThread; + + bool firstLoad = true; + MercuryContextController({ this.enableDebug = false, - required this.rootController}) { + required this.rootController, + required this.runningThread}) { + } + + Future initialize() async { if (enableDebug) { debugDefaultTargetPlatformOverride = TargetPlatform.fuchsia; } defineDispatcher(); BindingBridge.setup(); - _contextId = initBridge(this); + _contextId = await initBridge(this, runningThread); Future.microtask(() { // Execute IsolateCommand.createGlobal to initialize the global. - flushIsolateCommand(this); + flushIsolateCommand(this, global.pointer!); }); } @@ -146,8 +158,8 @@ class MercuryContextController { } // Index value which identify javascript runtime context. - late int _contextId; - int get contextId => _contextId; + late double _contextId; + double get contextId => _contextId; // Enable print debug message when rendering. bool enableDebug; @@ -161,20 +173,28 @@ class MercuryContextController { void initGlobal(MercuryContextController context, Pointer pointer) { global = Global(BindingContext(context, _contextId, pointer)); + + firstLoad = false; + + // 3 seconds should be enough for page loading, make sure the JavaScript GC was opened. + Timer(Duration(seconds: 3), () { + global.dispatchEvent(Event('gcopen')); + }); } - void evaluateJavaScripts(String code) async { + Future evaluateJavaScripts(String code) async { assert(!_disposed, 'Mercury have already disposed'); - await evaluateScripts(_contextId, code); + List data = utf8.encode(code); + await evaluateScripts(_contextId, Uint8List.fromList(data)); } // Dispose controller and recycle all resources. - void dispose() { + Future dispose() async { _disposed = true; clearIsolateCommand(_contextId); - disposeMercuryIsolate(_contextId); + await disposeMercuryIsolate(false, _contextId); _nativeObjects.forEach((key, object) { object.dispose(); @@ -237,12 +257,15 @@ class MercuryModuleController with TimerMixin { late ModuleManager _moduleManager; ModuleManager get moduleManager => _moduleManager; - MercuryModuleController(MercuryController controller, int contextId) { + MercuryModuleController(MercuryController controller, double contextId) { _moduleManager = ModuleManager(controller, contextId); } + bool _initialized = false; Future initialize() async { + if (_initialized) return; await _moduleManager.initialize(); + _initialized = true; } void dispose() { @@ -252,12 +275,12 @@ class MercuryModuleController with TimerMixin { } class MercuryController { - static final Map _controllerMap = {}; - static final Map _nameIdMap = {}; + static final Map _controllerMap = {}; + static final Map _nameIdMap = {}; UriParser? uriParser; - static MercuryController? getControllerOfJSContextId(int? contextId) { + static MercuryController? getControllerOfJSContextId(double? contextId) { if (!_controllerMap.containsKey(contextId)) { return null; } @@ -265,13 +288,13 @@ class MercuryController { return _controllerMap[contextId]; } - static Map getControllerMap() { + static Map getControllerMap() { return _controllerMap; } static MercuryController? getControllerOfName(String name) { if (!_nameIdMap.containsKey(name)) return null; - int? contextId = _nameIdMap[name]; + double? contextId = _nameIdMap[name]; return getControllerOfJSContextId(contextId); } @@ -301,7 +324,7 @@ class MercuryController { set name(String? value) { if (value == null) return; if (_name != null) { - int? contextId = _nameIdMap[_name]; + double? contextId = _nameIdMap[_name]; _nameIdMap.remove(_name); _nameIdMap[value] = contextId!; } @@ -310,53 +333,64 @@ class MercuryController { // The mercury context entrypoint bundle. MercuryBundle? _entrypoint; + MercuryBundle? get entrypoint => _entrypoint; - MercuryController( - String? name, { + final MercuryThread runningThread; + + bool externalController; + + MercuryController({ + String? name, bool showPerformanceOverlay = false, bool enableDebug = false, bool autoExecuteEntrypoint = true, MercuryMethodChannel? methodChannel, - MercuryBundle? entrypoint, + MercuryBundle? bundle, + MercuryThread? runningThread, this.onLoad, this.onLoadError, this.onJSError, this.httpClientInterceptor, this.devToolsService, this.uriParser, + this.externalController = true, }) : _name = name, - _entrypoint = entrypoint { + _entrypoint = bundle, + runningThread = runningThread ?? DedicatedThread() { _methodChannel = methodChannel; MercuryMethodChannel.setJSMethodCallCallback(this); _context = MercuryContextController( enableDebug: enableDebug, + runningThread: this.runningThread, rootController: this, ); - final int contextId = _context.contextId; + _context.initialize().then((_) { + final double contextId = _context.contextId; - _module = MercuryModuleController(this, contextId); + _module = MercuryModuleController(this, contextId); - assert(!_controllerMap.containsKey(contextId), 'found exist contextId of MercuryController, contextId: $contextId'); - _controllerMap[contextId] = this; - assert(!_nameIdMap.containsKey(name), 'found exist name of MercuryController, name: $name'); - if (name != null) { - _nameIdMap[name] = contextId; - } + assert(!_controllerMap.containsKey(contextId), 'found exist contextId of MercuryController, contextId: $contextId'); + _controllerMap[contextId] = this; + assert(!_nameIdMap.containsKey(name), 'found exist name of MercuryController, name: $name'); + if (name != null) { + _nameIdMap[name] = contextId; + } - setupHttpOverrides(httpClientInterceptor, contextId: contextId); + setupHttpOverrides(httpClientInterceptor, contextId: contextId); - uriParser ??= UriParser(); + uriParser ??= UriParser(); - if (devToolsService != null) { - devToolsService!.init(this); - } + if (devToolsService != null) { + devToolsService!.init(this); + } - if (autoExecuteEntrypoint) { - executeEntrypoint(); - } + if (autoExecuteEntrypoint) { + executeEntrypoint(); + } + }); } late MercuryContextController _context; @@ -371,7 +405,7 @@ class MercuryController { return _module; } - static Uri fallbackBundleUri([int? id]) { + static Uri fallbackBundleUri([double? id]) { // The fallback origin uri, like `vm://bundle/0` return Uri(scheme: 'vm', host: 'bundle', path: id != null ? '$id' : null); } @@ -382,15 +416,18 @@ class MercuryController { // Wait for next microtask to make sure C++ native Elements are GC collected. Completer completer = Completer(); - Future.microtask(() { + Future.microtask(() async { _module.dispose(); - _context.dispose(); + await _context.dispose(); - int oldId = _context.contextId; + double oldId = _context.contextId; _context = MercuryContextController( enableDebug: _context.enableDebug, - rootController: this,); + rootController: this, + runningThread: runningThread); + + await _context.initialize(); _module = MercuryModuleController(this, _context.contextId); @@ -426,6 +463,9 @@ class MercuryController { } await unload(); + + flushIsolateCommand(context, nullptr); + await executeEntrypoint(); if (devToolsService != null) { @@ -442,6 +482,8 @@ class MercuryController { await unload(); + flushIsolateCommand(context, nullptr); + // Update entrypoint. _entrypoint = bundle; @@ -505,7 +547,7 @@ class MercuryController { _module.initialize() ]); if (_entrypoint!.isResolved && shouldEvaluate) { - await _evaluateEntrypoint(); + await evaluateEntrypoint(); } else { throw FlutterError('Unable to resolve $_entrypoint'); } @@ -538,24 +580,26 @@ class MercuryController { } // Execute the content from entrypoint bundle. - Future _evaluateEntrypoint() async { + Future evaluateEntrypoint() async { assert(!_context._disposed, 'Mercury have already disposed'); if (_entrypoint != null) { MercuryBundle entrypoint = _entrypoint!; - int contextId = _context.contextId; + double contextId = _context.contextId; assert(entrypoint.isResolved, 'The mercury bundle $entrypoint is not resolved to evaluate.'); Uint8List data = entrypoint.data!; if (entrypoint.isJavascript) { + assert(isValidUTF8String(data), 'The JavaScript codes should be in UTF-8 encoding format'); // Prefer sync decode in loading entrypoint. - await evaluateScripts(contextId, await resolveStringFromData(data, preferSync: true), url: url); + await evaluateScripts(contextId, data, url: url); } else if (entrypoint.isBytecode) { - evaluateQuickjsByteCode(contextId, data); + await evaluateQuickjsByteCode(contextId, data); } else if (entrypoint.contentType.primaryType == 'text') { // Fallback treating text content as JavaScript. try { - await evaluateScripts(contextId, await resolveStringFromData(data, preferSync: true), url: url); + assert(isValidUTF8String(data), 'The JavaScript codes should be in UTF-8 encoding format'); + await evaluateScripts(contextId, data, url: url); } catch (error) { print('Fallback to execute JavaScript content of $url'); rethrow; diff --git a/mercury/lib/src/module/fetch.dart b/mercury/lib/src/module/fetch.dart index a88eb01..15ced26 100644 --- a/mercury/lib/src/module/fetch.dart +++ b/mercury/lib/src/module/fetch.dart @@ -86,8 +86,20 @@ class FetchModule extends BaseModule { }); } + HttpClientRequest? _currentRequest; + + void _abortRequest() { + _currentRequest?.abort(); + _currentRequest = null; + } + @override String invoke(String method, params, InvokeModuleCallback callback) { + if (method == 'abortRequest') { + _abortRequest(); + return ''; + } + Uri uri = _resolveUri(method); Map options = params; @@ -106,6 +118,7 @@ class FetchModule extends BaseModule { HttpClientResponse? response; getRequest(uri, options['method'], options['headers'], options['body']).then((HttpClientRequest request) { if (_disposed) return Future.value(null); + _currentRequest = request; return request.close(); }).then((HttpClientResponse? res) { if (res == null) { diff --git a/mercury/lib/src/module/module_manager.dart b/mercury/lib/src/module/module_manager.dart index 95bbd53..f9dfd3a 100644 --- a/mercury/lib/src/module/module_manager.dart +++ b/mercury/lib/src/module/module_manager.dart @@ -47,7 +47,7 @@ void _defineModule(ModuleCreator moduleCreator) { } class ModuleManager { - final int contextId; + final double contextId; final MercuryController controller; final Map _moduleMap = {}; bool disposed = false; diff --git a/mercury/lib/src/module/timer.dart b/mercury/lib/src/module/timer.dart index 62361e8..284407a 100644 --- a/mercury/lib/src/module/timer.dart +++ b/mercury/lib/src/module/timer.dart @@ -4,6 +4,7 @@ */ import 'dart:async'; +import 'package:flutter/foundation.dart'; /// A [Timer] that can be paused, resumed. class PausablePeriodicTimer implements Timer { @@ -57,17 +58,21 @@ class PausablePeriodicTimer implements Timer { } mixin TimerMixin { - int _timerId = 1; final Map _timerMap = {}; - int setTimeout(int timeout, void Function() callback) { + bool _isPaused = false; + final List _pendingUnFinishedCallbacks = []; + + void setTimeout(int newTimerId, int timeout, void Function() callback) { Duration timeoutDurationMS = Duration(milliseconds: timeout); - int id = _timerId++; - _timerMap[id] = Timer(timeoutDurationMS, () { + _timerMap[newTimerId] = Timer(timeoutDurationMS, () { + if (_isPaused) { + _pendingUnFinishedCallbacks.add(callback); + return; + } callback(); - _timerMap.remove(id); + _timerMap.remove(newTimerId); }); - return id; } void clearTimeout(int timerId) { @@ -78,29 +83,39 @@ mixin TimerMixin { } } - int setInterval(int timeout, void Function() callback) { + void setInterval(int newTimerId, int timeout, void Function() callback) { Duration timeoutDurationMS = Duration(milliseconds: timeout); - int id = _timerId++; - _timerMap[id] = PausablePeriodicTimer(timeoutDurationMS, (_) { + _timerMap[newTimerId] = PausablePeriodicTimer(timeoutDurationMS, (_) { + if (_isPaused) return; callback(); }); - return id; } - void pauseInterval() { + void pauseTimer() { + // Pause all intervals _timerMap.forEach((key, timer) { if (timer is PausablePeriodicTimer) { timer.pause(); } }); + + _isPaused = true; } - void resumeInterval() { + void resumeTimer() { + // Resume all intervals _timerMap.forEach((key, timer) { if (timer is PausablePeriodicTimer) { timer.resume(); } }); + + _pendingUnFinishedCallbacks.forEach((callback) { + callback(); + }); + _pendingUnFinishedCallbacks.clear(); + _isPaused = false; + } void disposeTimer() { @@ -108,5 +123,6 @@ mixin TimerMixin { timer.cancel(); }); _timerMap.clear(); + _pendingUnFinishedCallbacks.clear(); } } diff --git a/mercury/windows/mercuryjs.dll b/mercury/windows/mercuryjs.dll new file mode 100644 index 0000000..dbc3e1e Binary files /dev/null and b/mercury/windows/mercuryjs.dll differ diff --git a/mercury/windows/pthreadVC2.dll b/mercury/windows/pthreadVC2.dll new file mode 100644 index 0000000..165b4d2 Binary files /dev/null and b/mercury/windows/pthreadVC2.dll differ diff --git a/mercury/windows/quickjs.dll b/mercury/windows/quickjs.dll new file mode 100644 index 0000000..167960a Binary files /dev/null and b/mercury/windows/quickjs.dll differ diff --git a/plugins/devtools/bridge/dart_methods.h b/plugins/devtools/bridge/dart_methods.h index bbbaec3..2a25692 100644 --- a/plugins/devtools/bridge/dart_methods.h +++ b/plugins/devtools/bridge/dart_methods.h @@ -13,12 +13,12 @@ struct NativeString; struct Screen; -typedef void (*InspectorMessage)(int32_t contextId, const char *message); +typedef void (*InspectorMessage)(double contextId, const char *message); typedef void (*InspectorMessageCallback)(void *rpcSession, const char *message); -typedef void (*RegisterInspectorMessageCallback)(int32_t contextId, void *rpcSession, +typedef void (*RegisterInspectorMessageCallback)(double contextId, void *rpcSession, InspectorMessageCallback inspectorMessageCallback); -typedef void (*PostTaskToInspectorThread)(int32_t contextId, void *context, void (*)(void *)); -typedef void (*PostTaskToUIThread)(int32_t contextId, void *context, void (*)(void *)); +typedef void (*PostTaskToInspectorThread)(double contextId, void *context, void (*)(void *)); +typedef void (*PostTaskToUIThread)(double contextId, void *context, void (*)(void *)); namespace kraken { diff --git a/plugins/devtools/bridge/inspector/frontdoor.h b/plugins/devtools/bridge/inspector/frontdoor.h index 538e340..5686a54 100644 --- a/plugins/devtools/bridge/inspector/frontdoor.h +++ b/plugins/devtools/bridge/inspector/frontdoor.h @@ -16,7 +16,7 @@ namespace kraken::debugger { class FrontDoor final { public: ~FrontDoor() = default; - FrontDoor(int32_t contextId, JSGlobalContextRef ctx, JSC::JSGlobalObject *globalObject, const std::shared_ptr handler) { + FrontDoor(double contextId, JSGlobalContextRef ctx, JSC::JSGlobalObject *globalObject, const std::shared_ptr handler) { m_rpc_session = std::make_shared(contextId, ctx, globalObject, handler); } diff --git a/plugins/devtools/bridge/inspector/impl/jsc_debugger_impl.cc b/plugins/devtools/bridge/inspector/impl/jsc_debugger_impl.cc index efaa41d..45d53b7 100644 --- a/plugins/devtools/bridge/inspector/impl/jsc_debugger_impl.cc +++ b/plugins/devtools/bridge/inspector/impl/jsc_debugger_impl.cc @@ -9,7 +9,7 @@ namespace kraken::debugger { using namespace JSC; -JSCDebuggerImpl::JSCDebuggerImpl(int32_t contextId, JSGlobalObject *globalObject) +JSCDebuggerImpl::JSCDebuggerImpl(double contextId, JSGlobalObject *globalObject) : Inspector::ScriptDebugServer(globalObject->globalExec()->vm()), m_globalObject(globalObject), m_contextId(contextId) {} void JSCDebuggerImpl::recompileAllJSFunctions() { diff --git a/plugins/devtools/bridge/inspector/impl/jsc_debugger_impl.h b/plugins/devtools/bridge/inspector/impl/jsc_debugger_impl.h index 62ae2bc..cbce4fa 100644 --- a/plugins/devtools/bridge/inspector/impl/jsc_debugger_impl.h +++ b/plugins/devtools/bridge/inspector/impl/jsc_debugger_impl.h @@ -23,7 +23,7 @@ class JSCDebuggerImpl : public Inspector::ScriptDebugServer { KRAKEN_DISALLOW_COPY_AND_ASSIGN(JSCDebuggerImpl); public: - explicit JSCDebuggerImpl(int32_t contextId, JSC::JSGlobalObject *); + explicit JSCDebuggerImpl(double contextId, JSC::JSGlobalObject *); virtual ~JSCDebuggerImpl() {} JSC::JSGlobalObject *globalObject() const { diff --git a/plugins/devtools/bridge/inspector/rpc_session.cc b/plugins/devtools/bridge/inspector/rpc_session.cc index ea6b92b..138799d 100644 --- a/plugins/devtools/bridge/inspector/rpc_session.cc +++ b/plugins/devtools/bridge/inspector/rpc_session.cc @@ -60,7 +60,7 @@ void RPCSession::_on_message(const std::string &message) { } } -void DartRPC::send(int32_t contextId, const std::string &msg) { +void DartRPC::send(double contextId, const std::string &msg) { if (std::this_thread::get_id() == getUIThreadId()) { auto ctx = new RPCContext{contextId, msg}; kraken::getUIDartMethod()->postTaskToInspectorThread(contextId, reinterpret_cast(ctx), [](void *ptr) { @@ -73,7 +73,7 @@ void DartRPC::send(int32_t contextId, const std::string &msg) { } } -void DartRPC::setOnMessageCallback(int32_t contextId, void *rpcSession, InspectorMessageCallback callback) { +void DartRPC::setOnMessageCallback(double contextId, void *rpcSession, InspectorMessageCallback callback) { getInspectorDartMethod()->registerInspectorMessageCallback(contextId, rpcSession, callback); } diff --git a/plugins/devtools/bridge/inspector/rpc_session.h b/plugins/devtools/bridge/inspector/rpc_session.h index 225fc18..b7716bb 100644 --- a/plugins/devtools/bridge/inspector/rpc_session.h +++ b/plugins/devtools/bridge/inspector/rpc_session.h @@ -20,11 +20,11 @@ class InspectorSession; class DartRPC { public: - void send(int32_t contextId, const std::string &msg); - void setOnMessageCallback(int32_t contextId, void* rpcSession, InspectorMessageCallback callback); + void send(double contextId, const std::string &msg); + void setOnMessageCallback(double contextId, void* rpcSession, InspectorMessageCallback callback); private: struct RPCContext { - int32_t contextId; + double contextId; std::string message; }; }; diff --git a/plugins/devtools/bridge/kraken_devtools.cc b/plugins/devtools/bridge/kraken_devtools.cc index d0b0270..05b2337 100644 --- a/plugins/devtools/bridge/kraken_devtools.cc +++ b/plugins/devtools/bridge/kraken_devtools.cc @@ -8,7 +8,7 @@ #include "dart_methods.h" #include -void attachInspector(int32_t contextId) { +void attachInspector(double contextId) { JSGlobalContextRef ctx = getGlobalContextRef(contextId); std::shared_ptr handler = std::make_shared(); JSC::ExecState* exec = toJS(ctx); @@ -23,7 +23,7 @@ void attachInspector(int32_t contextId) { setConsoleMessageHandler(kraken::debugger::FrontDoor::handleConsoleMessage); } -void dispatchInspectorTask(int32_t contextId, void *context, void *callback) { +void dispatchInspectorTask(double contextId, void *context, void *callback) { assert(std::this_thread::get_id() != getUIThreadId()); reinterpret_cast(callback)(context); } diff --git a/plugins/devtools/bridge/kraken_devtools.h b/plugins/devtools/bridge/kraken_devtools.h index dfdf022..12fedeb 100644 --- a/plugins/devtools/bridge/kraken_devtools.h +++ b/plugins/devtools/bridge/kraken_devtools.h @@ -26,7 +26,7 @@ class BridgeProtocolHandler : public ProtocolHandler { } KRAKEN_EXPORT_C -void attachInspector(int32_t contextId); +void attachInspector(double contextId); KRAKEN_EXPORT_C void registerInspectorDartMethods(uint64_t *methodBytes, int32_t length); @@ -34,6 +34,6 @@ KRAKEN_EXPORT_C void registerUIDartMethods(uint64_t *methodBytes, int32_t length); KRAKEN_EXPORT_C -void dispatchInspectorTask(int32_t contextId, void *context, void *callback); +void dispatchInspectorTask(double contextId, void *context, void *callback); #endif //KRAKEN_DEVTOOLS_KRAKEN_DEVTOOLS_H diff --git a/scripts/build_android_so.js b/scripts/build_android_so.js index 6c10ed9..f03de08 100644 --- a/scripts/build_android_so.js +++ b/scripts/build_android_so.js @@ -9,7 +9,7 @@ const { copyFileSync } = require('fs'); const buildTasks = [ 'git-submodule', - 'android-so-clean', + // 'android-so-clean', 'compile-polyfill', 'generate-bindings-code', 'build-android-mercury-lib' diff --git a/scripts/build_darwin_dylib.js b/scripts/build_darwin_dylib.js index c8b5d8a..53d5cab 100644 --- a/scripts/build_darwin_dylib.js +++ b/scripts/build_darwin_dylib.js @@ -9,7 +9,7 @@ require('./tasks'); // Run tasks series( 'git-submodule', - 'macos-dylib-clean', + // 'macos-dylib-clean', 'compile-polyfill', 'generate-bindings-code', 'build-darwin-mercury-lib', diff --git a/scripts/build_linux.js b/scripts/build_linux.js index 1c24e64..17ef126 100644 --- a/scripts/build_linux.js +++ b/scripts/build_linux.js @@ -7,7 +7,7 @@ const chalk = require('chalk'); // Run tasks series( - 'clean', + // 'clean', 'git-submodule', 'compile-polyfill', 'generate-bindings-code', diff --git a/scripts/build_windows_dll.js b/scripts/build_windows_dll.js index 63da39e..0dfd377 100644 --- a/scripts/build_windows_dll.js +++ b/scripts/build_windows_dll.js @@ -7,7 +7,7 @@ // Run tasks series( - 'clean', + // 'clean', 'git-submodule', 'compile-polyfill', 'generate-bindings-code', diff --git a/scripts/clean.js b/scripts/clean.js new file mode 100644 index 0000000..2fa0905 --- /dev/null +++ b/scripts/clean.js @@ -0,0 +1,22 @@ +/** + * Only build libkraken.dylib for macOS + */ +const { series } = require('gulp'); +const chalk = require('chalk'); + +require('./tasks'); + +// Run tasks +series( + 'android-so-clean', + 'ios-framework-clean', + 'macos-dylib-clean', + 'android-so-clean', + 'clean', +)((err) => { + if (err) { + console.log(err); + } else { + console.log(chalk.green('Success.')); + } +}); diff --git a/scripts/tasks.js b/scripts/tasks.js index 9d14588..6709b31 100644 --- a/scripts/tasks.js +++ b/scripts/tasks.js @@ -19,6 +19,7 @@ const uploader = require('./utils/uploader'); program .option('--static-quickjs', 'Build quickjs as static library and bundled into mercury library.', false) +.option('--enable-log', 'Enable log printing') .parse(process.argv); const SUPPORTED_JS_ENGINES = ['jsc', 'quickjs']; @@ -122,6 +123,10 @@ task('build-darwin-mercury-lib', done => { externCmakeArgs.push('-DSTATIC_QUICKJS=true'); } + if (program.enableLog) { + externCmakeArgs.push('-DENABLE_LOG=true'); + } + execSync(`cmake -DCMAKE_BUILD_TYPE=${buildType} ${externCmakeArgs.join(' ')} \ -G "Unix Makefiles" -B ${paths.bridge}/cmake-build-macos-x86_64 -S ${paths.bridge}`, { cwd: paths.bridge, @@ -223,6 +228,10 @@ task(`build-ios-mercury-lib`, (done) => { externCmakeArgs.push('-DUSE_SYSTEM_MALLOC=true'); } + if (program.enableLog) { + externCmakeArgs.push('-DENABLE_LOG=true'); + } + // generate build scripts for simulator execSync(`cmake -DCMAKE_BUILD_TYPE=${buildType} \ -DCMAKE_TOOLCHAIN_FILE=${paths.bridge}/cmake/ios.toolchain.cmake \ @@ -354,6 +363,10 @@ task('build-linux-mercury-lib', (done) => { externCmakeArgs.push('-DUSE_SYSTEM_MALLOC=true'); } + if (program.enableLog) { + externCmakeArgs.push('-DENABLE_LOG=true'); + } + const soBinaryDirectory = path.join(paths.bridge, `build/linux/lib/`); const bridgeCmakeDir = path.join(paths.bridge, 'cmake-build-linux'); // generate project @@ -439,6 +452,10 @@ task('build-window-mercury-lib', (done) => { externCmakeArgs.push('-DUSE_SYSTEM_MALLOC=true'); } + if (program.enableLog) { + externCmakeArgs.push('-DENABLE_LOG=true'); + } + const soBinaryDirectory = path.join(paths.bridge, `build/windows/lib/`); const bridgeCmakeDir = path.join(paths.bridge, 'cmake-build-windows'); // generate project @@ -510,6 +527,10 @@ task('build-android-mercury-lib', (done) => { externCmakeArgs.push('-DUSE_SYSTEM_MALLOC=true'); } + if (program.enableLog) { + externCmakeArgs.push('-DENABLE_LOG=true'); + } + // Bundle quickjs into mercury. if (program.staticQuickjs) { externCmakeArgs.push('-DSTATIC_QUICKJS=true');