86-Execution of intermediate code opcode

Source: Internet
Author: User

86-Execution of intermediate code opcode

If we are using the CLI mode now, we find the main function directly in the Sapi/cli/php_cli.c file, and by default PHP's CLI mode behaves as Php_mode_standard. The PHP kernel in this behavior pattern calls Php_execute_script (&file_handle tsrmls_cc) to execute PHP files. Along this line of execution, you can see a PHP file in the process of parsing, parsing, and compiling the intermediate code after the compilation:

EG(active_op_array) = zend_compile_file(file_handle, type TSRMLS_CC);

After destroying the handler of the file, if there is an intermediate code, the PHP virtual machine executes the intermediate code with the following code:

zend_execute(EG(active_op_array) TSRMLS_CC);

If you are using VS to view the source, move the cursor to Zend_execute and press F12 directly, you will find that Zend_execute's definition jumps to the declaration of a pointer function (ZEND/ZEND_EXECUTE_API.C).

ZEND_API void (*zend_execute)(zend_op_array *op_array TSRMLS_DC);

This is a global function pointer, its role is to execute the PHP code file parsing finished Zend_op_array. As with Zend_execute, there is also a zedn_execute_internal function, which is used to execute intrinsic functions. When the PHP kernel is started (zend_startup), this global function pointer will point to the Execute function. Note the modifier in front of the function pointer is ZEND_API, which is part of the ZENDAPI. In the Zend_execute function pointer assignment, there is also the PHP Intermediate code compilation function zend_compile_file (file form) and zend_compile_string (string form).

zend_compile_file = compile_file;zend_compile_string = compile_string;zend_execute = execute;zend_execute_internal = NULL;zend_throw_exception_hook = NULL;

These global function pointers call only a few functions of the system's default implementation, such as the Compile_file and compile_string functions, which are present with global function pointers, which are ubiquitous in the PHP kernel, with the advantage of lower coupling You can even customize these functions. For example, in an opcode optimization extension such as APC, the function pointer is my_compile_file by replacing the system's default Zend_compile_file function pointer, and functions such as caching are added to the my_compile_file.

Here we find the final function of the intermediate code execution: Execute (zend/zend_vm_execure.h). The execution of all the intermediate code in this function will eventually call handler. What is this handler?

if ((ret = EX(opline)->handler(execute_data TSRMLS_CC)) > 0) {}

The

Handler here is a function pointer to the handler function that is called when the opcode is executed. At this point we need to see how the handler function pointer is set. In the preceding we have mentioned the global pointer function set with execute: zend_compile_string. Its role is to compile the string as an intermediate code. There is an implementation of the Compile_string function in the zend/zend_language_scanner.c file. In this function, when the intermediate code is parsed, it typically executes the pass_two (zend/zend_opcode.c) function. Pass_two this function, from its name is really a bit of what it means. But what we're concerned about is that inside the function, it iterates through the entire set of intermediate codes, calls Zend_vm_set_opcode_handler (Opline), and sets the handler function for each intermediate code. Zend_vm_set_opcode_handler is an interface macro for the Zend_vm_set_opcode_handler function, Zend_vm_set_opcode_handler function is defined in ZEND/ZEND_VM_ Execute.h file. The code is as follows:

Static opcode_handler_t zend_vm_get_opcode_handler (Zend_uchar opcode, zend_op* op) {static const int Zend_vm_decode    [] = {_unused_code,/* 0 */_const_code,/* 1 = is_const */_tmp_code,            /* 2 = Is_tmp_var */_unused_code,/* 3 */_var_code,/* 4 = Is_var */              _unused_code,/* 5 */_unused_code,/* 6 */_unused_code,/* 7 */_unused_code,/* 8 = is_unused */_unused_code,/* 9 */_unused_co            DE,/* */_unused_code,/* */_unused_code,/* 12 */             _unused_code,/* */_unused_code,/* */_unused_code,/* 15        */_cv_code/* = IS_CV */}; return Zend_opcode_handlers[opcode * + Zend_vm_decode[Op->op1.op_type] * 5 + zend_vm_decode[op->op2.op_type]];} Zend_api void Zend_vm_set_opcode_handler (zend_op* op) {op->handler = Zend_vm_get_opcode_handler (zend_user_opcodes [Op->opcode], op);}

The four ways to find opcode processing functions are described earlier, and the search is based on their essence, but this method is easy to identify for the computer, but not very friendly to the natural people. For example a simple A + b addition, if you want to use this method to find out where the code is implemented, first you need to know the value of the middle Code representative, and then know the value of the first expression and the type of the second expression result, and then calculate the result of a numeric value, and then from the array zend_ Opcode_handlers find this position, where the function is the function of the intermediate code. This is not good for reading the code, but it is good to go through the logic of the code as you begin to read the code.

Back to the chase. The method that handler points to is basically present in the Zend/zend_vm_execute.h file file. Knowing the origin of handler, we know where each opcode calls the handler pointer function when it is called.

After OPCODE's handler performs its job, the regular OPCODE adds a sentence on the last side of the function: Zend_vm_next_opcode ();. This is a macro whose purpose is to point the current opcode pointer to the next opcode and return 0. The following code:

#define ZEND_VM_NEXT_OPCODE() CHECK_SYMBOL_TABLES() EX(opline)++; ZEND_VM_CONTINUE()#define ZEND_VM_CONTINUE()   return 0

In the Execute function, the execution of the handler function is in the scope of a while (1) loop. As follows:

while (1) {        int ret;#ifdef ZEND_WIN32        if (EG(timed_out)) {            zend_timeout(0);        }#endif        if ((ret = EX(opline)->handler(execute_data TSRMLS_CC)) > 0) {            switch (ret) {                case 1:                    EG(in_execution) = original_in_execution;                    return;                case 2:                    op_array = EG(active_op_array);                    goto zend_vm_enter;                case 3:                    execute_data = EG(current_execute_data);                default:                    break;            }        }    }

It says that each intermediate code, after execution, points to the next instruction with the pointer of the intermediate code, and returns 0. When 0 o'clock is returned, the IF statement in the while loop does not satisfy the condition, so that the intermediate code can continue to execute. It is this while (1) loop that allows the opcode in the PHP kernel to be executed from the first to the last, of course, there are some functions of the jump or the execution of class methods.

The above is the execution of an intermediate code, then the recursive invocation of the function, the PHP kernel is how to handle it? Look at the following PHP code:

function t($c) {    echo $c, "\n";    if ($c > 2) {            return ;    }    t($c + 1);}t(1);

This is a simple recursive call function implementation, it recursive call two times, how does this recursive call? We know that the intermediate code where the function is called is ultimately called Zend_do_fcall_common_helper_spec (zend/zend_vm_execute.h). The following paragraph is in this function:

if (zend_execute == execute && !EG(exception)) {    EX(call_opline) = opline;    ZEND_VM_ENTER();} else {    zend_execute(EG(active_op_array) TSRMLS_CC);}

As mentioned earlier, Zend_execute API may be overwritten, here is a simple judgment, if the extension covers the opcode execution function, then the special logic processing.

The Zend_vm_enter () in the previous code is defined at the beginning of the zend/zend_vm_execute.h, as follows:

#define ZEND_VM_CONTINUE()   return 0 #define ZEND_VM_RETURN()     return 1 #define ZEND_VM_ENTER()      return 2 #define ZEND_VM_LEAVE()      return 3

These are useful in the execution function of the intermediate code, where Zend_vm_enter () represents return 2. In the preceding content we have said that the return value is assigned to RET after the call of Ex (Opline)->handler (Execute_data tsrmls_cc). Then according to RET judge next operation, here the recursive function is returned 2, so the next action is:

op_array = EG(active_op_array);goto zend_vm_enter;

After assigning the value of eg (Active_op_array) to Op_array, it jumps directly to the Zend_vm_enter label of the definition of the Execute function, where the value of eg (Active_op_array) is already zend_do_fcall The _common_helper_spec is replaced with an intermediate code set for the current function, with the following implementation code:

    if (EX(function_state).function->type == ZEND_USER_FUNCTION) {  //  用户自定义的函数        EX(original_return_value) = EG(return_value_ptr_ptr);        EG(active_symbol_table) = NULL;        EG(active_op_array) = &EX(function_state).function->op_array;   //  将当前活动的中间代码指针指向用户自定义函数的中间代码数组        EG(return_value_ptr_ptr) = NULL;    }

When the kernel executes a user-defined function, how does it return to the previous intermediate code trunk path? This is due to the fact that the current path has been recorded in Ex (Op_array) when the data is initialized in the Execute function (ex (Op_array) = Op_array;) When the user function returns, the program restores the previously saved path back to the eg (active_op_ Array) (EG (Active_op_array) = EX (Op_array);). Maybe at this point you will ask if the function does not return? This does not happen in user-defined functions, and even if you do not write a return statement, the PHP kernel automatically adds a return statement.

The entire invocation path is as follows:





Zend Intermediate code Call path map

The above is the implementation of the opcode process, compared with the process, the data in the process is more important, then in the implementation process of the core data structure? In the Execute function implementation in the Zend/zend_vm_execute.h file, the execute_data variable of the Zend_execute_data type runs through the execution of the entire intermediate code, which is not used directly at invocation Execute_ Data, instead of using the EX macro, which is defined in the Zend/zend_compile.h file, as follows:

#define EX(element) execute_data.element

So we see EX (FBC), ex (object) macro calls in the Execute function or in the opcode implementation function, which are the elements that call the function local variable execute_data: EXECUTE_DATA.FBC and Execute_ Data.object. Execute_data is not only the FBC, object and other elements, it contains the intermediate code in the execution process, the last function executed, the function of the current scope of execution, class and other information. The structure is as follows:

typedef struct _zend_execute_data zend_execute_data;struct _zend_execute_data {    struct _zend_op *opline;    zend_function_state function_state;    zend_function *fbc; /* Function Being Called */    zend_class_entry *called_scope;     zend_op_array *op_array;  /* 当前执行的中间代码 */    zval *object;    union _temp_variable *Ts;    zval ***CVs;    HashTable *symbol_table; /* 符号表 */    struct _zend_execute_data *prev_execute_data;   /* 前一条中间代码执行的环境*/    zval *old_error_reporting;    zend_bool nested;    zval **original_return_value; /* */    zend_class_entry *current_scope;    zend_class_entry *current_called_scope;    zval *current_this;    zval *current_object;    struct _zend_op *call_opline;};

As described in the preceding intermediate code execution, the execution of the intermediate code ultimately calls the final intermediate code program through EX (Opline)->handler (Execute_data tsrmls_cc). In this case, the initialized execture_data in the Execute function in charge of intermediate code execution is passed to the executing program.

The Zend_execute_data structure section field is described below:

    • Opline field: struct _ZEND_OP type, currently executing intermediate code
    • Op_array field: Zend_op_array type, currently executing intermediate code queue
    • FBC field: zend_function type, called function
    • Called_scope field: The Zend_class_entry type, the current calling object scope, the common operation is ex (called_scope) = Z_objce_p (ex (object)), the object just called is assigned to it.
    • symbol_table field: Symbol table, storing local variables. At Execute_data Initial, EX (symbol_table) = EG (active_symbol_table);
    • Prev_execute_data field: Intermediate data executed by the previous intermediate code, used to restore the run environment of operations such as function calls.

When initialized in the Execute function, the Zend_vm_stack_alloc function is called to allocate memory. This is a stack allocation operation, for a section of the PHP code context, it exists in such an allocated space for the placement of intermediate data, and as the top element of the stack. When there are other context transitions (such as function calls), a new element is generated, the previous context is pressed down by the new element, and the element that contains the new context is present as the top element of the stack.

In the Zend_vm_stack_alloc function we can see some optimizations in the PHP kernel. For example, in the allocation, there will be a minimum allocation unit, in the Zend_vm_stack_extend function, the allocation of the smallest unit is zend_vm_stack_page_size ((64 * 1024)-64), This allows the size of the memory fragment to be controlled within a certain range. Another example is to determine whether the stack element is empty, before PHP5.3.1 version (such as 5.3.0) is through the fourth element elelments and top of the location comparison to achieve, and from the PHP5.3.1 version, the struct _zend_vm_stack structure has no fourth element, This is done directly by adding the length of the entire structure to the address of the top at the current address. Two versions of the structure code and comparison code are as follows:

// PHP5.3.0struct _zend_vm_stack {    void **top;    void **end;    zend_vm_stack prev;    void *elements[1];};if (UNEXPECTED(EG(argument_stack)->top == EG(argument_stack)->elements)) {}//  PHP5.3.1struct _zend_vm_stack {    void **top;    void **end;    zend_vm_stack prev;};if (UNEXPECTED(EG(argument_stack)->top == ZEND_VM_STACK_ELEMETS(EG(argument_stack)))) {}#define ZEND_VM_STACK_ELEMETS(stack) ((void**)(((char*)(stack)) + ZEND_MM_ALIGNED_SIZE(sizeof(struct _zend_vm_stack))))

When a context environment ends its life cycle, what if this memory is recycled? Or in the case of a function, each function will have a function return, even if it is not defined in the implementation of the function, it will return a null by default. As an example of the Zend_return_spec_const_handler implementation, a function zend_leave_helper_spec is called at the end of the function's return.

In the Zend_leave_helper_spec function, there are several key points for function handling during execution:

    • Context switching: The key code here is: EG (current_execute_data) = EX (Prev_execute_data);. EX (Prev_execute_data) is used to preserve the context before the current function call, thus achieving the purpose of recovery and switchover.
    • Release of memory space occupied by the current context: The key code here is: Zend_vm_stack_free (Execute_data tsrmls_cc);. The implementation of the Zend_vm_stack_free function exists in the Zend/zend_execute.h file, which is the function of releasing the memory occupied by the stack element.
    • Return to the previous middle Code execution path: The key code here is: Zend_vm_leave ();. We know from the beginning of the zend_vm_execute.h file that the effect of the Zend_vm_leave macro is to return 3. In the while loop that executes the intermediate code, when ret=3 is executed, the execution process resumes the previous context and continues execution.

86-Execution of intermediate code opcode

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.