-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Mark unterminated INIT_CALL and SEND_VAL instructions as NOP when removing unreachable basic blocks #5358
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
Conversation
when removing unreachable basic blocks
Converting THROW into expression changed Optimizer assumptions and it starts to optimize it incorrectly. Actually, it already incorrectly optimizes exit(). It doesn't keep live range for result of the first NEW. <?php
class A {
function __construct($a, $b){
}
}
new A(new A(1,2), exit(0));
@iluuu1994 I think, your approach with removing INIT_FCALL, SEND_VAL is incorrect. @nikic ? |
Yes, I don't think this problem really has to do anything with calls, it will probably also appear with other typical live-range uses like Handling EXIT/THROW similar to RETURN in the compiler makes sense to me, I'll take a look at that. |
I see, thanks guys for your assessment! Regardless, wouldn't it be better to optimize away unused |
INIT_CALL and SEND_VAL can have a lot of side-effects (e.g., function does not exist, passing constant by reference, etc), so only very limited cases can be removed. It's unlikely to be worthwhile.
Hm, this doesn't look simple. The problem is that we currently only track loop vars on the loop/finally stack, on the premise that these are the only ones that can occur on a statement level. In this case we would need to know all the currently open live ranges, which are not so easy to compute (this would require part of the logic from zend_calc_live_ranges). We protect against the removal of loop var frees using ZEND_BB_UNREACHABLE_FREE, which is also not easy to extend to this case either. |
That's interesting, I wasn't aware I checked your example. It does indeed leak memory. $ary = [];
($ary + [1]) + throw new Exception();
|
I'll close this PR as this isn't the right approach. @nikic Do you have some solution in mind or should I investigate further? |
If this can't be fixed cleanly, would it be possible to use
Then maybe it'd be possible for the optimizer would act as though ZEND_EXIT_EXPR/ZEND_THROW_EXPR wasn't the end of a basic block, and avoid these memory leaks (I guess there'd still need to be the following ZEND_JMP for conditionals just so that control flow and ranges could be analyzed) |
Then again, my idea has the drawback that opcache wouldn't infer the block containing // $value is non-nullable.
$value = $nullableValue ?? throw new InvalidArgumentException(); |
If I remember correctly, some phases of the optimizer were turned off when a function body contains catch statements. Another possible workaround would be to disable the optimizer (or just the problematic phases of the optimizer) if there is a
|
@nikic @dstogov
DISCLAIMER: This is a "fix" for the memory leak in #5279. I don't expect this PR to be usable but maybe it can demonstrate the problem so you can point me to the right solution. But it does indeed solve the problem.
generates these opcodes:
which are then optimized to:
The problem here is that the instruction 0000 isn't removed.
zend_default_exception_new
assumes thatDO_FCALL
will be called at some point to release memory it has allocated which is never does. Here is a different example:Unoptimized:
Optimized:
The instructions 0000 and 0004 should be removed, This doesn't cause any problems but they are useless. This PR marks unterminated
INIT_CALL
s andSEND_VAL
s of previous building blocks as NOPs.