Skip to content

Commit 94136cf

Browse files
Improve asymmetric visibility write performance in fast path (GH-22709)
Skip the visibility check in the fast path of ZEND_ASSIGN_OBJ when the cache slot is primed. For this to work, ZEND_FETCH_OBJ_R and the other object handlers must not share a cache slot with ZEND_ASSIGN_OBJ. Also reset the cache slot when the visibility check has failed in the slow path. Co-authored-by: Ilija Tovilo <ilija.tovilo@me.com>
1 parent 588dd04 commit 94136cf

6 files changed

Lines changed: 120 additions & 67 deletions

File tree

‎Zend/Optimizer/compact_literals.c‎

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx
120120
HashTable hash;
121121
zend_string *key = NULL;
122122
void *checkpoint = zend_arena_checkpoint(ctx->arena);
123-
int *const_slot, *class_slot, *func_slot, *bind_var_slot, *property_slot, *method_slot, *jmp_slot;
123+
int *const_slot, *class_slot, *func_slot, *bind_var_slot, *property_slot, *method_slot, *jmp_slot, *assign_obj_slots;
124124

125125
if (op_array->last_literal) {
126126
uint32_t j;
@@ -439,14 +439,15 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx
439439
zend_hash_clean(&hash);
440440
op_array->last_literal = j;
441441

442-
const_slot = zend_arena_alloc(&ctx->arena, j * 7 * sizeof(int));
443-
memset(const_slot, -1, j * 7 * sizeof(int));
442+
const_slot = zend_arena_alloc(&ctx->arena, j * 8 * sizeof(int));
443+
memset(const_slot, -1, j * 8 * sizeof(int));
444444
class_slot = const_slot + j;
445445
func_slot = class_slot + j;
446446
bind_var_slot = func_slot + j;
447447
property_slot = bind_var_slot + j;
448448
method_slot = property_slot + j;
449449
jmp_slot = method_slot + j;
450+
assign_obj_slots = jmp_slot + j;
450451

451452
/* Update opcodes to use new literals table */
452453
cache_size = zend_op_array_extension_handles * sizeof(void*);
@@ -500,6 +501,19 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx
500501
}
501502
break;
502503
case ZEND_ASSIGN_OBJ:
504+
if (opline->op2_type == IS_CONST) {
505+
if (opline->op1_type == IS_UNUSED &&
506+
assign_obj_slots[opline->op2.constant] >= 0) {
507+
opline->extended_value = assign_obj_slots[opline->op2.constant];
508+
} else {
509+
opline->extended_value = cache_size;
510+
cache_size += 3 * sizeof(void *);
511+
if (opline->op1_type == IS_UNUSED) {
512+
assign_obj_slots[opline->op2.constant] = opline->extended_value;
513+
}
514+
}
515+
}
516+
break;
503517
case ZEND_ASSIGN_OBJ_REF:
504518
case ZEND_FETCH_OBJ_R:
505519
case ZEND_FETCH_OBJ_W:
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
--TEST--
2+
Asymmetric set visibility survives optimizer cache-slot sharing between $this reads and writes
3+
--FILE--
4+
<?php
5+
class P {
6+
public private(set) string $prop = 'default';
7+
}
8+
9+
class C extends P {
10+
public function test() {
11+
// The read populates a runtime cache slot for $this->prop; the write
12+
// below must not reuse that (read-kind) resolution to bypass the
13+
// set-visibility check when the optimizer shares property slots.
14+
var_dump($this->prop);
15+
try {
16+
$this->prop = 'overwritten';
17+
} catch (Error $e) {
18+
echo $e->getMessage(), "\n";
19+
}
20+
var_dump($this->prop);
21+
}
22+
}
23+
24+
$c = new C;
25+
$c->test();
26+
$c->test();
27+
?>
28+
--EXPECT--
29+
string(7) "default"
30+
Cannot modify private(set) property P::$prop from scope C
31+
string(7) "default"
32+
string(7) "default"
33+
Cannot modify private(set) property P::$prop from scope C
34+
string(7) "default"

‎Zend/zend_execute.c‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1065,7 +1065,7 @@ ZEND_API bool zend_never_inline zend_verify_property_type(const zend_property_in
10651065
return i_zend_verify_property_type(info, property, strict);
10661066
}
10671067

1068-
static zend_never_inline zval* zend_assign_to_typed_prop(const zend_property_info *info, zval *property_val, zval *value, zend_refcounted **garbage_ptr EXECUTE_DATA_DC)
1068+
static zend_never_inline zval* zend_assign_to_typed_prop(const zend_property_info *info, zval *property_val, zval *value, zend_refcounted **garbage_ptr, bool check_writable EXECUTE_DATA_DC)
10691069
{
10701070
zval tmp;
10711071

@@ -1074,7 +1074,7 @@ static zend_never_inline zval* zend_assign_to_typed_prop(const zend_property_inf
10741074
zend_readonly_property_modification_error(info);
10751075
return &EG(uninitialized_zval);
10761076
}
1077-
if (info->flags & ZEND_ACC_PPP_SET_MASK && !zend_asymmetric_property_has_set_access(info)) {
1077+
if (check_writable && (info->flags & ZEND_ACC_PPP_SET_MASK) && !zend_asymmetric_property_has_set_access(info)) {
10781078
zend_asymmetric_visibility_property_modification_error(info, "modify");
10791079
return &EG(uninitialized_zval);
10801080
}

‎Zend/zend_object_handlers.c‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1138,6 +1138,11 @@ ZEND_API zval *zend_std_write_property(zend_object *zobj, zend_string *name, zva
11381138
if ((prop_info->flags & ZEND_ACC_PPP_SET_MASK) && !zend_asymmetric_property_has_set_access(prop_info)) {
11391139
zend_asymmetric_visibility_property_modification_error(prop_info, "modify");
11401140
variable_ptr = &EG(error_zval);
1141+
if (cache_slot) {
1142+
/* Reset cache slot to dodge fast path in next execution. */
1143+
CACHE_POLYMORPHIC_PTR_EX(cache_slot, NULL, NULL);
1144+
CACHE_PTR_EX(cache_slot + 2, NULL);
1145+
}
11411146
goto exit;
11421147
}
11431148
}

‎Zend/zend_vm_def.h‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2524,7 +2524,7 @@ ZEND_VM_C_LABEL(assign_obj_simple):
25242524
property_val = OBJ_PROP(zobj, prop_offset);
25252525
if (Z_TYPE_P(property_val) != IS_UNDEF) {
25262526
if (prop_info != NULL) {
2527-
value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC);
2527+
value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, /* check_writable */ false EXECUTE_DATA_CC);
25282528
ZEND_VM_C_GOTO(free_and_exit_assign_obj);
25292529
} else {
25302530
ZEND_VM_C_LABEL(fast_assign_obj):
@@ -2659,7 +2659,7 @@ ZEND_VM_HANDLER(25, ZEND_ASSIGN_STATIC_PROP, ANY, ANY, CACHE_SLOT, SPEC(OP_DATA=
26592659
value = GET_OP_DATA_ZVAL_PTR(BP_VAR_R);
26602660

26612661
if (ZEND_TYPE_IS_SET(prop_info->type)) {
2662-
value = zend_assign_to_typed_prop(prop_info, prop, value, &garbage EXECUTE_DATA_CC);
2662+
value = zend_assign_to_typed_prop(prop_info, prop, value, &garbage, /* check_writable */ true EXECUTE_DATA_CC);
26632663
FREE_OP_DATA();
26642664
} else {
26652665
value = zend_assign_to_variable_ex(prop, value, OP_DATA_TYPE, EX_USES_STRICT_TYPES(), &garbage);

0 commit comments

Comments
 (0)