PHP-7.1 源代碼學習:語法分析 之 概述

前言

php 使用 lex 和 bison 進行語法分析和詞法分析,本文以 bison 語法定義文件爲起點,使用 find, grep 等命令行工具搜索相關源碼,以此來展現探索 PHP 語法分析源碼思路php

bison 語法定義文件

在 源代碼 根目錄下經過 find 命令查找 *.y 文件node

# find . -name *.y
./sapi/phpdbg/phpdbg_parser.y
./ext/json/json_parser.y
./Zend/zend_ini_parser.y
./Zend/zend_language_parser.y

咱們找到了 zend_language_parser.y 文件,裏面定義了 PHP 腳本 的語法json

語法分析樹(AST)

AST 節點類型: YYSTYPE

在查看具體的語法規則前,咱們先看看 PHP 使用什麼樣的數據結構表示 AST 根節點,使用 grep 命令 搜索 YYSTYPEapi

# grep -rin --color --include=*.h --include=*.c "#define YYSTYPE"
Zend/zeng_language_parser.c:108:#define YYSTYPE zend_parser_stack_elem

zend_parser_stack_elem

grep zend_parser_stack_elem 結構體定義數組

# grep -rin --color --include=*.h --include=*.c "zend_parser_stack_elem"
Zend/zend_compile.h:126:typedef union _zend_parser_stack_elem

打開 zend_compile.h 文件安全

typedef union _zend_parser_stack_elem {
    zend_ast *ast;
    zend_string *str;
    zend_ulong num;
} zend_parser_stack_elem;

zend_parser_stack_elem 是一個聯合體,一個 AST 節點多是 num(數值),str(字符串)或者 ast(非終結符)數據結構

zend_ast

搜索 _zend_ast 結構體定義多線程

# grep -rin --color --include=*.h --include=*.c _zend_ast *
Zend/zend_ast.h:153:struct _zend_ast {

打開 zend_ast.h 文件函數

struct _zend_ast {
    zend_ast_kind kind;
    zend_ast_attr attire;
    uint32_t linen;
    zend_ast *child[1];
}

kind

Type of the node(ZEND_AST_* enum constant)
zend_ast.h 文件頭部 enum 枚舉類型包含了各個 ZEND_AST_* 定義工具

enum _zend_ast_kind {
    /* special nodes */
    ZEND_AST_ZVAL = 1 << ZEND_AST_SPECIAL_SHIFT,
    ZEND_AST_ZNODE,

    /* declaration nodes */
    ZEND_AST_FUNC_DECL,
    ZEND_AST_CLOSURE,
    ZEND_AST_METHOD,
    ZEND_AST_CLASS,
    ...
}

attr

Additional attribute,use depending on node type

linen

Line number

child

Array of children(using struct hack)

zend_ast 其它子類

zend_ast.h 文件中還包含其它 和 zend_ast 在結構上相似的結構,相似 OOP 中的 子類

zend_ast_list

zend_ast_zval

zend_ast_decl

建立 zend_ast

zend_ast.c 中有一系列的函數用於建立 zend_ast, zend_list

  • zend_ast_create

  • zend_ast_create_ex

  • zend_ast_create_list

zend_ast_create

zend_ast_create 函數根據 kind 和一個或多個 child zend_ast 建立一個新的 zend_ast,它在內部調用 zend_ast_create_from_va_list

ZEND_API zend_ast *zend_ast_create(zend_ast_kind kind, ...) {
    va_list va;
    zend_ast *ast;

    va_start(va, kind);
    ast = zend_ast_create_from_va_list(kind, 0, va);
    va_end(va);

    return ast;
}

zend_ast_create_from_va_list

yyparse

bison 語法分析工具通常以 yyparse 函數爲入口

# grep --color -rinw --include=*.h --include=*.c yyparse *
Zend/zend_language_parser.c:63:#define yyparse zendparse

看來 PHP 給 yyparse 起了個別名,咱們再看看代碼裏面哪些地方引用了 zendparse

# grep --color -rinw --include=*.h --include=*.c zendparse *
Zend/zend_language_scanner.c:587: if (!zendparse()) {
Zend/zend_language_parser.c:436:int zendparse (void);

咱們看到 zend_language_scanner.c 文件中使用了 zenparse()

static zend_op_array *zend_compile(int type) {
    ...
    if (!zendparse()) {
        ...
    }
    ...
}

PHP 命名規範仍是不錯的,從 zend_compile 能夠推測出這個函數應該是用來編譯一段 PHP 代碼,返回值 zend_op_array 估計是 PHP 虛擬機字節碼數組

# grep --color -rinw --include=*.h --include=*.c zend_compile

Zend/zend_language_scanner.c:637: op_array = zend_compile
Zend/zend_language_scanner.c:769: op_array = zend_compile

咱們在 zend_language_scanner.c 的 637 和 769 行找到了兩處對 zend_compile 的引用

623 ZEND_API zend_op_array *compile_file(...)
645 zend_op_array *compile_filename(int type, zval *filename)

順藤摸瓜,咱們接着查找 compile_file,compile_filename

# grep --color -rinw --include=*.h --include=*.c compile_file
Zend/zend.c:705 zend_compile_file = compile_file;
Zend/zend.c:711 zend_compile_file = compile_file;

zend.c 705 在函數 zend_startup 內

int zend_startup(zend_utility_functions *utility_functions, char **extensions)

zend_compile

compiler globals(CG)

語法分析和中間代碼生成過程當中使用了 全局變量 CG 來保存中間結果(AST),搜索 CG 宏定義

/* Compiler */
#ifdef ZTS
# define CG(v) ZEND_TSRMG(compiler_globals_id, zend_compiler_globals *, v)
#else
# define CG(v) (compiler_globals.v)
extern ZEND_API struct _zend_compiler_globals compiler_globals;
#endif

這裏有一個條件編譯開關 ZTS,PHP 老鳥因該都知道 "要使用 pthreads 擴展,須要構建 PHP 時啓用 ZTS (Zend Thread Safety)",很天然想到:由於 CG 是一個全局變量,因此爲了在多線程環境下保證線程安全,須要使用特殊機制(相似 Java 中的 TLS,thread local storage)訪問 CG 中的字段

實現

有了關於 CG 的鋪墊,咱們立刻來看 zend_compile 函數的實現

// zend_language_scanner.c

static zend_op_array *zend_compile(int type) {
    // 編譯生成的字節碼數組
    zend_op_array *op_array = NULL;
    zend_bool original_in_compilation = CG(in_compilation);

    CG(in_compilation) = 1;
    CG(ast) = NULL;
    CG(ast_arena) = zend_arena_create(1024 * 32);
    if (!zendparse()) {
        ...
        op_array = emalloc(sizeof(zend_op_array));
        init_op_array(op_array, type, INITIAL_OP_ARRAY_SIZE);
        ...
        zend_compile_top_stmt(CG(ast));
        zend_emit_final_return(type == ZEND_USER_FUNCTION);
        pass_two(op_array);

        CG(active_op_array) = original_active_op_array;
    }
    ...
    CG(in_compilation) = original_in_compilation;

    return op_array;
}

這裏忽略了一些細節,突出語法分析和字節碼生成的流程

  • 調用 zend_parse 進行語法分析,生成的 AST 根節點保存在 GC(ast) 中

  • 爲字節碼數組分配內存

  • 調用 zend_compile_top_stmt 根據 AST 生成字節碼數組保存在 GC(active_op_array) 中

總結

相關文章
相關標籤/搜索