PHP Compiler Internals
Total Page:16
File Type:pdf, Size:1020Kb
PHP Compiler Internals Sebastian Bergmann Sebastian Bergmann Helps teams build testable soware and implement ecient tests. Disclaimer Disclaimer Over the last 18 years, I contributed every once in a while to the "PHP core". Disclaimer Over the last 18 years, I contributed every once in a while to the "PHP core". I implemented functionality for the Reection API, for instance. Most of my contributions, though, were housekeeping chores and occasionally bug xes. Disclaimer Over the last 18 years, I contributed every once in a while to the "PHP core". I implemented functionality for the Reection API, for instance. Most of my contributions, though, were housekeeping chores and occasionally bug xes. I am by no means an expert when it comes to C programming in general and "PHP core" development in particular. Disclaimer Over the last 18 years, I contributed every once in a while to the "PHP core". I implemented functionality for the Reection API, for instance. Most of my contributions, though, were housekeeping chores and occasionally bug xes. I am by no means an expert when it comes to C programming in general and "PHP core" development in particular. So take those 552 commits on the next slide with a large grain of salt ... $ git log [email protected] --full-history --no-merges --date=relative --pretty="%cd %s" 6 months ago Fix typo 2 years, 3 months ago Leftover from 5230541ef59e0637d5522293a7d099bf18ce6af3 2 years, 6 months ago Fix bug #74409 2 years, 7 months ago Fugbix typo 3 years, 10 months ago Fugbix typo 3 years, 10 months ago Fugbix typo 7 years ago Ignore Zend/zend_dtrace_gen.h and Zend/zend_dtrace_gen.hbak 7 years ago Cleanup NEWS 7 years ago Leftover: Invoke re2c with --no-generation-date to prevent unintentional / unnecessary changes in generated files. 7 years ago Invoke re2c with --no-generation-date to prevent unintentional / unnecessary changes in generated files. 7 years ago Leftover: Bump version to 5.6.0 7 years ago WTF? 7 years ago Bump version. 7 years ago Fix NEWS entry for #61602. 8 years ago Revert r322390. 8 years ago Implement ReflectionClass::setFinal() and ReflectionMethod::setFinal(). 8 years ago Close #55490. 8 years ago Close #55490. 9 years ago Add optional argument to debug_backtrace() and debug_print_backtrace() to limit the amount of stack frames returned. 9 years ago Fix prototype of zend_fetch_debug_backtrace(). 9 years ago Fix prototype of zend_fetch_debug_backtrace(). 10 years ago Add ReflectionMethod::setAccessible() for invoking non-public methods through the Reflection API. 18 years ago Rename storage* to container*. 18 years ago Fix ZTS build. 18 years ago Even more TSRM cleanup. 18 years ago More tsrm-related cleanup. 18 years ago Fixed some TSRMLS_CC instances (at least it looked odd to me the way it was before). 18 years ago Remove more duplicate TSRMLS_FETCH() calls. 18 years ago Another one bites the dust. 18 years ago Remove duplicate TSRMLS_FETCH() call. $ git shortlog [email protected] --summary --email 552 Sebastian Bergmann Here Here be Here be dragons. Here be dragons? Lexical Analysis Syntax Analysis Code Generation Optimization And so our journey through PHP's internals begins ... Source Code <?php declare(strict_types=1); if (true) { print '*'; } Source Code → Token Stream Line Token Text Line Token Text 1 T_OPEN_TAG <?php 2 T_STRING true 1 T_DECLARE declare 2 T_CLOSE_BRACKET ) 1 T_OPEN_BRACKET ( 2 T_WHITESPACE 1 T_STRING strict_types 2 T_OPEN_CURLY { 1 T_EQUAL = 2 T_WHITESPACE 1 T_LNUMBER 1 3 T_PRINT print 1 T_CLOSE_BRACKET ) 3 T_WHITESPACE 1 T_SEMICOLON ; 3 T_CONSTANT_ENCAPSED_STRING '*' 1 T_WHITESPACE 3 T_SEMICOLON ; 2 T_IF if 3 T_WHITESPACE 2 T_WHITESPACE 4 T_CLOSE_CURLY } 2 T_OPEN_BRACKET ( 4 T_WHITESPACE Source Code → Token Stream Zend/zend_language_scanner.c int ZEND_FASTCALL lex_scan(zval *zendlval, zend_parser_stack_elem *elem) { // ... yych = *YYCURSOR; // ... switch (yych) { // ... case 'i': goto yy45; // ... yy45: // ... yych = *++YYCURSOR; // ... if (yych == 'f') goto yy163; // ... yy163: // ... yych = *++YYCURSOR; // ... RETURN_TOKEN(T_IF); // ... } Source Code → Token Stream Zend/zend_language_scanner.l "if" { RETURN_TOKEN(T_IF); } Token Stream → Abstract Syntax Tree $ wget https://raw.githubusercontent.com/nikic/php-ast/master/util.php <?php declare(strict_types=1); require __DIR__ . '/util.php'; ast_dump(ast\parse_file('if.php', 70)); AST_STMT_LIST 0: AST_DECLARE declares: AST_CONST_DECL 0: AST_CONST_ELEM name: "strict_types" value: 1 docComment: null stmts: null 1: AST_IF 0: AST_IF_ELEM cond: AST_CONST name: AST_NAME flags: NAME_NOT_FQ (1) name: "true" stmts: AST_STMT_LIST 0: AST_PRINT expr: "*" Token Stream → Abstract Syntax Tree AST_STMT_LIST AST_DECLARE AST_IF AST_CONST_DECL AST_IF_ELEM name: strict_types AST_CONST AST_STMT_LIST value: 1 AST_NAME AST_PRINT ['name' => 'true'] ['expr' => '*'] Token Stream → Abstract Syntax Tree start next Stmt_Declare Stmt_If cond declares Expr_ConstFetch stmts name Stmt_DeclareDeclare Name Stmt_Expression key value expr Identifier Scalar_LNumber Expr_Print name: strict_types value: 1 expr Scalar_String "*" https://github.com/ircmaxell/php-ast-visualizer Abstract Syntax Tree → Bytecode $ php -d opcache.enable=1 \ -d opcache.enable_cli=1 \ -d opcache.optimization_level=0 \ -d opcache.opt_debug_level=0x20000 \ if.php $_main: ; (lines=6, args=0, vars=0, tmps=0) ; (after optimizer) ; /home/sb/if.php:1-6 L0 (1): NOP L1 (2): EXT_STMT L2 (2): JMPZ bool(true) L5 L3 (3): EXT_STMT L4 (3): ECHO string("*") L5 (6): RETURN int(1) Abstract Syntax Tree → Bytecode $ php -d vld.active=1 \ -d vld.execute=0 \ -d vld.verbosity=0 \ -d vld.save_paths=1 \ if.php filename: /home/sb/if.php function name: (null) number of ops: 6 compiled vars: none line #* E I O op fetch ext return operands ------------------------------------------------------------------------------------- 1 0 E > NOP 2 1 EXT_STMT 2 > JMPZ <true>, ->5 3 3 > EXT_STMT 4 ECHO '%2A' 6 5 > > RETURN 1 branch: # 0; line: 1- 2; sop: 0; eop: 2; out0: 3; out1: 5 branch: # 3; line: 3- 6; sop: 3; eop: 4; out0: 5 branch: # 5; line: 6- 6; sop: 5; eop: 5; out0: -2 path #1: 0, 3, 5, path #2: 0, 5, $ dot -Tsvg -o if.svg /tmp/paths.dot Abstract Syntax Tree → Bytecode file /home/sb/if.php __main __main_ENTRY op #0-2 line 1-2 op #3-4 line 3-6 op #5-5 line 6-6 __main_EXIT Abstract Syntax Tree → Bytecode Zend/zend_compiler.c void zend_compile_stmt(zend_ast *ast) { // ... switch (ast->kind) { // ... case ZEND_AST_IF: zend_compile_if(ast); break; // ... } // ... } Abstract Syntax Tree → Bytecode Zend/zend_compiler.c void zend_compile_if(zend_ast *ast) { zend_ast_list *list = zend_ast_get_list(ast); uint32_t i; uint32_t *jmp_opnums = NULL; if (list->children > 1) { jmp_opnums = safe_emalloc(sizeof(uint32_t), list->children - 1, 0); } for (i = 0; i < list->children; ++i) { zend_ast *elem_ast = list->child[i]; zend_ast *cond_ast = elem_ast->child[0]; zend_ast *stmt_ast = elem_ast->child[1]; znode cond_node; uint32_t opnum_jmpz; if (cond_ast) { zend_compile_expr(&cond_node, cond_ast); opnum_jmpz = zend_emit_cond_jump(ZEND_JMPZ, &cond_node, 0); } // ... } Abstract Syntax Tree → Bytecode Zend/zend_compiler.c void zend_compile_if(zend_ast *ast) { // ... zend_compile_stmt(stmt_ast); if (i != list->children - 1) { jmp_opnums[i] = zend_emit_jump(0); } if (cond_ast) { zend_update_jump_target_to_next(opnum_jmpz); } } if (list->children > 1) { for (i = 0; i < list->children - 1; ++i) { zend_update_jump_target_to_next(jmp_opnums[i]); } efree(jmp_opnums); } } Bytecode → Optimized Bytecode $ php -d opcache.enable=1 \ -d opcache.enable_cli=1 \ -d opcache.optimization_level=-1 \ -d opcache.opt_debug_level=0x20000 \ if.php ; (after optimizer) ; /home/sb/if.php:1-6 L0 (2): EXT_STMT L1 (3): EXT_STMT L2 (3): ECHO string("*") L3 (6): RETURN int(1) Bytecode → Optimized Bytecode $ php -d opcache.enable=1 \ -d opcache.enable_cli=1 \ -d opcache.optimization_level=-1 \ -d vld.active=1 \ -d vld.execute=0 \ -d vld.verbosity=0 \ -d vld.save_paths=1 \ if.php filename: /home/sb/if.php function name: (null) number of ops: 4 compiled vars: none line #* E I O op fetch ext return operands ------------------------------------------------------------------------------------- 2 0 E > EXT_STMT 3 1 EXT_STMT 2 ECHO '%2A' 6 3 > RETURN 1 branch: # 0; line: 2- 6; sop: 0; eop: 3; out0: -2 path #1: 0, Bytecode → Optimized Bytecode file /home/sb/if.php __main __main_ENTRY op #0-3 line 2-6 __main_EXIT ⏮ Rewind tests/Zend/unless_001.phpt --TEST-- unless statement --FILE-- <?php declare(strict_types=1); unless (false) { print 'unless works'; } --EXPECT-- unless works tests/Zend/unless_002.phpt --TEST-- unless statement --FILE-- <?php declare(strict_types=1); unless (true) { print 'unless does not work'; } print 'unless works'; --EXPECT-- unless works ./sapi/cli/php run-tests.php Zend/tests/unless* ===================================================================== PHP : /usr/local/src/php/src/sapi/cli/php PHP_SAPI : cli PHP_VERSION : 8.0.0-dev ZEND_VERSION: 4.0.0-dev PHP_OS : Linux 5.3.6-200.fc30.x86_64 #1 SMP Mon Oct 14 13:11:01 UTC 2019 x86_64 INI actual : /usr/local/php-8.0/lib/php.ini More .INIs : --------------------------------------------------------------------- CWD : /usr/local/src/php/src Extra dirs : VALGRIND : Not used ===================================================================== Running selected tests. FAIL unless statement [Zend/tests/unless_001.phpt] FAIL unless statement [Zend/tests/unless_002.phpt] ===================================================================== Number of tests : 2 2 Tests skipped