Browse Source

Merge pull request #207 from stesie/custom-snapshots

Handle V8 heap snapshots well + allow custom snapshot generation
Stefan Siegl 9 years ago
parent
commit
87b29749b9
7 changed files with 305 additions and 32 deletions
  1. 25 12
      README.Linux.md
  2. 14 2
      README.md
  3. 92 6
      config.m4
  4. 32 0
      tests/create_snapshot_basic.phpt
  5. 82 10
      v8js_class.cc
  6. 9 1
      v8js_class.h
  7. 51 1
      v8js_v8.cc

+ 25 - 12
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

+ 14 - 2
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

+ 92 - 6
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 <v8.h>
+        #include <libplatform/libplatform.h>
+        #include <stdlib.h>
+        #include <string.h>
+
+#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					\

+ 32 - 0
tests/create_snapshot_basic.phpt

@@ -0,0 +1,32 @@
+--TEST--
+Test V8Js::createSnapshot() : Basic snapshot creation & re-use
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . '/skipif.inc');
+
+if (!method_exists('V8Js', 'createSnapshot')) {
+    die('SKIP V8Js::createSnapshot not supported');
+}
+?>
+--FILE--
+<?php
+$doublifySource = <<<EOJS
+function doublify(x) {
+    return 2 * x;
+}
+EOJS;
+
+$snap = V8Js::createSnapshot($doublifySource);
+
+if (strlen($snap) > 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===

+ 82 - 10
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}
 };
 /* }}} */

+ 9 - 1
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 <[email protected]>                         |
   | Author: Patrick Reilly <[email protected]>                             |
+  | Author: Stefan Siegl <[email protected]>                                |
   +----------------------------------------------------------------------+
 */
 
@@ -67,6 +68,13 @@ struct v8js_ctx {
   std::vector<v8js_accessor_ctx *> accessor_list;
   std::vector<struct _v8js_script *> 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

+ 51 - 1
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<int>(ftell(file));
+	rewind(file);
+
+	startup_data->data = new char[startup_data->raw_size];
+	int read_size = static_cast<int>(fread(const_cast<char*>(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);