1. Opcode简介2. PHP中的Opcode3. opcode翻译执行(即时解释执行)
オペコードは、実行される演算を指定するコンピュータ命令の一部であり、命令の形式と仕様はプロセッサの命令仕様によって指定されます。 命令自体に加えて、通常は命令に必要なオペランドがあります。一部の命令は明示的なオペランドを必要としない場合があります。 これらのオペランドは、レジスタ内の値、スタック内の値、特定のメモリ内の値、または IO ポート内の値などです。
通常、オペコードにはバイト コードという別の名前があります。 たとえば、Java 仮想マシン (JVM)、.NET の共通中間言語 (CIL: Common Intermediate Language) などです。
PHP のオペコードは、前の紹介の後半に属します。PHP は Zend 仮想マシン ( Zend VM)。 PHP のオペコードは、Zend 仮想マシン内の命令です (Zend の中間コードに基づく)
関連リンク:
http://www.luocong.com/learningopcode/doc/1._%E4%BB%80%E4%B9%88%E6%98%AFOpCode%EF%BC%9F.htm
0x1: データ構造
PHP 実装内部では、オペコードは次の構造を持っています。 body は php-5.6.17Zendzend_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;};
php-5.6.17Zendzend_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);}
アセンブリ言語の概念をそれぞれ比較してくださいopcode すべては型に対応し、opcpde の「操作命令」を示します。opcode の型は zend_uchar で、実際には unsigned char です。このフィールドに保存される整数値は、さまざまな op 型を区別するために使用されます。オペコード 利用可能な値は、macros/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
0x3: オペコード実行ハンドル: zend_op->handler
op の実行ハンドル、その型は opcode_handler_t です
typedef int (ZEND_FASTCALL *opcode_handler_t) (ZEND_OPCODE_HANDLER_ARGS);
この関数ポインタは定義されていますop 実行メソッドの場合、各 opcode フィールドはハンドラーのタイプに対応します。たとえば、$a = 1; の場合、そのコードによって生成される op にはオペランド const と cv があり、最終的にハンドラーは関数 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, .. }}
0x4: opcpde オペランド znode
operand フィールドは _zend_op 型の重要な部分です。3 つのオペランド op1、op2、および result は znode 型 php-5.6.17Zendzend_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;
0x5 : オペコードがコンパイルされた配列 op_array
zend_do_print 関数の最初の行で、次のコード行に気づきました
zend_op *opline = get_next_op(CG(active_op_array) TSRMLS_CC);
PHP スクリプト コードがコンパイルされた後に生成されたオペコードは op_array に保存され、その内部ストレージ構造は次のとおりですphp-5.6 .17Zendzend_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 スクリプト コード全体のコンパイルされたオペコードはここに保存され、実行中に次の実行関数によって実行されます
ZEND_API void execute(zend_op_array *op_array TSRMLS_DC){ // ... 循环执行op_array中的opcode或者执行其他op_array中的opcode}
各オペコードには、opcode_handler_t の関数ポインタ フィールドがあり、これが使用されますオペコードを実行するために、PHP にはオペコードを処理する 3 つの方法があります
1. CALL: PHP默认使用CALL的方式,也就是函数调用的方式2. SWITCH: 由于opcode执行是每个PHP程序频繁需要进行的操作,可以使用SWITCH或者GOTO的方式来分发3. GOTO: 通常GOTO的效率相对会高一些,不过效率是否提高依赖于不同的CPU
http://www.nowamagic.net/librarys/veda/detail/1325http://php.net/manual/zh/internals2.opcodes.list.phphttp://www.nowamagic.net/librarys/veda/detail/1543http://www.nowamagic.net/librarys/veda/detail/1324http://www.nowamagic.net/librarys/veda/detail/1543 http://www.laruence.com/2008/06/18/221.htmlhttp://www.php-internals.com/book/?p=chapt02/02-03-02-opcode
関連リンク:
http://www.php-internals.com/book/?p=chapt02/02-03-03-from-opcode-to-handler