SitePoint Wunderbarer Artikel Empfehlung: Verbesserte Implementierung des PHP -Bereichs Operator
Dieser Artikel wird mit der Autorisierung des Autors auf SitePoint reproduziert. Der folgende Inhalt wird von Thomas Punt verfasst und führt die verbesserte Implementierungsmethode des PHP -Bereichs -Operators ein. Wenn Sie an PHP -Interna interessiert sind und Funktionen zu Ihren bevorzugten Programmiersprachen hinzufügen, ist jetzt ein guter Zeitpunkt zum Lernen!
In diesem Artikel geht davon aus, dass Leser PHP aus dem Quellcode erstellen können. Wenn dies nicht der Fall ist, lesen Sie bitte zunächst das Kapitel "Bauen -PHP" des PHP -Buches für interne Mechanismus.
im vorherigen Artikel (Tipp: Stellen Sie sicher, dass Sie es gelesen haben), habe ich eine Möglichkeit gezeigt, Bereiche in PHP zu implementieren. Erste Implementierungen sind jedoch selten die besten, sodass dieser Artikel untersucht wird, wie frühere Implementierungen verbessert werden können.
Nochmals vielen Dank, Nikita Popov für den Korrekturlesen diesen Artikel!
Die erste Implementierung stellt die gesamte Logik des Bereichs Operator in die virtuelle Zend -Maschine ein, wodurch die Berechnung zur Laufzeit nur zur Ausführung des Opcode von Zend_Range durchgeführt wird. Dies bedeutet nicht nur, dass für buchstäbliche Operanden die Berechnungen nicht in die Kompilierung der Zeit übertragen werden können, sondern auch, dass einige Funktionen einfach nicht funktionieren.
In dieser Implementierung verschieben wir die Range -Operator -Logik aus der virtuellen Zend -Maschine, um Berechnungen zur Kompilierung (für buchstäbliche Operanden) oder Laufzeit (für dynamische Operanden) durchzuführen. Dies bringt nicht nur einen kleinen Nutzen für Opcache -Benutzer, sondern ermöglicht es, die Funktionalität mit konstanter Expression bei Bereichsbetreibern zu verwenden.
Beispiel:
// 作为常量定义 const AN_ARRAY = 1 |> 100; // 作为初始属性定义 class A { private $a = 1 |> 2; } // 作为可选参数的默认值: function a($a = 1 |> 2) { // }
Lassen Sie uns ohne weiteres den Bereich Operator neu einstellen.
Die Implementierung der lexikalischen Analysator bleibt völlig unverändert. Das Token ist zuerst in Zend/Zend_Language_Scanner.l (ca. 1200 Zeilen):
registriert:<st_in_scripting>"|>" { </st_in_scripting> RETURN_TOKEN(T_RANGE); }
Dann deklarieren Sie in Zend/Zend_Language_Parser.y (ungefähr 220 Zeilen): <🎜>
// 作为常量定义 const AN_ARRAY = 1 |> 100; // 作为初始属性定义 class A { private $a = 1 |> 2; } // 作为可选参数的默认值: function a($a = 1 |> 2) { // }
Die Tokenizer -Erweiterung muss erneut regeneriert werden, indem das EXT/Tokenizer -Verzeichnis eingegeben und die Datei tokenizer_data_gen.sh ausgeführt wird.
Die Parser -Implementierung ist die gleiche wie zuvor. Auch hier deklarieren wir die Priorität und Bindung des Operators, indem wir das T_Range -Token zum Ende der folgenden Zeile hinzufügen:
<st_in_scripting>"|>" { </st_in_scripting> RETURN_TOKEN(T_RANGE); }
Dann aktualisieren wir die Produktionsregeln für Expr_Without_Variable erneut, aber diesmal unterscheidet sich die semantische Aktion (Code in den Klammern) geringfügig. Aktualisieren Sie es mit dem folgenden Code (ich stelle es unter die T_spaceship -Regel, ungefähr 930 Zeilen):
%token T_RANGE "|> (T_RANGE)"
Diesmal haben wir die Funktion zend_ast_create_bary_op (anstelle der Funktion zend_ast_create) verwendet, die einen Knoten zend_ast_bary_op für uns erstellt hat. Zend_AST_CREATE_BINY_OP nimmt einen Opcode -Namen an, mit dem binäre Operationen während der Kompilierungsphase unterschieden werden.
Da wir jetzt den Knotentyp von Zend_AST_BINY_OP wiederverwenden, müssen ein neuer Zend_AST_Range -Knotentyp wie zuvor in der Datei zend/zend_ast.h nicht definiert werden.
Diesmal müssen die Datei Zend/Zend_Compile.c nicht aktualisiert werden, da sie bereits die erforderliche Logik für binäre Vorgänge enthält. Daher müssen wir diese Logik nur wiederverwenden, indem wir unseren Bediener auf den Knoten Zend_AST_BINY_OP einstellen.
Folgendes ist eine vereinfachte Version der Funktion Zend_Compile_BINY_OP:
%nonassoc T_IS_EQUAL T_IS_NOT_EQUAL T_IS_IDENTICAL T_IS_NOT_IDENTICAL T_SPACESHIP T_RANGE
Wie wir sehen, ist es der Funktion Zend_Compile_Range, die wir beim letzten Mal erstellt haben, sehr ähnlich. Die beiden wichtigen Unterschiede sind, wie man den Opcode -Typ erhalten und was passiert, wenn beide Operanden Literale sind.
Opcode -Typ wird diesmal aus dem AST -Knoten (anstatt wie beim letzten Mal festcodiert) entnommen, da der Knoten zend_ast_binary_op diesen Wert (wie in der semantischen Wirkung der neuen Produktionsregel gezeigt) speichert, um binäre Operationen zu unterscheiden. Wenn beide Operanden Literale sind, wird die Funktion zend_try_ct_eval_bary_op aufgerufen. Diese Funktion sieht so aus:
| expr T_RANGE expr { $$ = zend_ast_create_binary_op(ZEND_RANGE, , ); }
Diese Funktion erhält einen Rückruf aus der Funktion get_bary_op (Quellcode) in Zend/Zend_opcode.c gemäß dem Opcode -Typ. Dies bedeutet, dass wir diese Funktion neben dem Zend_Range -Opcode aktualisieren müssen. Fügen Sie der folgenden Fallanweisung zur Funktion get_bary_op hinzu (ungefähr 750 Zeilen):
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); } /* }}} */
Jetzt müssen wir die Funktion range_function definieren. Dies erfolgt in der Datei Zend/Zend_operators.c mit allen anderen Operatoren:
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; } /* }}} */
Der Funktionsprototyp enthält zwei neue Makros: Zend_API und Zend_fastcall. Zend_API wird verwendet, um die Sichtbarkeit einer Funktion zu steuern, indem sie zur Verfügung steht, um in eine Erweiterung eines gemeinsam genutzten Objekts zu kompilieren. Zend_Fastcall wird verwendet, um sicherzustellen, dass effizientere Aufrufkonventionen verwendet werden, wobei die ersten beiden Parameter in Registern anstelle von Stapeln übergeben werden (relevanter für 64-Bit-Builds auf x86 als für 32-Bit-Builds).
Funktionskörper ist dem, was wir in der Datei Zend/Zend_vm_def.h im vorherigen Artikel haben, sehr ähnlich. VM-spezifische Inhalte existieren nicht mehr, einschließlich des Makroaufrufs von Handlungen (ersetzt durch den Rücklauffehler;) und der Makroaufruf von Zend_VM_NEXT_OPCODE_Check_except aus dem VM -Code). Darüber hinaus vermeiden wir, wie bereits erwähnt, die Pseudo-Macro (anstelle des GET_OPN_ZVAL_PTR_DEREF), um Referenzen in der VM zu verarbeiten.
Ein weiterer bemerkenswerter Unterschied besteht darin, dass wir ZVAL_DEFEF auf beide Operanden anwenden, um sicherzustellen, dass die Referenzen korrekt verarbeitet werden. Dies geschah zuvor mit dem Pseudo-Macro get_opn_zval_ptr_deref innerhalb der VM, wurde aber jetzt auf diese Funktion übertragen. Dies geschieht nicht, da es zusammengestellt werden muss (da beide Operanden für die Kompilierungszeitverarbeitung Literale sein müssen und nicht verwiesen werden können) . Daher führen die meisten Bedienerfunktionen (außer wenn die Leistung kritisch ist) eine Referenzverarbeitung aus und nicht in ihren VM -Opcode -Definitionen.
Schließlich müssen wir den Prototyp von Range_function zur Datei Zend/Zend_operators hinzufügen:
// 作为常量定义 const AN_ARRAY = 1 |> 100; // 作为初始属性定义 class A { private $a = 1 |> 2; } // 作为可选参数的默认值: function a($a = 1 |> 2) { // }
Jetzt müssen wir die Zend Virtual Machine erneut aktualisieren, um die Ausführung des Zend_Range -Opcode zur Laufzeit zu verarbeiten. Setzen Sie den folgenden Code in Zend/Zend_vm_def.h (unten) ein:
<st_in_scripting>"|>" { </st_in_scripting> RETURN_TOKEN(T_RANGE); }
(Auch hier muss die Opcode -Nummer eine größer als die aktuelle höchste Opcode -Nummer sein, die am unteren Rand der Datei Zend/Zend_vm_opcodes.h zu sehen ist.)
Die Definition ist diesmal viel kürzer, da alle Arbeiten in Range_function behandelt werden. Wir müssen diese Funktion nur aufrufen und im Ergebnis der aktuellen Operation übergeben, um den berechneten Wert zu speichern. Ausnahmeprüfungen, die von Range_function entfernt wurden und zum nächsten Opcode überspringen, werden im VM mit einem Aufruf an zend_vm_next_opcode_check_exception weiter verarbeitet. Darüber hinaus vermeiden wir, wie bereits erwähnt, die Pseudo-Macro (anstelle des GET_OPN_ZVAL_PTR_DEREF), um Referenzen in der VM zu verarbeiten.
regenerieren Sie nun die VM, indem Sie die Datei Zend/Zend_vm_gen.php ausführen.
Schließlich muss der schöne Drucker die Datei zend/zend_ast.c erneut aktualisieren. Aktualisieren Sie den Kommentar der Prioritätstabelle (etwa 520 Zeilen):
%token T_RANGE "|> (T_RANGE)"
Fügen Sie dann eine Fallanweisung in die Funktion zend_ast_export_ex ein, um den zend_range opcode (ca. 1300 Zeilen) zu verarbeiten:
%nonassoc T_IS_EQUAL T_IS_NOT_EQUAL T_IS_IDENTICAL T_IS_NOT_IDENTICAL T_SPACESHIP T_RANGE
Dieser Artikel zeigt eine Alternative zur Implementierung von Bereichsbetreibern, bei der die Rechenlogik von der VM verschoben wurde. Dies hat den Vorteil, dass Sie im Kontext konstanter Ausdrücke Bereiche -Operatoren verwenden können.
Der dritte Teil dieser Artikelreihe wird auf dieser Implementierung aufbauen, in der erklärt wird, wie dieser Betreiber überlastet wird. Auf diese Weise können Objekte als Operanden verwendet werden (z. B. Objekte von GMP -Bibliotheken oder Objekte, die __tostring -Methoden implementieren). Es wird auch angezeigt, wie Sie Strings entsprechende Unterstützung hinzufügen können (im Gegensatz zu den aktuellen Bereichen von PHP). Aber im Moment hoffe ich, dass dies eine gute Demonstration einiger tieferer Aspekte von ZE bei der Implementierung von Operatoren in PHP ist.
Das obige ist der detaillierte Inhalt vonImplementierung des Bereichs Operator in PHP neu einplanen. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!