搜索

查看: 3105|回复: 11

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

[复制链接]
发表于 2023-5-4 17:07:20 | 显示全部楼层 |阅读模式
Editor 2023-5-4 17:07:20 3105 11 看全部
在浏览PHP源码的时候,在众多的*.stub.php中,发现了这样的注释,@refcount 1
通过翻看build/gen_stub.php源码,发现了在解析*.stub.php文件时,关于返回信息的代码。
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
* @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 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,还是不太清楚。
总结
到此这篇关于php源码中refcount疑问的文章就介绍到这了,更多相关php源码中refcount内容请搜索知鸟论坛以前的文章或继续浏览下面的相关文章希望大家以后多多支持知鸟论坛
回复

使用道具 举报

发表于 2023-6-29 04:28:00 | 显示全部楼层
462710480 2023-6-29 04:28:00 看全部
楼主,大恩不言谢了!知鸟论坛是最棒的!
回复

使用道具 举报

发表于 2023-6-29 14:45:09 | 显示全部楼层
幸福341 2023-6-29 14:45:09 看全部
既然你诚信诚意的推荐了,那我就勉为其难的看看吧!知鸟论坛不走平凡路。
回复

使用道具 举报

发表于 2023-6-30 08:23:35 | 显示全部楼层
贺老师 2023-6-30 08:23:35 看全部
这东西我收了!谢谢楼主!知鸟论坛真好!
回复

使用道具 举报

发表于 2023-6-30 15:36:36 | 显示全部楼层
Gordon520 2023-6-30 15:36:36 看全部
这东西我收了!谢谢楼主!知鸟论坛真好!
回复

使用道具 举报

发表于 2023-6-30 21:56:51 | 显示全部楼层
执着等待等wc 2023-6-30 21:56:51 看全部
楼主发贴辛苦了,谢谢楼主分享!我觉得知鸟论坛是注册对了!
回复

使用道具 举报

发表于 2023-6-30 22:08:37 | 显示全部楼层
123456868 2023-6-30 22:08:37 看全部
感谢楼主的无私分享!要想知鸟论坛好 就靠你我他
回复

使用道具 举报

发表于 2023-7-3 07:50:21 | 显示全部楼层
风来时狂放 2023-7-3 07:50:21 看全部
论坛不能没有像楼主这样的人才啊!我会一直支持知鸟论坛
回复

使用道具 举报

发表于 2023-7-4 08:03:30 | 显示全部楼层
风吹吹蛋蛋疼风w 2023-7-4 08:03:30 看全部
其实我一直觉得楼主的品味不错!呵呵!知鸟论坛太棒了!
回复

使用道具 举报

发表于 2023-7-5 02:54:10 | 显示全部楼层
惜颜705 2023-7-5 02:54:10 看全部
我看不错噢 谢谢楼主!知鸟论坛越来越好!
回复

使用道具 举报

  • 您可能感兴趣
点击右侧快捷回复 【请勿灌水】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则 返回列表

RSS订阅| SiteMap| 小黑屋| 知鸟论坛
联系邮箱E-mail:zniao@foxmail.com
快速回复 返回顶部 返回列表