搜索
首页后端开发php教程重新实现PHP中的范围运算符

SitePoint精彩文章推荐:改进后的PHP范围运算符实现

本文经作者授权转载于SitePoint。以下内容由Thomas Punt撰写,介绍了PHP范围运算符的改进实现方法。如果您对PHP内部机制和为喜爱的编程语言添加功能感兴趣,那么现在正是学习的好时机!

本文假设读者能够从源代码构建PHP。如果不是这样,请先阅读PHP内部机制书籍的“构建PHP”章节。

Re-Implementing the Range Operator in PHP


在本文的前篇(提示:请确保您已阅读),我展示了一种在PHP中实现范围运算符的方法。然而,最初的实现很少是最好的,因此本文旨在探讨如何改进之前的实现。

再次感谢Nikita Popov校对本文!

关键要点

  • Thomas Punt重新实现了PHP中的范围运算符,将计算逻辑从Zend虚拟机中移出,从而允许在常量表达式上下文中使用范围运算符。
  • 此重新实现能够在编译时(对于字面量操作数)或运行时(对于动态操作数)进行计算。这不仅为Opcache用户带来了一点好处,而且允许将常量表达式功能与范围运算符一起使用。
  • 重新实现过程涉及更新词法分析器、解析器、编译阶段和Zend虚拟机。词法分析器实现保持不变,而解析器实现与之前部分相同。编译阶段不需要更新Zend/zend_compile.c文件,因为它已经包含处理二元运算的必要逻辑。Zend虚拟机已更新为在运行时处理ZEND_RANGE操作码的执行。
  • 在本系列的第三部分中,Punt计划通过介绍如何重载此运算符来构建此实现。这将使对象能够用作操作数,并为字符串添加适当的支持。

先前实现的缺点

最初的实现将范围运算符的所有逻辑都放在Zend虚拟机中,这迫使计算在执行ZEND_RANGE操作码时纯粹在运行时进行。这不仅意味着对于字面量操作数,计算不能转移到编译时,而且还意味着某些功能根本无法工作。

在此实现中,我们将范围运算符逻辑从Zend虚拟机中移出,以便能够在编译时(对于字面量操作数)或运行时(对于动态操作数)进行计算。这不仅为Opcache用户带来了一点好处,更重要的是允许将常量表达式功能与范围运算符一起使用。

例如:

// 作为常量定义
const AN_ARRAY = 1 |> 100;

// 作为初始属性定义
class A
{
    private $a = 1 |> 2;
}

// 作为可选参数的默认值:
function a($a = 1 |> 2)
{
    //
}

因此,事不宜迟,让我们重新实现范围运算符。

更新词法分析器

词法分析器实现保持完全不变。令牌首先在Zend/zend_language_scanner.l(约1200行)中注册:

<st_in_scripting>"|>" {
</st_in_scripting>    RETURN_TOKEN(T_RANGE);
}

然后在Zend/zend_language_parser.y(约220行)中声明:

// 作为常量定义
const AN_ARRAY = 1 |> 100;

// 作为初始属性定义
class A
{
    private $a = 1 |> 2;
}

// 作为可选参数的默认值:
function a($a = 1 |> 2)
{
    //
}

必须再次通过进入ext/tokenizer目录并执行tokenizer_data_gen.sh文件来重新生成标记器扩展。

更新解析器

解析器实现与之前部分相同。我们再次通过将T_RANGE令牌添加到以下行的末尾来声明运算符的优先级和结合性(约70行):

<st_in_scripting>"|>" {
</st_in_scripting>    RETURN_TOKEN(T_RANGE);
}

然后,我们再次更新expr_without_variable产生式规则,但这次语义动作(花括号内的代码)将略有不同。使用以下代码更新它(我将其放在T_SPACESHIP规则下方,约930行):

%token T_RANGE           "|> (T_RANGE)"

这次,我们使用了zend_ast_create_binary_op函数(而不是zend_ast_create函数),它为我们创建了一个ZEND_AST_BINARY_OP节点。zend_ast_create_binary_op采用一个操作码名称,该名称将在编译阶段用于区分二元运算。

由于我们现在正在重用ZEND_AST_BINARY_OP节点类型,因此无需像之前在Zend/zend_ast.h文件中那样定义新的ZEND_AST_RANGE节点类型。

更新编译阶段

这次,无需更新Zend/zend_compile.c文件,因为它已经包含处理二元运算的必要逻辑。因此,我们只需通过将我们的运算符设为ZEND_AST_BINARY_OP节点来重用此逻辑。

以下是zend_compile_binary_op函数的简化版本:

%nonassoc T_IS_EQUAL T_IS_NOT_EQUAL T_IS_IDENTICAL T_IS_NOT_IDENTICAL T_SPACESHIP T_RANGE

正如我们所看到的,它与我们上次创建的zend_compile_range函数非常相似。两个重要的区别在于如何获取操作码类型以及当两个操作数都是字面量时会发生什么。

操作码类型这次是从AST节点获取的(而不是像上次那样硬编码),因为ZEND_AST_BINARY_OP节点存储此值(如新的产生式规则的语义动作所示)以区分二元运算。当两个操作数都是字面量时,将调用zend_try_ct_eval_binary_op函数。此函数如下所示:

    |   expr T_RANGE expr
            { $$ = zend_ast_create_binary_op(ZEND_RANGE, , ); }

该函数根据操作码类型从Zend/zend_opcode.c中的get_binary_op函数(源代码)获取回调。这意味着我们需要接下来更新此函数以适应ZEND_RANGE操作码。将以下case语句添加到get_binary_op函数(约750行):

void zend_compile_binary_op(znode *result, zend_ast *ast) /* {{{ */
{
    zend_ast *left_ast = ast->child[0];
    zend_ast *right_ast = ast->child[1];
    uint32_t opcode = ast->attr;

    znode left_node, right_node;
    zend_compile_expr(&left_node, left_ast);
    zend_compile_expr(&right_node, right_ast);

    if (left_node.op_type == IS_CONST && right_node.op_type == IS_CONST) {
        if (zend_try_ct_eval_binary_op(&result->u.constant, opcode,
                &left_node.u.constant, &right_node.u.constant)
        ) {
            result->op_type = IS_CONST;
            zval_ptr_dtor(&left_node.u.constant);
            zval_ptr_dtor(&right_node.u.constant);
            return;
        }
    }

    do {
        // redacted code
        zend_emit_op_tmp(result, opcode, &left_node, &right_node);
    } while (0);
}
/* }}} */

现在我们必须定义range_function函数。这将在Zend/zend_operators.c文件中与所有其他运算符一起完成:

static inline zend_bool zend_try_ct_eval_binary_op(zval *result, uint32_t opcode, zval *op1, zval *op2) /* {{{ */
{
    binary_op_type fn = get_binary_op(opcode);

    /* don't evaluate division by zero at compile-time */
    if ((opcode == ZEND_DIV || opcode == ZEND_MOD) &&
        zval_get_long(op2) == 0) {
        return 0;
    } else if ((opcode == ZEND_SL || opcode == ZEND_SR) &&
        zval_get_long(op2)      return 0;
    }

    fn(result, op1, op2);
    return 1;
}
/* }}} */

函数原型包含两个新的宏:ZEND_API和ZEND_FASTCALL。ZEND_API用于通过使函数可用于编译为共享对象的扩展来控制函数的可见性。ZEND_FASTCALL用于确保使用更高效的调用约定,其中前两个参数将使用寄存器而不是堆栈传递(对于x86上的64位构建比32位构建更相关)。

函数体与我们在上一篇文章中的Zend/zend_vm_def.h文件中所拥有的非常相似。VM特定的内容不再存在,包括HANDLE_EXCEPTION宏调用(已替换为return FAILURE;),并且ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION宏调用已被完全删除(此检查和操作需要保留在VM中,因此宏将稍后从VM代码中调用)。此外,如前所述,我们避免使用GET_OPn_ZVAL_PTR伪宏(而不是GET_OPn_ZVAL_PTR_DEREF)在VM中处理引用。

另一个值得注意的区别是我们正在对两个操作数应用ZVAL_DEFEF以确保正确处理引用。这以前是在VM内部使用伪宏GET_OPn_ZVAL_PTR_DEREF完成的,但现在已转移到此函数中。这样做不是因为它在编译时需要(因为对于编译时处理,两个操作数都必须是字面量,并且它们不能被引用),而是因为它使代码库中的其他位置能够安全地调用range_function,而无需担心引用处理。因此,大多数运算符函数(除了性能至关重要的地方)都执行引用处理,而不是在其VM操作码定义中执行。

最后,我们必须将range_function原型添加到Zend/zend_operators.h文件:

// 作为常量定义
const AN_ARRAY = 1 |> 100;

// 作为初始属性定义
class A
{
    private $a = 1 |> 2;
}

// 作为可选参数的默认值:
function a($a = 1 |> 2)
{
    //
}

更新Zend虚拟机

现在我们必须再次更新Zend虚拟机以在运行时处理ZEND_RANGE操作码的执行。将以下代码放在Zend/zend_vm_def.h(底部):

<st_in_scripting>"|>" {
</st_in_scripting>    RETURN_TOKEN(T_RANGE);
}

(同样,操作码编号必须比当前最高操作码编号大一,这可以在Zend/zend_vm_opcodes.h文件的底部看到。)

这次的定义要短得多,因为所有工作都在range_function中处理。我们只需调用此函数,传入当前opline的结果操作数即可保存计算值。从range_function中删除的异常检查和跳到下一个操作码仍在VM中由对ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION的调用处理。此外,如前所述,我们避免使用GET_OPn_ZVAL_PTR伪宏(而不是GET_OPn_ZVAL_PTR_DEREF)在VM中处理引用。

现在通过执行Zend/zend_vm_gen.php文件重新生成VM。

最后,漂亮打印机需要再次更新Zend/zend_ast.c文件。通过指定新运算符的优先级为170来更新优先级表注释(约520行):

%token T_RANGE           "|> (T_RANGE)"

然后,在zend_ast_export_ex函数中插入一个case语句,以在ZEND_AST_BINARY_OP case语句中处理ZEND_RANGE操作码(约1300行):

%nonassoc T_IS_EQUAL T_IS_NOT_EQUAL T_IS_IDENTICAL T_IS_NOT_IDENTICAL T_SPACESHIP T_RANGE

结论

本文展示了一种实现范围运算符的替代方法,其中计算逻辑已从VM中移出。这具有能够在常量表达式上下文中使用范围运算符的优点。

本系列文章的第三部分将在此实现的基础上构建,介绍如何重载此运算符。这将允许对象用作操作数(例如来自GMP库的对象或实现__toString方法的对象)。它还将展示如何为字符串添加适当的支持(不像PHP当前范围函数中看到的支持)。但就目前而言,我希望这能很好地演示ZE在将运算符实现到PHP中时的一些更深层次的方面。

以上是重新实现PHP中的范围运算符的详细内容。更多信息请关注PHP中文网其他相关文章!

声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
解释负载平衡如何影响会话管理以及如何解决。解释负载平衡如何影响会话管理以及如何解决。Apr 29, 2025 am 12:42 AM

负载均衡会影响会话管理,但可以通过会话复制、会话粘性和集中式会话存储解决。1.会话复制在服务器间复制会话数据。2.会话粘性将用户请求定向到同一服务器。3.集中式会话存储使用独立服务器如Redis存储会话数据,确保数据共享。

说明会话锁定的概念。说明会话锁定的概念。Apr 29, 2025 am 12:39 AM

Sessionlockingisatechniqueusedtoensureauser'ssessionremainsexclusivetooneuseratatime.Itiscrucialforpreventingdatacorruptionandsecuritybreachesinmulti-userapplications.Sessionlockingisimplementedusingserver-sidelockingmechanisms,suchasReentrantLockinJ

有其他PHP会议的选择吗?有其他PHP会议的选择吗?Apr 29, 2025 am 12:36 AM

PHP会话的替代方案包括Cookies、Token-basedAuthentication、Database-basedSessions和Redis/Memcached。1.Cookies通过在客户端存储数据来管理会话,简单但安全性低。2.Token-basedAuthentication使用令牌验证用户,安全性高但需额外逻辑。3.Database-basedSessions将数据存储在数据库中,扩展性好但可能影响性能。4.Redis/Memcached使用分布式缓存提高性能和扩展性,但需额外配

在PHP的上下文中定义'会话劫持”一词。在PHP的上下文中定义'会话劫持”一词。Apr 29, 2025 am 12:33 AM

Sessionhijacking是指攻击者通过获取用户的sessionID来冒充用户。防范方法包括:1)使用HTTPS加密通信;2)验证sessionID的来源;3)使用安全的sessionID生成算法;4)定期更新sessionID。

PHP的完整形式是什么?PHP的完整形式是什么?Apr 28, 2025 pm 04:58 PM

文章讨论了PHP,详细介绍了其完整形式,在We​​b开发中的主要用途,与Python和Java的比较以及对初学者的学习便利性。

PHP如何处理形式数据?PHP如何处理形式数据?Apr 28, 2025 pm 04:57 PM

PHP使用$ \ _ post和$ \ _获取超级全局的php处理数据,并通过验证,消毒和安全数据库交互确保安全性。

PHP和ASP.NET有什么区别?PHP和ASP.NET有什么区别?Apr 28, 2025 pm 04:56 PM

本文比较了PHP和ASP.NET,重点是它们对大规模Web应用程序,性能差异和安全功能的适用性。两者对于大型项目都是可行的,但是PHP是开源和无关的,而ASP.NET,

PHP是对病例敏感的语言吗?PHP是对病例敏感的语言吗?Apr 28, 2025 pm 04:55 PM

PHP的情况敏感性各不相同:功能不敏感,而变量和类是敏感的。最佳实践包括一致的命名和使用对案例不敏感的功能进行比较。

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

mPDF

mPDF

mPDF是一个PHP库,可以从UTF-8编码的HTML生成PDF文件。原作者Ian Back编写mPDF以从他的网站上“即时”输出PDF文件,并处理不同的语言。与原始脚本如HTML2FPDF相比,它的速度较慢,并且在使用Unicode字体时生成的文件较大,但支持CSS样式等,并进行了大量增强。支持几乎所有语言,包括RTL(阿拉伯语和希伯来语)和CJK(中日韩)。支持嵌套的块级元素(如P、DIV),

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

WebStorm Mac版

WebStorm Mac版

好用的JavaScript开发工具

安全考试浏览器

安全考试浏览器

Safe Exam Browser是一个安全的浏览器环境,用于安全地进行在线考试。该软件将任何计算机变成一个安全的工作站。它控制对任何实用工具的访问,并防止学生使用未经授权的资源。