Rework the Zend compat layer to be more robust
commit4e5773ad816ce4fc319b4d5be10fb95e8a0cab50
authorDrew Paroski <andrewparoski@fb.com>
Sat, 17 Aug 2013 04:18:03 +0000 (16 21:18 -0700)
committerSara Golemon <sgolemon@fb.com>
Wed, 18 Sep 2013 18:07:19 +0000 (18 11:07 -0700)
treeaba0ae9a818bcca5c1152ce2eb5f2f9fa9c820a1
parent1e8f096231a25d081824e02280588a42e786e7dd
Rework the Zend compat layer to be more robust

This diff reworks the PHP extension compat layer to be more robust by
taking steps to reconcile the difference between mainstream PHP's way and
HHVM's way of representing program values and operating on program values.
This includes the shape of the object graph in the heap (what points to
what), how refcounting is done and how "reffiness" is handled, how copy-
on-write semantics are honored, and how values are stored into arrays.

One of the core ideas here is that HHVM supports "boxing" a program value
via the RefData type and that there's a reasonably clean mapping between
PHP's object graph and HHVM's object graph (when HHVM program values are
"boxed") that maps PHP "zval" objects onto HHVM RefData objects. For the
purposes of a compat layer for PHP extensions, it makes sense to use
RefData to represent "zvals". Of course, it is unreasonable to box every
program variable in HHVM since this would hurt performance. However, we
can make things work by boxing program values lazily as needed in the
right places to ensure that Zend PHP extensions only deal with boxed
values. Along the way I also fixed a bug where we were leaking all of
the arguments that were passed to Zend PHP extension functions.

The next issue involves "reffiness". At present, HHVM RefDatas are treated
as "reffy" when their refcount is 2 or greater, while Zend PHP zvals have a
separate flag (independent of the refcount) that indicates whether they
should be treated as "reffy". Zend PHP extensions are capable of setting
two or more program variable to point to the same zval where the zval's
"reffy" flag is set to false. In such cases, the program variables share
the zval using copy-on-write semantics, where the zval must be copied
before making a modification. To make this work, we need to change RefData
somehow so that we can keep track of whether copy-on-write is needed when
the refcount is 2 or greater. Adding "reffy" flag directly poses some
challenges in terms of performance, because it means that when a value's
refcount decreases from 2 to 1 we'll need to make sure the "reffy" flag
gets cleared. I was able to avoid this by adding two flags (m_cow and m_z)
and creating a scheme that maps the 3-tuple (m_count, m_cow, m_z) to the
2-tuple (realRefcount, reffy). Under this scheme decRef continues to work
as it does today, decrementing the m_count field and calling a helper
m_count reaches zero. Another neat thing about this scheme is that m_cow is
set to 1 iff copy-on-write would be required before modifying m_tv. (NOTE:
This diff does not implement copy-on-write yet for these cases, it will be
addressed in a later diff.)

Another issue is the relationship between strings/arrays and the
zvals/RefDatas that point to them. Under Zend PHP, it is assumed that a
zval exclusively owns the string or array and that nothing else points to
the string/array. A consequence of this is that Zend PHP extensions do not
perform any copy-on-write checks with the string or array's refcount before
modifying the string/array. HHVM on the other hand supports sharing strings
and arrays between multiple RefDatas and/or program variables. Thus, we
need some way to protect against Zend PHP extensions modifying a string or
array when its refcount is 2 or greater. This is achieved by putting checks
in the right places to lazily make a copy of the string or array (when
refcount >= 2) so that Zend PHP extensions only deal with strings and
arrays with a refcount of 1.

Finally, the Zend PHP APIs for adding values to an array are a little
different. Zend PHP extensions pass a zval to the API, and the appropriate
array slot is set to point at the zval. The API does not increment the
zval's refcount; it is the caller's responsibility to do this is needed.
New APIs were added to HphpArray to address this need.

With this in place, the next things to go after would be (1) Fixing up how
the Zend compat layer deals emulating Zend PHP string macros and
operations, this may require adding a new KindOfZStr type if StringData
can't be adapted without hurting HHVM performance; (2) Adding checks in the
right places to make a copy of a RefData if m_cow is 1, I've found most the
places where this is needed and it doesn't look like these checks will
noticably hurt performance; (3) improving support for resource types and
object types defined by Zend PHP extensions and interoperability with HHVM
extension classes, HHVM resources, and user-defined pure-PHP classes.

Reviewed By: @ptarjan

Differential Revision: D966781
37 files changed:
hphp/doc/php.extension.compat.layer [new file with mode: 0644]
hphp/runtime/base/array-data-defs.h
hphp/runtime/base/array-data.cpp
hphp/runtime/base/array-data.h
hphp/runtime/base/array-iterator.cpp
hphp/runtime/base/countable.h
hphp/runtime/base/hphp-array.cpp
hphp/runtime/base/hphp-array.h
hphp/runtime/base/ref-data.cpp
hphp/runtime/base/ref-data.h
hphp/runtime/base/string-data.h
hphp/runtime/base/tv-helpers.cpp
hphp/runtime/base/tv-helpers.h
hphp/runtime/base/type-variant.cpp
hphp/runtime/base/type-variant.h
hphp/runtime/ext_hhvm/ext_zend_compat.cpp [copied from hphp/runtime/base/ref-data.cpp with 65% similarity]
hphp/runtime/ext_hhvm/ext_zend_compat.h [new file with mode: 0644]
hphp/runtime/ext_zend_compat/php-src/Zend/zend.cpp
hphp/runtime/ext_zend_compat/php-src/Zend/zend.h
hphp/runtime/ext_zend_compat/php-src/Zend/zend_API.cpp
hphp/runtime/ext_zend_compat/php-src/Zend/zend_API.h
hphp/runtime/ext_zend_compat/php-src/Zend/zend_alloc.h
hphp/runtime/ext_zend_compat/php-src/Zend/zend_hash.cpp
hphp/runtime/ext_zend_compat/php-src/Zend/zend_hash.h
hphp/runtime/ext_zend_compat/php-src/Zend/zend_interfaces.cpp
hphp/runtime/ext_zend_compat/php-src/Zend/zend_list.cpp
hphp/runtime/ext_zend_compat/php-src/Zend/zend_list.h
hphp/runtime/ext_zend_compat/php-src/Zend/zend_operators.cpp
hphp/runtime/ext_zend_compat/php-src/Zend/zend_operators.h
hphp/runtime/ext_zend_compat/php-src/Zend/zend_types.h
hphp/runtime/ext_zend_compat/php-src/Zend/zend_variables.h
hphp/runtime/ext_zend_compat/php-src/ext/standard/php_var.h
hphp/runtime/ext_zend_compat/php-src/main/snprintf.cpp
hphp/runtime/vm/bytecode.cpp
hphp/runtime/vm/jit/translator-x64.cpp
hphp/runtime/vm/jit/unique-stubs-x64.cpp
hphp/tools/bootstrap/gen-infotabs.cpp