在浏览PHP
源码的时候,在众多的*.stub.php
中,发现了这样的注释,@refcount 1。
通过翻看build/gen_stub.php
源码,发现了在解析*.stub.php
文件时,关于返回信息的代码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
<?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
为例:
1
2
3
4
5
|
/** * @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
。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
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
。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
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
的宏定义。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
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行:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
#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链是显式的,每个包含一个元素。
原文链接:https://segmentfault.com/a/1190000042715840
暂无评论内容