Copy On Write and Assign By Reference perform different on PHP5 and PHP7

php.internals

Huqiu Liao

10 years ago
Hi, internals, I have a question about Assign By Reference and I posted on StackOverflow, I'd like to know the reason behind it, and I did not get any this kind of answer, can anyone give me some clues. --- We have a piece of simple code: 1 <?php 2 $i = 2; 3 $j = &$i; 4 echo (++$i) + (++$i); On PHP5, it outputs 8, because: $i is a reference, when we increase $i by ++i, it will change the zval rather than make a copy, so line 4 will be 4 + 4 = 8. This is Assign By Reference. If we comment line 3, it will output 7, every time we change the value by increasing it, PHP will make a copy, line 4 will be 3 + 4 = 7. This is Copy On Write. But in PHP7, it always outputs 7. I've checked the changes in PHP7: http://php.net/manual/en/migration70.incompatible.php, but I did not get any clue. Here is the result of the code on PHP5 / PHP7: https://3v4l.org/USTHR And there is the link on Stackoverflow: http://stackoverflow.com/questions/36338661/php-copy-on-write-and-assign-by-reference-perform-different-on-php5-and-php7 Thanks guys. Best,
-- Huqiu Liao http://www.liaohuqiu.net

Rowan Collins

10 years ago
Huqiu Liao wrote on 31/03/2016 17:47:
> Hi, internals, > > I have a question about Assign By Reference and I posted on StackOverflow, > I'd like to know the reason behind it, and I did not get any this kind of > answer, can anyone give me some clues. > > --- > > We have a piece of simple code: > > 1 <?php > 2 $i = 2; > 3 $j = &$i; > 4 echo (++$i) + (++$i); > On PHP5, it outputs 8, because: > > $i is a reference, when we increase $i by ++i, it will change the zval > rather than make a copy, so line 4 will be 4 + 4 = 8. This is Assign By > Reference.
To me, that sounds like a fixed bug. It's noteworthy in the 3v4l results that HHVM also gives 7 as the answer, and I can't see any logical reason for the answer to be 8 - regardless of execution order, the result of the two calls to ++$i should never be the same. It would be interesting to see the VLD dump in PHP 5, with and without reference, because looking at the PHP 7 one, it's clear that the two increments result in two temp vars so it shouldn't make any difference what type of zval was given as input: 4 2 PRE_INC $2 !0 # !0 is compiled $i, $2 is a new value returned 3 PRE_INC $3 !0 # !0 is referenced again, but $3 is unrelated to $2 4 ADD ~4 $2, $3 # the two values are added, with no reference to !0 Maybe in PHP 5 the opcodes are the same, but $2 and $3 somehow end up as references to !0, rather than new zvals, as though the code were: $i = 2; $i++; $temp1 =& $i; $i++; $temp2 =& $i; echo $temp1 + $temp2; rather than, as seems logical to me, HHVM, and PHP7: $i = 2; $i++; $temp1 = $i; $i++; $temp2 = $i; echo $temp1 + $temp2; Regards,
-- Rowan Collins [IMSoP]

Rowan Collins

10 years ago
On 31/03/2016 18:23, Rowan Collins wrote:
> Maybe in PHP 5 the opcodes are the same, but $2 and $3 somehow end up > as references to !0, rather than new zvals
So, it turns out, this is exactly what happens. Specifically, there is a call to SEPARATE_ZVAL_IF_NOT_REF(var_ptr); in the definition of the ZEND_PRE_INC opcode [http://lxr.php.net/xref/PHP_5_6/Zend/zend_vm_def.h#814] If the zval *is* a reference, the code just ends up incrementing its reference count, returning a reference to it rather than the calculated value. In PHP 7, references are very different, and integers aren't refcounted, so it's all a different story. I got a bit curious and did some deep investigation, which I've written up on StackOverflow: http://stackoverflow.com/a/36344524/157957 I've about reached my limit now, though, so I'll leave it to others to correct or fill in any further info. :) Regards,
-- Rowan Collins [IMSoP]

Yasuo Ohgaki

10 years ago
Hi Huqui, On Fri, Apr 1, 2016 at 1:47 AM, Huqiu Liao <liaohuqiu@gmail.com> wrote:
> 4 echo (++$i) + (++$i);
I brought up this topic before and the conclusion is This kind of operation result is undefined and user shouldn't write such expression. There are undefined behaviors for ++ and --. C/C++ has undefined behavior for ++/-- https://en.wikipedia.org/wiki/Undefined_behavior#Examples_in_C_and_C.2B.2B Some languages do not have these operators for this reason. PHP may define behavior for these, but it is undefined for future improvements. (And behavior is changed in PHP 7.0) Regards,
-- Yasuo Ohgaki yohgaki@ohgaki.net

Sara Golemon

10 years ago
On Thu, Mar 31, 2016 at 9:47 AM, Huqiu Liao <liaohuqiu@gmail.com> wrote:
> I have a question about Assign By Reference and I posted on StackOverflow, > I'd like to know the reason behind it, and I did not get any this kind of > answer, can anyone give me some clues. >
Are you asking out of curiosity? Or because you think a new bug has been introduced in PHP7? If the latter, I'd respond by pointing out that the behavior of using multiple operations with side-effects (like pre/post inc/dec) is defined as undefined. That is, while the PHP reference implementation may give you a particular result, you should not rely on that result. If the former, and you're just curious why, it comes down to a very fundamental change in the way PHP7 deals with references. I'd recommend reading Nikita's writeup of the differences between PHP 5 and PHP 7 variables at: https://nikic.github.io/2015/05/05/Internal-value-representation-in-PHP-7-part-1.html -Sara

Rowan Collins

10 years ago
Sara Golemon wrote on 01/04/2016 02:52:
> On Thu, Mar 31, 2016 at 9:47 AM, Huqiu Liao <liaohuqiu@gmail.com> wrote: >> I have a question about Assign By Reference and I posted on StackOverflow, >> I'd like to know the reason behind it, and I did not get any this kind of >> answer, can anyone give me some clues. >> > Are you asking out of curiosity? Or because you think a new bug has > been introduced in PHP7? > > If the latter, I'd respond by pointing out that the behavior of using > multiple operations with side-effects (like pre/post inc/dec) is > defined as undefined. That is, while the PHP reference implementation > may give you a particular result, you should not rely on that result. >
I just dug into the spec out of curiosity, and I have a nit-pick: there is relevant language under "Section 10: Expressions" [1]:
> Unless stated explicitly in this specification, the order in which
the operands in an expression are evaluated relative to each other is unspecified. [...] (For example,[...] in the full expression $j = $i + $i++, whether the value of $i is the old or new $i, is unspecified.) But arguably this doesn't go far enough to justify the PHP 5 engine's behaviour in this case, because it only mentions the *order* of evaluation being unspecified, not the actual semantics. The definition of the pre-increment operator [2] states:
> The result is the value of the operand after it has been incremented.
That is, the operator does not return the operand itself, but its value at a particular time. However, there is no order of execution which allows the value of the variable to be 4 on both sides of the addition; to get this requires the actual behaviour of the ++ operator to be re-defined as "return a reference to the incremented operand". (You could make an argument for the two increments happening "simultaneously" and giving a result of 6, but to get 8, they have to both happen simultaneously *and* see each other's results.) I'm not seriously suggesting that the PHP 5 behaviour is problematic, but unless there are additional caveats somewhere else, it does seem to violate the letter of the current spec. [1] https://github.com/php/php-langspec/blob/master/spec/10-expressions.md [2] https://github.com/php/php-langspec/blob/master/spec/10-expressions.md#prefix-increment-and-decrement-operators Regards,
-- Rowan Collins [IMSoP]