As it drops the warning. This is more problematic with constant
propagation, as tests would fail.
Extract a zend_optimizer_classify_function() function, as its now
needed by zend_cfg and update_opN.
Do not mark loop var free blocks as reachable after all -- as we
can't construct SSA for unreachable blocks, this would cause
issues down the line.
Instead add an extra UNREACHABLE_FREE flag and retain only the
FREE instruction during NOP removal. (If we retain all
instructions in the BB we might leave a jump instruction that goes
into the nowhere.)
We need to be careful about correctly handling that undef results
in a null value. Otherwise, apart from simply generating incorrect
results, we may also end up performing non-monotonic lattice
transitions, thus causing an infinite type inference loop (see
test).
Extract a helper function for handling ASSIGN_DIM and ASSIGN_*
with ASSIGN_DIM ev uniformly. Previously the code was copy pasted
for each ASSIGN_* op, but the variable names were not correctly
adjusted (tmp vs orig), so the type inference result was wrong.
Also sligthly improve handling:
* Result cannot be false, will always be converted to array.
* If the dim is a CONST string operand, we don't have to assume
numeric strings.
* If the inserted value may be undef, the array may contain null.
This slightly improves calls to regular function and method calls in cost of a bit slower generator initialization.
Separate call frame for generators, allocated on heap, now created by ZEND_GENERATOR_CREATE instruction.
If the pi target has multiple predecessors, we need to place a
phi there. However it's not possible to express this in terms of
dominance frontiers, so we need to explicitly add it to the phi
set.
This does not yet solve the problem of non-minimal SSA for the
case where the target has multiple predecessors, but dominates
all predecessors (apart from the one creating the pi) -- but
that's an existing issue.
Previously the phi set was first computed during def propagation
and then computed again (per-block) during actual phi placement.
This commit changes this to store the phi set computed during
def propagation.
This makes SSA construction slightly faster (5%), but the main
purpose here is to pave the way for the next commit.
This also fixes a potential issue with the handling of irreducible
loops -- they generated additional phis, but these were not
accounted for in def propagation. (Though I'm not sure if we can
even have any irreducible loops right now.)
The result_type was not copied, resulting in a corrupted JMPZ_EX.
Fix can be verified by inspecting the opcodes of the following
function (it should not contain any _EX opcodes):
function test() {
if ($a && $b) {
echo "a";
}
if ($b || $c || $d) {
echo "b";
}
}
Conflicts:
ext/opcache/Optimizer/block_pass.c
Previously pi placement happened after initial phi placement.
Afterwards a second phi placement pass was performed, however it
incorrectly only placed phis on the dominance frontier, rather
than the iterated dominance frontier.
This is fixed by moving pi placement before the propagation of
defs on the iterated DFs, and adding a def for each added pi.
While this ensures that we generate correct conservative SSA, there
is still one remaining case in which we may generate non-minimal
SSA form. Consider:
|1|
|pi
v
|2|<--\
| |
\----/
The pi is semanically located along the edge 1->2, however we place
it (and its def point) in 2, thus leading to the generation of an
additional (trivial) phi in 2.
Conflicts:
ext/opcache/Optimizer/zend_ssa.c
This interdependence is problematic because we can't propagate pi
def points in the initial dominance frontier propagation. The used
rule for multiple-predecessor blocks may also miss cases where
placing the pi would have been useful.
The new heuristic for pi placement checks that a) the variable is
live-in and b) for the "from" block that generated the pi, the
other successor does not dominate all other predecessors of the
"to" block.
The purpose of case b) may be illustrated with an example:
if (is_int($i)) {
// place pi here
}
// but don't place pi here
The reason we do not want to place the second pi is that we generally
place pis in positive+negative pairs, and in this case the pair
would merge in a phi and cancel out, so we get no useful information
out of it.
For live-variable analysis it does not matter if def includes
variables that are previously use in the same block, the data flow
equations still have the same result. As such there is no need to
compute separate gen & def sets.
I'm keeping the name "def", because use of "gen" in this context is
pretty confusing (gen is usually the use set, not the def set).
Instead decide whether a function returned by reference or by value
by checking whether the return value has REFERENCE type. This means
that functions returning by reference must always return a reference
and functions returning by value must not return a reference.