diff --git a/.gitignore b/.gitignore index fbfa1d78..01fd062c 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,9 @@ Thumbs.db # VSCode .vscode +# Generated by tools/js2c.mjs (Xcode "Generate RuntimeBuiltins" build phase) +NativeScript/runtime/generated/ + # Other node_modules/ package-lock.json diff --git a/NativeScript/runtime/BuiltinLoader.cpp b/NativeScript/runtime/BuiltinLoader.cpp new file mode 100644 index 00000000..d004db84 --- /dev/null +++ b/NativeScript/runtime/BuiltinLoader.cpp @@ -0,0 +1,123 @@ +#include "BuiltinLoader.h" + +#include +#include + +#include "Helpers.h" + +using namespace v8; + +namespace tns { + +namespace { + +// Process-wide bytecode cache shared across isolates (main + workers). +std::mutex builtinCacheMutex; +std::vector builtinCache[static_cast(BuiltinId::kCount)]; + +// Every builtin is compiled as a function body receiving these fixed +// parameters, mirroring Node's module wrapper: a file exports through +// `module.exports`/`exports`, and natives arrive as properties of the +// `binding` bag (Node's internalBinding idiom) for each file to destructure. +constexpr const char* kExportsParamName = "exports"; +constexpr const char* kModuleParamName = "module"; +constexpr const char* kBindingParamName = "binding"; +constexpr int kParamCount = 3; + +MaybeLocal CompileBuiltin(Local context, BuiltinId id) { + Isolate* isolate = v8::Isolate::GetCurrent(); + const BuiltinSource& builtin = GetBuiltinSource(id); + const unsigned index = static_cast(id); + + // Copy the blob out so the shared slot can be refreshed concurrently while + // this compile still reads from the copy. + std::vector blob; + { + std::lock_guard lock(builtinCacheMutex); + blob = builtinCache[index]; + } + + ScriptOrigin origin(tns::ToV8String(isolate, builtin.name), + 0, // line offset + 0, // column offset + false, // shared_cross_origin + -1, // script_id + Local(), + false, // is_opaque + false, // is_wasm + false // is_module + ); + Local sourceText = tns::ToV8String( + isolate, builtin.source, static_cast(builtin.length)); + Local params[] = {tns::ToV8String(isolate, kExportsParamName), + tns::ToV8String(isolate, kModuleParamName), + tns::ToV8String(isolate, kBindingParamName)}; + + Local fn; + if (!blob.empty()) { + // The Source owns and deletes the CachedData object; BufferNotOwned keeps + // the underlying bytes (our copy) out of its hands. + auto* cachedData = new ScriptCompiler::CachedData( + blob.data(), static_cast(blob.size()), + ScriptCompiler::CachedData::BufferNotOwned); + ScriptCompiler::Source source(sourceText, origin, cachedData); + if (ScriptCompiler::CompileFunction(context, &source, kParamCount, params, + 0, nullptr, + ScriptCompiler::kConsumeCodeCache) + .ToLocal(&fn) && + !cachedData->rejected) { + return fn; + } + // Rejected cache (e.g. produced under different flags): fall through and + // recompile eagerly so the refreshed blob covers inner functions again. + } + + ScriptCompiler::Source source(sourceText, origin); + if (!ScriptCompiler::CompileFunction(context, &source, kParamCount, params, 0, + nullptr, ScriptCompiler::kEagerCompile) + .ToLocal(&fn)) { + return MaybeLocal(); + } + + std::unique_ptr produced( + ScriptCompiler::CreateCodeCacheForFunction(fn)); + if (produced != nullptr && produced->data != nullptr && + produced->length > 0) { + std::lock_guard lock(builtinCacheMutex); + builtinCache[index].assign(produced->data, + produced->data + produced->length); + } + + return fn; +} + +} // namespace + +MaybeLocal BuiltinLoader::RunBuiltin(Local context, + BuiltinId id, + Local binding) { + Isolate* isolate = v8::Isolate::GetCurrent(); + + Local fn; + if (!CompileBuiltin(context, id).ToLocal(&fn)) { + return MaybeLocal(); + } + + Local exportsObj = Object::New(isolate); + Local moduleObj = Object::New(isolate); + Local exportsKey = tns::ToV8String(isolate, kExportsParamName); + if (!moduleObj->Set(context, exportsKey, exportsObj).FromMaybe(false)) { + return MaybeLocal(); + } + + Local args[] = { + exportsObj, moduleObj, + binding.IsEmpty() ? v8::Undefined(isolate).As() : binding}; + if (fn->Call(context, v8::Undefined(isolate), kParamCount, args).IsEmpty()) { + return MaybeLocal(); + } + + return moduleObj->Get(context, exportsKey); +} + +} // namespace tns diff --git a/NativeScript/runtime/BuiltinLoader.h b/NativeScript/runtime/BuiltinLoader.h new file mode 100644 index 00000000..453c3fa7 --- /dev/null +++ b/NativeScript/runtime/BuiltinLoader.h @@ -0,0 +1,27 @@ +#ifndef BuiltinLoader_h +#define BuiltinLoader_h + +#include "Common.h" +#include "RuntimeBuiltins.h" + +namespace tns { + +class BuiltinLoader { + public: + // Compiles the builtin identified by id as a function body with the fixed + // parameters `exports`, `module` and `binding` (Node's module wrapper plus + // its internalBinding idiom), calls it with the given bag of natives (or + // undefined when omitted), and returns the resulting `module.exports`. + // Scripts carry an "internal/.js" origin so runtime + // frames are identifiable in stack traces. Compilation goes through a + // process-wide bytecode cache: the first run in the process compiles + // eagerly and populates the cache, later isolates (workers) consume it + // instead of re-parsing the source. + static v8::MaybeLocal RunBuiltin( + v8::Local context, BuiltinId id, + v8::Local binding = v8::Local()); +}; + +} // namespace tns + +#endif /* BuiltinLoader_h */ diff --git a/NativeScript/runtime/ClassBuilder.mm b/NativeScript/runtime/ClassBuilder.mm index 4b7a552b..75936775 100644 --- a/NativeScript/runtime/ClassBuilder.mm +++ b/NativeScript/runtime/ClassBuilder.mm @@ -3,6 +3,7 @@ #include #include #include "ArgConverter.h" +#include "BuiltinLoader.h" #include "Caches.h" #include "FastEnumerationAdapter.h" #include "Helpers.h" @@ -162,27 +163,10 @@ return; } - std::string extendsFuncScript = "(function() { " - " function __extends(d, b) { " - " for (var p in b) {" - " if (b.hasOwnProperty(p)) {" - " d[p] = b[p];" - " }" - " }" - " function __() { this.constructor = d; }" - " d.prototype = b === null ? Object.create(b) : " - "(__.prototype = b.prototype, new __());" - " } " - " return __extends;" - "})()"; - - Local