Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
123 changes: 123 additions & 0 deletions NativeScript/runtime/BuiltinLoader.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
#include "BuiltinLoader.h"

#include <mutex>
#include <vector>

#include "Helpers.h"

using namespace v8;

namespace tns {

namespace {

// Process-wide bytecode cache shared across isolates (main + workers).
std::mutex builtinCacheMutex;
std::vector<uint8_t> builtinCache[static_cast<unsigned>(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<v8::Function> CompileBuiltin(Local<Context> context, BuiltinId id) {
Isolate* isolate = v8::Isolate::GetCurrent();
const BuiltinSource& builtin = GetBuiltinSource(id);
const unsigned index = static_cast<unsigned>(id);

// Copy the blob out so the shared slot can be refreshed concurrently while
// this compile still reads from the copy.
std::vector<uint8_t> blob;
{
std::lock_guard<std::mutex> 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<Value>(),
false, // is_opaque
false, // is_wasm
false // is_module
);
Local<v8::String> sourceText = tns::ToV8String(
isolate, builtin.source, static_cast<int>(builtin.length));
Local<v8::String> params[] = {tns::ToV8String(isolate, kExportsParamName),
tns::ToV8String(isolate, kModuleParamName),
tns::ToV8String(isolate, kBindingParamName)};

Local<v8::Function> 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<int>(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<v8::Function>();
}

std::unique_ptr<ScriptCompiler::CachedData> produced(
ScriptCompiler::CreateCodeCacheForFunction(fn));
if (produced != nullptr && produced->data != nullptr &&
produced->length > 0) {
std::lock_guard<std::mutex> lock(builtinCacheMutex);
builtinCache[index].assign(produced->data,
produced->data + produced->length);
}

return fn;
}

} // namespace

MaybeLocal<Value> BuiltinLoader::RunBuiltin(Local<Context> context,
BuiltinId id,
Local<Value> binding) {
Isolate* isolate = v8::Isolate::GetCurrent();

Local<v8::Function> fn;
if (!CompileBuiltin(context, id).ToLocal(&fn)) {
return MaybeLocal<Value>();
}

Local<Object> exportsObj = Object::New(isolate);
Local<Object> moduleObj = Object::New(isolate);
Local<v8::String> exportsKey = tns::ToV8String(isolate, kExportsParamName);
if (!moduleObj->Set(context, exportsKey, exportsObj).FromMaybe(false)) {
return MaybeLocal<Value>();
}

Local<Value> args[] = {
exportsObj, moduleObj,
binding.IsEmpty() ? v8::Undefined(isolate).As<Value>() : binding};
if (fn->Call(context, v8::Undefined(isolate), kParamCount, args).IsEmpty()) {
return MaybeLocal<Value>();
}

return moduleObj->Get(context, exportsKey);
}

} // namespace tns
27 changes: 27 additions & 0 deletions NativeScript/runtime/BuiltinLoader.h
Original file line number Diff line number Diff line change
@@ -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/<name>.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<v8::Value> RunBuiltin(
v8::Local<v8::Context> context, BuiltinId id,
v8::Local<v8::Value> binding = v8::Local<v8::Value>());
};

} // namespace tns

#endif /* BuiltinLoader_h */
24 changes: 4 additions & 20 deletions NativeScript/runtime/ClassBuilder.mm
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <numeric>
#include <sstream>
#include "ArgConverter.h"
#include "BuiltinLoader.h"
#include "Caches.h"
#include "FastEnumerationAdapter.h"
#include "Helpers.h"
Expand Down Expand Up @@ -163,27 +164,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<Script> script;
tns::Assert(Script::Compile(context, tns::ToV8String(isolate, extendsFuncScript.c_str()))
.ToLocal(&script),
isolate);

Local<Value> extendsFunc;
tns::Assert(script->Run(context).ToLocal(&extendsFunc) && extendsFunc->IsFunction(), isolate);
tns::Assert(BuiltinLoader::RunBuiltin(context, BuiltinId::kClassExtends).ToLocal(&extendsFunc) &&
extendsFunc->IsFunction(),
isolate);

cache->OriginalExtendsFunc =
std::make_unique<Persistent<v8::Function>>(isolate, extendsFunc.As<v8::Function>());
Expand Down
14 changes: 13 additions & 1 deletion NativeScript/runtime/DataWrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -157,14 +157,26 @@ class BaseDataWrapper {

class EnumDataWrapper : public BaseDataWrapper {
public:
EnumDataWrapper(std::string jsCode) : jsCode_(jsCode) {}
EnumDataWrapper(std::string jsCode)
: jsCode_(jsCode), hasCachedValue_(false), cachedValue_(0) {}

const WrapperType Type() { return WrapperType::Enum; }

std::string JSCode() { return jsCode_; }

// Numeric value memoized by Interop::WriteValue so the enum's JS snippet is
// compiled and evaluated at most once per wrapper.
bool HasCachedValue() const { return hasCachedValue_; }
double CachedValue() const { return cachedValue_; }
void SetCachedValue(double value) {
cachedValue_ = value;
hasCachedValue_ = true;
}

private:
std::string jsCode_;
bool hasCachedValue_;
double cachedValue_;
};

class PointerTypeWrapper : public BaseDataWrapper {
Expand Down
Loading
Loading