- URL: https://www.laruence.com/en/2008/06/18/221.html
- Please include attribution when republishing.
I'm about to give a talk to my colleagues at Yahoo about the internals of how PHP and Apache handle requests. Since I happened to have written some notes on Opcodes, I'm posting them here. This article is based on Sara Golemon's masterful "Understanding OPcode".
An Opcode is the intermediate language a PHP script is compiled into, much like Java's ByteCode or .NET's MSL. For example, suppose you write the following PHP code:
<?php echo "Hello World"; $a = 1 + 1; echo $a; ?>
Executing this code with PHP goes through the following 4 steps (strictly speaking, it's PHP's language engine, Zend, that does it):
1.Scanning(Lexing) , converting PHP code into language fragments (Tokens) 2.Parsing, converting Tokens into simple, meaningful expressions 3.Compilation, compiling the expressions into Opocdes 4.Execution, executing the Opcodes one at a time in sequence, which is what actually implements the functionality of the PHP script.
A side note: some caches today, such as APC, let PHP cache the Opcodes, so that when a request comes in, the first 3 steps don't have to be repeated, which greatly improves PHP's execution speed.
So what is Lexing? Anyone who has studied compiler theory should know about the lexical analysis step; Lex is the table that lexical analysis is based on. Zend/zend_language_scanner.c performs lexical analysis on the input PHP code according to Zend/zend_language_scanner.l (the Lex file), producing one "word" after another. Starting with PHP 4.2, a function called token_get_all was provided, and this function can Scan a piece of PHP code into Tokens;
If we process the PHP code from the beginning of this article with this function, we get the following result:
Array
(
[0] => Array
(
[0] => 367
[1] => Array
(
[0] => 316
[1] => echo
)
[2] => Array
(
[0] => 370
[1] =>
)
[3] => Array
(
[0] => 315
[1] => "Hello World"
)
[4] => ;
[5] => Array
(
[0] => 370
[1] =>
)
[6] => =
[7] => Array
(
[0] => 370
[1] =>
)
[8] => Array
(
[0] => 305
[1] => 1
)
[9] => Array
(
[0] => 370
[1] =>
)
[10] => +
[11] => Array
(
[0] => 370
[1] =>
)
[12] => Array
(
[0] => 305
[1] => 1
)
[13] => ;
[14] => Array
(
[0] => 370
[1] =>
)
[15] => Array
(
[0] => 316
[1] => echo
)
[16] => Array
(
[0] => 370
[1] =>
)
[17] => ;
)
Analyzing this result, we can see that strings, characters and whitespace from the source code are all returned as they are. Every character in the source appears at its corresponding position. Everything else, such as tags, operators and statements, is converted into an Array containing two parts: the Token ID (that is, the code Zend uses internally for that token, for example T_ECHO, T_STRING) and the original content from the source.
Next comes the Parsing stage. Parsing first discards the extra whitespace from the Tokens Array, then converts the remaining Tokens into simple expressions, one by one.
1.echo a constant string 2.add two numbers together 3.store the result of the prior expression to a variable 4.echo a variable
Then comes the Compilation stage, which compiles the Tokens into op_arrays. Each op_array contains the following 5 parts:
1.Opcode numeric identifier, indicating the type of operation of each op_array, such as add, echo 2.Result holds the result of the Opcode 3.Operand 1 the operand given to the Opcode 4.Operand 2 5.Extended value an integer used to distinguish overloaded operators
For example, our PHP code would be Parsed into:
* ZEND_ECHO 'Hello World' * ZEND_ADD ~0 1 1 * ZEND_ASSIGN !0 ~0 * ZEND_ECHO !0
Heh, you might ask, where did our $a go?
Well, that calls for an introduction to operands. Each operand consists of the following two parts:
a)op_type : is IS_CONST, IS_TMP_VAR, IS_VAR, IS_UNUSED, or IS_CV b)u, a union which, depending on op_type, stores this operand's value (const) or lvalue (var) using a different type
And for vars, each kind of var is different too.
IS_TMP_VAR, as the name suggests, is a temporary variable that holds the result of some op_array so that the following op_array can use it. The u field of such an operand holds a handle (an integer) pointing into the variable table, and these operands generally start with ~, for example ~0, meaning the temporary variable at slot 0 of the variable table.
IS_VAR is the var in the ordinary sense, denoted by a leading $.
IS_CV is a caching mechanism used by the compiler from ZE2.1/PHP5.1 onward. Such a variable holds the address of the variable it references; when a variable is referenced for the first time it gets CV'd, and afterwards references to that variable no longer need to look it up in the active symbol table again. CV variables are denoted by a leading !.
From this we can see that our $a has been optimized into !0.
Be First to Comment