diff --git a/README.Linux.md b/README.Linux.md index e85c261..9f9afc9 100644 --- a/README.Linux.md +++ b/README.Linux.md @@ -10,7 +10,23 @@ years ago, since Node.js requires such an old version. This means that you usually need to compile v8 on your own before you can start to compile & install v8js itself. -Compile latest v8 +Snapshots +--------- + +V8 has (optional) support for so-called snapshots which speed up startup +performance drastically. Hence they are generally recommended for use. + +There are two flavours of snapshots: internal & external. + +Internal snapshots are built right into the V8 library (libv8.so file), +so there's no need to handle them specially. + +Besides there are external snapshots (which are enabled unless configured +otherwise). If V8 is compiled with these, then V8Js needs to provide two +"binary blobs" to V8, named `natives_blob.bin` and `snapshot_blob.bin`. +In that case copy those two files to `/usr/share/v8/...`. + +Compile latest V8 ----------------- ``` @@ -25,11 +41,15 @@ fetch v8 cd v8 # (optional) If you'd like to build a certain version: -git checkout 3.32.6 +git checkout 4.9.385.28 gclient sync -# Build (disable snapshots for V8 > 4.4.9.1) -make native library=shared snapshot=off -j8 +# use libicu of operating system +export GYP_DEFINES="use_system_icu=1" + +# Build (with internal snapshots) +export GYPFLAGS="-Dv8_use_external_startup_data=0" +make native library=shared snapshot=on -j8 # Install to /usr sudo mkdir -p /usr/lib /usr/include @@ -40,16 +60,9 @@ echo -e "create /usr/lib/libv8_libplatform.a\naddlib out/native/obj.target/tools Then add `extension=v8js.so` to your php.ini file. If you have a separate configuration for CLI, add it there also. -* If the V8 library is newer than 4.4.9.1 you need to pass `snapshot=off` to - `make`, otherwise the V8 library will not be usable - (see V8 [Issue 4192](https://code.google.com/p/v8/issues/detail?id=4192)) * If you don't want to overwrite the system copy of v8, replace `/usr` in the above commands with some other path like `/opt/v8` and then add `--with-v8js=/opt/v8` to the php-v8js `./configure` command below. -* If you do that with a v8 library of 4.2 branch or newer, then you need - to fix the RUNPATH header in the v8js.so library so the libicui18n.so - is found. By default it is set to `$ORIGIN/lib.target/`, however the files - lie side by side. Use `chrpath -r '$ORIGIN' libv8.so` to fix. `libv8_libplatform.a` should not be copied directly since it's a thin archive, i.e. it contains only pointers to the build objects, which @@ -62,7 +75,7 @@ Compile php-v8js itself ``` cd /tmp -git clone https://github.com/preillyme/v8js.git +git clone https://github.com/phpv8/v8js.git cd v8js phpize ./configure diff --git a/README.md b/README.md index 18ce894..c4a54b6 100644 --- a/README.md +++ b/README.md @@ -62,13 +62,15 @@ class V8Js /* Methods */ /** - * Initializes and starts V8 engine and Returns new V8Js object with it's own V8 context. + * Initializes and starts V8 engine and returns new V8Js object with it's own V8 context. + * Snapshots are supported by V8 4.3.7 and higher. * @param string $object_name * @param array $variables * @param array $extensions * @param bool $report_uncaught_exceptions + * @param string $snapshot_blob */ - public function __construct($object_name = "PHP", array $variables = NULL, array $extensions = NULL, $report_uncaught_exceptions = TRUE) + public function __construct($object_name = "PHP", array $variables = [], array $extensions = [], $report_uncaught_exceptions = TRUE, $snapshot_blob = NULL) {} /** @@ -174,6 +176,16 @@ class V8Js */ public static function getExtensions() {} + + /** + * Creates a custom V8 heap snapshot with the provided JavaScript source embedded. + * Snapshots are supported by V8 4.3.7 and higher. For older versions of V8 this + * extension doesn't provide this method. + * @param string $embed_source + * @return string|false + */ + public static function createSnapshot($embed_source) + {} } final class V8JsScriptException extends Exception diff --git a/config.m4 b/config.m4 index 1e3c488..f721165 100644 --- a/config.m4 +++ b/config.m4 @@ -130,11 +130,6 @@ int main () AC_MSG_ERROR([could not determine libv8 version]) fi - AC_LANG_RESTORE - LIBS=$old_LIBS - LDFLAGS=$old_LDFLAGS - CPPFLAGS=$old_CPPFLAGS - if test "$V8_API_VERSION" -ge 3029036 ; then dnl building for v8 3.29.36 or later, which requires us to dnl initialize and provide a platform; hence we need to @@ -169,10 +164,101 @@ int main () AC_MSG_ERROR([Please provide $static_link_extra_file next to the libv8.so, see README.md for details]) fi - LDFLAGS="$LDFLAGS $static_link_dir/$static_link_extra_file" + LDFLAGS_libplatform="$static_link_dir/$static_link_extra_file" done + + # modify flags for (possibly) succeeding V8 startup check + CPPFLAGS="$CPPFLAGS -I$V8_DIR" + LIBS="$LIBS $LDFLAGS_libplatform" fi + if test "$V8_API_VERSION" -ge 4004010 ; then + dnl building for v8 4.4.10 or later, which requires us to + dnl provide startup data, if V8 wasn't compiled with snapshot=off. + AC_MSG_CHECKING([whether V8 requires startup data]) + AC_TRY_RUN([ + #include + #include + #include + #include + +#if PHP_V8_API_VERSION >= 4004010 +class ArrayBufferAllocator : public v8::ArrayBuffer::Allocator { +public: + virtual void* Allocate(size_t length) { + void* data = AllocateUninitialized(length); + return data == NULL ? data : memset(data, 0, length); + } + virtual void* AllocateUninitialized(size_t length) { return malloc(length); } + virtual void Free(void* data, size_t) { free(data); } +}; +#endif + + int main () + { + v8::Platform *v8_platform = v8::platform::CreateDefaultPlatform(); + v8::V8::InitializePlatform(v8_platform); + v8::V8::Initialize(); + +#if PHP_V8_API_VERSION >= 4004044 + static ArrayBufferAllocator array_buffer_allocator; + v8::Isolate::CreateParams create_params; + create_params.array_buffer_allocator = &array_buffer_allocator; + + v8::Isolate::New(create_params); +#else /* PHP_V8_API_VERSION < 4004044 */ + v8::Isolate::New(); +#endif + return 0; + } + ], [ + AC_MSG_RESULT([no]) + ], [ + AC_MSG_RESULT([yes]) + AC_DEFINE([PHP_V8_USE_EXTERNAL_STARTUP_DATA], [1], [Whether V8 requires (and can be provided with custom versions of) external startup data]) + + SEARCH_PATH="$V8_DIR/lib $V8_DIR/share/v8" + + AC_MSG_CHECKING([for natives_blob.bin]) + SEARCH_FOR="natives_blob.bin" + + for i in $SEARCH_PATH ; do + if test -r $i/$SEARCH_FOR; then + AC_MSG_RESULT([found ($i/$SEARCH_FOR)]) + AC_DEFINE_UNQUOTED([PHP_V8_NATIVES_BLOB_PATH], "$i/$SEARCH_FOR", [Full path to natives_blob.bin file]) + native_blob_found=1 + fi + done + + if test -z "$native_blob_found"; then + AC_MSG_RESULT([not found]) + AC_MSG_ERROR([Please provide V8 native blob as needed]) + fi + + AC_MSG_CHECKING([for snapshot_blob.bin]) + SEARCH_FOR="snapshot_blob.bin" + + for i in $SEARCH_PATH ; do + if test -r $i/$SEARCH_FOR; then + AC_MSG_RESULT([found ($i/$SEARCH_FOR)]) + AC_DEFINE_UNQUOTED([PHP_V8_SNAPSHOT_BLOB_PATH], "$i/$SEARCH_FOR", [Full path to snapshot_blob.bin file]) + snapshot_blob_found=1 + fi + done + + if test -z "$snapshot_blob_found"; then + AC_MSG_RESULT([not found]) + AC_MSG_ERROR([Please provide V8 snapshot blob as needed]) + fi + ]) + fi + + AC_LANG_RESTORE + LIBS=$old_LIBS + LDFLAGS="$old_LDFLAGS $LDFLAGS_libplatform" + CPPFLAGS=$old_CPPFLAGS + + PHP_NEW_EXTENSION(v8js, [ \ v8js_array_access.cc \ v8js.cc \ diff --git a/tests/create_snapshot_basic.phpt b/tests/create_snapshot_basic.phpt new file mode 100644 index 0000000..731426d --- /dev/null +++ b/tests/create_snapshot_basic.phpt @@ -0,0 +1,32 @@ +--TEST-- +Test V8Js::createSnapshot() : Basic snapshot creation & re-use +--SKIPIF-- + +--FILE-- + 0) { + var_dump("snapshot successfully created"); +} + +$v8 = new V8Js('PHP', array(), array(), true, $snap); +$v8->executeString('var_dump(doublify(23));'); +?> +===EOF=== +--EXPECT-- +string(29) "snapshot successfully created" +int(46) +===EOF=== diff --git a/v8js_class.cc b/v8js_class.cc index a472209..f10725f 100644 --- a/v8js_class.cc +++ b/v8js_class.cc @@ -207,6 +207,12 @@ static void v8js_free_storage(void *object TSRMLS_DC) /* {{{ */ c->modules_stack.~vector(); c->modules_base.~vector(); +#if PHP_V8_API_VERSION >= 4003007 + if (c->zval_snapshot_blob) { + zval_ptr_dtor(&c->zval_snapshot_blob); + } +#endif + efree(object); } /* }}} */ @@ -327,7 +333,7 @@ static void v8js_fatal_error_handler(const char *location, const char *message) ((key_len == sizeof(mname)) && \ !strncasecmp(key, mname, key_len - 1)) -/* {{{ proto void V8Js::__construct([string object_name [, array variables [, array extensions [, bool report_uncaught_exceptions]]]) +/* {{{ proto void V8Js::__construct([string object_name [, array variables [, array extensions [, bool report_uncaught_exceptions [, string snapshot_blob]]]]]) __construct for V8Js */ static PHP_METHOD(V8Js, __construct) { @@ -338,6 +344,7 @@ static PHP_METHOD(V8Js, __construct) zval *vars_arr = NULL, *exts_arr = NULL; const char **exts = NULL; int exts_count = 0; + zval *snapshot_blob = NULL; v8js_ctx *c = (v8js_ctx *) zend_object_store_get_object(getThis() TSRMLS_CC); @@ -346,7 +353,7 @@ static PHP_METHOD(V8Js, __construct) return; } - if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|saab", &object_name, &object_name_len, &vars_arr, &exts_arr, &report_uncaught) == FAILURE) { + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|saabz", &object_name, &object_name_len, &vars_arr, &exts_arr, &report_uncaught, &snapshot_blob) == FAILURE){ return; } @@ -358,12 +365,30 @@ static PHP_METHOD(V8Js, __construct) c->pending_exception = NULL; c->in_execution = 0; +#if PHP_V8_API_VERSION >= 4003007 + new (&c->create_params) v8::Isolate::CreateParams(); + #if PHP_V8_API_VERSION >= 4004044 static ArrayBufferAllocator array_buffer_allocator; - static v8::Isolate::CreateParams create_params; - create_params.array_buffer_allocator = &array_buffer_allocator; - c->isolate = v8::Isolate::New(create_params); -#else + c->create_params.array_buffer_allocator = &array_buffer_allocator; +#endif + + new (&c->snapshot_blob) v8::StartupData(); + if (snapshot_blob) { + if (Z_TYPE_P(snapshot_blob) == IS_STRING) { + c->zval_snapshot_blob = snapshot_blob; + Z_ADDREF_P(c->zval_snapshot_blob); + + c->snapshot_blob.data = Z_STRVAL_P(snapshot_blob); + c->snapshot_blob.raw_size = Z_STRLEN_P(snapshot_blob); + c->create_params.snapshot_blob = &c->snapshot_blob; + } else { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Argument snapshot_blob expected to be of string type"); + } + } + + c->isolate = v8::Isolate::New(c->create_params); +#else /* PHP_V8_API_VERSION < 4003007 */ c->isolate = v8::Isolate::New(); #endif @@ -991,6 +1016,10 @@ static int v8js_register_extension(char *name, uint name_len, char *source, uint } /* }}} */ + + +/* ## Static methods ## */ + /* {{{ proto bool V8Js::registerExtension(string ext_name, string script [, array deps [, bool auto_enable]]) */ static PHP_METHOD(V8Js, registerExtension) @@ -1015,8 +1044,6 @@ static PHP_METHOD(V8Js, registerExtension) } /* }}} */ -/* ## Static methods ## */ - /* {{{ proto array V8Js::getExtensions() */ static PHP_METHOD(V8Js, getExtensions) @@ -1063,12 +1090,47 @@ static PHP_METHOD(V8Js, getExtensions) } /* }}} */ +#if PHP_V8_API_VERSION >= 4003007 +/* {{{ proto string|bool V8Js::createSnapshot(string embed_source) + */ +static PHP_METHOD(V8Js, createSnapshot) +{ + char *script; + int script_len; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &script, &script_len) == FAILURE) { + return; + } + + if (!script_len) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Script cannot be empty"); + RETURN_FALSE; + } + + /* Initialize V8, if not already done. */ + v8js_v8_init(TSRMLS_C); + + v8::StartupData snapshot_blob = v8::V8::CreateSnapshotDataBlob(script); + + if (!snapshot_blob.data) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to create V8 heap snapshot. Check $embed_source for errors."); + RETURN_FALSE; + } + + RETVAL_STRINGL(snapshot_blob.data, snapshot_blob.raw_size, 1); + delete[] snapshot_blob.data; +} +/* }}} */ +#endif /* PHP_V8_API_VERSION >= 4003007 */ + + /* {{{ arginfo */ ZEND_BEGIN_ARG_INFO_EX(arginfo_v8js_construct, 0, 0, 0) ZEND_ARG_INFO(0, object_name) ZEND_ARG_INFO(0, variables) ZEND_ARG_INFO(0, extensions) ZEND_ARG_INFO(0, report_uncaught_exceptions) + ZEND_ARG_INFO(0, snapshot_blob) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO(arginfo_v8js_sleep, 0) @@ -1126,6 +1188,12 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO(arginfo_v8js_getextensions, 0) ZEND_END_ARG_INFO() +#if PHP_V8_API_VERSION >= 4003007 +ZEND_BEGIN_ARG_INFO_EX(arginfo_v8js_createsnapshot, 0, 0, 1) + ZEND_ARG_INFO(0, script) +ZEND_END_ARG_INFO() +#endif + ZEND_BEGIN_ARG_INFO_EX(arginfo_v8js_settimelimit, 0, 0, 1) ZEND_ARG_INFO(0, time_limit) ZEND_END_ARG_INFO() @@ -1147,10 +1215,14 @@ const zend_function_entry v8js_methods[] = { /* {{{ */ PHP_ME(V8Js, clearPendingException, arginfo_v8js_clearpendingexception, ZEND_ACC_PUBLIC) PHP_ME(V8Js, setModuleNormaliser, arginfo_v8js_setmodulenormaliser, ZEND_ACC_PUBLIC) PHP_ME(V8Js, setModuleLoader, arginfo_v8js_setmoduleloader, ZEND_ACC_PUBLIC) - PHP_ME(V8Js, registerExtension, arginfo_v8js_registerextension, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) - PHP_ME(V8Js, getExtensions, arginfo_v8js_getextensions, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) PHP_ME(V8Js, setTimeLimit, arginfo_v8js_settimelimit, ZEND_ACC_PUBLIC) PHP_ME(V8Js, setMemoryLimit, arginfo_v8js_setmemorylimit, ZEND_ACC_PUBLIC) + PHP_ME(V8Js, registerExtension, arginfo_v8js_registerextension, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) + PHP_ME(V8Js, getExtensions, arginfo_v8js_getextensions, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) + +#if PHP_V8_API_VERSION >= 4003007 + PHP_ME(V8Js, createSnapshot, arginfo_v8js_createsnapshot, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) +#endif {NULL, NULL, NULL} }; /* }}} */ diff --git a/v8js_class.h b/v8js_class.h index ab7b7aa..0e91243 100644 --- a/v8js_class.h +++ b/v8js_class.h @@ -2,12 +2,13 @@ +----------------------------------------------------------------------+ | PHP Version 5 | +----------------------------------------------------------------------+ - | Copyright (c) 1997-2013 The PHP Group | + | Copyright (c) 1997-2016 The PHP Group | +----------------------------------------------------------------------+ | http://www.opensource.org/licenses/mit-license.php MIT License | +----------------------------------------------------------------------+ | Author: Jani Taskinen | | Author: Patrick Reilly | + | Author: Stefan Siegl | +----------------------------------------------------------------------+ */ @@ -67,6 +68,13 @@ struct v8js_ctx { std::vector accessor_list; std::vector script_objects; char *tz; + +#if PHP_V8_API_VERSION >= 4003007 + v8::Isolate::CreateParams create_params; + zval *zval_snapshot_blob; + v8::StartupData snapshot_blob; +#endif + #ifdef ZTS void ***zts_ctx; #endif diff --git a/v8js_v8.cc b/v8js_v8.cc index 8b52063..8091429 100644 --- a/v8js_v8.cc +++ b/v8js_v8.cc @@ -2,7 +2,7 @@ +----------------------------------------------------------------------+ | PHP Version 5 | +----------------------------------------------------------------------+ - | Copyright (c) 1997-2015 The PHP Group | + | Copyright (c) 1997-2016 The PHP Group | +----------------------------------------------------------------------+ | http://www.opensource.org/licenses/mit-license.php MIT License | +----------------------------------------------------------------------+ @@ -34,6 +34,43 @@ extern "C" { #include "v8js_timer.h" #include "v8js_exceptions.h" +#if defined(PHP_V8_USE_EXTERNAL_STARTUP_DATA) && PHP_V8_API_VERSION < 4006076 +/* Old V8 version, requires startup data but has no + * (internal/API) means to let it be loaded. */ +static v8::StartupData natives_; +static v8::StartupData snapshot_; + +static void v8js_v8_load_startup_data(const char* blob_file, + v8::StartupData* startup_data, + void (*setter_fn)(v8::StartupData*)) { + startup_data->data = NULL; + startup_data->raw_size = 0; + + if (!blob_file) { + return; + } + + FILE* file = fopen(blob_file, "rb"); + if (!file) { + return; + } + + fseek(file, 0, SEEK_END); + startup_data->raw_size = static_cast(ftell(file)); + rewind(file); + + startup_data->data = new char[startup_data->raw_size]; + int read_size = static_cast(fread(const_cast(startup_data->data), + 1, startup_data->raw_size, file)); + fclose(file); + + if (startup_data->raw_size == read_size) { + (*setter_fn)(startup_data); + } +} +#endif + + void v8js_v8_init(TSRMLS_D) /* {{{ */ { /* Run only once; thread-local test first */ @@ -54,6 +91,19 @@ void v8js_v8_init(TSRMLS_D) /* {{{ */ } #endif +#ifdef PHP_V8_USE_EXTERNAL_STARTUP_DATA + /* V8 doesn't work without startup data, load it. */ +#if PHP_V8_API_VERSION >= 4006076 + v8::V8::InitializeExternalStartupData( + PHP_V8_NATIVES_BLOB_PATH, + PHP_V8_SNAPSHOT_BLOB_PATH + ); +#else + v8js_v8_load_startup_data(PHP_V8_NATIVES_BLOB_PATH, &natives_, v8::V8::SetNativesDataBlob); + v8js_v8_load_startup_data(PHP_V8_SNAPSHOT_BLOB_PATH, &snapshot_, v8::V8::SetSnapshotDataBlob); +#endif +#endif + #if !defined(_WIN32) && PHP_V8_API_VERSION >= 3029036 v8js_process_globals.v8_platform = v8::platform::CreateDefaultPlatform(); v8::V8::InitializePlatform(v8js_process_globals.v8_platform);