【PHP7源碼學習】2019-03-27 pass_two函數詳解筆記

grapephp

所有視頻:https://segmentfault.com/a/11...node

原視頻地址:http://replay.xesv5.com/ll/24...segmentfault

流程回顧

上節課咱們把$a=1這個過程編譯梳理了一遍,咱們瞭解到op1,op2,result,opcode的生成過程,下面咱們把整個過程來回顧一下。數組

static zend_op_array *zend_compile(int type)
{
    zend_op_array *op_array = NULL;
    zend_bool original_in_compilation = CG(in_compilation);

    CG(in_compilation) = 1;
    CG(ast) = NULL;
    CG(ast_arena) = zend_arena_create(1024 * 32); //首先會分配內存

    if (!zendparse()) {  //zendparse(就是yyparse)(zend_language_parse.y) ==> 經過parser調用lexer,生成抽象語法樹ast_list,存到CG(ast);yyparse是經過bison編譯zend_language_parser.y生成
        int last_lineno = CG(zend_lineno);
        zend_file_context original_file_context;
        zend_oparray_context original_oparray_context;
        zend_op_array *original_active_op_array = CG(active_op_array);

        op_array = emalloc(sizeof(zend_op_array));
        init_op_array(op_array, type, INITIAL_OP_ARRAY_SIZE); //初始化oparray
        CG(active_op_array) = op_array;

        if (zend_ast_process) {
            zend_ast_process(CG(ast));
        }

        zend_file_context_begin(&original_file_context);  
        zend_oparray_context_begin(&original_oparray_context);
        zend_compile_top_stmt(CG(ast));  //編譯ast生成oparray
        CG(zend_lineno) = last_lineno;
        zend_emit_final_return(type == ZEND_USER_FUNCTION); //PHP中會加return 1,在此進行處理
        op_array->line_start = 1;
        op_array->line_end = last_lineno;
        pass_two(op_array);  //對於handler的處理
        zend_oparray_context_end(&original_oparray_context);
        zend_file_context_end(&original_file_context);

        CG(active_op_array) = original_active_op_array;
    }

    zend_ast_destroy(CG(ast));
    zend_arena_destroy(CG(ast_arena));

    CG(in_compilation) = original_in_compilation;

    return op_array;
}

大致流程爲:詞法分析->語法分析->編譯ast生成op_array->處理return 1->對於handler作處理
以上處理return 1 環節以前的文章中咱們都已經提到過,若是有不太理解的請翻閱以前的文章。接下來咱們gdb程序到環節return 1。代碼:函數

<?php
$a = 2;
$b = 3;

咱們來看一看到編譯ast生成op_array處的結果:源碼分析

clipboard.png
咱們來看這個結果,vars是存咱們的變量的,在這存的是a和b,而且last_Var=2只有兩個;T是temporary,T=2說明有兩個臨時變量。而後literals是存咱們的字面量,再這裏存的是2,3,last_literal=2表示如今有兩個字面量,接下來咱們打印一下看是否和咱們所解釋的一致。學習

clipboard.png

結果和咱們設想的一致。另外,對於opcode的值又是如何呢?ui

clipboard.png
咱們發現,$a=2 op1是80,$b=3 op1爲96,這是爲何呢?這以前咱們說過這個問題,由於在棧中咱們是分配一個大小爲16的內存,因此須要增長16.第二個,咱們知道result.constant的0和1表明字面量偏移量分別爲0和1.
到這裏都是以前學習過的內容,接下來繼續學習。spa

return 1的作了什麼?

繼續執行代碼:3d

clipboard.png

咱們發如今執行完zend_emit_final_return這句以後咱們的op_array發生了變化。那麼爲何會發生這樣的變化呢?咱們在文章開頭有些到這個函數的做用是增長return 1結尾,那麼具體其中是怎麼來操做呢?咱們來看代碼:

void zend_emit_final_return(int return_one) /* {{{ */
{
    znode zn;
    zend_op *ret;
    zend_bool returns_reference = (CG(active_op_array)->fn_flags & ZEND_ACC_RETURN_REFERENCE) != 0;

    if (CG(active_op_array)->fn_flags & ZEND_ACC_HAS_RETURN_TYPE
            && !(CG(active_op_array)->fn_flags & ZEND_ACC_GENERATOR)) {
        zend_emit_return_type_check(NULL, CG(active_op_array)->arg_info - 1, 1);
    }

    zn.op_type = IS_CONST;
    if (return_one) {
        ZVAL_LONG(&zn.u.constant, 1); //在gdb過程當中會走到這一步,把1賦值給zn.u.constant
    } else {
        ZVAL_NULL(&zn.u.constant);
    }

    ret = zend_emit_op(NULL, returns_reference ? ZEND_RETURN_BY_REF : ZEND_RETURN, &zn, NULL);//在此會像字面量中添加一個新的元素1
    ret->extended_value = -1;
}
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;
}
#define SET_NODE(target, src) do { \
        target ## _type = (src)->op_type; \
        if ((src)->op_type == IS_CONST) { \
            target.constant = zend_add_literal(CG(active_op_array), &(src)->u.constant); \ //增長元素
        } else { \
            target = (src)->u.op; \
        } \
    } while (0)

咱們發現,gdb過程在這個函數中像literals裏邊又新增1個元素,咱們打印opcodes:

clipboard.png
咱們發現,新增了一條指令,在代碼中就是return 1。
好的,到此,咱們發現,有三條指令,兩個變量,三個字面量。$a和$b的位置已經有了,字面量也有了,咱們發現handler仍是個空指針,接下來咱們看handler的生成。

pass_two設置handler

咱們接着走,會走到pass_two這個函數,這個函數中,對opline指令集作了進一步的加工,最主要的工做是設置指令的handler,源碼以下:

ZEND_API int pass_two(zend_op_array *op_array)

{

     /**代碼省略**/

   while (opline < end) {//遍歷opline數組

      if (opline->op1_type == IS_CONST) {

               ZEND_PASS_TWO_UPDATE_CONSTANT(op_array, opline->op1);

      } else if (opline->op1_type & (IS_VAR|IS_TMP_VAR)) {

       opline->op1.var = (uint32_t)(zend_intptr_t)ZEND_CALL_VAR_NUM(NULL, op_array->last_var + opline->op1.var);

          }

     

      if (opline->op2_type == IS_CONST) {

          ZEND_PASS_TWO_UPDATE_CONSTANT(op_array, opline->op2);

      } else if (opline->op2_type & (IS_VAR|IS_TMP_VAR)) {

          opline->op2.var = (uint32_t)(zend_intptr_t)ZEND_CALL_VAR_NUM(NULL, op_array->last_var + opline->op2.var);

               }

    if (opline->result_type & (IS_VAR|IS_TMP_VAR)) {

       opline->result.var = (uint32_t)(zend_intptr_t)ZEND_CALL_VAR_NUM(NULL, op_array->last_var + opline->result.var);

     }

     ZEND_VM_SET_OPCODE_HANDLER(opline);

     /**代碼省略**/

}

觀察代碼,該函數會對opline指令數組進行遍歷,他會處理以前生成的每一條opline,咱們拿IS_CONST來舉例,若是op1,op2的type爲IS_CONST,那麼將會調用ZEND_PASS_TWO_UPDATE_CONSTANT,代碼以下:

/* convert constant from compile-time to run-time */
# define ZEND_PASS_TWO_UPDATE_CONSTANT(op_array, node) do { \
        (node).zv = CT_CONSTANT_EX(op_array, (node).constant); \
    } while (0)
# define CT_CONSTANT_EX(op_array, num) \
    ((op_array)->literals + (num))

咱們知道,對於IS_CONST的變量的字面量是存在與literals裏邊的,而constant是相對的下標,所以咱們能夠經過對於首地址偏移constant來進行轉換爲真實的偏移量。對於IS_VAR|IS_TMP_VAR類型的變量,會經過ZEND_CALL_VAR_NUM計算偏移量。

另一個很是重要的工做是經過ZEND_VM_SET_OPCODE_HANDLER(opline),設置opline對應的hanlder,代碼以下:

ZEND_API void zend_vm_set_opcode_handler(zend_op* op)
{
    op->handler = zend_vm_get_opcode_handler(zend_user_opcodes[op->opcode], op);
}
static const void *zend_vm_get_opcode_handler(zend_uchar opcode, const zend_op* op)
{
    return zend_vm_get_opcode_handler_ex(zend_spec_handlers[opcode], op);
}

其中opcode和handler以前的對應關係在Zend/zend_vm_execute.h中定義的。opline數組通過一次遍歷後,handler也就設置完畢,設置後的opline數組如圖所示:

clipboard.png

結尾

最後咱們打印下生成handler後的op_array:

clipboard.png

咱們發現,handler已經被賦值。
至此,整個抽象語法樹就編譯完成了,最終的結果爲opline指令集,接下來就是在Zend虛擬機上執行這些指令。

參考資料:
【PHP7源碼分析】PHP7源碼研究之淺談Zend虛擬機

相關文章
相關標籤/搜索