共计 5609 个字符,预计需要花费 15 分钟才能阅读完成。
在浏览 PHP
源码的时候,在泛滥的 *.stub.php
中,发现了这样的正文,@refcount 1。
通过翻看 build/gen_stub.php
源码,发现了在解析 *.stub.php
文件时,对于返回信息的代码。
<?php | |
class ReturnInfo { | |
const REFCOUNT_0 = "0"; | |
const REFCOUNT_1 = "1"; | |
const REFCOUNT_N = "N"; | |
const REFCOUNTS = [ | |
self::REFCOUNT_0, | |
self::REFCOUNT_1, | |
self::REFCOUNT_N, | |
]; | |
//... | |
private function setRefcount(?string $refcount): void | |
{ | |
$type = $this->phpDocType ?? $this->type; | |
$isScalarType = $type !== null && $type->isScalar(); | |
if ($refcount === null) { | |
$this->refcount = $isScalarType ? self::REFCOUNT_0 : self::REFCOUNT_N; | |
return; | |
} | |
if (!in_array($refcount, ReturnInfo::REFCOUNTS, true)) {throw new Exception("@refcount must have one of the following values: \"0\", \"1\", \"N\", $refcount given"); | |
} | |
if ($isScalarType && $refcount !== self::REFCOUNT_0) {throw new Exception('A scalar return type of"' . $type->__toString() . '"must have a refcount of"' . self::REFCOUNT_0 . '"'); | |
} | |
if (!$isScalarType && $refcount === self::REFCOUNT_0) {throw new Exception('A non-scalar return type of"' . $type->__toString() . '"cannot have a refcount of"' . self::REFCOUNT_0 . '"'); | |
} | |
$this->refcount = $refcount; | |
} |
显著,如果返回值类型是 scalar
,也就是标量(根本数据类型,整型、浮点型、字符串等),那么refcount
指定为 0,否则为 N。如果设置了正文,那么以正文为最高优先级。
以函数 ob_list_handlers
为例:
/** | |
* @return array<int, string> | |
* @refcount 1 | |
*/ | |
function ob_list_handlers(): array {} |
返回值是 array
,所以默认的refcount
应该是 N,但因为设置了正文@refcount 1
,所以返回值的援用计数被替换成 1。
这些逻辑我能看懂,但设置返回值援用计数的目标是什么?我还是一头雾水
我接着往下排查,发现通过返回值的援用计数,在生成 func_info
的时候,会有些不同。如果返回值援用计数为 1 或 N,则会用对应的宏去初始化 func_info
构造体。如果是 0,则不进入初始化列表。
以上的代码逻辑仍然能够在 gen_stub.php
中找到,1393 行,getOptimizerInfo
。
public function getOptimizerInfo(): ?string {if ($this->isMethod()) {return null;} | |
if ($this->alias !== null) {return null;} | |
if ($this->return->refcount !== ReturnInfo::REFCOUNT_1 && $this->return->phpDocType === null) {return null;} | |
$type = $this->return->phpDocType ?? $this->return->type; | |
if ($type === null) {return null;} | |
return "\tF" . $this->return->refcount . '("' . $this->name->__toString() . '",' . $type->toOptimizerTypeMask() . "),\n"; | |
} |
获取函数原型的 refcount
,生成诸如F1()
或FN()
的代码,生成的头文件地位在Zend/Optimizer/zend_func_infos.h
。
static const func_info_t func_infos[] = {F1("zend_version", MAY_BE_STRING), | |
FN("func_get_args", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_OF_ANY), | |
F1("get_class_vars", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF), | |
F1("get_class_methods", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_OF_STRING), | |
F1("get_included_files", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_OF_STRING), | |
FN("set_error_handler", MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_OF_STRING|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_OBJECT|MAY_BE_NULL), | |
FN("set_exception_handler", MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_OF_STRING|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_OBJECT|MAY_BE_NULL), | |
F1("get_declared_classes", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_OF_STRING), | |
F1("get_declared_traits", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_OF_STRING), | |
F1("get_declared_interfaces", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_OF_STRING), | |
F1("get_defined_functions", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_ARRAY), | |
F1("get_defined_vars", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF), | |
F1("get_resource_type", MAY_BE_STRING), | |
F1("get_loaded_extensions", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_OF_STRING), | |
F1("get_defined_constants", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_ANY), | |
F1("debug_backtrace", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_OF_ARRAY), | |
F1("get_extension_funcs", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_OF_STRING|MAY_BE_FALSE), | |
F1("gc_status", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_LONG|MAY_BE_ARRAY_OF_FALSE|MAY_BE_ARRAY_OF_TRUE), | |
F1("bcadd", MAY_BE_STRING), | |
F1("bcsub", MAY_BE_STRING), | |
F1("bcmul", MAY_BE_STRING), | |
F1("bcdiv", MAY_BE_STRING), | |
F1("bcmod", MAY_BE_STRING), | |
F1("bcpowmod", MAY_BE_STRING), | |
F1("bcpow", MAY_BE_STRING), | |
F1("bcsqrt", MAY_BE_STRING), | |
FN("bzopen", MAY_BE_RESOURCE|MAY_BE_FALSE), | |
F1("bzerror", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_LONG|MAY_BE_ARRAY_OF_STRING), | |
F1("cal_from_jd", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_LONG|MAY_BE_ARRAY_OF_STRING|MAY_BE_ARRAY_OF_NULL), | |
F1("cal_info", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_LONG|MAY_BE_ARRAY_OF_STRING|MAY_BE_ARRAY_OF_ARRAY), | |
F1("curl_copy_handle", MAY_BE_OBJECT|MAY_BE_FALSE), | |
//... | |
}; |
再去看看 F1
和FN
的宏定义。
typedef struct _func_info_t { | |
const char *name; | |
unsigned name_len; | |
uint32_t info; | |
info_func_t info_func; | |
} func_info_t; | |
#define F0(name, info) \ | |
{name, sizeof(name)-1, (info), NULL} | |
#define F1(name, info) \ | |
{name, sizeof(name)-1, (MAY_BE_RC1 | (info)), NULL} | |
#define FN(name, info) \ | |
{name, sizeof(name)-1, (MAY_BE_RC1 | MAY_BE_RCN | (info)), NULL} | |
#define FC(name, callback) \ | |
{name, sizeof(name)-1, 0, callback} |
仅仅是设置了不同的 type mask
,F1
设置了 MAY_BE_RC1
,FN
设置了MAY_BE_RCN | MAY_BE_RC1
。
仍然一头雾水,然而通过目录名,我依稀能猜出这跟性能优化无关,跟 JIT 有关系。我决定持续追究上来,看看这些初始化后的构造体在哪里应用过。
咱们很分明,设置位信息用 |
,那判断有没有设置必定用&
,全局搜寻& MAY_BE_RCN
,再看看哪些代码跟优化无关,定位到了如下代码,在zend_jit.c
的 530 行:
#ifdef ZEND_JIT_USE_RC_INFERENCE | |
/* Refcount may be increased by RETURN opcode */ | |
if ((info & MAY_BE_RC1) && !(info & MAY_BE_RCN)) {for (j = 0; j < ssa->cfg.blocks_count; j++) {if ((ssa->cfg.blocks[j].flags & ZEND_BB_REACHABLE) && | |
ssa->cfg.blocks[j].len > 0) {const zend_op *opline = op_array->opcodes + ssa->cfg.blocks[j].start + ssa->cfg.blocks[j].len - 1; | |
if (opline->opcode == ZEND_RETURN) {if (opline->op1_type == IS_CV && opline->op1.var == EX_NUM_TO_VAR(var)) { | |
info |= MAY_BE_RCN; | |
break; | |
} | |
} | |
} | |
} | |
} | |
#endif |
如果返回值的援用计数是 1,而不是 N 的时候,并且开启了返回值援用计数推导性能,就走这段代码。这段代码又波及到所谓SSA
,动态单赋值的编译器设计形式。
在编译器设计中,动态繁多赋值模式(通常缩写为 SSA 模式或简称 SSA)是两头示意(IR)的属性,它要求每个变量只调配一次,并且每个变量在应用之前定义。原始 IR 中的现有变量被拆分为版本,在教科书中,新变量通常由原始名称用下标示意,以便每次定义都有本人的版本。在 SSA 模式中,use-def 链是显式的,每个蕴含一个元素。
所以下面的代码就是判断 SSA 的 cfg(control flow graph 控制流图)的块是不是可达的,如果可达,执行条件中的代码。
还是不太通透,尽管能推断出设置 refcount 跟优化无关,跟动态繁多赋值无关,但在写扩大的时候,什么时候该用 @refcount 1,还是不太分明。心愿对源码有深入研究的敌人能够提点我一下,谢谢。