ホームページ >バックエンド開発 >PHPチュートリアル >PHP 構文シュガーから Zend VM エンジンを分析する

PHP 構文シュガーから Zend VM エンジンを分析する

巴扎黑
巴扎黑オリジナル
2016-11-21 09:46:141150ブラウズ

## 1.通常、このように書くことができます。 PHP 7 の新しい追加として: ($a) ? $a : 1;

?: と ?? 混乱しやすい場合は、これらを使用しないことをお勧めします。メンテナンスが簡単です。

この記事の目的は、構文シュガーから始めて、Zend VM の解析原理について説明することです。

## 2.

分析された PHP ソース コード ブランチ =>remotes/origin/PHP-5.6.14。vld を介してオペコードを表示する方法については、以前に書いたこの記事を参照してください:
コンパイル済み変数: !0 = $a, !1 = $b
行 ----------- ------------------------ ---------
2 0 E > ASSIGN
# 0 行目; : 2- 4; sop: 0; eop: 4; out1: -2
パス #1: 0,

vim ~~~.bash
834 › |› expr '?' ':' { zend_do_jmp_set(&$1, &$2, &$3 TSRMLS_CC) }
835 › › › expr { zend_do_jmp_set_else(&$$, &$5, &$2, &$3 TSRMLS_CC) }
~~~

必要に応じて、自分で実行して、 ?: の糖衣構文を再定義することもできます。 BNF 文法規則に従い、Bison 分析を使用します。興味がある場合は、関連する知識を Google で調べてさらに学習してください。

vld のオペコードから、zend_do_jmp_set_else が実行され、コードが Zend/zend_compile.c にあることがわかります:

~~~.java
void zend_do_jmp_set_else(znode *result, const znode *false_value, const znode * jmp_token, const znode * Colon_token TSRMLS_DC)
{
› zend_op *opline = get_next_op(CG(active_op_array) TSRMLS_CC);

› SET_NODE(opline->result, Colon_token);
› if (colon_token->op_type = = IS_TMP_ VAR) {
› › if (false_value->op_type == IS_VAR || false_value->op_type == IS_CV) {
› › › CG(active_op_array)->opcodes[jmp_token->u.op. opline_num].opcode = ZEND_JMP_SET_VAR ;
› › CG(active_op_array)->opcodes[jmp_token->u.op.opline_num].result_type =
› › › opline->opcode = ZEND_QM_ASSIGN_VAR; opline->result_type = IS_VAR;
› › } else {
› › › opline->opcode = ZEND_QM_ASSIGN; }
› } else {
› › opline->opcode = ZEND_QM_ASSIGN_VAR;
› opline->extended_value = 0;
> SET_NODE(opline->op2);

> CG(active_op_array)- >opcodes[jmp_token->u.op.opline_num].op2.opline_num = get_next_op_number(CG(active_op_array))

› DEC_BPC(CG(active_op_array));
~~~

## 3.

重要な 2 つのオペコードは ZEND_JMP_SET_VAR と ZEND_QM_ASSIGN_VAR です。コードの読み取りを続けるにはどうすればよいですか? PHP のオペコードについて話しましょう。

PHP5.6 には 167 のオペコードがあり、167 の異なる計算操作を実行できます。公式ドキュメントはここにあります。 _zend_op 構造体は、オペコードを表すために内部的に使用されます。 115 › znode_op の結果
116 › ulong 拡張値
118 › zend_uchar op1_type
121 › zend_uchar 結果タイプ;

PHP 7.0 は若干異なりますが、主な違いは次のとおりです。 64 ビット システムの場合、uint は uint32_t に置き換えられ、バイト数が明示的に指定されます。

オペコードは、2 つのオペランド (op1、op2) のみを受け入れ、演算 (加算、減算、乗算、除算などのハンドラー) を実行し、結果 (result) を返す計算機と考えることができます。そして、オーバーフロー状況 (extended_value) のちょっとした算術処理を行います。

Zend の VM は、処理関数のアドレスを指すハンドラー (関数ポインター) を使用して、各オペコードに対してまったく同じように動作します。これは、opcode の実行に対応するコードを含む C 関数であり、op1 と op2 をパラメータとして使用し、実行が完了すると結果 (result) が返され、場合によっては情報 (extended_value) が返されます。添付。

この例ではオペランド ZEND_JMP_SET_VAR を使用して説明します。 vim Zend/zend_vm_def.h +4995

4942 ZEND_VM_HANDLER(158, ZEND_JMP_SET_VAR, CONST|TMP|VAR|CV, ANY)
4943 {
4944 › USE_OPLINE
4945 › zend_free_op free_op1;
4946 › zval *value, *ret; 4948 › SAVE_OPLINE(); 4950
4951 › if (i_zend_is_true(value)); 952 › › if ( OP1_TYPE == IS_VAR || OP1_TYPE == IS_CV) {
    4953 › › › Z_ADDREF_P(値); 
4954 › › › EX_T(opline->result.var).var.ptr = 値; 
4955 › › › EX_T(opline->result.var).var.ptr_ptr = &EX_T(opline->result.var).var.ptr; 
4956 › › } else {
4957 › › › ALLOC_ZVAL(ret); 
4958 › › › INIT_PZVAL_COPY(ret, value); 
4959 › › › EX_T(opline->result.var).var.ptr = ret; 
4960 › › › EX_T(opline->result.var).var.ptr_ptr = &EX_T(opline->result.var).var.ptr; 
4961 › › › if (!IS_OP1_TMP_FREE()) {
4962 › › › › zval_copy_ctor(EX_T(opline->result.var).var.ptr); 
4963 › › › }
4964 › › }
4965 › › FREE_OP1_IF_VAR(); 
4966 #if DEBUG_ZEND>=2
4967 › › printf("Conditional jmp to %dn", opline->op2.opline_num); 
4968 #endif
4969 › › ZEND_VM_JMP(opline->op2.jmp_addr); 
4970 › }
4971
4972 › FREE_OP1(); 
4973 › CHECK_EXCEPTION(); 
4974 › ZEND_VM_NEXT_OPCODE(); 
4975 }

i_zend_is_true は、操作数が true であるかどうかを判断します。したがって、ZEND_JMP_SET_VAR は条件値であり、相信大家全員が明白に、以下の説明を参照できます。编译のCのファイル、唯一のモジュールであり、具体的に評価できるファイルは `zend_vm_execute.h` (このファイルには 45000 の多行数が含まれます)、手動生成ではなく、`zend_vm_gen.php` による PHP 解析です。 `zend_vm_def.h` の後に生成 (意図的に、先のタンパク質も PHP パッケージなしでこのスクリプトが来ていますか?)、これは後の期の成果物であることを確認し、php の初期バージョンではこれは使用できません。 、異なるパラメーター `CONST|TMP|VAR|CV' に基づいて異なる種類が生成されますが、機能一致するハンドラー関数:

static int ZEND_FASTCALL ZEND_JMP_SET_VAR_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
static int ZEND_FASTCALL ZEND_JMP_SET_VAR_SPEC_TMP_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
static int ZEND_FASTCALL ZEND_JMP_SET_VAR_SPEC_VAR_HANDLER( ZEND_OPCODE_HANDLER_ARGS)
static int ZEND_FASTCALL ZEND_JMP_SET_VAR_SPEC_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS)

これは、ハンドラーを決定し、実行期間のパフォーマンスを向上させるために行われます。実行期間パラメータの種類の選択に応じて実行することもできますが、パフォーマンスは異なりますもちろん、これを行うと、(不要に見える) いくつかのフレームコードが生成されることもありますが、C のエディターはさらに処理を強化します。 Zend/README.ZEND_VM` には詳細な説明があります。

## 4.

ここに来て、オペコードとハンドラーの対応を知っています。しかし、全体的には、法解析であり、解析後のすべてのオペコードは怎么串通来的呢?

语法解析的细节不说了解析,过後,说链表可能更正确),上面代码我们可看到,各ハンドラー実行完了その後、都市を使用して ZEND_VM_NEXT_OPCODE()、次のオペコードを取り出し、最後に退出するまで実行し、循環の代コード vim Zend/zend_vm_execute.h +337:

~~~.java
ZEND_API voidexecute_ex(zend_execute_data *execute_data)
{
› DCL_OPLINE
› zend_booloriginal_in_execution; 



› original_in_execution = EG(in_execution); 
› EG(実行中) = 1; 

› if (0) {
zend_vm_enter:
› › execute_data = i_create_execute_data_from_op_array(EG(active_op_array), 1 TSRMLS_CC); 
› }

› LOAD_REGS(); 
› LOAD_OPLINE(); 

› while (1) {
› int ret; 
#ifdef ZEND_WIN32
› › if (EG(timed_out)) {
› › › zend_timeout(0); 
› › }
#endif

› › if ((ret = OPLINE->handler(execute_data TSRMLS_CC)) > 0) {
› › › switch (ret) {
› › › › case 1:
› › › › › EG(実行中) = オリジナル実行中; 
› › › › › 戻る; 
› › › › ケース 2:
› › › › › goto zend_vm_enter; 
› › › › › 休憩。 
› › › › ケース 3:
› › › › › execute_data = EG(current_execute_data); 
› › › › › 休憩。 
› › › › デフォルト:
› › › › › ブレーク; 
› › › }
› › }

› }
› zend_error_noreturn(E_ERROR, "発生するはずのないメインループの終わりに到着しました"); 
}
~~~

宏定义, vim Zend/zend_execute.c +1772

1772 #define ZEND_VM_NEXT_OPCODE()
1773 › CHECK_SYMBOL_TABLES()
1774 › ZEND_VM_INC_OPCODE();  
1775 › ZEND_VM_CONTINUE()

329 #define ZEND_VM_CONTINUE() return 0
330 #define ZEND_VM_RETURN() return 1
331 #define Z END_VM_ENTER() return 2
332 #define ZEND_VM_LEAVE() return 3

while是一死循環,実行一ハンドラー関数数,除别情况,多数ハンドラー関数数末尾都调用 ZEND_VM_NEXT_OPCODE() -> ZEND_VM_CONTINUE()、0 を返す、継続循環。

> 注:yield 処理は例外で、1 を返し、直接循環を返します。その後、有機的な会議で個別に Yield 分析を行います。理解してください。以下では、原理の分析に基づいて、PHP の進化を再度確認します。

## 5. PHP の進化に関する注意事項

### 5.1 echo 出力

$foo = 'foo'; 
$bar = 'バー'; 
$foo をエコーし​​ます。 $バー; 

vld 查看オペコード:

オペ数: 5
コンパイル済み変数: !0 = $foo, !1 = $bar
line #* E I O op fetch ext return オペランド
----------- -------------------------------------------------- ------------------------
2 0 E >   ASSIGN !0, 'foo'
3 Assion:4; out1:-2
Zend_concat $ aと$ bの値を接続し、それを一時変数〜2に保存し、エコーアウトします。このプロセスには、一時変数用のメモリの割り当てが含まれます。一時変数は使用後に解放する必要があり、スプライシング プロセスを実行するにはスプライシング関数を呼び出す必要があります。

これを次のように変更すると:

$foo = 'foo';
$bar = 'bar';
対応するオペコード:

演算の数: 5
コンパイル済み変数: !0 = $foo, !1 = $bar
line #* E I O op fetch ext return オペランド
---------------------- -------------------------------------------------- - ------------
2 0 E > ASSIGN 3 1 ASSIGN !5 4 4; out1: -2
path #1: 0,

メモリの割り当てやスプライシング関数の実行は不要ですが、より効率的ですか?スプライシングのプロセスを理解したい場合は、この記事の内容に従って ZEND_CONCAT オペコードに対応するハンドラーを検索してください。

### 5.2 define() と const

const キーワードは 5.3 から導入されました。これは C 言語の `#define` と似た意味を持ちます。

*define() は関数呼び出しであり、関数呼び出しのオーバーヘッドがあります。
* const はオペコードを直接生成するキーワードであり、コンパイル中に決定でき、実行中に動的に割り当てる必要はありません。

const の値は無効であり、実行時に変更できないため、コンパイル中に決定され、数値型に制限がある C 言語の #define に似ています。コードを直接見て、オプコードを比較します:

define例:

<?php

コンパイルされたvars:none e line#* e i o op fetch ext returnオペラ
---------------------------------------- ------------------------ ------------------------ --------------------
2 0 e> ?php
const FOO = 'foo';echo FOO;

const オペコード:

演算数: 4
コンパイル済み変数: なし
line #* E I O op fetch ext return オペランド
------------------ - ------------------------------------------------- - ---------------
2 0 E > DECLARE_CONST 1 FETCH_CONSTANT 0
4 3 > RETURN 1

### 5.3 動的関数のコスト

& lt;? PHP
FUNCTION FOO () {}
EI O op ---------------------------------- --------
2 0 E > NOP
3 1 Do_fcall 0 'FOO'
4 2 & GT; Return 1

コードの動的呼び出し:

& lt ;? {}
$ a = 'foo';
$ a ();

オペコード:

演算数: 5
コンパイルされた変数: !0 = $a
line #* E I O op fetch ext return オペランド
--- ------------------ -------------------------------- ----------------- ---------------
2 0 E >
3 1 ASSIGN !0, 'foo'
4 2 INIT_FCALL_BY_NAME 0
5 4 > RETURN AME Things が作成したコードですが、コードが長すぎるため、ここには記載しません。動的機能は便利ですが、パフォーマンスが確実に犠牲になるため、使用する前に長所と短所のバランスを取る必要があります。 4 ### 5.4 カテゴリ内の遅延ステートメントのコスト: コンパイル済み変数: なし
LINE #* E I O OP FETCH EXT RETURN オペランド
--------------------- -------------------------------------------------------- -------------- T 2 0 e & gt;
3 1 NOP
2 NOP
4 3 & gt; ステートメントの順序を変更します:

& lt ;?
class foo extends bar {}
class bar {}

対応する OPCODE:

op の数: 4
コンパイルされた変数: none
line #* E I O op fetch fetch ext return オペランド
--------- ------------------ -------------------------------- -------- --------
2 0 E > FETCH_CLASS1 DECLARE_INHERITED_CLASS '%00foo%2FUsers%2Fqisen%2Ftmp%2Fvld.php0x103d58020', 'foo'
3 2 NOP
4 3 > 注意しないと、動的言語はクラス宣言を延期する可能性があります。この罠を踏んでください。

したがって、Zend VM の原理を理解した後は、動的でない機能の使用にもっと注意を払う必要があり、それが必要不可欠な場合は使用しないでください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。