猿代码 — 科研/AI模型/高性能计算
0

GCC:从源代码到AST

摘要: 从源代码到AST运行预处理器后,GCC 会为源文件的每个函数构造一个抽象语法树 (AST)。AST 是结构树类型的多个连接节点。每个节点都有一个树代码,用于定义树的类型。宏TREE_CODE()用于引用代码。树代码在文件 tre ...

从源代码到AST[编辑源代码]

运行预处理器后,GCC 会为源文件的每个函数构造一个抽象语法树 (AST)。AST 是结构类型的多个连接节点。每个节点都有一个树代码,用于定义树的类型。宏 TREE_CODE() 用于引用代码。树代码在文件 tree.def 和 c-common.def 中定义。具有不同树代码的树被分组为树类。在 GCC 中定义了以下树类:

GCC 树类
Tree 类解释
'd'声明
'<'比较
'2'二进制算术


有两种类型的树:语句表达式。语句对应于 C 结构,例如表达式后跟 ';'、条件语句、循环语句等。表达式是语句的构建基础。表达式的示例包括赋值表达式、算术表达式、函数调用等。下表给出了树代码的示例:

GCC 树代码
树代码Tree 类解释操作
MODIFY_EXPR'e'赋值表达式TREE_OPERAND(t,0) - 左侧;TREE_OPERAND(t,1) - 右侧;
CALL_EXPR'e'函数调用TREE_OPERAND(t,0) - 函数定义;TREE_OPERAND(t,1) - 函数参数;
FUNCTION_DECL'd'变量声明DECL_SOURCE_FILE(t) - 源文件;DECL_NAME(t) - 变量名;
ARRAY_TYPE't'阵列类型TREE_TYPE(t) - 数组元素的类型;TYPE_DOMAIN(t) - 索引类型;
DECL_STMT'e'变量声明TREE_OPERAND(t,0) - 变量;DECL_INITIAL(TREE_OPERAND(t,1)) - 初始值;

除了定义树类型的树代码外,还提供了许多针对每种树类型的不同操作数。例如,赋值表达式有两个操作数,分别对应于表达式的左侧和右侧。宏TREE_OPERAND用于指代操作数。宏IDENTIFIER_POINTER用于查找IDENTIFIER_NODE树所表示的标识符的名称。下表显示了几个树节点、它们的用途和操作数。

每棵树都有一个类型,该类型对应于它所表示的 C 表达式的类型 例如,MODIFY_EXPR节点的类型是左侧操作数的类型。NOP_EXPR树和CONVERT_EXPR树用于类型铸造。

树 NULL_TREE 等效于 NULL。函数 debug_tree() 将树打印到 stderr

当一个新的标识符被 lexed 时,它将被添加到 GCC 维护的字符串池中。标识符的树代码为IDENTIFIER_NODE。当再次对相同的标识符进行词法化时,将返回相同的树节点。函数 get_identifier() 返回标识符的树节点。

解析变量声明。

在许多函数调用中处理新的变量声明。首先,使用声明的名称、词法分析器返回的类型及其属性调用函数 start_decl()。该函数调用 grokdeclarator() 来检查类型和参数节点,并返回一个树,其中包含适合声明的代码:变量的 VAR_DECL类型TYPE_DECL等。然后,声明将添加到作用域中。作用域包含在函数中创建的所有声明,但不包含全局声明。还有包含全局声明的全局范围。解析函数时,其声明将作为 BLOCK 节点附加到其主体。创建声明时,标识符节点将使用 IDENTIFIER_SYMBOL_VALUE 与声明节点相关联。函数 lookup_name() 返回给定标识符的声明。当声明离开作用域时,将断言树属性C_DECL_INVISIBLE

GCC 中不维护符号表。相反,编译器使用标识符池和 C_DECL_INVISIBLE 属性。语言钩子 lang_hooks.decls.getdecls() 返回链接在一起的作用域中的变量。

函数 start_init() 和 finish_init() 用于初始化声明。函数 finish_decl() 完成声明。对于数组声明,它计算初始化数组的大小。然后调用函数 layout_decl()。它计算声明的大小和对齐方式。

解析函数取决于其主体的存在。使用与变量声明相同的函数来分析函数声明。对于函数定义,调用函数 start_function()。然后,编译器解析函数的主体。当函数结束时,调用函数 finish_function()。

函数 start_decl() 和 start_function() 将声明的 attributes 参数作为其参数之一。这些属性在 GCC 手册中进行了描述。属性是 C 的 GNU 实现的扩展。下表列出了其中的一些并解释了它们的用途:

函数属性
属性解释
构造 函数在 main() 之前自动调用函数
破坏者在 main() 之后自动调用函数
别名另一个函数的别名

对于每种类型的 C 语句,都有一个函数来构造相应类型的树节点。例如,函数 build_function_call() 为函数调用构建一个CALL_EXPR节点。它采用函数名称和参数的标识符作为其参数。该函数使用 lookup_name() 查找函数声明,并使用 default_conversion() 类型强制转换参数。

解析函数后,使用宏DECL_SAVED_TREE 访问它的身体。它用绑定的BIND_EXPR树表示 局部变量添加到语句中。BIND_EXPR_VARS给出了一连串的 声明的变量。BIND_EXPR_BODY 返回 STATEMENT_LIST 类型的树。

以下 API 允许遍历语句列表并 操纵它:

树木构造 API
功能目的
tsi_start(stmt_list)获取指向列表头的迭代器
tsi_last(stmt_list)获取指向列表尾部的迭代器
tsi_end_p(ITER)列表结束吗?
tsi_stmt(ITER)获取当前元素
tsi_split_statement_list_before(&ITER)在ITER中拆分元素
tsi_link_after(&iter、stmt、模式)ITER之后的链节
tsi_next(&ITER)列表的下一个元素
append_to_statement_list(树,&stmt_list)将树追加到stmt_list

可以在此级别检测函数 prolog/epilog 为 在 gimplify_function_tree() 中演示。将语句添加到 函数 epilog,使用TRY_FINALLY_EXPR树。它的第一个操作数是 旧语句,第二个参数是 Epilog 语句。这 tree 的类型指示以下传递执行 语句,当创建函数的公共出口基本块时。

要检测函数 prolog,请在树前加上 使用所需的语句。因此,BIND_EXPR_BODY将拥有 Prolog 和 TRY_FINALLY_EXPR 树。

然后,AST 被转换为 SSA,并最终转换为 RTL 表示。转换是在解析每个函数后还是解析整个文件时进行,由编译器选项 -funit-at-a-time 控制。默认情况下为 false。

带回家GCC 解析器生成源文件的 AST 表示形式。AST 由树节点构建而成。每个节点都有一个代码。树节点对应于 C 的语句和表达式,函数 debug_tree() 打印出树。

说点什么...

已有0条评论

最新评论...

本文作者
2024-2-14 01:48
  • 0
    粉丝
  • 204
    阅读
  • 0
    回复
资讯幻灯片
热门评论
热门专题
排行榜
Copyright   ©2015-2023   猿代码-超算人才智造局 高性能计算|并行计算|人工智能      ( 京ICP备2021026424号-2 )