PHPカーネル学習チュートリアルのphp opcodeカーネル実現

12133 ワード

opcodeはコンピュータ命令の一部であり、実行する操作を指定するために使用され、命令のフォーマットと仕様はプロセッサの命令仕様によって指定される.命令そのもの以外にも通常命令に必要なオペランドがあり、明示的なオペランドを必要としない命令もあるかもしれません.これらのオペランドは、レジスタの値、スタックの値、ブロックメモリの値、IOポートの値などです.
通常opcodeには、バイトコード(byte codes)という別の呼び方があります.例えばJava仮想マシン(JVM)である.NETの共通中間言語(CIL:Common Intermeditate Language)など.
1.Opcodeの概要
opcodeはコンピュータ命令の一部であり、実行する操作を指定するために使用され、命令のフォーマットと仕様はプロセッサの命令仕様によって指定される.命令そのもの以外にも通常命令に必要なオペランドがあり、明示的なオペランドを必要としない命令もあるかもしれません.これらのオペランドは、レジスタの値、スタックの値、ブロックメモリの値、IOポートの値などです.
通常opcodeには、バイトコード(byte codes)という別の呼び方があります.例えばJava仮想マシン(JVM)である.NETの共通中間言語(CIL:Common Intermeditate Language)など
PHPのopcodeは前述の後者に属し,PHPはZend仮想マシン(ZendVM)の上に構築されている.PHPのopcodeはZend仮想マシンにおける命令(Zendの中間コードに基づく)である.
Relevant Link:
http://www.luocong.com/learningopcode/doc/1._%E4%BB%80%E4%B9%88%E6%98%AFOpCode%EF%BC%9F.htm
2.PHP中のOpcode
0 x 1:データ構造
PHP実装内部では,opcodeは以下のような構造体で表される.
\php-5.6.17\Zend\zend_compile.h

struct _zend_op 
{
opcode_handler_t handler; //    opcode        
znode_op op1; // opcode       
znode_op op2; // opcode       
znode_op result;
ulong extended_value;
uint lineno;
zend_uchar opcode; // opcode  
zend_uchar op1_type;
zend_uchar op2_type;
zend_uchar result_type;
}; 

CPUの命令と同様に、命令を示すopcodeフィールドと、このopcodeが操作する操作数があります.PHPはアセンブリほど下層ではありません.スクリプトが実際に実行されるときには、他の情報が必要になる可能性があります.extended_valueフィールドには、このような情報が保存され、resultドメインは、命令の実行が完了した結果を保存します.
たとえば、コンパイラがprint文に遭遇したときにコンパイルする関数です.
\php-5.6.17\Zend\zend_compile.c

void zend_do_print(znode *result, const znode *arg TSRMLS_DC) /* {{{ */
{ 
//     zend_op 
zend_op *opline = get_next_op(CG(active_op_array) TSRMLS_CC);

//    zend_op             (IS_TMP_VAR),  print            ,      
opline->result_type = IS_TMP_VAR;
//         
opline->result.var = get_temporary_variable(CG(active_op_array));
//  opcode ZEND_PRINT
opline->opcode = ZEND_PRINT;
//             opcode       
SET_NODE(opline->op1, arg);
SET_UNUSED(opline->op2);
GET_NODE(result, opline->result);
}

0 x 2:opcodeタイプ:zend_op->zend_uchar opcode
アセンブリ言語の概念に比べて、各opcodeは1つのタイプに対応し、このopcpdeの「操作命令」を示し、opcodeのタイプはzend_である.uchar,zend_ucharは実際にはunsigned charであり、このフィールドに保存されている整形値はopの番号であり、異なるopタイプを区別するために使用され、opcodeの取得可能な値はマクロとして定義されている.
/Zend/zend_vm_opcodes.h

#define ZEND_NOP 0
#define ZEND_ADD 1
#define ZEND_SUB 2
#define ZEND_MUL 3
#define ZEND_DIV 4
#define ZEND_MOD 5
#define ZEND_SL 6
#define ZEND_SR 7
#define ZEND_CONCAT 8
#define ZEND_BW_OR 9
#define ZEND_BW_AND 10
#define ZEND_BW_XOR 11
#define ZEND_BW_NOT 12
#define ZEND_BOOL_NOT 13
#define ZEND_BOOL_XOR 14
#define ZEND_IS_IDENTICAL 15
#define ZEND_IS_NOT_IDENTICAL 16
#define ZEND_IS_EQUAL 17
#define ZEND_IS_NOT_EQUAL 18
#define ZEND_IS_SMALLER 19
#define ZEND_IS_SMALLER_OR_EQUAL 20
#define ZEND_CAST 21
#define ZEND_QM_ASSIGN 22
#define ZEND_ASSIGN_ADD 23
#define ZEND_ASSIGN_SUB 24
#define ZEND_ASSIGN_MUL 25
#define ZEND_ASSIGN_DIV 26
#define ZEND_ASSIGN_MOD 27
#define ZEND_ASSIGN_SL 28
#define ZEND_ASSIGN_SR 29
#define ZEND_ASSIGN_CONCAT 30
#define ZEND_ASSIGN_BW_OR 31
#define ZEND_ASSIGN_BW_AND 32
#define ZEND_ASSIGN_BW_XOR 33
#define ZEND_PRE_INC 34
#define ZEND_PRE_DEC 35
#define ZEND_POST_INC 36
#define ZEND_POST_DEC 37
#define ZEND_ASSIGN 38
#define ZEND_ASSIGN_REF 39
#define ZEND_ECHO 40
#define ZEND_PRINT 41
#define ZEND_JMP 42
#define ZEND_JMPZ 43
#define ZEND_JMPNZ 44
#define ZEND_JMPZNZ 45
#define ZEND_JMPZ_EX 46
#define ZEND_JMPNZ_EX 47
#define ZEND_CASE 48
#define ZEND_SWITCH_FREE 49
#define ZEND_BRK 50
#define ZEND_CONT 51
#define ZEND_BOOL 52
#define ZEND_INIT_STRING 53
#define ZEND_ADD_CHAR 54
#define ZEND_ADD_STRING 55
#define ZEND_ADD_VAR 56
#define ZEND_BEGIN_SILENCE 57
#define ZEND_END_SILENCE 58
#define ZEND_INIT_FCALL_BY_NAME 59
#define ZEND_DO_FCALL 60
#define ZEND_DO_FCALL_BY_NAME 61
#define ZEND_RETURN 62
#define ZEND_RECV 63
#define ZEND_RECV_INIT 64
#define ZEND_SEND_VAL 65
#define ZEND_SEND_VAR 66
#define ZEND_SEND_REF 67
#define ZEND_NEW 68
#define ZEND_INIT_NS_FCALL_BY_NAME 69
#define ZEND_FREE 70
#define ZEND_INIT_ARRAY 71
#define ZEND_ADD_ARRAY_ELEMENT 72
#define ZEND_INCLUDE_OR_EVAL 73
#define ZEND_UNSET_VAR 74
#define ZEND_UNSET_DIM 75
#define ZEND_UNSET_OBJ 76
#define ZEND_FE_RESET 77
#define ZEND_FE_FETCH 78
#define ZEND_EXIT 79
#define ZEND_FETCH_R 80
#define ZEND_FETCH_DIM_R 81
#define ZEND_FETCH_OBJ_R 82
#define ZEND_FETCH_W 83
#define ZEND_FETCH_DIM_W 84
#define ZEND_FETCH_OBJ_W 85
#define ZEND_FETCH_RW 86
#define ZEND_FETCH_DIM_RW 87
#define ZEND_FETCH_OBJ_RW 88
#define ZEND_FETCH_IS 89
#define ZEND_FETCH_DIM_IS 90
#define ZEND_FETCH_OBJ_IS 91
#define ZEND_FETCH_FUNC_ARG 92
#define ZEND_FETCH_DIM_FUNC_ARG 93
#define ZEND_FETCH_OBJ_FUNC_ARG 94
#define ZEND_FETCH_UNSET 95
#define ZEND_FETCH_DIM_UNSET 96
#define ZEND_FETCH_OBJ_UNSET 97
#define ZEND_FETCH_DIM_TMP_VAR 98
#define ZEND_FETCH_CONSTANT 99
#define ZEND_GOTO 100
#define ZEND_EXT_STMT 101
#define ZEND_EXT_FCALL_BEGIN 102
#define ZEND_EXT_FCALL_END 103
#define ZEND_EXT_NOP 104
#define ZEND_TICKS 105
#define ZEND_SEND_VAR_NO_REF 106
#define ZEND_CATCH 107
#define ZEND_THROW 108
#define ZEND_FETCH_CLASS 109
#define ZEND_CLONE 110
#define ZEND_RETURN_BY_REF 111
#define ZEND_INIT_METHOD_CALL 112
#define ZEND_INIT_STATIC_METHOD_CALL 113
#define ZEND_ISSET_ISEMPTY_VAR 114
#define ZEND_ISSET_ISEMPTY_DIM_OBJ 115
#define ZEND_PRE_INC_OBJ 132
#define ZEND_PRE_DEC_OBJ 133
#define ZEND_POST_INC_OBJ 134
#define ZEND_POST_DEC_OBJ 135
#define ZEND_ASSIGN_OBJ 136
#define ZEND_INSTANCEOF 138
#define ZEND_DECLARE_CLASS 139
#define ZEND_DECLARE_INHERITED_CLASS 140
#define ZEND_DECLARE_FUNCTION 141
#define ZEND_RAISE_ABSTRACT_ERROR 142
#define ZEND_DECLARE_CONST 143
#define ZEND_ADD_INTERFACE 144
#define ZEND_DECLARE_INHERITED_CLASS_DELAYED 145
#define ZEND_VERIFY_ABSTRACT_CLASS 146
#define ZEND_ASSIGN_DIM 147
#define ZEND_ISSET_ISEMPTY_PROP_OBJ 148
#define ZEND_HANDLE_EXCEPTION 149
#define ZEND_USER_OPCODE 150
#define ZEND_JMP_SET 152
#define ZEND_DECLARE_LAMBDA_FUNCTION 153
#define ZEND_ADD_TRAIT 154
#define ZEND_BIND_TRAITS 155
#define ZEND_SEPARATE 156
#define ZEND_QM_ASSIGN_VAR 157
#define ZEND_JMP_SET_VAR 158
#define ZEND_DISCARD_EXCEPTION 159
#define ZEND_YIELD 160
#define ZEND_GENERATOR_RETURN 161
#define ZEND_FAST_CALL 162
#define ZEND_FAST_RET 163
#define ZEND_RECV_VARIADIC 164
#define ZEND_SEND_UNPACK 165
#define ZEND_POW 166
#define ZEND_ASSIGN_POW 167 

0 x 3:opcode実行ハンドル:zend_op->handler
opの実行ハンドル、そのタイプはopcode_handler_t
typedef int (ZEND_FASTCALL *opcode_handler_t) (ZEND_OPCODE_HANDLER_ARGS); この関数ポインタはopの実行方式を定義し、各opcodeフィールドは$a=1の場合のhandlerの種類に対応する.このようなコード生成のopは,オペランドがconstとcvであり,最後にhandlerが関数ZEND_であると決定できる.ASSIGN_SPEC_CV_CONST_HANDLER
/Zend/zend_vm_execute.h

void zend_init_opcodes_handlers(void)
{
static const opcode_handler_t labels[] = {
..
ZEND_ASSIGN_SPEC_CV_CONST_HANDLER,
..
}
}

0 x 4:opcpdeオペランドznode
オペランドセグメントは_zend_opタイプの重要な部分です.op 1,op 2,resultの3つのオペランドはznodeタイプとして定義されています.
\php-5.6.17\Zend\zend_compile.h

typedef struct _znode { /* used only during compilation */
/*
  int       znode      
#define IS_CONST (1<<0) //    ,  $a = 123; $b = "hello";      OP ,123 "hello"            
#define IS_TMP_VAR (1<<1) //      ,          ~   ,    OP              ,            ,                zval,          
#define IS_VAR (1<<2) //        , $    
#define IS_UNUSED (1<<3) // Unused variable 
#define IS_CV (1<<4) // Compiled variable,            ,     PHP      (  5.1)    ,CV    compiled variable,       ,             ,           ,                      ,          ,           ,                 。         !    ,    $a=123;$b="hello"    ,$a $b          !0 !1, 0 1        ,               
*/
int op_type;
/*
         ,  op_type   ,u     
1. op_type=IS_CONST   ,u  constant           zval  
2.   $a=123 ,123      ,u  constant   IS_LONG   zval,  lval 123 
*/
union {
znode_op op;
zval constant; /* replaced by literal/zv */
zend_op_array *op_array;
zend_ast *ast;
} u;
zend_uint EA; /* extended attributes */
} znode; 

0 x 5:opcodeコンパイル後配列op_array
zend_でdo_print関数の最初の行は、次の行のコードに気づきました.

zend_op *opline = get_next_op(CG(active_op_array) TSRMLS_CC); 

PHPスクリプトコードがコンパイルされた後に生成されたopcodeはop_に保存されるarrayでは、その内部記憶の構造は以下の通りである.
\php-5.6.17\Zend\zend_compile.h

struct _zend_op_array 
{
/* Common elements */
zend_uchar type;
const char *function_name; //            ,          
zend_class_entry *scope;
zend_uint fn_flags;
union _zend_function *prototype;
zend_uint num_args;
zend_uint required_num_args;
zend_arg_info *arg_info;
/* END of common elements */
zend_uint *refcount;
zend_op *opcodes; // opcode  
zend_uint last;
zend_compiled_variable *vars;
int last_var;
zend_uint T;
zend_uint nested_calls;
zend_uint used_stack;
zend_brk_cont_element *brk_cont_array;
int last_brk_cont;
zend_try_catch_element *try_catch_array;
int last_try_catch;
zend_bool has_finally_block;
/* static variables support */
HashTable *static_variables;
zend_uint this_var;
const char *filename;
zend_uint line_start;
zend_uint line_end;
const char *doc_comment;
zend_uint doc_comment_len;
zend_uint early_binding; /* the linked list of delayed declarations */
zend_literal *literals;
int last_literal;
void **run_time_cache;
int last_cache_slot;
void *reserved[ZEND_MAX_RESERVED_RESOURCES];
}; 

PHPスクリプトコード全体がコンパイルされたopcodesはここに保存され、実行時に次のexecute関数で実行されます.

ZEND_API void execute(zend_op_array *op_array TSRMLS_DC)
{
// ...     op_array  opcode      op_array  opcode
}

各opcodeにはopcodeが1つありますhandler_tの関数ポインタフィールド、このopcodeを実行するために使用され、PHPはopcodeの処理を行う3つの方法がある.
1.CALL:PHPはデフォルトでCALLを使用する方式、つまり関数呼び出しの方式2.SWITCH:opcode実行はPHPプログラム毎に頻繁に行う操作であるため、SWITCHまたはGOTO方式で配布することができる.GOTO:通常GOTOの方が効率的ですが、効率が上がるかどうかはCPUによって異なりますが、実際には/zend/zend_language_parser.cはZendのopcode翻訳解釈実行過程であり、call、switch、gotoの3種類のopcode実行方式が含まれている.
これがPHPがなぜ解釈型言語のカーネル原理と呼ばれているのか、PHPはLex語法解析を完了した後、文法解析すなわち生成式を生成する際に、直接call、switch、gotoの方式でzend apiを呼び出して解釈実行しても
Relevant Link:

http://www.nowamagic.net/librarys/veda/detail/1325
http://php.net/manual/zh/internals2.opcodes.list.php
http://www.nowamagic.net/librarys/veda/detail/1543
http://www.nowamagic.net/librarys/veda/detail/1324
http://www.nowamagic.net/librarys/veda/detail/1543 
http://www.laruence.com/2008/06/18/221.html
http://www.php-internals.com/book/?p=chapt02/02-03-02-opcode 

3.opcode翻訳実行(即時解釈実行)
Relevant Link:

http://www.php-internals.com/book/?p=chapt02/02-03-03-from-opcode-to-handler

以上、ここで紹介したPHPカーネル学習チュートリアルのphp opcodeカーネル実装に関する知識は、皆さんの役に立つことを願っています.