Rumah >pembangunan bahagian belakang >tutorial php >Mimplem semula pengendali pelbagai di PHP

Mimplem semula pengendali pelbagai di PHP

Christopher Nolan
Christopher Nolanasal
2025-02-15 09:36:12213semak imbas

SitePoint Cadangan Artikel Wonderful: Pelaksanaan Operator PHP Range yang lebih baik

Artikel ini diterbitkan semula di Sitepoint dengan kebenaran pengarang. Kandungan berikut ditulis oleh Thomas Punt dan memperkenalkan kaedah pelaksanaan yang lebih baik bagi pengendali pelbagai PHP. Jika anda berminat dengan PHP dalaman dan menambah ciri -ciri ke bahasa pengaturcaraan kegemaran anda, kini adalah masa yang baik untuk belajar!

Artikel ini mengandaikan bahawa pembaca boleh membina PHP dari kod sumber. Jika ini tidak berlaku, sila terlebih dahulu baca "Bangunan PHP" bab Buku Mekanisme Dalaman PHP.

Re-Implementing the Range Operator in PHP


Dalam artikel sebelumnya (Petua: Pastikan anda membacanya), saya menunjukkan cara untuk melaksanakan pengendali pelbagai dalam PHP. Walau bagaimanapun, pelaksanaan awal jarang berlaku, jadi artikel ini bertujuan untuk meneroka bagaimana untuk meningkatkan pelaksanaan sebelumnya.

Terima kasih sekali lagi Nikita Popov untuk membuktikan artikel ini!

mata utama

    Thomas Punt memimpikan pengendali pelbagai dalam PHP, menggerakkan logik pengiraan keluar dari Zend Virtual Machine, yang membolehkan penggunaan pengendali pelbagai dalam konteks ekspresi malar.
  • Pembayaran balik ini boleh dikira pada masa penyusunan (untuk operan harfiah) atau pada masa runtime (untuk operan dinamik). Ini bukan sahaja memberi sedikit manfaat kepada pengguna Opcache, tetapi juga membolehkan fungsi ekspresi berterusan digunakan dengan pengendali pelbagai.
  • Proses reimplementation melibatkan mengemas kini lexer, parser, peringkat penyusunan, dan mesin maya Zend. Pelaksanaan penganalisis leksikal tetap sama, sementara pelaksanaan parser adalah sama dengan bahagian sebelumnya. Fasa penyusunan tidak memerlukan mengemas kini fail ZEND/ZEND_COMPILE.C, kerana ia sudah mengandungi logik yang diperlukan untuk mengendalikan operasi binari. Mesin Virtual Zend telah dikemas kini untuk mengendalikan pelaksanaan opcode Zend_range pada masa runtime.
  • Di bahagian ketiga siri ini, Punt merancang untuk membina pelaksanaan ini dengan menerangkan bagaimana untuk membebankan pengendali ini. Ini akan membolehkan objek digunakan sebagai pengendali dan menambah sokongan yang sesuai kepada rentetan.
Kekurangan pelaksanaan sebelumnya

Pelaksanaan awal meletakkan semua logik pengendali pelbagai dalam mesin maya Zend, yang memaksa pengiraan dilakukan semata -mata semasa runtime ketika melaksanakan opcode Zend_range. Ini bukan sahaja bermakna bahawa untuk operan harfiah, pengiraan tidak boleh dipindahkan untuk menyusun masa, tetapi juga bermakna bahawa beberapa fungsi tidak berfungsi.

Dalam pelaksanaan ini, kami memindahkan logik pengendali pelbagai dari Zend Virtual Machine untuk dapat melakukan pengiraan pada masa penyusunan (untuk operan harfiah) atau runtime (untuk operan dinamik). Ini bukan sahaja memberi sedikit manfaat kepada pengguna Opcache, tetapi lebih penting lagi, membolehkan fungsi ekspresi berterusan digunakan dengan pengendali pelbagai.

Contoh:

<code class="language-php">// 作为常量定义
const AN_ARRAY = 1 |> 100;

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

// 作为可选参数的默认值:
function a($a = 1 |> 2)
{
    //
}</code>
Jadi, tanpa lagi ado, mari kita gunakan semula pengendali pelbagai.

Kemas kini Lexical Analyzer

Pelaksanaan penganalisis leksikal tetap tidak berubah sepenuhnya. Token pertama kali didaftarkan di zend/zend_language_scanner.l (kira -kira 1200 baris):

<code class="language-c"><st_in_scripting>"|>" {
</st_in_scripting>    RETURN_TOKEN(T_RANGE);
}</code>
kemudian mengisytiharkan di zend/zend_language_parser.y (kira -kira 220 baris):

<code class="language-php">// 作为常量定义
const AN_ARRAY = 1 |> 100;

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

// 作为可选参数的默认值:
function a($a = 1 |> 2)
{
    //
}</code>

Pelanjutan tokenizer mesti diperbaharui semula dengan memasukkan direktori ext/tokenizer dan melaksanakan fail tokenizer_data_gen.sh.

Kemas kini Parser

Pelaksanaan parser adalah sama seperti sebelumnya. Sekali lagi kami mengisytiharkan keutamaan dan mengikat pengendali dengan menambah token T_RANGE ke akhir baris berikut:

<code class="language-c"><st_in_scripting>"|>" {
</st_in_scripting>    RETURN_TOKEN(T_RANGE);
}</code>

Kemudian kami mengemas kini peraturan pengeluaran expr_without_variable sekali lagi, tetapi kali ini tindakan semantik (kod di dalam pendakap) akan sedikit berbeza. Kemas kini dengan kod berikut (saya meletakkannya di bawah peraturan T_Spaceship, kira -kira 930 baris):

<code class="language-c">%token T_RANGE           "|> (T_RANGE)"</code>

Kali ini, kami menggunakan fungsi zend_ast_create_binary_op (bukan fungsi zend_ast_create), yang mencipta nod zend_ast_binary_op untuk kami. zend_ast_create_binary_op mengambil nama opcode yang akan digunakan untuk membezakan operasi binari semasa fasa penyusunan.

Oleh kerana kita kini menggunakan semula jenis nod zend_ast_binary_op, tidak perlu menentukan jenis nod zend_ast_range baru seperti sebelumnya dalam fail zend/zend_ast.h.

Kemas kini fasa kompilasi

Kali ini, tidak perlu mengemas kini fail ZEND/ZEND_COMPILE.C, kerana ia sudah mengandungi logik yang diperlukan untuk mengendalikan operasi binari. Oleh itu, kita hanya perlu menggunakan semula logik ini dengan menetapkan pengendali kami ke nod Zend_AST_BINARY_OP.

Berikut adalah versi mudah fungsi zend_compile_binary_op:

<code class="language-c">%nonassoc T_IS_EQUAL T_IS_NOT_EQUAL T_IS_IDENTICAL T_IS_NOT_IDENTICAL T_SPACESHIP T_RANGE</code>

Seperti yang dapat kita lihat, ia sangat mirip dengan fungsi zend_compile_range yang kita buat kali terakhir. Kedua -dua perbezaan penting adalah bagaimana untuk mendapatkan jenis opcode dan apa yang berlaku apabila kedua -dua operan adalah literal.

Jenis Opcode diambil kali ini dari nod AST (bukannya hardcoded sebagai kali terakhir), kerana nod Zend_AST_BINARY_OP menyimpan nilai ini (seperti yang ditunjukkan dalam tindakan semantik peraturan pengeluaran baru) untuk membezakan operasi binari. Apabila kedua -dua operan adalah literal, fungsi zend_try_ct_eval_binary_op dipanggil. Fungsi ini kelihatan seperti ini:

<code class="language-c">    |   expr T_RANGE expr
            { $$ = zend_ast_create_binary_op(ZEND_RANGE, , ); }</code>

Fungsi ini memperoleh panggilan balik dari fungsi get_binary_op (kod sumber) di zend/zend_opcode.c mengikut jenis opcode. Ini bermakna kita perlu mengemas kini fungsi ini bersebelahan untuk sesuai dengan opcode zend_range. Tambahkan pernyataan kes berikut ke fungsi get_binary_op (kira -kira 750 baris):

<code class="language-c">void zend_compile_binary_op(znode *result, zend_ast *ast) /* {{{ */
{
    zend_ast *left_ast = ast->child[0];
    zend_ast *right_ast = ast->child[1];
    uint32_t opcode = ast->attr;

    znode left_node, right_node;
    zend_compile_expr(&left_node, left_ast);
    zend_compile_expr(&right_node, right_ast);

    if (left_node.op_type == IS_CONST && right_node.op_type == IS_CONST) {
        if (zend_try_ct_eval_binary_op(&result->u.constant, opcode,
                &left_node.u.constant, &right_node.u.constant)
        ) {
            result->op_type = IS_CONST;
            zval_ptr_dtor(&left_node.u.constant);
            zval_ptr_dtor(&right_node.u.constant);
            return;
        }
    }

    do {
        // redacted code
        zend_emit_op_tmp(result, opcode, &left_node, &right_node);
    } while (0);
}
/* }}} */</code>

Sekarang kita perlu menentukan fungsi range_function. Ini akan dilakukan dalam fail Zend/Zend_Operators.c dengan semua pengendali lain:

<code class="language-c">static inline zend_bool zend_try_ct_eval_binary_op(zval *result, uint32_t opcode, zval *op1, zval *op2) /* {{{ */
{
    binary_op_type fn = get_binary_op(opcode);

    /* don't evaluate division by zero at compile-time */
    if ((opcode == ZEND_DIV || opcode == ZEND_MOD) &&
        zval_get_long(op2) == 0) {
        return 0;
    } else if ((opcode == ZEND_SL || opcode == ZEND_SR) &&
        zval_get_long(op2)      return 0;
    }

    fn(result, op1, op2);
    return 1;
}
/* }}} */</code>

Prototaip fungsi mengandungi dua makro baru: zend_api dan zend_fastcall. Zend_API digunakan untuk mengawal penglihatan fungsi dengan menjadikannya tersedia untuk disusun menjadi lanjutan objek bersama. Zend_fastcall digunakan untuk memastikan konvensyen panggilan yang lebih cekap digunakan, di mana dua parameter pertama akan diluluskan dalam daftar dan bukannya susunan (lebih relevan untuk membina 64-bit pada x86 daripada untuk membina 32-bit).

Badan fungsi sangat mirip dengan apa yang kita ada dalam fail ZEND/ZEND_VM_DEF.H dalam artikel sebelumnya. Kandungan khusus VM tidak lagi wujud, termasuk panggilan makro handle_excepti dari kod VM). Tambahan pula, seperti yang dinyatakan sebelum ini, kami mengelakkan menggunakan get_opn_zval_ptr pseudo-macro (bukannya get_opn_zval_ptr_deref) untuk memproses rujukan dalam VM.

Satu lagi perbezaan yang ketara ialah kami menggunakan zval_defef untuk kedua -dua operan untuk memastikan rujukan diproses dengan betul. Ini sebelum ini dilakukan menggunakan pseudo-Macro get_opn_zval_ptr_deref di dalam VM, tetapi kini telah dipindahkan ke fungsi ini. Ini tidak dilakukan kerana ia perlu disusun pada (kerana untuk pemprosesan masa kompilasi kedua-dua operan mestilah literals dan mereka tidak boleh dirujuk), tetapi kerana ia membolehkan range_function selamat dipanggil di tempat lain di pangkalan kod, tanpa bimbang tentang pemprosesan rujukan . Oleh itu, kebanyakan fungsi pengendali (kecuali jika prestasi adalah kritikal) melaksanakan pemprosesan rujukan, dan bukannya dalam definisi VM opcode mereka.

Akhirnya, kita perlu menambah prototaip range_function ke fail zend/zend_operators.h:

<code class="language-php">// 作为常量定义
const AN_ARRAY = 1 |> 100;

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

// 作为可选参数的默认值:
function a($a = 1 |> 2)
{
    //
}</code>

Kemas kini Zend Virtual Machine

Sekarang kita perlu mengemas kini mesin Virtual Zend sekali lagi untuk mengendalikan pelaksanaan opcode Zend_range semasa runtime. Masukkan kod berikut di zend/zend_vm_def.h (bawah):

<code class="language-c"><st_in_scripting>"|>" {
</st_in_scripting>    RETURN_TOKEN(T_RANGE);
}</code>

(sekali lagi, nombor opcode mestilah satu lebih besar daripada nombor opcode tertinggi semasa, yang boleh dilihat di bahagian bawah fail zend/zend_vm_opcodes.h.)

Definisi kali ini jauh lebih pendek, kerana semua kerja dikendalikan dalam range_function. Kami hanya perlu memanggil fungsi ini dan lulus dalam hasil operan opline semasa untuk menyimpan nilai yang dikira. Pemeriksaan pengecualian yang dikeluarkan dari range_function dan melangkau ke opcode seterusnya masih diproses dalam VM dengan panggilan ke Zend_VM_NEXT_OPCODE_CHECK_EXCEPTION. Tambahan pula, seperti yang dinyatakan sebelum ini, kami mengelakkan menggunakan get_opn_zval_ptr pseudo-macro (bukannya get_opn_zval_ptr_deref) untuk memproses rujukan dalam VM.

kini menjana semula VM dengan melaksanakan fail ZEND/ZEND_VM_GEN.PHP.

Akhirnya, pencetak yang indah perlu mengemas kini fail zend/zend_ast.c sekali lagi. Kemas kini komen jadual keutamaan (kira -kira 520 baris):

<code class="language-c">%token T_RANGE           "|> (T_RANGE)"</code>

Kemudian, masukkan pernyataan kes dalam fungsi zend_ast_export_ex untuk memproses kod zend_range (kira -kira 1300 baris):

<code class="language-c">%nonassoc T_IS_EQUAL T_IS_NOT_EQUAL T_IS_IDENTICAL T_IS_NOT_IDENTICAL T_SPACESHIP T_RANGE</code>

Kesimpulan

Artikel ini menunjukkan alternatif untuk melaksanakan pengendali pelbagai, di mana logik pengiraan telah dipindahkan dari VM. Ini mempunyai kelebihan untuk dapat menggunakan pengendali pelbagai dalam konteks ekspresi yang berterusan.

Bahagian ketiga siri artikel ini akan dibina di atas pelaksanaan ini, menjelaskan cara membebankan pengendali ini. Ini akan membolehkan objek digunakan sebagai operan (seperti objek dari perpustakaan GMP atau objek yang melaksanakan kaedah __toString). Ia juga akan menunjukkan cara menambah yang sesuai sokongan kepada rentetan (tidak seperti yang dilihat dalam fungsi pelbagai semasa PHP). Tetapi buat masa ini, saya harap ini adalah demonstrasi yang baik dari beberapa aspek yang lebih mendalam dari ZE ketika melaksanakan pengendali ke PHP.

Atas ialah kandungan terperinci Mimplem semula pengendali pelbagai di PHP. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn