=========================
original problem
the biggest problem caused by early class binding is that it's
unfriendly to opcode cachers.
parent1.php
class P
{
function __construct()
{
echo "parent2";
}
}
include "child.php"
parent2.php
class P
{
function __construct()
{
echo "parent2";
}
}
include "child.php"
child.php
class child extends P
{
}
new child();
without opcode cacher, when parent1.php is requested, "parent1" is
echoed, while parent2.php is requested "parent2" will be echoed.
but with opcode cachers, it always use the echo from first
requested/cached class P because when class child is being cached,
it's after "early class binding" and is binded with its parent class
P, the binding will not be done again after the child class is
restored from cache
=========================
other problems
read the sample coe first:
class MyException extends Exception
{
}
early class binding is done at compile time and opcode cachers will
only get binded not raw classes
the problem is that, the binded class MyException is has so many
handler pointers copied (inherited) from internal class Exception.
it's hard for a opcode cacher to cache such opcode data to disk, or
share with another instance of php which is not forked from the same
parent php process due to possible symbol relocation.
=========================
compatibility issue without early class binding
iirc the early class binding was removed but added back later due to
back compatibility issue:
if the class is early binded, code before class declaration can use class
new Foo(); // works
class Foo // early binding at compile time
{
}
but when there's no early binding
new Foo(); // error, undefined class
class Foo
{
}
// late class binding is done here at runtime
=========================
solution proposal
solution a: forget the back compatibility issue, early class binding
was deprecated and it's now gone.
solution b: add an api that allow extension/zend_extension disable
early class binding let opcode cachers decide to do nothing (which
mean the user should accept that, the early binding is gone), or to
redo the binding after restore from cache but before execution. when
no extension use this api, no behavior change.
pitfall for solution b: opcode cachers have to write their own scanner
to figure out which classes should be early binded and which opline
should be replaced with NOP
solution c: like solution b, but ZendEngine do most of the work, for
example: remember all the position/pointer to opline that might be
replaced with NOP, return from zend_compile_file or
zend_compile_string so opcode cachers will catch it. do early binding
after zend_compile_file/string, which also mean after opcode cacher
stuff
Tips for those don't know zend_compile_file/string:
zend_compile_file/string is a pointer that can be overloaded by
extension/zend_extension so one can hook into the compile time
=========================
PS
XCache tricked to disable early class binding by emptying
CG(class_table) before and restore it after compile so the compiler
won't see any class that isn't in the same file.
the reason i bring up this topic again before PHP_5_3 is out is
becuase 5.3 namespace patch breaks the XCache trick and i find it no
way to trick without changing php source:
5.3 namespace code will check for CG(class_table) and see if a class
is internal class to generate different code.
namespace ns;
e.g.:
class b extends Exception // will check ::Exception at runtime
{
}
class a extends c // always ns::c
{
}
with XCache enabled:
class b extends Exception // always ns::Exception because there's no
such internal class at compile time due to XCache trick.
sorry for the long post so i mark the sections with ===s that will
help your navigation
5.3 namespace code will check for CG(class_table) and see if a class
is internal class to generate different code.
Compiler shouldn't generate different code. Unqualified name is resolved
at runtime, but code should be the same whatever resolution was made.
Did you check that different code is indeed generated?
--
Stanislav Malyshev, Zend Software Architect
stas@zend.com http://www.zend.com/
(408)253-8829 MSN: stas@zend.com
Compiler shouldn't generate different code. Unqualified name is resolved
at runtime, but code should be the same whatever resolution was made.
Did you check that different code is indeed generated?
zend_compile.c
===============
if (zend_hash_find(CG(class_table), lcname,
Z_STRLEN(class_name->u.constant)+1, (void**)&pce) == SUCCESS &&
(pce)->type == ZEND_INTERNAL_CLASS) {
/ There is an internal class with the same name exists.
PHP will need to perform additional cheks at run-time to
determine if we assume class in current namespace or
internal one. */
*fetch_type |= ZEND_FETCH_CLASS_RT_NS_CHECK;
}
it's an optimization and assume that internal class never change
let me guess, if you have php_abc.so which have "internal" (well, 3rd
party) "class abc" not loaded when you encode php files with
ZendEncoder
namespace mynamespace;
var_dump(new abc());
but you do load file for production server, you still get "undefined
class mynamespace::abc" at runtime, internal class abc is never
checked.
more pitfall: apc and XCache uses fast copy of opcodes for restoring
opcode, but this will never work if early binding which have to modify
opcodes is done after restore
see apc_copy_op_array_for_execution() in apc_compile.c
there is my_copy_data_exceptions(dst, src); which do a check if to deep copy.
and btw, i just find fe->common.arg_info needed to be check in
apc_copy_op_array() for my_copy_data_exceptions() because:
in zend_compile.c
zend_do_perform_implementation_check
fe->common.arg_info[i].class_name = ......;
any updates op_array at runtime looks evil for opcode cacher fast
copy, can't we just assume opcode readonly like all elf .exe etc?
any news on this topic?