#include <unistd.h>
#include <v8-version.h>
#include "libplatform/libplatform.h"
#include "pl_util.h"
#include "pl_v8.h"
#include "pl_eval.h"
#include "pl_native.h"
#include "pl_console.h"
#include "pl_eventloop.h"
#include "pl_inlined.h"
#include "pl_stats.h"
#include "V8Context.h"
#include "ppport.h"
#define V8_PROFILE_RESET 0 /* set to 1 to profile */
#define PROGRAM_NAME "JavaScript-V8-XS"
#define ICU_DTL_DATA "icudtl.dat"
#define V8_NATIVES_BLOB "natives_blob.bin"
#define V8_SNAPSHOT_BLOB "snapshot_blob.bin"
#define MAX_MEMORY_MINIMUM (128 * 1024) /* 128 KB */
#define MAX_TIMEOUT_MINIMUM (500000) /* 500_000 us = 500 ms = 0.5 s */
#define ENTER_SCOPE \
Isolate::Scope isolate_scope(isolate); \
HandleScope handle_scope(isolate)
int V8Context::instance_count = 0;
std::unique_ptr<Platform> V8Context::platform = 0;
V8Context::V8Context(HV* opt)
: isolate(0),
persistent_context(0),
persistent_template(0),
flags(0),
version(0),
stats(0),
msgs(0),
pagesize_bytes(0),
max_allocated_bytes(0),
max_timeout_us(0),
inited(0)
{
V8Context::initialize_v8();
pagesize_bytes = total_memory_pages();
stats = newHV();
msgs = newHV();
flags = 0;
if (opt) {
hv_iterinit(opt);
while (1) {
SV* value = 0;
I32 klen = 0;
char* kstr = 0;
HE* entry = hv_iternext(opt);
if (!entry) {
break; /* no more hash keys */
}
kstr = hv_iterkey(entry, &klen);
if (!kstr || klen < 0) {
continue; /* invalid key */
}
value = hv_iterval(opt, entry);
if (!value) {
continue; /* invalid value */
}
if (memcmp(kstr, V8_OPT_NAME_GATHER_STATS, klen) == 0) {
flags |= SvTRUE(value) ? V8_OPT_FLAG_GATHER_STATS : 0;
continue;
}
if (memcmp(kstr, V8_OPT_NAME_SAVE_MESSAGES, klen) == 0) {
flags |= SvTRUE(value) ? V8_OPT_FLAG_SAVE_MESSAGES : 0;
continue;
}
if (memcmp(kstr, V8_OPT_NAME_MAX_MEMORY_BYTES, klen) == 0) {
size_t param = SvIV(value);
max_allocated_bytes = param > MAX_MEMORY_MINIMUM ? param : MAX_MEMORY_MINIMUM;
continue;
}
if (memcmp(kstr, V8_OPT_NAME_MAX_TIMEOUT_US, klen) == 0) {
long param = SvIV(value);
max_timeout_us = param > MAX_TIMEOUT_MINIMUM ? param : MAX_TIMEOUT_MINIMUM;
continue;
}
croak("Unknown option %*.*s\n", (int) klen, (int) klen, kstr);
}
}
create_params.array_buffer_allocator =
ArrayBuffer::Allocator::NewDefaultAllocator();
set_up();
}
V8Context::~V8Context()
{
tear_down();
delete create_params.array_buffer_allocator;
#if 0
/*
* We should terminate v8 at some point. However, because the calling code
* may create multiple instances of V8Context, whether "nested" or
* "sequential", we cannot just assume we should do this. For now, we just
* *never* terminate v8.
*/
V8Context::terminate_v8();
#endif
}
SV* V8Context::get(const char* name)
{
ENTER_SCOPE;
set_up();
Perf perf;
pl_stats_start(aTHX_ this, &perf);
SV* ret = pl_get_global_or_property(aTHX_ this, name);
pl_stats_stop(aTHX_ this, &perf, "get");
return ret;
}
SV* V8Context::exists(const char* name)
{
ENTER_SCOPE;
set_up();
Perf perf;
pl_stats_start(aTHX_ this, &perf);
SV* ret = pl_exists_global_or_property(aTHX_ this, name);
pl_stats_stop(aTHX_ this, &perf, "exists");
return ret;
}
SV* V8Context::typeof(const char* name)
{
ENTER_SCOPE;
set_up();
Perf perf;
pl_stats_start(aTHX_ this, &perf);
SV* ret = pl_typeof_global_or_property(aTHX_ this, name);
pl_stats_stop(aTHX_ this, &perf, "typeof");
return ret;
}
SV* V8Context::instanceof(const char* oname, const char* cname)
{
ENTER_SCOPE;
set_up();
Perf perf;
pl_stats_start(aTHX_ this, &perf);
SV* ret = pl_instanceof_global_or_property(aTHX_ this, oname, cname);
pl_stats_stop(aTHX_ this, &perf, "instanceof");
return ret;
}
void V8Context::set(const char* name, SV* value)
{
ENTER_SCOPE;
set_up();
Perf perf;
pl_stats_start(aTHX_ this, &perf);
pl_set_global_or_property(aTHX_ this, name, value);
pl_stats_stop(aTHX_ this, &perf, "set");
}
void V8Context::remove(const char* name)
{
ENTER_SCOPE;
set_up();
Perf perf;
pl_stats_start(aTHX_ this, &perf);
pl_del_global_or_property(aTHX_ this, name);
pl_stats_stop(aTHX_ this, &perf, "remove");
}
SV* V8Context::eval(const char* code, const char* file)
{
ENTER_SCOPE;
set_up();
/* performance is tracked inside this call */
return pl_eval(aTHX_ this, code, file);
}
SV* V8Context::dispatch_function_in_event_loop(const char* func)
{
ENTER_SCOPE;
set_up();
Perf perf;
pl_stats_start(aTHX_ this, &perf);
SV* ret = pl_run_function_in_event_loop(aTHX_ this, func);
pl_stats_stop(aTHX_ this, &perf, "dispatch");
return ret;
}
SV* V8Context::global_objects()
{
ENTER_SCOPE;
set_up();
Perf perf;
pl_stats_start(aTHX_ this, &perf);
SV* ret = pl_global_objects(aTHX_ this);
pl_stats_stop(aTHX_ this, &perf, "global_objects");
return ret;
}
int V8Context::run_gc()
{
ENTER_SCOPE;
set_up();
Perf perf;
pl_stats_start(aTHX_ this, &perf);
int ret = pl_run_gc(this);
pl_stats_stop(aTHX_ this, &perf, "run_gc");
return ret;
}
HV* V8Context::get_version_info()
{
if (!version) {
GetVersionInfo();
}
return version;
}
HV* V8Context::get_stats()
{
return stats;
}
void V8Context::reset_stats()
{
stats = newHV();
}
HV* V8Context::get_msgs()
{
return msgs;
}
void V8Context::reset_msgs()
{
msgs = newHV();
}
void V8Context::set_up()
{
if (inited) {
return;
}
inited = 1;
#if defined(V8_PROFILE_RESET) && V8_PROFILE_RESET > 0
double t0 = now_us();
#endif
/* Create a new Isolate and make it the current one. */
isolate = Isolate::New(create_params);
#if defined(V8_PROFILE_RESET) && V8_PROFILE_RESET > 0
double t1 = now_us();
#endif
ENTER_SCOPE;
/* Create the persistent objects that store our context. */
persistent_context = new Persistent<Context>;
persistent_template = new Persistent<ObjectTemplate>;
/* Create a template for the global object. */
Local<ObjectTemplate> object_template = ObjectTemplate::New(isolate);
/* Register callbacks to native functions in the template */
pl_register_native_functions(this, object_template);
/* Create a new context and reset the persistent objects. */
Local<Context> context = Context::New(isolate, 0, object_template);
persistent_context->Reset(isolate, context);
persistent_template->Reset(isolate, object_template);
/* Register eventloop handlers. */
pl_register_eventloop_functions(this);
/* Register inlined JS code. */
pl_register_inlined_functions(this);
/* Register console handlers. */
pl_register_console_functions(this);
#if defined(V8_PROFILE_RESET) && V8_PROFILE_RESET > 0
double t2 = now_us();
fprintf(stderr, "SET_UP: %5.0lf + %5.0lf = %5.0lf us\n", t1 - t0, t2 - t1, t2 - t0);
#endif
}
void V8Context::tear_down()
{
if (!inited) {
return;
}
inited = 0;
#if defined(V8_PROFILE_RESET) && V8_PROFILE_RESET > 0
double t0 = now_us();
#endif
delete persistent_template;
delete persistent_context;
isolate->Dispose();
#if defined(V8_PROFILE_RESET) && V8_PROFILE_RESET > 0
double t1 = now_us();
fprintf(stderr, "TEAR_DOWN: %5.0lf us\n", t1 - t0);
#endif
persistent_template = 0;
persistent_context = 0;
isolate = 0;
}
void V8Context::reset()
{
tear_down();
set_up();
}
const char* get_data_path()
{
static const char* locations[] = {
"/usr/lib64",
"/usr/lib",
"/usr/local/lib",
};
static const char* files[] = {
ICU_DTL_DATA,
V8_NATIVES_BLOB,
V8_SNAPSHOT_BLOB,
};
int num_locations = sizeof(locations) / sizeof(locations[0]);
for (int j = 0; j < num_locations; ++j) {
int num_files = sizeof(files) / sizeof(files[0]);
int found = 0;
for (int k = 0; k < num_files; ++k) {
char full[1024];
sprintf(full, "%s/%s", locations[j], files[k]);
if (access(full, R_OK) != 0) {
continue;
}
++found;
}
if (found < num_locations) {
continue;
}
/* found all required files -- yipee! */
return locations[j];
}
/* fallback -- it might fail */
return ".";
}
void V8Context::initialize_v8()
{
if (instance_count++) {
return;
}
/* get location for our V8 binary files */
const char* data_path = get_data_path();
/* initialize ICU, make it point to that path */
char icu_dtl_data[1024];
sprintf(icu_dtl_data, "%s/%s", data_path, ICU_DTL_DATA);
V8::InitializeICUDefaultLocation(PROGRAM_NAME, icu_dtl_data);
/* initialize V8 with the appropriate blob files */
char natives_blob[1024];
char snapshot_blob[1024];
sprintf(natives_blob, "%s/%s", data_path, V8_NATIVES_BLOB);
sprintf(snapshot_blob, "%s/%s", data_path, V8_SNAPSHOT_BLOB);
V8::InitializeExternalStartupData(natives_blob, snapshot_blob);
platform = platform::NewDefaultPlatform();
V8::InitializePlatform(platform.get());
V8::Initialize();
}
void V8Context::terminate_v8()
{
if (--instance_count) {
return;
}
V8::Dispose();
V8::ShutdownPlatform();
}
uint64_t V8Context::GetTypeFlags(const Local<Value>& v)
{
uint64_t result = 0;
if (v->IsArgumentsObject() ) result |= 0x0000000000000001;
if (v->IsArrayBuffer() ) result |= 0x0000000000000002;
if (v->IsArrayBufferView() ) result |= 0x0000000000000004;
if (v->IsArray() ) result |= 0x0000000000000008;
if (v->IsBooleanObject() ) result |= 0x0000000000000010;
if (v->IsBoolean() ) result |= 0x0000000000000020;
if (v->IsDataView() ) result |= 0x0000000000000040;
if (v->IsDate() ) result |= 0x0000000000000080;
if (v->IsExternal() ) result |= 0x0000000000000100;
if (v->IsFalse() ) result |= 0x0000000000000200;
if (v->IsFloat32Array() ) result |= 0x0000000000000400;
if (v->IsFloat64Array() ) result |= 0x0000000000000800;
if (v->IsFunction() ) result |= 0x0000000000001000;
if (v->IsGeneratorFunction()) result |= 0x0000000000002000;
if (v->IsGeneratorObject() ) result |= 0x0000000000004000;
if (v->IsInt16Array() ) result |= 0x0000000000008000;
if (v->IsInt32Array() ) result |= 0x0000000000010000;
if (v->IsInt32() ) result |= 0x0000000000020000;
if (v->IsInt8Array() ) result |= 0x0000000000040000;
if (v->IsMapIterator() ) result |= 0x0000000000080000;
if (v->IsMap() ) result |= 0x0000000000100000;
if (v->IsName() ) result |= 0x0000000000200000;
if (v->IsNativeError() ) result |= 0x0000000000400000;
if (v->IsNull() ) result |= 0x0000000000800000;
if (v->IsNumberObject() ) result |= 0x0000000001000000;
if (v->IsNumber() ) result |= 0x0000000002000000;
if (v->IsObject() ) result |= 0x0000000004000000;
if (v->IsPromise() ) result |= 0x0000000008000000;
if (v->IsRegExp() ) result |= 0x0000000010000000;
if (v->IsSetIterator() ) result |= 0x0000000020000000;
if (v->IsSet() ) result |= 0x0000000040000000;
if (v->IsStringObject() ) result |= 0x0000000080000000;
if (v->IsString() ) result |= 0x0000000100000000;
if (v->IsSymbolObject() ) result |= 0x0000000200000000;
if (v->IsSymbol() ) result |= 0x0000000400000000;
if (v->IsTrue() ) result |= 0x0000000800000000;
if (v->IsTypedArray() ) result |= 0x0000001000000000;
if (v->IsUint16Array() ) result |= 0x0000002000000000;
if (v->IsUint32Array() ) result |= 0x0000004000000000;
if (v->IsUint32() ) result |= 0x0000008000000000;
if (v->IsUint8Array() ) result |= 0x0000010000000000;
if (v->IsUint8ClampedArray()) result |= 0x0000020000000000;
if (v->IsUndefined() ) result |= 0x0000040000000000;
if (v->IsWeakMap() ) result |= 0x0000080000000000;
if (v->IsWeakSet() ) result |= 0x0000100000000000;
return result;
}
static void add_hash_key(HV* hash, const char* key, int val)
{
STRLEN klen = strlen(key);
SV* pval = sv_2mortal(newSVnv(val));
if (hv_store(hash, key, klen, pval, 0)) {
SvREFCNT_inc(pval);
}
else {
croak("Could not create numeric entry %s=%d in hash\n", key, val);
}
}
static void add_hash_key(HV* hash, const char* key, const char* val)
{
STRLEN klen = strlen(key);
STRLEN vlen = strlen(val);
SV* pval = sv_2mortal(newSVpv(val, vlen));
if (hv_store(hash, key, klen, pval, 0)) {
SvREFCNT_inc(pval);
}
else {
croak("Could not create string entry %s=[%s] in hash\n", key, val);
}
}
void V8Context::GetVersionInfo()
{
static struct version_info {
const char* field;
int value;
int skip_zero;
} VersionInfo[] = {
{ "major", V8_MAJOR_VERSION, 0 },
{ "minor", V8_MINOR_VERSION, 0 },
{ "build", V8_BUILD_NUMBER , 0 },
{ "patch", V8_PATCH_LEVEL , 1 },
};
version = newHV();
char buf[100];
int pos = 0;
for (unsigned int j = 0; j < sizeof(VersionInfo) / sizeof(VersionInfo[0]); ++j) {
int value = VersionInfo[j].value;
add_hash_key(version, VersionInfo[j].field, value);
if (!value && VersionInfo[j].skip_zero) {
continue;
}
if (pos > 0) {
buf[pos++] = '.';
}
pos += sprintf(buf + pos, "%d", value);
}
add_hash_key(version, "version", buf);
}