PHP-7.1 源代碼學習:字節碼生成 之 "$a = 1"

前言

本文經過分析 "$a=1" 這個 PHP 語句的編譯和執行來窺探 php-cli 解釋執行邏輯php

準備

  • 參考以前的系列文章,在 ubuntu 環境下下載,編譯 PHP 源代碼node

  • 將代碼導入 idea clion IDE 中ubuntu

  • 編輯運行選項,增長運行參數:-f test.phpapi

  • 設置斷點開始調試數組

test.php 是一個測試腳本,放在 sapi/cli/ 目錄下,test.php 中只包含一條簡單的賦值語句:ide

<?php
$a = 1
?>

調用堆棧

參考以前的系列文章來了解 php-cli 啓動過程以及語法分析和字節碼生成的基本概念,這裏直接給出調用堆棧:
圖片描述函數

咱們嘗試從 zend_compile_expr 函數提及測試

zend_compile_expr

賦值語句 is-a 表達式,zend_compile_expr 函數根據 ast 類型選擇調用 zend_compile_assign:fetch

// zend_compile.c

void zend_compile_expr(znode *result, zend_ast *ast) {
    ...
    switch (ast->kind) {
        ...
        case ZEND_AST_ASSIGN:
            zend_compile_assign(result, ast);
            break;
    }
}

zend_compile_assign

賦值語句的 ast 包含兩個 child ast,即 left hand side var(ast->child[0]) 和 right hand side expr(ast->child[1]),var_node 和 expr_node 兩個 znode 類型的變量是生成字節碼過程使用的中間變量ui

// zend_compile.c

void zend_compile_assign(znode *result, zend_ast *ast) /* {{{ */
{
    zend_ast *var_ast = ast->child[0];
    zend_ast *expr_ast = ast->child[1];

    znode var_node, expr_node;
    zend_op *opline;
    uint32_t offset;

    if (is_this_fetch(var_ast)) {
        zend_error_noreturn(E_COMPILE_ERROR, "Cannot re-assign $this");
    }

    zend_ensure_writable_variable(var_ast);

而後咱們來看看 switch case 語句

// zend_compile.c

switch (var_ast->kind) {
    case ZEND_AST_VAR:
    case ZEND_AST_STATIC_PROP:
        offset = zend_delayed_compile_begin();
        zend_delayed_compile_var(&var_node, var_ast, BP_VAR_W);
        zend_compile_expr(&expr_node, expr_ast);
        zend_delayed_compile_end(offset);
        zend_emit_op(result, ZEND_ASSIGN, &var_node, &expr_node);
        return;
}

剛看到這段代碼可能會以爲挺繞的:zend_delayed_xxx 函數是幹啥的?最終生成的字節碼又保存在哪呢?

zend_emit_op

emit 有 "發射,散播"的意思,因此 zend_emit_op 可能和字節碼保存相關:

// zend_compile.c

static zend_op *zend_emit_op(znode *result, zend_uchar opcode,   
znode *op1, znode *op2) /* {{{ */
{
    zend_op *opline = get_next_op(CG(active_op_array));
    opline->opcode = opcode;

    if (op1 == NULL) {
        SET_UNUSED(opline->op1);
    } else {
        SET_NODE(opline->op1, op1);
    }

    if (op2 == NULL) {
        SET_UNUSED(opline->op2);
    } else {
        SET_NODE(opline->op2, op2);
    }

    zend_check_live_ranges(opline);
    if (result) {
        zend_make_var_result(result, opline);
    }
    return opline;
}

這裏咱們又遇到了全局變量 CG(compile globals),zend_emit_op 先調用 get_next_op 獲取可用的 zend_op(虛擬機指令),而後設置 op1, op2 爲 opline 的兩個操做數

如今咱們知道生成的字節碼保存在 CG 的 active_op_array 數組裏

總結

相關文章
相關標籤/搜索