Skip to content

Commit

Permalink
Optimize ReflectionProperty::getValue(), ::getRawValue(), ::isInitial…
Browse files Browse the repository at this point in the history
…ized(), ::setValue(), ::setRawValue()

- Pass a cache slot to the property handler
- Inline the simple case of fetching a declared property
- Use the new parameter parsing API

This makes these methods 12% to 60% faster.

Using the new parameter parsing API had the most impact, by far.
  • Loading branch information
arnaud-lb committed Feb 4, 2025
1 parent 4fcbdea commit cf16078
Showing 1 changed file with 86 additions and 32 deletions.
118 changes: 86 additions & 32 deletions ext/reflection/php_reflection.c
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ PHPAPI zend_class_entry *reflection_property_hook_type_ptr;
typedef struct _property_reference {
zend_property_info *prop;
zend_string *unmangled_name;
void *cache_slot[3];
} property_reference;

/* Struct for parameters */
Expand Down Expand Up @@ -1506,6 +1507,7 @@ static void reflection_property_factory(zend_class_entry *ce, zend_string *name,
reference = (property_reference*) emalloc(sizeof(property_reference));
reference->prop = prop;
reference->unmangled_name = zend_string_copy(name);
memset(reference->cache_slot, 0, sizeof(reference->cache_slot));
intern->ptr = reference;
intern->ref_type = REF_TYPE_PROPERTY;
intern->ce = ce;
Expand Down Expand Up @@ -5643,6 +5645,7 @@ ZEND_METHOD(ReflectionProperty, __construct)
reference = (property_reference*) emalloc(sizeof(property_reference));
reference->prop = dynam_prop ? NULL : property_info;
reference->unmangled_name = zend_string_copy(name);
memset(reference->cache_slot, 0, sizeof(reference->cache_slot));
intern->ptr = reference;
intern->ref_type = REF_TYPE_PROPERTY;
intern->ce = ce;
Expand Down Expand Up @@ -5794,9 +5797,10 @@ ZEND_METHOD(ReflectionProperty, getValue)
zval *object = NULL;
zval *member_p = NULL;

if (zend_parse_parameters(ZEND_NUM_ARGS(), "|o!", &object) == FAILURE) {
RETURN_THROWS();
}
ZEND_PARSE_PARAMETERS_START(0, 1)
Z_PARAM_OPTIONAL
Z_PARAM_OBJECT_EX(object, 1, 0)
ZEND_PARSE_PARAMETERS_END();

GET_REFLECTION_OBJECT_PTR(ref);

Expand All @@ -5813,13 +5817,29 @@ ZEND_METHOD(ReflectionProperty, getValue)
RETURN_THROWS();
}

/* TODO: Should this always use intern->ce? */
if (!instanceof_function(Z_OBJCE_P(object), ref->prop ? ref->prop->ce : intern->ce)) {
_DO_THROW("Given object is not an instance of the class this property was declared in");
RETURN_THROWS();
if (ref->cache_slot[0] == Z_OBJCE_P(object)) {
uintptr_t prop_offset = (uintptr_t) ref->cache_slot[1];

if (EXPECTED(IS_VALID_PROPERTY_OFFSET(prop_offset))) {
zval *retval = OBJ_PROP(Z_OBJ_P(object), prop_offset);
if (EXPECTED(Z_TYPE_INFO_P(retval) != IS_UNDEF)) {
RETURN_COPY_DEREF(retval);
}
}
} else {
/* TODO: Should this always use intern->ce? */
if (!instanceof_function(Z_OBJCE_P(object), ref->prop ? ref->prop->ce : intern->ce)) {
_DO_THROW("Given object is not an instance of the class this property was declared in");
RETURN_THROWS();
}
}

member_p = zend_read_property_ex(intern->ce, Z_OBJ_P(object), ref->unmangled_name, 0, &rv);
zend_class_entry *old_scope = EG(fake_scope);
EG(fake_scope) = intern->ce;
member_p = Z_OBJ_P(object)->handlers->read_property(Z_OBJ_P(object),
ref->unmangled_name, BP_VAR_R, ref->cache_slot, &rv);
EG(fake_scope) = old_scope;

if (member_p != &rv) {
RETURN_COPY_DEREF(member_p);
} else {
Expand Down Expand Up @@ -5873,7 +5893,10 @@ ZEND_METHOD(ReflectionProperty, setValue)
Z_PARAM_ZVAL(value)
ZEND_PARSE_PARAMETERS_END();

zend_update_property_ex(intern->ce, object, ref->unmangled_name, value);
zend_class_entry *old_scope = EG(fake_scope);
EG(fake_scope) = intern->ce;
object->handlers->write_property(object, ref->unmangled_name, value, ref->cache_slot);
EG(fake_scope) = old_scope;
}
}
/* }}} */
Expand All @@ -5884,25 +5907,41 @@ ZEND_METHOD(ReflectionProperty, getRawValue)
property_reference *ref;
zval *object;

if (zend_parse_parameters(ZEND_NUM_ARGS(), "o", &object) == FAILURE) {
RETURN_THROWS();
}
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_OBJECT(object)
ZEND_PARSE_PARAMETERS_END();

GET_REFLECTION_OBJECT_PTR(ref);

if (prop_get_flags(ref) & ZEND_ACC_STATIC) {
_DO_THROW("May not use getRawValue on static properties");
RETURN_THROWS();
}
if (ref->cache_slot[0] == Z_OBJCE_P(object)) {
uintptr_t prop_offset = (uintptr_t) ref->cache_slot[1];

if (!instanceof_function(Z_OBJCE_P(object), intern->ce)) {
_DO_THROW("Given object is not an instance of the class this property was declared in");
RETURN_THROWS();
if (EXPECTED(IS_VALID_PROPERTY_OFFSET(prop_offset))) {
zval *retval = OBJ_PROP(Z_OBJ_P(object), prop_offset);
if (EXPECTED(Z_TYPE_INFO_P(retval) != IS_UNDEF)) {
RETURN_COPY_DEREF(retval);
}
}
} else {
if (prop_get_flags(ref) & ZEND_ACC_STATIC) {
_DO_THROW("May not use getRawValue on static properties");
RETURN_THROWS();
}

if (!instanceof_function(Z_OBJCE_P(object), intern->ce)) {
_DO_THROW("Given object is not an instance of the class this property was declared in");
RETURN_THROWS();
}
}

if (!ref->prop || !ref->prop->hooks || !ref->prop->hooks[ZEND_PROPERTY_HOOK_GET]) {
zval rv;
zval *member_p = zend_read_property_ex(intern->ce, Z_OBJ_P(object), ref->unmangled_name, 0, &rv);
zend_class_entry *old_scope = EG(fake_scope);
EG(fake_scope) = intern->ce;
zval *member_p = Z_OBJ_P(object)->handlers->read_property(
Z_OBJ_P(object), ref->unmangled_name, BP_VAR_R,
ref->cache_slot, &rv);
EG(fake_scope) = old_scope;

if (member_p != &rv) {
RETURN_COPY_DEREF(member_p);
Expand All @@ -5921,7 +5960,10 @@ ZEND_METHOD(ReflectionProperty, getRawValue)
static void reflection_property_set_raw_value(property_reference *ref, reflection_object *intern, zend_object *object, zval *value)
{
if (!ref->prop || !ref->prop->hooks || !ref->prop->hooks[ZEND_PROPERTY_HOOK_SET]) {
zend_update_property_ex(intern->ce, object, ref->unmangled_name, value);
zend_class_entry *old_scope = EG(fake_scope);
EG(fake_scope) = intern->ce;
object->handlers->write_property(object, ref->unmangled_name, value, ref->cache_slot);
EG(fake_scope) = old_scope;
} else {
zend_function *func = zend_get_property_hook_trampoline(ref->prop, ZEND_PROPERTY_HOOK_SET, ref->unmangled_name);
zend_call_known_instance_method_with_1_params(func, object, NULL, value);
Expand All @@ -5942,9 +5984,10 @@ ZEND_METHOD(ReflectionProperty, setRawValue)
RETURN_THROWS();
}

if (zend_parse_parameters(ZEND_NUM_ARGS(), "oz", &object, &value) == FAILURE) {
RETURN_THROWS();
}
ZEND_PARSE_PARAMETERS_START(2, 2) {
Z_PARAM_OBJECT(object)
Z_PARAM_ZVAL(value)
} ZEND_PARSE_PARAMETERS_END();

reflection_property_set_raw_value(ref, intern, Z_OBJ_P(object), value);
}
Expand Down Expand Up @@ -6116,9 +6159,10 @@ ZEND_METHOD(ReflectionProperty, isInitialized)
zval *object = NULL;
zval *member_p = NULL;

if (zend_parse_parameters(ZEND_NUM_ARGS(), "|o!", &object) == FAILURE) {
RETURN_THROWS();
}
ZEND_PARSE_PARAMETERS_START(0, 1)
Z_PARAM_OPTIONAL
Z_PARAM_OBJECT_EX(object, 1, 0)
ZEND_PARSE_PARAMETERS_END();

GET_REFLECTION_OBJECT_PTR(ref);

Expand All @@ -6137,15 +6181,25 @@ ZEND_METHOD(ReflectionProperty, isInitialized)
RETURN_THROWS();
}

/* TODO: Should this always use intern->ce? */
if (!instanceof_function(Z_OBJCE_P(object), ref->prop ? ref->prop->ce : intern->ce)) {
_DO_THROW("Given object is not an instance of the class this property was declared in");
RETURN_THROWS();
if (ref->cache_slot[0] == Z_OBJCE_P(object)) {
uintptr_t prop_offset = (uintptr_t) ref->cache_slot[1];

if (EXPECTED(IS_VALID_PROPERTY_OFFSET(prop_offset))) {
zval *value = OBJ_PROP(Z_OBJ_P(object), prop_offset);
RETURN_BOOL(Z_TYPE_INFO_P(value) != IS_UNDEF);
}
} else {
/* TODO: Should this always use intern->ce? */
if (!instanceof_function(Z_OBJCE_P(object), ref->prop ? ref->prop->ce : intern->ce)) {
_DO_THROW("Given object is not an instance of the class this property was declared in");
RETURN_THROWS();
}
}

old_scope = EG(fake_scope);
EG(fake_scope) = intern->ce;
retval = Z_OBJ_HT_P(object)->has_property(Z_OBJ_P(object), ref->unmangled_name, ZEND_PROPERTY_EXISTS, NULL);
retval = Z_OBJ_HT_P(object)->has_property(Z_OBJ_P(object),
ref->unmangled_name, ZEND_PROPERTY_EXISTS, ref->cache_slot);
EG(fake_scope) = old_scope;

RETVAL_BOOL(retval);
Expand Down

0 comments on commit cf16078

Please sign in to comment.