From 303f3f52b52f2f177bfeb2aa65056cd1f4093401 Mon Sep 17 00:00:00 2001 From: "C. Scott Ananian" Date: Fri, 25 Oct 2013 14:05:51 -0400 Subject: [PATCH] Refactor template cache into php_v8js_ctx. Reduce map lookups by adding internal object field pointing to function template. Use hidden field to allow easy unwrapping of PHP objects. --- php_v8js_macros.h | 30 ++++++++ v8js.cc | 18 ++++- v8js_convert.cc | 192 +++++++++++++++++++++------------------------- 3 files changed, 131 insertions(+), 109 deletions(-) diff --git a/php_v8js_macros.h b/php_v8js_macros.h index ba6a74e..67a5469 100644 --- a/php_v8js_macros.h +++ b/php_v8js_macros.h @@ -49,6 +49,35 @@ extern "C" { #define V8JS_THROW(type, message, message_len) v8::ThrowException(v8::Exception::type(V8JS_STRL(message, message_len))) #define V8JS_GLOBAL v8::Context::GetCurrent()->Global() +#if PHP_V8_API_VERSION < 3022000 +/* CopyablePersistentTraits is only part of V8 from 3.22.0 on, + to be compatible with lower versions add our own (compatible) version. */ +namespace v8 { + template + struct CopyablePersistentTraits { + typedef Persistent > CopyablePersistent; + static const bool kResetInDestructor = true; + template +#if PHP_V8_API_VERSION >= 3021015 + static V8_INLINE void Copy(const Persistent& source, + CopyablePersistent* dest) +#else + V8_INLINE(static void Copy(const Persistent& source, + CopyablePersistent* dest)) +#endif + { + // do nothing, just allow copy + } + }; +} +#endif + +/* Abbreviate long type names */ +typedef v8::Persistent > v8js_tmpl_t; + +/* Hidden field name used to link JS wrappers with underlying PHP object */ +#define PHPJS_OBJECT_KEY "phpjs::object" + /* Helper macros */ #if PHP_V8_API_VERSION < 2005009 # define V8JS_GET_CLASS_NAME(var, obj) \ @@ -131,6 +160,7 @@ struct php_v8js_ctx { zval *module_loader; std::vector modules_stack; std::vector modules_base; + std::map template_cache; }; /* }}} */ diff --git a/v8js.cc b/v8js.cc index c7ba662..8681103 100644 --- a/v8js.cc +++ b/v8js.cc @@ -285,11 +285,13 @@ int php_v8js_v8_get_properties_hash(v8::Handle jsValue, HashTable *re const char *key = ToCString(cstr); zval *value = NULL; - if(jsVal->IsObject() - && !jsVal->IsFunction() - && jsVal->ToObject()->InternalFieldCount() == 2) { + v8::Local php_object; + if (jsVal->IsObject()) { + php_object = v8::Local::Cast(jsVal)->GetHiddenValue(V8JS_SYM(PHPJS_OBJECT_KEY)); + } + if (!php_object.IsEmpty()) { /* This is a PHP object, passed to JS and back. */ - value = reinterpret_cast(jsVal->ToObject()->GetAlignedPointerFromInternalField(0)); + value = reinterpret_cast(v8::External::Cast(*php_object)->Value()); Z_ADDREF_P(value); } else { @@ -530,6 +532,13 @@ static void php_v8js_free_storage(void *object TSRMLS_DC) /* {{{ */ c->global_template.Reset(); c->global_template.~Persistent(); + /* Clear persistent handles in template cache */ + for (std::map::iterator it = c->template_cache.begin(); + it != c->template_cache.end(); ++it) { + it->second.Reset(); + } + c->template_cache.~map(); + /* Clear global object, dispose context */ if (!c->context.IsEmpty()) { c->context.Reset(); @@ -569,6 +578,7 @@ static zend_object_value php_v8js_new(zend_class_entry *ce TSRMLS_DC) /* {{{ */ new(&c->modules_stack) std::vector(); new(&c->modules_base) std::vector(); + new(&c->template_cache) std::map(); retval.handle = zend_objects_store_put(c, NULL, (zend_objects_free_object_storage_t) php_v8js_free_storage, NULL TSRMLS_CC); retval.handlers = &v8js_object_handlers; diff --git a/v8js_convert.cc b/v8js_convert.cc index 525b4fe..d8e7e55 100644 --- a/v8js_convert.cc +++ b/v8js_convert.cc @@ -29,37 +29,6 @@ extern "C" { #include #include -#define PHPJS_OBJECT_KEY "phpjs::object" - -#if PHP_V8_API_VERSION < 3022000 -/* CopyablePersistentTraits is only part of V8 from 3.22.0 on, - to be compatible with lower versions add our own (compatible) version. */ -namespace v8 { - template - struct CopyablePersistentTraits { - typedef Persistent > CopyablePersistent; - static const bool kResetInDestructor = true; - template -#if PHP_V8_API_VERSION >= 3021015 - static V8_INLINE void Copy(const Persistent& source, - CopyablePersistent* dest) -#else - V8_INLINE(static void Copy(const Persistent& source, - CopyablePersistent* dest)) -#endif - { - // do nothing, just allow copy - } - }; -} -#endif - -typedef std::pair TemplateCacheKey; -typedef v8::Persistent > TemplateCacheEntry; -typedef std::map TemplateCache; - -static TemplateCache tpl_map; - /* Callback for PHP methods and functions */ static void php_v8js_call_php_func(zval *value, zend_class_entry *ce, zend_function *method_ptr, v8::Isolate *isolate, const v8::FunctionCallbackInfo& info TSRMLS_DC) /* {{{ */ { @@ -115,11 +84,13 @@ static void php_v8js_call_php_func(zval *value, zend_class_entry *ce, zend_funct fci.params = (zval ***) safe_emalloc(argc, sizeof(zval **), 0); argv = (zval **) safe_emalloc(argc, sizeof(zval *), 0); for (i = 0; i < argc; i++) { - if(info[i]->IsObject() - && !info[i]->IsFunction() - && info[i]->ToObject()->InternalFieldCount() == 2) { + v8::Local php_object; + if (info[i]->IsObject()) { + php_object = v8::Local::Cast(info[i])->GetHiddenValue(V8JS_SYM(PHPJS_OBJECT_KEY)); + } + if (!php_object.IsEmpty()) { /* This is a PHP object, passed to JS and back. */ - argv[i] = reinterpret_cast(info[i]->ToObject()->GetAlignedPointerFromInternalField(0)); + argv[i] = reinterpret_cast(v8::External::Cast(*php_object)->Value()); Z_ADDREF_P(argv[i]); } else { MAKE_STD_ZVAL(argv[i]); @@ -180,8 +151,9 @@ failure: /* Callback for PHP methods and functions */ static void php_v8js_php_callback(const v8::FunctionCallbackInfo& info) /* {{{ */ { - zval *value = reinterpret_cast(info.Holder()->GetAlignedPointerFromInternalField(0)); - v8::Isolate *isolate = reinterpret_cast(info.Holder()->GetAlignedPointerFromInternalField(1)); + v8::Isolate *isolate = info.GetIsolate(); + v8::Local self = info.Holder(); + zval *value = reinterpret_cast(v8::External::Cast(*self->GetHiddenValue(V8JS_SYM(PHPJS_OBJECT_KEY)))->Value()); zend_function *method_ptr; TSRMLS_FETCH(); zend_class_entry *ce = Z_OBJCE_P(value); @@ -204,19 +176,23 @@ static void php_v8js_construct_callback(const v8::FunctionCallbackInfo newobj = info.This(); - zval *value; + v8::Local php_object; TSRMLS_FETCH(); if (!info.IsConstructCall()) { return; } + v8::Local cons_data = v8::Local::Cast(info.Data()); + v8::Local ext_tmpl = v8::Local::Cast(cons_data->Get(0)); + v8::Local ext_ce = v8::Local::Cast(cons_data->Get(1)); + if (info[0]->IsExternal()) { // Object created by v8js in php_v8js_hash_to_jsobj, PHP object passed as v8::External. - value = static_cast(v8::External::Cast(*info[0])->Value()); + php_object = v8::Local::Cast(info[0]); } else { // Object created from JavaScript context. Need to create PHP object first. - zend_class_entry *ce = static_cast(v8::External::Cast(*info.Data())->Value()); + zend_class_entry *ce = static_cast(ext_ce->Value()); zend_function *ctor_ptr = ce->constructor; // Check access on __construct function, if any @@ -225,6 +201,7 @@ static void php_v8js_construct_callback(const v8::FunctionCallbackInfoSetAlignedPointerInInternalField(0, value); - newobj->SetAlignedPointerInInternalField(1, (void *) isolate); - newobj->SetHiddenValue(V8JS_SYM(PHPJS_OBJECT_KEY), V8JS_BOOL(true)); + newobj->SetAlignedPointerInInternalField(0, ext_tmpl->Value()); + newobj->SetHiddenValue(V8JS_SYM(PHPJS_OBJECT_KEY), php_object); } /* }}} */ @@ -262,10 +239,9 @@ static int _php_v8js_is_assoc_array(HashTable *myht TSRMLS_DC) /* {{{ */ } /* }}} */ -static void php_v8js_weak_object_callback(v8::Isolate *isolate, v8::Persistent *object, zval *value) -{ +static void php_v8js_weak_object_callback(const v8::WeakCallbackData &data) { TSRMLS_FETCH(); - + zval *value = data.GetParameter(); if (READY_TO_DESTROY(value)) { zval_dtor(value); FREE_ZVAL(value); @@ -274,9 +250,14 @@ static void php_v8js_weak_object_callback(v8::Isolate *isolate, v8::PersistentDispose(); } +static void php_v8js_weak_closure_callback(const v8::WeakCallbackData &data) { + v8js_tmpl_t *persist_tpl_ = data.GetParameter(); + persist_tpl_->Reset(); + delete persist_tpl_; +}; + /* These are not defined by Zend */ #define ZEND_WAKEUP_FUNC_NAME "__wakeup" #define ZEND_SLEEP_FUNC_NAME "__sleep" @@ -293,6 +274,7 @@ static void php_v8js_weak_object_callback(v8::Isolate *isolate, v8::Persistent &info) /* {{{ */ { // note: 'special' properties like 'constructor' are not enumerated. + v8::Isolate *isolate = info.GetIsolate(); v8::Local self = info.Holder(); v8::Local result = v8::Array::New(0); uint32_t result_len = 0; @@ -305,8 +287,7 @@ static void php_v8js_named_property_enumerator(const v8::PropertyCallbackInfo(self->GetAlignedPointerFromInternalField(0)); - v8::Isolate *isolate = reinterpret_cast(self->GetAlignedPointerFromInternalField(1)); + zval *object = reinterpret_cast(v8::External::Cast(*self->GetHiddenValue(V8JS_SYM(PHPJS_OBJECT_KEY)))->Value()); ce = Z_OBJCE_P(object); /* enumerate all methods */ @@ -413,6 +394,7 @@ static void php_v8js_invoke_callback(const v8::FunctionCallbackInfo& // call (as opposed to a property get) from JavaScript using __call. static void php_v8js_fake_call_impl(const v8::FunctionCallbackInfo& info) /* {{{ */ { + v8::Isolate *isolate = info.GetIsolate(); v8::Local self = info.Holder(); v8::Handle return_value; @@ -420,8 +402,7 @@ static void php_v8js_fake_call_impl(const v8::FunctionCallbackInfo& i int error_len; zend_class_entry *ce; - zval *object = reinterpret_cast(self->GetAlignedPointerFromInternalField(0)); - v8::Isolate *isolate = reinterpret_cast(self->GetAlignedPointerFromInternalField(1)); + zval *object = reinterpret_cast(v8::External::Cast(*self->GetHiddenValue(V8JS_SYM(PHPJS_OBJECT_KEY)))->Value()); ce = Z_OBJCE_P(object); // first arg is method name, second arg is array of args. @@ -475,23 +456,17 @@ static void php_v8js_fake_call_impl(const v8::FunctionCallbackInfo& i return; } - php_v8js_ctx *ctx = (php_v8js_ctx *) isolate->GetData(); - try { - v8::Local tmpl = - v8::Local::New - (isolate, tpl_map.at(std::make_pair(ctx, ce->name))); - // use php_v8js_php_callback to actually execute the method - v8::Local cb = PHP_V8JS_CALLBACK(method_ptr, tmpl); - uint32_t i, argc = args->Length(); - v8::Local argv[argc]; - for (i=0; iGet(i); - } - return_value = cb->Call(info.This(), (int) argc, argv); - } catch (const std::out_of_range &) { - /* shouldn't fail! but bail safely */ - return_value = V8JS_NULL; + v8::Local tmpl = + v8::Local::New + (isolate, *reinterpret_cast(self->GetAlignedPointerFromInternalField(0))); + // use php_v8js_php_callback to actually execute the method + v8::Local cb = PHP_V8JS_CALLBACK(method_ptr, tmpl); + uint32_t i, argc = args->Length(); + v8::Local argv[argc]; + for (i=0; iGet(i); } + return_value = cb->Call(info.This(), (int) argc, argv); info.GetReturnValue().Set(return_value); } /* }}} */ @@ -507,6 +482,7 @@ typedef enum { template static inline v8::Local php_v8js_named_property_callback(v8::Local property, const v8::PropertyCallbackInfo &info, property_op_t callback_type, v8::Local set_value = v8::Local()) /* {{{ */ { + v8::Isolate *isolate = info.GetIsolate(); v8::String::Utf8Value cstr(property); const char *name = ToCString(cstr); uint name_len = strlen(name); @@ -523,8 +499,10 @@ static inline v8::Local php_v8js_named_property_callback(v8::Local(self->GetAlignedPointerFromInternalField(0)); - v8::Isolate *isolate = reinterpret_cast(self->GetAlignedPointerFromInternalField(1)); + zval *object = reinterpret_cast(v8::External::Cast(*self->GetHiddenValue(V8JS_SYM(PHPJS_OBJECT_KEY)))->Value()); + v8::Local tmpl = + v8::Local::New + (isolate, *reinterpret_cast(self->GetAlignedPointerFromInternalField(0))); ce = Z_OBJCE_P(object); /* First, check the (case-insensitive) method table */ @@ -549,27 +527,18 @@ static inline v8::Local php_v8js_named_property_callback(v8::LocalGetConstructor(); } else { - php_v8js_ctx *ctx = (php_v8js_ctx *) isolate->GetData(); - try { - v8::Local tmpl = - v8::Local::New - (isolate, tpl_map.at(std::make_pair(ctx, ce->name))); - if (is_magic_call && method_ptr==NULL) { - // Fake __call implementation - // (only use this if method_ptr==NULL, which means - // there is no actual PHP __call() implementation) - v8::Local cb = - v8::FunctionTemplate::New( - php_v8js_fake_call_impl, V8JS_NULL, - v8::Signature::New(tmpl))->GetFunction(); - cb->SetName(property); - ret_value = cb; - } else { - ret_value = PHP_V8JS_CALLBACK(method_ptr, tmpl); - } - } catch (const std::out_of_range &) { - /* shouldn't fail! but bail safely */ - ret_value = V8JS_NULL; + if (is_magic_call && method_ptr==NULL) { + // Fake __call implementation + // (only use this if method_ptr==NULL, which means + // there is no actual PHP __call() implementation) + v8::Local cb = + v8::FunctionTemplate::New( + php_v8js_fake_call_impl, V8JS_NULL, + v8::Signature::New(tmpl))->GetFunction(); + cb->SetName(property); + ret_value = cb; + } else { + ret_value = PHP_V8JS_CALLBACK(method_ptr, tmpl); } } } else if (callback_type == V8JS_PROP_QUERY) { @@ -709,25 +678,30 @@ static v8::Handle php_v8js_hash_to_jsobj(zval *value, v8::Isolate *is } else if (ce) { php_v8js_ctx *ctx = (php_v8js_ctx *) isolate->GetData(); v8::Local new_tpl; + v8js_tmpl_t *persist_tpl_; try { new_tpl = v8::Local::New - (isolate, tpl_map.at(std::make_pair(ctx, ce->name))); + (isolate, ctx->template_cache.at(ce->name)); } catch (const std::out_of_range &) { /* No cached v8::FunctionTemplate available as of yet, create one. */ new_tpl = v8::FunctionTemplate::New(); new_tpl->SetClassName(V8JS_STRL(ce->name, ce->name_length)); - new_tpl->InstanceTemplate()->SetInternalFieldCount(2); - - v8::Handle wrapped_ce = v8::External::New(ce); - new_tpl->SetCallHandler(php_v8js_construct_callback, wrapped_ce); + new_tpl->InstanceTemplate()->SetInternalFieldCount(1); if (ce == zend_ce_closure) { /* Got a closure, mustn't cache ... */ + persist_tpl_ = new v8js_tmpl_t(isolate, new_tpl); + /* We'll free persist_tpl_ via php_v8js_weak_closure_callback, below */ new_tpl->InstanceTemplate()->SetCallAsFunctionHandler(php_v8js_php_callback); } else { + /* Add new v8::FunctionTemplate to tpl_map, as long as it is not a closure. */ + persist_tpl_ = &ctx->template_cache[ce->name]; + persist_tpl_->Reset(isolate, new_tpl); + /* We'll free persist_tpl_ when template_cache is destroyed */ + // Finish setup of new_tpl new_tpl->InstanceTemplate()->SetNamedPropertyHandler (php_v8js_named_property_getter, /* getter */ php_v8js_named_property_setter, /* setter */ @@ -744,12 +718,17 @@ static v8::Handle php_v8js_hash_to_jsobj(zval *value, v8::Isolate *is invoke_method_ptr->common.fn_flags & ZEND_ACC_PUBLIC) { new_tpl->InstanceTemplate()->SetCallAsFunctionHandler(php_v8js_invoke_callback, PHP_V8JS_CALLBACK(invoke_method_ptr, new_tpl)); } - /* Add new v8::FunctionTemplate to tpl_map, as long as it is not a closure. */ - TemplateCacheEntry tce(isolate, new_tpl); - tpl_map[std::make_pair(ctx, ce->name)] = tce; } + v8::Local call_handler_data = v8::Array::New(2); + call_handler_data->Set(0, v8::External::New(persist_tpl_)); + call_handler_data->Set(1, v8::External::New(ce)); + new_tpl->SetCallHandler(php_v8js_construct_callback, call_handler_data); } + // Create v8 wrapper object + v8::Handle external = v8::External::New(value); + newobj = new_tpl->GetFunction()->NewInstance(1, &external); + // Increase the reference count of this value because we're storing it internally for use later // See https://github.com/preillyme/v8js/issues/6 Z_ADDREF_P(value); @@ -757,16 +736,18 @@ static v8::Handle php_v8js_hash_to_jsobj(zval *value, v8::Isolate *is // Since we got to decrease the reference count again, in case v8 garbage collector // decides to dispose the JS object, we add a weak persistent handle and register // a callback function that removes the reference. - v8::Handle external = v8::External::New(value); - v8::Persistent persist_newobj(isolate, new_tpl->GetFunction()->NewInstance(1, &external)); - persist_newobj.MakeWeak(value, php_v8js_weak_object_callback); + v8::Persistent persist_newobj(isolate, newobj); + persist_newobj.SetWeak(value, php_v8js_weak_object_callback); // Just tell v8 that we're allocating some external memory // (for the moment we just always tell 1k instead of trying to find out actual values) v8::V8::AdjustAmountOfExternalAllocatedMemory(1024); - newobj = v8::Local::New(isolate, persist_newobj); - + if (ce == zend_ce_closure) { + // free uncached function template when object is freed + v8::Persistent persist_newobj2(isolate, newobj); + persist_newobj2.SetWeak(persist_tpl_, php_v8js_weak_closure_callback); + } } else { v8::Local new_tpl = v8::FunctionTemplate::New(); // @todo re-use template likewise @@ -955,8 +936,9 @@ int v8js_to_zval(v8::Handle jsValue, zval *return_value, int flags, v { v8::Handle self = v8::Handle::Cast(jsValue); // if this is a wrapped PHP object, then just unwrap it. - if (!self->GetHiddenValue(V8JS_SYM(PHPJS_OBJECT_KEY)).IsEmpty()) { - zval *object = reinterpret_cast(self->GetAlignedPointerFromInternalField(0)); + v8::Local php_object = self->GetHiddenValue(V8JS_SYM(PHPJS_OBJECT_KEY)); + if (!php_object.IsEmpty()) { + zval *object = reinterpret_cast(v8::External::Cast(*php_object)->Value()); RETVAL_ZVAL(object, 1, 0); return SUCCESS; }