/* * Tencent is pleased to support the open source community by making ScriptX available. * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "QjsEngine.h" #include namespace script::qjs_backend { JSClassID QjsEngine::kPointerClassId = 0; JSClassID QjsEngine::kInstanceClassId = 0; JSClassID QjsEngine::kFunctionDataClassId = 0; static std::once_flag kGlobalQjsClass; constexpr auto kGetByteBufferInfo = R"( (function (val) { // NOTE: KEEP SYNC WITH CPP const kUnspecified = 0x1; const kInt8 = 0x101; const kUint8 = 0x201; const kInt16 = 0x302; const kUint16 = 0x402; const kInt32 = 0x504; const kUint32 = 0x604; const kInt64 = 0x708; const kUint64 = 0x808; const KFloat32 = 0x904; const kFloat64 = 0xa08; let byteBuffer = val; let length = val.byteLength; let offset = 0; let type = kUnspecified; if (ArrayBuffer.isView(val)) { byteBuffer = val.buffer; offset = val.byteOffset; if (val instanceof Int8Array) { type = kInt8; } else if (val instanceof Uint8Array || val instanceof Uint8ClampedArray) { type = kUint8; } else if (val instanceof Int16Array) { type = kInt16; } else if (val instanceof Uint16Array) { type = kUint16; } else if (val instanceof Int16Array) { type = kUint16; } else if (val instanceof Int32Array) { type = kInt32; } else if (val instanceof Uint32Array) { type = kUint32; } else if (val instanceof Float32Array) { type = KFloat32; } else if (val instanceof Float64Array) { type = kFloat64; } else if (val instanceof BigInt64Array) { type = kInt64; } else if (val instanceof BigUint64Array) { type = kUint64; } } return [byteBuffer, length, offset, type]; }) )"; QjsEngine::QjsEngine(std::shared_ptr queue, const QjsFactory& factory) : queue_(queue ? std::move(queue) : std::make_shared()) { if (factory) { std::tie(runtime_, context_) = factory(); } else { runtime_ = JS_NewRuntime(); if (runtime_) { context_ = JS_NewContext(runtime_); } } if (!runtime_ || !context_) { throw std::logic_error("QjsEngine: runtime or context is nullptr"); } initEngineResource(); } void QjsEngine::initEngineResource() { std::call_once(kGlobalQjsClass, []() { JS_NewClassID(&kPointerClassId); JS_NewClassID(&kInstanceClassId); JS_NewClassID(&kFunctionDataClassId); }); JSClassDef pointer{}; pointer.class_name = "RawPointer"; JS_NewClass(runtime_, kPointerClassId, &pointer); JSClassDef function{}; function.class_name = "RawFunction"; function.finalizer = [](JSRuntime* /*rt*/, JSValue val) { auto ptr = JS_GetOpaque(val, kFunctionDataClassId); if (ptr) { delete static_cast(ptr); } }; JS_NewClass(runtime_, kFunctionDataClassId, &function); JSClassDef instance{}; instance.class_name = "ScriptXInstance"; instance.finalizer = [](JSRuntime* /*rt*/, JSValue val) { auto ptr = JS_GetOpaque(val, kInstanceClassId); if (ptr) { auto opaque = static_cast(ptr); // reset the weak reference PauseGc pauseGc(opaque->scriptClassPointer->internalState_.engine); opaque->scriptClassPointer->internalState_.weakRef_ = JS_UNDEFINED; delete opaque->scriptClassPointer; delete opaque; } }; JS_NewClass(runtime_, kInstanceClassId, &instance); lengthAtom_ = JS_NewAtom(context_, "length"); { EngineScope scope(this); { auto ret = eval("(function(a, b) {return a === b;})"); helperFunctionStrictEqual_ = qjs_interop::getLocal(ret); } { auto ret = eval( "(function(b) { return b instanceof ArrayBuffer || b instanceof SharedArrayBuffer || " "ArrayBuffer.isView(b);})"); helperFunctionIsByteBuffer_ = qjs_interop::getLocal(ret); } { auto ret = eval(kGetByteBufferInfo); helperFunctionGetByteBufferInfo_ = qjs_interop::getLocal(ret); } { // TODO(landerl): can we create symbol through C-API? Not yet. auto ret = eval("(Symbol('ScriptX.InternalStore'))"); auto atom = JS_ValueToAtom(context_, qjs_interop::peekLocal(ret)); assert(atom != JS_ATOM_NULL); helperSymbolInternalStore_ = atom; } } } QjsEngine::~QjsEngine() = default; void QjsEngine::destroy() noexcept { isDestroying_ = true; ScriptEngine::destroyUserData(); queue_->removeMessageByTag(static_cast(this)); globalWeakBookkeeping_.clear(); JS_FreeAtom(context_, lengthAtom_); JS_FreeValue(context_, helperFunctionStrictEqual_); JS_FreeValue(context_, helperFunctionIsByteBuffer_); JS_FreeValue(context_, helperFunctionGetByteBufferInfo_); JS_FreeAtom(context_, helperSymbolInternalStore_); for (auto&& [key, v] : nativeInstanceRegistry_) { JS_FreeValue(context_, v.first); JS_FreeValue(context_, v.second); } nativeInstanceRegistry_.clear(); JS_RunGC(runtime_); JS_FreeContext(context_); JS_FreeRuntime(runtime_); delete this; } void QjsEngine::scheduleTick() { bool no = false; if (tickScheduled_.compare_exchange_strong(no, true)) { utils::Message tick( [](auto& m) { auto eng = static_cast(m.ptr0); JSContext* ctx = nullptr; EngineScope scope(eng); while (JS_ExecutePendingJob(eng->runtime_, &ctx) > 0) { } eng->tickScheduled_ = false; }, [](auto& m) { }); tick.ptr0 = this; tick.tag = this; queue_->postMessage(tick); } } void QjsEngine::extendLifeTimeToNextLoop(JSValue value) { // schedule -> JS_Free(ref) class ExtendLifeTime { JSValue ref; qjs_backend::QjsEngine* engine; public: explicit ExtendLifeTime(JSValue val, qjs_backend::QjsEngine* engine) : ref(val), engine(engine) {} ~ExtendLifeTime() { JS_FreeValue(engine->context_, ref); } }; auto mq = messageQueue(); auto msg = mq->obtainInplaceMessage([](utils::InplaceMessage& msg) {}); msg->template inplaceObject(value, this); msg->tag = this; mq->postMessage(msg); } Local QjsEngine::get(const Local& key) { return getGlobal().get(key); } void QjsEngine::set(const Local& key, const Local& value) { getGlobal().set(key, value); } Local QjsEngine::getGlobal() const { auto global = JS_GetGlobalObject(context_); qjs_backend::checkException(global); return qjs_interop::makeLocal(global); } Local QjsEngine::eval(const Local& script) { return eval(script, Local()); } Local QjsEngine::eval(const Local& script, const Local& sourceFile) { return eval(script, sourceFile.asValue()); } Local QjsEngine::eval(const Local& script, const Local& sourceFile) { Tracer trace(this, "QjsEngine::eval"); JSValue ret = JS_UNDEFINED; StringHolder sh(script); if (sourceFile.isString()) { StringHolder source(sourceFile.asString()); ret = JS_Eval(context_, sh.c_str(), sh.length(), source.c_str(), JS_EVAL_TYPE_GLOBAL); } else { ret = JS_Eval(context_, sh.c_str(), sh.length(), "", JS_EVAL_TYPE_GLOBAL); } qjs_backend::checkException(ret); scheduleTick(); return Local(ret); } std::shared_ptr QjsEngine::messageQueue() { return queue_; } void QjsEngine::gc() { EngineScope scope(this); if (isDestroying() || pauseGcCount_ != 0) return; JS_RunGC(runtime_); } size_t QjsEngine::getHeapSize() { EngineScope scope(this); JSMemoryUsage usage{}; JS_ComputeMemoryUsage(runtime_, &usage); return usage.memory_used_size; } void QjsEngine::adjustAssociatedMemory(int64_t count) {} ScriptLanguage QjsEngine::getLanguageType() { return ScriptLanguage::kJavaScript; } std::string QjsEngine::getEngineVersion() { return "QuickJS"; } bool QjsEngine::isDestroying() const { return isDestroying_; } void QjsEngine::registerNativeStatic(const Local& module, const internal::StaticDefine& def) { for (auto&& f : def.functions) { using FuncDef = ::script::internal::StaticDefine::FunctionDefine; auto fun = newRawFunction(this, const_cast(&f), nullptr, [](const Arguments& args, void* data1, void*, bool) { auto f = static_cast(data1); Tracer trace(args.engine(), f->traceName); return (f->callback)(args); }); module.set(f.name, fun); } for (auto&& prop : def.properties) { using PropDef = ::script::internal::StaticDefine::PropertyDefine; Local getterFun; Local setterFun; if (prop.getter) { getterFun = newRawFunction(this, const_cast(&prop), nullptr, [](const Arguments& args, void* data, void*, bool) { auto p = static_cast(data); Tracer trace(args.engine(), p->traceName); return (p->getter)(); }) .asValue(); } if (prop.setter) { setterFun = newRawFunction(this, const_cast(&prop), nullptr, [](const Arguments& args, void* data, void*, bool) { auto p = static_cast(data); Tracer trace(args.engine(), p->traceName); (p->setter)(args[0]); return Local(); }); } auto atom = JS_NewAtomLen(context_, prop.name.c_str(), prop.name.length()); auto ret = JS_DefinePropertyGetSet(context_, qjs_interop::peekLocal(module), atom, qjs_interop::getLocal(getterFun), qjs_interop::getLocal(setterFun), JS_PROP_C_W_E); JS_FreeAtom(context_, atom); qjs_backend::checkException(ret); } } } // namespace script::qjs_backend