聊一聊關(guān)于php源碼中refcount的疑問
在瀏覽PHP源碼的時候,在眾多的*.stub.php中,發(fā)現(xiàn)了這樣的注釋,@refcount 1。
通過翻看build/gen_stub.php源碼,發(fā)現(xiàn)了在解析*.stub.php文件時,關(guān)于返回信息的代碼。
<?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,也就是標量(基本數(shù)據(jù)類型,整型、浮點型、字符串等),那么refcount指定為0,否則為N。如果設置了注釋,那么以注釋為最高優(yōu)先級。
以函數(shù)ob_list_handlers為例:
/**
* @return array<int, string>
* @refcount 1
*/
function ob_list_handlers(): array {}返回值是array,所以默認的refcount應該是N,但由于設置了注釋@refcount 1,所以返回值的引用計數(shù)被替換成1。
這些邏輯我能看懂,但設置返回值引用計數(shù)的目的是什么?我還是一頭霧水
我接著往下排查,發(fā)現(xiàn)通過返回值的引用計數(shù),在生成func_info的時候,會有些不同。如果返回值引用計數(shù)為1或N,則會用對應的宏去初始化func_info結(jié)構(gòu)體。如果是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";
}獲取函數(shù)原型的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。
依然一頭霧水,但是通過目錄名,我依稀能猜出這跟性能優(yōu)化有關(guān),跟JIT有關(guān)系。我決定繼續(xù)追查下去,看看這些初始化后的結(jié)構(gòu)體在哪里使用過。
我們很清楚,設置位信息用|,那判斷有沒有設置肯定用&,全局搜索& MAY_BE_RCN,再看看哪些代碼跟優(yōu)化有關(guān),定位到了如下代碼,在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如果返回值的引用計數(shù)是1,而不是N的時候,并且開啟了返回值引用計數(shù)推導功能,就走這段代碼。這段代碼又涉及到所謂SSA,靜態(tài)單賦值的編譯器設計方式。
在編譯器設計中,靜態(tài)單一賦值形式(通常縮寫為SSA形式或簡稱SSA)是中間表示(IR)的屬性,它要求每個變量只分配一次,并且每個變量在使用之前定義。原始IR中的現(xiàn)有變量被拆分為版本,在教科書中,新變量通常由原始名稱用下標表示,以便每次定義都有自己的版本。在SSA形式中,use-def鏈是顯式的,每個包含一個元素。
所以上面的代碼就是判斷SSA的cfg(control flow graph控制流圖)的塊是不是可達的,如果可達,執(zhí)行條件中的代碼。
還是不太通透,雖然能推斷出設置refcount跟優(yōu)化有關(guān),跟靜態(tài)單一賦值有關(guān),但在寫擴展的時候,什么時候該用@refcount 1,還是不太清楚。
總結(jié)
到此這篇關(guān)于php源碼中refcount疑問的文章就介紹到這了,更多相關(guān)php源碼中refcount內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
解析PHP中intval()等int轉(zhuǎn)換時的意外異常情況
本篇文章是對PHP中intval()等int轉(zhuǎn)換時的意外異常情況進行了詳細的分析介紹,需要的朋友參考下2013-06-06
php json_encode值中大括號與花括號區(qū)別
這篇文章主要介紹了json_encode值中大括號與花括號區(qū)別,具體的看下實例說明,需要的朋友參考下2013-09-09
PHP XML error parsing SOAP payload on line 1
PHP中GBK頁面調(diào)用WebService的編碼問題:XML error parsing SOAP payload on line 12010-06-06
linux php mysql數(shù)據(jù)庫備份實現(xiàn)代碼
想在PHP后臺管理直接能夠備份數(shù)據(jù)庫,于是想呀想,一直沒有什么思路,一開始是考慮用php來訪問服務器安裝mysql的目錄,比如 /usr/local/mysql/data目錄,直接把下面對應的文件進行備份2009-03-03
php實現(xiàn)的數(shù)字驗證碼及數(shù)字運算驗證碼
這篇文章主要介紹了php實現(xiàn)的數(shù)字驗證碼及數(shù)字運算驗證碼,以實例形式分別描述了php實現(xiàn)數(shù)字驗證碼及數(shù)學運算驗證碼的相關(guān)技巧,非常簡單實用,需要的朋友可以參考下2015-07-07
PHP實現(xiàn)獲取ip地址的5種方法,以及插入用戶登錄日志操作示例
這篇文章主要介紹了PHP實現(xiàn)獲取ip地址的5種方法,以及插入用戶登錄日志操作,結(jié)合實例形式總結(jié)分析了php獲取訪客IP地址的5種常見操作方法,以及將用戶登陸信息寫入登陸日志數(shù)據(jù)庫相關(guān)操作技巧,需要的朋友可以參考下2019-02-02
探討Smarty中如何獲取數(shù)組的長度以及smarty調(diào)用php函數(shù)的詳解
本篇文章是對Smarty中如何獲取數(shù)組的長度以及smarty調(diào)用php函數(shù)的方法進行了詳細的分析介紹,需要的朋友參考下2013-06-06

