On Thu, Jan 25, 2024, at 11:28 AM, Ilija Tovilo wrote:
> This leads to very similar issues as break/continue inside blocks. See: > https://wiki.php.net/rfc/match_blocks#technical_implications_of_control_statements > > I'll try to explain. > > The VM works with temporary variables. For the expression foo() + > bar() two temporary variables for the result of foo() and bar() will > be created, which are then used for the + operation. Normally, + will > consume both operands, i.e. use and then free them. However, with > break/continue etc. being expressions, it would become possible to > skip over the consuming instructions. > > do { > echo foo() + break; > } while (true); > echo 'Done'; > > Pseudo opcodes: > > 0000 V1 = CALL foo > 0001 JMP 0005 > 0002 V2 = ADD V1 false ; false is here represents a bottom value > that will never actually be used > 0003 ECHO V2 > 0004 JMP 0000 > 0005 ECHO 'Done' > > Since JMP will skip over the ADD instruction, V1 remains unused. A > similar problem already exists for break/continue in foreach itself. > > foreach ($foos as $foo) { > foreach ($bars as $bar) { > break 2; > } > } > > foreach holds a copy of $bars (in case it gets modified) that normally > gets cleaned up when the loop ends. With break over multiple > loop-boundaries, we can completely skip over this freeing mechanism. > PHP solves this by inserting an explicit FE_FREE instruction before > the break 2, which itself is essentially just a JMP to the end of the > outer loop. > > Hopefully it's now more evident why this is a problem: > > while (true) { > foo() && break; > } > > foo() returns a value that would normally be consumed by the && > operation. However, with break, we may skip over the && operation > entirely. As such, the break itself becomes responsible for freeing > these values. This requires significant changes in the compiler to > track variables that are currently "live" (i.e. haven't been consumed > yet), and emitting FREE opcodes for them as needed. I've implemented > this for match blocks here: > > https://github.com/php/php-src/compare/master...iluuu1994:php-src:match-blocks-var-tracking > > However, note that due to complexity, I've decided to disallow using > break/continue and the likes in such contexts to avoid this issue > completely, which isn't possible for what you are suggesting. > > There's another related issue. > > foo(bar(), break); > > Function calls in PHP consist of multiple instructions, namely an > INIT_CALL, 0-n SEND and a DO_CALL opcode. INIT_CALL creates a stack > frame, SEND pushes arguments onto the stack frame, and DO_CALL starts > the execution of the function and frees both arguments and stack frame > when the function ends. If prior to a SEND opcode we break, we skip > over the DO_CALL, so the stack frame needs to be freed manually. > > The patch linked above solves this by inserting CLEAN_UNFINISHED_CALLS > opcodes that do as the name suggests. This mechanism is already used > for exceptions. This should work for you, but was insufficient for > match blocks, for reasons I won't get into here. > > All this to say: Don't expect the implementation here to be trivial. > > Regards, > Ilija I'm curious, how did `throw` expressions manage to avoid these issues? Or was it just "Ilija did the hard work of tracking down the weirdness?" --Larry Garfield -- PHP Internals - PHP Runtime Development Mailing List To unsubscribe, visit: https://www.php.net/unsub.php
