GNU C 编译器内部/函数编译 4 1
序言用于初始化函数,例如设置其栈帧。函数尾声用于完成执行。在每个编译级别,每个函数都有一些常见的操作。因此,GCC 中存在许多序言/尾声生成函数。
在 GIMPLE 级别,序言在函数 gimplify_function_tree() 中生成。如果使用 -finstrument-functions 指令,它将添加性能分析钩子。
在 RTL 级别,使用函数 expand_function_start。它开始一个新函数的 RTL,并设置用于发出 RTL 的变量。它还为返回语句生成跳转标签,并决定返回值是在内存中还是在寄存器中。之后,它调用 assign_parms(),该函数将形式参数映射到实际参数。
机器级别的序言和尾声在 pass flow2 的函数 thread_prologue_and_epilogue_insns() 中添加,该函数依赖于机器描述文件,例如 i386.md。描述包含许多条目。在这种情况下,序言和尾声条目被使用。它们分别设置为 ix86_expand_prologue() 和 ix86_expand_epilogue(),它们为 i386 架构生成 RTL 序言和尾声。特定于机器的代码负责函数中使用的寄存器,以便在函数调用之间保留它们。push 指令作为函数的第一条指令添加。
特定于机器的代码作为 RTL 表达式而不是汇编代码生成,因为这是编译器此阶段使用的表示形式。RTL 表示形式是针对各种目标最通用的。因此,i386 的 push 指令对应于多个 RTL 表达式。
static rtx gen_push (rtx arg) { return gen_rtx_SET (VOIDmode, gen_rtx_MEM (Pmode, gen_rtx_PRE_DEC (Pmode, stack_pointer_rtx)), arg); }
在汇编输出级别,序言在函数 assemble_start_function() 和 final_start_function() 中生成。它们的输出主要与调试相关。
需要注意的是,序言函数从较高级别表示形式到较低级别表示形式执行。在运行时,添加的代码的执行顺序相反。也就是说,机器级别的序言首先执行。对于 i386,它保存函数中使用的寄存器并设置一个新的栈帧。然后接收参数并调用性能分析钩子。
一个控制流图由基本块组成,基本块是一系列指令,只有一个入口和一个出口。类型 struct basic_block 描述它。有一个指向定义基本块的语句列表的指针。每个基本块还有一个指向前一个和下一个 BB 的指针。因此,可以将它们链接到列表中。字段 preds 和 succs 可以访问进入和离开块的控制/数据流边。
函数 create_basic_block() 接收第一个和最后一个语句,并在给定语句之后插入新的 BB。make_edge() 将两个 BB 链接起来。
使用宏 FOR_EACH_BB 遍历 CFG。每个函数流图都有一个入口块和一个出口块,分别使用 ENTRY_BLOCK_PTR 和 EXIT_BLOCK_PTR 访问。
函数 build_tree_cfg() 接收一个 GIMPLE 树并生成 CFG。首先,它用两个 BB 初始化 CFG:ENTRY_BLOCK 和 EXIT_BLOCK。它调用 make_blocks(),然后调用 make_edges()。