PHPの範囲演算子の再実装

Christopher Nolan
リリース: 2025-02-15 09:36:12
オリジナル
264 人が閲覧しました

SitePoint素晴らしい記事の推奨事項:PHPレンジオペレーターの実装の改善

この記事は、著者の承認とともにSitePointで再現されています。次のコンテンツはThomas Puntによって作成され、PHPレンジオペレーターの改善された実装方法を紹介します。 PHPの内部に興味があり、お気に入りのプログラミング言語に機能を追加する場合は、学ぶのに良い時期です!

この記事は、読者がソースコードからPHPを構築できると想定しています。そうでない場合は、まずPHP内部メカニズムの本の「Building PHP」の章を読んでください。

Re-Implementing the Range Operator in PHP


前の記事(ヒント:読んでいることを確認してください)では、PHPに範囲オペレーターを実装する方法を示しました。ただし、初期の実装がめったに最適ではないため、この記事は以前の実装を改善する方法を探ることを目的としています。

この記事を校正してくれたNikita Popovに再び感謝します!

キーポイント

  • Thomas Puntは、PHPの範囲演算子を再インプレクトし、Zend Virtual Machineから計算ロジックを移動し、一定の表現のコンテキストで範囲演算子を使用できるようにします。
  • この再実装は、コンパイル時間(リテラルオペランドの場合)または実行時(動的オペランドの場合)で計算できます。これにより、Opcacheユーザーに少しの利益がもたらされるだけでなく、一定の表現機能をレンジオペレーターで使用できます。
  • 再実装プロセスには、レクサー、パーサー、コンピレーション段階、Zend仮想マシンの更新が含まれます。語彙アナライザーの実装は同じままですが、パーサーの実装は前の部分と同じです。コンパイルフェーズでは、Zend/zend_compile.cファイルを更新する必要はありません。これは、バイナリ操作を処理するために必要なロジックが既に含まれているためです。 Zend Virtual Machineは、実行時にZend_Range OpCodeの実行を処理するために更新されました。
  • このシリーズの第3部では、PUNTはこのオペレーターを過負荷する方法を説明することにより、この実装を構築する予定です。これにより、オブジェクトをオペランドとして使用し、文字列に適切なサポートを追加できます。
  • 以前の実装の欠点

最初の実装により、Zend_Rangeオペコードの実行時に純粋に実行時に計算を強制するZend Virtual Machineに範囲演算子のすべてのロジックが配置されます。これは、文字通りのオペランドの場合、計算をコンパイルするために転送できないことを意味するだけでなく、一部の関数が単に機能しないことも意味します。

この実装では、Zend仮想マシンから範囲演算子ロジックを移動して、コンパイル時間(リテラルオペランド用)またはランタイム(動的オペランド用)で計算を実行できるようにします。これは、Opcacheユーザーに少しの利益をもたらすだけでなく、さらに重要なことに、範囲演算子で一定の発現機能を使用できるようにすることです。

例:

それでは、それ以上のADOなしで、範囲演算子を再実装しましょう。

// 作为常量定义
const AN_ARRAY = 1 |> 100;

// 作为初始属性定义
class A
{
    private $a = 1 |> 2;
}

// 作为可选参数的默认值:
function a($a = 1 |> 2)
{
    //
}
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
字句分析剤を更新

語彙アナライザーの実装はまったく変更されていません。トークンはZend/zend_language_scanner.l(約1200行)に最初に登録されています:

// 作为常量定义
const AN_ARRAY = 1 |> 100;

// 作为初始属性定义
class A
{
    private $a = 1 |> 2;
}

// 作为可选参数的默认值:
function a($a = 1 |> 2)
{
    //
}
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

トークン剤拡張機能は、ext/トークナイザーディレクトリを入力し、tokenizer_data_gen.shファイルを実行することにより、再生成する必要があります。

Parserを更新

パーサーの実装は以前と同じです。繰り返しますが、次の行の最後にT_RANGEトークンを追加することにより、オペレーターの優先度と結合を宣言します。

<st_in_scripting>"|>" {
</st_in_scripting>    RETURN_TOKEN(T_RANGE);
}
ログイン後にコピー
ログイン後にコピー
その後、expr_without_variableの生産ルールを再度更新しますが、今回はセマンティックアクション(ブレース内のコード)がわずかに異なります。次のコードで更新します(T_SPACESHIPルール、約930行の下に置いてください):

%token T_RANGE           "|> (T_RANGE)"
ログイン後にコピー
ログイン後にコピー

コンピレーションフェーズを更新します

今回は、バイナリ操作を処理するために必要なロジックが既に含まれているため、Zend/zend_compile.cファイルを更新する必要はありません。したがって、オペレーターをZend_ast_binary_opノードに設定することにより、このロジックを再利用する必要があります。

以下は、zend_compile_binary_op関数の単純化されたバージョンです。

ご覧のとおり、前回作成したzend_compile_range関数に非常に似ています。 2つの重要な違いは、オペコードタイプを取得する方法と、両方のオペランドがリテラルである場合に何が起こるかです。

ZEND_AST_BINARY_OPノードはバイナリ操作を区別するためにこの値を(新しい生産ルールのセマンティックアクションに示す)を保存するため、

opCodeタイプはASTノード(前回のようにハードコードではなく)から取得されます。両方のオペランドがリテラルである場合、zend_try_ct_eval_binary_op関数が呼び出されます。この関数は次のようになります:
%nonassoc T_IS_EQUAL T_IS_NOT_EQUAL T_IS_IDENTICAL T_IS_NOT_IDENTICAL T_SPACESHIP T_RANGE
ログイン後にコピー
ログイン後にコピー

この関数は、zend/zend_opcode.cのget_binary_op関数(ソースコード)からオペコードタイプに従ってコールバックを取得します。これは、Zend_Range OpCodeに合わせてこの関数を次に更新する必要があることを意味します。次のケースステートメントをget_binary_op関数に追加します(約750行):

range_function関数を定義する必要があります。これは、Zend/zend_operators.cファイルで他のすべての演算子を使用します。
    |   expr T_RANGE expr
            { $$ = zend_ast_create_binary_op(ZEND_RANGE, , ); }
ログイン後にコピー

関数プロトタイプには、2つの新しいマクロが含まれています:Zend_apiとZend_fastCall。 Zend_Apiは、共有オブジェクトの拡張機能にコンパイルできるようにすることにより、関数の可視性を制御するために使用されます。 Zend_fastCallは、より効率的な呼び出しコンベンションが使用されることを保証するために使用されます。ここで、最初の2つのパラメーターがスタックの代わりにレジスタに渡されます(32ビットビルドよりもX86の64ビットビルドに関連します)。

関数本体は、前の記事のZend/Zend_Vm_Def.hファイルにあるものと非常に似ています。 handle_exceptionマクロコール(返品障害に置き換えられた;)、zend_vvm_next_opcode_check_exceptionマクロコールなど、VM固有のコンテンツは存在しなくなりました。 VMコードから)。さらに、前述のように、get_opn_zval_ptr pseudo-macro(get_opn_zval_ptr_deref)を使用してVMで参照を処理することを避けます。

もう1つの顕著な違いは、参照が正しく処理されることを確認するために、両方のオペランドにZVAL_DEFEFを適用していることです。これは、以前にVM内の擬似-Macro get_opn_zval_ptr_derefを使用して行われましたが、この関数に転送されました。これは、コンパイルする必要があるために行われません(コンパイル時間処理の場合、両方のオペランドがリテラルである必要があり、参照できないため)が、参照処理について心配することなく、コードベースの他の場所で安全に呼び出される可能性があるため。したがって、ほとんどの演算子関数(パフォーマンスが重要な場合を除く)は、VMオペコード定義ではなく、参照処理を実行します。

最後に、zend/zend_operators.hファイルにrange_functionプロトタイプを追加する必要があります。

// 作为常量定义
const AN_ARRAY = 1 |> 100;

// 作为初始属性定义
class A
{
    private $a = 1 |> 2;
}

// 作为可选参数的默认值:
function a($a = 1 |> 2)
{
    //
}
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
Zend Virtual Machineを更新

実行時にZend_Range OpCodeの実行を処理するには、Zend Virtual Machineをもう一度更新する必要があります。次のコードをZend/zend_vm_def.h(bottom)に入れます:

<st_in_scripting>"|>" {
</st_in_scripting>    RETURN_TOKEN(T_RANGE);
}
ログイン後にコピー
ログイン後にコピー
(繰り返しますが、opcode番号は現在の最高のオペコード番号よりも大きくなければなりません。これは、Zend/zend_vm_opcodes.hファイルの下部に表示されます。)

すべての作業がrange_functionで処理されるため、

今回の定義ははるかに短くなります。この関数を呼び出して、計算された値を保存するために現在のOplineの結果オペランドを渡すだけです。 rience_functionから削除され、次のオペコードへのスキップがZend_vm_next_opcode_check_exceptionに呼び出されてVMで処理されます。さらに、前述のように、get_opn_zval_ptr pseudo-macro(get_opn_zval_ptr_deref)を使用してVMで参照を処理することを避けます。

Zend/zend_vm_gen.phpファイルを実行してVMを再生します。

最後に、美しいプリンターはZend/zend_ast.cファイルを再度更新する必要があります。優先順位のテーブルコメント(約520行)を更新します:

%token T_RANGE           "|> (T_RANGE)"
ログイン後にコピー
ログイン後にコピー

結論

この記事は、計算ロジックがVMから移動された範囲演算子を実装する代替案を示しています。これには、一定の表現のコンテキストで範囲演算子を使用できるという利点があります。
%nonassoc T_IS_EQUAL T_IS_NOT_EQUAL T_IS_IDENTICAL T_IS_NOT_IDENTICAL T_SPACESHIP T_RANGE
ログイン後にコピー
ログイン後にコピー

この一連の記事の第3部は、この実装に基づいて構築され、このオペレーターを過負荷する方法を説明します。これにより、オブジェクトをオペランド(GMPライブラリのオブジェクトや__tostringメソッドを実装するオブジェクトなど)として使用できます。また、文字列に適切なサポートを追加する方法も示します(PHPの現在の範囲関数で見られるものとは異なります)。しかし、今のところ、これがオペレーターをPHPに実装する際に、ZEのより深い側面の良いデモンストレーションであることを願っています。

以上がPHPの範囲演算子の再実装の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

前の記事:同期サイトのWebSockets 次の記事:作曲家プラグインの力で酔っています
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
関連トピック
詳細>
人気のおすすめ
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート