Skip to content

fix(codegen): keep Zend operand read order around hoisted side effects - #52

Open
AlessioGiacobbe wants to merge 2 commits into
swoole:masterfrom
AlessioGiacobbe:split/eval-order-side-effects
Open

fix(codegen): keep Zend operand read order around hoisted side effects#52
AlessioGiacobbe wants to merge 2 commits into
swoole:masterfrom
AlessioGiacobbe:split/eval-order-side-effects

Conversation

@AlessioGiacobbe

Copy link
Copy Markdown
Contributor

When a later call argument or concat operand hoisted a side effect, it executed before earlier operands were read: $j = 1; two($j, $j = 5) printed "5,5" where Zend SENDs left-to-right and prints "1,5"; $m . "," . ($m = 9) printed "9,9" instead of "1,9".

Earlier plain-variable operands are now snapshotted at their exact Zend read positions when a later operand materializes statements. Concat read positions were probed against Zend empirically (the VM reads the first two chain items together after both items' side effects), and plain arithmetic ($k + ($k = 5)) is deliberately unchanged — Zend itself reads the CV at the op, so the existing codegen already matches; a no-regression test pins that.

Verified against Zend 8.4.13; codegen tests + phpt included.

Part of the split of #39.

Lowering a later call argument or concat operand that materializes
captured statements (an assignment, a call result) appended them to the
enclosing statement, executing the side effect before earlier operands
were read: two($j, $j = 5) with $j = 1 produced "5,5" (Zend "1,5") and
$m . "," . ($m = 9) produced "9,9" (Zend "1,9").

Call arguments: Zend SENDs strictly left to right, so when a later
argument hoists statements, every earlier by-value plain-variable
argument is snapshotted into a temporary at its own argument position.
By-reference parameters, unpacked arguments, $this and $GLOBALS are
left alone.

Concat chains: Zend reads a CV operand when its CONCAT opcode executes,
so in the left-associated chain the first two items are read together
at the first op (after both items' side effects: $s . ($s = 'b') . $s
is "bbb") and each later item after the side effects of everything up
to itself. The flattened braced-list lowering now snapshots a
plain-variable item exactly at that read position, deferring the first
item's snapshot until the second item has been lowered.

Plain arithmetic is intentionally unchanged: Zend's ADD reads the CV at
op time, so $k + ($k = 5) is 10 in both worlds, and the existing
codegen already matches.

@matyhtf matyhtf left a comment

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.

The current operand classifier misses side effects wrapped in non-binary expressions, so the argument-order bug is still observable.

For example:

function pairValue(mixed $a, mixed $b): string
{
    return $a . ',' . $b;
}

$i = 1;
var_dump(pairValue($i, (int) ($i = 5)));

$k = 1;
var_dump(pairValue($k, !($k = 0)));

Zend PHP outputs:

string(3) "1,5"
string(3) "1,1"

This branch outputs:

string(3) "5,5"
string(3) "0,1"

shouldMaterializeOrderedOperand() only descends through Expr\BinaryOp. An assignment wrapped by Expr\Cast or BooleanNot is therefore classified as non-hoisting, even though lowering it appends captured statements. The earlier by-value argument is not snapshotted and the nested assignment overtakes its read.

Please make the side-effect/hoisting detection structurally recursive through expression wrappers such as casts, unary expressions, boolean-not, bitwise-not, and error suppression, or derive the decision from the captured statements produced by lowering. A generic AST walk must stop at nested Closure/ArrowFunction bodies because their bodies are not evaluated at creation time.

Add PHPT coverage for at least the cast and boolean-not examples above. The existing tests pass but do not cover wrapped side effects.

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