php if 的實現

 

 簡單分析下php中的分支背後的實現php

<?php if($a == 1){ echo "a";  
}else{ echo "b"; }

 

1.語法分析node

unticked_statement: '{' inner_statement_list '}' | T_IF parenthesis_expr { zend_do_if_cond(&$2, &$1 TSRMLS_CC); } statement { zend_do_if_after_statement(&$1, 1 TSRMLS_CC); } elseif_list else_single { zend_do_if_end(TSRMLS_C); } parenthesis_expr: '(' expr ')' { $$ = $2; } | '(' yield_expr ')' { $$ = $2; } ; elseif_list: /* empty */ | elseif_list T_ELSEIF parenthesis_expr { zend_do_if_cond(&$3, &$2 TSRMLS_CC); } statement { zend_do_if_after_statement(&$2, 0 TSRMLS_CC); } ; %token T_ELSEIF "elseif (T_ELSEIF)" else_single: /* empty */
    | T_ELSE statement ; %token T_ELSE      "else (T_ELSE)"

expr_without_variable:
| expr T_IS_EQUAL expr { zend_do_binary_op(ZEND_IS_EQUAL, &$$, &$1, &$3 TSRMLS_CC); }

對於上面的php代碼來講數組

if 匹配 T_IF函數

$a == 1 匹配 parenthesis_expr , 同時語法分析器要執行 zend_do_if_condoop

op爲zend_is_equalspa

 

void zend_do_binary_op(zend_uchar op, znode *result, const znode *op1, const znode *op2 TSRMLS_DC) /* {{{ */
{
    zend_op *opline = get_next_op(CG(active_op_array) TSRMLS_CC);

    opline->opcode = op;
    opline->result_type = IS_TMP_VAR;
    opline->result.var = get_temporary_variable(CG(active_op_array));
    SET_NODE(opline->op1, op1);
    SET_NODE(opline->op2, op2);
    GET_NODE(result, opline->result);
}

 

 

 

 

 

static int ZEND_FASTCALL  ZEND_IS_EQUAL_SPEC_CV_TMP_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
    USE_OPLINE
    zend_free_op free_op2;
    zval *result = &EX_T(opline->result.var).tmp_var;

    SAVE_OPLINE();
    ZVAL_BOOL(result, fast_equal_function(result,
        _get_zval_ptr_cv_BP_VAR_R(execute_data, opline->op1.var TSRMLS_CC),
        _get_zval_ptr_tmp(opline->op2.var, execute_data, &free_op2 TSRMLS_CC) TSRMLS_CC));

    zval_dtor(free_op2.var);
    CHECK_EXCEPTION();
    ZEND_VM_NEXT_OPCODE();
}

 

 

 

 

echo "a"  匹配 statement, 同時語法分析器要執行 zend_do_if_after_statementcode

else 匹配 T_ELSE, 同時語法分析器再執行zend_do_if_endblog

 


  因爲是先分析 $a==1, 那麼它對應的opcode的opline_num 假設爲1,發現if ($a == 1) 匹配BNF後,執行下面的函數,獲得新的opline_num (這裏爲2)
,同時獲得新的opline, 設置opcode爲ZEND_JMPZ, 將op1設置爲$a==1對應的opcode, 將op2設置爲unused, 爲何呢?後來看代碼才知道,
噹噹前分支不成立時,是要跳轉的,在執行下面的函數時,還不知道要跳轉的opline_num, 只有當分析完statement後,才知道這個跳轉opline_num
void zend_do_if_cond(const znode *cond, znode *closing_bracket_token TSRMLS_DC) /* {{{ */ { int if_cond_op_number = get_next_op_number(CG(active_op_array)); zend_op *opline = get_next_op(CG(active_op_array) TSRMLS_CC);  opline->opcode = ZEND_JMPZ; SET_NODE(opline->op1, cond); closing_bracket_token->u.op.opline_num = if_cond_op_number; SET_UNUSED(opline->op2); INC_BPC(CG(active_op_array)); }

    

  echo "a"; 這個匹配statement 就不說了,而後執行zend_do_if_after_statement,這裏也要得到一個新的opline以及opline_num(這裏爲3),token

設置opcode爲ZEND_JMP, 意思爲無條件跳轉,同時設置opcodes的數組裏第2個元素的屬性op2.opline_num設置爲5,爲何不是4,是由於自己的無條件跳轉ZEND_JMP也算一個opcodeelement

   

void zend_do_if_after_statement(const znode *closing_bracket_token, unsigned char initialize TSRMLS_DC) /* {{{ */ { int if_end_op_number = get_next_op_number(CG(active_op_array)); zend_op *opline = get_next_op(CG(active_op_array) TSRMLS_CC); zend_llist *jmp_list_ptr; opline->opcode = ZEND_JMP; /* save for backpatching */
    if (initialize) { zend_llist jmp_list; zend_llist_init(&jmp_list, sizeof(int), NULL, 0); zend_stack_push(&CG(bp_stack), (void *) &jmp_list, sizeof(zend_llist)); } zend_stack_top(&CG(bp_stack), (void **) &jmp_list_ptr); zend_llist_add_element(jmp_list_ptr, &if_end_op_number); CG(active_op_array)->opcodes[closing_bracket_token->u.op.opline_num].op2.opline_num = if_end_op_number+1; SET_UNUSED(opline->op1); SET_UNUSED(opline->op2); }

   

  else { echo "b"; }  匹配 T_ELSE statement後,立刻執行 zend_do_if_end,獲得新的opline_num,注意:如今的opcodes數組裏有4個opcode了

第一個是$a==1對應的opcode, opline=1

第二個是ZEND_JMPZ 當  $a!=1時的opcode, opline=5

第三個是 echo "a";對應的opcode, opline =3

第四個是ZEND_JMP對應的opcode, opline=6

第五個是echo "b";對應的opcode  opline=5

 

因此ZEND_JMP 對應的是 else {echo "b";}以後的opcode了

void zend_do_if_end(TSRMLS_D) /* {{{ */ 
{
    int next_op_number = get_next_op_number(CG(active_op_array));
    zend_llist *jmp_list_ptr;
    zend_llist_element *le;

    zend_stack_top(&CG(bp_stack), (void **) &jmp_list_ptr);
    for (le=jmp_list_ptr->head; le; le = le->next) {
        CG(active_op_array)->opcodes[*((int *) le->data)].op1.opline_num = next_op_number;
    }
    zend_llist_destroy(jmp_list_ptr);
    zend_stack_del_top(&CG(bp_stack));
    DEC_BPC(CG(active_op_array));
}

 

 

 

 

pass_two函數,處理op_array中的各個opline, 上面unused掉的op2,在這裏又從新賦值,就是當分支不成立時,要跳轉的opcode所在的行號

ZEND_API int pass_two(zend_op_array *op_array TSRMLS_DC) { zend_op *opline, *end; if (op_array->type!=ZEND_USER_FUNCTION && op_array->type!=ZEND_EVAL_CODE) { return 0; } 。。。。 opline = op_array->opcodes; end = opline + op_array->last; while (opline < end) { if (opline->op1_type == IS_CONST) { opline->op1.zv = &op_array->literals[opline->op1.constant].constant; } if (opline->op2_type == IS_CONST) { opline->op2.zv = &op_array->literals[opline->op2.constant].constant; } switch (opline->opcode) { case ZEND_GOTO: if (Z_TYPE_P(opline->op2.zv) != IS_LONG) { zend_resolve_goto_label(op_array, opline, 1 TSRMLS_CC); } /* break omitted intentionally */
                 case ZEND_JMP: case ZEND_FAST_CALL: opline->op1.jmp_addr = &op_array->opcodes[opline->op1.opline_num]; break; case ZEND_JMPZ: case ZEND_JMPNZ: case ZEND_JMPZ_EX: case ZEND_JMPNZ_EX: case ZEND_JMP_SET: case ZEND_JMP_SET_VAR: opline->op2.jmp_addr = &op_array->opcodes[opline->op2.opline_num]; break; case ZEND_RETURN: case ZEND_RETURN_BY_REF: if (op_array->fn_flags & ZEND_ACC_GENERATOR) { if (opline->op1_type != IS_CONST || Z_TYPE_P(opline->op1.zv) != IS_NULL) { CG(zend_lineno) = opline->lineno; zend_error(E_COMPILE_ERROR, "Generators cannot return values using \"return\""); } opline->opcode = ZEND_GENERATOR_RETURN; } break; } ZEND_VM_SET_OPCODE_HANDLER(opline); opline++; } op_array->fn_flags |= ZEND_ACC_DONE_PASS_TWO; return 0; }

 

 

static int ZEND_FASTCALL ZEND_JMPZ_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE zval *val; int ret; SAVE_OPLINE();
//opline->op1爲 if後面的表達式的值 val
= opline->op1.zv; if (IS_CONST == IS_TMP_VAR && EXPECTED(Z_TYPE_P(val) == IS_BOOL)) {


ret
= Z_LVAL_P(val); } else {
//判斷val是否爲1 true 0 false,若是爲1,執行下一條opcode,若是爲0,進行opcode的跳轉 ret
= i_zend_is_true(val); if (UNEXPECTED(EG(exception) != NULL)) { HANDLE_EXCEPTION(); } } if (!ret) { #if DEBUG_ZEND>=2 printf("Conditional jmp to %d\n", opline->op2.opline_num); #endif ZEND_VM_SET_OPCODE(opline->op2.jmp_addr); ZEND_VM_CONTINUE(); } ZEND_VM_NEXT_OPCODE(); }

 

關於i_zend_is_true的實現

 

static zend_always_inline int i_zend_is_true(zval *op) { int result; switch (Z_TYPE_P(op)) { case IS_NULL: result = 0; break; case IS_LONG: case IS_BOOL: case IS_RESOURCE: result = (Z_LVAL_P(op)?1:0); break; case IS_DOUBLE: result = (Z_DVAL_P(op) ? 1 : 0); break; case IS_STRING: if (Z_STRLEN_P(op) == 0
                || (Z_STRLEN_P(op)==1 && Z_STRVAL_P(op)[0]=='0')) { result = 0; } else { result = 1; } break; case IS_ARRAY: result = (zend_hash_num_elements(Z_ARRVAL_P(op))?1:0); break; case IS_OBJECT: if(IS_ZEND_STD_OBJECT(*op)) { TSRMLS_FETCH(); if (Z_OBJ_HT_P(op)->cast_object) { zval tmp; if (Z_OBJ_HT_P(op)->cast_object(op, &tmp, IS_BOOL TSRMLS_CC) == SUCCESS) { result = Z_LVAL(tmp); break; } } else if (Z_OBJ_HT_P(op)->get) { zval *tmp = Z_OBJ_HT_P(op)->get(op TSRMLS_CC); if(Z_TYPE_P(tmp) != IS_OBJECT) { /* for safety - avoid loop */ convert_to_boolean(tmp); result = Z_LVAL_P(tmp); zval_ptr_dtor(&tmp); break; } } } result = 1; break; default: result = 0; break; } return result; }

 

 

<?php
if($a == 1){
    echo "a";
}else{
    echo "b";
}

echo "c";

 

 

1. zend_is_equsl 

  op1.zv $a 

     op1_type cv 

     op2.zv  1

    op2_type const

   result.zv  0/1

  result_type tmp_var

 

2.  zend_jmpz

    op1.zv  上面的0/1

   op2 暫時沒有

 

3.zend_echo 

4.zend_jmp  下一個跳轉,這裏能夠知道jmpz的跳轉地址了,就是當前 opline_num+1, 因而 zend_jmpz的 op2.opline_num=5

5.zend_echo 

這時知道zend_jmp的跳轉地址 ,是5, 即 zend_jmp 的op1.opline_num爲5

相關文章
相關標籤/搜索