Skip to content

Fix readonly+clone JIT issues #10748

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from

Conversation

iluuu1994
Copy link
Member

@iluuu1994 iluuu1994 commented Mar 2, 2023

No description provided.

@iluuu1994 iluuu1994 force-pushed the clone-readonly-jit-fixes branch 2 times, most recently from f4fe194 to 57ed3bc Compare March 3, 2023 01:25
@iluuu1994 iluuu1994 force-pushed the clone-readonly-jit-fixes branch from 57ed3bc to ee15caf Compare March 3, 2023 01:26
@iluuu1994 iluuu1994 marked this pull request as ready for review March 3, 2023 02:06
@iluuu1994 iluuu1994 requested a review from dstogov March 3, 2023 02:06
@dstogov
Copy link
Member

dstogov commented Mar 3, 2023

What is IS_PROP_REINITABLE and where it came from?
It looks like I messed some committed hack. Please point me to that PR.

As I understood from a quick look, zend_readonly_property_modification_error() shouldn't be called on some rare condition. I think, Instead of generation this checking code for each property access, it should be moved into a new helper function.

I'll take a deep look on Monday.

@iluuu1994
Copy link
Member Author

This was introduced for https://wiki.php.net/rfc/readonly_amendments (vote 2) in #10389.

iluuu1994 added a commit that referenced this pull request Mar 3, 2023
@dstogov
Copy link
Member

dstogov commented Mar 6, 2023

I really don't like the original path 3bcf2c37553
@kocsismate sorry I didn't review that commit. It would be better to ping me.

The patch introduces a hack with complex behaviour and logic spread among the code.
This almost always causes missed edge cases and subsequent issues.
Playing for a few minutes I found this weird case:
In clone we may assign to uninitialized read-only property, but for some reason we can't pass it by reference.

<?php
class Foo {
    readonly int $y;
    public function __construct(
        public readonly int $bar
    ) {}

    public function test(&$x) {
            $x = 4;
    }

    public function __clone()
    {
            $this->test($this->bar);
            $this->test($this->y);
    }
}
$a = new Foo(1);
var_dump($a);
$b = clone $a;
var_dump($b);
?>

I suggest to make a deeper review of 3bcf2c37553 fix all the issues and only then try to fix JIT. If more issues are found, I would prefer to revert 3bcf2c3 and re-commit it when all issues are fixed.

@iluuu1994
Copy link
Member Author

iluuu1994 commented Mar 6, 2023

@dstogov Thank you for your comments!

In clone we may assign to uninitialized read-only property, but for some reason we can't pass it by reference.

Unless I'm misunderstanding, I think this is the default behavior without clone too.

https://3v4l.org/ghZR5

The error message is a bit misleading, as we don't disallow all indirect modification but just references. Actually, we changed that. I forgot.

@kocsismate
Copy link
Member

Unless I'm misunderstanding, I think this is the default behavior without clone too.

Yes, you cannot acquire reference on readonly properties. The readonly RFC doesn't highlight this too much, but that was the case since PHP 8.1.

@kocsismate
Copy link
Member

@dstogov

The patch introduces a hack with complex behaviour and logic spread among the code.
This almost always causes missed edge cases and subsequent issues.

I understand your point of view, and I would appreciate if you could tell me what direction the implementation should rather take?

@dstogov
Copy link
Member

dstogov commented Mar 6, 2023

I see, we have a difference in behaviour, because now we may work with uninitialized read-only properties and with already initialized ones. So the behaviour of the case above may be considered as expected, but it's not easy to understand.

I don't have any better solution out of the box, and most probably won't. I'll try to think a bit.

@dstogov
Copy link
Member

dstogov commented Mar 6, 2023

@kocsismate As I expected, I can't suggest any other way to implement cloning of read-only properties. It like adding exceptions for some rule, then exceptions for exceptions, etc. I think, PHP took wrong direction few years ago, and over-complication of the sources that we have now don't cost many of new PHP features :(

@iluuu1994 This patch looks right. It may be improved a bit by removing few instructions on cold path and jumping to the hot path instead. This may fix (or open) some issues with null-to-array promotion. Patch for x86:

diff --git a/ext/opcache/jit/zend_jit_x86.dasc b/ext/opcache/jit/zend_jit_x86.dasc
index 42785e285f..27ed084fd9 100644
--- a/ext/opcache/jit/zend_jit_x86.dasc
+++ b/ext/opcache/jit/zend_jit_x86.dasc
@@ -12983,6 +12983,17 @@ static int zend_jit_fetch_obj(dasm_State          **Dst,
 			|	SET_ZVAL_TYPE_INFO res_addr, IS_OBJECT_EX
 			|	jmp >9
 			|2:
+			|	mov eax, dword [FCARG1a + offsetof(zval, u2.extra)]
+			|	test eax, IS_PROP_REINITABLE
+			|	jz >6
+			|	and eax, ~IS_PROP_REINITABLE
+			|	mov dword [FCARG1a + offsetof(zval, u2.extra)], eax
+			if (flags) {
+				|	jmp >3
+			} else {
+				|	jmp >4
+			}
+			|6:
 			|	mov FCARG1a, FCARG2a
 			|	SET_EX_OPLINE opline, r0
 			|	EXT_CALL zend_readonly_property_modification_error, r0
@@ -13034,7 +13045,7 @@ static int zend_jit_fetch_obj(dasm_State          **Dst,
 		if (opline->opcode == ZEND_FETCH_OBJ_W && (prop_info->flags & ZEND_ACC_READONLY)) {
 			if (!type_loaded) {
 				type_loaded = 1;
-				|	mov edx, dword [FCARG1a + prop_info->offset + 8]
+				|	mov edx, dword [FCARG1a + prop_info->offset + offsetof(zval, u1.type_info)]
 			}
 			|	IF_NOT_TYPE dl, IS_OBJECT, >4
 			|	GET_ZVAL_PTR r0, prop_addr
@@ -13044,12 +13055,20 @@ static int zend_jit_fetch_obj(dasm_State          **Dst,
 			|	jmp >9
 			|.cold_code
 			|4:
+			|	mov eax, dword [FCARG1a + prop_info->offset + offsetof(zval, u2.extra)]
+			|	test eax, IS_PROP_REINITABLE
+			|	jz >6
+			|	and eax, ~IS_PROP_REINITABLE
+			|	mov dword [FCARG1a + prop_info->offset + offsetof(zval, u2.extra)], eax
+			|	jmp >4
+			|6:
 			|	LOAD_ADDR FCARG1a, prop_info
 			|	SET_EX_OPLINE opline, r0
 			|	EXT_CALL zend_readonly_property_modification_error, r0
 			|	SET_ZVAL_TYPE_INFO res_addr, _IS_ERROR
 			|	jmp >9
 			|.code
+			|4:
 		}
 		if (opline->opcode == ZEND_FETCH_OBJ_W
 		 && (opline->extended_value & ZEND_FETCH_OBJ_FLAGS)

I think it won't be a problem to update ARM part accordingly.

@iluuu1994
Copy link
Member Author

It may be improved a bit by removing few instructions on cold path and jumping to the hot path instead.

That looks better, thank you! I'll adjust this tomorrow.

@iluuu1994 iluuu1994 closed this in 3f7dadf Mar 7, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants