翼度科技»论坛 编程开发 PHP 查看内容

聊一聊关于php源码中refcount的疑问

7

主题

7

帖子

21

积分

新手上路

Rank: 1

积分
21
在浏览
  1. PHP
复制代码
源码的时候,在众多的
  1. *.stub.php
复制代码
中,发现了这样的注释,@refcount 1
通过翻看
  1. build/gen_stub.php
复制代码
源码,发现了在解析
  1. *.stub.php
复制代码
文件时,关于返回信息的代码。
  1. <?php
  2. class ReturnInfo {
  3.     const REFCOUNT_0 = "0";
  4.     const REFCOUNT_1 = "1";
  5.     const REFCOUNT_N = "N";

  6.     const REFCOUNTS = [
  7.         self::REFCOUNT_0,
  8.         self::REFCOUNT_1,
  9.         self::REFCOUNT_N,
  10.     ];

  11.     //...
  12.    
  13.     private function setRefcount(?string $refcount): void
  14.     {
  15.         $type = $this->phpDocType ?? $this->type;
  16.         $isScalarType = $type !== null && $type->isScalar();

  17.         if ($refcount === null) {
  18.             $this->refcount = $isScalarType ? self::REFCOUNT_0 : self::REFCOUNT_N;
  19.             return;
  20.         }

  21.         if (!in_array($refcount, ReturnInfo::REFCOUNTS, true)) {
  22.             throw new Exception("@refcount must have one of the following values: "0", "1", "N", $refcount given");
  23.         }

  24.         if ($isScalarType && $refcount !== self::REFCOUNT_0) {
  25.             throw new Exception('A scalar return type of "' . $type->__toString() . '" must have a refcount of "' . self::REFCOUNT_0 . '"');
  26.         }

  27.         if (!$isScalarType && $refcount === self::REFCOUNT_0) {
  28.             throw new Exception('A non-scalar return type of "' . $type->__toString() . '" cannot have a refcount of "' . self::REFCOUNT_0 . '"');
  29.         }

  30.         $this->refcount = $refcount;
  31.     }
复制代码
明显,如果返回值类型是
  1. scalar
复制代码
,也就是标量(基本数据类型,整型、浮点型、字符串等),那么
  1. refcount
复制代码
指定为0,否则为N。如果设置了注释,那么以注释为最高优先级。
以函数
  1. ob_list_handlers
复制代码
为例:
  1. /**
  2. * @return array<int, string>
  3. * @refcount 1
  4. */
  5. function ob_list_handlers(): array {}
复制代码
返回值是
  1. array
复制代码
,所以默认的
  1. refcount
复制代码
应该是N,但由于设置了注释
  1. @refcount 1
复制代码
,所以返回值的引用计数被替换成1。
  1. 这些逻辑我能看懂,但设置返回值引用计数的目的是什么?我还是一头雾水
复制代码
我接着往下排查,发现通过返回值的引用计数,在生成
  1. func_info
复制代码
的时候,会有些不同。如果返回值引用计数为1或N,则会用对应的宏去初始化
  1. func_info
复制代码
结构体。如果是0,则不进入初始化列表。
以上的代码逻辑依然可以在
  1. gen_stub.php
复制代码
中找到,1393行,
  1. getOptimizerInfo
复制代码
  1. public function getOptimizerInfo(): ?string {
  2.         if ($this->isMethod()) {
  3.             return null;
  4.         }

  5.         if ($this->alias !== null) {
  6.             return null;
  7.         }

  8.         if ($this->return->refcount !== ReturnInfo::REFCOUNT_1 && $this->return->phpDocType === null) {
  9.             return null;
  10.         }

  11.         $type = $this->return->phpDocType ?? $this->return->type;
  12.         if ($type === null) {
  13.             return null;
  14.         }

  15.         return "\tF" . $this->return->refcount . '("' . $this->name->__toString() . '", ' . $type->toOptimizerTypeMask() . "),\n";
  16.     }
复制代码
获取函数原型的
  1. refcount
复制代码
,生成诸如
  1. F1()
复制代码
  1. FN()
复制代码
的代码,生成的头文件位置在
  1. Zend/Optimizer/zend_func_infos.h
复制代码
  1. static const func_info_t func_infos[] = {
  2.     F1("zend_version", MAY_BE_STRING),
  3.     FN("func_get_args", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_OF_ANY),
  4.     F1("get_class_vars", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF),
  5.     F1("get_class_methods", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_OF_STRING),
  6.     F1("get_included_files", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_OF_STRING),
  7.     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),
  8.     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),
  9.     F1("get_declared_classes", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_OF_STRING),
  10.     F1("get_declared_traits", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_OF_STRING),
  11.     F1("get_declared_interfaces", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_OF_STRING),
  12.     F1("get_defined_functions", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_ARRAY),
  13.     F1("get_defined_vars", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF),
  14.     F1("get_resource_type", MAY_BE_STRING),
  15.     F1("get_loaded_extensions", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_OF_STRING),
  16.     F1("get_defined_constants", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_ANY),
  17.     F1("debug_backtrace", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_OF_ARRAY),
  18.     F1("get_extension_funcs", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_OF_STRING|MAY_BE_FALSE),
  19.     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),
  20.     F1("bcadd", MAY_BE_STRING),
  21.     F1("bcsub", MAY_BE_STRING),
  22.     F1("bcmul", MAY_BE_STRING),
  23.     F1("bcdiv", MAY_BE_STRING),
  24.     F1("bcmod", MAY_BE_STRING),
  25.     F1("bcpowmod", MAY_BE_STRING),
  26.     F1("bcpow", MAY_BE_STRING),
  27.     F1("bcsqrt", MAY_BE_STRING),
  28.     FN("bzopen", MAY_BE_RESOURCE|MAY_BE_FALSE),
  29.     F1("bzerror", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_LONG|MAY_BE_ARRAY_OF_STRING),
  30.     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),
  31.     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),
  32.     F1("curl_copy_handle", MAY_BE_OBJECT|MAY_BE_FALSE),
  33.     //...
  34. };
复制代码
再去看看
  1. F1
复制代码
  1. FN
复制代码
的宏定义。
  1. typedef struct _func_info_t {
  2.     const char *name;
  3.     unsigned    name_len;
  4.     uint32_t    info;
  5.     info_func_t info_func;
  6. } func_info_t;

  7. #define F0(name, info) \
  8.     {name, sizeof(name)-1, (info), NULL}
  9. #define F1(name, info) \
  10.     {name, sizeof(name)-1, (MAY_BE_RC1 | (info)), NULL}
  11. #define FN(name, info) \
  12.     {name, sizeof(name)-1, (MAY_BE_RC1 | MAY_BE_RCN | (info)), NULL}
  13. #define FC(name, callback) \
  14.     {name, sizeof(name)-1, 0, callback}
复制代码
仅仅是设置了不同的
  1. type mask
复制代码
  1. F1
复制代码
设置了
  1. MAY_BE_RC1
复制代码
  1. FN
复制代码
设置了
  1. MAY_BE_RCN | MAY_BE_RC1
复制代码
  1. 依然一头雾水,但是通过目录名,我依稀能猜出这跟性能优化有关,跟JIT有关系。我决定继续追查下去,看看这些初始化后的结构体在哪里使用过。
复制代码
我们很清楚,设置位信息用
  1. |
复制代码
,那判断有没有设置肯定用
  1. &
复制代码
,全局搜索
  1. & MAY_BE_RCN
复制代码
,再看看哪些代码跟优化有关,定位到了如下代码,在
  1. zend_jit.c
复制代码
的530行:
  1. #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的时候,并且开启了返回值引用计数推导功能,就走这段代码。这段代码又涉及到所谓
  1. SSA
复制代码
,静态单赋值的编译器设计方式。
  1. 在编译器设计中,静态单一赋值形式(通常缩写为SSA形式或简称SSA)是中间表示(IR)的属性,它要求每个变量只分配一次,并且每个变量在使用之前定义。原始IR中的现有变量被拆分为版本,在教科书中,新变量通常由原始名称用下标表示,以便每次定义都有自己的版本。在SSA形式中,use-def链是显式的,每个包含一个元素。
复制代码
所以上面的代码就是判断SSA的cfg(control flow graph控制流图)的块是不是可达的,如果可达,执行条件中的代码。
  1. 还是不太通透,虽然能推断出设置refcount跟优化有关,跟静态单一赋值有关,但在写扩展的时候,什么时候该用@refcount 1,还是不太清楚。
复制代码
总结
到此这篇关于php源码中refcount疑问的文章就介绍到这了,更多相关php源码中refcount内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

来源:https://www.jb51.net/article/266646.htm
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

举报 回复 使用道具