Skip to content

Reference runtime-defined functions from literals #5593

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

nikic
Copy link
Member

@nikic nikic commented May 18, 2020

Currently, we handle closures and conditionally defined functions by inserting them into the function table with an RTD key. There are two issues with this:

  1. RTD key collisions. It is hard to ensure that RTD keys unique if opcache is involved. Our changes in 7.4 were insufficient, see https://bugs.php.net/bug.php?id=79603.
  2. Leaks. Function templates that end up being unused or not used anymore are never released and leak, see https://bugs.php.net/bug.php?id=76982. This is not much of a problem in web scenarios, but may result in huge leaks in unit tests for example.

This patch addresses this by storing a direct reference to the function as CONST opcode literal. We add a small zend_func_ref wrapper to allow zval storage in a way that is integrated with the existing refcounting system.

This naturally avoids RTD key collisions, because we no longer use an RTD key for this purpose. It partially avoids the memory leak as well. Most of the function structures will now get freed and we only leak the arena allocated parts. Avoiding arena-allocated parts for runtime-defined functions would be a followup.

There are still two problems here:

  • As the functions are no longer part of the function_table, they'll get skipped by the optimizer.
  • This only works if the function is immutable in opcache. I think this is currently not fully guaranteed, but I'm not sure under what circumstances free functions can be non-immutable in opcache.

Unfortunately this approach will not extend to classes, because those are often not immutable...

@dstogov What do you think about this?

nikic added 2 commits May 18, 2020 12:06
zend_functions for closures and dynamically declared functions
are now referenced in the literals array through zend_func_ref.
This avoids the need for collision-prone runtime-definition keys,
and avoid memory leaks when the same file is included many times.
@nikic
Copy link
Member Author

nikic commented May 18, 2020

A possible alternative in terms of general approach would be to add zend_function **dynamic_funcs to op_array, store all dynamically declared functions in there and reference them by offset from the literals. The advantage of this approach is that it does not require immutability and as such naturally extends to classes.

@nikic
Copy link
Member Author

nikic commented May 18, 2020

I created a prototype for the variant in my last comment at #5595. Unfortunately, I don't think it really helps with the class case either, mutability still remains an issue.

Overall I'm not sure how to solve these problems. Just solving the function case would already be good, but it would be good to have a solution that also works with classes.

@dstogov
Copy link
Member

dstogov commented May 18, 2020

1. RTD key collisions. It is hard to ensure that RTD keys unique if opcache is involved. Our changes in 7.4 were insufficient, see https://bugs.php.net/bug.php?id=79603.

To fix the problem, we may increment CG(rtd_key_counter) and try another key.

2. Leaks. Function templates that end up being unused or not used anymore are never released and leak, see https://bugs.php.net/bug.php?id=76982. This is not much of a problem in web scenarios, but may result in huge leaks in unit tests for example.

The problem occurs only without opcache, if we include the same file again and again.

I understood the problem and the ways you are trying to solve it.
Unfortunately, the solutions look too complex and incomplete + add troubles to optimizer.

Quick ideas:

  1. zend_op_array and zend_class_entry already have reference counters and we may reuse them.
  2. we may delete RTD keys by special opcodes in implicit "finally" block added to main op_array

@nikic
Copy link
Member Author

nikic commented May 19, 2020

To fix the problem, we may increment CG(rtd_key_counter) and try another key.

It's not a full solution, but yes, we should do this for 7.4 at least.

2. Leaks. Function templates that end up being unused or not used anymore are never released and leak, see https://bugs.php.net/bug.php?id=76982. This is not much of a problem in web scenarios, but may result in huge leaks in unit tests for example.

The problem occurs only without opcache, if we include the same file again and again.

That's right.

I understood the problem and the ways you are trying to solve it.
Unfortunately, the solutions look too complex and incomplete + add troubles to optimizer.

Quick ideas:

1. zend_op_array and zend_class_entry already have reference counters and we may reuse them.

This patch is actually based on the normal zend_op_array refcount (which is already incremented/decremented by closures). The additional refcount in the zend_func_ref structure is not really needed, it's just there so functions can be stored as a normal zval, without special-casing. The variant in #5595 has a simpler implementation that does not store functions in zvals.

For zend_class_entry, apart from the issues with mutability, there is also the issue that objects referencing a class don't increase its refcount, which means it wouldn't actually be safe to drop "unused" anon class templates, as we don't track if they are still used by an object. (We could of course add this tracking).

2. we may delete RTD keys by special opcodes in implicit "finally" block added to main op_array

Handling this just in the main op_array is not enough, due to e.g. nested closures. But yes, in principle it can work. However, finally blocks interfere with optimizer and runtime behavior (e.g. in generators).

@nikic
Copy link
Member Author

nikic commented Jan 26, 2021

Closing in favor of #5595.

@nikic nikic closed this Jan 26, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants