Skip to content

Elide ZEND_VERIFY_RETURN_TYPE when returning $this if possible#21948

Open
Girgias wants to merge 4 commits intophp:masterfrom
Girgias:elide-return-type-check-for-this-with-static
Open

Elide ZEND_VERIFY_RETURN_TYPE when returning $this if possible#21948
Girgias wants to merge 4 commits intophp:masterfrom
Girgias:elide-return-type-check-for-this-with-static

Conversation

@Girgias
Copy link
Copy Markdown
Member

@Girgias Girgias commented May 4, 2026

Not sure if this is the most sensible approach, I would expect this to be profitable.

Maybe we can extend this to type checks of instances of the current class so that it covers self types that are resolved at compile time. Done

Maybe the test should also be more somewhere more appropriate?

@Girgias Girgias marked this pull request as ready for review May 4, 2026 20:55
@Girgias Girgias requested a review from dstogov as a code owner May 4, 2026 20:55
@Girgias Girgias requested a review from iluuu1994 May 4, 2026 20:55
@Girgias Girgias force-pushed the elide-return-type-check-for-this-with-static branch from e874ad3 to bf784bd Compare May 4, 2026 23:39
@Girgias Girgias changed the title Do not emit ZEND_VERIFY_RETURN_TYPE when returning $this with static return type Elide ZEND_VERIFY_RETURN_TYPE when returning $this if possible May 4, 2026
Copy link
Copy Markdown
Member

@iluuu1994 iluuu1994 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks correct otherwise. No strong opinions, this might probably also be improved in the optimizer instead.

Comment thread Zend/zend_compile.c
* then we don't need to check the return type */
const zend_op_array *op_array = CG(active_op_array);
if (expr && op_array->last >= 1
&& op_array->opcodes[op_array->last-1].opcode == ZEND_FETCH_THIS
Copy link
Copy Markdown
Member

@iluuu1994 iluuu1994 May 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should probably check that the last opline is the right one, i.e. compare vars of expr and result var of the last opline. Maybe this is guaranteed, but then an assert would at least be useful.

; (after optimizer)
; %s:19-21
0000 T0 = FETCH_THIS
0001 RETURN T0
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test already almost passes on master with the exception of C4.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, seems this is a "recent" optimization since self is resolved to the class name as I don't think zend_optimizer_get_class_entry() is able to resolve self.

And I guess those could indeed be improved by handling unlinked classes in safe_instanceof() of dfa_pass.c

@Girgias
Copy link
Copy Markdown
Member Author

Girgias commented May 5, 2026

Looks correct otherwise. No strong opinions, this might probably also be improved in the optimizer instead.

I tried doing it here as we do some other basic elision in the compiler, but also I don't really understand the optimizer ^^" but will try touching it instead.

@Girgias Girgias force-pushed the elide-return-type-check-for-this-with-static branch from cf008aa to 3b6fd35 Compare May 5, 2026 14:36
@Girgias Girgias requested a review from iluuu1994 May 5, 2026 14:36
@Girgias
Copy link
Copy Markdown
Member Author

Girgias commented May 5, 2026

@iluuu1994 I tried improving the Optimizer, but it still doesn't give that good results compared to doing it at compile time. Ideally one should be able to elide cases when the interface is inherited/the class is extended, but seemingly zend_optimizer_get_class_entry() returns NULL in those cases, and I don't understand the optimizer enough to debug this case.

Doing it at compile time also allows traits to benefit from this optimization, covering more cases.

Comment thread Zend/Optimizer/dfa_pass.c
Comment on lines +272 to +279
if (ce1->ce_flags & ZEND_ACC_RESOLVED_INTERFACES) {
/* Unlike the normal instanceof_function(), we have to perform a recursive
* check here, as the parent interfaces might not have been fully copied yet. */
for (i = 0; i < ce1->num_interfaces; i++) {
if (safe_instanceof(ce1->interfaces[i], ce2, script, op_array)) {
return true;
}
}
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure how to test we hit this case? As the tests I added don't.

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.

2 participants