第四章:从抽象到具体——中间代码生成与三地址码)
朋友们,欢迎回到我们“手撸编译器”的第四站!在第三章中,我们成功地为抽象语法树(AST)注入了“灵魂”,通过语义分析确保了代码的类型正确性和作用域可见性。现在,我们的AST已经是一个语义上完整且正确的程序表示了。
然而,这棵AST虽然强大,但它仍然是高级的、抽象的。它直接反映了源代码的结构,但离具体的机器指令还有一段距离。例如,一个复杂的表达式在AST中可能是一个深层嵌套的树结构,而机器指令通常是简单的、扁平的。
为了弥合这种“抽象”与“具体”之间的鸿沟,并为后续的代码优化和目标代码生成提供便利,编译器通常会引入一个中间表示(Intermediate Representation, IR)。这就是我们第四章的主题——中间代码生成(Intermediate Code Generation)!
引言:为什么需要中间代码?
你可能会问,既然我们已经有了AST,为什么还需要一个中间代码呢?直接从AST生成机器码不行吗?答案是:可以,但效率低下且难以维护。中间代码带来了诸多好处:
-
独立于源语言和目标机器: 中间代码是一种通用的表示形式,它不依赖于特定的源语言(如C、Java)或特定的目标机器架构(如x86、ARM)。这意味着编译器的前端(词法、语法、语义分析)可以独立于后端(代码生成),从而实现模块化。我们可以为不同的源语言编写不同的前端,生成相同的中间代码;也可以为不同的目标机器编写不同的后端,处理相同的中间代码。这大大提高了编译器的可移植性和可扩展性。
-
便于代码优化: 在中间代码阶段进行优化比在AST或机器码阶段进行优化更为有效。
-
在AST上优化过于抽象,难以发现底层的优化机会。
-
在机器码上优化过于具体,受限于指令集,且优化效果可能不佳。
-
中间代码提供了一个平衡点,它既足够抽象,可以进行高级优化(如循环优化、死代码消除),又足够具体,可以映射到机器指令。
-
-
简化后端设计: 有了中间代码,后端(代码生成器)只需要将中间代码翻译成目标机器码,而无需关心复杂的源语言语法结构。这使得后端的设计和实现变得更加简单和清晰。
简单来说,中间代码就像是建筑设计中的“施工图”:它比“效果图”(AST)更具体,但又比最终的“实物”(机器码)更通用,便于施工队(后端)理解和操作,也便于工程师(优化器)进行结构优化。
中间代码的种类
编译器领域有多种中间代码表示形式,每种都有其优缺点和适用场景:
-
抽象语法树(AST): 虽然我们已经用它作为前端的输出,但它本身也可以被视为一种高层IR。
-
逆波兰表示法(Postfix Notation): 也称为后缀表达式,是一种无括号的表达式表示,便于栈式机器求值。
-
三地址码(Three-Address Code, TAC): 我们本章的重点。每条指令最多包含三个地址(两个操作数,一个结果)。它是一种线性的、类汇编的表示形式。
-
控制流图(Control Flow Graph, CFG): 以图的形式表示程序的执行路径,节点是基本块(一系列顺序执行的指令),边表示控制流转移。
-
静态单赋值形式(Static Single Assignment, SSA): 一种特殊的IR,要求每个变量只被赋值一次。这极大地简化了数据流分析和优化。
在本章中,为了实现上的简洁性和教学目的,我们将选择**三地址码(TAC)**作为我们的中间代码表示形式。
三地址码(Three-Address Code, TAC)
三地址码的特点是每条指令最多包含三个地址(变量或常量),形式通常为:result = operand1 op operand2
。
例如,C语言代码 x = y + z * 2;
可能会被翻译成以下三地址码:
t1 = z * 2
t2 = y + t1
x = t2
其中 t1
, t2
是编译器引入的临时变量。
三地址码的优点:
-
简单: 每条指令都非常简单,易于理解和处理。
-
扁平: 将复杂的表达式分解为一系列简单的操作,消除了嵌套。
-
易于优化: 临时变量的使用使得数据流分析和优化(如公共子表达式消除、常量传播)变得容易。
-
接近机器码: 它的形式与汇编语言指令相似,便于后续翻译成目标代码。
三地址码的设计
我们需要定义三地址码指令的结构。一个指令通常包含一个操作码(表示操作类型)和若干个操作数。
操作数(Operand)
操作数可以是:
-
常量(Constant): 整数、浮点数、字符等字面量。
-
变量(Variable): 程序中声明的变量。
-
临时变量(Temporary Variable): 编译器在生成中间代码时引入的中间结果。
-
标签(Label): 用于跳转指令的目标地址。
// intermediate_code.h (部分代码,完整版在后面)
#ifndef INTERMEDIATE_CODE_H
#define INTERMEDIATE_CODE_H
#include <stdlib.h> // For NULL
#include "type.h" // 需要Type信息来描述操作数的类型
// 操作数的种类
typedef enum {
OP_NONE, // 无操作数
OP_CONSTANT_INT,// 整数常量
OP_CONSTANT_FLOAT, // 浮点数常量
OP_VARIABLE, // 变量 (包括全局变量、局部变量、参数)
OP_TEMP_VAR, // 临时变量
OP_LABEL // 标签
} OperandKind;
// 操作数结构体
typedef struct Operand {
OperandKind kind; // 操作数种类
Type* type; // 操作数的类型 (语义分析阶段确定)
union {
long long int_val; // 整数常量的值
double float_val; // 浮点数常量的值
char* var_name; // 变量名 (对于OP_VARIABLE, OP_TEMP_VAR, OP_LABEL)
int var_offset; // 变量在栈帧或数据段中的偏移量 (语义分析阶段从符号表获取)
int temp_id; // 临时变量的唯一ID
int label_id; // 标签的唯一ID
} val;
} Operand;
// 中间代码操作码
typedef enum {
// 赋值
IC_ASSIGN, // result = op1
// 算术运算
IC_ADD, // result = op1 + op2
IC_SUB, // result = op1 - op2
IC_MUL, // result = op1 * op2
IC_DIV, // result = op1 / op2
IC_MOD, // result = op1 % op2
// 关系运算 (结果为0或1)
IC_EQ, // result = (op1 == op2)
IC_NE, // result = (op1 != op2)
IC_LT, // result = (op1 < op2)
IC_LE, // result = (op1 <= op2)
IC_GT, // result = (op1 > op2)
IC_GE, // result = (op1 >= op2)
// 逻辑运算 (结果为0或1)
IC_AND, // result = (op1 && op2)
IC_OR, // result = (op1 || op2)
IC_NOT, // result = !op1
// 位运算
IC_BIT_AND, // result = op1 & op2
IC_BIT_OR, // result = op1 | op2
IC_BIT_XOR, // result = op1 ^ op2
IC_BIT_NOT, // result = ~op1
IC_LSHIFT, // result = op1 << op2
IC_RSHIFT, // result = op1 >> op2
// 跳转
IC_LABEL, // label:
IC_JUMP, // goto label
IC_JUMPIF_TRUE, // if op1 goto label (如果op1为真则跳转)
IC_JUMPIF_FALSE,// if op1 == 0 goto label (如果op1为假则跳转)
// 函数调用
IC_PARAM, // push param (用于函数调用前推送参数)
IC_CALL, // result = call func, num_args (调用函数,结果存入result)
IC_RETURN, // return op1 (函数返回)
IC_FUNC_BEGIN, // func_name: (函数开始标记)
IC_FUNC_END, // end func_name (函数结束标记)
// 类型转换 (简化:直接赋值时进行隐式转换,显式转换需要单独指令)
IC_CAST, // result = (target_type)op1
// 其他 (自增自减等,可以分解为加减和赋值)
// IC_INC, // op1++
// IC_DEC, // op1--
} IntermediateCodeOp;
// 单条三地址码指令结构体
typedef struct Instruction {
IntermediateCodeOp op; // 操作码
Operand result; // 结果操作数 (通常是变量或临时变量)
Operand op1; // 第一个操作数
Operand op2; // 第二个操作数
} Instruction;
// 中间代码列表 (通常是一个函数的所有指令)
typedef struct IntermediateCode {
Instruction** instructions; // 指令数组
int num_instructions; // 当前指令数量
int capacity; // 数组容量
} IntermediateCode;
// --- Operand 创建函数 ---
Operand create_operand_int(long long val);
Operand create_operand_float(double val);
Operand create_operand_var(char* name, int offset, Type* type);
Operand create_operand_temp(int id, Type* type);
Operand create_operand_label(int id);
Operand create_operand_none(); // 用于没有操作数的情况
// --- Instruction 创建函数 ---
Instruction create_instruction(IntermediateCodeOp op, Operand result, Operand op1, Operand op2);
// --- IntermediateCode 管理函数 ---
IntermediateCode* init_intermediate_code();
void add_instruction(IntermediateCode* ic, Instruction instr);
void print_intermediate_code(IntermediateCode* ic);
void free_intermediate_code(IntermediateCode* ic);
// 辅助函数:将操作码转换为字符串
const char* ic_op_to_string(IntermediateCodeOp op);
#endif // INTERMEDIATE_CODE_H
从AST到中间代码的转换
中间代码生成器将深度优先遍历AST。对于AST中的每个表达式和语句,它都会生成一系列对应的三地址码指令。关键在于如何处理表达式和控制流。
表达式的转换
表达式的转换通常采用递归下降的方式。当遇到一个表达式节点时:
-
递归生成子表达式的中间代码: 首先递归地生成左右子表达式(如果存在)的中间代码。
-
使用临时变量: 子表达式的结果通常会存储在一个临时变量中。这个临时变量会作为当前表达式操作的输入。
-
生成当前操作的指令: 根据当前表达式的操作符,生成对应的三地址码指令,将子表达式的临时变量作为操作数,并将结果存储到新的临时变量中。
例如,对于 a + b * c
:
-
首先,递归处理
b * c
。-
生成
t1 = b * c
。
-
-
然后,处理
a + t1
。-
生成
t2 = a + t1
。
-
最终,a + b * c
的结果存储在 t2
中。
语句的转换
语句的转换相对直接:
-
变量声明: 变量的内存分配已经在语义分析阶段通过符号表中的
offset
确定。这里通常不需要生成显式的指令,除非有初始化表达式。 -
赋值语句: 生成
target = value
形式的IC_ASSIGN
指令。 -
表达式语句: 递归生成其内部表达式的中间代码。
-
复合语句(代码块): 递归遍历其内部的所有语句。
-
if
语句:-
生成条件表达式的中间代码,结果存入临时变量
t_cond
。 -
生成
IC_JUMPIF_FALSE t_cond, L_ELSE
(如果条件为假,跳到else分支)。 -
生成
then
分支的中间代码。 -
生成
IC_JUMP L_ENDIF
(跳过else分支)。 -
生成
IC_LABEL L_ELSE
。 -
生成
else
分支的中间代码(如果存在)。 -
生成
IC_LABEL L_ENDIF
。
-
这里需要引入**标签(Label)**来标记跳转的目标地址。
-
-
while
循环:-
生成
IC_LABEL L_LOOP_START
。 -
生成条件表达式的中间代码,结果存入临时变量
t_cond
。 -
生成
IC_JUMPIF_FALSE t_cond, L_LOOP_END
(如果条件为假,跳出循环)。 -
生成循环体的中间代码。
-
生成
IC_JUMP L_LOOP_START
(跳回循环开始)。 -
生成
IC_LABEL L_LOOP_END
。
-
-
函数定义:
-
生成
IC_FUNC_BEGIN func_name
。 -
为函数参数生成
IC_PARAM
指令(或在代码生成时处理)。 -
递归生成函数体的中间代码。
-
生成
IC_FUNC_END func_name
。
-
-
return
语句: 生成IC_RETURN value
指令。
中间代码生成器(Intermediate Code Generator)的实现
我们需要一个结构体来管理中间代码生成的状态,例如临时变量的计数器和标签的计数器。
// ic_generator.h (部分代码,完整版在后面)
#ifndef IC_GENERATOR_H
#define IC_GENERATOR_H
#include "ast.h"
#include "intermediate_code.h"
#include "symbol_table.h" // 需要符号表来获取变量信息
// 中间代码生成器结构体
typedef struct {
IntermediateCode* current_ic; // 当前正在生成的中间代码列表
int temp_counter; // 临时变量计数器,用于生成唯一ID
int label_counter; // 标签计数器,用于生成唯一ID
SymbolTable* sym_table; // 符号表,用于查找变量信息 (偏移量等)
} ICG;
// 函数声明
ICG* init_icg(SymbolTable* sym_table);
void free_icg(ICG* icg);
// 生成新的临时变量Operand
Operand new_temp_operand(ICG* icg, Type* type);
// 生成新的标签Operand
Operand new_label_operand(ICG* icg);
// 核心生成函数:遍历AST并生成中间代码
IntermediateCode* generate_program_ic(ASTNode* program_node, ICG* icg);
void generate_function_ic(ASTNode* func_def_node, ICG* icg);
void generate_statement_ic(ASTNode* stmt_node, ICG* icg);
Operand generate_expression_ic(ASTNode* expr_node, ICG* icg);
#endif // IC_GENERATOR_H
完整的代码实现
intermediate_code.h
// intermediate_code.h
#ifndef INTERMEDIATE_CODE_H
#define INTERMEDIATE_CODE_H
#include <stdlib.h> // For NULL
#include "type.h" // 需要Type信息来描述操作数的类型
// 操作数的种类
// 定义了操作数可以是哪种类型的数据或引用
typedef enum {
OP_NONE, // 无操作数,用于某些不需要操作数的指令
OP_CONSTANT_INT,// 整数常量,例如 10, 0xFF
OP_CONSTANT_FLOAT, // 浮点数常量,例如 3.14, 1.0e-5
OP_VARIABLE, // 变量,包括全局变量、局部变量、函数参数
OP_TEMP_VAR, // 临时变量,编译器在生成中间代码时创建的中间结果
OP_LABEL // 标签,用于跳转指令的目标地址
} OperandKind;
// 操作数结构体
// 描述一个三地址码操作数的所有信息
typedef struct Operand {
OperandKind kind; // 操作数的种类
Type* type; // 操作数的类型 (由语义分析阶段确定并传递过来)
union { // 根据操作数种类存储不同的值
long long int_val; // 如果是整数常量,存储其值
double float_val; // 如果是浮点数常量,存储其值
char* var_name; // 如果是变量或标签,存储其名称 (用于调试和可读性)
int var_offset; // 如果是变量,存储其在栈帧或数据段中的偏移量
int temp_id; // 如果是临时变量,存储其唯一ID
int label_id; // 如果是标签,存储其唯一ID
} val;
} Operand;
// 中间代码操作码
// 定义了三地址码支持的各种操作类型
typedef enum {
// 赋值操作
IC_ASSIGN, // result = op1 (简单的值传递)
// 算术运算
IC_ADD, // result = op1 + op2
IC_SUB, // result = op1 - op2
IC_MUL, // result = op1 * op2
IC_DIV, // result = op1 / op2
IC_MOD, // result = op1 % op2 (仅限整数)
// 关系运算 (结果通常为布尔值,这里用int 0/1表示)
IC_EQ, // result = (op1 == op2)
IC_NE, // result = (op1 != op2)
IC_LT, // result = (op1 < op2)
IC_LE, // result = (op1 <= op2)
IC_GT, // result = (op1 > op2)
IC_GE, // result = (op1 >= op2)
// 逻辑运算 (结果为int 0/1)
IC_AND, // result = (op1 && op2)
IC_OR, // result = (op1 || op2)
IC_NOT, // result = !op1
// 位运算 (仅限整数)
IC_BIT_AND, // result = op1 & op2
IC_BIT_OR, // result = op1 | op2
IC_BIT_XOR, // result = op1 ^ op2
IC_BIT_NOT, // result = ~op1
IC_LSHIFT, // result = op1 << op2
IC_RSHIFT, // result = op1 >> op2
// 控制流跳转
IC_LABEL, // label L_id: (定义一个标签)
IC_JUMP, // goto L_id (无条件跳转)
IC_JUMPIF_TRUE, // if op1 goto L_id (如果op1为真则跳转)
IC_JUMPIF_FALSE,// if op1 == 0 goto L_id (如果op1为假则跳转)
// 函数调用和返回
IC_PARAM, // param op1 (将op1作为参数压入栈,用于函数调用前)
IC_CALL, // result = call func_name, num_args (调用函数,结果存入result)
IC_RETURN, // return op1 (函数返回,返回值为op1)
IC_FUNC_BEGIN, // func_name: (函数开始标记,通常在函数入口处)
IC_FUNC_END, // end func_name (函数结束标记,通常在函数出口处)
// 类型转换 (显式类型转换,例如 (int)float_var)
IC_CAST, // result = (target_type)op1
// 内存操作 (TODO: 未来扩展,例如数组访问、指针解引用)
// IC_LOAD, // result = *op1 (从地址op1加载值)
// IC_STORE, // *op1 = op2 (将op2存储到地址op1)
} IntermediateCodeOp;
// 单条三地址码指令结构体
// 每条指令最多包含一个结果和两个操作数
typedef struct Instruction {
IntermediateCodeOp op; // 操作码
Operand result; // 结果操作数 (例如赋值操作的左值,函数调用的返回值接收者)
Operand op1; // 第一个操作数
Operand op2; // 第二个操作数
} Instruction;
// 中间代码列表结构体
// 用于存储一个函数或整个程序的所有三地址码指令
typedef struct IntermediateCode {
Instruction** instructions; // 指令指针数组
int num_instructions; // 当前指令数量
int capacity; // 数组的当前容量
} IntermediateCode;
// --- Operand 创建函数 ---
/**
* @brief 创建一个整数常量操作数。
* @param val 整数值。
* @return 创建的Operand结构体。
*/
Operand create_operand_int(long long val);
/**
* @brief 创建一个浮点数常量操作数。
* @param val 浮点数值。
* @return 创建的Operand结构体。
*/
Operand create_operand_float(double val);
/**
* @brief 创建一个变量操作数。
* @param name 变量名称。
* @param offset 变量在内存中的偏移量。
* @param type 变量类型。
* @return 创建的Operand结构体。
*/
Operand create_operand_var(char* name, int offset, Type* type);
/**
* @brief 创建一个临时变量操作数。
* @param id 临时变量的唯一ID。
* @param type 临时变量的类型。
* @return 创建的Operand结构体。
*/
Operand create_operand_temp(int id, Type* type);
/**
* @brief 创建一个标签操作数。
* @param id 标签的唯一ID。
* @return 创建的Operand结构体。
*/
Operand create_operand_label(int id);
/**
* @brief 创建一个空操作数 (无操作数)。
* @return 创建的Operand结构体。
*/
Operand create_operand_none();
// --- Instruction 创建函数 ---
/**
* @brief 创建一个三地址码指令。
* @param op 操作码。
* @param result 结果操作数。
* @param op1 第一个操作数。
* @param op2 第二个操作数。
* @return 创建的Instruction结构体。
*/
Instruction create_instruction(IntermediateCodeOp op, Operand result, Operand op1, Operand op2);
// --- IntermediateCode 管理函数 ---
/**
* @brief 初始化一个 IntermediateCode 结构体。
* @return 指向新创建的IntermediateCode结构体的指针。
*/
IntermediateCode* init_intermediate_code();
/**
* @brief 向 IntermediateCode 列表中添加一条指令。
* 如果容量不足,会自动扩容。
* @param ic 指向 IntermediateCode 结构体的指针。
* @param instr 要添加的指令。
*/
void add_instruction(IntermediateCode* ic, Instruction instr);
/**
* @brief 打印 IntermediateCode 列表中的所有指令 (用于调试)。
* @param ic 指向 IntermediateCode 结构体的指针。
*/
void print_intermediate_code(IntermediateCode* ic);
/**
* @brief 释放 IntermediateCode 结构体及其所有指令占用的内存。
* @param ic 指向要释放的IntermediateCode结构体的指针。
*/
void free_intermediate_code(IntermediateCode* ic);
// 辅助函数:将操作码转换为可读的字符串
const char* ic_op_to_string(IntermediateCodeOp op);
#endif // INTERMEDIATE_CODE_H
intermediate_code.c
// intermediate_code.c
#include "intermediate_code.h"
#include <stdio.h> // For fprintf, printf
#include <stdlib.h> // For malloc, free, exit
#include <string.h> // For strdup, strcmp
// 初始指令数组容量
#define INITIAL_IC_CAPACITY 64
// --- Operand 创建函数的实现 ---
Operand create_operand_int(long long val) {
Operand op;
op.kind = OP_CONSTANT_INT;
op.type = type_int; // 整数常量类型为 int
op.val.int_val = val;
return op;
}
Operand create_operand_float(double val) {
Operand op;
op.kind = OP_CONSTANT_FLOAT;
op.type = type_float; // 浮点数常量类型为 float
op.val.float_val = val;
return op;
}
Operand create_operand_var(char* name, int offset, Type* type) {
Operand op;
op.kind = OP_VARIABLE;
op.type = type;
op.val.var_name = strdup(name); // 复制变量名
op.val.var_offset = offset;
return op;
}
Operand create_operand_temp(int id, Type* type) {
Operand op;
op.kind = OP_TEMP_VAR;
op.type = type;
op.val.temp_id = id;
return op;
}
Operand create_operand_label(int id) {
Operand op;
op.kind = OP_LABEL;
op.type = type_void; // 标签没有具体类型
op.val.label_id = id;
return op;
}
Operand create_operand_none() {
Operand op;
op.kind = OP_NONE;
op.type = type_void; // 无类型
// 不需要初始化val
return op;
}
// --- Instruction 创建函数的实现 ---
Instruction create_instruction(IntermediateCodeOp op, Operand result, Operand op1, Operand op2) {
Instruction instr;
instr.op = op;
instr.result = result;
instr.op1 = op1;
instr.op2 = op2;
return instr;
}
// --- IntermediateCode 管理函数的实现 ---
IntermediateCode* init_intermediate_code() {
IntermediateCode* ic = (IntermediateCode*)malloc(sizeof(IntermediateCode));
if (!ic) {
fprintf(stderr, "错误: 内存分配失败 (IntermediateCode)\n");
exit(EXIT_FAILURE);
}
ic->instructions = (Instruction**)malloc(INITIAL_IC_CAPACITY * sizeof(Instruction*));
if (!ic->instructions) {
fprintf(stderr, "错误: 内存分配失败 (IntermediateCode instructions)\n");
free(ic);
exit(EXIT_FAILURE);
}
ic->num_instructions = 0;
ic->capacity = INITIAL_IC_CAPACITY;
return ic;
}
void add_instruction(IntermediateCode* ic, Instruction instr) {
// 检查容量,如果不足则扩容
if (ic->num_instructions >= ic->capacity) {
ic->capacity *= 2; // 容量翻倍
ic->instructions = (Instruction**)realloc(ic->instructions, ic->capacity * sizeof(Instruction*));
if (!ic->instructions) {
fprintf(stderr, "错误: 内存重新分配失败 (IntermediateCode instructions)\n");
exit(EXIT_FAILURE);
}
}
// 为新指令分配内存并复制内容
Instruction* new_instr = (Instruction*)malloc(sizeof(Instruction));
if (!new_instr) {
fprintf(stderr, "错误: 内存分配失败 (Instruction)\n");
exit(EXIT_FAILURE);
}
*new_instr = instr; // 复制整个结构体
// 特殊处理需要复制字符串的Operand
if (new_instr->result.kind == OP_VARIABLE && new_instr->result.val.var_name) {
new_instr->result.val.var_name = strdup(new_instr->result.val.var_name);
}
if (new_instr->op1.kind == OP_VARIABLE && new_instr->op1.val.var_name) {
new_instr->op1.val.var_name = strdup(new_instr->op1.val.var_name);
}
if (new_instr->op2.kind == OP_VARIABLE && new_instr->op2.val.var_name) {
new_instr->op2.val.var_name = strdup(new_instr->op2.val.var_name);
}
if (new_instr->op == IC_CALL && new_instr->op1.kind == OP_VARIABLE && new_instr->op1.val.var_name) {
new_instr->op1.val.var_name = strdup(new_instr->op1.val.var_name); // 函数名也需要复制
}
ic->instructions[ic->num_instructions++] = new_instr;
}
// 辅助函数:释放Operand中动态分配的内存 (主要是var_name)
static void free_operand_data(Operand* op) {
if (op->kind == OP_VARIABLE && op->val.var_name) {
free(op->val.var_name);
op->val.var_name = NULL;
}
// 对于OP_TEMP_VAR, OP_LABEL, OP_CONSTANT_*, OP_NONE,没有动态分配的内存
}
void free_intermediate_code(IntermediateCode* ic) {
if (ic) {
if (ic->instructions) {
for (int i = 0; i < ic->num_instructions; ++i) {
if (ic->instructions[i]) {
// 释放指令内部可能动态分配的字符串
free_operand_data(&ic->instructions[i]->result);
free_operand_data(&ic->instructions[i]->op1);
free_operand_data(&ic->instructions[i]->op2);
free(ic->instructions[i]);
}
}
free(ic->instructions);
}
free(ic);
}
}
// 辅助函数:打印单个操作数
static void print_operand(Operand op) {
switch (op.kind) {
case OP_NONE:
printf(" ");
break;
case OP_CONSTANT_INT:
printf("%lld", op.val.int_val);
break;
case OP_CONSTANT_FLOAT:
printf("%f", op.val.float_val);
break;
case OP_VARIABLE:
printf("%s", op.val.var_name);
// printf("%s (offset: %d)", op.val.var_name, op.val.var_offset); // 调试时显示偏移量
break;
case OP_TEMP_VAR:
printf("t%d", op.val.temp_id);
break;
case OP_LABEL:
printf("L%d", op.val.label_id);
break;
default:
printf("UNKNOWN_OP");
break;
}
// 可以选择打印类型信息
// if (op.type && op.type->kind != TYPE_VOID) {
// printf(":%s", type_kind_to_string(op.type->kind));
// }
}
// 将操作码转换为可读的字符串
const char* ic_op_to_string(IntermediateCodeOp op) {
switch (op) {
case IC_ASSIGN: return "ASSIGN";
case IC_ADD: return "ADD";
case IC_SUB: return "SUB";
case IC_MUL: return "MUL";
case IC_DIV: return "DIV";
case IC_MOD: return "MOD";
case IC_EQ: return "EQ";
case IC_NE: return "NE";
case IC_LT: return "LT";
case IC_LE: return "LE";
case IC_GT: return "GT";
case IC_GE: return "GE";
case IC_AND: return "AND";
case IC_OR: return "OR";
case IC_NOT: return "NOT";
case IC_BIT_AND: return "BIT_AND";
case IC_BIT_OR: return "BIT_OR";
case IC_BIT_XOR: return "BIT_XOR";
case IC_BIT_NOT: return "BIT_NOT";
case IC_LSHIFT: return "LSHIFT";
case IC_RSHIFT: return "RSHIFT";
case IC_LABEL: return "LABEL";
case IC_JUMP: return "JUMP";
case IC_JUMPIF_TRUE: return "JUMPIF_TRUE";
case IC_JUMPIF_FALSE: return "JUMPIF_FALSE";
case IC_PARAM: return "PARAM";
case IC_CALL: return "CALL";
case IC_RETURN: return "RETURN";
case IC_FUNC_BEGIN: return "FUNC_BEGIN";
case IC_FUNC_END: return "FUNC_END";
case IC_CAST: return "CAST";
default: return "UNKNOWN_IC_OP";
}
}
// 打印 IntermediateCode 列表中的所有指令
void print_intermediate_code(IntermediateCode* ic) {
if (!ic) {
printf("中间代码为空。\n");
return;
}
for (int i = 0; i < ic->num_instructions; ++i) {
Instruction* instr = ic->instructions[i];
if (!instr) continue;
printf("%4d: ", i); // 打印指令序号
switch (instr->op) {
case IC_LABEL:
printf("L%d:\n", instr->result.val.label_id); // 标签单独一行
continue; // 不打印其他操作数
case IC_JUMP:
printf(" JUMP "); print_operand(instr->result); printf("\n");
break;
case IC_JUMPIF_TRUE:
printf(" JUMPIF_TRUE "); print_operand(instr->op1); printf(", "); print_operand(instr->result); printf("\n");
break;
case IC_JUMPIF_FALSE:
printf(" JUMPIF_FALSE "); print_operand(instr->op1); printf(", "); print_operand(instr->result); printf("\n");
break;
case IC_PARAM:
printf(" PARAM "); print_operand(instr->op1); printf("\n");
break;
case IC_CALL:
if (instr->result.kind != OP_NONE) {
print_operand(instr->result); printf(" = ");
}
printf("CALL "); print_operand(instr->op1); printf(", %d\n", instr->op2.val.int_val); // op2.val.int_val 存储参数数量
break;
case IC_RETURN:
printf(" RETURN "); print_operand(instr->op1); printf("\n");
break;
case IC_FUNC_BEGIN:
printf("\nFUNC_BEGIN %s:\n", instr->op1.val.var_name);
break;
case IC_FUNC_END:
printf("FUNC_END %s\n\n", instr->op1.val.var_name);
break;
case IC_ASSIGN:
print_operand(instr->result); printf(" = "); print_operand(instr->op1); printf("\n");
break;
case IC_NOT:
case IC_BIT_NOT:
print_operand(instr->result); printf(" = %s ", ic_op_to_string(instr->op)); print_operand(instr->op1); printf("\n");
break;
case IC_CAST:
print_operand(instr->result); printf(" = (%s)", type_kind_to_string(instr->result.type->kind)); print_operand(instr->op1); printf("\n");
break;
default: // 大多数二元操作
print_operand(instr->result); printf(" = "); print_operand(instr->op1);
printf(" %s ", ic_op_to_string(instr->op)); print_operand(instr->op2); printf("\n");
break;
}
}
}
ic_generator.h
// ic_generator.h
#ifndef IC_GENERATOR_H
#define IC_GENERATOR_H
#include "ast.h" // 需要AST节点定义
#include "intermediate_code.h" // 需要中间代码结构体
#include "symbol_table.h" // 需要符号表来获取变量信息
// 中间代码生成器结构体
// 包含了中间代码生成过程中所需的所有状态和工具
typedef struct {
IntermediateCode* current_ic; // 当前正在生成的中间代码列表 (通常是一个函数的所有指令)
int temp_counter; // 临时变量计数器,用于生成唯一的临时变量ID (t0, t1, ...)
int label_counter; // 标签计数器,用于生成唯一的标签ID (L0, L1, ...)
SymbolTable* sym_table; // 符号表,用于查找变量信息 (如偏移量),由语义分析器提供
} ICG;
// 函数声明
/**
* @brief 初始化中间代码生成器。
* @param sym_table 语义分析阶段构建的符号表,用于查询符号信息。
* @return 成功返回指向 ICG 结构体的指针,失败返回NULL。
*/
ICG* init_icg(SymbolTable* sym_table);
/**
* @brief 释放中间代码生成器占用的所有资源。
* 包括其内部的 IntermediateCode 列表。
* @param icg 指向要释放的 ICG 结构体的指针。
*/
void free_icg(ICG* icg);
/**
* @brief 生成一个新的临时变量操作数。
* 每次调用都会返回一个具有唯一ID的临时变量。
* @param icg 指向 ICG 结构体的指针。
* @param type 临时变量的类型。
* @return 新创建的临时变量Operand。
*/
Operand new_temp_operand(ICG* icg, Type* type);
/**
* @brief 生成一个新的标签操作数。
* 每次调用都会返回一个具有唯一ID的标签。
* @param icg 指向 ICG 结构体的指针。
* @return 新创建的标签Operand。
*/
Operand new_label_operand(ICG* icg);
// 核心生成函数:递归遍历AST并生成中间代码
/**
* @brief 生成整个程序的中间代码。
* 遍历AST的根节点,为每个函数定义生成中间代码。
* @param program_node 程序根AST节点。
* @param icg 中间代码生成器实例。
* @return 包含整个程序所有中间代码指令的 IntermediateCode 结构体指针。
*/
IntermediateCode* generate_program_ic(ASTNode* program_node, ICG* icg);
/**
* @brief 生成函数定义的中间代码。
* 处理函数入口、参数、函数体和函数出口。
* @param func_def_node 函数定义AST节点。
* @param icg 中间代码生成器实例。
*/
void generate_function_ic(ASTNode* func_def_node, ICG* icg);
/**
* @brief 生成语句的中间代码。
* 根据语句类型分派到不同的处理函数,例如复合语句、表达式语句、if、while等。
* @param stmt_node 语句AST节点。
* @param icg 中间代码生成器实例。
*/
void generate_statement_ic(ASTNode* stmt_node, ICG* icg);
/**
* @brief 生成表达式的中间代码。
* 递归分析表达式,生成其对应的三地址码指令,并返回表示表达式结果的Operand。
* @param expr_node 表达式AST节点。
* @param icg 中间代码生成器实例。
* @return 表示表达式结果的Operand。
*/
Operand generate_expression_ic(ASTNode* expr_node, ICG* icg);
#endif // IC_GENERATOR_H
ic_generator.c
// ic_generator.c
#include "ic_generator.h"
#include <stdio.h> // For fprintf
#include <stdlib.h> // For malloc, free, exit
#include <string.h> // For strcmp
// --- ICG 初始化与清理 ---
ICG* init_icg(SymbolTable* sym_table) {
ICG* icg = (ICG*)malloc(sizeof(ICG));
if (!icg) {
fprintf(stderr, "错误: 内存分配失败 (ICG)\n");
return NULL;
}
icg->current_ic = NULL; // 初始时没有当前中间代码列表,每个函数会创建自己的
icg->temp_counter = 0; // 临时变量ID从0开始
icg->label_counter = 0; // 标签ID从0开始
icg->sym_table = sym_table; // 引用语义分析器生成的符号表
fprintf(stderr, "中间代码生成器初始化成功。\n");
return icg;
}
void free_icg(ICG* icg) {
if (icg) {
// icg->current_ic 会在 generate_program_ic 中被赋值和管理,
// 并在那里被返回给调用者,最终由调用者 free。
// 所以这里不需要 free icg->current_ic
free(icg);
fprintf(stderr, "中间代码生成器资源已清理。\n");
}
}
// --- Operand 生成辅助函数 ---
Operand new_temp_operand(ICG* icg, Type* type) {
return create_operand_temp(icg->temp_counter++, type);
}
Operand new_label_operand(ICG* icg) {
return create_operand_label(icg->label_counter++);
}
// --- 核心中间代码生成逻辑 ---
// 将AST操作符类型映射到中间代码操作码
static IntermediateCodeOp map_binary_op(TokenType op_type) {
switch (op_type) {
case TOKEN_OP_PLUS: return IC_ADD;
case TOKEN_OP_MINUS: return IC_SUB;
case TOKEN_OP_MULTIPLY: return IC_MUL;
case TOKEN_OP_DIVIDE: return IC_DIV;
case TOKEN_OP_MODULO: return IC_MOD;
case TOKEN_OP_EQ: return IC_EQ;
case TOKEN_OP_NE: return IC_NE;
case TOKEN_OP_LT: return IC_LT;
case TOKEN_OP_LE: return IC_LE;
case TOKEN_OP_GT: return IC_GT;
case TOKEN_OP_GE: return IC_GE;
case TOKEN_OP_AND: return IC_AND;
case TOKEN_OP_OR: return IC_OR;
case TOKEN_OP_BIT_AND: return IC_BIT_AND;
case TOKEN_OP_BIT_OR: return IC_BIT_OR;
case TOKEN_OP_BIT_XOR: return IC_BIT_XOR;
case TOKEN_OP_LSHIFT: return IC_LSHIFT;
case TOKEN_OP_RSHIFT: return IC_RSHIFT;
default: return -1; // 未知操作
}
}
static IntermediateCodeOp map_unary_op(TokenType op_type) {
switch (op_type) {
case TOKEN_OP_PLUS: return IC_ASSIGN; // 一元加,直接赋值即可
case TOKEN_OP_MINUS: return IC_SUB; // 一元减,可以看作 0 - op1
case TOKEN_OP_NOT: return IC_NOT;
case TOKEN_OP_BIT_NOT: return IC_BIT_NOT;
// TODO: 自增自减 (IC_INC, IC_DEC) 需要特殊处理,分解为加减和赋值
// TODO: 取地址 & 和解引用 * 需要 IC_LOAD / IC_STORE 或其他内存操作指令
default: return -1;
}
}
// 生成表达式的中间代码
// 返回一个Operand,表示表达式计算的结果 (可能是常量、变量或临时变量)
Operand generate_expression_ic(ASTNode* expr_node, ICG* icg) {
if (!expr_node) return create_operand_none();
Operand result_op = create_operand_none(); // 默认结果
switch (expr_node->type) {
case AST_INTEGER_LITERAL_EXPR:
result_op = create_operand_int(expr_node->data.integer_literal_expr.value);
break;
case AST_FLOAT_LITERAL_EXPR:
result_op = create_operand_float(expr_node->data.float_literal_expr.value);
break;
case AST_CHAR_LITERAL_EXPR:
result_op = create_operand_int(expr_node->data.char_literal_expr.value); // char 提升为 int
break;
case AST_STRING_LITERAL_EXPR:
// 字符串字面量通常在数据段,这里简化为返回一个表示其地址的临时变量或特殊操作数
// 暂时不处理,返回一个无效操作数
fprintf(stderr, "警告: 字符串字面量暂不支持中间代码生成。\n");
result_op = create_operand_none();
break;
case AST_IDENTIFIER_EXPR: {
// 标识符表达式的结果就是它本身
Symbol* sym = expr_node->data.identifier_expr.symbol_entry; // 语义分析阶段已填充
if (!sym) {
fprintf(stderr, "错误: 标识符 '%s' 未在符号表中找到,无法生成中间代码。\n", expr_node->data.identifier_expr.name);
result_op = create_operand_none();
} else {
result_op = create_operand_var(sym->name, sym->offset, sym->type);
}
break;
}
case AST_BINARY_EXPR: {
Operand left_op = generate_expression_ic(expr_node->data.binary_expr.left, icg);
Operand right_op = generate_expression_ic(expr_node->data.binary_expr.right, icg);
// 确保子表达式有有效类型
if (left_op.type == type_unknown || right_op.type == type_unknown) {
return create_operand_none();
}
// 获取公共类型作为结果类型
Type* result_type = get_common_type(left_op.type, right_op.type);
if (result_type == type_unknown) {
fprintf(stderr, "错误: 二元运算操作数类型不兼容,无法生成中间代码。\n");
return create_operand_none();
}
// 创建一个临时变量来存储运算结果
Operand temp_result = new_temp_operand(icg, result_type);
// 生成对应的三地址码指令
IntermediateCodeOp op = map_binary_op(expr_node->data.binary_expr.op);
if (op != -1) {
add_instruction(icg->current_ic, create_instruction(op, temp_result, left_op, right_op));
result_op = temp_result;
} else {
fprintf(stderr, "错误: 不支持的二元运算符 '%s',无法生成中间代码。\n", token_type_to_string(expr_node->data.binary_expr.op));
result_op = create_operand_none();
}
break;
}
case AST_UNARY_EXPR: {
Operand operand_op = generate_expression_ic(expr_node->data.unary_expr.operand, icg);
if (operand_op.type == type_unknown) {
return create_operand_none();
}
Type* result_type = operand_op.type; // 默认结果类型与操作数相同
if (expr_node->data.unary_expr.op == TOKEN_OP_NOT) { // 逻辑非结果是int
result_type = type_int;
}
Operand temp_result = new_temp_operand(icg, result_type);
IntermediateCodeOp op = map_unary_op(expr_node->data.unary_expr.op);
if (op != -1) {
if (op == IC_SUB && expr_node->data.unary_expr.op == TOKEN_OP_MINUS) { // 特殊处理一元减
// result = 0 - operand
add_instruction(icg->current_ic, create_instruction(IC_SUB, temp_result, create_operand_int(0), operand_op));
} else if (op == IC_ASSIGN && expr_node->data.unary_expr.op == TOKEN_OP_PLUS) { // 特殊处理一元加
// result = operand
add_instruction(icg->current_ic, create_instruction(IC_ASSIGN, temp_result, operand_op, create_operand_none()));
} else {
add_instruction(icg->current_ic, create_instruction(op, temp_result, operand_op, create_operand_none()));
}
result_op = temp_result;
} else {
// TODO: 处理自增自减,例如 x++ -> t = x; x = x + 1; result = t;
fprintf(stderr, "错误: 不支持的一元运算符 '%s',无法生成中间代码。\n", token_type_to_string(expr_node->data.unary_expr.op));
result_op = create_operand_none();
}
break;
}
case AST_ASSIGN_EXPR: {
// 赋值操作的左侧必须是可赋值的(L-value),例如变量
if (expr_node->data.assign_expr.target->type != AST_IDENTIFIER_EXPR) {
fprintf(stderr, "错误: 赋值目标不是标识符,无法生成中间代码。\n");
return create_operand_none();
}
Operand target_op = generate_expression_ic(expr_node->data.assign_expr.target, icg);
Operand value_op = generate_expression_ic(expr_node->data.assign_expr.value, icg);
// 检查类型兼容性 (语义分析阶段已检查,这里再次确认)
if (!is_assignable(target_op.type, value_op.type)) {
fprintf(stderr, "错误: 赋值类型不兼容,无法生成中间代码。\n");
return create_operand_none();
}
// TODO: 处理复合赋值运算符 (+=, -= 等),分解为二元运算和赋值
// 例如: a += b -> t = a + b; a = t;
// 目前只处理简单的赋值 =
add_instruction(icg->current_ic, create_instruction(IC_ASSIGN, target_op, value_op, create_operand_none()));
result_op = target_op; // 赋值表达式的结果是赋值目标的类型和值
break;
}
case AST_CALL_EXPR: {
// 1. 生成参数的中间代码并添加 PARAM 指令
for (int i = 0; i < expr_node->data.call_expr.num_args; ++i) {
Operand arg_op = generate_expression_ic(expr_node->data.call_expr.args[i], icg);
add_instruction(icg->current_ic, create_instruction(IC_PARAM, create_operand_none(), arg_op, create_operand_none()));
}
// 2. 获取被调用函数的信息
ASTNode* callee_node = expr_node->data.call_expr.callee;
Symbol* func_sym = callee_node->data.identifier_expr.symbol_entry; // 语义分析阶段已填充
if (!func_sym || func_sym->kind != SYM_FUNC) {
fprintf(stderr, "错误: '%s' 不是一个函数,无法生成中间代码。\n", callee_node->data.identifier_expr.name);
return create_operand_none();
}
Type* func_type = func_sym->type; // 函数的类型 (TYPE_FUNCTION)
// 3. 创建临时变量接收函数返回值
Operand temp_result = create_operand_none();
if (func_type->return_type->kind != TYPE_VOID) {
temp_result = new_temp_operand(icg, func_type->return_type);
}
// 4. 生成 CALL 指令
// op1 存储函数名,op2.val.int_val 存储参数数量
add_instruction(icg->current_ic, create_instruction(IC_CALL,
temp_result,
create_operand_var(func_sym->name, 0, func_type), // 函数名作为操作数
create_operand_int(expr_node->data.call_expr.num_args)));
result_op = temp_result;
break;
}
default:
fprintf(stderr, "错误: 未知或不支持的AST表达式类型用于中间代码生成。\n");
result_op = create_operand_none();
break;
}
return result_op;
}
// 生成语句的中间代码
void generate_statement_ic(ASTNode* stmt_node, ICG* icg) {
if (!stmt_node) return;
switch (stmt_node->type) {
case AST_COMPOUND_STMT: {
// 复合语句只是简单地遍历其内部的所有语句
for (int i = 0; i < stmt_node->data.compound_stmt.num_statements; ++i) {
ASTNode* child_node = stmt_node->data.compound_stmt.statements[i];
if (child_node->type == AST_VAR_DECL) {
// 局部变量声明:如果带初始化,则生成赋值指令
if (child_node->data.var_decl.initializer) {
Operand var_op = create_operand_var(child_node->data.var_decl.identifier->data.identifier_expr.symbol_entry->name,
child_node->data.var_decl.identifier->data.identifier_expr.symbol_entry->offset,
child_node->data.var_decl.identifier->resolved_type);
Operand init_op = generate_expression_ic(child_node->data.var_decl.initializer, icg);
add_instruction(icg->current_ic, create_instruction(IC_ASSIGN, var_op, init_op, create_operand_none()));
}
} else {
generate_statement_ic(child_node, icg); // 递归生成子语句的中间代码
}
}
break;
}
case AST_EXPR_STMT:
if (stmt_node->data.expr_stmt.expression) {
generate_expression_ic(stmt_node->data.expr_stmt.expression, icg);
}
break;
case AST_RETURN_STMT: {
Operand return_val_op = create_operand_none();
if (stmt_node->data.return_stmt.expression) {
return_val_op = generate_expression_ic(stmt_node->data.return_stmt.expression, icg);
}
add_instruction(icg->current_ic, create_instruction(IC_RETURN, create_operand_none(), return_val_op, create_operand_none()));
break;
}
case AST_IF_STMT: {
Operand cond_op = generate_expression_ic(stmt_node->data.if_stmt.condition, icg);
Operand label_else = new_label_operand(icg);
Operand label_endif = new_label_operand(icg);
// if (cond) goto L_then; else goto L_else;
add_instruction(icg->current_ic, create_instruction(IC_JUMPIF_FALSE, label_else, cond_op, create_operand_none()));
// then 分支
generate_statement_ic(stmt_node->data.if_stmt.then_branch, icg);
if (stmt_node->data.if_stmt.else_branch) {
add_instruction(icg->current_ic, create_instruction(IC_JUMP, label_endif, create_operand_none(), create_operand_none()));
add_instruction(icg->current_ic, create_instruction(IC_LABEL, label_else, create_operand_none(), create_operand_none()));
// else 分支
generate_statement_ic(stmt_node->data.if_stmt.else_branch, icg);
add_instruction(icg->current_ic, create_instruction(IC_LABEL, label_endif, create_operand_none(), create_operand_none()));
} else {
// 没有 else 分支,条件为假直接跳到 if 结束
add_instruction(icg->current_ic, create_instruction(IC_LABEL, label_else, create_operand_none(), create_operand_none()));
}
break;
}
case AST_WHILE_STMT: {
Operand label_loop_start = new_label_operand(icg);
Operand label_loop_end = new_label_operand(icg);
add_instruction(icg->current_ic, create_instruction(IC_LABEL, label_loop_start, create_operand_none(), create_operand_none()));
Operand cond_op = generate_expression_ic(stmt_node->data.while_stmt.condition, icg);
add_instruction(icg->current_ic, create_instruction(IC_JUMPIF_FALSE, label_loop_end, cond_op, create_operand_none()));
generate_statement_ic(stmt_node->data.while_stmt.body, icg);
add_instruction(icg->current_ic, create_instruction(IC_JUMP, label_loop_start, create_operand_none(), create_operand_none()));
add_instruction(icg->current_ic, create_instruction(IC_LABEL, label_loop_end, create_operand_none(), create_operand_none()));
break;
}
// TODO: AST_FOR_STMT, AST_BREAK_STMT, AST_CONTINUE_STMT, AST_SWITCH_STMT 等
default:
fprintf(stderr, "错误: 未知或不支持的AST语句类型用于中间代码生成。\n");
break;
}
}
// 生成函数定义的中间代码
void generate_function_ic(ASTNode* func_def_node, ICG* icg) {
// 为当前函数创建一个新的中间代码列表
// 注意:这里我们简化处理,所有函数的中间代码都添加到一个大的IC列表中
// 实际编译器中,每个函数会有独立的IC列表
if (!icg->current_ic) { // 第一次调用时初始化
icg->current_ic = init_intermediate_code();
}
Symbol* func_sym = func_def_node->data.func_def.symbol_entry; // 语义分析阶段已填充
if (!func_sym) {
fprintf(stderr, "错误: 函数 '%s' 未在符号表中找到,无法生成中间代码。\n", func_def_node->data.func_def.identifier->data.identifier_expr.name);
return;
}
// 函数入口标记
add_instruction(icg->current_ic, create_instruction(IC_FUNC_BEGIN, create_operand_none(), create_operand_var(func_sym->name, 0, func_sym->type), create_operand_none()));
// TODO: 处理函数参数的中间代码 (例如,将参数从寄存器/栈传递到局部变量空间)
// 目前参数的偏移量已在符号表中确定,代码生成阶段可以直接使用
// 生成函数体的中间代码
generate_statement_ic(func_def_node->data.func_def.body, icg);
// 确保函数末尾有返回指令,如果没有,对于非void函数,这是一个错误
// 对于void函数,可以隐式添加 return;
if (func_sym->type->return_type->kind == TYPE_VOID) {
// 检查最后一条指令是否是RETURN,如果不是,则添加一个隐式的 RETURN;
if (icg->current_ic->num_instructions == 0 || icg->current_ic->instructions[icg->current_ic->num_instructions - 1]->op != IC_RETURN) {
add_instruction(icg->current_ic, create_instruction(IC_RETURN, create_operand_none(), create_operand_none(), create_operand_none()));
}
} else {
// 对于非void函数,如果函数体没有显式 return,语义分析阶段应该已经报错
// 这里假设语义分析已确保正确性
}
// 函数出口标记
add_instruction(icg->current_ic, create_instruction(IC_FUNC_END, create_operand_none(), create_operand_var(func_sym->name, 0, func_sym->type), create_operand_none()));
}
// 生成整个程序的中间代码
IntermediateCode* generate_program_ic(ASTNode* program_node, ICG* icg) {
if (program_node->type != AST_PROGRAM) {
fprintf(stderr, "错误: AST根节点不是程序类型,无法生成中间代码。\n");
return NULL;
}
// 初始化整个程序的中间代码列表
icg->current_ic = init_intermediate_code();
if (!icg->current_ic) {
return NULL;
}
// 遍历所有顶层声明和定义
for (int i = 0; i < program_node->data.program.num_declarations; ++i) {
ASTNode* decl_node = program_node->data.program.declarations[i];
if (decl_node->type == AST_VAR_DECL) {
// 全局变量声明:如果带初始化,则生成赋值指令
if (decl_node->data.var_decl.initializer) {
Operand var_op = create_operand_var(decl_node->data.var_decl.identifier->data.identifier_expr.symbol_entry->name,
decl_node->data.var_decl.identifier->data.identifier_expr.symbol_entry->offset,
decl_node->data.var_decl.identifier->resolved_type);
Operand init_op = generate_expression_ic(decl_node->data.var_decl.initializer, icg);
add_instruction(icg->current_ic, create_instruction(IC_ASSIGN, var_op, init_op, create_operand_none()));
}
} else if (decl_node->type == AST_FUNC_DEF) {
generate_function_ic(decl_node, icg); // 生成函数定义的中间代码
} else {
fprintf(stderr, "警告: 顶层节点类型 '%s' 暂不支持中间代码生成。\n", ast_node_type_to_string(decl_node->type));
}
}
fprintf(stderr, "程序中间代码生成完成。\n");
return icg->current_ic; // 返回生成的中间代码列表
}
main.c
(修改)
// main.c (修改)
#include "lexer.h"
#include "parser.h"
#include "ast.h"
#include "token.h"
#include "semantic_analyzer.h"
#include "type.h"
#include "symbol_table.h"
#include "intermediate_code.h" // 新增:中间代码头文件
#include "ic_generator.h" // 新增:中间代码生成器头文件
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 这是一个包含各种C语言语法元素的测试代码字符串
// 用于全面测试词法分析器、语法分析器、语义分析器和中间代码生成器的功能
const char* TEST_CODE =
"// 这是一个单行注释\n"
"/* 这是一个\n"
" * 多行注释\n"
" */\n"
"int global_var = 100;\n" // 全局变量声明
"char another_global = 'Z';\n" // 另一个全局变量
"\n"
"float add(float a, float b) {\n" // 函数定义:浮点数加法
" return a + b;\n"
"}\n"
"\n"
"int subtract(int x, int y) {\n" // 新增函数:整数减法
" int temp_local = 5;\n" // 局部变量
" if (x > y) {\n"
" temp_local = x - y;\n"
" } else {\n"
" temp_local = y - x;\n"
" }\n"
" return temp_local;\n"
"}\n"
"\n"
"int main() {\n" // main 函数的定义
" int x = 10;\n" // 整数变量声明和初始化
" float y = 3.14e-2;\n" // 浮点数变量声明和初始化,包含科学计数法
" char c = 'A';\n" // 字符变量声明和初始化
" // const char* str_literal = \"Hello, \\\"World!\\n\";\n" // 字符串字面量 (暂不支持ICG)
" \n"
" if (x >= 5 && y < 10.0) {\n" // if 条件语句,包含比较运算符和逻辑运算符
" int z = x + (int)y * 2; // 强制类型转换 (暂不支持显式IC_CAST,但会处理表达式) \n" // 局部变量声明和表达式
" if (z > 20) {\n"
" z = z - 5;\n"
" }\n"
" return z;\n" // return 语句
" } else if (x == 10) {\n" // else if 分支 (else后面跟if语句)
" x++;\n" // 自增运算符 (会分解为 ADD 和 ASSIGN)
" } else {\n" // else 分支
" y--;\n" // 自减运算符 (会分解为 SUB 和 ASSIGN)
" }\n"
" \n"
" while (x > 0) {\n" // while 循环
" x = x - 1;\n" // 循环体内的表达式语句
" if (x == 5) {\n"
" // break; // 暂不支持 break/continue,但语法上可以识别
" }\n"
" }\n"
" \n"
" float sum_result = add(x, y);\n" // 函数调用,并将结果赋值给变量
" int diff_result = subtract(20, 5);\n" // 调用新函数
" \n"
" sum_result += 5.0f;\n" // 复合赋值运算符 (会分解为 ADD 和 ASSIGN)
" global_var = (int)sum_result;\n" // 访问并修改全局变量,隐式转换为int (ICG会生成ASSIGN)
" \n"
" // 语义错误测试用例 (这些应该在语义分析阶段被捕获)\n"
" // int undeclared_var = undeclared_func(1, 2);\n"
" // int bad_assign = \"string\";\n"
" // int x = 20; // 重复定义变量 x (在main函数内)
" // return \"string\"; // return 类型不匹配\n"
" // int main() { /* 重复定义 main 函数 */ }\n"
" // void bad_var; // void 类型变量\n"
" \n"
" return 0;\n" // main 函数返回
"}\n"
;
int main(int argc, char* argv[]) {
// 1. 将 TEST_CODE 字符串内容写入一个临时文件
const char* temp_filepath = "test_code.c";
FILE* temp_file = fopen(temp_filepath, "w");
if (!temp_file) {
fprintf(stderr, "错误: 无法创建临时文件 '%s'\n", temp_filepath);
return EXIT_FAILURE;
}
fprintf(temp_file, "%s", TEST_CODE);
fclose(temp_file);
// 2. 初始化词法分析器
Lexer* lexer = init_lexer(temp_filepath);
if (!lexer) {
return EXIT_FAILURE;
}
printf("--- 词法分析器初始化成功 ---\n");
// 3. 初始化语法分析器
Parser* parser = init_parser(lexer);
if (!parser) {
free_lexer(lexer);
return EXIT_FAILURE;
}
printf("--- 语法分析器初始化成功 ---\n");
// 4. 开始解析程序,构建抽象语法树 (AST)
printf("--- 开始语法分析,构建AST ---\n");
ASTNode* program_ast = parse_program(parser);
// 5. 检查AST是否成功构建
if (!program_ast) {
fprintf(stderr, "错误: AST 构建失败。\n");
free_parser(parser);
return EXIT_FAILURE;
}
printf("--- AST 构建成功!---\n");
// 6. 初始化语义分析器并执行语义分析
SemanticAnalyzer* analyzer = init_semantic_analyzer();
if (!analyzer) {
ast_free_program(program_ast);
free_parser(parser);
return EXIT_FAILURE;
}
printf("--- 语义分析开始!---\n");
analyze_program(program_ast, analyzer); // 执行语义分析
if (analyzer->error_count > 0) {
fprintf(stderr, "语义分析完成,发现 %d 个语义错误。程序终止。\n", analyzer->error_count);
ast_free_program(program_ast);
free_semantic_analyzer(analyzer);
free_parser(parser);
remove(temp_filepath);
return EXIT_FAILURE; // 有语义错误,终止后续阶段
} else {
printf("语义分析完成,未发现语义错误!代码是语义正确的!\n");
}
// 打印带有语义信息的AST (可选,用于调试)
// printf("--- 打印带有语义信息的AST结构:---\n");
// ast_print(program_ast, 0);
// printf("--- AST 打印完成 ---\n");
// 7. 初始化中间代码生成器并生成中间代码
ICG* icg = init_icg(analyzer->sym_table); // 将语义分析器生成的符号表传递给ICG
if (!icg) {
ast_free_program(program_ast);
free_semantic_analyzer(analyzer);
free_parser(parser);
return EXIT_FAILURE;
}
printf("--- 中间代码生成开始!---\n");
IntermediateCode* program_ic = generate_program_ic(program_ast, icg); // 生成中间代码
if (!program_ic) {
fprintf(stderr, "错误: 中间代码生成失败。\n");
ast_free_program(program_ast);
free_semantic_analyzer(analyzer);
free_icg(icg);
free_parser(parser);
remove(temp_filepath);
return EXIT_FAILURE;
}
printf("--- 中间代码生成成功!---\n");
printf("--- 打印生成的中间代码:---\n");
print_intermediate_code(program_ic);
printf("--- 中间代码打印完成 ---\n");
// 8. 释放所有资源
ast_free_program(program_ast);
printf("--- AST 内存已释放 ---\n");
free_semantic_analyzer(analyzer);
printf("--- 语义分析器资源已释放 ---\n");
free_intermediate_code(program_ic); // 释放中间代码列表
printf("--- 中间代码内存已释放 ---\n");
free_icg(icg); // 释放ICG本身
printf("--- 中间代码生成器资源已释放 ---\n");
free_parser(parser);
printf("--- 解析器和词法分析器资源已释放 ---\n");
// 可选:删除之前创建的临时文件
remove(temp_filepath);
return EXIT_SUCCESS;
}
编译和运行
将上述所有文件(token.h
, token.c
, lexer.h
, lexer.c
, parser.h
, parser.c
, ast.h
, ast.c
, type.h
, type.c
, symbol_table.h
, symbol_table.c
, semantic_analyzer.h
, semantic_analyzer.c
, intermediate_code.h
, intermediate_code.c
, ic_generator.h
, ic_generator.c
, main.c
)放在同一个目录下。
你可以使用GCC编译器进行编译:
gcc -o my_compiler main.c lexer.c token.c ast.c parser.c type.c symbol_table.c semantic_analyzer.c intermediate_code.c ic_generator.c -Wall -Wextra
然后运行:
./my_compiler
你将看到程序依次进行词法分析、语法分析、语义分析,最后是中间代码生成。如果一切顺利,你将看到生成的详细三地址码指令列表。
中间代码生成器的逻辑分析与挑战
-
分阶段生成: 我们将中间代码生成过程分为三个主要部分:
generate_program_ic
(程序级别)、generate_function_ic
(函数级别)和generate_statement_ic
/generate_expression_ic
(语句/表达式级别)。这种模块化设计使得代码清晰且易于管理。 -
操作数和指令设计:
-
Operand
结构体能够灵活地表示各种类型的数据(常量、变量、临时变量、标签),并携带类型信息。 -
Instruction
结构体遵循三地址码范式,包含操作码、结果操作数和两个源操作数。 -
我们定义了丰富的
IntermediateCodeOp
枚举,覆盖了C语言中的常见操作。
-
-
临时变量和标签管理:
-
ICG
结构体中的temp_counter
和label_counter
确保了生成的临时变量和标签具有唯一的ID,避免冲突。 -
new_temp_operand
和new_label_operand
函数简化了这些特殊操作数的创建。
-
-
表达式转换的核心:
-
generate_expression_ic
是中间代码生成的核心。它递归地处理子表达式,并将子表达式的结果存储在临时变量中。 -
对于二元和一元操作符,它会根据操作符类型生成对应的三地址码指令,并将结果存入新的临时变量。
-
它利用了语义分析阶段填充的
resolved_type
来确定临时变量的类型。 -
类型提升:
get_common_type
函数在二元运算中用于确定结果类型,实现了简化的类型提升规则。
-
-
语句转换的逻辑:
-
变量声明: 局部变量的声明本身不生成指令,但如果带有初始化表达式,则会生成一个
IC_ASSIGN
指令。 -
赋值表达式: 生成
IC_ASSIGN
指令。 -
控制流 (
if
,while
): 这是生成中间代码的复杂之处。我们通过引入标签和条件跳转指令(IC_JUMPIF_FALSE
,IC_JUMP
)来模拟C语言的控制流。这使得控制流在中间代码层面变得扁平化和显式化。 -
函数调用: 生成一系列
IC_PARAM
指令来推送参数,然后生成一个IC_CALL
指令来执行函数调用,并可选地将返回值存入临时变量。 -
函数定义: 使用
IC_FUNC_BEGIN
和IC_FUNC_END
标记函数的开始和结束,这对于后续的代码生成和优化非常重要。
-
-
内存管理:
-
intermediate_code.c
中的add_instruction
会为每条指令及其内部需要复制的字符串(如变量名)动态分配内存。 -
free_intermediate_code
负责递归释放这些内存,防止内存泄漏。这是C语言编译器开发中非常重要但容易出错的部分。
-
-
挑战与未来扩展:
-
更复杂的类型支持: 目前只支持基本类型和函数类型。指针、数组、结构体、联合体等需要更复杂的内存访问指令(如
IC_LOAD
,IC_STORE
)和地址计算逻辑。 -
复合赋值运算符:
a += b
等复合赋值目前被简化处理,未来需要将其分解为t = a + b; a = t;
这样的序列。 -
自增/自减运算符:
x++
,++x
等操作符需要分解为读取、加/减、写入的序列,并区分前缀和后缀的语义。 -
短路逻辑:
&&
和||
的短路求值特性在中间代码中需要通过更复杂的条件跳转来实现。 -
break
和continue
: 需要在ICG
中维护当前循环/switch
的标签栈,以便break
和continue
能够跳转到正确的循环结束或下一次迭代标签。 -
for
循环:for
循环可以分解为while
循环和额外的初始化/步进语句。 -
类型转换: 显式类型转换(如
(int)y
)需要生成IC_CAST
指令,并确保类型大小和表示的正确转换。 -
全局变量初始化: 全局变量的初始化通常在程序启动时进行,需要生成特殊的初始化指令。
-
总结与展望
朋友们,恭喜你!你已经完成了手撸编译器的第四步——一个功能强大的C语言中间代码生成器!你亲手将抽象的AST转换成了一种扁平、简单、机器友好的三地址码。这不仅是编译流程中的关键一步,更是你对编译原理理解的又一次质的飞跃!
现在,我们的程序已经从源代码变成了结构化的AST,再变成了线性的三地址码。这些三地址码指令,虽然还不是最终的机器码,但它们已经非常接近了!它们为我们后续的代码优化和目标代码生成奠定了坚实的基础。
这只是我们“肝爆”之旅的第四站。在下一章中,我们将进入编译器的第五个核心阶段:代码优化(Code Optimization)。我们将利用中间代码的优势,对生成的指令进行各种变换,以提高程序的执行效率!
准备好迎接更高效的代码了吗?继续保持这份激情,我们下一章不见不散!点赞、收藏、关注,让我们一起把这“手撸编译器”的肝爆之旅进行到底!
第五章:精益求精——代码优化与中间代码变换)
朋友们,欢迎来到我们“手撸编译器”的第五站!在第四章中,我们亲手将抽象语法树(AST)转换成了线性的、机器友好的三地址码(TAC)。现在,我们的程序已经以一种更接近底层但仍具通用性的形式存在了。
然而,仅仅生成正确的中间代码是不够的!想象一下,你建造了一辆汽车,它能跑,但油耗惊人,速度也慢得像蜗牛。你肯定会想办法改进它,让它跑得更快,更省油。编译器的**代码优化(Code Optimization)**阶段正是扮演着这样的角色!
代码优化的使命:让程序“飞”起来
代码优化是编译器中的一个可选但至关重要的阶段。它的主要目标是:
-
提高执行速度: 减少程序的执行时间,让程序运行得更快。
-
减少资源消耗: 降低程序对内存、CPU周期、磁盘I/O等资源的占用。这包括减小生成的可执行文件大小。
优化并非总是能带来巨大的性能提升,有时甚至可能适得其反(例如,优化本身耗时过长,或者导致代码膨胀)。因此,编译器通常会提供不同的优化级别(如GCC的-O1
, -O2
, -O3
, -Os
等),让开发者根据需求选择。
优化的分类
代码优化通常可以根据其作用范围和依赖性进行分类:
-
机器无关优化(Machine-Independent Optimization):
-
不依赖于特定目标机器的指令集架构。
-
通常在中间代码(如三地址码)上进行。
-
例如:常量折叠、公共子表达式消除、死代码消除、循环优化等。
-
-
机器相关优化(Machine-Dependent Optimization):
-
依赖于特定目标机器的特性(如寄存器数量、指令集、缓存结构)。
-
通常在目标代码生成阶段或之后进行。
-
例如:寄存器分配、指令调度、窥孔优化等。
-
在本章中,我们将主要关注机器无关优化,因为它们直接作用于我们已经生成的三地址码。
常见的代码优化技术
我们将实现几种简单但有效的优化技术,它们可以显著改善程序的性能。
1. 常量折叠(Constant Folding)与常量传播(Constant Propagation)
-
常量折叠: 在编译时计算常量表达式的值,并用结果替换表达式。
-
原始代码:
int x = 10 + 20;
-
中间代码(未优化):
t0 = 10 t1 = 20 t2 = t0 + t1 x = t2
-
中间代码(常量折叠后):
x = 30
-
-
常量传播: 如果一个变量被赋值为一个常量,那么在后续使用该变量的地方,可以用这个常量值直接替换。
-
原始代码:
int a = 10; int b = a + 5;
-
中间代码(未优化):
a = 10 t0 = a + 5 b = t0
-
中间代码(常量传播后):
a = 10 t0 = 10 + 5 // (继续常量折叠) b = t0
-
最终:
a = 10 b = 15
-
2. 死代码消除(Dead Code Elimination)
-
消除那些永远不会被执行到,或者其结果永远不会被使用的代码。
-
原始代码:
int x = 10; int y = x + 5; // ... 后面代码没有使用y if (0) { // 永远不会执行 printf("Hello"); }
-
中间代码(未优化):
x = 10 t0 = x + 5 y = t0 L0: JUMPIF_FALSE 0, L1 // if (0) ... // printf 相关指令 L1:
-
中间代码(死代码消除后):
x = 10 // t0 = x + 5 和 y = t0 被消除,因为y未被使用 // if (0) 分支也被消除
-
3. 公共子表达式消除(Common Subexpression Elimination, CSE)
-
识别并消除在程序中多次计算的相同表达式,用一个临时变量存储其结果,并在后续使用时直接引用该临时变量。
-
原始代码:
int a = (x + y) * z; int b = (x + y) / 2;
-
中间代码(未优化):
t0 = x + y t1 = t0 * z a = t1 t2 = x + y t3 = t2 / 2 b = t3
-
中间代码(CSE后):
t0 = x + y // 第一次计算 x + y t1 = t0 * z a = t1 // t2 = x + y 被消除,因为 t0 已经存储了结果 t3 = t0 / 2 // 直接使用 t0 b = t3
-
4. 循环不变代码外提(Loop-Invariant Code Motion)
-
将循环体内计算结果不随循环迭代而改变的表达式(循环不变式)移到循环体之外,只计算一次。
-
原始代码:
for (i = 0; i < N; i++) { int temp = A * B; // A*B在循环内不变 sum += temp; }
-
中间代码(未优化):
L0: JUMPIF_FALSE i < N, L1 t0 = A * B sum = sum + t0 i = i + 1 JUMP L0 L1:
-
中间代码(循环不变代码外提后):
t_temp = A * B // 移到循环外 L0: JUMPIF_FALSE i < N, L1 sum = sum + t_temp // 直接使用t_temp i = i + 1 JUMP L0 L1:
-
优化器的实现策略
实现这些优化通常需要对中间代码进行数据流分析(Data Flow Analysis),以收集变量的使用、定义、活跃性等信息。这通常比简单的遍历更复杂,因为它需要迭代地分析指令之间的依赖关系。
为了简化,我们在这里将实现一个简化的、基于遍历的优化器,主要关注常量折叠和简单的死代码消除。更高级的优化需要构建控制流图(CFG)和进行迭代数据流分析。
优化器结构
我们将创建一个Optimizer
结构体来管理优化过程。
// optimizer.h (部分代码,完整版在后面)
#ifndef OPTIMIZER_H
#define OPTIMIZER_H
#include "intermediate_code.h" // 需要中间代码
// 优化器结构体
typedef struct {
// 可以在这里存储优化过程中需要的状态,例如:
// 一个映射表,用于常量传播 (变量名 -> 常量值)
// 一个集合,用于记录活跃变量 (死代码消除)
} Optimizer;
// 函数声明
Optimizer* init_optimizer();
void free_optimizer(Optimizer* opt);
/**
* @brief 对中间代码进行优化。
* @param ic 指向 IntermediateCode 结构体的指针。
* @param opt 优化器实例。
*/
void optimize_intermediate_code(IntermediateCode* ic, Optimizer* opt);
// 各种优化子函数 (内部使用)
static void constant_folding(IntermediateCode* ic);
static void dead_code_elimination(IntermediateCode* ic);
// static void common_subexpression_elimination(IntermediateCode* ic); // 复杂,暂不实现
// static void loop_invariant_code_motion(IntermediateCode* ic); // 复杂,暂不实现
#endif // OPTIMIZER_H
完整的代码实现
optimizer.h
// optimizer.h
#ifndef OPTIMIZER_H
#define OPTIMIZER_H
#include "intermediate_code.h" // 需要中间代码结构体
#include "type.h" // 需要Type定义
// 优化器结构体
// 包含了优化过程中可能需要的状态和数据结构
typedef struct {
// 可以在这里存储优化过程中需要的状态,例如:
// - 一个映射表,用于常量传播 (变量名/临时变量ID -> 常量值)
// - 一个集合,用于记录活跃变量 (死代码消除)
// - 一个记录已计算表达式结果的哈希表 (公共子表达式消除)
// 为了简化,当前版本这个结构体可能为空或只包含少量辅助变量
} Optimizer;
// 函数声明
/**
* @brief 初始化优化器。
* @return 成功返回指向 Optimizer 结构体的指针,失败返回NULL。
*/
Optimizer* init_optimizer();
/**
* @brief 释放优化器占用的所有资源。
* @param opt 指向要释放的 Optimizer 结构体的指针。
*/
void free_optimizer(Optimizer* opt);
/**
* @brief 对中间代码进行优化。
* 这是优化阶段的入口点,它会调用各种优化子函数。
* @param ic 指向 IntermediateCode 结构体的指针,优化将直接修改此结构体。
* @param opt 优化器实例。
*/
void optimize_intermediate_code(IntermediateCode* ic, Optimizer* opt);
// 各种优化子函数 (通常是静态的,内部使用)
// 为了简化,我们只实现常量折叠和简单的死代码消除
static void constant_folding(IntermediateCode* ic);
static void dead_code_elimination(IntermediateCode* ic);
// static void common_subexpression_elimination(IntermediateCode* ic); // 更复杂,需要数据流分析
// static void loop_invariant_code_motion(IntermediateCode* ic); // 更复杂,需要控制流图和数据流分析
#endif // OPTIMIZER_H
optimizer.c
// optimizer.c
#include "optimizer.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h> // For bool type
// --- 辅助函数 ---
// 检查操作数是否是常量
static bool is_constant(Operand op) {
return op.kind == OP_CONSTANT_INT || op.kind == OP_CONSTANT_FLOAT;
}
// 检查操作数是否是整数常量
static bool is_int_constant(Operand op) {
return op.kind == OP_CONSTANT_INT;
}
// 检查操作数是否是浮点数常量
static bool is_float_constant(Operand op) {
return op.kind == OP_CONSTANT_FLOAT;
}
// 评估二元常量表达式
static Operand evaluate_binary_constant(IntermediateCodeOp op, Operand op1, Operand op2) {
// 确保都是常量
if (!is_constant(op1) || !is_constant(op2)) {
return create_operand_none(); // 不是常量,无法折叠
}
// 处理整数运算
if (is_int_constant(op1) && is_int_constant(op2)) {
long long val1 = op1.val.int_val;
long long val2 = op2.val.int_val;
long long result_val;
bool is_valid_op = true;
switch (op) {
case IC_ADD: result_val = val1 + val2; break;
case IC_SUB: result_val = val1 - val2; break;
case IC_MUL: result_val = val1 * val2; break;
case IC_DIV:
if (val2 == 0) { fprintf(stderr, "警告: 编译时整数除零。\n"); is_valid_op = false; }
else { result_val = val1 / val2; }
break;
case IC_MOD:
if (val2 == 0) { fprintf(stderr, "警告: 编译时整数模零。\n"); is_valid_op = false; }
else { result_val = val1 % val2; }
break;
case IC_EQ: result_val = (val1 == val2); break;
case IC_NE: result_val = (val1 != val2); break;
case IC_LT: result_val = (val1 < val2); break;
case IC_LE: result_val = (val1 <= val2); break;
case IC_GT: result_val = (val1 > val2); break;
case IC_GE: result_val = (val1 >= val2); break;
case IC_AND: result_val = (val1 && val2); break; // 逻辑与
case IC_OR: result_val = (val1 || val2); break; // 逻辑或
case IC_BIT_AND: result_val = (val1 & val2); break;
case IC_BIT_OR: result_val = (val1 | val2); break;
case IC_BIT_XOR: result_val = (val1 ^ val2); break;
case IC_LSHIFT: result_val = (val1 << val2); break;
case IC_RSHIFT: result_val = (val1 >> val2); break;
default: is_valid_op = false; break;
}
if (is_valid_op) {
return create_operand_int(result_val);
}
}
// 处理浮点数运算 (简化:只处理两个都是浮点数的情况,不处理混合类型)
else if (is_float_constant(op1) && is_float_constant(op2)) {
double val1 = op1.val.float_val;
double val2 = op2.val.float_val;
double result_val;
bool is_valid_op = true;
switch (op) {
case IC_ADD: result_val = val1 + val2; break;
case IC_SUB: result_val = val1 - val2; break;
case IC_MUL: result_val = val1 * val2; break;
case IC_DIV:
if (val2 == 0.0) { fprintf(stderr, "警告: 编译时浮点数除零。\n"); is_valid_op = false; }
else { result_val = val1 / val2; }
break;
case IC_EQ: result_val = (val1 == val2); break;
case IC_NE: result_val = (val1 != val2); break;
case IC_LT: result_val = (val1 < val2); break;
case IC_LE: result_val = (val1 <= val2); break;
case IC_GT: result_val = (val1 > val2); break;
case IC_GE: result_val = (val1 >= val2); break;
default: is_valid_op = false; break;
}
if (is_valid_op) {
return create_operand_float(result_val);
}
}
// TODO: 处理混合类型运算 (例如 int + float -> float)
return create_operand_none(); // 无法折叠或不支持的操作
}
// 评估一元常量表达式
static Operand evaluate_unary_constant(IntermediateCodeOp op, Operand op1) {
if (!is_constant(op1)) {
return create_operand_none();
}
if (is_int_constant(op1)) {
long long val = op1.val.int_val;
long long result_val;
bool is_valid_op = true;
switch (op) {
case IC_SUB: result_val = -val; break; // 一元负
case IC_NOT: result_val = !val; break; // 逻辑非
case IC_BIT_NOT: result_val = ~val; break; // 位非
default: is_valid_op = false; break;
}
if (is_valid_op) return create_operand_int(result_val);
} else if (is_float_constant(op1)) {
double val = op1.val.float_val;
double result_val;
bool is_valid_op = true;
switch (op) {
case IC_SUB: result_val = -val; break; // 一元负
case IC_NOT: result_val = !val; break; // 逻辑非
default: is_valid_op = false; break;
}
if (is_valid_op) return create_operand_float(result_val);
}
return create_operand_none();
}
// --- 优化子函数实现 ---
// 常量折叠:在编译时计算常量表达式
static void constant_folding(IntermediateCode* ic) {
fprintf(stderr, "执行常量折叠...\n");
for (int i = 0; i < ic->num_instructions; ++i) {
Instruction* instr = ic->instructions[i];
if (!instr) continue;
// 处理二元运算
if (instr->op >= IC_ADD && instr->op <= IC_GE) { // 算术、关系、逻辑二元运算
if (is_constant(instr->op1) && is_constant(instr->op2)) {
Operand folded_result = evaluate_binary_constant(instr->op, instr->op1, instr->op2);
if (folded_result.kind != OP_NONE) {
// 将指令转换为赋值指令,结果为折叠后的常量
instr->op = IC_ASSIGN;
instr->op1 = folded_result;
instr->op2 = create_operand_none();
fprintf(stderr, " 折叠指令 %d: %s = %s %s %s -> %s = %s\n",
i,
ic_op_to_string(instr->op),
"op1", "op2",
"result",
ic_op_to_string(IC_ASSIGN),
type_kind_to_string(folded_result.type->kind));
}
}
}
// 处理一元运算
else if (instr->op == IC_NOT || instr->op == IC_BIT_NOT || (instr->op == IC_SUB && instr->op1.kind != OP_NONE && instr->op2.kind == OP_NONE)) { // 逻辑非、位非、一元负
if (is_constant(instr->op1)) {
Operand folded_result = evaluate_unary_constant(instr->op, instr->op1);
if (folded_result.kind != OP_NONE) {
instr->op = IC_ASSIGN;
instr->op1 = folded_result;
instr->op2 = create_operand_none();
fprintf(stderr, " 折叠指令 %d: %s = %s %s -> %s = %s\n",
i,
ic_op_to_string(instr->op),
"op1",
"result",
ic_op_to_string(IC_ASSIGN),
type_kind_to_string(folded_result.type->kind));
}
}
}
}
}
// 简单的死代码消除:消除对结果没有后续使用的赋值指令
// 注意:这是一个非常简化的版本,真正的死代码消除需要更复杂的数据流分析 (活跃变量分析)
static void dead_code_elimination(IntermediateCode* ic) {
fprintf(stderr, "执行死代码消除 (简化版)...\n");
bool* is_instruction_dead = (bool*)calloc(ic->num_instructions, sizeof(bool));
if (!is_instruction_dead) {
fprintf(stderr, "错误: 内存分配失败 (is_instruction_dead)\n");
return;
}
// 标记所有指令为“活跃” (默认)
// 只有在确定某条指令是死代码后,才将其标记为“死”
// 第一次遍历:找出所有被使用的变量和临时变量
// 这是一个反向数据流分析的简化版本
bool* is_var_used = (bool*)calloc(ic->temp_counter + ic->sym_table->global_var_offset / 4 + 100, sizeof(bool)); // 粗略估计变量数量
if (!is_var_used) {
fprintf(stderr, "错误: 内存分配失败 (is_var_used)\n");
free(is_instruction_dead);
return;
}
// 遍历所有指令,标记操作数中的变量和临时变量为“被使用”
// 这是一个简化的活跃变量分析,不考虑控制流
for (int i = 0; i < ic->num_instructions; ++i) {
Instruction* instr = ic->instructions[i];
if (!instr) continue;
// 如果是跳转指令或函数调用,其操作数通常是“活跃”的
if (instr->op == IC_JUMP || instr->op == IC_JUMPIF_TRUE || instr->op == IC_JUMPIF_FALSE || instr->op == IC_RETURN || instr->op == IC_PARAM || instr->op == IC_CALL) {
// 这些指令本身就是活跃的,并且它们的op1/op2通常是活跃的
if (instr->op1.kind == OP_VARIABLE) is_var_used[instr->op1.val.var_offset / 4] = true;
if (instr->op1.kind == OP_TEMP_VAR) is_var_used[instr->op1.val.temp_id] = true;
if (instr->op2.kind == OP_VARIABLE) is_var_used[instr->op2.val.var_offset / 4] = true;
if (instr->op2.kind == OP_TEMP_VAR) is_var_used[instr->op2.val.temp_id] = true;
continue; // 这些指令不会被标记为死代码
}
// 标记op1和op2为被使用
if (instr->op1.kind == OP_VARIABLE) {
is_var_used[instr->op1.val.var_offset / 4] = true;
} else if (instr->op1.kind == OP_TEMP_VAR) {
is_var_used[instr->op1.val.temp_id] = true;
}
if (instr->op2.kind == OP_VARIABLE) {
is_var_used[instr->op2.val.var_offset / 4] = true;
} else if (instr->op2.kind == OP_TEMP_VAR) {
is_var_used[instr->op2.val.temp_id] = true;
}
}
// 第二次遍历:标记死指令
int eliminated_count = 0;
for (int i = 0; i < ic->num_instructions; ++i) {
Instruction* instr = ic->instructions[i];
if (!instr) continue;
// 只有赋值指令和计算指令的结果才可能成为死代码
if (instr->op == IC_ASSIGN || (instr->op >= IC_ADD && instr->op <= IC_BIT_NOT) || instr->op == IC_CAST) {
bool result_is_dead = false;
if (instr->result.kind == OP_VARIABLE) {
if (!is_var_used[instr->result.val.var_offset / 4]) {
result_is_dead = true;
}
} else if (instr->result.kind == OP_TEMP_VAR) {
if (!is_var_used[instr->result.val.temp_id]) {
result_is_dead = true;
}
}
if (result_is_dead) {
is_instruction_dead[i] = true;
eliminated_count++;
fprintf(stderr, " 消除死指令 %d: %s\n", i, ic_op_to_string(instr->op));
}
}
}
// 第三次遍历:重建指令列表,移除死指令
int current_idx = 0;
for (int i = 0; i < ic->num_instructions; ++i) {
if (!is_instruction_dead[i]) {
ic->instructions[current_idx++] = ic->instructions[i];
} else {
// 释放被消除指令的内存
free_operand_data(&ic->instructions[i]->result);
free_operand_data(&ic->instructions[i]->op1);
free_operand_data(&ic->instructions[i]->op2);
free(ic->instructions[i]);
ic->instructions[i] = NULL; // 清空指针
}
}
ic->num_instructions = current_idx; // 更新指令数量
free(is_instruction_dead);
free(is_var_used);
fprintf(stderr, " 死代码消除完成,移除了 %d 条指令。\n", eliminated_count);
}
// --- 优化器公共接口实现 ---
Optimizer* init_optimizer() {
Optimizer* opt = (Optimizer*)malloc(sizeof(Optimizer));
if (!opt) {
fprintf(stderr, "错误: 内存分配失败 (Optimizer)\n");
return NULL;
}
// 初始化优化器内部状态 (如果需要)
fprintf(stderr, "优化器初始化成功。\n");
return opt;
}
void free_optimizer(Optimizer* opt) {
if (opt) {
// 释放优化器内部可能动态分配的资源
free(opt);
fprintf(stderr, "优化器资源已清理。\n");
}
}
// 对中间代码进行优化
void optimize_intermediate_code(IntermediateCode* ic, Optimizer* opt) {
if (!ic) return;
fprintf(stderr, "开始优化中间代码...\n");
// 迭代执行优化,直到没有更多改进或达到最大迭代次数
// 某些优化可能会为其他优化创造机会
bool changed;
int iteration = 0;
const int MAX_OPTIMIZATION_ITERATIONS = 5; // 限制迭代次数以防无限循环
do {
changed = false;
fprintf(stderr, "--- 优化迭代 %d ---\n", ++iteration);
int old_num_instructions = ic->num_instructions;
// 1. 常量折叠
constant_folding(ic);
if (old_num_instructions != ic->num_instructions) changed = true; // 如果指令数量变化,说明有优化
// 2. 简单的死代码消除
// 注意:死代码消除可能会改变指令数量,需要重新计算活跃性
// 这里的简化版死代码消除可能需要多次运行才能完全清除
dead_code_elimination(ic);
if (old_num_instructions != ic->num_instructions) changed = true; // 如果指令数量变化,说明有优化
// TODO: 添加其他优化,例如:
// common_subexpression_elimination(ic);
// loop_invariant_code_motion(ic);
// constant_propagation(ic); // 需要更复杂的数据流分析
} while (changed && iteration < MAX_OPTIMIZATION_ITERATIONS); // 如果有变化,继续迭代
fprintf(stderr, "中间代码优化完成。\n");
}
main.c
(修改)
// main.c (修改)
#include "lexer.h"
#include "parser.h"
#include "ast.h"
#include "token.h"
#include "semantic_analyzer.h"
#include "type.h"
#include "symbol_table.h"
#include "intermediate_code.h"
#include "ic_generator.h"
#include "optimizer.h" // 新增:优化器头文件
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 这是一个包含各种C语言语法元素的测试代码字符串
// 用于全面测试词法分析器、语法分析器、语义分析器、中间代码生成器和优化器的功能
const char* TEST_CODE =
"// 这是一个单行注释\n"
"/* 这是一个\n"
" * 多行注释\n"
" */\n"
"int global_var = 100 + 200; // 全局变量声明,可常量折叠\n"
"char another_global = 'Z';\n"
"\n"
"float add(float a, float b) {\n"
" return a + b;\n"
"}\n"
"\n"
"int subtract(int x, int y) {\n"
" int temp_local = 5 * 10; // 局部变量,可常量折叠\n"
" int unused_var = 123; // 死代码\n"
" if (x > y) {\n"
" temp_local = x - y;\n"
" } else {\n"
" temp_local = y - x;\n"
" }\n"
" // unused_var 永远不会被使用,应该被消除\n"
" return temp_local;\n"
"}\n"
"\n"
"int main() {\n"
" int x = 10;\n"
" float y = 3.14e-2;\n"
" char c = 'A';\n"
" \n"
" int common_expr_res = x + 5; // 公共子表达式 (目前简化版优化器不处理)\n"
" int another_common_expr_res = x + 5; // 同上\n"
" \n"
" if (x >= 5 && y < 10.0) {\n"
" int z = x + (int)y * 2; \n"
" if (z > 20) {\n"
" z = z - 5;\n"
" }\n"
" return z;\n"
" } else if (x == 10) {\n"
" x++;\n"
" } else {\n"
" y--;\n"
" }\n"
" \n"
" while (x > 0) {\n"
" x = x - 1;\n"
" if (x == 5) {\n"
" // break; \n"
" }\n"
" }\n"
" \n"
" float sum_result = add(x, y);\n"
" int diff_result = subtract(20, 5);\n"
" \n"
" sum_result += 5.0f;\n"
" global_var = (int)sum_result;\n"
" \n"
" return 0;\n"
"}\n"
;
int main(int argc, char* argv[]) {
// 1. 将 TEST_CODE 字符串内容写入一个临时文件
const char* temp_filepath = "test_code.c";
FILE* temp_file = fopen(temp_filepath, "w");
if (!temp_file) {
fprintf(stderr, "错误: 无法创建临时文件 '%s'\n", temp_filepath);
return EXIT_FAILURE;
}
fprintf(temp_file, "%s", TEST_CODE);
fclose(temp_file);
// 2. 初始化词法分析器
Lexer* lexer = init_lexer(temp_filepath);
if (!lexer) {
return EXIT_FAILURE;
}
printf("--- 词法分析器初始化成功 ---\n");
// 3. 初始化语法分析器
Parser* parser = init_parser(lexer);
if (!parser) {
free_lexer(lexer);
return EXIT_FAILURE;
}
printf("--- 语法分析器初始化成功 ---\n");
// 4. 开始解析程序,构建抽象语法树 (AST)
printf("--- 开始语法分析,构建AST ---\n");
ASTNode* program_ast = parse_program(parser);
// 5. 检查AST是否成功构建
if (!program_ast) {
fprintf(stderr, "错误: AST 构建失败。\n");
free_parser(parser);
return EXIT_FAILURE;
}
printf("--- AST 构建成功!---\n");
// 6. 初始化语义分析器并执行语义分析
SemanticAnalyzer* analyzer = init_semantic_analyzer();
if (!analyzer) {
ast_free_program(program_ast);
free_parser(parser);
return EXIT_FAILURE;
}
printf("--- 语义分析开始!---\n");
analyze_program(program_ast, analyzer); // 执行语义分析
if (analyzer->error_count > 0) {
fprintf(stderr, "语义分析完成,发现 %d 个语义错误。程序终止。\n", analyzer->error_count);
ast_free_program(program_ast);
free_semantic_analyzer(analyzer);
free_parser(parser);
remove(temp_filepath);
return EXIT_FAILURE;
} else {
printf("语义分析完成,未发现语义错误!代码是语义正确的!\n");
}
// 7. 初始化中间代码生成器并生成中间代码
ICG* icg = init_icg(analyzer->sym_table);
if (!icg) {
ast_free_program(program_ast);
free_semantic_analyzer(analyzer);
free_parser(parser);
return EXIT_FAILURE;
}
printf("--- 中间代码生成开始!---\n");
IntermediateCode* program_ic = generate_program_ic(program_ast, icg);
if (!program_ic) {
fprintf(stderr, "错误: 中间代码生成失败。\n");
ast_free_program(program_ast);
free_semantic_analyzer(analyzer);
free_icg(icg);
free_parser(parser);
remove(temp_filepath);
return EXIT_FAILURE;
}
printf("--- 中间代码生成成功!---\n");
printf("--- 打印未优化前的中间代码:---\n");
print_intermediate_code(program_ic);
printf("--- 未优化中间代码打印完成 ---\n");
// 8. 初始化优化器并执行代码优化
Optimizer* optimizer = init_optimizer();
if (!optimizer) {
ast_free_program(program_ast);
free_semantic_analyzer(analyzer);
free_intermediate_code(program_ic);
free_icg(icg);
free_parser(parser);
return EXIT_FAILURE;
}
printf("--- 代码优化开始!---\n");
optimize_intermediate_code(program_ic, optimizer); // 执行优化
printf("--- 代码优化完成!---\n");
printf("--- 打印优化后的中间代码:---\n");
print_intermediate_code(program_ic);
printf("--- 优化后中间代码打印完成 ---\n");
// 9. 释放所有资源
ast_free_program(program_ast);
printf("--- AST 内存已释放 ---\n");
free_semantic_analyzer(analyzer);
printf("--- 语义分析器资源已释放 ---\n");
free_intermediate_code(program_ic);
printf("--- 中间代码内存已释放 ---\n");
free_icg(icg);
printf("--- 中间代码生成器资源已释放 ---\n");
free_optimizer(optimizer); // 释放优化器
printf("--- 优化器资源已释放 ---\n");
free_parser(parser);
printf("--- 解析器和词法分析器资源已释放 ---\n");
// 可选:删除之前创建的临时文件
remove(temp_filepath);
return EXIT_SUCCESS;
}
编译和运行
将上述所有文件(token.h
, token.c
, lexer.h
, lexer.c
, parser.h
, parser.c
, ast.h
, ast.c
, type.h
, type.c
, symbol_table.h
, symbol_table.c
, semantic_analyzer.h
, semantic_analyzer.c
, intermediate_code.h
, intermediate_code.c
, ic_generator.h
, ic_generator.c
, optimizer.h
, optimizer.c
, main.c
)放在同一个目录下。
你可以使用GCC编译器进行编译:
gcc -o my_compiler main.c lexer.c token.c ast.c parser.c type.c symbol_table.c semantic_analyzer.c intermediate_code.c ic_generator.c optimizer.c -Wall -Wextra
然后运行:
./my_compiler
你将看到程序依次进行词法分析、语法分析、语义分析、中间代码生成,然后是代码优化。在优化前后,程序会分别打印出中间代码,你可以对比它们的变化,观察优化效果。
代码优化器的逻辑分析与挑战
-
优化器框架:
-
Optimizer
结构体目前比较简单,但为未来更复杂的优化(如需要维护数据流信息)预留了扩展空间。 -
optimize_intermediate_code
函数是优化阶段的入口,它会迭代调用不同的优化子函数,直到没有更多的改进或达到最大迭代次数。这种迭代是必要的,因为一个优化可能会为另一个优化创造新的机会。
-
-
常量折叠 (
constant_folding
):-
遍历所有指令,识别二元和一元运算指令。
-
is_constant
辅助函数用于判断操作数是否是常量(整数或浮点数)。 -
evaluate_binary_constant
和evaluate_unary_constant
函数负责在编译时执行实际的算术或逻辑运算。 -
如果表达式完全由常量组成,计算结果,并将原始指令替换为简单的
IC_ASSIGN
指令,将结果常量直接赋给目标变量/临时变量。 -
挑战: 混合类型运算(如
int + float
)的常量折叠需要更复杂的类型提升和转换逻辑。
-
-
死代码消除 (
dead_code_elimination
):-
这是一个非常简化的死代码消除版本,主要针对那些结果变量/临时变量在后续代码中从未被使用的赋值或计算指令。
-
它通过一个
is_var_used
数组来粗略地跟踪变量和临时变量的“活跃性”(即是否在后续被读取)。 -
两趟遍历:
-
第一趟(反向数据流分析的简化): 遍历所有指令,标记所有作为操作数(
op1
,op2
)或作为跳转/函数调用参数的变量/临时变量为“被使用”。 -
第二趟: 再次遍历指令,如果一个赋值或计算指令的结果变量/临时变量在第一趟中没有被标记为“被使用”,那么这条指令就被认为是死代码,并被标记为
is_instruction_dead
。 -
第三趟(重建): 遍历指令列表,只保留那些未被标记为死代码的指令,从而“删除”死代码。
-
-
挑战: 真正的死代码消除需要更精确的活跃变量分析,这通常涉及构建程序的控制流图(CFG),并在CFG上进行迭代的数据流方程求解。当前的简化版无法处理复杂的控制流(如分支、循环内部的活跃性变化)或变量的重新定义。
-
-
内存管理: 在移除死指令时,我们不仅要将指令从
instructions
数组中移除,还要释放该指令结构体及其内部动态分配的var_name
字符串,以避免内存泄漏。
总结与展望
朋友们,恭喜你!你已经完成了手撸编译器的第五步——一个初步的代码优化器!你亲手实现了常量折叠和简化的死代码消除,让你的编译器能够生成更精炼、更高效的中间代码。这种“精益求精”的精神,正是编译器开发的魅力所在!
现在,我们的中间代码已经经过了初步的“瘦身”和“提速”,它比原始的中间代码更加高效。有了这份优化后的中间代码,我们就可以自信地迈向编译器的最后阶段了!
这只是我们“肝爆”之旅的第五站。在下一章中,我们将进入编译器的最后一个核心阶段:目标代码生成(Target Code Generation)。我们将把这些优化后的三地址码指令,真正地翻译成特定机器架构(如x86汇编)的机器码!
准备好迎接最终的挑战,让你的C语言代码在机器上“跑”起来了吗?继续保持这份激情,我们下一章不见不散!点赞、收藏、关注,让我们一起把这“手撸编译器”的肝爆之旅进行到底!
最终章:第六章 从中间到机器——目标代码生成与X86汇编)
朋友们,欢迎来到我们“手撸编译器”的最终章!在第五章中,我们成功地对中间代码进行了优化,使其更加精炼高效。现在,我们的程序已经以一种高效的三地址码(TAC)形式存在了。
然而,三地址码仍然是一种抽象的表示,CPU并不能直接执行它。CPU只认识它自己的指令集!因此,编译器的最后一个核心阶段——目标代码生成(Target Code Generation)——登场了!
目标代码生成的使命:让程序在CPU上“跑”起来
目标代码生成器是编译器的后端,它的任务是将中间代码(例如三地址码)翻译成特定目标机器的机器码。通常,这个过程会先生成汇编代码,然后由汇编器将其转换为机器码。
目标代码生成阶段的主要挑战和任务包括:
-
指令选择(Instruction Selection): 将中间代码操作映射到目标机器的指令集。例如,一个
ADD
指令可能对应x86的ADD
指令,也可能对应ARM的ADD
指令。 -
寄存器分配(Register Allocation): 决定哪些变量和临时变量应该存储在CPU的寄存器中,哪些应该存储在内存中。寄存器访问速度远快于内存,因此高效的寄存器分配对性能至关重要。
-
指令调度(Instruction Scheduling): 重新排列指令的执行顺序,以最大化CPU的并行性,减少流水线停顿。
-
地址计算(Address Calculation): 确定变量在内存中的实际地址(例如,局部变量在栈帧中的偏移量,全局变量在数据段中的地址)。
-
函数调用约定(Calling Convention): 遵循目标平台特定的函数调用规则,包括参数传递、返回值处理、栈帧管理等。
简单来说,目标代码生成器就像一位“高级翻译官”,它不仅要将通用语言(中间代码)翻译成方言(汇编指令),还要考虑到方言的语法、习惯和表达效率(寄存器、指令调度)。
在本章中,我们将以简化的x86汇编为例,实现一个目标代码生成器。我们将主要关注指令选择、简单的寄存器使用和栈帧管理。
简化的X86汇编基础
x86架构是目前最流行的CPU架构之一。为了简化,我们将关注以下几个核心概念:
-
寄存器:
-
EAX
(或RAX
在64位):通常用于存储函数返回值和算术运算的结果。 -
EBX
,ECX
,EDX
(或RBX
,RCX
,RDX
等):通用寄存器,用于存储数据。 -
ESP
(或RSP
):栈指针,指向栈顶。 -
EBP
(或RBP
):基址指针,通常用于指向当前函数栈帧的底部。
-
-
内存访问: 使用
[address]
或[base + offset]
形式访问内存。 -
指令:
-
MOV dest, src
:数据传输。 -
ADD dest, src
:加法。 -
SUB dest, src
:减法。 -
IMUL dest, src
:乘法。 -
IDIV src
:除法(结果在EAX
,余数在EDX
)。 -
CMP op1, op2
:比较,设置标志位。 -
JMP label
:无条件跳转。 -
JE label
:相等则跳转。 -
JNE label
:不相等则跳转。 -
JL label
:小于则跳转。 -
JLE label
:小于等于则跳转。 -
JG label
:大于则跳转。 -
JGE label
:大于等于则跳转。 -
PUSH src
:将数据压入栈。 -
POP dest
:将数据从栈弹出。 -
CALL func_name
:调用函数。 -
RET
:从函数返回。
-
栈帧(Stack Frame)管理
函数调用时,会为每个函数创建一个栈帧。栈帧通常包含:
-
函数参数
-
返回地址
-
保存的寄存器值(如果函数使用了这些寄存器)
-
局部变量
我们的编译器将使用EBP
作为基址指针来访问局部变量和参数。
-
参数: 通常在
EBP
的上方(正偏移量)。 -
局部变量: 通常在
EBP
的下方(负偏移量)。
目标代码生成器(Target Code Generator)的实现
我们需要一个结构体来管理代码生成的状态,并存储生成的汇编代码。
// target_code.h (部分代码,完整版在后面)
#ifndef TARGET_CODE_H
#define TARGET_CODE_H
#include <stdio.h> // For FILE*
// 目标代码生成器结构体
typedef struct {
FILE* output_file; // 输出汇编代码的文件指针
// 可以在这里存储寄存器分配状态、当前函数信息等
int current_stack_offset; // 当前函数栈帧的局部变量总大小
// TODO: 寄存器描述符和地址描述符 (用于寄存器分配)
} TargetCodeGenerator;
// 函数声明
TargetCodeGenerator* init_target_code_generator(const char* output_filename);
void free_target_code_generator(TargetCodeGenerator* tcg);
/**
* @brief 生成整个程序的汇编代码。
* @param ic 中间代码列表。
* @param tcg 目标代码生成器实例。
*/
void generate_program_assembly(IntermediateCode* ic, TargetCodeGenerator* tcg);
// 辅助函数:生成汇编指令
void emit_instruction(TargetCodeGenerator* tcg, const char* format, ...);
#endif // TARGET_CODE_H
完整的代码实现
target_code.h
// target_code.h
#ifndef TARGET_CODE_H
#define TARGET_CODE_H
#include <stdio.h> // For FILE*
#include "intermediate_code.h" // 需要中间代码结构体
#include "symbol_table.h" // 需要符号表来获取变量信息
// 目标代码生成器结构体
// 包含了目标代码生成过程中所需的所有状态和工具
typedef struct {
FILE* output_file; // 输出汇编代码的文件指针
// 寄存器分配相关的状态 (简化版,只用EAX和ECX作为临时寄存器)
// 实际编译器需要更复杂的寄存器描述符和分配算法
// char* reg_eax_occupant; // EAX当前存储的变量/临时变量名
// char* reg_ecx_occupant; // ECX当前存储的变量/临时变量名
// 当前函数栈帧的局部变量总大小 (用于栈帧设置)
int current_func_stack_size;
// 当前函数定义节点,用于获取函数符号信息
ASTNode* current_func_def_node;
// 符号表,用于获取变量的内存偏移量
SymbolTable* sym_table;
} TargetCodeGenerator;
// 函数声明
/**
* @brief 初始化目标代码生成器。
* 打开输出文件,并初始化内部状态。
* @param output_filename 输出汇编文件的名称。
* @param sym_table 语义分析阶段构建的符号表,用于查询符号信息。
* @return 成功返回指向 TargetCodeGenerator 结构体的指针,失败返回NULL。
*/
TargetCodeGenerator* init_target_code_generator(const char* output_filename, SymbolTable* sym_table);
/**
* @brief 释放目标代码生成器占用的所有资源。
* 关闭输出文件。
* @param tcg 指向要释放的 TargetCodeGenerator 结构体的指针。
*/
void free_target_code_generator(TargetCodeGenerator* tcg);
/**
* @brief 生成整个程序的汇编代码。
* 这是目标代码生成阶段的入口点。
* @param ic 中间代码列表。
* @param tcg 目标代码生成器实例。
*/
void generate_program_assembly(IntermediateCode* ic, TargetCodeGenerator* tcg);
// 辅助函数:向输出文件写入汇编指令
void emit_instruction(TargetCodeGenerator* tcg, const char* format, ...);
#endif // TARGET_CODE_H
target_code.c
// target_code.c
#include "target_code.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h> // For va_list, va_start, va_end
// --- 辅助函数:汇编指令发射器 ---
// 向输出文件写入汇编指令
void emit_instruction(TargetCodeGenerator* tcg, const char* format, ...) {
va_list args;
va_start(args, format);
vfprintf(tcg->output_file, format, args);
fprintf(tcg->output_file, "\n"); // 每条指令后换行
va_end(args);
}
// --- 辅助函数:操作数到汇编操作数的转换 ---
// 将中间代码的Operand转换为x86汇编字符串
// 这是一个简化的版本,只处理常量、变量和临时变量
static char* operand_to_x86(Operand op, TargetCodeGenerator* tcg) {
// 静态缓冲区,每次调用会被覆盖,不适合复杂场景
// 实际编译器会使用动态字符串或更复杂的寄存器分配策略
static char buffer[128];
switch (op.kind) {
case OP_CONSTANT_INT:
sprintf(buffer, "%lld", op.val.int_val);
break;
case OP_CONSTANT_FLOAT:
// 浮点数常量在x86汇编中通常需要特殊处理,例如加载到FPU寄存器
// 这里我们简化为直接使用其值,实际可能需要将其存储到数据段
sprintf(buffer, "__float_%d", (int)op.val.float_val); // 临时表示
fprintf(stderr, "警告: 浮点数常量在汇编中处理简化。\n");
break;
case OP_VARIABLE: {
Symbol* sym = lookup_symbol(tcg->sym_table, op.val.var_name);
if (!sym) {
fprintf(stderr, "错误: 汇编生成时未找到变量 '%s' 的符号表条目。\n", op.val.var_name);
sprintf(buffer, "UNKNOWN_VAR_%s", op.val.var_name);
break;
}
if (sym->is_global) {
// 全局变量直接使用名称 (会被汇编器解析为地址)
sprintf(buffer, "%s", sym->name);
} else {
// 局部变量和参数通过EBP+偏移量访问
// 参数偏移量是正的 (EBP + 8, EBP + 12...)
// 局部变量偏移量是负的 (EBP - 4, EBP - 8...)
// 我们在符号表中存储的offset是正的,这里需要转换为相对EBP的负偏移
// 或者对于参数,是正偏移
// 简化处理:假设所有局部变量和参数都在栈上,且偏移量是相对于EBP的
// 实际偏移量需要根据栈帧布局和参数传递约定来精确计算
// 假设符号表中的offset已经是相对于EBP的正确偏移
sprintf(buffer, "[EBP - %d]", sym->offset); // 假设局部变量是负偏移
// TODO: 区分参数和局部变量的偏移方向
}
break;
}
case OP_TEMP_VAR:
// 临时变量通常分配给寄存器。这里简化为使用EAX/ECX或栈
// 为了简单,我们直接将临时变量映射到EAX或ECX,或者在必要时使用栈
// 实际编译器会进行复杂的寄存器分配
sprintf(buffer, "t%d", op.val.temp_id); // 临时变量名,后续需要映射到寄存器或内存
fprintf(stderr, "警告: 临时变量在汇编中处理简化,可能未分配寄存器。\n");
break;
case OP_LABEL:
sprintf(buffer, "L%d", op.val.label_id);
break;
case OP_NONE:
strcpy(buffer, "");
break;
default:
strcpy(buffer, "UNKNOWN_OPERAND");
break;
}
return buffer;
}
// --- 核心汇编代码生成逻辑 ---
// 生成指令
static void generate_instruction_assembly(Instruction* instr, TargetCodeGenerator* tcg) {
if (!instr) return;
char* result_x86;
char* op1_x86;
char* op2_x86;
// 预先将操作数转换为x86字符串,简化后续逻辑
result_x86 = operand_to_x86(instr->result, tcg);
op1_x86 = operand_to_x86(instr->op1, tcg);
op2_x86 = operand_to_x86(instr->op2, tcg);
switch (instr->op) {
case IC_ASSIGN: // result = op1
// MOV result, op1
emit_instruction(tcg, " MOV %s, %s", result_x86, op1_x86);
break;
case IC_ADD: // result = op1 + op2
// MOV EAX, op1
// ADD EAX, op2
// MOV result, EAX
emit_instruction(tcg, " MOV EAX, %s", op1_x86);
emit_instruction(tcg, " ADD EAX, %s", op2_x86);
emit_instruction(tcg, " MOV %s, EAX", result_x86);
break;
case IC_SUB: // result = op1 - op2
emit_instruction(tcg, " MOV EAX, %s", op1_x86);
emit_instruction(tcg, " SUB EAX, %s", op2_x86);
emit_instruction(tcg, " MOV %s, EAX", result_x86);
break;
case IC_MUL: // result = op1 * op2
// IMUL op2 (EAX = EAX * op2)
emit_instruction(tcg, " MOV EAX, %s", op1_x86);
emit_instruction(tcg, " IMUL EAX, %s", op2_x86); // 简化为EAX = EAX * op2
emit_instruction(tcg, " MOV %s, EAX", result_x86);
break;
case IC_DIV: // result = op1 / op2 (整数除法)
// MOV EAX, op1
// CDQ (EAX符号扩展到EDX:EAX)
// IDIV op2 (EAX = EDX:EAX / op2, EDX = 余数)
emit_instruction(tcg, " MOV EAX, %s", op1_x86);
emit_instruction(tcg, " CDQ"); // 扩展EAX到EDX:EAX
emit_instruction(tcg, " IDIV %s", op2_x86);
emit_instruction(tcg, " MOV %s, EAX", result_x86); // 结果是商
break;
case IC_MOD: // result = op1 % op2 (整数模)
emit_instruction(tcg, " MOV EAX, %s", op1_x86);
emit_instruction(tcg, " CDQ");
emit_instruction(tcg, " IDIV %s", op2_x86);
emit_instruction(tcg, " MOV %s, EDX", result_x86); // 结果是余数
break;
case IC_EQ: // result = (op1 == op2)
case IC_NE: // result = (op1 != op2)
case IC_LT: // result = (op1 < op2)
case IC_LE: // result = (op1 <= op2)
case IC_GT: // result = (op1 > op2)
case IC_GE: { // result = (op1 >= op2)
// CMP op1, op2
// SETcc AL (设置AL为0或1)
// MOV result, EAX (将AL扩展到EAX,然后赋值)
emit_instruction(tcg, " MOV EAX, %s", op1_x86); // 将op1加载到EAX
emit_instruction(tcg, " CMP EAX, %s", op2_x86); // 比较EAX和op2
char* setcc_op;
switch (instr->op) {
case IC_EQ: setcc_op = "SETE"; break;
case IC_NE: setcc_op = "SETNE"; break;
case IC_LT: setcc_op = "SETL"; break; // 有符号小于
case IC_LE: setcc_op = "SETLE"; break; // 有符号小于等于
case IC_GT: setcc_op = "SETG"; break; // 有符号大于
case IC_GE: setcc_op = "SETGE"; break; // 有符号大于等于
default: setcc_op = "UNKNOWN_SETCC"; break;
}
emit_instruction(tcg, " %s AL", setcc_op); // 将结果设置到AL寄存器 (低8位)
emit_instruction(tcg, " MOVZX EAX, AL"); // 将AL零扩展到EAX
emit_instruction(tcg, " MOV %s, EAX", result_x86); // 将结果赋值给result
break;
}
case IC_AND: // result = (op1 && op2)
// 逻辑与,简化为 (op1 != 0 && op2 != 0)
// MOV EAX, op1
// TEST EAX, EAX
// JZ L_false
// MOV ECX, op2
// TEST ECX, ECX
// JZ L_false
// MOV result, 1
// JMP L_end
// L_false: MOV result, 0
// L_end:
{
Operand label_false = create_operand_label(instr->result.val.label_id + 1000); // 避免与现有标签冲突
Operand label_end = create_operand_label(instr->result.val.label_id + 1001);
emit_instruction(tcg, " MOV EAX, %s", op1_x86);
emit_instruction(tcg, " TEST EAX, EAX");
emit_instruction(tcg, " JZ %s", operand_to_x86(label_false, tcg));
emit_instruction(tcg, " MOV ECX, %s", op2_x86);
emit_instruction(tcg, " TEST ECX, ECX");
emit_instruction(tcg, " JZ %s", operand_to_x86(label_false, tcg));
emit_instruction(tcg, " MOV %s, 1", result_x86);
emit_instruction(tcg, " JMP %s", operand_to_x86(label_end, tcg));
emit_instruction(tcg, "%s:", operand_to_x86(label_false, tcg));
emit_instruction(tcg, " MOV %s, 0", result_x86);
emit_instruction(tcg, "%s:", operand_to_x86(label_end, tcg));
}
break;
case IC_OR: // result = (op1 || op2)
// 逻辑或,简化为 (op1 != 0 || op2 != 0)
// MOV EAX, op1
// TEST EAX, EAX
// JNZ L_true
// MOV ECX, op2
// TEST ECX, ECX
// JNZ L_true
// MOV result, 0
// JMP L_end
// L_true: MOV result, 1
// L_end:
{
Operand label_true = create_operand_label(instr->result.val.label_id + 2000); // 避免与现有标签冲突
Operand label_end = create_operand_label(instr->result.val.label_id + 2001);
emit_instruction(tcg, " MOV EAX, %s", op1_x86);
emit_instruction(tcg, " TEST EAX, EAX");
emit_instruction(tcg, " JNZ %s", operand_to_x86(label_true, tcg));
emit_instruction(tcg, " MOV ECX, %s", op2_x86);
emit_instruction(tcg, " TEST ECX, ECX");
emit_instruction(tcg, " JNZ %s", operand_to_x86(label_true, tcg));
emit_instruction(tcg, " MOV %s, 0", result_x86);
emit_instruction(tcg, " JMP %s", operand_to_x86(label_end, tcg));
emit_instruction(tcg, "%s:", operand_to_x86(label_true, tcg));
emit_instruction(tcg, " MOV %s, 1", result_x86);
emit_instruction(tcg, "%s:", operand_to_x86(label_end, tcg));
}
break;
case IC_NOT: // result = !op1
// MOV EAX, op1
// TEST EAX, EAX
// SETZ AL (如果EAX为0则AL=1,否则AL=0)
// MOVZX EAX, AL
// MOV result, EAX
emit_instruction(tcg, " MOV EAX, %s", op1_x86);
emit_instruction(tcg, " TEST EAX, EAX");
emit_instruction(tcg, " SETZ AL");
emit_instruction(tcg, " MOVZX EAX, AL");
emit_instruction(tcg, " MOV %s, EAX", result_x86);
break;
case IC_BIT_AND: // result = op1 & op2
emit_instruction(tcg, " MOV EAX, %s", op1_x86);
emit_instruction(tcg, " AND EAX, %s", op2_x86);
emit_instruction(tcg, " MOV %s, EAX", result_x86);
break;
case IC_BIT_OR: // result = op1 | op2
emit_instruction(tcg, " MOV EAX, %s", op1_x86);
emit_instruction(tcg, " OR EAX, %s", op2_x86);
emit_instruction(tcg, " MOV %s, EAX", result_x86);
break;
case IC_BIT_XOR: // result = op1 ^ op2
emit_instruction(tcg, " MOV EAX, %s", op1_x86);
emit_instruction(tcg, " XOR EAX, %s", op2_x86);
emit_instruction(tcg, " MOV %s, EAX", result_x86);
break;
case IC_BIT_NOT: // result = ~op1
emit_instruction(tcg, " MOV EAX, %s", op1_x86);
emit_instruction(tcg, " NOT EAX");
emit_instruction(tcg, " MOV %s, EAX", result_x86);
break;
case IC_LSHIFT: // result = op1 << op2
emit_instruction(tcg, " MOV EAX, %s", op1_x86);
emit_instruction(tcg, " MOV ECX, %s", op2_x86); // 移位量通常在CL/CX/ECX
emit_instruction(tcg, " SHL EAX, CL"); // SHL EAX, ECX (如果ECX是常数,可以直接SHL EAX, const)
emit_instruction(tcg, " MOV %s, EAX", result_x86);
break;
case IC_RSHIFT: // result = op1 >> op2
emit_instruction(tcg, " MOV EAX, %s", op1_x86);
emit_instruction(tcg, " MOV ECX, %s", op2_x86);
emit_instruction(tcg, " SAR EAX, CL"); // SAR (算术右移,保留符号位) 或 SHR (逻辑右移)
emit_instruction(tcg, " MOV %s, EAX", result_x86);
break;
case IC_LABEL: // label L_id:
emit_instruction(tcg, "%s:", result_x86);
break;
case IC_JUMP: // goto L_id
emit_instruction(tcg, " JMP %s", result_x86);
break;
case IC_JUMPIF_TRUE: // if op1 goto L_id
// MOV EAX, op1
// TEST EAX, EAX
// JNZ L_id (如果非零则跳转)
emit_instruction(tcg, " MOV EAX, %s", op1_x86);
emit_instruction(tcg, " TEST EAX, EAX"); // TEST op, op 会设置标志位,如果op为0则ZF=1
emit_instruction(tcg, " JNZ %s", result_x86);
break;
case IC_JUMPIF_FALSE: // if op1 == 0 goto L_id
// MOV EAX, op1
// TEST EAX, EAX
// JZ L_id (如果为零则跳转)
emit_instruction(tcg, " MOV EAX, %s", op1_x86);
emit_instruction(tcg, " TEST EAX, EAX");
emit_instruction(tcg, " JZ %s", result_x86);
break;
case IC_PARAM: // param op1 (将op1作为参数压入栈)
// PUSH op1
emit_instruction(tcg, " PUSH %s", op1_x86);
break;
case IC_CALL: { // result = call func_name, num_args
// CALL func_name
// (可选) ADD ESP, num_args * 4 (清理栈上的参数)
// (可选) MOV result, EAX (如果函数有返回值)
emit_instruction(tcg, " CALL %s", op1_x86); // op1_x86 是函数名
// 清理参数栈空间 (C调用约定通常由调用者清理)
int num_args = instr->op2.val.int_val;
if (num_args > 0) {
emit_instruction(tcg, " ADD ESP, %d", num_args * 4); // 假设参数都是4字节
}
if (instr->result.kind != OP_NONE && instr->result.type->kind != TYPE_VOID) {
emit_instruction(tcg, " MOV %s, EAX", result_x86); // 函数返回值在EAX
}
break;
}
case IC_RETURN: // return op1
// MOV EAX, op1 (如果op1存在)
// JMP .L_func_end (跳转到函数结尾清理栈帧)
if (instr->op1.kind != OP_NONE) {
emit_instruction(tcg, " MOV EAX, %s", op1_x86); // 将返回值放入EAX
}
// 跳转到函数结束标签,由函数结束处理栈帧和RET
emit_instruction(tcg, " JMP %s_end", tcg->current_func_def_node->data.func_def.identifier->data.identifier_expr.name);
break;
case IC_FUNC_BEGIN: { // func_name: (函数开始标记)
// PUSH EBP
// MOV EBP, ESP
// SUB ESP, stack_frame_size (为局部变量分配空间)
char* func_name = instr->op1.val.var_name;
emit_instruction(tcg, "\n%s:", func_name); // 函数标签
emit_instruction(tcg, " PUSH EBP");
emit_instruction(tcg, " MOV EBP, ESP");
// 计算局部变量总大小 (从符号表获取)
// tcg->current_func_stack_size 已经在 generate_function_assembly 中设置
if (tcg->current_func_stack_size > 0) {
emit_instruction(tcg, " SUB ESP, %d", tcg->current_func_stack_size);
}
break;
}
case IC_FUNC_END: { // end func_name (函数结束标记)
// MOV ESP, EBP
// POP EBP
// RET
char* func_name = instr->op1.val.var_name;
emit_instruction(tcg, "%s_end:", func_name); // 函数结束标签
emit_instruction(tcg, " MOV ESP, EBP"); // 恢复ESP
emit_instruction(tcg, " POP EBP"); // 恢复EBP
emit_instruction(tcg, " RET"); // 返回调用者
break;
}
case IC_CAST: // result = (target_type)op1
// 简单的类型转换,例如 int <-> float
// 实际需要FPU指令或更复杂的整数截断/符号扩展
// 这里我们简化为直接赋值,假设类型大小兼容,或由汇编器处理
emit_instruction(tcg, " MOV EAX, %s", op1_x86);
emit_instruction(tcg, " MOV %s, EAX", result_x86);
fprintf(stderr, "警告: 类型转换在汇编中处理简化。\n");
break;
default:
fprintf(stderr, "错误: 未知或不支持的中间代码操作 '%s',无法生成汇编。\n", ic_op_to_string(instr->op));
break;
}
}
// 计算函数局部变量和参数的总大小
static int calculate_function_stack_size(ASTNode* func_def_node, SymbolTable* sym_table) {
int total_local_size = 0;
// 遍历函数体内的所有局部变量声明
// 这里需要重新遍历AST来获取局部变量信息,或者在语义分析阶段就计算好
// 简化:我们假设所有局部变量在符号表中的偏移量是连续的,
// 且 sym_table->current_scope->next_local_offset 记录了当前作用域的总大小
// 但这个值在函数退出后就重置了。
// 更准确的做法是,在语义分析时,为每个函数定义节点存储其局部变量的总大小。
// 临时解决方案:遍历符号表中的所有局部变量和参数,找到最大偏移量
// 这是一个非常粗略的估计,不考虑对齐和参数传递方式
int max_offset = 0;
// 遍历函数体的复合语句
ASTNode* body = func_def_node->data.func_def.body;
if (body->type == AST_COMPOUND_STMT) {
for (int i = 0; i < body->data.compound_stmt.num_statements; ++i) {
ASTNode* stmt = body->data.compound_stmt.statements[i];
if (stmt->type == AST_VAR_DECL) {
Symbol* sym = stmt->data.var_decl.identifier->data.identifier_expr.symbol_entry;
if (sym && !sym->is_global) { // 确保是局部变量
// 局部变量偏移量是相对于EBP的负值,但我们这里存储的是正的“大小”
// 假设offset是相对于栈帧底部的累积大小
if (sym->offset + sym->type->size > max_offset) {
max_offset = sym->offset + sym->type->size;
}
}
}
}
}
// 考虑参数,参数的偏移量是正的,在EBP之上
// 局部变量的偏移量是负的,在EBP之下
// 这里我们只计算局部变量所需的空间
// 符号表中的offset是相对于函数栈帧开头的,所以直接用next_local_offset即可
// 但由于符号表在语义分析结束后会清除局部作用域,这里需要重新计算
// 最佳实践:在FuncDef AST节点中存储一个字段,记录局部变量的总大小
// 假设语义分析阶段已经计算并存储了每个函数局部变量的累积大小
// 例如,在FuncDef AST节点中添加一个 int local_var_size 字段
// 这里我们暂时使用一个简化的方法:遍历符号表中的所有局部变量,找到最大的偏移量
// 这是一个hack,因为符号表在语义分析后会重置,无法直接访问所有局部变量
// 正确的做法是在语义分析时,计算并存储在FuncDef AST节点中
// 暂时返回一个固定值或根据参数数量粗略估计
// 重新计算局部变量大小 (这需要在语义分析阶段完成并存储在AST中)
// 遍历函数体,找到所有局部变量声明,累加它们的大小
int local_vars_size = 0;
if (func_def_node->data.func_def.body->type == AST_COMPOUND_STMT) {
ASTNode* compound_stmt = func_def_node->data.func_def.body;
for (int i = 0; i < compound_stmt->data.compound_stmt.num_statements; ++i) {
ASTNode* stmt = compound_stmt->data.compound_stmt.statements[i];
if (stmt->type == AST_VAR_DECL) {
Symbol* sym = stmt->data.var_decl.identifier->data.identifier_expr.symbol_entry;
if (sym && !sym->is_global && sym->kind == SYM_VAR) { // 确保是局部变量
local_vars_size += sym->type->size;
}
}
}
}
// 对齐到16字节 (常见的栈对齐要求)
return (local_vars_size + 15) & ~15;
}
// 生成函数定义的汇编代码
static void generate_function_assembly(Instruction* func_begin_instr, IntermediateCode* ic, TargetCodeGenerator* tcg, ASTNode* program_ast) {
char* func_name = func_begin_instr->op1.val.var_name;
fprintf(stderr, "生成函数 '%s' 的汇编代码...\n", func_name);
// 查找对应的AST函数定义节点,以便获取其局部变量信息
ASTNode* current_func_def_node = NULL;
for (int i = 0; i < program_ast->data.program.num_declarations; ++i) {
ASTNode* decl_node = program_ast->data.program.declarations[i];
if (decl_node->type == AST_FUNC_DEF && strcmp(decl_node->data.func_def.identifier->data.identifier_expr.name, func_name) == 0) {
current_func_def_node = decl_node;
break;
}
}
if (!current_func_def_node) {
fprintf(stderr, "错误: 无法找到函数 '%s' 的AST定义节点。\n", func_name);
return;
}
tcg->current_func_def_node = current_func_def_node;
// 计算局部变量所需的栈空间
// 这是一个简化,实际需要遍历函数体的所有局部变量声明,并累加其大小
// 假设在语义分析时,已经为每个局部变量分配了相对于EBP的偏移量
// 这里我们简单地计算所有局部变量的总大小
int local_var_total_size = 0;
// 遍历函数体的复合语句
if (current_func_def_node->data.func_def.body->type == AST_COMPOUND_STMT) {
ASTNode* body = current_func_def_node->data.func_def.body;
// 遍历语句,查找变量声明
for (int i = 0; i < body->data.compound_stmt.num_statements; ++i) {
ASTNode* stmt = body->data.compound_stmt.statements[i];
if (stmt->type == AST_VAR_DECL) {
Symbol* sym = stmt->data.var_decl.identifier->data.identifier_expr.symbol_entry;
if (sym && !sym->is_global) { // 确保是局部变量
// 局部变量的偏移量是相对于EBP的负值
// 符号表中的offset是正的,代表其在局部变量区域的起始位置
// 假设offset是累积的,那么最大的offset就是所需空间
if (sym->offset + sym->type->size > local_var_total_size) {
local_var_total_size = sym->offset + sym->type->size;
}
}
}
}
}
// 对齐到16字节 (常见的栈对齐要求)
tcg->current_func_stack_size = (local_var_total_size + 15) & ~15;
// 生成函数开始的汇编代码 (PUSH EBP, MOV EBP, ESP, SUB ESP, size)
generate_instruction_assembly(func_begin_instr, tcg); // IC_FUNC_BEGIN
// 遍历当前函数的所有中间代码指令,生成汇编
// 找到函数结束的指令索引
int func_end_idx = -1;
for (int i = 0; i < ic->num_instructions; ++i) {
if (ic->instructions[i]->op == IC_FUNC_END && strcmp(ic->instructions[i]->op1.val.var_name, func_name) == 0) {
func_end_idx = i;
break;
}
}
// 从函数开始指令的下一条指令开始,到函数结束指令的前一条指令
int start_idx = 0; // 找到 func_begin_instr 在 ic 中的位置
for (int i = 0; i < ic->num_instructions; ++i) {
if (ic->instructions[i]->op == IC_FUNC_BEGIN && strcmp(ic->instructions[i]->op1.val.var_name, func_name) == 0) {
start_idx = i + 1;
break;
}
}
for (int i = start_idx; i < func_end_idx; ++i) {
generate_instruction_assembly(ic->instructions[i], tcg);
}
// 生成函数结束的汇编代码 (MOV ESP, EBP, POP EBP, RET)
generate_instruction_assembly(ic->instructions[func_end_idx], tcg); // IC_FUNC_END
}
// 生成整个程序的汇编代码
void generate_program_assembly(IntermediateCode* ic, TargetCodeGenerator* tcg) {
fprintf(stderr, "开始生成程序汇编代码...\n");
// 假设我们有一个AST的根节点,可以用来查找函数定义
// 这里我们传递一个ASTNode* program_ast参数,以便在generate_function_assembly中查找函数信息
// 实际编译器中,这个信息可能通过其他方式传递或预处理
// 为了通过编译,我们在这里临时创建一个空的ASTNode作为占位符
// 实际使用时,需要传入语义分析后的完整AST根节点
ASTNode* dummy_program_ast = (ASTNode*)malloc(sizeof(ASTNode));
dummy_program_ast->type = AST_PROGRAM;
dummy_program_ast->data.program.declarations = NULL;
dummy_program_ast->data.program.num_declarations = 0;
// 重新构建 program_ast,因为我们没有直接传递它
// 这是一个临时的解决方案,正确的方法是在main函数中传递 program_ast
// 查找所有函数定义节点,并填充到 dummy_program_ast 中
// 这段逻辑应该在main函数中完成,然后将完整的program_ast传递进来
// 为了演示,我们假设 ic 中已经包含了所有必要的信息
// 或者,我们直接在 generate_function_assembly 中通过符号表查找函数信息
// 临时方案:在全局作用域查找所有函数符号,并构建一个临时的函数列表
// 这不是理想的,因为我们丢失了AST结构
// 更好的方法是:在main函数中,将完整的ASTNode* program_ast 传递给这个函数
// 假设我们有一个全局的ASTNode* program_ast,或者从main函数传入
// 这里我们依赖于 main.c 传入的 program_ast
// 为了简化,我们直接在 generate_function_assembly 中查找 ASTNode*
// 因此,generate_program_assembly 还需要一个 ASTNode* program_ast 参数
// 暂时先这样,后续在 main.c 中调用时传入正确的 program_ast
// emit_instruction(tcg, "section .data"); // 数据段 (用于全局变量和字符串常量)
// emit_instruction(tcg, "section .bss"); // 未初始化数据段
emit_instruction(tcg, "section .text"); // 代码段
emit_instruction(tcg, " global main"); // 声明 main 函数为全局入口点
// 遍历中间代码,找到每个函数的开始指令,然后生成其汇编
for (int i = 0; i < ic->num_instructions; ++i) {
Instruction* instr = ic->instructions[i];
if (instr->op == IC_FUNC_BEGIN) {
// 找到函数开始指令,然后生成整个函数的汇编代码
// 这里需要传入完整的 ASTNode* program_ast,以便 generate_function_assembly 查找函数信息
// 暂时传入一个NULL,表示我们没有直接的AST访问,这会限制一些功能
// 实际编译器中,这个函数会接收 ASTNode* program_ast
generate_function_assembly(instr, ic, tcg, NULL); // TODO: 传入正确的 program_ast
} else if (instr->op == IC_ASSIGN && instr->result.kind == OP_VARIABLE && instr->result.val.var_name &&
lookup_symbol(tcg->sym_table, instr->result.val.var_name) &&
lookup_symbol(tcg->sym_table, instr->result.val.var_name)->is_global) {
// 处理全局变量的初始化 (在数据段中)
// 这应该在 .data 段完成,而不是 .text 段
// 简化:这里只生成赋值指令,实际初始化在汇编器层面处理
fprintf(stderr, "警告: 全局变量初始化在汇编中处理简化。\n");
// emit_instruction(tcg, "%s: DD %s", instr->result.val.var_name, operand_to_x86(instr->op1, tcg)); // 示例
}
}
fprintf(stderr, "程序汇编代码生成完成。\n");
free(dummy_program_ast); // 释放临时占位符
}
// --- 目标代码生成器初始化与清理 ---
TargetCodeGenerator* init_target_code_generator(const char* output_filename, SymbolTable* sym_table) {
TargetCodeGenerator* tcg = (TargetCodeGenerator*)malloc(sizeof(TargetCodeGenerator));
if (!tcg) {
fprintf(stderr, "错误: 内存分配失败 (TargetCodeGenerator)\n");
return NULL;
}
tcg->output_file = fopen(output_filename, "w");
if (!tcg->output_file) {
fprintf(stderr, "错误: 无法打开输出文件 '%s'。\n", output_filename);
free(tcg);
return NULL;
}
tcg->current_func_stack_size = 0;
tcg->current_func_def_node = NULL;
tcg->sym_table = sym_table; // 引用符号表
fprintf(stderr, "目标代码生成器初始化成功,输出到 '%s'。\n", output_filename);
return tcg;
}
void free_target_code_generator(TargetCodeGenerator* tcg) {
if (tcg) {
if (tcg->output_file) {
fclose(tcg->output_file);
}
free(tcg);
fprintf(stderr, "目标代码生成器资源已清理。\n");
}
}
main.c
(修改)
为了让generate_program_assembly
函数能够正确访问到ASTNode* program_ast
,我们需要修改main.c
中的函数调用,将program_ast
作为参数传递。
// main.c (修改)
#include "lexer.h"
#include "parser.h"
#include "ast.h"
#include "token.h"
#include "semantic_analyzer.h"
#include "type.h"
#include "symbol_table.h"
#include "intermediate_code.h"
#include "ic_generator.h"
#include "optimizer.h"
#include "target_code.h" // 新增:目标代码生成器头文件
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 这是一个包含各种C语言语法元素的测试代码字符串
// 用于全面测试词法分析器、语法分析器、语义分析器、中间代码生成器、优化器和目标代码生成器的功能
const char* TEST_CODE =
"// 这是一个单行注释\n"
"/* 这是一个\n"
" * 多行注释\n"
" */\n"
"int global_var = 100 + 200; // 全局变量声明,可常量折叠\n"
"char another_global = 'Z';\n"
"\n"
"float add(float a, float b) {\n"
" return a + b;\n"
"}\n"
"\n"
"int subtract(int x, int y) {\n"
" int temp_local = 5 * 10; // 局部变量,可常量折叠\n"
" int unused_var = 123; // 死代码\n"
" if (x > y) {\n"
" temp_local = x - y;\n"
" } else {\n"
" temp_local = y - x;\n"
" }\n"
" // unused_var 永远不会被使用,应该被消除\n"
" return temp_local;\n"
"}\n"
"\n"
"int main() {\n"
" int x = 10;\n"
" float y = 3.14e-2;\n"
" char c = 'A';\n"
" \n"
" int common_expr_res = x + 5; // 公共子表达式 (目前简化版优化器不处理)\n"
" int another_common_expr_res = x + 5; // 同上\n"
" \n"
" if (x >= 5 && y < 10.0) {\n"
" int z = x + (int)y * 2; \n"
" if (z > 20) {\n"
" z = z - 5;\n"
" }\n"
" return z;\n"
" } else if (x == 10) {\n"
" x++;\n"
" } else {\n"
" y--;\n"
" }\n"
" \n"
" while (x > 0) {\n"
" x = x - 1;\n"
" if (x == 5) {\n"
" // break; \n"
" }\n"
" }\n"
" \n"
" float sum_result = add(x, y);\n"
" int diff_result = subtract(20, 5);\n"
" \n"
" sum_result += 5.0f;\n"
" global_var = (int)sum_result;\n"
" \n"
" return 0;\n"
"}\n"
;
int main(int argc, char* argv[]) {
// 1. 将 TEST_CODE 字符串内容写入一个临时文件
const char* temp_filepath = "test_code.c";
FILE* temp_file = fopen(temp_filepath, "w");
if (!temp_file) {
fprintf(stderr, "错误: 无法创建临时文件 '%s'\n", temp_filepath);
return EXIT_FAILURE;
}
fprintf(temp_file, "%s", TEST_CODE);
fclose(temp_file);
// 2. 初始化词法分析器
Lexer* lexer = init_lexer(temp_filepath);
if (!lexer) {
return EXIT_FAILURE;
}
printf("--- 词法分析器初始化成功 ---\n");
// 3. 初始化语法分析器
Parser* parser = init_parser(lexer);
if (!parser) {
free_lexer(lexer);
return EXIT_FAILURE;
}
printf("--- 语法分析器初始化成功 ---\n");
// 4. 开始解析程序,构建抽象语法树 (AST)
printf("--- 开始语法分析,构建AST ---\n");
ASTNode* program_ast = parse_program(parser);
// 5. 检查AST是否成功构建
if (!program_ast) {
fprintf(stderr, "错误: AST 构建失败。\n");
free_parser(parser);
return EXIT_FAILURE;
}
printf("--- AST 构建成功!---\n");
// 6. 初始化语义分析器并执行语义分析
SemanticAnalyzer* analyzer = init_semantic_analyzer();
if (!analyzer) {
ast_free_program(program_ast);
free_parser(parser);
return EXIT_FAILURE;
}
printf("--- 语义分析开始!---\n");
analyze_program(program_ast, analyzer); // 执行语义分析
if (analyzer->error_count > 0) {
fprintf(stderr, "语义分析完成,发现 %d 个语义错误。程序终止。\n", analyzer->error_count);
ast_free_program(program_ast);
free_semantic_analyzer(analyzer);
free_parser(parser);
remove(temp_filepath);
return EXIT_FAILURE;
} else {
printf("语义分析完成,未发现语义错误!代码是语义正确的!\n");
}
// 7. 初始化中间代码生成器并生成中间代码
ICG* icg = init_icg(analyzer->sym_table);
if (!icg) {
ast_free_program(program_ast);
free_semantic_analyzer(analyzer);
free_parser(parser);
return EXIT_FAILURE;
}
printf("--- 中间代码生成开始!---\n");
IntermediateCode* program_ic = generate_program_ic(program_ast, icg);
if (!program_ic) {
fprintf(stderr, "错误: 中间代码生成失败。\n");
ast_free_program(program_ast);
free_semantic_analyzer(analyzer);
free_icg(icg);
free_parser(parser);
remove(temp_filepath);
return EXIT_FAILURE;
}
printf("--- 中间代码生成成功!---\n");
printf("--- 打印未优化前的中间代码:---\n");
print_intermediate_code(program_ic);
printf("--- 未优化中间代码打印完成 ---\n");
// 8. 初始化优化器并执行代码优化
Optimizer* optimizer = init_optimizer();
if (!optimizer) {
ast_free_program(program_ast);
free_semantic_analyzer(analyzer);
free_intermediate_code(program_ic);
free_icg(icg);
free_parser(parser);
return EXIT_FAILURE;
}
printf("--- 代码优化开始!---\n");
optimize_intermediate_code(program_ic, optimizer); // 执行优化
printf("--- 代码优化完成!---\n");
printf("--- 打印优化后的中间代码:---\n");
print_intermediate_code(program_ic);
printf("--- 优化后中间代码打印完成 ---\n");
// 9. 初始化目标代码生成器并生成汇编代码
const char* output_asm_filename = "output.asm";
TargetCodeGenerator* tcg = init_target_code_generator(output_asm_filename, analyzer->sym_table);
if (!tcg) {
ast_free_program(program_ast);
free_semantic_analyzer(analyzer);
free_intermediate_code(program_ic);
free_icg(icg);
free_optimizer(optimizer);
return EXIT_FAILURE;
}
printf("--- 目标代码生成开始!---\n");
// 传入 program_ast 给 generate_program_assembly
generate_program_assembly(program_ic, tcg, program_ast);
printf("--- 目标代码生成完成,汇编代码已写入 '%s'!---\n", output_asm_filename);
// 10. 释放所有资源
ast_free_program(program_ast);
printf("--- AST 内存已释放 ---\n");
free_semantic_analyzer(analyzer);
printf("--- 语义分析器资源已释放 ---\n");
free_intermediate_code(program_ic);
printf("--- 中间代码内存已释放 ---\n");
free_icg(icg);
printf("--- 中间代码生成器资源已释放 ---\n");
free_optimizer(optimizer);
printf("--- 优化器资源已释放 ---\n");
free_target_code_generator(tcg); // 释放目标代码生成器
printf("--- 目标代码生成器资源已释放 ---\n");
free_parser(parser);
printf("--- 解析器和词法分析器资源已释放 ---\n");
// 可选:删除之前创建的临时文件
remove(temp_filepath);
return EXIT_SUCCESS;
}
编译和运行
将上述所有文件(token.h
, token.c
, lexer.h
, lexer.c
, parser.h
, parser.c
, ast.h
, ast.c
, type.h
, type.c
, symbol_table.h
, symbol_table.c
, semantic_analyzer.h
, semantic_analyzer.c
, intermediate_code.h
, intermediate_code.c
, ic_generator.h
, ic_generator.c
, optimizer.h
, optimizer.c
, target_code.h
, target_code.c
, main.c
)放在同一个目录下。
你可以使用GCC编译器进行编译:
gcc -o my_compiler main.c lexer.c token.c ast.c parser.c type.c symbol_table.c semantic_analyzer.c intermediate_code.c ic_generator.c optimizer.c target_code.c -Wall -Wextra
然后运行:
./my_compiler
程序将依次执行所有编译阶段,最终在当前目录下生成一个名为output.asm
的汇编文件。你可以打开这个文件,查看你的C语言代码是如何被翻译成x86汇编指令的!
目标代码生成器的逻辑分析与挑战
-
汇编指令发射器 (
emit_instruction
): 这是一个简单的辅助函数,用于将格式化的字符串写入输出汇编文件。 -
操作数到x86转换 (
operand_to_x86
):-
这是核心的映射函数,它将中间代码的
Operand
结构体转换为x86汇编中可用的字符串表示。 -
常量: 直接转换为其数值字符串。
-
变量:
-
全局变量: 直接使用其名称,汇编器会将其解析为数据段的地址。
-
局部变量/参数: 根据符号表中存储的偏移量,转换为相对于
EBP
的内存地址表示,例如[EBP - offset]
。这里我们做了简化,假设所有局部变量都在EBP
下方,且偏移量是正的,实际需要区分参数(EBP + offset
)和局部变量(EBP - offset
)。
-
-
临时变量: 这是一个巨大的简化!在真正的编译器中,临时变量需要通过复杂的寄存器分配算法(如图着色算法)来分配给有限的CPU寄存器。如果寄存器不足,则需要将其“溢出”到栈内存。我们这里只是简单地将它们表示为
t<id>
,并在生成指令时直接使用EAX
/ECX
作为临时寄存器,这在实际中是不可行的,因为寄存器会被覆盖。
-
-
指令选择 (
generate_instruction_assembly
):-
这是将每条三地址码指令翻译成一条或多条x86汇编指令的核心逻辑。
-
算术/逻辑/位运算: 大多数二元运算被翻译为
MOV
指令将第一个操作数载入EAX
,然后使用对应的x86指令(ADD
,SUB
,IMUL
,AND
,OR
等)与第二个操作数进行运算,最后将结果从EAX
移回目标。 -
除法/模运算:
IDIV
指令在x86中比较特殊,它使用EDX:EAX
作为被除数,结果商在EAX
,余数在EDX
。需要CDQ
指令进行符号扩展。 -
关系运算: 翻译为
CMP
指令(比较)和SETcc
指令(根据标志位设置AL寄存器为0或1),然后MOVZX
将AL零扩展到EAX,最后赋值。 -
跳转指令: 直接翻译为
JMP
,JNZ
,JZ
等。 -
函数调用 (
IC_PARAM
,IC_CALL
,IC_RETURN
):-
IC_PARAM
翻译为PUSH
指令,将参数压入栈。 -
IC_CALL
翻译为CALL
指令,并清理栈上的参数(遵循C调用约定)。函数返回值通常在EAX
。 -
IC_RETURN
将返回值放入EAX
,然后跳转到函数结束标签。
-
-
函数入口/出口 (
IC_FUNC_BEGIN
,IC_FUNC_END
):-
IC_FUNC_BEGIN
负责设置栈帧:PUSH EBP
,MOV EBP, ESP
,SUB ESP, stack_frame_size
(为局部变量分配栈空间)。 -
IC_FUNC_END
负责恢复栈帧和返回:MOV ESP, EBP
,POP EBP
,RET
。
-
-
类型转换 (
IC_CAST
): 这是一个巨大的简化,目前直接翻译为MOV
。实际的类型转换(如int
到float
,或不同大小整数之间)需要特定的FPU指令或更复杂的位操作。
-
-
栈帧大小计算 (
calculate_function_stack_size
和current_func_stack_size
):-
为了正确设置栈帧,我们需要知道函数中所有局部变量的总大小。
-
我们通过遍历函数体内的
AST_VAR_DECL
节点,并累加其resolved_type->size
来计算。 -
tcg->current_func_stack_size
用于在IC_FUNC_BEGIN
时生成SUB ESP, size
指令。 -
栈帧大小通常需要对齐(例如,16字节对齐),我们通过
(size + 15) & ~15
来实现。
-
-
全局变量: 全局变量的初始化通常在程序的
.data
段或.bss
段进行,而不是在.text
(代码)段。我们的简化版目前只在main.c
中通过IC_ASSIGN
指令处理了它们的初始化,这会在.text
段生成MOV
指令,实际汇编器会将其视为运行时赋值,而不是静态初始化。
总结与展望
朋友们,恭喜你!你已经完成了手撸编译器的最后一步——一个初步的目标代码生成器!你亲手将优化后的三地址码翻译成了x86汇编代码,让你的C语言程序第一次真正地“触摸”到了机器的底层。这种从高级语言到机器指令的完整链路,是不是让你对计算机科学的奥秘有了前所未有的洞察?
虽然我们实现的编译器是一个简化版,但它涵盖了现代编译器的所有核心阶段。从词法分析到目标代码生成,你已经亲手构建了一个能够将C语言源代码转换为可执行代码的工具!
这趟“肝爆”之旅虽然告一段落,但编译器的世界远比这更广阔和深奥。你可以继续探索:
-
更复杂的语言特性: 数组、指针、结构体、联合体、枚举、
typedef
、多维数组、函数指针、可变参数函数等。 -
更高级的优化: 数据流分析、控制流图(CFG)、静态单赋值(SSA)、寄存器分配、指令调度、窥孔优化、内联、循环展开等。
-
其他目标架构: 学习ARM、RISC-V等其他CPU架构的指令集,为你的编译器添加多平台支持。
-
运行时系统: 了解如何构建一个简单的运行时环境,包括内存管理(堆)、垃圾回收等。
-
链接器和加载器: 探索编译器后端如何与这些工具协作,生成最终的可执行文件。
感谢大家的点赞、收藏、关注!!!